redis学习

redis 学习

redis 应用场景
记录帖子的点赞数,评论数和点击数(hash)
记录用户的帖子id列表(排序),便于快速显示用户的帖子列表(zset)
记录帖子的标题,摘要,作者和封面信息,用于列表页展示(hash)
记录帖子的点赞用户id列表,评论id列表,用于显示和去重计数(zset)
缓存近期热帖内容,减少数据库压力(hash)
记录帖子的相关文章id,根据内容推荐相关帖子(list)
如果帖子id是整数自增的,可以使用redis来分配帖子id(计数器)
收藏和帖子之间的关系(zset)
记录热榜帖子id列表,总热榜和分类热榜(zset)
缓存用户行为历史,过滤恶意行为(zset,hash)

总的来说,
非强一致性(强一致性就要牺牲可用性)
无复杂逻辑计算
存储的数据量不大

redis安装

docker方式
docker pull redis
docker run --name myredis -d -p 6379:6379 redis

实际安装
mkdir -p ~/redis ~/redis/data ~/redis/conf

docker run -p 6379:6379 --name redis5.0 -v /home/docker-redis/data:/data -v /home/docker-redis/conf:/etc/redis/redis.conf  -d redis:5.0 redis-server /etc/redis/redis.conf/redis.conf --appendonly yes

基本使用

5种数据结构,不同数据结构的差异在value
string/list/hash/set/zset

string 内部表示就是一个字符数组,是动态字符串,是可以修改的字符串
字符串小于1m时,扩容都是加倍现有的空间,超过1m时,扩容时一次只会多扩1m的空间
字符串最大长度为512m

键值对

set name1 aaa
get name1
批量键值对
set name1 aaa
set name2 bbb
mget name1 name2 //返回一个列表

mset name1 aaa name2 bbb
mget name1 name2

expire name1 5 //5秒后过期

setex name 5 aaa //5秒后过期,等价于上面

setx name aaa //如果name不存在就执行创建,存在就创建不成功,不改变原来的

自增

set age 30
incr age //31
incrby age 5 //36
incrby age -5 //31
范围在signed long的最大值(9223372036854775807)和最小值

list

链表结构(双向指针)
rpush books python java golang
lpop book

rpush books python java golang
rpop books

慢操作
rpush books python java golang
lindex books 1
lrange books 0 -1
ltrim books 1 -1

快速链表
ziplist 压缩列表,数据量多的时候才会改成quicklist
将多个ziplist使用双向指针串起来使用

hash

无序字典
数组+链表二维结构,第一维hash的数组位置碰撞时,就会将碰撞的元素使用链表串接起来
渐进式rehash
单独存储字符串,减少网络流量
hash结构的存储消耗要高于单个字符串
hset books java “think in java”
hset books golang “go”
hset books python “python cookbook”
hgetall books
hlen books //3
hget books java
hset books golang “learning go programming” //更新操作,返回0
hmset books java “effective java” python “learning python” golang “learning golang” //批量set

hset test age 29
hincrby test age 1

set

内部的键值对是无序的,唯一的
sadd books python
sadd books python
sadd books java golang
smembers books //无序
sismember books java //查询某个value是否存在
scard books //获取长度
spop books //弹出一个

zset

sortedset + HashMap 每个value赋予一个score,代表value的排序权重,跳跃列表的数据结构
zadd books 9.0 “java 0”
zadd books 8.0 “java 1”
zadd books 7.0 “java 2”
zrevrange books 0 -1 //逆序列出
zcard books //个数
zscore books “java 0” //获取指定value的score,double类型,存在精度问题
zrangebyscore books 0 9.0 //根据分值区间遍历
zrangebyscore books -inf 8.0 withscores //根据分值区间遍历,同时返回分值
zrem books “java 1” //删除value

容器类型数据结构通用规则

1,如果容器不存在就创建一个,比如rpush 没有就自动创建一个
2,如果容器没有元素就立即删除容器释放内存,lpop操作到最后一个元素,列表消失

过期时间
过期是以对象为单位的

如果一个字符串已经设置了过期时间,然后调用set方法修改了它,它的过期时间会消失
ttl key

分布式锁

setnx xxx true
…业务逻辑
del xxx

setnx xxx true
//这里可能出问题,因为不是一个事务
expire xxx 5
…业务逻辑
del xxx

