Dunwu Blog

大道至简,知易行难

Java 容器之 Queue

Queue 简介

Queue 接口

Queue 接口定义如下:

1
public interface Queue<E> extends Collection<E> {}

Queue 是单端队列,只能从一端插入元素,另一端删除元素,实现上一般遵循 先进先出(FIFO) 规则。

Queue 扩展了 Collection 的接口,根据 因为容量问题而导致操作失败后处理方式的不同 可以分为两类方法: 一种在操作失败后会抛出异常,另一种则会返回特殊值。

Queue 接口 抛出异常 返回特殊值
插入队尾 add(E e) offer(E e)
删除队首 remove() poll()
查询队首元素 element() peek()

AbstractQueue 抽象类

AbstractQueue 类提供 Queue 接口的核心实现,以最大限度地减少实现 Queue 接口所需的工作。

AbstractQueue 抽象类定义如下:

1
2
3
public abstract class AbstractQueue<E>
extends AbstractCollection<E>
implements Queue<E> {}

Deque 接口

Deque 接口是 double ended queue 的缩写,即双端队列。Deque 继承 Queue 接口,并扩展支持在队列的两端插入和删除元素

所以提供了特定的方法,如:

大多数的实现对元素的数量没有限制,但这个接口既支持有容量限制的 deque,也支持没有固定大小限制的。

Deque 扩展了 Queue 的接口, 增加了在队首和队尾进行插入和删除的方法,同样根据失败后处理方式的不同分为两类:

Deque 接口 抛出异常 返回特殊值
插入队首 addFirst(E e) offerFirst(E e)
插入队尾 addLast(E e) offerLast(E e)
删除队首 removeFirst() pollFirst()
删除队尾 removeLast() pollLast()
查询队首元素 getFirst() peekFirst()
查询队尾元素 getLast() peekLast()

事实上,Deque 还提供有 push()pop() 等其他方法,可用于模拟栈。

ArrayDeque

ArrayDequeDeque 的顺序表实现。

ArrayDeque 用一个动态数组实现了栈和队列所需的所有操作。

LinkedList

LinkedListDeque 的链表实现。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class LinkedListQueueDemo {

public static void main(String[] args) {
//add()和remove()方法在失败的时候会抛出异常(不推荐)
Queue<String> queue = new LinkedList<>();

queue.offer("a"); // 入队
queue.offer("b"); // 入队
queue.offer("c"); // 入队
for (String q : queue) {
System.out.println(q);
}
System.out.println("===");
System.out.println("poll=" + queue.poll()); // 出队
for (String q : queue) {
System.out.println(q);
}
System.out.println("===");
System.out.println("element=" + queue.element()); //返回第一个元素
for (String q : queue) {
System.out.println(q);
}
System.out.println("===");
System.out.println("peek=" + queue.peek()); //返回第一个元素
for (String q : queue) {
System.out.println(q);
}
}

}

ArrayDequeLinkedList 都实现了 Deque 接口,两者都具有队列的功能,但两者有什么区别呢?

  • ArrayDeque 是基于可变长的数组和双指针来实现,而 LinkedList 则通过链表来实现。
  • ArrayDeque 不支持存储 NULL 数据,但 LinkedList 支持。
  • ArrayDeque 是在 JDK1.6 才被引入的,而LinkedList 早在 JDK1.2 时就已经存在。
  • ArrayDeque 插入时可能存在扩容过程, 不过均摊后的插入操作依然为 O(1)。虽然 LinkedList 不需要扩容,但是每次插入数据时均需要申请新的堆空间,均摊性能相比更慢。

从性能的角度上,选用 ArrayDeque 来实现队列要比 LinkedList 更好。此外,ArrayDeque 也可以用于实现栈。

PriorityQueue

PriorityQueue 是在 JDK1.5 中被引入的, 其与 Queue 的区别在于元素出队顺序是与优先级相关的,即总是优先级最高的元素先出队。

PriorityQueue 类定义如下:

1
2
public class PriorityQueue<E> extends AbstractQueue<E>
implements java.io.Serializable {}

PriorityQueue 要点:

  • PriorityQueue 实现了 Serializable,支持序列化。
  • PriorityQueue 类是无界优先级队列。
  • PriorityQueue 中的元素根据自然顺序或 Comparator 提供的顺序排序。
  • PriorityQueue 不接受 null 值元素。
  • PriorityQueue 不是线程安全的。
  • PriorityQueue 利用了二叉堆的数据结构来实现的,底层使用可变长的数组来存储数据
  • PriorityQueue 通过堆元素的上浮和下沉,实现了在 O(logn) 的时间复杂度内插入元素和删除堆顶元素。
  • PriorityQueue 默认是小顶堆,但可以接收一个 Comparator 作为构造参数,从而来自定义元素优先级的先后。

参考资料

Java NIO

NIO 简介

在传统的 Java I/O 模型(BIO)中,I/O 操作是以阻塞的方式进行的。也就是说,当一个线程执行一个 I/O 操作时,它会被阻塞直到操作完成。这种阻塞模型在处理多个并发连接时可能会导致性能瓶颈,因为需要为每个连接创建一个线程,而线程的创建和切换都是有开销的。

为了解决此问题,在 Java 1.4 中引入了非阻塞的 I/O 模型——NIO(New IO,也称为 Non-blocking IO)。NIO 对应 java.nio 包,提供了 ChannelSelectorBuffer 等抽象。它支持面向缓冲的,基于通道的 I/O 操作方法。

NIO 提供了与传统 BIO 模型中的 SocketServerSocket 相对应的 SocketChannelServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞 I/O 来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。

注意:使用 NIO 并不一定意味着高性能,它的性能优势主要体现在高并发和高延迟的网络环境下。当连接数较少、并发程度较低或者网络传输速度较快时,NIO 的性能并不一定优于传统的 BIO 。

NIO 的基本流程

通常来说 NIO 中的所有 IO 都是从 Channel(通道) 开始的。

  • 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。
  • 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。

NIO 核心组件

NIO 包含下面几个核心的组件:

  • Channel(通道) - Channel 是一个双向的、可读可写的数据传输通道,NIO 通过 Channel 来实现数据的输入输出。通道是一个抽象的概念,它可以代表文件、套接字或者其他数据源之间的连接。
  • Buffer(缓冲区) - NIO 读写数据都是通过缓冲区进行操作的。读操作的时候将 Channel 中的数据填充到 Buffer 中,而写操作时将 Buffer 中的数据写入到 Channel 中。
  • Selector(选择器) - 允许一个线程处理多个 Channel,基于事件驱动的 I/O 多路复用模型。所有的 Channel 都可以注册到 Selector 上,由 Selector 来分配线程来处理事件。

Channel(通道)

通道(Channel)是对 BIO 中的流的模拟,可以通过它读写数据。

Channel,类似在 Linux 之类操作系统上看到的文件描述符,是 NIO 中被用来支持批量式 IO 操作的一种抽象。

File 或者 Socket,通常被认为是比较高层次的抽象,而 Channel 则是更加操作系统底层的一种抽象,这也使得 NIO 得以充分利用现代操作系统底层机制,获得特定场景的性能优化,例如,DMA(Direct Memory Access)等。不同层次的抽象是相互关联的,我们可以通过 Socket 获取 Channel,反之亦然。

通道与流的不同之处在于:

  • 流是单向的 - 一个流只能单纯的负责读或写。
  • 通道是双向的 - 一个通道可以同时用于读写。

通道包括以下类型:

  • FileChannel:从文件中读写数据;
  • DatagramChannel:通过 UDP 读写网络中数据;
  • SocketChannel:通过 TCP 读写网络中数据;
  • ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。

Buffer(缓冲区)

**BIO 面向流 (Stream oriented),而 NIO 面向缓冲区 (Buffer oriented)**。

在 NIO 中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读缓冲区中的数据;在写入数据时,写入到缓冲区中。任何时候访问 NIO 中的数据,都是通过缓冲区进行操作。

Channel 读写的数据都必须先置于缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。

BIO 和 NIO 已经很好地集成了,java.io.* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如,java.io.* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。

事实上,每一种 Java 基本类型(除了 Boolean 类型)都对应有一种缓冲区:

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

缓冲区状态变量

  • capacity:最大容量;
  • position:当前已经读写的字节数;
  • limit:还可以读写的字节数。
  • mark:记录上一次 postion 的位置,默认是 0,算是一个便利性的考虑,往往不是必须
    的。

缓冲区状态变量的改变过程举例:

  1. 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 8。capacity 变量不会改变,下面的讨论会忽略它。
  2. 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 移动设置为 5,limit 保持不变。
  3. 在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0。
  4. 从缓冲区中取 4 个字节到输出缓冲中,此时 position 设为 4。
  5. 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。

