从此Redis是路人

序言:Redis(Remote DIctionary Server)作为一个开源/C实现/高性能/基于内存的key-value存储系统,相信做Java的小伙伴都不会陌生。Redis常用于缓存、分布式锁、队列(或有序集合)等场景,追求技术的小伙伴们肯定不只满足于Redis的使用上,肯定也想了解Redis背后的设计思想及对应的开发实践,话不多少,上车吧~

ps:文章较长,小伙伴可以搬个小板凳慢慢阅读,也可以结合自身情况进行选择阅读 : )

 

由于文章内容较多,下面就按照Redis对象、事件处理机制、持久化机制、事务机制、主备模型、集群机制等内容进行分析讨论,中间可能穿插着相关内容的扩展和思考。Let’s go….

 

Redis对象

Redis主要有5种不同类型的对象,分别是字符串、列表、哈希表、集合、有序集合。这些对象都是基于Redis基础数据结构来构建的,并且每种对象都用到了至少一种基础数据结构。Redis对象还实现了引用计数的内存回收技术,当不再使用某个对象时,可以及时释放其内存;通过引用计数实现了对象共享机制,节约内存(Redis只对包含整数值的字符串对象进行共享);Redis的对象带有访问时间戳,可用于计算该对象空转时间,启用maxmemroy功能时,空转时间较长的键优先被删除。

Redis用到的底层数据结构有:简单动态字符串、双端链表、字典、压缩列表、整数集合、跳跃表等,Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些基础数据结构创建了一个对象系统,这写对象包括字符串对象、列表对象、哈希对象、集合对象和有序集合对象等。

 

Redis中使用对象表示键和值,当新建一个键值对时,Redis至少创建2个对象,一个是键对象,另一个是值对象。

typedef struct redisObject {
   unsigned type:4;
   unsigned encoding:4;
   unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
   int refcount;
   void *ptr;
robj;

type表示对象类型(有REDIS_STRING/REDIS_LIST/REDIS_HASH/REDIS_SET/REDIS_ZSET几种),对于Redis键值对来说,键永远都是字符串,值可以是字符串、列表、哈希表、集合、有序集合中的一种。encoding表示对象编码,也就是该对象使用什么底层数据结构实现。ptr指向对象的底层数据结构。

 

Redis对象:

  • 字符串:字符串对象底层可以是int和raw编码方式,如set num 1后执行append num hello,则会导致编码方式由int到raw转换。

  • 127.0.0.1:6379> set num 1
    OK
    127.0.0.1:6379> object encoding num
    "int"
    127.0.0.1:6379> append num hello
    (integer) 6
    127.0.0.1:6379> object encoding num
    "raw"
  • 列表:列表对象的编码可以是ziplist、linkedlist和quicklist(新版本Redis使用quicklist作为列表底层数据结构了)。ziplist使用功能压缩列表作为底层实现,每个压缩列表节点保存一个列表元素。

  • 127.0.0.1:6379> rpush nums 1 "tow" 3
    127.0.0.1:6379> object encoding nums
    "quicklist"
  • 集合:set容器,集合对象的编码可以是intset和hashtable。intset编码的集合对象使用整数集合作为底层实现,所有元素都保存在整数集合中。另一方面,使用hashtable的集合对象使用字典作为底层实现,字典中每个键都是一个字符串对象,即一个集合元素,而字典的值都是NULL。

  • 127.0.0.1:6379> sadd mans 1
    127.0.0.1:6379> sadd mans 2
    127.0.0.1:6379> object encoding mans
    "intset"
    127.0.0.1:6379> sadd mans luoxn28
    127.0.0.1:6379> object encoding mans
    "hashtable"
  • 有序集合:有序集合对象的编码可以是ziplist和skiplist。ziplist编码的压缩列表对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨着的压缩列表节点保存,第一个保存集合元素,第二个保存集合元素对应的分值。压缩列表内集合元素按照分值大小进行排序,分值较小的在前,分值大的在后;skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表,通过字典提高获取单个元素效率,通过skiplist提高获取范围查询能力,二者各取所长。

  • 哈希表:哈希对象的编码可以是ziplist和hashtable。hashtable编码的哈希对象使用字典作为底层实现,则哈希对象中的每个键值对都是字典键值对来保存,hashtable为数组+链表的分离连接法实现。

 

事件处理机制

Redis的事件类型分为时间事件和文件事件,文件事件也就是IO事件。时间事件的处理是在epoll_wait返回处理文件事件后处理的,每次epoll_wait的超时时间都是Redis最近的一个定时器时间。Redis对epoll进行了简单封装,不像memcached直接使用libevent作为网络通信组件。

 

Redis在进行事件处理前,首先会进行初始化,初始化的主要逻辑在main/initServer函数中。初始化流程主要做的工作如下:

  • 设置回调函数;

  • 创建事件循环机制,即调用epoll_create;

  • 创建服务监听端口,创建定时事件,并将这些事件添加到事件机制中。

void initServer(void) {
   // 设置信号对应的处理函数
   signal(SIGHUP, SIG_IGN);
   signal(SIGPIPE, SIG_IGN);
   setupSignalHandlers();
  ...

   createSharedObjects();
   adjustOpenFilesLimit();
   // 创建事件循环机制,及调用epoll_create创建epollfd用于事件监听
   server.el aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
   server.db zmalloc(sizeof(redisDb)*server.dbnum);

   /* Open the TCP listening socket for the user commands. */
   // 创建监听服务端口,socket/bind/listen
   if (server.port != &&
       listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
       exit(1);
  ...

   /* Create the Redis databases, and initialize other internal state. */
   for (0; server.dbnum; j++) {
       server.db[j].dict dictCreate(&dbDictType,NULL);
       server.db[j].expires dictCreate(&keyptrDictType,NULL);
       server.db[j].blocking_keys dictCreate(&keylistDictType,NULL);
       server.db[j].ready_keys dictCreate(&setDictType,NULL);
       server.db[j].watched_keys dictCreate(&keylistDictType,NULL);
       server.db[j].eviction_pool evictionPoolAlloc();
       server.db[j].id j;
       server.db[j].avg_ttl 0;
  }
  ...

   /* Create the serverCron() time event, that's our main way to process
    * background operations. 创建定时事件 */
   if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
       serverPanic("Can't create the serverCron time event.");
       exit(1);
  }

   /* Create an event handler for accepting new connections in TCP and Unix
    * domain sockets. */
   for (0; server.ipfd_count; j++) {
       if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
           acceptTcpHandler,NULL) == AE_ERR)
          {
               serverPanic("Unrecoverable error creating server.ipfd file event.");
          }
  }
   // 将事件加入到事件机制中,调用链为 aeCreateFileEvent/aeApiAddEvent/epoll_ctl
   if (server.sofd && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
       acceptUnixHandler,NULL) == AE_ERR)
   /* Open the AOF file if needed. */
   if (server.aof_state == AOF_ON) {
       server.
版权声明:本文为luoxn28原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/luoxn28/p/11096354.html