读11万次/秒,写8万次/秒
String(最大512MB)、List(最多2^32-1个元素)、Hash、Set、ZSet、地理空间GEO、去重基数统计HyperLogLog(例如uv统计)、位图bitmap、位域bitfield、流Stream
redis-cli -a 111111 -c --raw
info replication
cluster info
cluster nodes
cluster failover
save,阻塞,直到持久化完成
bgsave,不阻塞,后台异步进行快照操作,fork一个子进程
缺点:
丢失最新数据
数据量比较大,fork的时候会导致内存中的数据被克隆一份,并且影响io
aof缓冲区,三种写回策略:always、everysec(默认)、no(由操作系统决定何时将缓冲区内容写回磁盘)
6.0,只有一个文件
7.0以后,基本文件base、增量文件incr、清单文件manifest
缺点:
aof文件通常比相同数据集的rdb文件大
aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同
aof重写,启动aof文件大内容压缩,只保留可以恢复数据的最小指令集。自动触发,手动触发bgrewriteaof
aof优先。重启时只会加载aof文件,不会加载rdb文件
禁用rdb:save ""
禁用aof:appendonly no
可以一次执行多个命令,是一组命令的集合。放到队列里面按顺序地执行。
不支持回滚
watch 锁
如何优化频繁命令往返造成的性能瓶颈?
一次发送多次命令给服务端。非原子性,支持批量执行不同命令
首次全量同步,后面增量同步
主观下线,客观下线,先选举出哨兵leader(raft算法,先到先得),故障切换选择新master(优先级、复制偏移量、Run ID 最小)
哨兵节点数量应为奇数
不能保证数据零丢失
满足AP,redis异步复制造成的数据丢失,比如:主节点没来的及把刚刚set进来这条数据给从节点。
支持多个master,每个master又可以挂载多个slave,建议最大不超过1000个
槽位slot:16384个hash槽
分片:将存储的数据分散到多台redis机器上,集群中每个redis实例都被认为是整个数据的一个分片
哈希取余分区,对节点数量取模
一致性哈希算法分区,对2^32取模;服务器ip节点映射;key落到服务器(顺时针第一个节点)上
优点:容错性、扩展性;缺点:数据倾斜
哈希槽分区,在数据和节点之间加入了一层,这层成为哈希槽。对key进行crc16算法
为什么集群的最大槽数是16384个?
(1)正常的心跳数据包带有节点的完整配置,如果槽位为65536,发送心跳消息的消息头达8k,发送的心跳包过于庞大
(2)集群主节点数量基本不可能超过1000个,如果节点超过1000个,会导致网络拥堵
(3)槽位越小,节点少的情况下,压缩比高,容易传输。redis主节点的配置信息中它所负责的哈希槽是通过一张bitmap的形式来存储的,在传输过程中会对bitmap进行压缩。
redis集群不保证强一致性
扩容、缩容
重新分派槽位reshard
SpringBoot客户端没有动态感知到RedisCluster的最新集群信息
原因:SpringBoot2.x版本,Rgdis默认的连接池采用Lettuce。当Redis集群节点发生变化后,Letture默认是不会刷新节点拓扑
解决:
1排除lettuce采用jers(不推荐)
2重写连接工厂实例(极度不推荐)
3刷新节点集群拓扑动态感应
spring.redis.lettuce.cluster.refresh.adaptive=true
spring.redis.lettuce.cluster.refresh.period=2000
redis是单线程还是多线程?
redis4之后才慢慢支持多线程,直到redis6/7后才稳定。
Redis的网络IO和键值对读写是由一个线程来完成的,但Redis的其他劝能,比如持久化RDR、AOF、异步删除、集群数据同步等等,其实是由额外的线程执行的。Redis命令工作钱程是单线程的,但是整个Redis来说,是多线程的。
后台子线程执行异步删除unlink、快照生成、aof重写
Redis6/7采用多个IO线程来处理网络请求,对于读写操作命令Redis仍然使用单线程来处理。
Redis工作线程是单线程的,但是整个Redis来说是多线程的。
Redis6.0及7以后,多线程机制默认使关闭的
Redis4.0之前一直采用单线程的主要原因有以下三个:
1.使用单线程榄型是 Redis 的开发和维护史简单,因为单线程榄型方便开发和调试;
2.即使使用单线程模型也并发的处理多客户端的请求,主要使用的是IO多路复用和非阻寨IO;
3.对于Redis系统来说,主要的性能瓶颈是内存或者网络带宽而并非CPU;
io多路复用
—种同步的IO模型,实现一个线程监视多个文件句柄,一旦某个文件句柄就绪就能够通知到对应应用程序进行相应的读写操作,没有文件句柄就绪时就会阻塞应用程序,从而释放cPU资源
也就是说一个或一组线程处理多个TCP连接,
使用单进程就能够实现同时处理多个客户端的连接,无需创建或者维护过多的进程/线程
一个服务端进程可以同时处理多个套接字描述符。
基于内存操作;数据结构简单;io多路复用和非阻塞IO;避免上下文切换
Redis6/7将网络数据读写、请求协议解析通过多个IO线程的来处理,将最耗时的Socket的读取、请求解析、写入单独外包出去。对于真正的命令执行来说,仍然使用主线程操作
Redis只是将IO读写变成了多线程,而命令的执行依旧是由主线程串行执行的
阿里,海量数据查询某一固定前缀的key
小红书,你如何生产上限制keys */flushdb/flushall等危险命令以防止误删误用?
通过配置设置禁用这些命令,redis.conf在SECURITY这一项中
美团,MEMORY USAGE命令你用过吗?
BigKey问题,多大算big?你如何发现?如何删除?如何处理?
大的内容不是key本身,而是value
string类型控制在10KB以内,hash、list、 set、 zset元素个数不要超过5000
内存不均,集群迁移困难
删除超时
网络流量阻塞
redis-cli --bigkeys
给出每种数裾结构Top1 bigkey,同时给出每种数据类型的键值个数+平均大小
memory usage
查询大于10kb的所有key,计算每个键值的字节数
非字符串的bigkey,不要使用del删除,使用hscan.sscan、zscan方式渐进式删除,同时要注意防止biqkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会出现在慢查询中(lateney可查))
一般用del,如果过于庞大unlink
使用hscan每次获取少量field-value,再使用hdel删除每个field
使用ltrim渐进式逐步删除,直到全部删除完成
使用sscan每次获取部分元素,再使用srem命令删除每个元素
使用zscan每次获取部分元素,再使用ZREMRANGEBYRANK命令删除每个元素
BigKey你做过调优吗?惰性释放lazyfree了解过吗?
redis.conf配置文件LAZY FREEING相关说明
Morekey问题,生产上redis数据库有1000W记录,你如何遍历? keys *可以吗?
生产上不能执行keys * 相关命令的,数据量大会导致redis锁住及cpu飙升。在生产环境应该禁用或重命名
scan命令
Slow log 是 Redis 用来记录查询执行时间的日志系统。
查询执行时间指的是不包括像客户端响应(talking)、发送回复等 IO 操作,而单单是执行一个查询命令所耗费的时间。
另外,slow log 保存在内存里面,读写速度非常快,因此你可以放心地使用它,不必担心因为开启 slow log 而损害 Redis 的速度。
slowlog-log-slower-than ,它决定要对执行时间大于多少微秒(microsecond,1秒 = 1,000,000 微秒)的查询进行记录。默认10,000
slowlog-max-len ,它决定 slow log 最多能保存多少条日志, slow log 本身是一个 FIFO(先进先出)队列,当队列大小超过 slowlog-max-len 时,最旧的一条日志将被删除,而最新的一条日志加入到 slow log ,以此类推。默认值128。
查看 slow log
要查看 slow log ,可以使用 SLOWLOG GET 或者 SLOWLOG GET number 命令,前者打印所有 slow log ,最大长度取决于 slowlog-max-len 选项的值,而 SLOWLOG GET number 则只打印指定数量的日志。
查看当前日志的数量
使用命令 SLOWLOG LEN 可以查看当前日志的数量。
请注意这个值和 slower-max-len 的区别,它们一个是当前日志的数量,一个是允许记录的最大日志的数量。
清空日志
slowlog reset
使用复杂度过高的命令
负责的命令一般指O(N)以上的命令,比如sort、sunion、zunionstore聚合类的命令,或是O(N)类的命令,但是N的值由于业务原因特别大。
对于O(N)以上的命令,redis在操作内存数据时,耗时过高,会耗费更多的cpu资源,导致查询变慢。对于O(N)类的命令,由于N的值特别大Redis 一次需要返回给客户端的数据过多,更多时间花费在数据协议的组装和网络传输过程中。
除此之外,我们都知道,Redis 是单线程处理客户端请求的,如果你经常使用以上命令,那么当 Redis 处理客户端请求时,一旦前面某个命令发生耗时,就会导致后面的请求发生排队,对于客户端来说,响应延迟也会长。
针对此类原因,我们一般有以下两个原则:
尽量不使用O(N)以上复杂度的命令,某些数据排序或聚合操作,可以放在客户端处理。
执行O(N)命令时,保证 N 尽量的小(推荐 N <= 300 经验值),每次获取尽量少的数据,让 Redis 可以及时处理返回。
大key问题
所谓大key问题其实并不是值key过大,实则值key对应的value的值过大,此类问题在SET / DEL这类命令中也常出现慢查询。
首先我们要了解下redis写入与删除数据做了什么:
写入数据:为该数据分配内存。
删除数据:释放该数据对应的内存空间。
很明显,当数据值比较大时,redis分配数据内存或释放空间内存都会比较耗时,针对大key问题,有以下建议:
尽量避免写入大key(不要写入无关的数据,数据实在过大可以进行拆分,通过多key存储)
如果你使用的 Redis 是 4.0 以上版本,用 UNLINK 命令替代 DEL,此命令可以把释放 key 内存的操作,放到后台线程中去执行,从而降低对 Redis 的影响
如果你使用的 Redis 是 6.0 以上版本,可以开启 lazy-free 机制(lazyfree-lazy-user-del = yes),在执行 DEL 命令时,释放内存也会放到后台线程中执行。
顺边提下检测大key的一个命令:
redis-cli -h 127.0.0.1 -p 6379 --bigkeys -i 0.01
1
输出结果会展示每种数据类型所占用的最大内存 / 拥有最多元素的 key 是哪一个,以及每种数据类型在整个实例中的占比和平均大小 / 元素数量。
其实,使用这个命令的原理,就是 Redis 在内部执行了 SCAN 命令,遍历整个实例中所有的 key,然后针对 key 的类型,分别执行 STRLEN、LLEN、HLEN、SCARD、ZCARD 命令,来获取 String 类型的长度、容器类型(List、Hash、Set、ZSet)的元素个数。
这里我需要提醒你的是,当执行这个命令时,要注意 2 个问题:
对线上实例进行 bigkey 扫描时,Redis 的 OPS 会突增,为了降低扫描过程中对 Redis 的影响,最好控制一下扫描的频率,指定 -i 参数即可,它表示扫描过程中每次扫描后休息的时间间隔,单位是秒
扫描结果中,对于容器类型(List、Hash、Set、ZSet)的 key,只能扫描出元素最多的 key。但一个 key 的元素多,不一定表示占用内存也多,你还需要根据业务情况,进一步评估内存占用情况
集中过期
集中过期产生的慢查询很容易被忽略,可能我们在业务上线时,并没有发生慢查询,而是业务运行时在某个时间点总是突然发生慢查询。为什么集中过期会导致慢查询呢?我们首先了解下redis的两种过期策略:
被动过期:只有当访问某个key时,才会检测该key是否已经过期,如果已经过期则从实例删除该key。
主动过期:redis内部存在一个定时任务,默认每间隔100毫秒就会从全局的过期哈希表里面随机取出20个key,然后删除其中过期的 key,如果过期 key 的比例超过了 25%,则继续重复此过程,直到过期 key 的比例下降到 25% 以下,或者这次任务的执行耗时超过了 25 毫秒,才会退出循环。
值得注意的是,主动过期key的定时任务是在redis主线程种执行的,也就是说如果在执行主动过期的过程中,出现了集中过期,那就需要大量删除过期 key ,那么此时应用程序在访问 Redis 时,必须要等待这个过期任务执行结束,Redis 才可以服务这个客户端请求,此时应用访问 Redis 就可能产生查询。
如果此时需要过期删除的是一个 bigkey,那么这个耗时会更久。而且,这个操作延迟的命令并不会记录在慢日志中。
因为慢日志中只记录一个命令真正操作内存数据的耗时,而 Redis 主动删除过期 key 的逻辑,是在命令真正执行之前执行的。
对于集中过期问题,有以下建议
避免集中过期,比如将过期时间随机化,添加一个随机的值,分散集中过期的key的过期时间,降低 Redis 清理过期 key 的压力
对于Redis 是 4.0 以上版本,可以开启 lazy-free 机制,当删除过期 key 时,把释放内存的操作放到后台线程中执行,避免阻塞主线程
slowlog-max-len:线上建议调大慢查询列表,记录慢查询时Redis会对长命令做阶段操作,并不会占用大量内存.增大慢查询列表可以减缓慢查询被剔除的可能,例如线上可设置为1000以上。
slowlog-log-slower-than:默认值超过10毫秒判定为慢查询,需要根据Redis并发量调整该值.由于Redis采用单线程相应命令,对于高流量的场景,如果命令执行时间超过1毫秒以上,那么Redis最多可支撑OPS不到1000因此对于高OPS场景下的Redis建议设置为1毫秒。
慢查询只记录命令的执行时间,并不包括命令排队和网络传输时间.因此客户端执行命令的时间会大于命令的实际执行时间.因为命令执行排队机制,慢查询会导致其他命令级联阻塞,因此客户端出现请求超时时,需要检查该时间点是否有对应的慢查询,从而分析是否为慢查询导致的命令级联阻塞。
由于慢查询日志是一个先进先出的队列,也就是说如果慢查询比较多的情况下,可能会丢失部分慢查询命令,为了防止这种情况发生,可以定期执行slowlog get命令将慢查询日志持久化到其他存储中(例如:MySQL、ElasticSearch等),然后可以通过可视化工具进行查询。
双写一致性,你先动缓存redis还是数据库mysql哪一个? why?
先动mysql再动redis。避免redis业务key突然消失,多线程请求集火打满mysql。
先更新数据库,再删除缓存。尝试使用双检加锁机制lock住mysql,只让一个请求线程回写redis,完成数据一致性
给缓存设置过期时间,定期清理缓存并回,是保证最终一致性的解决方案。
所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存,达到一致性。
先更新数据库再更新缓存
问题:
数据库更新成功,回写redis失败
A、B两个线程并行,有前有后
先更新缓存再更新数据库
不太推荐,业务上一般把mysql作为底单数据库,保证最后解释
问题:
A、B两个线程并行,有前有后
先删除缓存再更新数据库
问题:
1.有可能导致请求因级存缺失而访向数据库。给数据库带来压力导致打满mysql。
2.A线程先成功删除了redis里面的数括,然后去更新mysql,此时mysql正在更新中,还没有结束。(比如网络延时);B突然出现要来读取缓存数据,从mysql获取了旧值,把获得的旧值写回redis。
解决:延时双删
延时双删你做过吗?会有哪些问题?
先删除缓存,数据库更新完成后再删除一次缓存
问题:
线程A sleep的时间,就盂要大于线程B读收效据再写入缓存的时问。
这个时间怎么确定?
1.统计下读数据时间,在此基础上加百毫秒即可
2.新启动一个后台监控程序,比如watchDog
先更新数据库再删除缓存,建议
问题:
假如缓存删除失败或者来不及,导致请求再次访问redis时缓存命中,读取到的是缓存旧值。短暂存在数据不一致。
解决:
非业务代码订阅binlog,删除缓存;如果失败,发送消息到消息队列,重试操作
有这么一种情况,微服务查询redis无mysql有,为保证数据双写一致性回写redis你需要注意什么?双检加锁策略你了解过吗?如何尽量避免缓存击穿?
多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个互斥锁来锁住它。其他的线程走到这一步拿不到镜就等着,等第一个我程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。
redis和mysql双写100%会出纰漏,做不到强一致性,你如何保证最终一致性?
删除失败,发送消息到消息队列
我想mysql有记录改动了(有增删改写操作),立刻同步反应到redis?该如何做?
binlog日志。canal能够监听到nysql的变动且能够通知给redis
抖音电商直播,主播介绍的商品有评论,1个商品对应了1系列的评论,排序+展现+取前10条记录
用户在手机App上的签到打卡信息:1天对应1系列用户的签到记录,新浪微博、钉钉打卡签到,来没来如何统计?
应用网站上的网页访问信息:1个网页对应1系列的访问点击,淘宝网首页,每天有多少人浏览首页?
你们公司系统上线后,说一下UV、PV、DAU分别是多少?
UV:Unique visitor,独立访客,一般理解为客户端IP,需要去重
PV:Page View,页面浏览量,不需要去重
DAU:日活跃用户量,登录或者使用了某个产品的用户数(去重复登录的用户)
MAU:月活跃用户量
基数统计:用于统计一个集合中不重复的元素个数,就是对集合去重复后剩余元素的计算
只是进行不重复的基数统计,不是集合也不保存数据,只记录数量而不是具体内容。
有误差。HyperLogLog提供不精确的去重计数方案;牺牲准确率来换取空间,误差仅仅只是0.81%左右
由一个初值都为零的bit数组和多个哈希函数构成,用来快速判断集合中是否存在某个元素。
一个元素如果判断结果:存在时,元素不一定存在,但是判断结果为不存在时,则一定不存在。(有是可能有,无是肯定无)
可以保证的是,如果布隆过滤器判断一个元素不在一个集合中,那这个元素一定不会在集合中。
布隆过滤器可以添加元素,但是不能删除元素,由于涉及hashcode判断依据,删掉元素会导致误判率增加。
添加key时,使用多个hash函数对key进行hash运算得到一个整数索引值,对位数组长度进行取模运算得到一个位置,每个hash函数都会得到一个不同的位置,将这几个位置都置1就完成了add操作。
查询key时,只要有其中一位是零就表示这个key不存在,但如果都是1,则不一定存在对应的key。
把数据写入数据库时,使用布隆过滤器做个标记。当缓存缺失后,应用查询数据库时,可以通过查询布隆过器快速判断数据是否存在。如果不存在,就不用再去数据库中查询了。这样一来,即使发生缓存穿透了,大量请求只会查询Redis和布隆过滤器,而不会积压到数据库,也就不会影响数据库的正常运行。
现有50亿个电话号码,现有10万个电话号码,如何要快速准确的判断这些电话号码是否已经存在?
假如出现了缓存不—致。你有哪些修补方案?
请求去查询一条记录,先查redis无,后查mysql无,都查询不到该条记录,但是请求每次都会打到数据库上面去,导致后台数据库压力暴增。
redis和mysql都没有
空对象缓存或者缺省值
Google布隆过滤器Guava解决缓存穿透
大量的请求同时查询一个key时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去。简单说就是热点key突然失效了,暴打mysql。
一开始redis有,mysql有。
差异失效时间,对于访问频繁的热点key,干脆就不设置过期时间
互斥更新,采用双检加锁策略
redis挂了
redis缓存集群实现高可用
redis中有大量key同时过期大面积失效
redis中key设置为永不过期or 过期时间错开
多缓存结合,本地缓存
服务降级,Hystrix或者sentinel限流降级
缓存
数据共享,分布式session
分布式锁
全局id
计数器
位统计
购物车
消息队列
抽奖
点赞、签到、打卡
用户关注、可能认识的人
热点新闻、排行榜
Redis做分布式锁的时候有需要注意的问题?
独占、高可用(集群)、超时、只能释放自己加的锁、重入、续命
你们公司自己实现的分布式锁是否用的setnx命令实现?
这个是最合适的吗?你如何考虑分布式锁的可重入问题?
hash实现可重入锁
setnx+expire不安全,两条命令非原子性的
lua脚本
如果是Redis是单点部署的,会带来什么问题?
Redis集群模式下,比如主从模式,CAP方面有没有什么问题呢?
客户A通过redis的set命令建分布式锁成功并持有锁。正常情况主从机都有这分布式锁。突然出故障了,master还没来得同步数据给slave,此时slave机器上没有对应锁的信息。从机slave上位,变成了新master。客户B照样可以同样的建锁成功,出现了可怕情况:一锁被多建多用。
CAP里面的CP遭到了破坏,而且redis无论单机、主从、哨兵和主从均有这样风险。
那你简单的介绍一下 Redlock吧?你简历上写redisson,你谈谈Redlock算法
Redis分布式锁如何续期?看门狗知道吗?
在获取锁成功后,给锁加一个watchdog, watchdog会起一个定时任务,在锁没有被释放且快要过期的时候会续期。
RedLock算法
假设我们有N个Redis主节点,例如N = 5这些节点是完全独立的,我们不使用复制或任何其他隐式协调系统。
该方案为了解决数据不一致的问题,直接舍弃了异步复制只使用master节点,同时由于舍弃了slave,为了保证可用性,引入了N个节点,官方建议是5。N=2X+1(N是最终部署机器数,X是容错机器数)
客户端只有在满足下面的这两个条件时,才能认为是加锁成功。
条件1:客户端从超过半数(大于等于N/2+1)的Redis实例上成功获取到了锁;
条件2:客户端获取锁的总耗时没有超过锁的有效时间。
生产上你们的redis内存设置多少?
一般推荐Redis设置内存为最大物理内存的四分之三
如何配置、修改redis的内存大小
配置文件修改maxmemory,默认为0。
如果不设置最大内存大小或者设置最大内存大小为0,在64位操作系统下不限制内存大小,在32位操作系统下最多使用3GB内存
查看内存大小
info memory
config get maxmemory
如果内存满了你怎么办
没有加上过期时间就会导致数据写满,为了避免类似情况,内存淘汰策略
redis清理内存的方式?定期删除和惰性删除了解过吗
立即删除:如果一个键是过期的,马上就从内存中被删除,对cpu不友好
惰性删除:数据到达过期时间,不做处理。等下次访问该数据时,如果未过期,返回数据;发现己过期,删除,返回不存在。
定期删除:每隔一段时间执行一次删除过期键操作并通过限制删除操作执行时长和频率来减少删除操作对CPU时间的影响。
redis缓存淘汰策略有哪些?分别是什么?你用那个?
2个维度:过期键中筛选、所有键中筛选
4个方面:LRU、LFU、random、ttl
1. noeviction(默认):不会驱逐任何key,表示即使内存达到上限也不进行置换,所有能引起内存增加的命令都会返回error
2. allkeys-lru:对所有key使用L RU算法进行删除,优先删除掉最近最不经常使用的key,用以保存新数据
3. volatile-lru:对所有设置了过期时间的key使用LRU算法进行删除
4. allkeys-random:对所有key随机删除
5. volatile -random:对所有设置了过期时间的key随机删除
6. volatile-ttl:删除马上要过期的key
7. allkeys-lfu:对所有key使用LFU算法进行删除
8. volatile-lfu:对所有设置了过期时间的key使用LFU算法进行删除
在所有的key都是最近最经常使用,那么就需要选择allkeys-lru进行置换最近最不经常使用的key,如果你不确定使用哪种策略,那么推荐使用allkeys-lru
如果所有的key的访问概率都是差不多的,那么可以选用allkeys-random策略去置换数据
如果对数据有足够的了解,能够为key指定hint (通过expire/ttI指定) ,那么可以选择volatile-ttI 进行置换
建议:
避免存储bigkey
开启惰性淘汰,lazyfree-lazy-eviction=yes
redis的LRU了解过吗?请手写LRU
Iru和Ifu算法的区别是什么
LRU:最近最少使用页面置换算法,淘汰最长时间未被使用的页面
LFU:最近最不常用页面置换算法,淘汰一定时期内被访问次数最少的页
底层数据结构:
SDS动态字符串
双向链表
压缩列表ziplist
哈希表hashtable
Redis数据类型的底层数据结构
跳表skiplist
整数集合intset
快速列表quicklist
紧凑列表listpack
redis的跳跃列表了解吗? 这个数据结构有什么缺点
redis的zset底层实现,
参考
https://www.bilibili.com/video/BV13R4y1v7sP
https://blog.csdn.net/qq_34343254/article/details/127454871
https://blog.csdn.net/weixin_44271683/article/details/125029966