redis2.8之后
set xxx true ex 5 nx //setnx和expire组合在一起的原子锁
…业务逻辑
del xxx

超时问题
redis分布式锁不要用于较长时间的任务
lua脚本可以保证连续多个指令的原子性执行,方案不完美

delifequals

if reids.call(“get”, KEYS[1]) == ARGV[1] then
return redis.call(“del”, KEYS[1])
else
return 0
end

主从模式下会出现问题
使用redrocks
需要提供多个redis实例,加锁时向过半节点发送加锁指令
释放锁时需要向所有节点发送del指令
性能下降一些
需要高性能场景才使用

加锁失败策略
1,直接抛异常
2,睡眠
3,请求转移到延时队列

延时队列

redis的消息队列没有ack
list,用rpush和lpush操作入队列,用lpop和rpop操作出队列
空队列会出现空轮询
1,让线程休眠一秒
2,blpop/brpop 替换上面的入队出队 //阻塞读
空闲连接会导致服务器主动断开连接,blpop/brpop抛出异常,所以客户端要捕获异常并重试

延时队列实现,使用多线程和原子锁

位图

零存整取
byte数组,先低位后高位
setbit s 1 1
setbit s 2 1

get s //“he”

零存领取
setbit w 1 1
get bit w 1 //1

整车领取
set w h
getbit w 1 //1

如果是不可打印的字符,显示十六进制

统计和查找

bitcount 用来统计指定位置范围内1的个数
bitpos 用来查找指定范围内出现的第一个0或1
set w hello
bitcount w //21
bitcount w 0 0 //第一个字符中1的位数
bitcount w 0 1 //前两个字符中1的位数
bitpos w 0 //第一个0位
bitpos w 1 //第一个1位
bitpos w 1 1 1 //从第二个字符算起,第一个1位
bitpos w 1 2 2 //从第三个字符算起,第一个1位

魔术指令

bitfield,get/set/incrby
都可以对指定位片段进行读写
最多只能处理64个连续的位
超过64个连续的位,就得使用多个子指令
set w hello
bitfield w get u4 0 //从第一个位开始取4个位,结果是无符号数
bitfield w get i4 0 //从第一个位开始取4个位,结果是有符号数
bitfield w get u4 0 get u3 2 get i4 0 get i3 2
bitfield w set u8 8 97
incrby
set w hello
bitfield w incrby u4 2 1 //从第三个位开始,对接下来的4位无符号数+1
溢出策略
饱和截断,停留在最大或最小值
bitfield w overflow sat incrby u4 2 1
失败不执行
bitfield w overflow fail incrby u4 2 1

HyperLogLog

提供不精确的去重计数方案
pfadd 和 pfcount
增加计数和获取计数
pfadd test user1
pfcount test //1
pfadd test user1 user2 user3
pfmerge
用于将多个pf计数值累加在一起形成一个新的pf值
稀疏矩阵存储
实现原理
记录低位连续零位的最长度,通过K值估算出随机数N
内存占用12K
16384个桶,每个桶需要6bit,最大可以表示maxbit=63,内存占用=2^14*6/8=12k

布隆过滤器

推送去重问题
爬虫去重
数据库查询内存过滤不存在的row请求
邮箱过滤
解决去重问题,有一定的误判率
bf.add
bf.exists
bf.madd # 一次性添加多个
bf.mexists #一次查询多个元素是否存在
bf.reserve指令显示创建,key,error_rate(误判率,越小需要的存储空间越大),initial_size(初始大小, 比这个大误判率升高)
java->createFilter
布隆过滤器原理
位数组+无偏hash函数
空间占用估计
k=0.7*(1/n)
f=0.6185^(1/n)
实际元素超出时,误判率会怎么样变化
f=(1-0.5t)k # t表示实际元素和预计元素的倍数,k是hash函数的最佳数量
redis4.0之前,替换版本,python 是 pyreBloom, java是orestes-bloomfilter

简单限流

zset + pipe 实现
不适合限制的量很大,浪费内存

String key = String.format(“hist:%s:%S”, userId, actionKey);
long nowTs = System.currentTimeMills();
Pipeline pipe = jedis.pipelined();
pipe.multi();
pipe.zadd(key, nowTs, “” + nowTs);
pipe.zremrangeByScore(key, 0, nowTs - period * 1000);
Response count = pipe.zcard(key);
pipe.expire(key, period + 1);
pipe.exec();
pipe.close();
return count.get() <= maxCount;

漏斗限流

redis-cell
cl.throttle key 15 30 60 1 # key 容量 15/30(速率) 最小空间(默认是1)
0 # 0允许,1拒绝
15 # 容量
14 # 剩余空间
-1 # 如果被拒绝,多久后再试(才有空间)
2 # 多长时间后漏斗完全空出来

GeoHash算法

地理位置Geo模块
寻找附近的xx
地理位置距离排序算法

geoadd # 增加
geoadd company 116.48105 39.996794 aaa
geoadd company 116.48105 39.996794 bbb
geoadd company 116.48105 39.996794 ccc
geoadd company 116.48105 39.996794 ddd 116.48105 39.996794 eee
geodist #距离

geodist company aaa bbb km # 单位可以是m,km,ml,ft(米/千米/英里/尺)

geopos # 获取任意元素的经纬度
geopos company aaa
geopos company bbb ccc

geohash # 获取元素的经纬度编码的字符串,base32编码,可以在http://geohash.org/${hash}上直接定位
geohash company aaa
geohash company bbb

georadiubymember #查询指定元素附近的其他元素,参数复杂
georadiubymember company aaa 20 km count 3 asc # 范围20公里以内最多3个元素按距离正排,不排除自身
georadiubymember company bbb 20 km count 3 desc
withcoord,withdist 显示距离,withhash
georadiubymember company aaa 20 km withcoord withdist withhash count 3 asc

georadius # 根据用户的定位来计算附近的车,餐馆等, 目标元素改成经纬度坐标值
georadius company 116.514202 39.905409 20 km withdist count 3 asc

geo的数据建议使用单独的redis实例部署,数据量大可以按国家拆分,按省拆分,按市拆分,按区拆分

sacan
游标分步进行,不会阻塞线程

keys 列出所有的key,可以使用正则字符串
keys *
keys aaa*
keys aaa*bbb

提供limit参数,限定服务器单次遍历的字典槽数量(约等于)
返回的结果可能会重复,需要客户端去重
遍历的结果可能不是最新修改后的
遍历是否结束要看游标是否为零

scan 0 match key99* count 1000
scan 13976 match key99* count 1000
scan 1996 match key99* count 1000
scan 11687 match key99* count 1000

  1. “0”
    1. “key9969”

因为key是hash+链表挂载,limit是遍历链表挂载的元素匹配后返回给客户端

遍历顺序采用高位进位加法来遍历,避免扩缩容时重复遍历

扩容时采用渐进式rehash

scan可以指定容器集合进行遍历

在平时的业务开发中,要尽量避免大key的产生(内存大起大落)

定位大key
redis-cli -h 127.0.0.1 -p 7001 --bigkeys
redis-cli -h 127.0.0.1 -p 7001 --bigkeys -i 0.1 #秒 间隔多久扫一下

原理

线程io模型

单线程多路复用(nio事件轮询)
指令队列
响应队列
定时任务
定时任务记录放在最小堆

通讯协议

文本协议
resp(redis serialization protocol) 序列化协议
5种最小单元类型,单元结束时统一加上回车换行符号\r\n
1,单行字符串以"+“符号开头
2,多行字符串以”$“符号开头,后跟整数的字符串形式
3,整数值以”:“符号开头,后跟整数的字符串形式
4,错误消息以”-“符号开头
5,数组以”*"号开头,后跟数组的长度
NULL用多行字符串表示,不过长度要写成-1
$-1\r\n
空串用多行字符串表示,长度填0
$0\r\n\r\n
客户端—>服务器
set author aaa
服务器 —> 客户端
响应支持多种数据结构
单行响应
多行响应
整数响应
数组响应
错误响应

持久化

第一种是快照,快照是全量备份,是内存数据的二进制序列化形式(rdb snapshot)
快照原理,多进程copy on write, cow机制来进行数据段页面的分离,然后父进程修改的是被分离的页面
第二种是AOF,AOF是连续的增量备份,是内存数据修改的指令记录文本,需要定期重写,给日志瘦身
AOF存储的是redis服务器的顺序指令序列
AOF重写,开辟子进程对内存进行遍历,写到新文件,追加操作期间的日志后替换原来的文件
fsync函数将内存缓存强制刷到磁盘, 配置1秒刷一次

