• Redis设计与实现 读书笔记


    Redis单线程
    采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗)
    (1)为什么不采用多进程或多线程处理? - 多线程处理可能涉及到锁 - 多线程处理会涉及到线程切换而消耗CPU
    (2)单线程处理的缺点? - 无法发挥多核CPU性能,不过可以通过在单机开多个Redis实例来完善
     
    Redis的高并发和快速原因
    • Redis是纯内存数据库,一般都是简单的存取操作,线程占用的时间很多,时间的花费主要集中在IO上,所以读取速度快。
    • 再说一下IO,Redis使用的是非阻塞IO,IO多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争。
    • Redis采用了单线程的模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争。
    • Redis全程使用hash结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。
    • Redis采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。
     
    Redis事务
    redis 事务一次可以执行多条命令,服务器在执行命令期间,不会去执行其他客户端的命令请求。
    事务中的多条命令被一次性发送给服务器,而不是一条一条地发送,这种方式被称为流水线,它可以减少客户端与服务器之间的网络通信次数从而提升性能。
    Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操作包围起来。
    • 批量操作在发送 EXEC 命令前被放入队列缓存。
    • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余命令依然被执行。也就是说 Redis 事务不保证原子性。
    • 在事务执行过程中,其他客户端提交的命令请求不会插入到事务执行命令序列中。
     
    redis-cli内存统计命令
    info memory
    1、used_memory redis分配器分配的内存总量,包括使用的虚拟内存(swap)
    2、used_memory_rss redis进程占据操作系统的内存,包括进程运行本身需要的内存、内存碎片等,但是不包括虚拟内存。
    PS:used_memory和used_memory_rss,前者是从Redis角度得到的量,后者是从操作系统角度得到的量。二者之所以有所不同,一方面是因为内存碎片和Redis进程运行需要占用内存,使得前者可能比后者小,另一方面虚拟内存的存在,使得前者可能比后者大。
    由于在实际应用中,Redis的数据量会比较大,此时进程运行占用的内存与Redis数据量和内存碎片相比,都会小得多;因此used_memory_rss和used_memory的比例便成了衡量Redis内存碎片率的参数;这个参数就是mem_fragmentation_ratio。
    3、mem_fragmentation_ratio 内存碎片比率,used_memory_rss/used_memory
    一般大于1,且该值越大,内存碎片比例越大。如果小于1,说明redis使用了虚拟内存。
    PS:一般来说,mem_fragmentation_ratio在1.03左右是比较健康的状态(对于jemalloc来说);刚启动的redis的mem_fragmentation_ratio值很大,是因为还没有向Redis中存入数据,Redis进程本身运行的内存使得used_memory_rss 比used_memory大得多。
    4、mem_allocator redis内存分配器,在编译时指定。libc、jemalloc或者tcmalloc,默认是jemalloc。
     
    redis内存划分
    1、数据
    最主要的部分。在used_memory中统计
    2、进程本身运行需要的内存
    大约几兆,在生产环境中一般忽略不计。这部分内存不是由jemalloc分配,因此不会统计在used_memory中
    3、缓冲内存
    1. 客户端缓冲区:存储客户端连接的输入输出缓冲
    2. 复制积压缓冲区:用于部分复制功能
    3. AOF缓冲区:用于在进行AOF重写时,保存最近的写入命令
    这部分内存由jemalloc分配,因此会统计在used_memory中
    4、内存碎片
    在分配、回收物理内存过程中产生的。
     
    redis数据存储细节
    1、概述
    数据存储细节,涉及到内存分配器、简单动态字符串(SDS)、5种对象类型及内部编码、RedisObject。
    1. redis是key-value数据库,因此对每个键值对都会有一个dictEntry,存储key和value的指针,next指向下一个dictEntry,与本键值对无关。
    2. key 不是直接以字符串存储,二十存储在SDS结构中
    3. redisObject value的存储数据结构,五种对象类型都是通过redisObject存储
    4. jemalloc,内存分配器。dictEntry、redisObject和sds对象的内存都是通过内存分配器分配的。
    2、内存分配器
    Redis在编译时便会指定内存分配器;内存分配器可以是 libc 、jemalloc或者tcmalloc,默认是jemalloc。
    3、redisObject
    RedisObject对象非常重要,Redis对象的类型、内部编码、内存回收、共享对象等功能,都需要RedisObject支持,下面将通过RedisObject的结构来说明它是如何起作用的。
    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;
    1. type 表示对象的类型,占4个比特。目前包括REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。通过命令type key命令获取数据的type
    2. encoding 表示对象的内部编码,占4个比特。5种对象类型对应的编码方式以及转换在后文介绍。
    3. lru 记录对象最后一次被命令程序访问的时间,占据的比特数不同的版本有所不同(如4.0版本占24比特,2.6版本占22比特)。
    通过对比lru时间与当前时间,可以计算某个对象的空转时间;object idletime命令可以显示该空转时间(单位是秒)。object idletime命令的一个特殊之处在于它不改变对象的lru值。lru值除了通过object idletime命令打印之外,还与Redis的内存回收有关系:如果Redis打开了maxmemory选项,且内存回收算法选择的是volatile-lru或allkeys—lru,那么当Redis内存占用超过maxmemory指定的值时,Redis会优先选择空转时间最长的对象进行释放。
    4.refcount refcount记录的是共享对象被引用的次数,类型为整型。refcount的作用,主要在于对象的引用计数和内存回收:
    • 当创建新对象时,refcount初始化为1;
    • 当有新程序使用该对象时,refcount加1;
    • 当对象不再被一个新程序使用时,refcount减1;
    • 当refcount变为0时,对象占用的内存会被释放
    redis的共享对象目前仅支持整数值的字符串对象。之所以如此,实际上是对内存和CPU的平衡:共享对象虽然会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。
    共享对象虽然只能是整数值的字符串对象,但是五种类型都可能使用共享对象。
    就目前实现来说,redis服务器在初始化时,会创建10000个字符串对象,值分别是0~9999的整数值;当redis需要使用这些值对象时,可以直接使用这些共享对象。10000这个数字可以通过调整参数REDIS_SHARED_INTEGERS的值进行改变。(4.0中是OBJ_SHARED_INTEGERS)
    5. ptr ptr指针指向具体的数据,set hello world,ptr指向包含字符串world的SDS对象地址。
    6. 总结,redisObject的结构与对象类型、编码、内存回收、共享对象都有关系;一个redisObject对象的大小为16字节:4bit+4bit+24bit+4Byte+8Byte=16Byte。
    4、SDS
    redis没有直接使用C字符串作为默认的字符串表示,而是使用SDS(Simple Dynamic String)
    1. 结构
    struct sdshdr { int len; int free; char buf[]; };
    buf表示字节数组,用来存储字符串;len表示buf已使用的长度,free表示buf未使用的长度。
    2. SDS与C字符串比较
    SDS增加了free和len字段,带来了如下的好处:
    • 获取字符串长度:SDS是O(1),C字符串是O(n)。
    • 缓冲区溢出:使用C字符串的API时,如果字符串长度增加(如strcat操作)而忘记重新分配内存,很容易造成缓冲区的溢出;而SDS由于记录了长度,相应的API在可能造成缓冲区溢出时会自动重新分配内存,杜绝了缓冲区溢出。
    • 修改字符串时内存的重分配:对于C字符串,如果要修改字符串,必须要重新分配内存(先释放再申请),因为如果没有重新分配,字符串长度增大时会造成内存缓冲区溢出,字符串长度减小时会造成内存泄露。而对于SDS,由于可以记录len和free,因此解除了字符串长度和空间数组长度之间的关联,可以在此基础上进行优化——空间预分配策略(即分配内存时比实际需要的多)使得字符串长度增大时重新分配内存的概率大大减小;惰性空间释放策略使得字符串长度减小时重新分配内存的概率大大减小。
    • 存取二进制数据:SDS可以,C字符串不可以。因为C字符串以空字符作为字符串结束的标识,而对于一些二进制文件(如图片等),内容可能包括空字符串,因此C字符串无法正确存取;而SDS以字符串长度len来作为字符串结束标识,因此没有这个问题。
     
    Redis的对象类型与内部编码
    Redis各种对象类型支持的内部编码如下图所示(图中版本为Redis 3.0):
    1、字符串
    字符串长度不能超过512MB,redis中最基础的类型。键和其他类型的元素都是字符串。
    内部编码应用场景:
    1. int:8个字节的长整型。字符串值是整型时,这个值使用long整型表示。
    2. embstr:<=39字节的字符串。embstr与raw都使用RedisObject和SDS保存数据。区别在于:embstr的使用只分配一次内存空间(因此RedisObject和SDS是连续的),而raw需要分配两次内存空间(分别为RedisObject和SDS分配空间)。因此与raw相比,embstr的好处在于创建时少分配一次空间、删除时少释放一次空间、对象的所有数据连在一起,寻找方便。而embstr的坏处也很明显:如果字符串的长度增加需要重新分配内存时,整个RedisObject和SDS都需要重新分配空间,因此Redis中的embstr实现为只读。
    3. raw:大于39个字节的字符串
    embstr和raw进行区分的长度是39是因为RedisObject的长度是16字节,SDS的长度是9+字符串长度.因此当字符串长度是39时,embstr的长度正好是16+9+39=64,jemalloc正好可以分配64字节的内存单元。
    当int数据不再是整数,或大小超过了long的范围时,自动转化为raw。
    而对于embstr,由于其实现是只读的,因此在对embstr对象进行修改时,都会先转化为raw再进行修改,因此,只要是修改embstr对象,修改后的对象一定是raw的,无论是否达到了39个字节。
    2、列表
    一个列表可以存储2^32-1个元素,redis列表支持两端插入和弹出,并可以获得指定位置的元素,可以充当数组、队列、栈等。
    内部编码:压缩列表ziplist、双端链表linkedlist
    双端链表:同时保存表头指针和表尾指针,链表中保存了列表的长度。
    压缩列表:为了节约内存而开发,由一系列特殊编码的连续内存块组成的顺序型数据结构。进行修改和增删操作复杂度较高。
    压缩列表的使用条件需要同时满足以下两个:1 列表元素个数小于512、列表中所有字符串对象都不足64字节。
    只能由压缩列表转为双端链表,反向则不可能。
    3、哈希
    内部编码:压缩列表ziplist、哈希表hashtable
    压缩列表同列表中的压缩列表
    哈希表:一个dict结构、两个dictht结构、一个dictEntry指针数组(称为bucket)和多个dictEntry结构组成。

    dictEntry
    dictEntry结构用于保存键值对,结构定义如下:
    typedef struct dictEntry{ void *key; union{ void *val; uint64_tu64; int64_ts64; }v; struct dictEntry *next; }dictEntry;
    其中,各个属性的功能如下:
    • key:键值对中的键;
    • val:键值对中的值,使用union(即共用体)实现,存储的内容既可能是一个指向值的指针,也可能是64位整型,或无符号64位整型;
    • next:指向下一个dictEntry,用于解决哈希冲突问题
    • 在64位系统中,一个dictEntry对象占24字节(key/val/next各占8字节)。
    bucket
    bucket是一个数组,数组的每个元素都是指向dictEntry结构的指针。Redis中bucket数组的大小计算规则如下:大于dictEntry的、最小的2^n。例如,如果有1000个dictEntry,那么bucket大小为1024;如果有1500个dictEntry,则bucket大小为2048。
    dictht
    dictht结构如下: typedef struct dictht{ dictEntry **table; unsigned long size; unsigned long sizemask; unsigned long used; }dictht;
    其中,各个属性的功能说明如下:
    • table属性是一个指针,指向bucket;
    • size属性记录了哈希表的大小,即bucket的大小;
    • used记录了已使用的dictEntry的数量;
    • sizemask属性的值总是为size-1,这个属性和哈希值一起决定一个键在table中存储的位置。
    dict
    一般来说,通过使用dictht和dictEntry结构,便可以实现普通哈希表的功能;但是Redis的实现中,在dictht结构的上层,还有一个dict结构。下面说明dict结构的定义及作用。
    dict结构如下:
    typedef struct dict{ dictType *type; void *privdata; dictht ht[2]; int trehashidx; } dict;
    其中,type属性和privdata属性是为了适应不同类型的键值对,用于创建多态字典。
    ht属性和trehashidx属性则用于rehash,即当哈希表需要扩展或收缩时使用。
    ht是一个包含两个项的数组,每项都指向一个dictht结构,这也是Redis的哈希会有1个dict、2个dictht结构的原因。通常情况下,所有的数据都是存在放dict的ht[0]中,ht[1]只在rehash的时候使用。dict进行rehash操作的时候,将ht[0]中的所有数据rehash到ht[1]中。然后将ht[1]赋值给ht[0],并清空ht[1]。
    因此,Redis中的哈希之所以在dictht和dictEntry结构之外还有一个dict结构,一方面是为了适应不同类型的键值对,另一方面是为了rehash。
    使用压缩列表条件同列表类型中一样,hash元素个数小于512、所有键值对的键和值字符串长度都小于64字节。
    有一个条件不满足则从压缩列表转换为哈希表,反向则不行。
    4、集合
    集合与列表不同的两点:元素无序、元素不重复。一个集合最多可以存储2^32-1个元素。支持多个集合取交集、并集和差集。
    内部编码:整数集合intset、哈希表hashtable
    使用哈希表的时候,值全部是null,相当于只使用了键。
    整数集合的结构定义:
    typedef struct intset{ uint32_t encoding; uint32_t length; int8_t contents[]; } intset;
    其中,encoding代表contents中存储内容的类型,虽然contents(存储集合中的元素)是int8_t类型,但实际上其存储的值是int16_t、int32_t或int64_t,具体的类型便是由encoding决定的。length表示元素个数。
    使用整数集合需同时满足两个条件:集合元素个数小于512、所有元素都是整数值。
    有一个不满足则转换为哈希表,反向则不行。
    5、有序集合
    与集合不同的是,有序集合中元素是有顺序的。与列表使用索引下标作为排序依据不同,有序集合为每个元素设置一个分数(score)作为排序依据。
    内部编码:压缩列表ziplist、跳跃表skiplist
    跳跃表是一种有序数据结构,通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的。
    除了跳跃表,实现有序数据结构的另一种典型实现是平衡树;大多数情况下,跳跃表的效率可以和平衡树媲美,且跳跃表实现比平衡树简单很多,因此Redis中选用跳跃表代替平衡树。
    跳跃表支持平均O(logN)、最坏O(N)的复杂点进行节点查找,并支持顺序操作。Redis的跳跃表实现由zskiplist和zskiplistNode两个结构组成:前者用于保存跳跃表信息(如头结点、尾节点、长度等),后者用于表示跳跃表节点。
    使用压缩列表需要满足两个条件:元素个数小于128、所有成员长度都不足64字节
    如果有一个条件不满足则使用跳跃表,反向转换不行。
     
    Redis淘汰策略
     
    Redis持久化
    RDB:快照形式是直接把内存中的数据保存到一个 dump 的文件中,定时保存,保存策略。
    AOF:把所有的对 Redis 的服务器进行修改的命令都存到一个文件里,命令的集合。Redis 默认是快照 RDB 的持久化方式。
    Redis重启后,优先使用AOF文件来还原数据集,AOF保存的数据集通常比RDB更完整。
    RDB原理:redis在需要做持久化的时候,fork一个子进程,子进程将数据写到磁盘的一个临时RDB文件中,在完成写临时文件后,将原来的RDB替换掉,这样的好处是可以copy-on-write。适合用于备份。
    AOF原理:将每一个写命令通过write函数追加到appendonly.aof文件中,配置如下:
    appendfsync yes appendfsync always #每次有数据修改发生时都会写入AOF文件。 appendfsync everysec #每秒钟同步一次,该策略为AOF的缺省策略。
    AOF 可以做到全程持久化,只需要在配置中开启 appendonly yes。这样 Redis 每执行一个修改数据的命令,都会把它添加到 AOF 文件中,当 Redis 重启时,将会读取 AOF 文件进行重放,恢复到 Redis 关闭前的最后时刻。
     
    主从复制
    主负责写操作,从节点提供读操作。
    过程:
    • 从节点执行 slaveof [masterIP] [masterPort],保存主节点信息。
    • 从节点中的定时任务发现主节点信息,建立和主节点的 Socket 连接。
    • 从节点发送 Ping 信号,主节点返回 Pong,两边能互相通信。
    • 连接建立后,主节点将所有数据发送给从节点(数据同步)。
    • 主节点把当前的数据同步给从节点后,便完成了复制的建立过程。接下来,主节点就会持续的把写命令发送给从节点,保证主从数据一致性。
    数据同步过程:
    redis2.8之前使用sync [runId] [offset]同步命令,redis 2.8之后使用psync [runid] [offset]命令。
    sync仅支持全量复制,psync支持全量和部分复制。
    runid 每个redis节点启动都会生成唯一的uuid
    offset 主节点和从节点都各自维护自己的主从复制偏移量offset,当主节点有写入命令时,offset=offset+命令的字节长度。从节点在收到主节点发送的命令后,也会增加自己的offset,并把自己的offset发送给主节点。这样主节点同事保存自己的offset和从节点的offset,通过对比offset来判断主从节点数据是否一致。
    repl_backlog_size 保存在主节点上的一个固定长度的先进先出队列,默认大小是1MB。
    上面是 Psync 的执行流程,从节点发送 psync[runId][offset] 命令,主节点有三种响应:
    • FULLRESYNC:第一次连接,进行全量复制
    • CONTINUE:进行部分复制
    • ERR:不支持 psync 命令,进行全量复制
    全量复制的流程。主要有以下几步:
    • 从节点发送 psync ? -1 命令(因为第一次发送,不知道主节点的 runId,所以为?,因为是第一次复制,所以 offset=-1)。
    • 主节点发现从节点是第一次复制,返回 FULLRESYNC {runId} {offset},runId 是主节点的 runId,offset 是主节点目前的 offset。
    • 从节点接收主节点信息后,保存到 info 中。
    • 主节点在发送 FULLRESYNC 后,启动 bgsave 命令,生成 RDB 文件(数据持久化)。
    • 主节点发送 RDB 文件给从节点。到从节点加载数据完成这段期间主节点的写命令放入缓冲区。
    • 从节点清理自己的数据库数据。
    • 从节点加载 RDB 文件,将数据保存到自己的数据库中。如果从节点开启了 AOF,从节点会异步重写 AOF 文件。
    关于部分复制有以下几点说明:
    ①部分复制主要是 Redis 针对全量复制的过高开销做出的一种优化措施,使用 psync[runId][offset] 命令实现。
    当从节点正在复制主节点时,如果出现网络闪断或者命令丢失等异常情况时,从节点会向主节点要求补发丢失的命令数据,主节点的复制积压缓冲区将这部分数据直接发送给从节点。
    这样就可以保持主从节点复制的一致性。补发的这部分数据一般远远小于全量数据。
    ②主从连接中断期间主节点依然响应命令,但因复制连接中断命令无法发送给从节点,不过主节点内的复制积压缓冲区依然可以保存最近一段时间的写命令数据。
    ③当主从连接恢复后,由于从节点之前保存了自身已复制的偏移量和主节点的运行 ID。因此会把它们当做 psync 参数发送给主节点,要求进行部分复制。
    ④主节点接收到 psync 命令后首先核对参数 runId 是否与自身一致,如果一致,说明之前复制的是当前主节点。
    之后根据参数 offset 在复制积压缓冲区中查找,如果 offset 之后的数据存在,则对从节点发送+COUTINUE 命令,表示可以进行部分复制。因为缓冲区大小固定,若发生缓冲溢出,则进行全量复制。
    ⑤主节点根据偏移量把复制积压缓冲区里的数据发送给从节点,保证主从复制进入正常状态。
     
    主从复制存在的问题:
    • 一旦主节点宕机,从节点晋升为主节点,同时需要修改应用方的主节点地址,还需要命令所有从节点去复制新的主节点,整个过程需要人工干预。
    • 主节点的写能力受到单机的限制。
    • 主节点的存储能力受到单机的限制。
    • 原生复制的弊端在早期的版本中也会比较突出,比如:Redis 复制中断后,从节点会发起 psync。此时如果同步不成功,则会进行全量同步,主库执行全量备份的同时,可能会造成毫秒或秒级的卡顿。
     
    哨兵
    Redis Sentinel(哨兵)主要功能包括主节点存活检测、主从运行情况检测、自动故障转移、主从切换。
    哨兵系统可以执行以下四个任务:
    • 监控:不断检查主服务器和从服务器是否正常运行。
    • 通知:当被监控的某个 Redis 服务器出现问题,Sentinel 通过 API 脚本向管理员或者其他应用程序发出通知。
    • 自动故障转移:当主节点不能正常工作时,Sentinel 会开始一次自动的故障转移操作,它会将与失效主节点是主从关系的其中一个从节点升级为新的主节点,并且将其他的从节点指向新的主节点,这样人工干预就可以免了。
    • 配置提供者:在 Redis Sentinel 模式下,客户端应用在初始化时连接的是 Sentinel 节点集合,从中获取主节点的信息。
     
    哨兵工作原理:
    ①每个 Sentinel 节点都需要定期执行以下任务:每个 Sentinel 以每秒一次的频率,向它所知的主服务器、从服务器以及其他的 Sentinel 实例发送一个 PING 命令
    ②如果一个实例距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 所指定的值,那么这个实例会被 Sentinel 标记为主观下线
    ③如果一个主服务器被标记为主观下线,那么正在监视这个服务器的所有 Sentinel 节点,要以每秒一次的频率确认主服务器的确进入了主观下线状态
    ④如果一个主服务器被标记为主观下线,并且有足够数量的 Sentinel(至少要达到配置文件指定的数量)在指定的时间范围内同意这一判断,那么这个主服务器被标记为客观下线
    ⑤一般情况下,每个 Sentinel 会以每 10 秒一次的频率向它已知的所有主服务器和从服务器发送 INFO 命令。
    当一个主服务器被标记为客观下线时,Sentinel 向下线主服务器的所有从服务器发送 INFO 命令的频率,会从 10 秒一次改为每秒一次。
    ⑥Sentinel 和其他 Sentinel 协商客观下线的主节点的状态,如果处于 SDOWN 状态,则投票自动选出新的主节点,将剩余从节点指向新的主节点进行数据复制。
    ⑦当没有足够数量的 Sentinel 同意主服务器下线时,主服务器的客观下线状态就会被移除。
    当主服务器重新向 Sentinel 的 PING 命令返回有效回复时,主服务器的主观下线状态就会被移除。
     
    PS:
    1 Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
    2 Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。
     
    Redis数据分片机制
    Redis Cluster 采用虚拟哈希槽分区,所有的键根据哈希函数映射到 0 ~ 16383 整数槽内,每个key通过CRC16校验后对16384取模来决定放置哪个槽(Slot),每一个节点负责维护一部分槽以及槽所映射的键值数据。
    计算公式:slot = CRC16(key) & 16383。
    使用哈希槽的好处就在于可以方便的添加或移除节点。
    1. 当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了;
    2. 当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了。0
     
    Pipeline
    redis客户端执行一条命令的过程:
    1 发送命令
    2 命令排队
    3 命令执行
    4 返回结果
    1-4的过程时间被称为RTT 往返时间
    redis提供批量操作命令mget、mset,可以有效地节约RTT(redis往返时间)。但是大部分命令都不支持批量操作。Pipeline能改善这个问题。
    Pipeline能够将命令进行组装,一次RTT传输给Redis,再按这组命令的执行结果按顺序返回给客户端。
    PS:pipeline不是原子操作,实际是多个命令
     
    Bitmaps
    本节将每个独立用户是否访问过网站存放在Bitmaps中,将访问的用户记做1,没有访问的用户记做0,用偏移量作为用户的id。
    setbit key offset value
    getbit key offset
    统计bitcount key [startoffset] [endoffset]
    运算bitop [and|or|not|xor] 目标key 运算key1 运算key2...
     
    GEO
    1 添加位置经纬度
    geoadd key 经度 纬度 地址信息 [经度 纬度 地址信息...]
    2 获取地理位置信息
    geopos key 地址信息 [地址信息...]
    3 获取两个地理位置的距离
    geolist key 地址信息1 地址信息2 单位(m:米,km:公里,mi:英里,ft:尺)
    4 获取指定位置范围内的地理信息位置集合
    georadius key longitude latitude radiusm|km|ft|mi [withcoord] [withdist]
    [withhash] [COUNT count] [asc|desc] [store key] [storedist key]
    georadiusbymember key member radiusm|km|ft|mi [withcoord] [withdist]
    [withhash] [COUNT count] [asc|desc] [store key] [storedist key]
    georadius和georadiusbymember两个命令的作用是一样的,都是以一个地
    理位置为中心算出指定半径内的其他地理信息位置,不同的是georadius命令
    的中心位置给出了具体的经纬度,georadiusbymember只需给出成员即可。
    5 获取geohash
    redis使用geohash将二维经纬度转换为一维字符串,字符串越长表示位置更精准
    6 删除地址信息
    zrem key 地址信息
     
     
     
     
  • 相关阅读:
    React——from
    React——条件渲染
    React——event
    React——组件
    React——JSX
    flask_mail使用
    flask开发restful api
    mysql limit和offset用法
    flask打包安装文件
    flask-sqlalchemy使用及数据迁移
  • 原文地址:https://www.cnblogs.com/prelude1214/p/16055474.html
Copyright © 2020-2023  润新知