Senlin's Blog


  • 分类

  • 归档

  • 标签

  • 关于

Redis Cluster

发表于 2017-04-08   |   分类于 数据库   |   阅读次数

高可用性

  Redis Cluster 能自动将数据在多个 master 节点之间分片,从而拓展了 Redis 的读写能力。集群中的每个 master 节点都对应着一个或多个 slave 节点,这也在一定程度上保证了 Redis 的可用性,譬如说,当某个 master 节点发生故障,那么它的其中一个 slave 节点可以提升成为新的 master 节点,从而保证整个集群继续可用。

数据分片

  整个集群固定地拥有 16384 个 hash slot,集群中的每个 master 节点都拥有若干个 slot,可以使用下面的算法计算某个 key 被分配到的哪个 slot 中:

1
HASH_SLOT = CRC16(key) mod 16384

集群配置

  为了启动集群,每个节点的配置文件redis.conf都需要指定相应的集群配置信息:

1
2
3
4
5
6
cluster-enabled yes # 开启集群功能
cluster-config-file nodes.conf
cluster-node-timeout 2000 # 2秒
cluster-slave-validity-factor 10
cluster-migration-barrier 1
cluster-require-full-coverage yes

  对于每个节点来说,它都需要一个配置文件用于记录整个集群的信息。cluster-config-file选项指定了这个配置文件的路径,当集群信息更新时,节点会自动将集群的信息更新到这个配置文件中。
  当集群的某个节点不可用(unavailable),通常不会立即认定它发生故障了,因为说不定隔一段时间它就恢复可用了。cluster-node-timeout用来指定节点的最大不可用时间,也就是说当节点的不可用时间超过这个值时,集群就会认为这个节点发生故障了。

参考资料

  • Redis Essentials
  • Redis cluster tutorial
  • Redis设计与实现

Redis Sentinel 与高可用

发表于 2017-04-08   |   分类于 数据库   |   阅读次数

Redis 的高可用

  使用 Redis Sentinel 可以提高 Redis 的可用性,譬如说,当 Redis 开启了主从复制功能之后,如果 master 实例发生故障,那么 Sentinel 可以做到自动的故障转移(failover):当 master 实例故障时,它的其中一个 slave 实例会自动提升成为新的 master 实例,同时其它的 slave 会重新指向这个新的 master 实例。
  对于一个健壮的系统来说,通常至少需要三个 Sentinel 实例,譬如说,如果拥有一个 master 实例和两个 slave 实例,那么经常使用的架构是这样的,即三台服务器上面各自运行一个 Redis 实例和一个 Sentinel 进程:

阅读全文 »

Redis 主从复制的原理

发表于 2017-04-07   |   分类于 数据库   |   阅读次数

配置主从复制

  为了拓展 Redis 的读性能,通常我们可以为一个master实例增加一个或多个slave实例,这就是主从复制。另一方面,主从复制提供了数据冗余,这在一定程度上提高了 Redis 的可用性,例如,当master实例发生故障时,我们可以将其中一个slave实例提升为master,从而继续保证 Redis 的可用性。
  设置主从复制很简单,只需要在slave实例的配置文件加入一行,然后重启slave实例:

1
2
# /etc/redis/redis.conf
slaveof your_redis_master_ip 6379

主从复制的原理

  在设置好主从复制之后,一开始 slave 与 master 之间的数据是不同步的,为了让 slave 与 master 之间保存数据同步,slave 会发送一个 PSYNC 命令给 master。由于 slave 是初次连接上 master,所以 master 接收到 PSYNC 命令之后会执行完整重同步(full resynchronization),具体的过程是这样的:

  • 首先 master 会执行 BGSAVE 命令,在后台生成一个 RDB 文件,与此同时开辟一个缓冲区用于存储 client 发来的命令。
  • 当 BGSAVE 执行成功之后,master 会将这个 RDB 文件发送给 slave,slave 在接收到这个 RDB 文件之后会将它加载到内存。
  • 接着 master 会将缓冲区内的所有命令发送给 slave 去执行。

  主从复制时还可能出现另一种情况,譬如说,master 与 slave 之间已经保持同步了,但是由于网络状况不好,导致 master 与 slave 断开连接,一段时间之后网络变好了,slave 又重新连上 master,但此时 slave 已经与 master 不同步了。那么为了让 slave 与 master 继续保持同步,这时候它们会怎么做呢?

阅读全文 »

浅谈 Linux 的 Zero Copy 技术

发表于 2017-03-25   |   分类于 网络编程   |   阅读次数

mmap 文件映射

  通常情况下,我们可以使用read()和write()去访问文件,除此之外,Linux 还提供了mmap()系统调用,它可以将文件映射到进程的地址空间,这样程序就可以通过访问内存的方式去访问文件了。那么与read()和write()相比,使用mmap()去访问文件能带来什么好处呢?
  使用mmap()一个明显的好处就是减少一次 I/O 拷贝,譬如说,当我们使用read()读取文件时,通常的做法是这样:

