学习笔记
Redis的数据结构的编码
常说的Redis五种基本数据结构string、list、hash、set、zset只是对一种对内部编码的包装,实际上每种数据结构都有着自己底层的内部编码,根据不同的场景选择合适的内部编码。例如hash的内部编码有hashtable和ziplist。当达到一定条件时(可以设置这种条件)hash数据结构的内部编码可以从hashtable转换为ziplist,这样就可以节约内存空间。

这样设计的好处:
1. 对于内部编码的改进不会影响到用户使用数据结构,改进内部编码之后不需要改动外部结构和命令。
2. 多种内部编码实现可以在不同场景下发挥各自的优势。例如ziplist比较节省内存,但是在列表元素较多的时候,redis会根据配置将list类型的内部实现转换为linkedlist。
Redis数据结构
string

使用场景:
1. 字符串类型的缓存
2. 计数器
3. 分布式锁:利用 set nx 来争抢锁,抢到之后利用expire来设置过期时间防止忘记释放锁。可以将set nx和expire合成一条命令:set key value nx expire timeout。但是要注意这个timeout和你程序之间的延迟匹配关系,否则可能会出现分布式锁失效的情况。
常用命令:
1. set key value 新增/更新(不论key是否存在)
2. set key value nx 新增(当key不存在时才设置值)
3. set key value xx 更新(当key存在时才设置值)
4. get key 查询
5. mget key1 key2 key3... 查询多个key
6. mset key1 value1 key2 value2... 设置多个key-value
7. incr key 自增1
8. decr key 自减1
9. incrby key count 自增count
10. decrby key count 自减count
hash

field不可以相同,value可以相同
使用场景:
1. 对象的缓存:与string相比hash可以单独设置field中的值,或者添加新的field,这对于缓存一个对象来说,修改对象的值不需要像string中先将对象反序列化,再修改值,最后再序列化存入缓存中这样麻烦。
常用命令:
1. hget key field 获取key对应的field的value
2. hgetall key 获取key对应的所有field的value
3. hdel key field 删除key对应的field
4. hmget key field1 field2... 获取key中多个field的值
5. hmset key field1 value1 field2 value2... 设置key中多个field的值
list

有序,value可以重复,相当于一个双向链表
使用场景:
1. 时间轴:例如微波刷消息,key可以是用户名,value是一条条的微博
2. 栈:LPUSH + LPOP
3. 队列:LPUSH + RPOP (RPUSH + LPOP)
4. 有固定数量的列表:LPUSH + LTRIM (RPUSH + LTRIM)
5. 阻塞队列:LPUSH + BRPOP (RPUSH + BLPOP)
常用命令:
1. rpush key value1 value2... 从右端插入值
2. lpush key value1 value2... 从左端插入值
3. rpop key 从右端弹出一个值
4. lpop key 从左端弹出一个值
5. ltrim key startindex endindex 按照索引范围修剪列表
6. blpop key timeout 阻塞弹出
7. brpop key timeout 阻塞弹出
set

value无序,不可重复,相当于一个HashSet,支持集合间的操作。其实set就是一个
使用场景:
1. 抽奖:可以使用spop,或者srandmember
2. 贴标签
3. 点赞,看谁点了赞
4. 社交分类
常用命令:
1. sadd value1 value2... 向集合key中添加value(如果value已经存在则添加失败)
2. srem key value 将集合key中的value移除
3. srandmember key count 随机返回集合key中count个项,但不会把这些项从集合中删除
4. spop key 从集合中随机弹出一个项,并从集合中删除这个项
5. sdiff key1 key2 求差集
6. sinter key1 key2 求交集
7. sunion key1 key2 求并集
zset

有序集合,根据score排序,value不可重复,根据score从小到大排序
使用场景:
1. 排行榜
常用命令:
1. zadd key score1 value1 score2 value2... 添加元素
2. zrem key value1 value2... 删除元素
3. zscore key value 获取value对应的score
4. zincrby key incrscore value 给指定的value的score增加incrscore
5. zrank key value 获取集合中value的排名(根据score从小到大排序)
6. zrevrank key value 获取集合中value的排名(根据score从大到小排序)
7. zrange key start end withscore 根据排名范围打印,withscore是可选项,是否要把score也返回
8. zcount key minscore maxscore 返回指定score范围内的个数
bitmap

使用场景:
1. 独立用户统计
常用命令:
1. setbit key offset value 给位图指定索引处设置值,value只能是0或1
2. getbit key offset 获取位图指定索引的值
3. bitcount key start end 获取位图指定范围内值为1的个数
4. bitop op destkey key1 key2... 做多个位图的and(交集)、or(并集)、not(非)、xor(异或)操作,并将结果保存到destkey中
hyperloglog
基于HyperLogLog算法,用极小的空间完成独立用户的统计,但数据多的时候是会出错的,不能取出单条数据,本质是string
使用场景:
1. 独立用户统计
常用命令:
1. pfadd key value1 value2... 向hyperloglog中添加元素
2. pfcount key 计算hyperloglog的独立总数
3. pfmerge destkey sourcekey1 sourcekey2 合并多个hyperloglog
geo

地理信息定位,存储经纬度,计算两地距离,范围计算,本质上是zset,使用geohash技术进行填充。redis中将经纬度使用52位的整数进行编码,放入zset中,score就是geohash的52位整数值。在使用redis进行geo查询时,其实就是zset(skiplist)的操作,通过zset的score进行排序可以得到坐标附近的元素,再将score还原成坐标经纬度返回。
二维平面坐标点---->一维整数编码值---->zset(score为编码值)---->zrangebyrank(获取score相近的元素)、zrangebyscore---->通过score反解坐标---->返回
常用命令:
1. geoadd key longtitude latitude member 增加位置,member是标识
2. geopos key member 获取地理位置
3. geodist key member1 member2 unit 获取两个地理位置的距离,unit是单位
4. georadius 获取指定位置范围内的地理信息集合
5. georadiusbymember 获取指定位置范围内的地理信息集合
5. zrem key member 删除
stream(Redis5.0)
内存版kafka。主要是一个append only的数据结构。底层数据结构是radix tree。具体的有点复杂,可以去看参考中的内容
内部编码
int
当一个key的value是整数时,redis就将他编码为int
embstr和raw
string的长度小于44时是embstr,大于44时是raw。embstr将string类型的数据保存在一块连续的内存,而raw是有一块不连续的内存存储数据,同时有一个指向这个内存的指针。

这样embstr释放内存,就一次。raw释放需要释放两个地方。分配内存的时候,embstr分配一次,raw分配两次。
ziplist
压缩列表(字面理解)。一个普通的双向链表,链表中每一个项都占用独立的一块内存,各项之间用地址指针连接,会造成内存碎片,而且会有额外的内存来存储指针。而ziplist将每一项存放在连续的地址空间,一个ziplist独占一块内存。
linkedlist
双向链表,相当于Java中的LinkedList。
hashtable
相当于Java中的HashMap。
intset
整型数组,独占一整块内存,是一个有序的整型数组,查找时利用二分查找。
skiplist
跳表。我理解是类似于一个二级索引,先去head里找到范围,然后顺着这个head去找你要的值。