文件 NIO 示例

以下展示了使用 NIO 快速复制文件的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public static void fastCopy(String src, String dist) throws IOException {

/* 获得源文件的输入字节流 */
FileInputStream fin = new FileInputStream(src);

/* 获取输入字节流的文件通道 */
FileChannel fcin = fin.getChannel();

/* 获取目标文件的输出字节流 */
FileOutputStream fout = new FileOutputStream(dist);

/* 获取输出字节流的通道 */
FileChannel fcout = fout.getChannel();

/* 为缓冲区分配 1024 个字节 */
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

while (true) {

/* 从输入通道中读取数据到缓冲区中 */
int r = fcin.read(buffer);

/* read() 返回 -1 表示 EOF */
if (r == -1) {
break;
}

/* 切换读写 */
buffer.flip();

/* 把缓冲区的内容写入输出文件中 */
fcout.write(buffer);

/* 清空缓冲区 */
buffer.clear();
}
}

DirectBuffer

NIO 还提供了一个可以直接访问物理内存的类 DirectBuffer。普通的 Buffer 分配的是 JVM 堆内存,而 DirectBuffer 是直接分配物理内存。

数据要输出到外部设备,必须先从用户空间复制到内核空间,再复制到输出设备,而 DirectBuffer 则是直接将步骤简化为从内核空间复制到外部设备,减少了数据拷贝。

这里拓展一点,由于 DirectBuffer 申请的是非 JVM 的物理内存,所以创建和销毁的代价很高。DirectBuffer 申请的内存并不是直接由 JVM 负责垃圾回收,但在 DirectBuffer 包装类被回收时,会通过 Java 引用机制来释放该内存块。

Selector(选择器)

NIO 常常被叫做非阻塞 IO,主要是因为 NIO 在网络通信中的非阻塞特性被广泛使用。

Selector 是 Java NIO 编程的基础。用于检查一个或多个 NIO Channel 的状态是否处于可读、可写。

NIO 实现了 IO 多路复用中的 Reactor 模型

  • 一个线程(Thread)使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件(accpetread,如果某个 Channel 上面发生监听事件,这个 Channel 就处于就绪状态,然后进行 I/O 操作。

  • 通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel,找到 IO 事件已经到达的 Channel 执行。

  • 因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件具有更好的性能。

需要注意的是,只有 SocketChannel 才能配置为非阻塞,而 FileChannel 不能,因为 FileChannel 配置非阻塞也没有意义。

目前操作系统的 I/O 多路复用机制都使用了 epoll,相比传统的 select 机制,epoll 没有最大连接句柄 1024 的限制。所以 Selector 在理论上可以轮询成千上万的客户端。

创建选择器

1
Selector selector = Selector.open();

将通道注册到选择器上

1
2
3
ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.register(selector, SelectionKey.OP_ACCEPT);

通道必须配置为非阻塞模式,否则使用选择器就没有任何意义了,因为如果通道在某个事件上被阻塞,那么服务器就不能响应其它事件,必须等待这个事件处理完毕才能去处理其它事件,显然这和选择器的作用背道而驰。

在将通道注册到选择器上时,还需要指定要注册的具体事件,主要有以下几类:

  • SelectionKey.OP_CONNECT
  • SelectionKey.OP_ACCEPT
  • SelectionKey.OP_READ
  • SelectionKey.OP_WRITE

它们在 SelectionKey 的定义如下:

1
2
3
4
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;

可以看出每个事件可以被当成一个位域,从而组成事件集整数。例如:

1
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

监听事件

1
int num = selector.select();

使用 select() 来监听到达的事件,它会一直阻塞直到有至少一个事件到达。

获取到达的事件

1
2
3
4
5
6
7
8
9
10
11
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// ...
} else if (key.isReadable()) {
// ...
}
keyIterator.remove();
}

事件循环

因为一次 select() 调用不能处理完所有的事件,并且服务器端有可能需要一直监听事件,因此服务器端处理事件的代码一般会放在一个死循环内。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
while (true) {
int num = selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// ...
} else if (key.isReadable()) {
// ...
}
keyIterator.remove();
}
}

套接字 NIO 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public class NIOServer {

public static void main(String[] args) throws IOException {

Selector selector = Selector.open();

ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.register(selector, SelectionKey.OP_ACCEPT);

ServerSocket serverSocket = ssChannel.socket();
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
serverSocket.bind(address);

while (true) {

selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();

while (keyIterator.hasNext()) {

SelectionKey key = keyIterator.next();

if (key.isAcceptable()) {

ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel();

// 服务器会为每个新连接创建一个 SocketChannel
SocketChannel sChannel = ssChannel1.accept();
sChannel.configureBlocking(false);

// 这个新连接主要用于从客户端读取数据
sChannel.register(selector, SelectionKey.OP_READ);

} else if (key.isReadable()) {

SocketChannel sChannel = (SocketChannel) key.channel();
System.out.println(readDataFromSocketChannel(sChannel));
sChannel.close();
}

keyIterator.remove();
}
}
}

private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException {

ByteBuffer buffer = ByteBuffer.allocate(1024);
StringBuilder data = new StringBuilder();

while (true) {

buffer.clear();
int n = sChannel.read(buffer);
if (n == -1) {
break;
}
buffer.flip();
int limit = buffer.limit();
char[] dst = new char[limit];
for (int i = 0; i < limit; i++) {
dst[i] = (char) buffer.get(i);
}
data.append(dst);
buffer.clear();
}
return data.toString();
}
}
1
2
3
4
5
6
7
8
9
10
public class NIOClient {

public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8888);
OutputStream out = socket.getOutputStream();
String s = "hello world";
out.write(s.getBytes());
out.close();
}
}

内存映射文件

内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。

向内存映射文件写入可能是危险的,只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。

下面代码行将文件的前 1024 个字节映射到内存中,map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行映射。

1
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);

NIO vs. BIO

BIO 与 NIO 最重要的区别是数据打包和传输的方式。**BIO 面向流 (Stream oriented),而 NIO 面向缓冲区 (Buffer oriented)**。

  • 面向流的 BIO 一次处理一个字节数据:一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责复杂处理机制的一部分。不利的一面是,面向流的 I/O 通常相当慢。
  • 面向块的 NIO 一次处理一个数据块,按块处理数据比按流处理数据要快得多。但是面向块的 NIO 缺少一些面向流的 BIO 所具有的优雅性和简单性。

BIO 模式:

img

NIO 模式:

img

参考资料

监控工具对比

监控工具发展史

img

监控工具比对

特性对比

img

生态对比

img

技术选型

  • Zipkin 欠缺 APM 报表能力,不推荐。
  • 企业级,推荐 CAT
  • 关注和试点 SkyWalking。

用好调用链监控,需要订制化、自研能力。

参考资料

CAT、Zipkin 和 SkyWalking 该如何选型?

CAT 快速入门

CAT 简介

CAT(Central Application Tracking),是基于 Java 开发的分布式实时监控系统。CAT 在基础存储、高性能通信、大规模在线访问、服务治理、实时监控、容器化及集群智能调度等领域提供业界领先的、统一的解决方案。CAT 目前在美团的产品定位是应用层的统一监控组件,基本接入了美团所有核心应用,在中间件(RPC、数据库、缓存、MQ 等)框架中得到广泛应用,为各业务线提供系统的性能指标、健康状况、实时告警等。

CAT 的优势

  • 实时处理:信息的价值会随时间锐减,尤其是事故处理过程中
  • 全量数据:最开始的设计目标就是全量采集,全量的好处有很多
  • 高可用:所有应用都倒下了,需要监控还站着,并告诉工程师发生了什么,做到故障还原和问题定位
  • 故障容忍:CAT 本身故障不应该影响业务正常运转,CAT 挂了,应用不该受影响,只是监控能力暂时减弱
  • 高吞吐:要想还原真相,需要全方位地监控和度量,必须要有超强的处理吞吐能力
  • 可扩展:支持分布式、跨 IDC 部署,横向扩展的监控系统

支持的消息类型

CAT 监控系统将每次 URL、Service 的请求内部执行情况都封装为一个完整的消息树、消息树可能包括 Transaction、Event、Heartbeat、Metric 等信息。

  • Transaction 适合记录跨越系统边界的程序访问行为,比如远程调用,数据库调用,也适合执行时间较长的业务逻辑监控,Transaction 用来记录一段代码的执行时间和次数
  • Event 用来记录一件事发生的次数,比如记录系统异常,它和 transaction 相比缺少了时间的统计,开销比 transaction 要小
  • Heartbeat 表示程序内定期产生的统计信息, 如 CPU 利用率, 内存利用率, 连接池状态, 系统负载等
  • Metric 用于记录业务指标、指标可能包含对一个指标记录次数、记录平均值、记录总和,业务指标最低统计粒度为 1 分钟