1
2
char buffer[SIZE];
ssize_t n = read(fd, buffer, SIZE);

  这个过程实际上发生了两次 I/O 拷贝,第一次是将磁盘中的文件内容拷贝到 OS 的文件系统缓冲区,第二次是将 OS 缓冲区的数据拷贝到用户缓冲区。而使用mmap()读取文件时,只会发生第一次拷贝操作,也就是将文件内容拷贝到 OS 文件系统缓冲区,完成这个拷贝操作之后,mmap()还会执行其它一些复杂的操作,例如将相应的 OS 缓冲区映射到进程的地址空间。
  尽管mmap()可以减少一次 I/O 拷贝,但由于mmap()的实现很复杂,调用mmap()将会带来额外的开销,因此在一些情况下,没有使用mmap()的必要:

  • 访问小文件时,直接使用read()或write()将更加高效。
  • 单个进程对文件执行顺序访问时(sequential access),使用mmap()几乎不会带来性能上的提升。譬如说,使用read()顺序读取文件时,文件系统会使用 read-ahead 的方式提前将文件内容缓存到文件系统的缓冲区,因此使用read()将很大程度上可以命中缓存。

  那么,在什么情况下使用mmap()去访问文件会更高效呢?

  • 对文件执行随机访问时,如果使用read()或write(),则意味着较低的 cache 命中率。这种情况下使用mmap()通常将更高效。
  • 多个进程同时访问同一个文件时(无论是顺序访问还是随机访问),如果使用mmap(),那么 OS 缓冲区的文件内容可以在多个进程之间共享,从操作系统角度来看,使用mmap()可以大大节省内存。
    阅读全文 »

如何设计动态链接库

发表于 2017-03-20   |   分类于 编译链接   |   阅读次数

动态库的创建

  Linux 系统上经常见到的 *.so 文件其实就是动态库,将一个 *.c 文件编译成动态库很简单:

1
2
$ gcc -g -c -fPIC -Wall demo.c
$ gcc -g -shared -o libdemo.so demo.o

  -fPIC选项用来指导编译器生成位置无关代码(position-independent code),在Linux/x86-64系统上必须指定这个选项。

动态库规范

  动态库的升级通常也分为小版本的升级和主版本的升级,通常来说主版本的升级是不兼容的,而小版本的升级则是兼容的。动态库通常有一种规范的命名方式,为的是能够清晰地了解不同版本之间的关系:

1
2
3
libdemo.so.1.0.1 # 主版本 1,小版本 0.1
libdemo.so.1.0.2 # 主版本 1,小版本 0.2
libdemo.so.2.0.1 # 主版本 2,小版本 0.1

  在编译动态库的时候,通常会为动态库创建一个 soname,作为动态库的别名,例如,这里我们给动态库libdemo.so.1.0.1创建了一个 soname 叫做libdemo.1:

1
2
$ gcc -g -c -fPIC -Wall demo.c
$ gcc -g -shared -Wl,-soname,libdemo.so.1 -o libdemo.so.1.0.1 demo.o

  在生产环境下,用户自己创建的动态库一般不能随便放置(防止找不到或者不小心被删除了),所以推荐放在系统目录/usr/local/lib/下面:

1
$ sudo mv libdemo.so.1.0.1 /usr/local/lib/

  通常来说,系统会在/etc/ld.so.cache这个文件里面缓存系统的动态库列表。如果用户自己添加了动态库,那么还需要用ldconfig命令去更新系统的动态库缓存列表,同时这个命令会自动创建一个与 soname 同名的符号链接,指向对应的动态库:

1
2
3
4
5
6
$ sudo ldconfig -v # 更新系统动态库缓存列表
/usr/local/lib:
libdemo.so.1 -> libdemo.so.1.0.1 (changed)
$ ls -l /usr/local/lib/
lrwxrwxrwx 1 root root 16 Mar 21 22:54 libdemo.so.1 -> libdemo.so.1.0.1
-rwxr-xr-x 1 root root 8776 Mar 21 22:54 libdemo.so.1.0.1

  通过这些步骤就顺利安装好了动态库了。那么还有一个问题,在编译程序时,怎么让 linker 找到动态库的位置呢?最简单的方法可以这样做:

1
$ gcc -g -Wall -o main main.c /usr/local/lib/libdemo.so.1.0.1

  在链接的过程中,如果动态库拥有 soname,那么 linker 会将这个 soname 嵌入到可执行文件中,如果动态库没有 soname,嵌入的则是动态库的真名。我们可以用readelf命令看到可执行文件main的 header 中是否包含动态库的信息:

1
2
readelf -d main | grep libdemo
0x0000000000000001 (NEEDED) Shared library: [libdemo.so.1]

  可以看到,可执行文件main中只记录动态库的 soname。

阅读全文 »
1…789…13

高性能

61 日志
13 分类
14 标签
GitHub 知乎
© 2015 - 2022
由 Hexo 强力驱动
主题 - NexT.Mist
  |   总访问量: