阅读文本大概需要15分钟。

1、为什么使用 Redis?

在项目中使用 Redis,主要考虑两个角度:性能和并发。如果只是为了分布式锁这些其他功能,还有其他中间件 Zookpeer 等代替,并非一定要使用 Redis。

性能:如下图所示,我们在碰到需要执行耗时特别久,且结果不频繁变动的 SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应。

特别是在秒杀系统,在同一时间,几乎所有人都在点,都在下单。。。执行的是同一操作———向数据库查数据。

根据交互效果的不同,响应时间没有固定标准。在理想状态下,我们的页面跳转需要在瞬间解决,对于页内操作则需要在刹那间解决。

并发:如下图所示,在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用 Redis 做一个缓冲操作,让请求先访问到 Redis,而不是直接访问数据库。

使用 Redis 的常见问题

  • 缓存和数据库双写一致性问题

  • 缓存雪崩问题

  • 缓存击穿问题

  • 缓存的并发竞争问题

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

这个问题是对 Redis 内部机制的一个考察。很多人都不知道 Redis 是单线程工作模型。

原因主要是以下三点:

  • 纯内存操作

  • 单线程操作,避免了频繁的上下文切换

  • 采用了非阻塞 I/O 多路复用机制

仔细说一说 I/O 多路复用机制,打一个比方:小名在 A 城开了一家快餐店店,负责同城快餐服务。小明因为资金限制,雇佣了一批配送员,然后小曲发现资金不够了,只够买一辆车送快递。

经营方式一

客户每下一份订单,小明就让一个配送员盯着,然后让人开车去送。慢慢的小曲就发现了这种经营方式存在下述问题:

  • 时间都花在了抢车上了,大部分配送员都处在闲置状态,抢到车才能去送。

  • 随着下单的增多,配送员也越来越多,小明发现快递店里越来越挤,没办法雇佣新的配送员了。

  • 配送员之间的协调很花时间。

综合上述缺点,小明痛定思痛,提出了经营方式二。

经营方式二

小明只雇佣一个配送员。当客户下单,小明按送达地点标注好,依次放在一个地方。最后,让配送员依次开着车去送,送好了就回来拿下一个。上述两种经营方式对比,很明显第二种效率更高。

在上述比喻中:

  • 每个配送员→每个线程

  • 每个订单→每个 Socket(I/O 流)

  • 订单的送达地点→Socket 的不同状态

  • 客户送餐请求→来自客户端的请求

  • 明曲的经营方式→服务端运行的代码

  • 一辆车→CPU 的核数

于是有了如下结论:

  • 经营方式一就是传统的并发模型,每个 I/O 流(订单)都有一个新的线程(配送员)管理。

  • 经营方式二就是 I/O 多路复用。只有单个线程(一个配送员),通过跟踪每个 I/O 流的状态(每个配送员的送达地点),来管理多个 I/O 流。

下面类比到真实的 Redis 线程模型,如图所示:

-

Redis-client 在操作的时候,会产生具有不同事件类型的 Socket。在服务端,有一段 I/O 多路复用程序,将其置入队列之中。然后,文件事件分派器,依次去队列中取,转发到不同的事件处理器中。

3、Redis 的数据类型及使用场景

一个合格的程序员,这五种类型都会用到。

  • String:最常规的 set/get 操作,Value 可以是 String 也可以是数字。一般做一些复杂的计数功能的缓存。

  • Hash:这里 Value 存放的是结构化的对象,比较方便的就是操作其中的某个字段。我在做单点登录的时候,就是用这种数据结构存储用户信息,以 CookieId 作为 Key,设置 30 分钟为缓存过期时间,能很好的模拟出类似 Session 的效果。

  • List:使用 List 的数据结构,可以做简单的消息队列的功能。另外,可以利用 lrange 命令,做基于 Redis 的分页功能,性能极佳,用户体验好。

  • Set:因为 Set 堆放的是一堆不重复值的集合。所以可以做全局去重的功能。我们的系统一般都是集群部署,使用 JVM 自带的 Set 比较麻烦。另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。

  • Sorted Set:Sorted Set 多了一个权重参数 Score,集合中的元素能够按 Score 进行排列。可以做排行榜应用,取 TOP N 操作。Sorted Set 可以用来做延时任务。

4、Redis 的过期策略和内存淘汰机制