img

CAT 部署

Cat 部署可以参考 官方 Wiki - 服务端部署 ,非常详细,不赘述。

CAT 报表

与其他监控工具(如 Zipkin、SkyWalking)相比,CAT 的报表功能最丰富。支持以下报表类型:

  • Transaction 报表 - 一段代码运行时间、次数,比如 URL、Cache、SQL 执行次数和响应时间
  • Event 报表 - 一行代码运行次数,比如出现一个异常
  • Problem 报表 - 根据 Transaction/Event 数据分析出来系统可能出现的异常,包括访问较慢的程序等
  • Heartbeat 报表 - JVM 内部一些状态信息,比如 Memory,Thread 等
  • Business 报表 - 业务监控报表,比如订单指标,支付等业务指标

CAT 配置

CAT 提供了以下配置:

  • 项目配置 包括项目基本信息、机器分组配置
  • 告警配置 包括基本告警配置、告警规则、以及具体告警配置
  • 全局配置 包括服务端配置、消息采样配置、客户端路由
  • 业务指标 包括业务监控配置、业务标签配置

CAT 架构

CAT 主要分为三个模块:

  • cat-client - 提供给业务以及中间层埋点的底层 SDK。
  • cat-consumer - 用于实时分析从客户端的提供的数据。
  • cat-home - 作为用户提供给用户的展示的控制端。

在实际开发和部署中,cat-consumer 和 cat-home 是部署在一个 jvm 内部,每个 CAT 服务端都可以作为 consumer 也可以作为 home,这样既能减少整个 CAT 层级结构,也可以增加整个系统稳定性。

img

上图是 CAT 目前多机房的整体结构图:

  • 路由中心是根据应用所在机房信息来决定客户端上报的 CAT 服务端地址
  • 每个机房内部都有的独立的原始信息存储集群 HDFS
  • cat-home 可以部署在一个机房也可以部署在多个机房,在做报表展示的时候,cat-home 会从 cat-consumer 中进行跨机房的调用,将所有的数据合并展示给用户
  • 实际过程中,cat-consumer、cat-home 以及路由中心都是部署在一起,每个服务端节点都可以充当任何一个角色

参考资料

时间服务器 - NTP

NTP 简介

网络时间协议(英语:Network Time Protocol,缩写:NTP)是在数据网络潜伏时间可变的计算机系统之间通过分组交换进行时钟同步的一个网络协议,位于 OSI 模型的应用层。自 1985 年以来,NTP 是目前仍在使用的最古老的互联网协议之一。NTP 由特拉华大学的 David L. Mills(英语:David L. Mills)设计。

NTP 意图将所有参与计算机的协调世界时(UTC)时间同步到几毫秒的误差内

NTP 要点:

  • 地球共有 24 个时区,而以格林威治时间 (GMT) 为标准时间;
  • 中国本地时间为 GMT +8 小时;
  • 最准确的时间为使用原子钟 (Atomic clock) 所计算的,例如 UTC (Coordinated Universal Time) 就是一例;
  • Linux 系统本来就有两种时间,一种是 Linux 以 1970/01/01 开始计数的系统时间,一种则是 BIOS 记载的硬件时间;
  • Linux 可以透过网络校时,最常见的网络校时为使用 NTP 服务器,这个服务启动在 udp port 123
  • 时区档案主要放置于 /usr/share/zoneinfo/ 目录下,而本地时区则参考 /etc/localtime
  • NTP 服务器为一种阶层式的服务,所以 NTP 服务器本来就会与上层时间服务器作时间的同步化, 因此 nptdntpdate 两个指令不可同时使用;
  • NTP 服务器的联机状态可以使用 ntpstatntpq -p 来查询;
  • NTP 提供的客户端软件为 ntpdate 这个指令;
  • 在 Linux 下想要手动处理时间时,需以 date 设定时间后,以 hwclock -w 来写入 BIOS 所记录的时间。
  • NTP 服务器之间的时间误差不可超过 1000 秒,否则 NTP 服务会自动关闭。

更多 NTP 详情可以参考:鸟哥的 Linux 私房菜– NTP 时间服务器

ntpd 服务

环境:CentOS

yum 安装

CentOS 安装 NTP 很简单,执行以下命令即可:

1
yum -y install ntp

ntpd 配置

ntp 的配置文件路径为: /etc/ntp.conf ,参考配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1. 先处理权限方面的问题,包括放行上层服务器以及开放区网用户来源:
# restrict default kod nomodify notrap nopeer noquery # 拒绝 IPv4 的用户
# restrict -6 default kod nomodify notrap nopeer noquery # 拒绝 IPv6 的用户
restrict default nomodify notrap nopeer noquery
#restrict 192.168.100.0 mask 255.255.255.0 nomodify # 放行同局域网来源(根据网关和子网掩码决定)
restrict 127.0.0.1 # 默认值,放行本机 IPv4 来源
restrict ::1 # 默认值,放行本机 IPv6 来源

# 2. 设定 NTP 主机来源
# 注释掉默认 NTP 来源
# server 0.centos.pool.ntp.org iburst
# server 1.centos.pool.ntp.org iburst
# server 2.centos.pool.ntp.org iburst
# server 3.centos.pool.ntp.org iburst
# 设置国内 NTP 来源
server cn.pool.ntp.org prefer # 以这个主机为优先
server ntp1.aliyun.com
server ntp.sjtu.edu.cn

# 3. 预设时间差异分析档案与暂不用到的 keys 等,不需要更改它:
driftfile /var/lib/ntp/drift
keys /etc/ntp/keys
includefile /etc/ntp/crypto/pw

注意:如果更改配置,必须重启 NTP 服务(systemctl restart ntpd)才能生效。

放开防火墙限制

NTP 服务的端口是 123,使用的是 udp 协议,所以 NTP 服务器的防火墙必须对外开放 udp 123 这个端口。

如果防火墙使用 **iptables**,执行以下命令:

1
iptables -A INPUT -p UDP -i eth0 -s 192.168.0.0/24 --dport 123 -j ACCEPT

如果防火墙使用 **firewalld**,执行以下命令:

1
firewall-cmd --zone=public --add-port=123/udp --permanent

ntpd 服务命令

1
2
3
4
5
6
7
systemctl enable ntpd.service  # 开启服务(开机自动启动服务)
systemctl disable ntpd.service # 关闭服务(开机不会自动启动服务)
systemctl start ntpd.service # 启动服务
systemctl stop ntpd.service # 停止服务
systemctl restart ntpd.service # 重启服务
systemctl reload ntpd.service # 重新载入配置
systemctl status ntpd.service # 查看服务状态

查看 ntp 服务状态

验证 NTP 服务正常工作

执行 ntpstat 可以查看 ntp 服务器有无和上层 ntp 连通,,如果成功,可以看到类似以下的内容:

1
2
3
4
$ ntpstat
synchronised to NTP server (5.79.108.34) at stratum 3
time correct to within 1129 ms
polling server every 64 s

查看 ntp 服务器与上层 ntp 的状态

1
2
3
4
5
6
ntpq -p
remote refid st t when poll reach delay offset jitter
==============================================================================
*ntp1.ams1.nl.le 130.133.1.10 2 u 36 64 367 230.801 5.271 2.791
120.25.115.20 10.137.53.7 2 u 33 64 377 25.930 15.908 3.168
time.cloudflare 10.21.8.251 3 u 31 64 367 251.109 16.976 3.264

ntpdate 命令

注意:NTP 服务器为一种阶层式的服务,所以 NTP 服务器本来就会与上层时间服务器作时间的同步化, 因此 nptdntpdate 两个指令不可同时使用。

手动执行时间同步

ntpdate 命令是 NTP 的客户端软件,它可以用于请求时间同步。

语法:

1
/usr/sbin/ntpdate <ntp_server>

