为什么redis4.x要开始支持多线程?

增加删除bigkey的时候会阻塞。增加多线程可以异步删除
3.X最头疼的就是删除大key
4.x 惰性删除 参考命令 unlink key

Redis单线程什么意思?

image.png
持久化、异步、集群同步 都是由其他的线程完成的。

redis6 IO多线程

缓存穿透、缓存击穿

穿透是redis里面没有,查数据库也没有。比如id是乱七八糟的东西。
击穿是一开始有,比如热点key,但是后来过期了。然后击穿了mysql。

数据结构应用场景

string 点赞
hash 购物车
list 存储公众号消息、评论消息等等。
set 抽奖、朋友圈点赞等社交关系(集合运算也是难点 SDIFF SINTER SUNION)
zset 排行榜

亿级流量系统

  1. 存的进去、取的快、多维度统计<br />

统计的类型

聚合 排序 二值 基数
聚合->集合运算
排序->zset,list,推荐用zset。因为list不好做分页,会随时变数量。
相关最重要的命令是zrank和zrevrange
二值统计->位图
基数统计(统计集合不重复的元素)->hyperloglog

位图操作

setbit key offset value
getbit key offset value
bitcount key
bitop op destkey key1 key2
get key 可以得到ascii,位图底层是String。

位图会按照byte扩容。

HyperLogLog和GEO

uv pv dau mau

uv 基本就是IP。大概是第一次登录? 需要去重
pv 刷新网页 不用去重
dau 日活
mau 月活

hyperloglog

可以用极小的空间计算大量数据的基数
错误率0.81%左右,只能存数量

为什么集群的槽最大16384个

image.png
这个槽的数量很占空间。CRC16最多产生65536种可能。
如果是65536个槽,需要8KB。这样发心跳包的时候开销太大。
1000个节点内的话,16384个槽够用了。

命令

pfadd key val1 val2 val3
pfcount key
pfmerge destkey key1 key2

GEO

image.png

布隆过滤器

常见面试题:已有50亿个电话号,给你10w个电话号判断是否存在。要求准确判断。

本质是二进制数组+一堆哈希函数
因为有哈希冲突,所以判断存在不一定存在,但判断不存在一定不存在。
不能删除元素,删除元素会提升误判率。(你把自己删了可能影响多个元素)
误判只会发生到没添加过的元素身上。

解决的问题

缓存穿透
解决方法是先查布隆过滤器。如果布隆里面有,再查redis。没有直接返回。这样基本不会出事。
黑、白名单

小tip

最好不要让元素数>数组长度

缓存雪崩

redis挂了。或者大量热点数据同时失效。

解决方案

事前=>redis缓存集群实现高可用
事中=>限流、降级
事后=>尽快恢复缓存集群

缓存穿透

偶尔没事,就怕大量的恶意攻击。

解决方案

空对象缓存/布隆过滤器

空对象缓存(治标不治本)

在redis里缓存一个商量好的值,比如0,来防止恶意攻击
如果每次恶意攻击的key相同,效果很好。但如果key不一样就麻烦了,会产生大量的无效空对象。

布隆过滤器

guava有单机版的实现
redisson实现好了redis的布隆过滤器。

缓存击穿

热点key失效导致大量请求打到mysql上。

解决方案

互斥更新、随机退避、差异失效时间
image.png
A缓存比B缓存活的短。就是一个数据存两份。
热点key不设置过期时间(最根本)
加互斥锁

分布式锁

image.png

分布式锁的特点

独占 高可用 防死锁 不乱抢 重入

分布式锁如果用zookeeper就是CP,用redis的话,redis单机是CP,集群是AP

AP和CP:
好比我请室友们吃饭。如果一个人替其他两个室友决定了,就是AP。(因为他没问那两个人的意见就决定了)
如果一个人问了其余人的意见,确定都同意了,才做决定,就是CP。
Eureka也是AP

redis实现分布式锁升级迭代