Redis 是否用到家,从这就能看出来。比如你 Redis 只能存 5G 数据,可是你写了 10G,那会删 5G 的数据。怎么删的,这个问题思考过么?

正解:Redis 采用的是定期删除+惰性删除策略。

为什么不用定时删除策略

定时删除,用一个定时器来负责监视 Key,过期则自动删除。虽然内存及时释放,但是十分消耗 CPU 资源。在大并发请求下,CPU 要将时间应用在处理请求,而不是删除 Key,因此没有采用这一策略。

定期删除+惰性删除如何工作

定期删除,Redis 默认每个 100ms 检查,有过期 Key 则删除。需要说明的是,Redis 不是每个 100ms 将所有的 Key 检查一次,而是随机抽取进行检查。如果只采用定期删除策略,会导致很多 Key 到时间没有删除。于是,惰性删除派上用场。

采用定期删除+惰性删除就没其他问题了么

不是的,如果定期删除没删除掉 Key。并且你也没及时去请求 Key,也就是说惰性删除也没生效。这样,Redis 的内存会越来越高。那么就应该采用内存淘汰机制。

在 redis.conf 中有一行配置:

# maxmemory-policy volatile-lru

该配置就是配内存淘汰策略的:

  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。

  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 Key。(推荐使用,目前项目在用这种)(最近最久使用算法)

  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 Key。(应该也没人用吧,你不删最少使用 Key,去随机删)

  • volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 Key。这种情况一般是把 Redis 既当缓存,又做持久化存储的时候才用。(不推荐)

  • volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 Key。(依然不推荐)

  • volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 Key 优先移除。(不推荐)

5、Redis 和数据库双写一致性问题

一致性问题还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致的问题。前提是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性。

另外,我们所做的方案从根本上来说,只能降低不一致发生的概率。因此,有强一致性要求的数据,不能放缓存。首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。

6、如何应对缓存穿透和缓存雪崩问题

这两个问题,一般中小型传统软件企业很难碰到。如果有大并发的项目,流量有几百万左右,这两个问题一定要深刻考虑。缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。

缓存穿透解决方案:

  • 利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试。

  • 采用异步更新策略,无论 Key 是否取到值,都直接返回。Value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。

  • 提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的 Key。迅速判断出,请求所携带的 Key 是否合法有效。如果不合法,则直接返回。

缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。

缓存雪崩解决方案:

  • 给缓存的失效时间,加上一个随机值,避免集体失效。

  • 使用互斥锁,但是该方案吞吐量明显下降了。

  • 双缓存。我们有两个缓存,缓存 A 和缓存 B。缓存 A 的失效时间为 20 分钟,缓存 B 不设失效时间。自己做缓存预热操作。

  • 然后细分以下几个小点:从缓存 A 读数据库,有则直接返回;A 没有数据,直接从 B 读数据,直接返回,并且异步启动一个更新线程,更新线程同时更新缓存 A 和缓存 B。

7、如何解决 Redis 的并发竞争 Key 问题

这个问题大致就是,同时有多个子系统去 Set 一个 Key。这个时候要注意什么呢?大家基本都是推荐用 Redis 事务机制。

但是我并不推荐使用 Redis 的事务机制。因为我们的生产环境,基本都是 Redis 集群环境,做了数据分片操作。你一个事务中有涉及到多个 Key 操作的时候,这多个 Key 不一定都存储在同一个 redis-server 上。因此,Redis 的事务机制,十分鸡肋。

如果对这个 Key 操作,不要求顺序

这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做 set 操作即可,比较简单。

如果对这个 Key 操作,要求顺序

假设有一个 key1,系统 A 需要将 key1 设置为 valueA,系统 B 需要将 key1 设置为 valueB,系统 C 需要将 key1 设置为 valueC。

期望按照 key1 的 value 值按照 valueA > valueB > valueC 的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳。

假设时间戳如下:

系统 A key 1 {valueA 3:00}

系统 B key 1 {valueB 3:05}

系统 C key 1 {valueC 3:10}

那么,假设系统 B 先抢到锁,将 key1 设置为{valueB 3:05}。接下来系统 A 抢到锁,发现自己的 valueA 的时间戳早于缓存中的时间戳,那就不做 set 操作了,以此类推。其他方法,比如利用队列,将 set 方法变成串行访问也可以。

