Redis常见问题,从入门到放弃

说明:本文内容来源公众号Java后端技术加上博主自己的一些理解

1、为什么使用Redis
在项目中主要是为了解决性能和并发问题,当然redis还具备做分布式锁的功能,但如果只是为了分布式锁,完全可以用其他的中间件(如zookeeper)代替,并不是非要用redis。

  • 性能
    我们在碰到需要执行特别耗时且查询结果不频繁变动的SQL,就特别适合将查询结果放入缓存。这样,后面的请求就可以去缓存里面查找,这样就能够使得响应更快速
  • 并发
    在大并发的情况下,所有的请求直接到达数据库,导致数据库压力很大,可能会出现连接异常等问题。这时候,就需要redis做一个缓冲操作,让请求先到redis,而不是直接访问数据库

2、使用Redis有什么缺点

  • 缓存与数据库双写一致性问题。有强一致性要求的数据,不能放缓存,我们所做的一切只是保证最终一致性,从根本上来说,只是降低缓存不一致的概率,并不能完全避免。理论上给缓存设置过期时间就是最好的一致性解决方案。对于没有设置缓存过期时间的key,可以考虑三种更新策略:
  1. 先更新数据库,再更新缓存
  2. 先删除缓存,再更新数据库
  3. 先更新数据库,在删除缓存

三种方案,哪个好???
(一)先更新数据,在更新缓存
普遍不推荐,为何?
原因一、从并发,线程安全角度
线程A和线程B同时进行更新操作,那么会出现以下问题:
线程A更新数据库
线程B更新数据库
线程B更新缓存
线程A更新缓存
这样就会出现脏数据,一般不考虑!
原因二、业务场景角度
(1)当我们在操作一个写比较多而读比较少的业务场景的时候,这种方案就会导致缓存还没有被读到就被频繁的更新
(2)如果你写入数据库的值,并不是直接写入缓存的,而是要经过一系列复杂的计算再写入缓存。那么,每次写入数据库后,都再次计算写入缓存的值,无疑是浪费性能的。显然,删除缓存更为适合。
(二)先删除缓存,再更新数据库
有这样一个场景,线程A进行更新操作,线程B进行查询操作
线程A删除缓存
线程B查询缓存,没有
线程B查询数据库,将值放入缓存并返回
线程A更新数据库
这样也导致了脏数据,如果没有给缓存设置过期时间,那么缓存与数据库就永远不一致。
可以解决吗?
采用延时双删策略,意思就是先删除缓存,再更新数据库,最后休眠一段时间再次删除缓存。
这里说的休眠时间是多久呢,根据自己的业务情况而定,可以在读操作所花的时间加上几百ms就行,目的就是删除读操作所产生的缓存。差不多解决了,你说要是加上休眠系统吞吐量降低了怎么办呢,那就将第二次删除作为异步的。自己起一个线程,异步删除。这样,写的请求就不用沉睡一段时间后了,再返回。这么做,加大吞吐量。

  • 缓存雪崩问题.由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有请求直接到达数据库,对数据库cpu和内存造成很大压力,严重的直接导致数据库宕机,从而引发一系列的连锁反应。解决方案:
  1. 大多数设计者会采用加锁或者队列的方式来保证不会有大量的请求直接到达数据库,当然如果并发量很大,这可能会使得用户等待超时
  2. 给缓存不同的缓存过期时间
  3. 给缓存加一个缓存过期标志。简要说明:就是每个缓存(key)都有一个与之对应的标记(key_sign)也存在缓存里面,且该标记的过期时间为对应缓存值的过期时间的一半。当请求到达缓存时,判断每个缓存(key)对应的标记(key_sign)是否过期,若没有,则直接返回缓存值(key对应的值value),若key_sign过期,去数据库查询数据,将数据更新到缓存值(key对应的value),同时将缓存标记(key_sign)设置为未过期,并更新两者的过期时间。
  • 缓存击穿问题。当数据库中不存在某个某个查询结果,自然缓存中也不存在,当恶意用户频繁触发此操作时,那么该请求就会绕过缓存直接查询数据库,即缓存命中率问题。常用的解决方案:
  1. 布隆过滤器,将所有可能存在的值放在一个足够大的bitmap中,当一个一定不存在bitmap中的数据会被这个bitmap过滤掉,从而避免了对底层存储系统的查询压力
  2. 另一种是,直接将从数据库中查到的空值进行缓存,但是它的过期时间很短,最长不超过五分钟,这样第二次查询时缓存中就有值了。
  • 缓存并发竞争问题
    这个问题就是多个子系统去同时set一个key,这时怎么解决这个冲突
    (1)如果对这个key的操作没有顺序要求,那可以直接设置一个分布式锁,谁抢到了谁就去set
    (2)如果要求顺序,可以利用队列,时间戳,将set方法变为串行访问就行了

3、单线程的Redis为什么这么快

  • 纯内存操作。 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈
  • 单线程操作,避免了频繁的上下文切换,CPU资源竞争以及多线程中竞争锁的问题
  • 采用I/O多路复用机制。这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗)

4、Redis的数据类型,以及每种数据类型的使用场景

  • 字符串
  • 列表
  • 哈希
  • 集合
  • 有序集合

5、Redis的过期策略以及内存淘汰机制

Redis采用的是定期删除+惰性删除

  • 为什么不采用定时删除?
    定时删除,用一个定时器负责监视key,过期则自动删除。虽然内存释放及时,但十分消耗CPU资源。在大并发请求下,CPU要将时间放在处理请求上,而不是删除过期key上,因此没有采用这一策略。
  • 什么是定期删除 + 惰性删除?
    定期删除,Redis会每100ms检查是否存在过期的key,如果有则删除,但是Redis检查并不是检查所有的key,而是随机的检查某些key,当然这种策略会导致很多key到期并不能及时的删除
    惰性删除,就是当你获取某个key的时候,Redis会检查一下,如果这个key设置了过期时间那么它是否过期了?如果过期了则删除。
  • 采用定期删除 + 惰性删除有问题吗?
    当然,定期删除没删掉,而你也没有去获取,这个过期的key也没有失效。这样,Redis的内存占用率会越来越高。so!
  • 内存淘汰机制就有了
    在redis.conf中有这样一行配置就是配置内存淘汰机制的
    #max-memory-policy volitile-lru
  1. noeviction:当内存不足以写入新数据时,新写入操作会报错
  2. allkeys-lru:当内存不足以写入新数据时,会移除最近最少使用的key
  3. allkeys-random:当内存不足以写入新数据时,在键中间中,随机移除某个key
  4. volatile-lru:当内存不足以写入新数据时,在设置了过期时间的键中间中,移除最近最少使用的key。这种情况一般是把redis既当缓存,又做持久化存储的时候才用。不推荐
  5. volatile-random:当内存不足以写入新数据时,在设置了过期时间的键中间中,随机移除某个key。依然不推荐
  6. volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。不推荐
    可以发现,如果在没有设置缓存过期时间的键空间中,volatile-lru、volatile-random、volatile-ttl和noeviction一样流氓!
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值