主节点一般不进行持久化操作,从节点进行持久化
两个从节点可以保证数据不轻易丢失

redis4.0混合持久化
rdb和aof日志文件存在一起

管道

本质上是客户端提供的,跟服务的没有什么关系
客户端改变了读写的顺序
读操作直接从本地内核的接收缓冲中读取,没有数据才阻塞
写操作要等待消息经过网络路由到目标机器处理后的响应消息,再送回到当前的内核读缓存才可以返回

事务

事务模型不严格

multi
incr books
exec

原子性
不具备原子性,只是隔离的串行化操作

exec之前丢弃
discard

优化
pipeline 多次io压缩为单词io
pipe = redis.pipeline(transaction=true)
pipe.multi()
pipe.incr(“books”)
pipe.incr(“books”)
values = pipe.execute()

watch
乐观锁
监视变量,如果变量被修改了返回NULL
multi之前监视

redis事务不支持回滚

小道消息PubSub

消息多播
生产publish(“test”, “aaa”)
消费
p = client.pubsub()
p.subscribe(“test”)

阻塞消费者
p.listen()

模式订阅
subscribe aaa.image aaa.text aaa.log
publish aaa.image xxx
匹配一次订阅多个
psubscribe aaa.*

返回的消息结构
{‘patrern’: None, ‘type’: ‘subscribe’, ‘channel’: ‘aaa’, ‘data’: 1L}
data 一个字符串
channel 主题名字
type 消息类型
pattern 那种模式订阅

缺点
消费者断连会丢消息
redis停机重启消息也会直接丢弃

多播单独的项目 Disque

被stream(持久化消息队列)代替了


小对象压缩

redis的ziplist是一个紧凑的字节数组结构
redis的intset是一个紧凑的字节数组结构

redis支持set集合动态从uint16升级到unit32再到unit64
如果set里面存储的是字符串,sadd立即升级为hashtable结构
小对象存储结构超过限制条件会改变存储结构

内存回收机制

以操作系统页为单位来回收内存,删除后使用flushdb才会立即回收,否则会重用页

内存分配算法

默认使用jemalloc(facebook),可选tcmalloc(google)
info memory 可以查看使用了什么内存分配算法

集群

主从同步

cap原理概括: 当网络分区发生时,一致性和可用性两难全
保证最终一致性

主从同步与从从同步

增量同步
redis同步指令流,记录到本地内存buffer,一个定长的环形数组,满了会覆盖,所以主从同步会丢失

快照同步
bgsave 将当前内存的数据全部快照到磁盘文件中,然后再将快照文件的内容全部传送到从节点
从节点接收完毕立即执行一次全量加载,加载之前先要将当前内存的数据清空,加载完毕后通知主节点继续增量同步
如果同步时间过长或复制buffer太小,都会导致同步期间的增量指令在复制buffer中被覆盖,
会导致快照同步后无法进行增量复制,再次发起快照同步,陷入死循环
需要配置一个合适的复制buffer大小参数

增加从节点
加入集群必须先要进行一次快照同步

无盘复制
redis2.8.18开始支持,主服务器直接通过套接字将快照内容发送到从节点,主节点一遍遍历内存,一遍将序列化的内容发送到从节点

wait指令
让一步复制变成同步复制
wait 1 0 第一个参数是从库的数量N,第二个参数是时间t(毫秒),t为0时如果发生网络分区,主从同步无法继续进行,wait指令会永远阻塞
redis服务器不可用

如果使用了redis的持久化功能,需要认真对待主从复制,保障数据一致性

哨兵

无法保证消息完全不丢失,但是也尽可能保证消息少丢失
min-slaves-to-write 1 # 至少有一个从节点在进行正常复制
min-slaves-max-lag 10 # 如果10s没有收到从节点的反馈,就意味着从节点同步不正常,要么网络断开,要么一直没有给反馈
搭建一套redis-sentinel集群

codis

代理分片
将所有的key默认划分为1024个槽位,crc32运算计算哈希值,再将hash后的整数值对1024这个整数进行取模得到一个余数,这个余数就是对于key的槽位

同步槽位信息,将槽位关系存储在zk,监听变化并重新同步槽位关系
扩容时寻找redis槽位,改造redis,增加了slotsscan指令,扫描后迁移每个key到新的redis节点
自动平衡slots
不支持事务
hash的kv太多会带来迁移卡顿
mget 汇总结果

cluster

