目录
- 一、基础数据结构
- string
- 结构
- 常用命令(命令不区分大小写)
- 位图
- list
- 结构
- 常用命令(命令不区分大小写)
- hash
- 结构
- 常用命令(命令不区分大小写)
- set
- 结构
- 常用命令(命令不区分大小写)
- zset
- 结构
- 常用命令(命令不区分大小写)
- 二、基本应用
- 分布式锁
- 核心
- 超时问题
- 可重入性
- 锁冲突处理
- 延时队列
- 简介
- 异步消息队列
- 延时队列的实现
- 统计UV
- 简介
- HyperLogLog
- 实现原理
- 基本用法
- 内存占用
- 布隆过滤器
- 简介
- 原理
- 基本用法
- 限流
- 滑动窗口
- 漏斗限流
- Redis-Cell
- 计算地理位置
- Geo简介
- Geo基本指令
- 查找key
- scan(Redis2.8后加入)与keys对比
- 实现原理
- 使用方法
一、基础数据结构
string
结构
内部结构定义源码
/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
根据字符串长度的不同来定义不同的结构来进行内存优化。以sdsgdr32结构为例:
- sdshdr32指字符串长度大于等于216 且长度小于232
- len指字符串长度,不包括结束符’\0’
- alloc指分配的总容量,alloc-len即为冗余长度,不包括header和结束符’\0’
- flags的低三位(leastsignificant bit)存储类型。在sdshdr5中,len最大为25 -1,存储在flags的高五位
- buf [] 是存储字符串的字符数组
字符串的内部结构是字符数组,是动态字符串,内部实现类似Java的ArrayList。采用预分配冗余的方式来防止内存的频繁分配。因此字符串的实际空间一般高于字符串大小。
当冗余空间不足以分配新字符串而引起扩容时:
- 若新字符串长度小于1MB,则冗余空间大小等于字符串大小,实际空间大小为字符串大小的2倍
- 若新字符串长度大于等于1MB,则冗余空间大小为1MB,实际空间大小为字符串大小+1MB
常用命令(命令不区分大小写)
- SET key value [EX seconds|PX milliseconds] [NX|XX] 【键值对。可选条件:(秒|毫秒),(当key不存在时|当key存在时)】
- GET key【取值】
redis> SET mykey "Hello"
OK
redis> GET mykey
"Hello"
redis> SET anotherkey "will expire in a minute" EX 60
OK
- MSET key value [key value …]【批量键值对】
- MGET key [key …]【批量取值】
- DEL key [key …]【删除key,复杂度O(n)】
redis> MSET key1 "Hello" key2 "World"
OK
redis> MGET key1 key2 nonexisting
1) "Hello"
2) "World"
3) (nil)
redis> DEL key1 key2 nonexisting
(integer) 2
- EXPIRE key seconds【设置key几秒后过期】
- TTL key【查询key剩余几秒过期】
redis> SET mykey "Hello"
OK
redis> EXPIRE mykey 10
(integer) 1
redis> TTL mykey
(integer) 10
/*等待十秒*/
redis> TTL mykey
(integer) -2
redis> SET mykey "Hello World"
OK
redis> TTL mykey
(integer) -1
- INCR key【若key为整数则自增1】
- INCRBY key increment【若key为整数则自增x】
redis> SET mykey "10"
OK
redis> INCR mykey
(integer) 11
redis> INCRBY mykey 5
(integer) 16
位图
位图是普通的字符串,也是byte数组。可以用作对一些bool型数据存储时,用bit存储来节省空间,例如存取人365天的签到情况。
字符串he对应的byte数组:
- SETBIT key offset value【设置偏移量对应的值(1|0),偏移量从0开始从左到右计算,返回值是原来的值】
- GETBIT key offset【取偏移量对应的值】
redis> SET mykey he
OK
redis> GETBIT mykey 0
(integer) 0
redis> GETBIT mykey 10
(integer) 1
redis> GET mykey
"he"
redis> SETBIT mykey 0 1
(integer) 0
/*若对应字节不是字符,则会显示其对应16进制形式*/
redis> GET mykey
"\xe8e"
- BITCOUNT key [start end]【查询指定字符范围内1的个数】
- BITPOS key bit [start] [end]【查询指定范围内第一次出现某bit的字符位置】
redis> SET mykey he
OK
/*第一个字符*/
redis> BITCOUNT mykey 0 0
(integer) 3
/*第二个字符*/
redis> BITPOS mykey 1 1 1
(integer) 9
- BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
【管道命令,一次可操作多个位,可连续执行多个命令。overflow指令只影响一次,针对incrby使用,wrap(默认):溢出后折返;sat:溢出后停留在最大值或最小值;fail:溢出后报错不执行】
redis> SET mykey he
OK
/*从第2个位开始,取三位,结果是无符号数*/
redis> BITFIELD mykey get u3 2
1) (integer) 5
/*从第2个位开始,取三位,结果是有符号数*/
redis> BITFIELD mykey get i3 2
1) (integer) -3
list
结构
typedef struct quicklist {
quicklistNode *head; /* 指向头结点 */
quicklistNode *tail; /* 指向尾结点 */
unsigned long count; /* 所有ziplist中节点的总数 */
unsigned long len; /* ziplist的个数 */
int fill : QL_FILL_BITS; /* 单个节点的填充因子 */
unsigned int compress : QL_COMP_BITS; /* 节点压缩深度,0为不压缩 */
unsigned int bookmark_count: QL_BM_BITS;
quicklistBookmark bookmarks[];
} quicklist;
typedef struct quicklistNode {
struct quicklistNode *prev; /* 指向前驱节点 */
struct quicklistNode *next; /* 指向后继节点 */
unsigned char *zl; /* 指向zipList或quickListLZF的指针 */
unsigned int sz; /* ziplist大小(字节)*/
unsigned int count : 16; /* ziplist中的数据项个数 */
unsigned int encoding : 2; /* 编码方式 ziplist=1 quicklistLZF=2 */
unsigned int container : 2; /* 存放数据方式 NONE==1 or ZIPLIST==2 */
unsigned int recompress : 1; /* 节点是否曾被压缩过 */
unsigned int attempted_compress : 1; /* node can't compress; too small */
unsigned int extra : 10; /* 预留空间 */
} quicklistNode;
/* 压缩后的存储结构 */
typedef struct quicklistLZF {
unsigned int sz; /* LZF 大小(字节)*/
char compressed[];
} quicklistLZF;
list的内部结构类似java的linkedlist,其使用的是叫做快速链表的结构(quicklist),快速链表是由多个压缩链表(ziplist)使用双向指针连接的链表,而ziplist是使用一块连续的内存进行存储数据,能减轻内存的碎片化。
常用命令(命令不区分大小写)
- LPUSH key element [element …]【左进队】
- RPUSH key element [element …]【右进队】
- LPOP key【左出队】
- RPOP key【右出队】
- LRANGE key start stop【查询指定下标范围内的元素,复杂度O(n)】
redis> RPUSH mylist "one"
(integer) 1
redis> RPUSH mylist "two"
(integer) 2
redis> RPUSH mylist "three"
(integer) 3
redis> LPOP mylist
"one"
redis> LRANGE mylist 0 -1
1) "two"
2) "three"
- LINDEX key index【查询指定下标元素,复杂度O(n)】
- LTRIM key start stop【保留指定范围内元素,复杂度O(n)】
redis> RPUSH mylist "one"
(integer) 1
redis> RPUSH mylist "two"
(integer) 2
redis> RPUSH mylist "three"
(integer) 3
redis> LINDEX mylist 0
"one"
redis> LTRIM mylist 1 -1
OK
redis> LRANGE mylist 0 -1
1) "two"
2) "three"
hash
结构
typedef struct dict {
dictType *type;
void *privdata; /* 私有数据指针 */
dictht ht[2]; /* 两个hash表 */
long rehashidx; /* rehashidx=1表示未进行rehash */
unsigned long iterators; /* 正在运行的迭代器数量 */
} dict;
typedef struct dictht {
dictEntry **table; /* 哈希表数组 */
unsigned long size; /*哈希表大小*/
unsigned long sizemask; /* 哈希表大小掩码,用于计算索引值,总是等于size-1 */
unsigned long used; /* 该哈希表已有节点个数 */
} dictht;
hash的结构类似java的hashmap(jdk1.7),采用的是数组加链表结构。当hash表需要进行扩容时,使用的策略是渐进式rehash。开辟一张新hash表,每次查询时先查询旧表,若查询到则将此元素返回并rehash到新表中,若旧表未查询到则查询新表。当旧表移除最后一个元素时旧表会被回收。
满足下列条件之一,哈希表会进行扩容操作
- 服务器目前没有在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于1。
- 服务器目前正在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于5。
哈希表的负载因子小于0.1时,程序自动开始对哈希表执行收缩操作。
负载因子=哈希表已保存节点数量/哈希表大小
常用命令(命令不区分大小写)
- HSET key field value [field value …]【hash表添加键值对,可批量】
- HGET key field【hash表获取某个键的值】
- HDEL key field [field …]【hash表删除key,复杂度O(n)】
redis> HSET myhash field1 "foo"
(integer) 1
redis> HGET myhash field1
"foo"
redis> HGET myhash field2
(nil)
redis> HDEL myhash field1
(integer) 1
redis> HDEL myhash field2
(integer) 0
- HMSET key field value [field value …]【hash表批量添加键值对】
- HMGET key field [field …]【hash表批量获取值】
redis> HMSET myhash field1 "Hello" field2 "World"
OK
redis> HMGET myhash field1 field2 nofield
1) "Hello"
2) "World"
3) (nil)
- HKEYS key【列出所有key】
- HLEN key【返回表的长度】
redis> HSET myhash field1 "Hello"
(integer) 1
redis> HSET myhash field2 "World"
(integer) 1
redis> HKEYS myhash
1) "field1"
2) "field2"
redis> HLEN myhash
(integer) 2
set
结构
set类似java中的hashset,元素无序且唯一。
常用命令(命令不区分大小写)
- SADD key member [member …]【添加元素】
- SISMEMBER key member【判断元素是否存在】
- SCARD key【查询元素个数】
redis> SADD myset "one"
(integer) 1
redis> SISMEMBER myset "one"
(integer) 1
redis> SISMEMBER myset "two"
(integer) 0
redis> SADD myset "two"
(integer) 1
redis> SCARD myset
(integer) 2
zset
结构
typedef struct zskiplistNode {
sds ele; /* member名称 */
double score; /* 分值 */
struct zskiplistNode *backward; /* 后退指针 */
struct zskiplistLevel {
struct zskiplistNode *forward; /* 前进指针(从表头->表尾方向)*/
unsigned long span; /* 跨度,同层level两结点距离 */
} level[];
} zskiplistNode;
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;
typedef struct zset {
dict *dict; /* 哈希表 */
zskiplist *zsl; /* 跳跃链表 */
} zset;
zset类似java的SortedSet和HashMap(jdk1.7)结合体。其member是唯一的,按score进行排序。内部实现使用的结构是跳跃列表。
如图,在zset内1-7个value已经按score权重从左到排好序,查找第6个member的顺序是自顶向下跳跃查找,跳过了第五个member,实际上是采用了二分查找的思想,能提高查找效率。
常用命令(命令不区分大小写)
- ZADD key [NX|XX] [CH] [INCR] score member [score member …]
【添加键值对(score内部使用double存储),可批量。可选条件:(当member不存在时|当member存在时),(修改返回值,当操作修改已存在member的score时返回1,否则返回0),(类似ZINCR操作,此时不可批量操作】 - ZREM key member [member …]【删除指定member,可批量删除】
- ZRANGE key start stop [WITHSCORES]【查询指定范围的member,可选附带score】
edis> ZADD myzset 1 "one"
(integer) 1
redis> ZADD myzset 2 "two" 3 "three"
(integer) 1
redis> ZREM myzset "two"
(integer) 1
redis> ZRANGE myzset 0 -1 WITHSCORES
1) "one"
2) "1"
3) "three"
4) "3"
- ZREMRANGEBYSCORE key min max【删除指定score范围内的member(包括min和max,不包括需要加上左括号,-inf表示负无穷大,inf表示正无穷大)】
- ZCARD key【查询zset内元素个数】
redis> ZADD myzset 1 "one"
(integer) 1
redis> ZADD myzset 2 "two"
(integer) 1
redis> ZADD myzset 3 "three"
(integer) 1
redis> ZREMRANGEBYSCORE myzset -inf (2
(integer) 1
redis> ZRANGE myzset 0 -1 WITHSCORES
1) "two"
2) "2"
3) "three"
4) "3"
redis> ZCARD myzset
(integer) 2
二、基本应用
分布式锁
核心
分布式锁的用来限制并发执行,使一个资源同时最多被一个程序抢占。
setnx(set if not exist)+del
setnx+expire
SET key value [EX seconds|PX milliseconds] [NX|XX]。在redis2.8版本后,作者加入了set指令的扩展参数,使setnx和expire指令可以原子性地执行,这个指令是分布式锁的核心所在。
超时问题
redis分布式锁不能解决超时问题。超时问题指的是程序1执行任务的时间过长,任务还未结束,锁的占用却已到期,程序2获得了锁开始执行,程序1执行完毕进行释放锁操作,导致程序2还未执行完毕锁却被程序1释放,程序3又可以趁机取得锁,最终导致临界区代码不能严格串行执行。因此redis分布式锁不应用于较长时间的任务。redis可用lua脚本原子性的解决一些问题。
当前程序释放其他程序锁的解决方案:程序获得锁时设置一个随机的value并记录,解锁时用自己记录下来的value与锁当前的value值校验,相同则说明是自己设置的锁,不相同则不能解锁。
#lua脚本
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
锁提前到期导致同时有两个程序执行临界区代码的解决方案: 启动另一个线程监控锁是否即将到期,若即将到期,持有锁的程序的任务还没执行完,将其延时。
可重入性
可重入性是指一个锁可被同一个线程重复加锁。Redis分布式锁若要支持可重入锁,需对客户端的set方法进行包装,使用线程的Threadlocal变量存储当前持有锁的计数。
锁冲突处理
- 直接抛出异常
这种方式比较适合用户自己发起的请求,若考虑用户体验,可用前端代码自动进行延时重试。 - sleep一会后重试
sleep会阻塞当前的消息队列线程,如果碰撞频繁或队列消息多,sleep并不合适。个别死锁导致加锁不成功,线程会彻底堵死。 - 将请求转移至延时队列,稍后重试
这种方式比较适合异步消息处理,可避开冲突。
延时队列
简介
延时队列是为了做消息中间件的作用。场景的消息队列中间件有Rabbitmq和Kafka等,使用起来较为复杂。Redis对于只有一组消费者的消息队列可以轻松搞定,但它没有非常多的高级特性,没有ack保证,如果对消息可靠性有极高的要求则不适用。
异步消息队列
Redis的list结构常用来实现异步消息队列。可采用一边进一边出的方式来入队出队。
队列空了怎么办▶线程sleep,防止陷入pop的死循环,降低cpu消耗
sleep线程导致延迟增大怎么办▶使用阻塞读,blpop/brpop,阻塞读在没有数据时会立即休眠,一旦数据到来会立即苏醒
阻塞过久导致线程成为空闲连接,服务器主动断开连接怎么办▶客户端捕获到异常要及时重试
延时队列的实现
延时队列可使用Redis的zset结构来实现。将消息序列化成zset的member,到期处理时间为score,然后用多个线程轮询zset获取到期的任务进行处理,多线程是为了保障可用性,一个线程挂了,其他线程可以继续处理,多个线程时需要考虑并发争抢任务的问题,保证任务不会被多次执行。
统计UV
简介
UV(unique vistor) 是需要统计一天之内有多少个用户访问,同一个用户多次访问请求只能计数一次,因此需要去重统计。若使用set存储所有用户ID将非常消耗内存,可使用HyperLogLog进行统计,它只占据12KB的空间,其使用的是依据与伯努利实验的概率学算法,因此并不是精确,标准误差是0.81%。
HyperLogLog
实现原理
- 将字符串hash成比特串。 假设某比特串为1001011000011
- 选取几个低位进行分桶。 假设选取2位,则比特串1001011000011分到第3个桶,共有m=4个桶。
- 从低位到高位找1的位,并判断是否可加入桶中作为其中的最大值k_max。 假设桶的最大比特位为6,比特串最近的1在第5位,5<6可以加入,桶中目前无最大值,因此设置此桶的最大值为k_max=6
- 根据每个桶的k_max求出求和平均数,并代入估算公式,求出估算值。
基本用法
- PFADD key element [element …]【可批量添加元素】
- PFCOUNT key [key …]【计算HyperLogLog中的元素个数】
- PFMERGE destkey sourcekey [sourcekey …]【将多个HyperLogLog的元素合并到一个新的HyperLogLog结构中】
redis> PFADD hll1 a b c
(integer) 1
redis> PFADD hll2 b c d
(integer) 1
redis> PFMERGE hll3 hll1 hll2
OK
redis> PFCOUNT hll1
(integer) 3
redis> PFCOUNT hll3
(integer) 4
内存占用
在Redis内部实现中12KB=16384*6(bit)/8
共2的14次方个桶,即16384个桶,每个桶的maxbits用6个bit来存储,最大可以用63个。
布隆过滤器
简介
新闻客户端推荐新闻时,它会不断地向用户更新新的内容,因此每次推出内容时都需要去重,防止推出看过的新闻。HyperLogLog可以实现去重统计的功能,但不能判断单个元素是否出现过,**布隆过滤器(Bloom Filter)**可以很好的胜任这种过滤问题,但也有一些误判率,旧的内容一定可以判断是旧的,新的内容有可能也会被判断是旧的内容。
原理
布隆过滤器在Redis的数据结构是一个大型的位数组,通过多个无偏hash函数将元素能够较为均匀的映射到多个不同的位置,然后判断这几个位置是否都为1,都为1说明元素已存在,只要有1个0就说明该元素不存在。若要添加元素将会把这几个位置都设置为1,因此有极小的概率出现不同元素可能会映射到的多个位置都相同而引起误判。
如图,key1已加入,由三个hash函数f,g,h确定了三个位置并置为1。此时若要判断key2是否存在,通过三个hash函数计算出三个位置,发现f和h计算的bit位不为1,因此key2不存在。添加时则将这两位置为1。
基本用法
使用前需先下载插件
- BF.ADD key item【添加元素】
- BF.MADD key item [item…]【批量添加元素】
- BF.EXISTS key item【判断元素是否存在】
- BF.MEXISTS key item [item…]【判断多个元素是否存在】
redis> BF.ADD mybf a
(integer) 1
redis> BF.MADD mybf b c d
1) (integer) 1
2) (integer) 1
3) (integer) 1
redis> BF.EXISTS mybf a
(integer) 1
redis> BF.MEXISTS mybf c d
1) (integer) 1
2) (integer) 0
限流
滑动窗口
通过zset维护一个固定的时间窗口,zset仅score值比较重要,member的值唯一即可,可选择将两值都设置为毫秒时间戳。假设规定5秒内最多只有10次操作,判断当前操作是否允许的过程如下:
- zadd(key,now_ts,now_ts) 将当前操作按毫秒时间戳对应毫秒时间戳的方式添加进时间窗口。
- zremrangebyscore(key,0,now_ts-5*1000) 将5秒前的操作舍去,不纳入统计。
- expire(key,6) 避免用户无操作而持续占用内存,设置过期时间为规定时间 +1s。
- zcard(key) 查询当前元素个数,若大于10则不允许当前操作。
漏斗限流
滑动窗口明显的缺点是需要消耗大量的存储空间,因为其需要记录所有操作,比如“限定60s内操作不能超过100万次”,滑动窗口就显得不是那么划算。
漏斗限流,顾名思义就是个漏斗结构。漏斗剩余空间代表当前可以额外持续进行任务的数量,漏嘴的控制加入任务的最大频率。
漏嘴流水的速率 (任务过期的速率) 大于灌水的速率 (添加任务的速率),漏斗则永远装不满。漏嘴流水的速率小于灌水的速率,一旦漏斗满了,就要停止灌水。
Redis-Cell
这是基于Rust语言的Redis的一个限流模块,该模块也使用了漏斗限流算法。
使用前需先自行下载。官方文档使用方法:
CL.THROTTLE {key} {max_burst} {count per period} {period} [{quantity}]
执行命令后将会返回一个结果数组,共包含5个值。
127.0.0.1:6379> CL.THROTTLE user123 15 30 60
1) (integer) 0 # 0表示允许,1表示拒绝
2) (integer) 16 # 漏桶总容量, 为输入值+1
3) (integer) 15 # 漏桶剩余容量
4) (integer) -1 # 如果被拒绝,所需的重试时间
5) (integer) 2 # 多长时间后,漏斗空出所有空间
计算地理位置
Geo简介
Geo模块是Redis3.2版本后添加的内置模块,地理位置常用经纬度表示,GeoHash算法将二维的经纬度映射到一维的整数上,提高了排序的效率。
GeoHash算法对二维平面进行切割,将每个坐标变成一个整数,因此整数越长,精确度越高。接着GeoHash继续对这个整数做一次base32编码,变成一个字符串存在Redis的zset里,zset的member是元素的key,score是使用了52位整数编码的经纬度。
Geo基本指令
- GEOADD key longitude latitude member [longitude latitude member …]【添加元素的经度,纬度,成员名称。可批量添加】
- GEODIST key member1 member2 [m|km|ft|mi]【计算成员之间的距离。可选择单位,默认是米(m)】
- GEOPOS key member [member …] 【返回元素的经纬度】
- GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC]
【计算指定坐标附近的元素。WITHCOORD:返回经纬度,WITHDIST:返回距离,WITHHASH:返回元素GeoHash后的编码值,COUNT :指定返回元素个数,ASC|DESC:升序|降序】
redis> GEOADD mygeo 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis> GEODIST mygeo Palermo Catania
"166274.1516"
redis> GEOPOS mygeo Palermo Catania
1) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "15.08726745843887329"
2) "37.50266842333162032"
redis> GEORADIUS mygeo 15 37 200 km WITHDIST WITHCOORD
1) 1) "Palermo"
2) "190.4424"
3) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "Catania"
2) "56.4413"
3) 1) "15.08726745843887329"
2) "37.50266842333162032"
查找key
scan(Redis2.8后加入)与keys对比
- 两者复杂度都是O(n),scan通过游标进行,不会阻塞线程。
- 两者都提供模式匹配功能。
- 两者返回的结果都有重复。
- scan提供limit参数,可以控制返回结果的最大条数,实际上是限制扫描的槽数,扫描结果可多可少。
- 服务器不需要保存游标个数,游标唯一状态是scan返回客户端的游标整数。
- 遍历时数据被修改不一定能查询到修改后的结果。
- scan返回结果空并不意味着遍历结束,而要看是否游标返回值为0。
实现原理
Redis中所有的key都存储在一个hashmap中,它是一维数组,二维链表的结构,扩容一次空间加倍。
scan返回的游标就是第一维数组的位置索引,将其称为槽(slot),limit参数表示需要遍历的槽位数,scan为了防止扩容和缩容的重复和遗漏。使用了高位加法的顺序进行遍历,遍历时同样会考虑渐进式hash的影响,同时扫描新旧表的槽位,将结果融合后返回。
使用方法
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
【扫描key,返回值为游标值和符合条件的key。可选项:模式匹配串,扫描槽位数,扫描元素类型(6.0版本后可用)】
set,hash,zset也有对应的scan命令:scan,,hscan,zscan
redis 127.0.0.1:6379> scan 0
1) "17"
2) 1) "key:12"
2) "key:8"
3) "key:4"
4) "key:14"
5) "key:16"
6) "key:17"
7) "key:15"
8) "key:10"
9) "key:3"
10) "key:7"
11) "key:1"
redis 127.0.0.1:6379> scan 17
1) "0"
2) 1) "key:5"
2) "key:18"
3) "key:0"
4) "key:2"
5) "key:19"
6) "key:13"
7) "key:6"
8) "key:9"
9) "key:11"