8、Redis有哪些适合的场景

  • 会话缓存(Session Cache)

最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的。随着 Redis 的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。

  • 全页缓存(FPC)

除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。

  • 队列

Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。

如果在Google中搜索“Redis queues”,就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker。

  • 排行榜/计数器

Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以要从排序集合中获取到排名最靠前的10个用户称之为“user_scores”,也非常简单。

  • 发布/订阅

最后(但肯定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。在社交网络连接中常用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统。

9、如何通过Redis实现分布式锁

分布式锁需要解决的问题:

  • 互斥性

  • 安全性

  • 死锁:一个持有锁的客户端宕机而导致其他客户端再也无法获得锁,从而导致的死锁;

  • 容错

如何实现:

SETNX(Set if not exsist) key value:如果key不存在,则创建并赋值。因为SETNX有上述功能,并且操作都是原子的,因此在初期的时候可以用来实现分布式锁。

  • 时间复杂度:O(1)

  • 返回值:设置成功,返回1,表明此时没有其他线程占用该资源;设置失败,返回0,表示此时有别的线程正在占用该资源。

使用EXPIRE key seconds来解决SETNX长期有效的问题:

  • 设置key的生存时间,当key过期时(生存时间为0),会被自动删除;

  1. RedisService redisService = SpringUtils.getBean(RedisService.class);

  2. long status = redisService.setnx(key,"1");

  3. if(status == 1){

  4. redisService.expire(key,expire);

  5. //执行独占资源逻辑

  6. doOcuppiedWork();

  7. }

  • 以上方法的缺点:原子性无法得到满足

从Redis 2.1.6 以后,原子操作set:

SET key value [EX seconds] [PX milliseconds] [NX|XX]

  • EX seconds:设置键的过期时间为second(秒)

  • PX milliseconds:设置键的过期时间为millisecond(毫秒)

  • NX:只在键不存在的时候,才对键进行设置操作,效果等同于setnx

  • XX:只在键已存在的时候,才对键进行设置操作

  • SET操作成功完成时,返回OK,否则则返回nil

  1. > set lock 123 ex 10 nx

  2. OK

  3. > set lock 122 ex 10 nx

  4. (nil)

代码实现例如:

  1. RedisService redisService = SpringUtils.getBean(RedisService.class);

  2. String result = redisService.set(lockKey,requestId,SET_IF_NOT_EXIST,

  3. SET_WITH_WITH_EXPIRE_TIME,expireTime);

  4. if("OK".equals(result)){

  5. //执行独占资源逻辑

  6. doOcuppiedWork();

  7. }

大量key同时过期的注意事项:

集中过期,由于清楚大量key很耗时,会出现短暂的卡顿现象。

解决方法:在设置key的过期时间的时候,给每个key加上一个随机值。

8、Redis集群方案应该怎么做?都有哪些方案?

  • twemproxy:大概概念是,它类似于一个代理方式,使用方法和普通redis无任何区别,设置好它下属的多个redis实例后,使用时在本需要连接redis的地方改为连接twemproxy,它会以一个代理的身份接收请求并使用一致性hash算法,将请求转接到具体redis,将结果再返回twemproxy。使用方式简便(相对redis只需修改连接端口),对旧项目扩展的首选。问题:twemproxy自身单端口实例的压力,使用一致性hash后,对redis节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。

  • codis:目前用的最多的集群方案,基本和twemproxy一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新hash节点。

  • redis cluster3.0自带的集群,特点在于他的分布式算法不是一致性hash,而是hash槽的概念,以及自身支持节点设置从节点。具体看官方文档介绍。

  • 在业务代码层实现,起几个毫无关联的redis实例,在代码层,对key 进行hash计算,然后去对应的redis实例操作数据。这种方式对hash层代码要求比较高,考虑部分包括,节点失效后的替代算法方案,数据震荡后的自动脚本恢复,实例的监控,等等。

往期精彩

01 漫谈发版哪些事,好课程推荐

02 Linux的常用最危险的命令

03 精讲Spring Boot—入门+进阶+实例

04 优秀的Java程序员必须了解的GC哪些

05 互联网支付系统整体架构详解

关注我

每天进步一点点

喜欢!在看☟

redis key失效的事件_Redis常见、常用的知识点相关推荐

  1. redis key失效的事件_《分享几道高频 Redis 高频面试题,面试不用愁》

    1.说说 Redis 都有哪些应用场景? 缓存:这应该是 Redis 最主要的功能了,也是大型网站必备机制,合理地使用缓存不仅可以加 快数据的访问速度,而且能够有效地降低后端数据源的压力. 共享Ses ...

  2. redis 能不能监听特定的key失效_php监听redis key失效触发回调事件

    订单超时.活动过期解决方案:php监听redis key失效触发回调事件 Redis 的 2.8.0 版本之后可用,键空间消息(Redis Keyspace Notifications),配合 2.0 ...

  3. php 监听redis,php监听redis key失效触发回调事件_后端开发

    php中使用fsockopen实现异步请求(代码示例)_后端开发 php执行一段程序,有可能几毫秒就执行完毕,也有可能耗时较长.例如,用户下单这个事件,如果调用了些第三方服务进行发邮件.短信.推送等通 ...

  4. php 监听redis,php监听redis key失效触发回调事件

    订单超时.活动过期解决方案:php监听redis key失效触发回调事件 Redis 的 2.8.0 版本之后可用,键空间消息(Redis Keyspace Notifications),配合 2.0 ...

  5. Spring Boot 监听 Redis Key 失效事件实现定时任务

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:彭超 https://antoniopeng.com 业务场 ...

  6. redis 能不能监听特定的key失效_Spring boot实现监听Redis key失效事件实现和其它方式...

    1.pom 中添加依赖 org.springframework.boot spring-boot-starter-data-redis 2.定义配置RedisListenerConfig /** * ...

  7. 多服务监听Redis key失效通知,并指定特定机器进行处理业务

    #1.修改 redis.conf 配置 找到notify-keyspace-events 并将notify-keyspace-events 修改为 notify-keyspace-events Ex ...

  8. springboot listener_Springboot 监听redis key的过期事件

    项目中常常会碰到这样的需求,用户下订单后,30分钟未完成自动取消订单的功能. 有人说这个简单呀,写个定时任务就搞定了.除了定时任务之外,难道就没有其他的方法来实现吗?有--Redis 的键空间通知事件 ...

  9. 监听Redis 缓存过期(Key 失效)事件

    很多业务场景,例如订单过期自动删除,订单几天后自动好评,这些常用操作可以通过定时任务,数据库轮询做,但是订单量大的情况可能会对数据库产生大的压力. 所以今天介绍的就是 redis 缓存过期通知. 注: ...

最新文章

  1. 1084 Broken Keyboard
  2. 使用VS Code新建编译Flutter项目
  3. ArcGIS for JavaScript 关于路径开发的一些记录(二)
  4. 深入浅出时序数据库之预处理篇——批处理和流处理,用户可定制,但目前流行influxdb没有做...
  5. 春运前五日全国累计发送旅客3.49亿人次 同比增0.58%
  6. c语言vbs,我的vbs整人程序
  7. PC,移动端H5实现实现小球加入购物车效果
  8. 创建物化视图commit_oracle慎用基于on commit刷新物化视图的方式(一)
  9. 哪吒U Pro试驾:透明A柱超实用、满电500公里保底续航
  10. elasticsearch6.1.3 集成分词器
  11. 农村环境保护学习资料
  12. (转)屈子:独立人格可与日月争光
  13. 电脑换硬盘要重装系统吗
  14. 医院管理信息系统 HIS EMR PACS LIS
  15. Linux文件系统以及目录结构简介
  16. 格式化硬盘并安装Win10和Ubuntu双系统
  17. 手机黑屏,但是按开机键有震动,能进系统,usb也能识别fastboot和rec(bl已解)
  18. beyond compare java_文件内容比较工具---Text compare Beyond compare
  19. 2018年最流行的5大开源项目
  20. 火车票查询项目计划进度安排

热门文章

  1. 团队作业4——第一次项目冲刺(Alpha版本) Day1
  2. Codeforces Round #327 div2
  3. php的计划任务定时执行
  4. php 不支持curl 的解决方案
  5. c++ primer 3.6节练习答案
  6. 30个Oracle语句优化规则详解(1)
  7. cocos2dx android运行Luac编译后的lua代码
  8. 数据库外连接和内连接详解
  9. 【jQuery笔记Part1】04-webstorm定义活动模板
  10. 2021总结-》2022年