三个redis节点组成,相互连接,特殊的二进制协议相互交互集群信息
每个节点负责一部分槽位

槽位定位算法
默认对key值使用crc32算法进行hash得到一个整数值,然后用这个整数值对16384进行取模来得到具体槽位
允许嵌入tag来允许用户强制某个key挂在特定槽位上

迁移
redis-trib可以手动调整槽位的分配
槽为单位迁移,一次性获取源节点槽位的所有key列表,再每个key进行迁移
源节点获取内容—>存到目标节点—>从源节点删除内容
迁移影响服务效率

容错
提供了一个参数cluster-require-full-coverage可以允许部分节点故障,其他节点还可以继续提供对外访问

网络抖动
cluster-node-timeout,表示当某个节点持续timeout的时间失联时,才可以认定该节点出现故障
cluster-slave-validity-factor作为倍乘系数来放大这个超时时间来宽松容错的紧急程度,为零不会抗拒网络抖动,大于1主从切换变为松弛系数

可能下线与确定下线
集群采用gossip协议来广播自己的状态以及自己对整个集群认知的改变,比如失联节点数达到大多数,通知其他节点下线并立即对该节点进行主从切换
不支持事务

槽位迁移感知
moved纠正槽位
asking临时纠正槽位
重试超过次数会抛出异常

集群变更感知
目标节点挂了,抛出ConnectionError,紧接着随机挑一个节点来重试
手动修改集群信息,将master切换到其他节点,旧节点的指令会收到ClusterDown的错误

stream

支持多播的可持久化的消息队列
消息链表,可持久化.xadd指令追加
每个stream可以挂多个消费组
xgroup create 创建消费组
状态变量pending_ids
xadd
xdel
xrange 自动过滤已删除
xlen
del
xadd aaa * k v //*表示自动生成id

独立消费
xread

xinfo stream groupName
组内消费
xreadgroup
xask
xinfo consumers groupname 消息id
提供定长功能
xadd aaa maxlen 3 * name aaa age 18
消息忘记ask会导致PEL列表不断增长占用内存
PEL如何避免消息丢失,保存了发出去的消息id
分配多个stream支持分区

info

1,server服务器运行的环境参数
2,client客户端相关信息
3,memory服务器运行内存统计数据
4,persistence持久化数据
5,stats通用统计数据
6,replication主从复制相关信息
7,cpu使用情况
8,cluster使用情况
9,keyspace键值对统计数量信息
info
info memory
info replication
redis-cli info stats | grep ops //每秒操作数,极限是10w/s
redis-cli monitor //ctrl+c中断输出
redis-cli info clients
如果出现rejected_connections,需要调整maxclients
redis-cli info stats | grep reject
redis-cli info memory | grep used | grep human //查看内存占用
redis-cli info replication | grep backlog //查看积压缓存区大小repl_backlog_size,影响主从复制效率
redis-cli info stats | grep sync //sync_partial_err //半同步失败次数

分布式锁

redlock
加锁时,它会向过半节点发送set(key,value,nx=True,ex=xxx)
释放锁时,向所有节点发送del指令

过期策略

将设置了过期时间的key放入到一个独立的字典中,定时遍历这个字典来删除到期的key
惰性删除,客户端访问的时候,检查过期时间,过期就立即删除
默认每十秒进行十次过期扫描
1,从字典中随机20个key
2,删除20个key中已经过期的key
3,如果过期的key比率超过1/4,重复步骤1
扫描时间上限默认不会超过25ms
从库不会进行过期扫描,主库key过期会在aof文件里增加一条del指令,同步到从库,从库通过执行这条del指令来删除过期的key
主从数据不一致

lru

生产环境中是不允许redis出现交换行为的,redis提供maxmemory来限制内存超过期望大小
当实际内存超出maxmemory时,redis提供了几种可选策略来决定如何腾出空间

noeviction 默认策略,可读不可写
volatile-lru 尝试淘汰设置了过期时间的key,最少使用的key优先被淘汰,没有设置过期时间的key不会被淘汰
volaile-ttl 尝试淘汰key剩余寿命ttl小的值,越小越优先被淘汰
volatile-random 随机淘汰过期key集合中随机的key
allkeys0lru 尝试淘汰所有最少使用的key优先被淘汰
allkeys-random 尝试随机淘汰所有key

