|
目录
开篇:Redis可以用来做什么?
基础:Redis基础数据结构
string
list
hash
set
zset
容器数据结构通用规则
应用1:分布式锁
应用2:延时队列
异步消息队列
所冲突处理
延时队列的实现
应用3:位图
应用4:HyperLogLog
应用5:布隆过滤器
应用6:简单限流
应用7:漏斗限流
应用8:GeoHash
应用9:Scan
字典结构
最近在看Redis深度历险,做个笔记,方便以后查看
开篇:Redis可以用来做什么?
基础:Redis基础数据结构
Redis的5种基础数据结构:string(字符串),list(列表),hash(哈希),set(不重复集合),zset(有序集合)
string
动态字符串,内部结构类似于java中的ArrayList,预分配冗余空间,当字符串小于1M时扩容为翻倍,大于1M时每次只会多扩容1M。字符串最大长度为512M。
常见用途:缓存用户信息。
> set name codehole
OK
> get name
"codehole"
> exists name
(integer) 1
> del name
(integer) 1
> get name
(nil)
批量操作:
> set name1 codehole
OK
> set name2 holycoder
OK
> mget name1 name2 name3 # 返回一个列表
1) "codehole"
2) "holycoder"
3) (nil)
> mset name1 boy name2 girl name3 unknown
> mget name1 name2 name3
1) "boy"
2) "girl"
3) "unknown"
过期:
设置key过期时间,到点自动删除,后续有具体的介绍
list
相当于java中的LinkedList。插入删除快,索引定位慢。
常见用途:异步队列。
队列:右进左出
> rpush books python java golang
(integer) 3
> llen books
(integer) 3
> lpop books
"python"
> lpop books
"java"
> lpop books
"golang"
> lpop books
(nil)
栈:右进右出
> rpush books python java golang
(integer) 3
> rpop books
"golang"
> rpop books
"java"
> rpop books
"python"
> rpop books
(nil)
Redis底层存储为快速列表quidcklist:
当元素较少的时候使用一块连续的内存,结构为ziplist,数据较多时转为quicklist,quicklist可以理解为链表与ziplist的结合,既满足了快速插入删除性能,又不会太占内存空间。

hash
相当于java中的HashMap,内部结构也与java中的HashMap一致,是数组+链表的二维结构。
与java的HashMap不同的是,Redis的hash的key只能是字符串,并且Redis的rehash的方式不同,Redis采取了渐进式rehash策略。

渐进式rehash会在rehash的同时,同时保留新旧两个hash,查询时同时查询两个hash,在后续的定时任务中以及hash的子指令中,循序渐进的将旧hash的内容一点点的迁移到新hash中。
set
相当于java中的HashSet,内部键值对是无序且唯一,内部实现相当于一个特殊的hash,hash中所有的value都是NULL
应用:可以用来存储活动的中奖名单
zset
类似于java中SortedSet和HashMap的结合,一方面,它是一个set,保证了内部value的唯一性,另一方面可以为每个value赋一个score,代表value的排序权重。内部实现是使用的跳跃列表的数据结构。
查找按HashMap方式查找,插入按二分插入法插入。

容器数据结构通用规则
list/hash/set/zset通用以下规则:
- create if not exists
- drop if no elements
过期:
所有结构都能设置过期时间,过期是以对象为单位,如某个hash结构的过期是整个hash对象的过期,而不是某个key的过期。
需要注意的是如果一个字符串设置了过期时间,再次调用set方法修改它,过期时间会失效。
应用1:分布式锁
使用setnx指令占领锁,通过del释放锁
如果出现异常导致无法释放锁,就有可能导致del没有执行,就会陷入死锁状态,锁永远得不到释放。
可以使用指令expire在拿到锁之后加上一个锁过期时间,可以保证到期后自动释放锁。
如果在setnx和expire中间服务器挂掉了,那同样可能导致expire没有执行,陷入死锁状态。Redis 2.8 加入了set指令的扩展参数,可以使得setnx和expire指令一起执行。
应用2:延时队列
异步消息队列
使用Redis的list来实现,用lpush/rpush进行入队,rpop/lpop出队。
队列空了:通过sleep让线程休息一下。
sleep会导致延迟增加,使用blpop/brpop代替lpop/rpop,b代表blocking,阻塞读,在队列没有数据时进入休眠状态,一旦有数据过来,立刻醒过来。
所冲突处理
加锁失败处理策略:
- 直接排除异常,通知用户稍后重试
- sleep,一会再试
- 将请求转至延时队列,一会再试
延时队列的实现
使用Redis的zset实现,将消息序列化为一个字符串作为zset的value,将消息的到期处理时间设置为score,多个线程轮询zset获取到期任务进行处理,多线程是为了保障可用性。
应用3:位图
应用4:HyperLogLog
HyperLogLog提供不精确的去重计数方案。
指令:pfadd,pfcount,pfmerge
应用5:布隆过滤器
指令:bf.add,bf.exists,bf.madd,bf.mexists

原理:对应到Redis中就是一个大型的位数组以及几个不一样的无偏差hash函数。无偏差就是能够把元素的hash值算的比较均匀。
添加key时,使用多个hash函数对key进行hash算的一个整数索引,然后对数组长度进行取模运算得到一个长度,将位数组中这几个位置都置1。
查询key时,把hash的位置算出来,看看位数组中这几个位置是不是都为1,只要有1位为0,那么这个key不存在,如果都为1,不一定说明这个key一定存在,只说明大概率存在。
应用6:简单限流
应用7:漏斗限流
应用8:GeoHash
Geo算法:将二维经纬度映射到一维整数,所有元素都挂在一条线上。
映射算法:将地球看成二维平面,然后切割成若干小方块,类似围棋盘一样,方块越小越精确。对方块进行整数编码,越靠近编码越接近。切蛋糕法,二刀分四格,00、01、10、11,然后对每个小方块继续二刀分。然后对这个经纬度整数做一个base32编码变成一个字符串。
Redis中,使用52位整数编码,放入zset中,value是元素的key,score是GeoHash的52位整数。通过score排序可以获得附近的人,通过将score还原可以得到原始坐标。
指令:geoadd(添加元素),geodist(计算两个元素之间的距离),geopost(获取元素经纬度坐标),geohash(获取元素经纬度编码字符串),georediusbymember(查找附近的人)
注意事项:如果数据了过多建议对geohash单独使用一个redis,甚至可以对geo数据进行拆分,按国家、省份等拆分成多个redis,这样就可以显著降低单个zset的大小。
应用9:Scan
搜素key:keys指令,无offset、limit参数,Redis单线程,可能造成卡顿
指令scan:
1、复杂度虽然也是 O(n),但是它是通过游标分步进行的,不会阻塞线程; 2、提供 limit 参数,可以控制每次返回结果的最大条数,limit 只是一个 hint,返回的 结果可多可少; 3、同 keys 一样,它也提供模式匹配功能; 4、服务器不需要为游标保存状态,游标的唯一状态就是 scan 返回给客户端的游标整数; 5、返回的结果可能会有重复,需要客户端去重复,这点非常重要; 6、遍历的过程中如果有数据修改,改动后的数据能不能遍历到是不确定的; 7、单次返回的结果是空的并不意味着遍历结束,而要看返回的游标值是否为零。
scan 参数提供了三个参数,第一个是 cursor 整数值,第二个是 key 的正则模式,第三 个是遍历的 limit hint。
字典结构
Redis中所有的key都存在一个很大的hash中,scan指令返回的是一维数据的位置索引,也称为槽(slot),limit表示要遍历的槽位数 |