一、Java-Redis-进阶
1.1 SDS(动态字符串)
struct sdshdr{
int len;
int free;
char buf[];
}
![](https://img.haomeiwen.com/i17524543/270bf76b3c45ff7c.png)
- 空间预分配:当我们对SDS进行扩展操作的时候,Redis会为SDS分配好内存,并且根据特定的公式,分配多余的free空间,还有多余的1byte空间(这1byte也是为了存空字符),这样就可以避免我们连续执行字符串添加所带来的内存分配消耗。
- 惰性空间释放:当我们执行完一个字符串缩减的操作,redis并不会马上收回我们的空间,因为可以预防你继续添加的操作,这样可以减少分配空间带来的消耗,但是当你再次操作还是没用到多余空间的时候,Redis也还是会收回对于的空间,防止内存的浪费的。
1.2 skiplist(跳跃表)
每一个节点的层数(level)是随机出来的,而且新插入一个节点并不会影响到其他节点的层数,因此,插入操作只需要修改节点前后的指针,而不需要对多个节点都进行调整,这就降低了插入操作的复杂度。
![](https://img.haomeiwen.com/i17524543/6e8180f6cd19498a.png)
![](https://img.haomeiwen.com/i17524543/e74e5654fb9338cf.png)
int zslRandomLevel(void) {
int level = 1;
while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
level += 1;
return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}
直观上期望的目标是50%的概率被分配到Level 1,25%的概率被分配到Level 2,12.5%的概率被分配到Level 3,以此类推。每一层的晋升率都是 50%。Redis 跳跃表默认允许最大的层数是32。
1.3 Redis慢日志
# 命令执行耗时超过 5 毫秒,记录慢日志
CONFIG SET slowlog-log-slower-than 5000
# 只保留最近 500 条慢日志
CONFIG SET slowlog-max-len 500
# 查看
SLOWLOG get 5
- 经常使用O(N) 以上复杂度的命令,例如 SORT、SUNION、ZUNIONSTORE 聚合类命令
- 使用O(N) 复杂度的命令,但N的值非常大
1.4 bigKey
--bigkeys -i 0.01
- 对线上实例进行bigkey扫描时,Redis的OPS会突增,为了降低扫描过程中对Redis的影响,最好控制一下扫描的频率,指定-i参数即可,它表示扫描过程中每次扫描后休息的时间间隔,单位是秒。
- 扫描结果中,对于容器类型(List、Hash、Set、ZSet)的 key,只能扫描出元素最多的 key。但一个 key的元素多,不一定表示占用内存也多,你还需要根据业务情况,进一步评估内存占用情况。
- 业务应用尽量避免写入bigkey
- 扫描分片,业务隔离
- Redis是4.0以上版本,用UNLINK命令替代DEL,此命令可以把释放key内存的操作,放到后台线程中去执行,从而降低对Redis的影响
- Redis是6.0以上版本,可以开启lazy-free机制(lazyfree-lazy-user-del = yes),在执行DEL命令时,释放内存也会放到后台线程中执行
1.5 fork耗时严重
# 上一次 fork 耗时,单位微秒
latest_fork_usec:59477
![](https://img.haomeiwen.com/i17524543/75bccb24408648b3.png)
1.6 缓存一致性问题
先删缓存,再更新数据库:先删除缓存,数据库还没有更新成功,此时如果读取缓存,缓存不存在,去数据库中读取到的是旧值,缓存不一致发生。
![](https://img.haomeiwen.com/i17524543/fdcf1249323b3034.png)
延时双删的方案的思路是,为了避免更新数据库的时候,其他线程从缓存中读取不到数据,就在更新完数据库之后,再sleep一段时间,然后再次删除缓存。
- 线程1删除缓存,然后去更新数据库
- 线程2来读缓存,发现缓存已经被删除,所以直接从数据库中读取,这时候由于线程1还没有更新完成,所以读到的是旧值,然后把旧值写入缓存
- 线程1,根据估算的时间,sleep,由于sleep的时间大于线程2读数据+写缓存的时间,所以缓存被再次删除
- 如果还有其他线程来读取缓存的话,就会再次从数据库中读取到最新值
![](https://img.haomeiwen.com/i17524543/f21fb44e74cb2974.png)
先更新数据库,再删除缓存:更新数据库成功,如果删除缓存失败或者还没有来得及删除,那么,其他线程从缓存中读取到的就是旧值,还是会发生不一致。
![](https://img.haomeiwen.com/i17524543/07fd1df9322b7f6a.png)
![](https://img.haomeiwen.com/i17524543/5f4ea8920b9cd565.png)
- 每次放入缓存的时候,设置一个过期时间,比如5分钟,以后的操作只修改数据库,不操作缓存,等待缓存超时后从数据库重新读取。
网友评论