redis使用近似lur算法(因为lru算法耗内存)
给每个key增加一个额外的小字段,长度为24bit,也就是最后一次被访问的时间戳
懒惰处理
算法是随机采样出5个(可以配置)key,然后淘汰掉最旧的key


懒惰删除

大key异步删除,小key直接删除
unlink key
清空数据库
flush async
aof 刷数据到硬盘也是异步处理

slave-lazy-flush 从库接受完rdb文件后的flush操作
lazyfree-lazy-eviction 内存达到maxmemory时进行淘汰
lazyfree-lazy-expire key 过期删除
lazyfree-lazy-server-del rename 指令删除destKey

jedis

jedis pool
需要注意获取jedis对象后报错要归还连接池
封装jedis
获取失败重试机制

安全

指令安全
修改不安全指令,比如keys,flushdb,flushall
rename-command keys icomfiregetallkeys
rename-command flushall “” //无法通过任何字符串来执行
端口安全
redis指定监听ip地址
配置密码
requirepass xxx
从库
masterauth xxx
脚本安全
禁止lua脚本由用户输入的内容(ugc)生成

spiped安全通讯 公网传输

brew install spiped
apt-get install spiped
yum install spiped
docker run -d -p127.0.0.1:6379:6379 --name redis-server-6379 redis
生成随机的密钥文件
使用密钥文件启动服务器spiped进程
使用密钥文件启动客户端spiped进程
启动客户端连接

源码

字符串内部结构

字节数组的形式
struct SDS {
T capacity; //数组容量
T len; //数组长度
byte flags; //特殊标识位
byte[] content; //数组内容
}
不同长度的字符串使用不同的结构体表示
长度短 emb
长度>44 raw

redis 对象头结构体
struct RedisObject {
int4 type; //4bits
int4 encoding; //4bits
int24 lru; //24bits
int32 refcount; 4bytes
void *ptr; //8bytes, 64-bit system
} robj;

struct SDS {
int8 capacity; //1byte
int 8 len; //1byte
int flags; //1byte
byte[] content; //内联数组,长度为capacity
}
embstr = malloc(redisObj + SDS)
大于64字节使用raw

扩容策略
字符串长度小于1m之前,采用加倍策略
超过1m,每次扩容只会多分配1m大小的冗余空间

字典

struct RedisDb {
dict* dict; //all keys
dict* expires; //allexpired keys

}
struct zset {
dict* dict; //allvalues
zskiplist* zsl;
}
两个hashtable(新旧)进行迁移
数组+链表
struct dictEnty {
void* key;
void* val;
dictEntry* next;
}
struct dictht {
dictEntry** table; //二维
long size; //第一维数组的长度
long used; //hash表中的元素个数
}
渐进式rehash
扩容,执行指令后触发小步搬迁,或者服务器定时任务触发

查找过程
key映射成整数

hash函数
siphash

hash攻击
存在偏向性会导致hash第二维链表长度极不均匀

扩容
正常情况下hash表中元素的个数等于第一维数组的长度时就会开始扩容,扩两倍
如果正在做bgsave,为了减少内存页的过多分离(cow),元素个数达到第一维数组长度的5倍才会强制扩容

缩容
缩容的条件是元素个数低于数组长度的10%,缩容不会考虑redis是否正在做bgsave
redis的set的结构底层实现也是字典,只不过所有的value都是null

压缩列表内部

zset和hash容器对象在元素个数较少的时候,采用压缩列表(ziplist)进行存储
压缩列表是连续的内存空间
debug object books
以你为是连续存储,所有不适合占据内存太大,因为重新分配内存和拷贝内存就会有很大的消耗

级联更新
IntSet
小整形存储

快速列表内部

quicklist
ziplist和linkedlist的混合体,将linkedlist按段切分,每一段使用ziplist来紧凑存储,多个ziplist之间使用双向指针串接起来

跳跃列表内部结构

跳跃表共64层
标准的源码的晋升概率只有25%
跳跃列表会记录一下当前的最高层数maxLevel,从maxLevel开始遍历会提高性能

紧凑列表内部

和ziplist比少了zltail_offset字段
可以通过total_byte字段和最后一个元素的长度字段计算出来
取消级联更新
目前只使用在stream数据结构中

rax基数树内部

有序字典树
被用在stream结构里面用于存储消息队列
消息id=时间戳+序号
压缩结构
非压缩结构

你可能感兴趣的:(架构)