1 加synchronized=>只能在单机环境下有用。
2 setnx=>可以,但是出错的话锁没法释放。key是固定的,value是UUID之类的,能标识自己线程的东西。
3 setnx + finally => 一般出错也能释放了。但是如果服务宕机了,就进不去finally了。
4 先设置setnx再设置expire=> 服务宕机一般也没什么事,但是不保证原子性,还是有可能出事
5 setnx和expire写到一行=>宕机没事了,但是写死的时间容易出问题。如果网络波动很卡,可能提前释放锁了。然后别的线程进来了,老线程还会把新线程的锁删掉
6 setnx+expire+uuid=>只有锁里存的UUID和自己的UUID是一样的才释放。但是判断过程不是原子的。
7 setnx+expire+uuid+用lua删除锁=>删除也没问题,但是要保证过期时间永远大于业务执行时间。俗称缓存续命问题。需要看门狗。
8 redis集群模式下异步复制造成的丢失=>master上有锁,然后突然死了,从机变成master,但是锁丢了。
Zookeeper主节点挂了的时候服务不可用。
9 直接上redisson=>极高并发下在finally里解锁也会有解锁别人的锁的问题,也需要判断。判断方法是调用isHeldByCurrentThread

Redlock

建议5台机器。
解决主从模式异步复制丢失的问题的方法就是=>5台机器全master
master一起获得锁。

计算公式

如果宕机X台我还想好用,需要2*X+1台机器
image.png

缓存续命怎么续?

额外起线程,定期检查线程是否还持有锁,如果有则延长过期时间
redisson里面使用看门狗定期检查(每1/3锁时间检查一次,默认1/3 * 30 = 10秒),如果线程还持有锁,则刷新过期时间。(也就是回到最开始设置的时间)。
看门狗检查锁的开始时间是30秒。

加锁Lua源码

image.png
如果没锁则加锁,设置过期时间
如果有锁 重入一下 设置过期时间
返回过期时间

解锁Lua源码

image.png

Redis缓存过期淘汰策略

常见面试题

redis默认内存多少? 32位OS 3G, 64位OS 无限
生存环境怎么配最大内存? 一般是最大内存的3/4
在哪配置最大内存? 配置文件 maxmemory,默认bytes
怎么看redis内存使用情况? info memory
redis内存满了怎么办? OOM

三大删除策略

1 立刻删除 浪费性能
2 惰性删除 如果不flushdb或者不get就不删除,可能有很多垃圾
3 定期删除 默认每隔100ms随即删除一些key。可能导致一些key运气好,老不被删除。

由于这三个方法都不太好,所以引出了redis缓存淘汰策略。

过期淘汰策略

8种。。
image.png
all就是所有key,volatile就是对所有设置了过期时间的key。
默认是noeviction

Redis数据结构

redis一切皆RedisObject
image.png

image.png

image.png

String

编码:int embstr raw
int是8字节整数,大于这个范围就是embstr
embstr保存大小小于44个字节的str

为什么要有个string呢?
1 获取长度快,O(1)
2 动态扩容,惰性释放
3 安全,不怕’\0’

对于10000以内的数字,多个redisObject共享内存空间
对于embstr,会在原有的RedisObject后面接一块内存
image.png就像嵌进去一样。
如果是raw格式的话,就不连续了。
image.png

Hash

当entry数小于512并且字段名和字段值小于64的时候用压缩链表。
否则用哈希表

ziplist

是一种紧凑编码格式,核心是用时间换空间。
image.png
它不存储指针,只存储每个entry的长度。所以时间复杂度高,空间紧凑。
image.png
zllen就是一个key下有多少个kv对。

压缩链表么有指针,而是存了前节点和自己的len。
链表在内存不连续,因此访问慢。
同时,压缩链表记录了节点数量,获取长度是O(1)的。
image.png

QuickList

是List底层的数据结构,就是双向链表+ziplist

set

元素少的时候用intset,多的时候用hashtable

sorted set

当元素值大于64或元素数超过默认值时,使用跳跃表。

跳表

查找logn,空间复杂度O(n)

双写一致性