ntp_server 可以从 [国内 NTP 服务器](#国内 NTP 服务器) 中选择。

示例:

1
2
$ ntpdate cn.pool.ntp.org
11 Feb 10:47:12 ntpdate[30423]: step time server 84.16.73.33 offset -49.894774 sec

自动定时同步时间

如果需要自动定时同步时间,可以利用 Crontab 工具。本质就是用 crontab 定时执行一次手动时间同步命令 ntp。

示例:执行如下命令,就可以在每天凌晨 3 点同步系统时间:

1
2
echo "0 3 * * * /usr/sbin/ntpdate cn.pool.ntp.org" >> /etc/crontab # 修改 crond 服务配置
systemctl restart crond # 重启 crond 服务以生效

四、国内 NTP 服务器

以下 NTP 服务器搜集自网络:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cn.pool.ntp.org  # 最常用的国内NTP服务器,参考:https://www.ntppool.org/zh/use.html
cn.ntp.org.cn # 中国
edu.ntp.org.cn # 中国教育网
ntp1.aliyun.com # 阿里云
ntp2.aliyun.com # 阿里云
ntp.sjtu.edu.cn # 上海交通大学
s1a.time.edu.cn # 北京邮电大学
s1b.time.edu.cn # 清华大学
s1c.time.edu.cn # 北京大学
s1d.time.edu.cn # 东南大学
s1e.time.edu.cn # 清华大学
s2a.time.edu.cn # 清华大学
s2b.time.edu.cn # 清华大学
s2c.time.edu.cn # 北京邮电大学
s2d.time.edu.cn # 西南地区网络中心
s2e.time.edu.cn # 西北地区网络中心
s2f.time.edu.cn # 东北地区网络中心
s2g.time.edu.cn # 华东南地区网络中心
s2h.time.edu.cn # 四川大学网络管理中心
s2j.time.edu.cn # 大连理工大学网络中心
s2k.time.edu.cn # CERNET桂林主节点

参考资料

防火墙 - Firewalld

firewalld 服务命令

1
2
3
4
5
6
7
systemctl enable firewalld.service  # 开启服务(开机自动启动服务)
systemctl disable firewalld.service # 关闭服务(开机不会自动启动服务)
systemctl start firewalld.service # 启动服务
systemctl stop firewalld.service # 停止服务
systemctl restart firewalld.service # 重启服务
systemctl reload firewalld.service # 重新载入配置
systemctl status firewalld.service # 查看服务状态

firewall-cmd 命令

firewall-cmd 命令用于配置防火墙。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
firewall-cmd --version                    # 查看版本
firewall-cmd --help # 查看帮助
firewall-cmd --state # 显示状态
firewall-cmd --reload # 更新防火墙规则
firewall-cmd --get-active-zones # 查看区域信息
firewall-cmd --get-zone-of-interface=eth0 # 查看指定接口所属区域
firewall-cmd --panic-on # 拒绝所有包
firewall-cmd --panic-off # 取消拒绝状态
firewall-cmd --query-panic # 查看是否拒绝

firewall-cmd --zone=public --list-ports # 查看所有打开的端口
firewall-cmd --zone=public --query-port=80/tcp # 查看是否有开放的 80 TCP 端口
firewall-cmd --zone=public --add-port=8080/tcp --permanent # 添加开放端口(--permanent永久生效,没有此参数重启后失效)
firewall-cmd --zone=public --remove-port=80/tcp --permanent # 永久删除开放的 80 TCP 端口

参考资料

定时任务 - crontab

环境:CentOS

通过 crontab 命令,我们可以在固定的间隔时间执行指定的系统指令或 shell script 脚本。时间间隔的单位可以是分钟、小时、日、月、周及以上的任意组合。这个命令非常适合周期性的日志分析或数据备份等工作。

crond 服务

Linux 通过 crond 服务来支持 crontab。

检查 crond 服务

使用 systemctl list-unit-files 命令确认 crond 服务是否已安装。

1
2
$ systemctl list-unit-files | grep crond
crond.service enabled

如果为 enabled,表示服务正运行。

crond 服务命令

开机自动启动 crond 服务:chkconfig crond on

或者,按以下命令手动启动:

1
2
3
4
5
6
7
systemctl enable crond.service  # 开启服务(开机自动启动服务)
systemctl disable crond.service # 关闭服务(开机不会自动启动服务)
systemctl start crond.service # 启动服务
systemctl stop crond.service # 停止服务
systemctl restart crond.service # 重启服务
systemctl reload crond.service # 重新载入配置
systemctl status crond.service # 查看服务状态

crontab

crontab 命令

crontab 命令格式如下:

1
crontab [-u user] file crontab [-u user] [ -e | -l | -r ]

说明:

  • -u user:用来设定某个用户的 crontab 服务;
  • file:file 是命令文件的名字,表示将 file 做为 crontab 的任务列表文件并载入 crontab。如果在命令行中没有指定这个文件,crontab 命令将接受标准输入(键盘)上键入的命令,并将它们载入 crontab。
  • -e:编辑某个用户的 crontab 文件内容。如果不指定用户,则表示编辑当前用户的 crontab 文件。
  • -l:显示某个用户的 crontab 文件内容,如果不指定用户,则表示显示当前用户的 crontab 文件内容。
  • -r:从/var/spool/cron 目录中删除某个用户的 crontab 文件,如果不指定用户,则默认删除当前用户的 crontab 文件。
  • -i:在删除用户的 crontab 文件时给确认提示。

有两种方法写入定时任务:

  • 在命令行输入:crontab -e 然后添加相应的任务,存盘退出。
  • 直接编辑 /etc/crontab 文件,即 vi /etc/crontab,添加相应的任务。

crontab 文件

crontab 要执行的定时任务都被保存在 /etc/crontab 文件中。

crontab 的文件格式如下:

img

标准字段

逗号用于分隔列表。例如,在第 5 个字段(星期几)中使用 MON,WED,FRI 表示周一、周三和周五。

连字符定义范围。例如,2000-2010 表示 2000 年至 2010 年期间的每年,包括 2000 年和 2010 年。

除非用反斜杠()转义,否则命令中的**百分号(%)**会被替换成换行符,第一个百分号后面的所有数据都会作为标准输入发送给命令。

字段 是否必填 允许值 允许特殊字符
Minutes 0–59 *,-
Hours 0–23 *,-
Day of month 1–31 *,-
Month 1–12 or JAN–DEC *,-
Day of week 0–6 or SUN–SAT *,-

/etc/crontab 文件示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root

# For details see man 4 crontabs

# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed

# 每两个小时以root身份执行 /home/hello.sh 脚本
0 */2 * * * root /home/hello.sh

crontab 实例

实例 1:每 1 分钟执行一次 myCommand

1
* * * * * myCommand

实例 2:每小时的第 3 和第 15 分钟执行

1
3,15 * * * * myCommand

实例 3:在上午 8 点到 11 点的第 3 和第 15 分钟执行

1
3,15 8-11 * * * myCommand

实例 4:每隔两天的上午 8 点到 11 点的第 3 和第 15 分钟执行

1
3,15 8-11 */2  *  * myCommand

实例 5:每周一上午 8 点到 11 点的第 3 和第 15 分钟执行

1
3,15 8-11 * * 1 myCommand

实例 6:每晚的 21:30 重启 smb

1
30 21 * * * /etc/init.d/smb restart

实例 7:每月 1、10、22 日的 4 : 45 重启 smb

1
45 4 1,10,22 * * /etc/init.d/smb restart

实例 8:每周六、周日的 1 : 10 重启 smb

1
10 1 * * 6,0 /etc/init.d/smb restart

实例 9:每天 18 : 00 至 23 : 00 之间每隔 30 分钟重启 smb

1
0,30 18-23 * * * /etc/init.d/smb restart

实例 10:每星期六的晚上 11 : 00 pm 重启 smb

1
0 23 * * 6 /etc/init.d/smb restart

实例 11:每一小时重启 smb

1
0 * * * * /etc/init.d/smb restart

实例 12:晚上 11 点到早上 7 点之间,每隔一小时重启 smb

1
0 23-7 * * * /etc/init.d/smb restart

参考资料

效率提升方法论

在智力水平相当的前提下,常常会发现:有些人做事,事倍功半;有些人做事,事半功倍。

做任何事,如果有了清晰的思路,正确的指导方针,肯定是比毫无头绪要高效很多。所以,现实中,常常会看到这样一种现象,优秀的人,往往全面优秀,干什么都出彩;而平庸的人,做什么都出不了成绩。

大多数人不是天才,想要变得优秀,唯一的途径就是:按照正确的习惯(方式方法),坚持不懈的努力进步(自律)。

我们日复一日做的事情,决定了我们是怎样的人。因此所谓卓越,并非指行为,而是习惯

We are what we repeatedly do. Excellence, then, is not an act, but a habit.

——莎士比亚

做计划常用方法

名称 图示 方式 优点 缺点
时间法 时间顺序 清晰明确
操作性强
时间分配不合理或
突出情况容易打乱计划
清单法 重要程度 要事优先
事无遗漏
未完成容易形成压力;
容易造成形式主义,为打卡而完成
三段法 完成状态 态度明了
有条不紊
灵活性差,只能电子版或软件实现编辑,
纸质版事项会重复
OKR 法 目标分解 目标导向
高效成事
适合复杂事情或大项目的分解执行跟进
分类法 八个方面 事事周全
面面俱到
越是想顾周全,越难周全,
面面俱到,也会事事难完成
四象限法 img 轻重缓急 要事优先
忽略次要
被要事牵着走,忽略了
人生应该适度娱乐的重要性
甘特图法 日期进度 进度直观
易于理解
进度条只能反映时间进度,
无法反映事项具体完成情况的进度
PDCA 法 流程顺序 流程推进
循环解决
流程化容易形成思维惯性,
并且缺乏压力难以形成创造性

W2H

5W2H 分析法是一种思考问题的启发式思维方式。5W2H 分析法用五个以 W 开头的英语单词和两个以 H 开头的英语单词进行设问,得到关键性问题的答案,最后总结归纳出问题的目标、解决思路、处理方法等,这就叫做 5W2H 法。

5W2H 分析法又叫七问分析法,是二战中美国陆军兵器修理部首创。这种分析法广泛用于企业管理和技术活动,对于决策和执行性的活动措施也非常有帮助,也有助于弥补考虑问题的疏漏。

5W2H 分析法的意义在于:避免遇到一个问题后,不知从何入手。通过设问方式,由点成线,由线成面,把问题的关键点串联起来,整理出问题的解决思路。

5W2H

  • why - 为什么?为什么要这么做?理由何在?原因是什么?
  • what - 是什么?目的是什么?作什么工作?
  • where - 何处?在哪里做?从哪里入手?
  • when - 何时?什么时间完成?什么时机最适宜?
  • who - 谁?有谁来承担?谁来完成?谁负责?
  • how - 怎么做?如何提高效率?如何实施?方法怎么样?
  • how much - 多少?做到什么程度?数量如何?质量水平如何?费用产出如何?

四象限原则

四象限原则是一种时间管理方式

有首歌唱出了大多数职场人的心声:时间都去哪儿了?

事情、任务太多,时间太少,分身乏术。

时间管理四象限法则是美国的管理学家科维提出的一个时间管理的理论,按处理顺序划分为:紧急又重要、重要不紧急、紧急不重要、不紧急不重要。

img

  • 第一象限(重要而紧急

    • 案例:应付难缠的客户、准时完成工作、住院开刀等等。
    • 这是考验我们的经验、判断力的时刻,也是可以用心耕耘的园地。如果荒废了,我们很会可能变成行尸走肉。但我们也不能忘记,很多重要的事都是因为一拖再拖或事前准备不足,而变成迫在眉睫。
    • 该象限的本质是缺乏有效的工作计划导致本处于“重要但不紧急”第二象限的事情转变过来的,这也是传统思维状态下的管理者的通常状况,就是“忙”。
  • 第二象限(重要但不紧急)

    • 案例:学习新技能、建立人际关系、保持身体健康、长期的规划、问题的发掘与预防、参加培训、向上级提出问题处理的建议等等事项。
    • 荒废这个领域将使第一象限日益扩大,使我们陷入更大的压力,在危机中疲于应付。反之,多投入一些时间在这个领域有利于提高实践能力,缩小第一象限的范围。做好事先的规划、准备与预防措施,很多急事将无从产生。这个领域的事情不会对我们造成催促力量,所以必须主动去做,这是发挥个人领导力的领域。
    • 这更是传统低效管理者与高效卓越管理者的重要区别标志,建议管理者要把 80%的精力投入到该象限的工作,以使第一象限的“急”事无限变少,不再瞎“忙”。
  • 第三象限(紧急但不重要)

    • 案例:电话、会议、突发的访客都属于这一类。
    • 表面看似第一象限,因为迫切的呼声会让我们产生“这件事很重要”的错觉——实际上就算重要也是对别人而言。我们花很多时间在这个里面打转,自以为是在第一象限,其实不过是在满足别人的期望与标准。
  • 第四象限(不紧急也不重要)

    • 案例:阅读无聊小说、看毫无内容的电视节目、办公室聊天、刷微博、刷朋友圈等。
    • 简而言之就是浪费生命,所以根本不值得花半点时间在这个象限。但我们往往在一、三象限来回奔走,忙得焦头烂额,不得不到第四象限去疗养一番再出发。这部分范围倒不见得都是休闲活动,因为真正有创造意义的休闲活动是很有价值的。然而像阅读令人上瘾的无聊小说、毫无内容的电视节目、办公室聊天等。这样的休息不但不是为了走更长的路,反而是对身心的毁损,刚开始时也许有滋有味,到后来你就会发现其实是很空虚的。

HBase 快速入门

HBase 简介

为什么需要 HBase

在介绍 HBase 之前,我们不妨先了解一下为什么需要 HBase,或者说 HBase 是为了达到什么目的而产生。

在 HBase 诞生之前,Hadoop 可以通过 HDFS 来存储结构化、半结构甚至非结构化的数据,它是传统数据库的补充,是海量数据存储的最佳方法,它针对大文件的存储,批量访问和流式访问都做了优化,同时也通过多副本解决了容灾问题。

Hadoop 的缺陷在于:它只能执行批处理,并且只能以顺序方式访问数据。这意味着即使是最简单的工作,也必须搜索整个数据集,即:Hadoop 无法实现对数据的随机访问。实现数据的随机访问是传统的关系型数据库所擅长的,但它们却不能用于海量数据的存储。在这种情况下,必须有一种新的方案来同时解决海量数据存储和随机访问的问题,HBase 就是其中之一 (HBase,Cassandra,CouchDB,Dynamo 和 MongoDB 都能存储海量数据并支持随机访问)。

注:数据结构分类:

  • 结构化数据:即以关系型数据库表形式管理的数据;
  • 半结构化数据:非关系模型的,有基本固定结构模式的数据,例如日志文件、XML 文档、JSON 文档、Email 等;
  • 非结构化数据:没有固定模式的数据,如 WORD、PDF、PPT、EXL,各种格式的图片、视频等。

什么是 HBase

HBase 是一个构建在 HDFS(Hadoop 文件系统)之上的列式数据库

HBase 是一种类似于 Google’s Big Table 的数据模型,它是 Hadoop 生态系统的一部分,它将数据存储在 HDFS 上,客户端可以通过 HBase 实现对 HDFS 上数据的随机访问。

img

HBase 的核心特性如下:

  • 分布式
    • 伸缩性:支持通过增减机器进行水平扩展,以提升整体容量和性能
    • 高可用:支持 RegionServers 之间的自动故障转移
    • 自动分区:Region 分散在集群中,当行数增长的时候,Region 也会自动的分区再均衡
  • 超大数据集:HBase 被设计用来读写超大规模的数据集(数十亿行至数百亿行的表)
  • 支持结构化、半结构化和非结构化的数据:由于 HBase 基于 HDFS 构建,所以和 HDFS 一样,支持结构化、半结构化和非结构化的数据
  • 非关系型数据库
    • 不支持标准 SQL 语法
    • 没有真正的索引
    • 不支持复杂的事务:只支持行级事务,即单行数据的读写都是原子性的

HBase 的其他特性

  • 读写操作遵循强一致性
  • 过滤器支持谓词下推
  • 易于使用的 Java 客户端 API
  • 它支持线性和模块化可扩展性。
  • HBase 表支持 Hadoop MapReduce 作业的便捷基类
  • 很容易使用 Java API 进行客户端访问
  • 为实时查询提供块缓存 BlockCache 和布隆过滤器
  • 它通过服务器端过滤器提供查询谓词下推

什么时候使用 HBase

根据上一节对于 HBase 特性的介绍,我们可以梳理出 HBase 适用、不适用的场景:

HBase 不适用场景

  • 需要索引
  • 需要复杂的事务
  • 数据量较小(比如:数据量不足几百万行)

HBase 适用场景

  • 能存储海量数据并支持随机访问(比如:数据量级达到十亿级至百亿级)
  • 存储结构化、半结构化数据
  • 硬件资源充足

一言以蔽之——HBase 适用的场景是:实时地随机访问超大数据集

HBase 的典型应用场景

  • 存储监控数据
  • 存储用户/车辆 GPS 信息
  • 存储用户行为数据
  • 存储各种日志数据,如:访问日志、操作日志、推送日志等。
  • 存储短信、邮件等消息类数据
  • 存储网页数据

HBase 数据模型简介

前面已经提及,HBase 是一个列式数据库,其数据模型和关系型数据库有所不同。其数据模型的关键术语如下:

  • Table - HBase 表由多行组成。
  • Row - HBase 中的一行由一个行键和一个或多个列以及与之关联的值组成。 行在存储时按行键的字母顺序排序。 为此,行键的设计非常重要。 目标是以相关行彼此靠近的方式存储数据。 常见的行键模式是网站域。 如果您的行键是域,您应该将它们反向存储(org.apache.www、org.apache.mail、org.apache.jira)。 这样,所有 Apache 域在表中彼此靠近,而不是根据子域的第一个字母展开。
  • Column - HBase 中的列由列族和列限定符组成,它们由 :(冒号)字符分隔。
  • Column Family - 通常出于性能原因,列族在物理上将一组列及其值放在一起。 每个列族都有一组存储属性,例如它的值是否应该缓存在内存中,它的数据是如何压缩的,它的行键是如何编码的,等等。 表中的每一行都有相同的列族,尽管给定的行可能不在给定的列族中存储任何内容。
  • 列限定符 - 将列限定符添加到列族以提供给定数据片段的索引。 给定列族内容,列限定符可能是 content:html,另一个可能是 content:pdf。 尽管列族在表创建时是固定的,但列限定符是可变的,并且行之间可能有很大差异。
  • Cell - 单元格是行、列族和列限定符的组合,包含一个值和一个时间戳,代表值的版本。
  • Timestamp - 时间戳写在每个值旁边,是给定版本值的标识符。 默认情况下,时间戳表示写入数据时 RegionServer 上的时间,但您可以在将数据放入单元格时指定不同的时间戳值。

img

特性比较

HBase vs. RDBMS

RDBMS HBase
RDBMS 有它的模式,描述表的整体结构的约束 HBase 无模式,它不具有固定列模式的概念;仅定义列族
支持的文件系统有 FAT、NTFS 和 EXT 支持的文件系统只有 HDFS
使用提交日志来存储日志 使用预写日志 (WAL) 来存储日志
使用特定的协调系统来协调集群 使用 ZooKeeper 来协调集群
存储的都是中小规模的数据表 存储的是超大规模的数据表,并且适合存储宽表
通常支持复杂的事务 仅支持行级事务
适用于结构化数据 适用于半结构化、结构化数据
使用主键 使用 row key

HBase vs. HDFS

HDFS HBase
HDFS 提供了一个用于分布式存储的文件系统。 HBase 提供面向表格列的数据存储。
HDFS 为大文件提供优化存储。 HBase 为表格数据提供了优化。
HDFS 使用块文件。 HBase 使用键值对数据。
HDFS 数据模型不灵活。 HBase 提供了一个灵活的数据模型。
HDFS 使用文件系统和处理框架。 HBase 使用带有内置 Hadoop MapReduce 支持的表格存储。
HDFS 主要针对一次写入多次读取进行了优化。 HBase 针对读/写许多进行了优化。

行式数据库 vs. 列式数据库

行式数据库 列式数据库
对于添加/修改操作更高效 对于读取操作更高效
读取整行数据 仅读取必要的列数据
最适合在线事务处理系统(OLTP) 不适合在线事务处理系统(OLTP)
将行数据存储在连续的页内存中 将列数据存储在非连续的页内存中

列式数据库的优点:

  • 支持数据压缩
  • 支持快速数据检索
  • 简化了管理和配置
  • 有利于聚合查询(例如 COUNT、SUM、AVG、MIN 和 MAX)的高性能
  • 分区效率很高,因为它提供了自动分片机制的功能,可以将较大的区域分配给较小的区域

列式数据库的缺点:

  • JOIN 查询和来自多个表的数据未优化
  • 必须为频繁的删除和更新创建拆分,因此降低了存储效率
  • 由于非关系数据库的特性,分区和索引的设计非常困难

HBase 安装

HBase 安装可以参考以下文档:

HBase Hello World 示例

(1)连接 HBase

在 HBase 安装目录的 /bin 目录下执行 hbase shell 命令进入 HBase 控制台。

1
2
$ ./bin/hbase shell
hbase(main):001:0>

(2)输入 help 可以查看 HBase Shell 命令。

(3)创建表

可以使用 create 命令创建一张新表。必须要指定表名和 Column Family。

1
2
3
4
hbase(main):001:0> create 'test', 'cf'
0 row(s) in 0.4170 seconds

=> Hbase::Table - test

(4)列出表信息

使用 list 命令来确认新建的表已存在。

1
2
3
4
5
6
hbase(main):002:0> list 'test'
TABLE
test
1 row(s) in 0.0180 seconds

=> ["test"]

可以使用 describe 命令可以查看表的细节信息,包括配置信息

1
2
3
4
5
6
7
8
9
10
hbase(main):003:0> describe 'test'
Table test is ENABLED
test
COLUMN FAMILIES DESCRIPTION
{NAME => 'cf', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE =>
'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'f
alse', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE
=> '65536'}
1 row(s)
Took 0.9998 seconds

(5)向表中写数据

可以使用 put 命令向 HBase 表中写数据。

1
2
3
4
5
6
7
8
hbase(main):003:0> put 'test', 'row1', 'cf:a', 'value1'
0 row(s) in 0.0850 seconds

hbase(main):004:0> put 'test', 'row2', 'cf:b', 'value2'
0 row(s) in 0.0110 seconds

hbase(main):005:0> put 'test', 'row3', 'cf:c', 'value3'
0 row(s) in 0.0100 seconds

(6)一次性扫描表的所有数据

使用 scan 命令来扫描表数据。

1
2
3
4
5
6
hbase(main):006:0> scan 'test'
ROW COLUMN+CELL
row1 column=cf:a, timestamp=1421762485768, value=value1
row2 column=cf:b, timestamp=1421762491785, value=value2
row3 column=cf:c, timestamp=1421762496210, value=value3
3 row(s) in 0.0230 seconds

(7)查看一行数据

使用 get 命令可以查看一行表数据。

1
2
3
4
hbase(main):007:0> get 'test', 'row1'
COLUMN CELL
cf:a timestamp=1421762485768, value=value1
1 row(s) in 0.0350 seconds

(8)禁用表

如果想要删除表或修改表设置,必须先使用 disable 命令禁用表。如果想再次启用表,可以使用 enable 命令。

1
2
3
4
5
hbase(main):008:0> disable 'test'
0 row(s) in 1.1820 seconds

hbase(main):009:0> enable 'test'
0 row(s) in 0.1770 seconds

(9)删除表

使用 drop 命令可以删除表。

1
2
hbase(main):011:0> drop 'test'
0 row(s) in 0.1370 seconds

(10)退出 HBase Shell

使用 quit 命令,就能退出 HBase Shell 控制台。

参考资料

Nosql 技术选型

img

Nosql 简介

传统的关系型数据库存在以下缺点:

  • 大数据场景下 I/O 较高 - 因为数据是按行存储,即使只针对其中某一列进行运算,关系型数据库也会将整行数据从存储设备中读入内存,导致 I/O 较高。
  • 存储的是行记录,无法存储数据结构
  • 表结构 schema 扩展不方便 - 如要需要修改表结构,需要执行执行 DDL(data definition language),语句修改,修改期间会导致锁表,部分服务不可用。
  • 全文搜索功能较弱 - 关系型数据库下只能够进行子字符串的匹配查询,当表的数据逐渐变大的时候,LIKE 查询的匹配会非常慢,即使在有索引的情况下。况且关系型数据库也不应该对文本字段进行索引。
  • 存储和处理复杂关系型数据功能较弱 - 许多应用程序需要了解和导航高度连接数据之间的关系,才能启用社交应用程序、推荐引擎、欺诈检测、知识图谱、生命科学和 IT/网络等用例。然而传统的关系数据库并不善于处理数据点之间的关系。它们的表格数据模型和严格的模式使它们很难添加新的或不同种类的关联信息。

随着大数据时代的到来,越来越多的网站、应用系统需要支撑海量数据存储,高并发请求、高可用、高可扩展性等特性要求。传统的关系型数据库在应付这些调整已经显得力不从心,暴露了许多能以克服的问题。由此,各种各样的 NoSQL(Not Only SQL)数据库作为传统关系型数据的一个有力补充得到迅猛发展。

nosql-history

NoSQL,泛指非关系型的数据库,可以理解为 SQL 的一个有力补充。

在 NoSQL 许多方面性能大大优于非关系型数据库的同时,往往也伴随一些特性的缺失,比较常见的,是事务库事务功能的缺失。 数据库事务正确执行的四个基本要素:ACID 如下:

名称 描述
A Atomicity (原子性) 一个事务中的所有操作,要么全部完成,要么全部不完成,不会在中间某个环节结束。 事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
C Consistency 一致性 在事务开始之前和事务结束以后,数据的数据的一致性约束没有被破坏。
I Isolation 隔离性 数据库允许多个并发事务同时对数据进行读写和修改的能力。隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
D Durability 持久性 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

下面介绍 5 大类 NoSQL 数据针对传统关系型数据库的缺点提供的解决方案:

列式数据库

列式数据库是以列相关存储架构进行数据存储的数据库,主要适合于批量数据处理和即时查询

相对应的是行式数据库,数据以行相关的存储体系架构进行空间分配,主要适合于小批量的数据处理,常用于联机事务型数据处理。

基于列式数据库的列列存储特性,可以解决某些特定场景下关系型数据库 I/O 较高的问题

列式数据库原理

传统关系型数据库是按照行来存储数据库,称为“行式数据库”,而列式数据库是按照列来存储数据。

将表放入存储系统中有两种方法,而我们绝大部分是采用行存储的。 行存储法是将各行放入连续的物理位置,这很像传统的记录和文件系统。 列存储法是将数据按照列存储到数据库中,与行存储类似,下图是两种存储方法的图形化解释:

按行存储和按列存储模式

列式数据库产品

  • HBase

    HBase

    HBase 是一个开源的非关系型分布式数据库(NoSQL),它参考了谷歌的 BigTable 建模,实现的编程语言为 Java。它是 Apache 软件基金会的 Hadoop 项目的一部分,运行于 HDFS 文件系统之上,为 Hadoop 提供类似于 BigTable 规模的服务。因此,它可以容错地存储海量稀疏的数据。

  • BigTable

    img

    BigTable 是一种压缩的、高性能的、高可扩展性的,基于 Google 文件系统(Google File System,GFS)的数据存储系统,用于存储大规模结构化数据,适用于云端计算。

列式数据库特性

优点如下:

  • 高效的储存空间利用率

列式数据库由于其针对不同列的数据特征而发明的不同算法,使其往往有比行式数据库高的多的压缩率,普通的行式数据库一般压缩率在 3:1 到 5:1 左右,而列式数据库的压缩率一般在 8:1 到 30:1 左右。 比较常见的,通过字典表压缩数据: 下面中才是那张表本来的样子。经过字典表进行数据压缩后,表中的字符串才都变成数字了。正因为每个字符串在字典表里只出现一次了,所以达到了压缩的目的(有点像规范化和非规范化 Normalize 和 Denomalize)

通过字典表压缩数据

  • 查询效率高

读取多条数据的同一列效率高,因为这些列都是存储在一起的,一次磁盘操作可以数据的指定列全部读取到内存中。 下图通过一条查询的执行过程说明列式存储(以及数据压缩)的优点

img

1
2
3
4
5
6
执行步骤如下:
i. 去字典表里找到字符串对应数字(只进行一次字符串比较)。
ii. 用数字去列表里匹配,匹配上的位置设为 1
iii. 把不同列的匹配结果进行位运算得到符合所有条件的记录下标。
iv. 使用这个下标组装出最终的结果集。
复制代码
  • 适合做聚合操作
  • 适合大量的数据而不是小数据

缺点如下:

  • 不适合扫描小量数据
  • 不适合随机的更新
  • 不适合做含有删除和更新的实时操作
  • 单行的数据是 ACID 的,多行的事务时,不支持事务的正常回滚,支持 I(Isolation) 隔离性(事务串行提交),D(Durability) 持久性,不能保证 A(Atomicity) 原子性, C(Consistency) 一致性

列式数据库使用场景

以 HBase 为例说明:

  • 大数据量 (100s TB 级数据) 且有快速随机访问的需求。增长量无法预估的应用,需要进行优雅的数据扩展的 HBase 支持在线扩展,即使在一段时间内数据量呈井喷式增长,也可以通过 HBase 横向扩展来满足功能。
  • 写密集型应用,每天写入量巨大,而相对读数量较小的应用 比如 IM 的历史消息,游戏的日志等等
  • 不需要复杂查询条件来查询数据的应用 HBase 只支持基于 rowkey 的查询,对于 HBase 来说,单条记录或者小范围的查询是可以接受的,大范围的查询由于分布式的原因,可能在性能上有点影响,HBase 不适用于有 join,多级索引,表关系复杂的数据模型。
  • 对性能和可靠性要求非常高的应用,由于 HBase 本身没有单点故障,可用性非常高。
  • 存储结构化和半结构化的数据

K-V 数据库

K-V 数据库指的是使用键值 (key-value) 存储的数据库,其数据按照键值对的形式进行组织、索引和存储

KV 存储非常适合存储不涉及过多数据关系业务关系的数据,同时能有效减少读写磁盘的次数,比 SQL 数据库存储拥有更好的读写性能,能够解决关系型数据库无法存储数据结构的问题

K-V 数据库产品

  • Redis

    img

    Redis 是一个使用 ANSI C 编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。从 2015 年 6 月开始,Redis 的开发由 Redis Labs 赞助,而 2013 年 5 月至 2015 年 6 月期间,其开发由 Pivotal 赞助。在 2013 年 5 月之前,其开发由 VMware 赞助。根据月度排行网站 DB-Engines.com 的数据显示,Redis 是最流行的键值对存储数据库。

  • Cassandra

    img

    Apache Cassandra(社区内一般简称为 C*)是一套开源分布式 NoSQL 数据库系统。它最初由 Facebook 开发,用于储存收件箱等简单格式数据,集 Google BigTable 的数据模型与 Amazon Dynamo 的完全分布式架构于一身。Facebook 于 2008 将 Cassandra 开源,此后,由于 Cassandra 良好的可扩展性和性能,被 Apple, Comcast,Instagram, Spotify, eBay, Rackspace, Netflix 等知名网站所采用,成为了一种流行的分布式结构化数据存储方案。

  • LevelDB

    img

    LevelDB 是一个由 Google 公司所研发的键/值对(Key/Value Pair)嵌入式数据库管理系统编程库, 以开源的 BSD 许可证发布。

K-V 数据库特性

以 Redis 为例:

优点如下:

  • 性能极高 - Redis 能支持超过 10W 的 TPS。
  • 丰富的数据类型 - Redis 支持包括 String,Hash,List,Set,Sorted Set,Bitmap 和 hyperloglog。
  • 丰富的特性 - Redis 还支持 publish/subscribe、通知、key 过期等等特性。

缺点如下: 针对 ACID,Redis 事务不能支持原子性和持久性 (A 和 D),只支持隔离性和一致性 (I 和 C) 特别说明一下,这里所说的无法保证原子性,是针对 Redis 的事务操作,因为事务是不支持回滚(roll back),而因为 Redis 的单线程模型,Redis 的普通操作是原子性的

大部分业务不需要严格遵循 ACID 原则,例如游戏实时排行榜,粉丝关注等场景,即使部分数据持久化失败,其实业务影响也非常小。因此在设计方案时,需要根据业务特征和要求来做选择

K-V 数据库使用场景

  • 适用场景 - 储存用户信息(比如会话)、配置文件、参数、购物车等等。这些信息一般都和 ID(键)挂钩。

  • 不适用场景

    • 需要通过值来查询,而不是键来查询。Key-Value 数据库中根本没有通过值查询的途径。
    • 需要储存数据之间的关系。在 Key-Value 数据库中不能通过两个或以上的键来关联数据
    • 需要事务的支持。在 Key-Value 数据库中故障产生时不可以进行回滚。

文档数据库

文档数据库(也称为文档型数据库)是旨在将半结构化数据存储为文档的一种数据库,它可以解决关系型数据库表结构 schema 扩展不方便的问题。文档数据库通常以 JSON 或 XML 格式存储数据

由于文档数据库的 no-schema 特性,可以存储和读取任意数据。由于使用的数据格式是 JSON 或者 XML,无需在使用前定义字段,读取一个 JSON 中不存在的字段也不会导致 SQL 那样的语法错误。

文档数据库产品

  • MongoDB

    img

    **MongoDB **是一种面向文档的数据库管理系统,由 C++ 撰写而成,以此来解决应用程序开发社区中的大量现实问题。2007 年 10 月,MongoDB 由 10gen 团队所发展。2009 年 2 月首度推出。

  • CouchDB

    img

    Apache CouchDB 是一个开源数据库,专注于易用性和成为”完全拥抱 web 的数据库“。它是一个使用 JSON 作为存储格式,JavaScript 作为查询语言,MapReduce 和 HTTP 作为 API 的 NoSQL 数据库。其中一个显著的功能就是多主复制。CouchDB 的第一个版本发布在 2005 年,在 2008 年成为了 Apache 的项目。

文档数据库特性

以 MongoDB 为例进行说明

优点如下:

  • 容易存储复杂数据结构 - JSON 是一种强大的描述语言,能够描述复杂的数据结构。
  • 容易变更数据结构 - 无需像关系型数据库一样先执行 DDL 语句修改表结构,程序代码直接读写即可。
  • 容易兼容历史数据 - 对于历史数据,即使没有新增的字段,也不会导致错误,只会返回空值,此时代码兼容处理即可。

缺点如下:

  • 部分支持事务
    • Atomicity(原子性) 仅支持单行/文档级原子性,不支持多行、多文档、多语句原子性。
    • Isolation(隔离性) 隔离级别仅支持已提交读(Read committed)级别,可能导致不可重复读,幻读的问题。
  • 不支持复杂查询 - 例如 join 查询,如果需要 join 查询,需要多次操作数据库。

MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持久性)

虽然官方宣布 MongoDB 将在 4.0 版本中正式推出多文档 ACID 事务支持,最后落地情况还有待见证。

文档数据库使用场景

适用场景

  • 大数据量,且未来数据增长很快
  • 表结构不明确,且字段在不断增加,例如内容管理系统,信息管理系统

不适用场景

  • 支持事务 - 在不同的文档上需要添加事务。Document-Oriented 数据库并不支持文档间的事务
  • 支持复杂查询 - 多个文档直接需要复杂查询,例如 join

全文搜索引擎

传统关系型数据库主要通过索引来达到快速查询的目的,在全文搜索的业务下,索引也无能为力,主要体现在:

  • 全文搜索的条件可以随意排列组合,如果通过索引来满足,则索引的数量非常多
  • 全文搜索的模糊匹配方式,索引无法满足,只能用 LIKE 查询,而 LIKE 查询是整表扫描,效率非常低

而全文搜索引擎的出现,正是解决关系型数据库全文搜索功能较弱的问题

搜索引擎原理

全文搜索引擎的技术原理称为 **倒排索引(inverted index)**,是一种索引方法,其基本原理是建立单词到文档的索引。与之相对是,是“正排索引”,其基本原理是建立文档到单词的索引。

现在有如下文档集合:

img

正排索引得到索引如下:

img

可见,正排索引适用于根据文档名称查询文档内容

简单的倒排索引如下:

img

带有单词频率信息的倒排索引如下:

img

可见,倒排索引适用于根据关键词来查询文档内容

搜索引擎产品

  • Elasticsearch

    img

    Elasticsearch 是一个基于 Lucene 的搜索引擎。它提供了一个分布式,多租户 -能够全文搜索与发动机 HTTP Web 界面和无架构 JSON 文件。Elasticsearch 是用 Java 开发的,并根据 Apache License 的条款作为开源发布。根据 DB-Engines 排名,Elasticsearch 是最受欢迎的企业搜索引擎,后面是基于 Lucene 的 Apache Solr。

  • Solr

    img

    Solr 是 Apache Lucene 项目的开源企业搜索平台。其主要功能包括全文检索、命中标示、分面搜索、动态聚类、数据库集成,以及富文本(如 Word、PDF)的处理。Solr 是高度可扩展的,并提供了分布式搜索和索引复制

搜索引擎特性

以 Elasticsearch 为例: 优点如下:

  • 查询效率高 - 对海量数据进行近实时的处理
  • 可扩展性 - 基于集群环境可以方便横向扩展,可以承载 PB 级数据
  • 高可用 - Elasticsearch 集群弹性-他们将发现新的或失败的节点,重组和重新平衡数据,确保数据是安全的和可访问的

缺点如下:

  • 部分支持事务 - 单一文档的数据是 ACID 的,包含多个文档的事务时不支持事务的正常回滚,支持 I(Isolation) 隔离性(基于乐观锁机制的),D(Durability) 持久性,不支持 A(Atomicity) 原子性,C(Consistency) 一致性
  • 对类似数据库中通过外键的复杂的多表关联操作支持较弱。
  • 读写有一定延时,写入的数据,最快 1s 中能被检索到
  • 更新性能较低,底层实现是先删数据,再插入新数据
  • 内存占用大,因为 Lucene 将索引部分加载到内存中

搜索引擎场景

适用场景如下:

  • 搜索引擎和数据分析引擎 - 全文检索,结构化检索,数据分析
  • 对海量数据进行近实时的处理 - 可以将海量数据分散到多台服务器上去存储和检索

不适用场景如下:

  • 数据需要频繁更新
  • 需要复杂关联查询

图数据库

img

图形数据库应用图论存储实体之间的关系信息。最常见例子就是社会网络中人与人之间的关系。关系型数据库用于存储“关系型”数据的效果并不好,其查询复杂、缓慢、超出预期,而图形数据库的独特设计恰恰弥补了这个缺陷,解决关系型数据库存储和处理复杂关系型数据功能较弱的问题。

图数据库产品

  • Neo4j

    img

    Neo4j 是由 Neo4j,Inc。开发的图形数据库管理系统。由其开发人员描述为具有原生图存储和处理的符合 ACID 的事务数据库,根据 DB-Engines 排名, Neo4j 是最流行的图形数据库。

  • ArangoDB

    img

    ArangoDB 是由 triAGENS GmbH 开发的原生多模型数据库系统。数据库系统支持三个重要的数据模型(键/值,文档,图形),其中包含一个数据库核心和统一查询语言 AQL(ArangoDB 查询语言)。查询语言是声明性的,允许在单个查询中组合不同的数据访问模式。ArangoDB 是一个 NoSQL 数据库系统,但 AQL 在很多方面与 SQL 类似。

  • Titan

    img

    Titan 是一个可扩展的图形数据库,针对存储和查询包含分布在多机群集中的数百亿个顶点和边缘的图形进行了优化。Titan 是一个事务性数据库,可以支持数千个并发用户实时执行复杂的图形遍历。

图数据库特性

以 Neo4j 为例:

Neo4j 使用数据结构中图(graph)的概念来进行建模。 Neo4j 中两个最基本的概念是节点和边。节点表示实体,边则表示实体之间的关系。节点和边都可以有自己的属性。不同实体通过各种不同的关系关联起来,形成复杂的对象图。

针对关系数据,2 种 2 数据库的存储结构不同:

2 种存储结构

Neo4j 中,存储节点时使用了”index-free adjacency”,即每个节点都有指向其邻居节点的指针,可以让我们在 O(1) 的时间内找到邻居节点。另外,按照官方的说法,在 Neo4j 中边是最重要的,是”first-class entities”,所以单独存储,这有利于在图遍历的时候提高速度,也可以很方便地以任何方向进行遍历

img

如下优点:

  • 高性能 - 图的遍历是图数据结构所具有的独特算法,即从一个节点开始,根据其连接的关系,可以快速和方便地找出它的邻近节点。这种查找数据的方法并不受数据量的大小所影响,因为邻近查询始终查找的是有限的局部数据,不会对整个数据库进行搜索
  • 设计的灵活性 - 数据结构的自然伸展特性及其非结构化的数据格式,让图数据库设计可以具有很大的伸缩性和灵活性。因为随着需求的变化而增加的节点、关系及其属性并不会影响到原来数据的正常使用
  • 开发的敏捷性 - 直观明了的数据模型,从需求的讨论开始,到程序开发和实现,以及最终保存在数据库中的样子,它的模样似乎没有什么变化,甚至可以说本来就是一模一样的
  • 完全支持 ACID - 不像别的 NoSQL 数据库 Neo4j 还具有完全事务管理特性,完全支持 ACID 事务管理

缺点如下:

  • 存在支持节点,关系和属性的数量的限制。
  • 不支持拆分。

图数据库场景

适用场景如下:

  • 关系性强的数据中,如社交网络
  • 推荐引擎。如果我们将数据以图的形式表现,那么将会非常有益于推荐的制定

不适用场景如下:

  • 记录大量基于事件的数据(例如日志条目或传感器数据)
  • 对大规模分布式数据进行处理
  • 保存在关系型数据库中的结构化数据
  • 二进制数据存储

总结

关系型数据库和 NoSQL 数据库的选型,往往需要考虑几个指标:

  • 数据量
  • 并发量
  • 实时性
  • 一致性要求
  • 读写分布和类型
  • 安全性
  • 运维成本

常见软件系统数据库选型参考如下:

  • 中后台管理型系统 - 如运营系统,数据量少,并发量小,首选关系型数据库。
  • 大流量系统 - 如电商单品页,后台考虑选关系型数据库,前台考虑选内存型数据库。
  • 日志型系统 - 原始数据考虑选列式数据库,日志搜索考虑选搜索引擎。
  • 搜索型系统 - 例如站内搜索,非通用搜索,如商品搜索,后台考虑选关系型数据库,前台考虑选搜索引擎。
  • 事务型系统 - 如库存,交易,记账,考虑选关系型数据库+K-V 数据库(作为缓存)+分布式事务。
  • 离线计算 - 如大量数据分析,考虑选列式数据库或关系型数据。
  • 实时计算 - 如实时监控,可以考虑选内存型数据库或者列式数据库。

设计实践中,要基于需求、业务驱动架构,无论选用 RDB/NoSQL/DRDB, 一定是以需求为导向,最终数据存储方案必然是各种权衡的综合性设计

参考资料