Redis 是一个基于内存的k-v结构数据库
- 基于内存存储,读写性能高
- 适合存储热点数据(热点商品, 资讯, 新闻)
- 企业应用广泛
Redis入门
简介:
应用场景
- ⭐缓存
- 任务队列
- 消息队列
- 分布式锁
数据类型
常用命令
redis常用命令链接 redis.net.cn
java中操作redis
介绍
:::info
redis启动默认有16个数据库, 设置使用0号数据库
key的序列化器 JdkSerializationRedisSerializer
:::
Redis 面试题
1. 简单介绍⼀下 Redis 呗!
Redis 就是⼀个使⽤ C 语⾔开发的数据库, Redis 的数据是存在内存中
它是内存数据库,所以读写速度⾮常快,因此 Redis 被⼴泛应⽤于缓存
Redis 也经常⽤来做分布式锁,甚⾄是消息队列。数据库也行;
多种数据类型⽀持不同的业务场景. ⽀持事务 、持久化、Lua 脚本、多种集群⽅案
2. 分布式缓存常⻅的技术选型⽅案
分布式缓存的话,使⽤的⽐较多的主要是 Memcached 和 Redis
分布式缓存主要解决的是单机缓存的容量受服务器限制并且⽆法保存通⽤的信息。因为,本地缓存只在当前服务⾥有效,⽐如如果你部署了两个相同的服务,他们两者之间的缓存数据是⽆法共同的。
3. 说⼀下 Redis 和 Memcached 的区别和共同点
共同点 :
- 都是基于内存的数据库,⼀般都⽤来当做缓存使⽤。
- 都有过期策略。
- 两者的性能都⾮常⾼。
区别 :
-
- Redis ⽀持更丰富的数据类型(⽀持更复杂的应⽤场景)。Redis 不仅仅⽀持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。Memcached 只⽀持最简单的 k/v 数据类型。
-
- Redis ⽀持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进⾏使⽤,⽽ Memecache 把数据全部存在内存之中。
-
- Redis 有灾难恢复机制。 因为可以把缓存中的数据持久化到磁盘上。
-
- Redis 在服务器内存使⽤完之后,可以将不⽤的数据放到磁盘上。但是,Memcached 在服务器内存使⽤完之后,就会直接报异常。
-
- Memcached 没有原⽣的集群模式,需要依靠客户端来实现往集群中分⽚写⼊数据;但是Redis ⽬前是原⽣⽀持 cluster 模式的.
-
- Memcached 是多线程,⾮阻塞 IO 复⽤的⽹络模型;Redis 使⽤单线程的多路 IO 复⽤模型。 (Redis 6.0 引⼊了多线程 IO )
-
- Redis ⽀持发布订阅模型、Lua 脚本、事务等功能,⽽ Memcached 不⽀持。并且,Redis⽀持更多的编程语⾔。
-
- Memcached过期数据的删除策略只⽤了惰性删除,⽽ Redis 同时使⽤了惰性删除与定期删除。
4. 缓存数据的处理流程
5. 为什么要⽤ Redis/为什么要⽤缓存?
- 高性能 硬盘/内存 数据一致性
- 高并发 QPS(Query Per Second) : 服务器每秒可执行的查询次数
6. Redis 常⻅数据结构以及使⽤场景分析
分类 | 主要命令 | 场景 |
---|---|---|
String k-v 简单动态字符串(simple dynamic string SDS) | set/get/strlen/exists/dect/incr/setex | 需要计数的场景,⽐如⽤户的访问次数、热点⽂章的点赞转发数量等缓存限流分布式锁分布式Session |
list 链表 双向链表 | rpush/lpop实现队列 rpush/rpop实现栈 lrange llen | 发布与订阅或者说消息队列、慢查询,简单队列,关注列表时间轴 |
hash | hset,hmset,hexists,hget,hgetall,hkeys,hvals | 系统中对象数据的存储。用户信息,用户主页访问量,组合查询 |
set | sadd,spop,smembers,sismember,scard,sinterstore,sunion | 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景,踩、赞、标签等 |
sorted set | zadd,zcard,zscore,zrange,zrevrange,zrem | 对数据根据某个权重进⾏排序的场景 实时排⾏信息,排行榜,好友关系链表 |
拓展数据结构:
- 跳表
Redis使用跳表来实现有序集合和有序字典。跳表是一种基于链表的数据结构,它允许快速地查找元素,同时保
持了元素的顺序。它是一种可以在O(log n)的时间复杂度下进行查找、插入和删除的数据结构。
跳表中每个元素都包含一个键和一个值,元素按照键的大小来排序。在实际的应用中,键通常是一个数字,而
值可以是任何数据类型。
跳表通过维护多层链表来加速查找。每一层链表的元素都是下一层链表中元素的子集,且每一层链表只保留一
部分元素。每一层链表都有一个指向下一层链表的指针,这些指针就是跳表的核心。
查找操作从顶层链表开始,沿着指针进行跳跃,直到找到目标元素或无法跳跃为止。由于每一层链表的元素
数量比下一层链表少得多,跳表的查询时间复杂度平均为O(log n)。
插入和删除操作也非常高效,只需要在跳表中找到插入或删除位置,然后进行简单的指针更新操作即可。由于
跳表每层链表的元素数量被控制在一个较小的范围内,插入和删除操作的时间复杂度也平均为O(log n)。
总的来说,跳表是一种高效的数据结构,可以用于实现有序集合等需要快速查找、插入和删除操作的场景。
在Redis中,跳表被广泛用于实现有序集合和有序字典,成为了性能优秀的键值对存储方案之一。
- HyperLogLog
HyperLogLog是一种基数估计算法,用于统计大型数据集合中唯一元素的数量。它可以在极短的时间内估算一
个数据集中不重复元素的数量,且误差率非常小。这个算法的特别之处在于相对于传统的基数算法,它占
用的内存空间更少,适合处理海量数据。
HyperLogLog的估算算法比较简单,它通过一些思维上巧妙的方法使用哈希函数对元素进行映射和计算。
HyperLogLog主要由以下两个步骤构成:
- 使用哈希函数将每个元素映射成一个唯一的整数;
- 统计哈希函数返回的整数中前导0的个数。
HyperLogLog使用多个哈希函数,将一个元素映射成多个整数。这些整数被分组,每一组中的整数前缀0的
个数被统计。通过这些0的个数来估算数据集合中唯一元素的数量。这种方法可以有效地压缩数据,
并且是一种概率算法,误差率可以控制在0.81%以内。
在Redis中,HyperLogLog被广泛应用于统计用户访问、统计页面浏览量、统计城市访问量等场景中,可以
提供快速的基数估计功能。Redis的HyperLogLog可用命令包括PFADD、PFCOUNT、PFMERGE等。
- Geo
Geo是Redis提供的地理位置数据类型,支持对地理信息数据进行存储、查询和计算。其可用命令包括GEOADD、
GEORADIUS、GEOHASH等。
Geo将地理位置数据表示为经度和纬度坐标,并使用ZSET有序集合来存储它们,其中ZSET的分数用于排序位置。
这使得可以轻松地将数据存储在Redis的内存中,而且允许进行地理位置搜索和聚合。
Geo提供了四个命令:GEOADD用于添加位置数据和信息,GEODIST用于计算两个位置之间的距离,GEORADIUS
和GEORADIUSBYMEMBER用于以圆形或矩形方式搜索给定地理区域的位置,以及GEOHASH用于获取给定位
置的GeoHash值。
在实际的应用中,Geo通常用于社交网络应用、物流和分布式系统的位置路由、广告投放、附近的人等场景。
可以使用Geo在Redis中实现简单的位置索引,以提高相关数据的访问效率和查询效率。
- Sup/Sub
Sup和Sub是两种不同的订阅协议,它们用于在WebSocket中支持实时消息广播和推送。
Sup是WebSocket的“发布-订阅”(Publish-Subscribe)协议,它与传统的HTTP请求/响应模型不同,采用数据
流模式。客户端使用Sup协议向服务器发送订阅请求,服务器收到请求后订阅相应的数据源,并将数据源的
数据流发送给所有订阅该数据源的客户端。
Sub是WebSocket的“订阅-发布”(Subscribe-Publish)协议,它与Sup协议相反。客户端使用Sub协议向服务
器发送订阅请求,服务器收到请求后创建一个数据源,然后将数据源的ID返回给客户端。当服务器有数据
可用时,会将数据发送到指定的数据源,然后所有订阅该数据源的客户端都会收到数据。
通过使用Sup和Sub协议,可以实现实时的消息广播和推送功能,使得客户端能够接收到实时的数据更新和事件
通知。这些协议已经被广泛地应用于实时聊天、在线游戏、数据可视化和监控等应用场景。
- Bitmap
Bitmap是一种特殊的序列化数据结构,它由多个二进制位构成,通常用于对大量的布尔型数据进行存储和操作。
在Redis中,Bitmap是一种内置的数据结构,可以使用BITFIELD命令进行操作。
位图可以通过一个整数来表示,其中每一位对应于某个对象的状态,比如0/1表示某个用户是否在线,或者某个
产品是否存在库存等。由于位图的每个元素只占用一位二进制空间,所以可以有效地压缩存储空间,
并且可以使用位运算来进行高效的位操作。
在Redis中,Bitmap可用于多种应用场景,包括统计在线用户数、过滤无效访问请求、记录用户行为、标志
位图等。比如,可以使用Bitmap来记录一段时间内所有访问过网站的用户,来统计日活跃用户数和
月活跃用户数。
使用Bitmap进行数据操作的优点是它所占用的内存空间比传统的数据类型更少,可以大大降低存储成本,并且
支持快速的数据操作。然而它也存在一些局限性,比如它只能存储布尔类型的值,如果需要存储其他
类型的数据,则需要进行转换。
7. Redis 单线程模型详解
Redis 基于 Reactor 模式来设计开发了⾃⼰的⼀套⾼效的事件处理模型
Redis中的⽂件事件处理器(file event handler)。 由于feh是单线程方式运行,一般说redis是单线程模式
Redis 通过IO 多路复⽤程序 来监听来⾃客户端的⼤量连接(或者说是监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发⽣。
I/O 多路复⽤技术的使⽤让 Redis 不需要额外创建多余的线程来监听客户端的⼤量连接,降低了资源的消耗(和 NIO 中的 Selector 组件很像)。
Redis 服务器是⼀个事件驱动程序,服务器需要处理两类事件:** 1. ⽂件事件**; 2. 时间事件。
⽂件事件(客户端进⾏读取写⼊等操作,涉及⼀系列⽹络通信)
⽂件事件处理器(file event handler)主要是包含 4 个部分:
- 多个 socket(客户端连接)
- IO 多路复⽤程序(⽀持多个客户端连接的关键)
- ⽂件事件分派器(将 socket 关联到相应的事件处理器)
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
8. Redis 没有使⽤多线程? Redis6.0 之前为什么不使⽤多线程?
我觉得主要原因有下⾯ 3 个:
- 单线程编程容易并且更容易维护;
- Redis 的性能瓶颈不再 CPU ,主要在内存和⽹络;
- 多线程就会存在死锁、线程上下⽂切换等问题,甚⾄会影响性能。
9. Redis6.0 之后为何引⼊了多线程?
Redis6.0 引⼊多线程主要是为了提⾼⽹络 IO 读写性能,因为这个算是 Redis 中的⼀个性能瓶颈(Redis 的瓶颈主要受限于内存和⽹络)。
⽹络数据的读写这类耗时操作上使⽤, 执⾏命令仍然是单线程顺序执⾏。因此,你也不需要担⼼线程安全问题。
Redis6.0 的多线程默认是禁⽤的,只使⽤主线程。如需开启需要修改 redis 配置⽂件 redis.conf ,开启多线程后,还需要设置线程数,否则是不⽣效的
10. Redis 给缓存数据设置过期时间有啥⽤?
因为内存是有限的,如果缓存中的所有数据都是⼀直保存的话,分分钟直接Out of memory。缓解内存消耗
Redis ⾃带了给缓存数据设置过期时间的功能,比如 exp key 60 setex key 60 value
:::info
注意:Redis中除了字符串类型有⾃⼰独有设置过期时间的命令 setex 外,其他⽅法都需要依靠expire 命令来设置过期时间 。另外, persist 命令可以移除⼀个键的过期时间:
:::
业务场景: 某数据某段时间内存在, 比如短信验证码1分钟有效, 用户登录 token 一天内有效 传统数据库处理自己判断过期,更麻烦并且性能差很多
11. Redis是如何判断数据是否过期的呢?
Redis 通过⼀个叫做过期字典(可以看作是hash表)来保存数据过期的时间。过期字典的键指向Redis数据库中的某个key(键),过期字典的值是⼀个long long类型的整数,这个整数保存了key所指向的数据库键的过期时间(毫秒精度的UNIX时间戳)。
过期字典是存储在redisDb这个结构⾥的
12. 过期的数据的删除策略了解么?
常⽤的过期数据的删除策略就两个(重要!⾃⼰造缓存轮⼦的时候需要格外考虑的东⻄):
- 惰性删除 :只会在取出key的时候才对数据进⾏过期检查。这样对CPU最友好,但是可能会造成太多过期 key 没有被删除。
-
定期删除 : 每隔⼀段时间抽取⼀批 key 执⾏删除过期key操作。对内存更友好. 并且,Redis 底层会通过限制删除操作执⾏的时⻓和频率来减少删除操作对CPU时间的影响。
Redis 采⽤的是 定期删除+惰性/懒汉式删除 。
Redis 内存淘汰机制(存在漏网之key)
13. Redis 内存淘汰机制了解么?
Redis 提供 6 种数据淘汰策略:
- volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使⽤的数据淘汰
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
- allkeys-lru(least recently used):当内存不⾜以容纳新写⼊数据时,在键空间中,移除最近最少使⽤的 key(这个是最常⽤的)
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
- no-eviction:禁⽌驱逐数据,也就是说当内存不⾜以容纳新写⼊数据时,新写⼊操作会报错。这个应该没⼈使⽤吧!
4.0 版本后增加以下两种:
7. volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中
挑选最不经常使⽤的数据淘汰
8. allkeys-lfu(least frequently used):当内存不⾜以容纳新写⼊数据时,在键空间中,移
除最不经常使⽤的 key
数据删除和淘汰策略小结
14. Redis 持久化机制
Redis 的⼀种持久化叫快照(snapshotting,RDB),另⼀种是只追加⽂件(append-only file, AOF)
快照(snapshotting)持久化(RDB)
Redis 可以通过创建快照来获得存储在内存⾥⾯的数据在某个时间点上的副本。Redis 创建快照之后,可以对快照进⾏备份,可以将快照复制到其他服务器从⽽创建具有相同数据的服务器副本(Redis 主从结构,主要⽤来提⾼ Redis 性能),还可以将快照留在原地以便重启服务器的时候使⽤。
快照持久化是 Redis 默认采⽤的持久化⽅式,在 Redis.conf 配置⽂件中默认有此下配置:
save 900 1 #在900秒(15分钟)之后,如果⾄少有1个key发⽣变化,Redis就会⾃动触发BGSAVE命令创建快照。
save 300 10 #在300秒(5分钟)之后,如果⾄少有10个key发⽣变化,Redis就会⾃动触发BGSAVE命令创建快照。
save 60 10000 #在60秒(1分钟)之后,如果⾄少有10000个key发⽣变化,Redis就会⾃动触发BGSAVE命令创建快照。
AOF(append-only file)持久化
与快照持久化相⽐,AOF 持久化 的实时性更好,因此已成为主流的持久化⽅案。默认情况下
Redis 没有开启 AOF(append only file)⽅式的持久化,可以通过 appendonly 参数开启:
appendonly yes
appendfsync always #每次有数据修改发⽣时都会写⼊AOF⽂件,这样会严重降低Redis的速度
appendfsync everysec #每秒钟同步⼀次,显示地将多个写命令同步到硬盘
appendfsync no #让操作系统决定何时进⾏同步
开启 AOF 持久化后每执⾏⼀条会更改 Redis 中的数据的命令,Redis 就会将该命令写⼊硬盘中的 AOF ⽂件。AOF ⽂件的保存位置和 RDB ⽂件的位置相同,都是通过 dir 参数设置的,默认的⽂件名是 appendonly.aof。
为了兼顾数据和写⼊性能,⽤户可以考虑 appendfsync everysec 选项 ,让 Redis 每秒同步⼀次AOF ⽂件,Redis 性能⼏乎没受到任何影响。⽽且这样即使出现系统崩溃,⽤户最多只会丢失⼀秒之内产⽣的数据。当硬盘忙于执⾏写⼊操作的时候,Redis 还会优雅的放慢⾃⼰的速度以便适应硬盘的最⼤写⼊速度。
:::info
- redis4.0 支持 混合持久化 (默认关闭,通过配置项 aof-use-rdb-preamble 开启)此时,AOF 重写的时候就直接把 RDB 的内容写到 AOF ⽂件开头,快速加载同时避免丢失过多的数据。但是这里rdb部分是压缩格式不再是aof格式,可读性差
- AOF 重写可以产⽣⼀个新的 AOF ⽂件,这个新的 AOF ⽂件和原有的 AOF ⽂件所保存的数据库状态⼀样,但体积更⼩。
AOF重写功能是通过读取DB的键值对实现,程序无需对现有AOF文件进行任何读入,分析或写入操作
在执⾏ BGREWRITEAOF 命令时,Redis 服务器会维护⼀个 AOF 重写缓冲区,该缓冲区会在⼦进程创建新 AOF ⽂件期间,记录服务器执⾏的所有写命令。当⼦进程完成创建新 AOF ⽂件的⼯作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF ⽂件的末尾,使得新旧两个 AOF ⽂件所保存的数据库状态⼀致。最后,服务器⽤新的 AOF ⽂件替换旧的 AOF ⽂件,以此来完成AOF ⽂件重写操作
:::
RDB(redis database backup file)Redis数据备份文件
AOF append only file (追加文件)
15. Redis 事务
Redis事务提供了⼀种将多个命令请求打包的功能。然后,再按顺序执⾏打包的所有命令,并且不会被中途打断。
Redis 是不⽀持 roll back 的,因⽽不满⾜原⼦性的(⽽且不满⾜持久性)
Redis 可以通过 MULTI,EXEC,DISCARD 和 WATCH 等命令来实现事务(transaction)功能。用 MULTI命令后可以输⼊多个命令。Redis不会⽴即执⾏这些命令,⽽是将它们放到队列,当调⽤了EXEC命令将执⾏所有命令。
16. 缓存穿透
缓存穿透是 查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,将导致这个不存在的数据每次请求直接落到DB,可能导致DB挂掉.这种情况大概率是遭到攻击。
解决通常用布隆过滤器
布隆过滤器主要用于检索一个元素是否在集合中,可使用 Redisson 实现布隆过滤器。
它的底层先初始化一个比较大数组,里面存放二进制0或1,一开始都是0,当一个key来了经过三次hash计算,取模数组长度找数据下标然后将数组的0改为1,这样的话,三个数组位置可标明一个key的存在,查找过程一样
当然它的缺点就是可能会发生误判,但可以设置误判率,大概不超过5%;因为误判必然存在,不然就得增加数组长度,其实5个点的误判率一般项目可以接收,不至于高并发下压倒数据库
缓存穿透说简单点就是⼤量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这⼀层。
⾸先做好参数校验,⼀些不合法的参数请求直接抛出异常信息返回给客户端
1)缓存⽆效 key
如果缓存和数据库都查不到某个 key 的数据就写⼀个到 Redis 中去并设置过期时间,具体命令如下: SET key value EX 10086 。这种⽅式可以解决请求的 key 变化不频繁的情况, 尽量将⽆效的 key 的过期时间设置短⼀点⽐如 1 分钟
public Object getObjectInclNullById(Integer id) {
// 从缓存中获取数据
Object cacheValue = cache.get(id);
// 缓存为空
if (cacheValue == null) {
// 从数据库中获取
Object storageValue = storage.get(key);
// 缓存空对象
cache.set(key, storageValue);
// 如果存储数据为空,需要设置⼀个过期时间(300秒)
if (storageValue == null) {
// 必须设置过期时间,否则有被攻击的⻛险
cache.expire(key, 60 * 5);
}
return storageValue;
}
return cacheValue;
}
2)布隆过滤器
布隆过滤器是⼀个⾮常神奇的数据结构,通过它我们可以⾮常⽅便地判断⼀个给定数据是否存在于海量数据中。我们需要的就是判断 key 是否合法,有没有感觉布隆过滤器就是我们想要找的那个“⼈”。
布隆过滤器说某个元素存在,⼩概率会误判。布隆过滤器说某个元素不在,那么这个元素⼀定不在。
当⼀个元素加⼊布隆过滤器中的时候,会进⾏哪些操作:
- 使⽤布隆过滤器中的哈希函数对元素值进⾏计算,得到哈希值(有⼏个哈希函数得到⼏个哈希值)。
- 根据得到的哈希值,在位数组中把对应下标的值置为 1。
当我们需要判断⼀个元素是否存在于布隆过滤器的时候,会进⾏哪些操作: - 对给定元素再次进⾏相同的哈希计算;
- 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在⼀个值不为 1,说明该元素不在布隆过滤器中。
不同的字符串可能哈希出来的位置相同。 (可以适当增加位数组⼤⼩或者调整我们的哈希函数来降低概率)
17.缓存击穿
缓存击穿是对于设置过期时间的key,缓存在某时间点过期时,恰好这个时间点对于这个key有大量并发请求过来,这些请求发现缓存过期一般从后端DB加载数据并回设到缓存,这个时候大并发的i请求可能会瞬间把DB压垮
解决方式:第一种用互斥锁,当缓存失效,不是立即load DB,先使用Redis的setnx设置一个互斥锁,操作成功返回时再进行load DB操作并回设缓存,否则重试get缓存的方法
第二种设置当前key逻辑过期: ①设置key时候,设置一个过期时间字段一块存缓存,不给当前key设置过期时间;②查询时候,从redis取出数据后判断时间上是否过期;③如果过期开通另一个线程进行数据同步,当前线程正常返回数据,这个数据不是最新;
如果选择数强一致性,建议用分布式锁,性能没那么高,锁需要等,也可能产生死锁问题;如果选择key逻辑删除,优先考虑高可用性,性能比较高,但数据同步做不到强一致性;
18. 缓存雪崩
缓存雪崩是设置缓存时用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩,与缓存击穿区别,雪崩是很多Key,击穿是某一个key缓存
解决方案一可以将缓存失效时间分散开,比如在原有失效时间基础上加一个随机值,比如1-5分钟随机,这样每一个缓存过期时间重复率会降低,很难引发集体失效事件(给不同key的TTL添加随机值)
其它可考虑用Redis集群提高服务可用性(哨兵模式,集群);
给缓存业务添加降级限流策略(nginx,或gateway)
给业务添加多级缓存 (Guava或Caffeine)
缓存在同⼀时间⼤⾯积的失效,后⾯的请求都直接落到了数据库上,造成数据库短时间内承受⼤量请求。
举个例⼦:系统的缓存模块出了问题⽐如宕机导致不可⽤。造成系统的所有访问,都要⾛数据库。
有⼀些被⼤量访问数据(热点缓存)在某⼀时刻⼤⾯积失效,导致对应的请求直接落到了数据库上。
数据库的压⼒可想⽽知,可能直接就被这么多请求弄宕机
针对 Redis 服务不可⽤的情况:
- 采⽤ Redis 集群,避免单机出现问题整个缓存服务都没办法使⽤。
- 限流,避免同时处理⼤量的请求。
针对热点缓存失效的情况: - 设置不同的失效时间⽐如随机设置缓存的失效时间。
- 缓存永不失效。
19.缓存三兄弟小结
<缓存三兄弟>
穿透不中生有key , 布隆过滤null隔离;
缓存击穿过期key , 锁与非期解难题;
雪崩大量过期key , 过期时间要随机;
面试必考三兄弟 , 可用限流来保底;
20. 如何保证缓存和数据库数据的⼀致性?
Cache Aside Pattern(旁路缓存模式)
遇到写请求是这样的:更新 DB,然后直接删除 cache 。
如果更新数据库成功,⽽删除缓存这⼀步失败的情况的话,简单说两个解决⽅案:
- 缓存失效时间变短(不推荐,治标不治本) :我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适⽤。
-
增加cache更新重试机制(常⽤): 如果 cache 服务当前不可⽤导致缓存删除失败的话,我们就隔⼀段时间进⾏重试,重试次数可以⾃⼰定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存⼊队列中,等缓存服务可⽤之后,再将 缓存中对应的 key 删除即可。
关键词 : 双写一致
- 比如业务:当时将文章的热点数据存入缓存,虽然是热点数据,但是实时要求行并不是特别高,所以,当时采用的是异步方案来同步数据;
- 再比如业务:当时将抢券的库存存入到缓存中,这个需要实时进行数据同步,为保证数据强一致性,当时采用的是 redisson提供的读写锁保证数据同步
方案介绍:
允许延时一致的业务,采用异步通知
①用MQ中间件,更新数据后,通知缓存删除
②用canal中间件,不用改业务代码,伪装为 mysql 的一个从节点,canal通过读取 binlog数据更新缓存
强一致,用 Redisson 提供的读写锁
①共享锁:读取readLock,加锁之后,其它线程可以共享读操作;
②排他锁:独占锁writeLock写锁,加锁之后,阻塞其它线程读写操作
21.Redis 分布式锁
// 利用 hash 结构记录线程id和重入次数
// key是somelock value是对象{field:t1 value:1}
public void add1(){
RLock lock = lock.tryLock();
boolean isLock = lock.tryLock();
// 执行业务
add2();
// 释放锁
lock.unlock();
}
public void add2(){
RLock lock = redissonClient.getLock("somelock");
boolean isLock = lock.tryLock();
// 执行业务
// 释放锁
lock.unlock();
}
Q: redis分布式锁如何实现?
A: redis中提供一个命令 setnx (set if not exists); 由于redis单线程, 用命令之后, 只有一个客户端对某一个Key设置值, 在没有过期或删除key的时候,其他客户端不能设置这个Key
Q : 有效时长
A: 的确,redis的setnx指令不太好控制这个问题,采用redis的一个框架 redisson 实现的
在redisson 中需要手动加锁,并且可以控制锁失效时间和等待时间, 当锁住的一个业务还没执行完时, redisson 引入一个看门狗机制, 就是每隔一段时间检查当前业务是否持有锁,如果持有就增加锁的持有时间, 当业务执行完以后需要使用释放锁就行
还有一个好处,高并发下,业务可能执行很快,先 客户1 持有锁时候,客户2来以后不会马上拒绝,会自旋 不断尝试获取锁,如果客户1释放, 客户2就可以马上持有锁,性能也得到提升
Q: 可重入吗?
A: 可以重入,这样做为避免死锁产生, 这个重入在内部判断是否当前线程持有的锁,如果是当前线程持有的锁就会计数,如果释放锁就会在计算上减一,在存储数据时候采用hash结构,大key可按照自己业务进行定制,其中小Key 是当前线程唯一标识, value是当前线程重入次数
Q: 主从一致性问题
A: 不能, 比如: 当线程1加锁成功,master节点数会异步复制到slave节点, 此时当前持有redis锁的master 节点宕机, slave节点被提升为新master节点, 加入现在来了一个线程2
22.主从数据同步原理
哨兵作用
Q : 保证Redis 的高并发高可用?
A : 哨兵模式,实现主从集群的自动故障恢复(监控/自动故障恢复/通知)
首先搭建主从集群,加上redis的哨兵模式,它可以实现主从集群的自动故障恢复,里面包括对主从服务的监控,自动故障恢复和通知;如果master故障,Sentinel会将一个从提升为主,即使故障实例恢复也以新的master为主;同时Sentinel也扮演Redis客户端服务发现来源,当集群故障转移时,会将最新信息推送给Redis客户端,所以一般项目采用哨兵模式保证redis高并发高可用
Q:redis是哪种集群
A: 主从(1主1从)+哨兵就可以,单节点不超过10G内存,如果Redis内存不足可以给不同服务分配独立的Redis主从节点
用主从(1主1从)加哨兵,一般单节点不超过10G内存,如果内存不足可以给不同服务分配独立的Redis主从节点。尽量不做分片集群,因为集群维护起来比较麻烦,并且集群间心跳检测和数据通信会消耗大量网络带宽,也没办法使用lua脚本和事务
Q:脑裂怎么解决
集群脑裂由于主节点和从节点和sentinel处于不同网络分区,然后未心跳感知到主节点,所以通过选举方式提升一个从节点为主,这样存在2个master,像大脑分裂一样,导致客户端还在老的主节点那写数据,新节点无法同步数据,网络恢复后,sentinel会将老的主节点降为从节点,这是再从新master同步数据,就会导致数据丢失;
解决: 可以修改redis配置,设置最少的从节点数量以及缩短主从数据同步的延迟时间。达不到要求就拒绝请求,可以避免大量数据丢失;
由于网络等原因可能会出现,那就是master和salve和sentinel处于不同网络分区,使得sentinel没有能够心跳感知到master,所以通过选举方式提升一个salve为master,这样存在2个主。像大脑分裂一样,会导致客户端还在老主节点那写数据,新节点无法同步数据,当网络恢复后,sentinel会将老的主降为salve,这时再从新master同步数据,导致老主的大量数据丢失;
在redis配置中可以设置,第一设置最少的salve节点个数,比如设置至少要有一个从节点才能同步数据,第二个可以设置主从数据复制和同步延迟时间,达不到要求就拒绝请求,可以避免大量数据丢失;
分片集群结构
I/O多路复用模型
IO多路复用
不过监听socket的方式,通知的方式有多种实现文章来源:https://www.toymoban.com/news/detail-470569.html
- select poll 只通知用户进程有socket就绪,但不确定具体的socket,需要用户逐个遍历确认
- epoll 通知用户进程Socket就绪的同时,把已经就绪的Socket写入用户空间.
文章来源地址https://www.toymoban.com/news/detail-470569.html
到了这里,关于第四章--Redis基础知识和面试题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!