2. 再说一下IO , Redis使用的是非阻塞IO , IO多路复用 , 使用了单线程来轮询描述符 , 将数据库的开、关、读、写都转换成了事件 , 减少了线程切换时上下文的切换和竞争 。
3. Redis采用了单线程的模型 , 保证了每个操作的原子性 , 也减少了线程的上下文切换和竞争 。
4. 另外 , 数据结构也帮了不少忙 , Redis全程使用hash结构 , 读取速度快 , 还有一些特殊的数据结构 , 对数据存储进行了优化 , 如压缩表 , 对短数据进行压缩存储 , 再如 , 跳表 , 使用有序的数据结构加快读取的速度 。
5. 还有一点 , Redis采用自己实现的事件分离器 , 效率比较高 , 内部采用非阻塞的执行方式 , 吞吐能力比较大 。
三、Redis缓存和MySQL数据一致性方案详解需求起因
在高并发的业务场景下 , 数据库大多数情况都是用户并发访问最薄弱的环节 。所以 , 就需要使用redis做一个缓冲操作 , 让请求先访问到redis , 而不是直接访问MySQL等数据库 。

文章插图
这个业务场景 , 主要是解决读数据从Redis缓存 , 一般都是按照下图的流程来进行业务操作 。

文章插图
读取缓存步骤一般没有什么问题 , 但是一旦涉及到数据更新:数据库和缓存更新 , 就容易出现缓存(Redis)和数据库(MySQL)间的数据一致性问题 。
不管是先写MySQL数据库 , 再删除Redis缓存;还是先删除缓存 , 再写库 , 都有可能出现数据不一致的情况 。举一个例子:
1.如果删除了缓存Redis , 还没有来得及写库MySQL , 另一个线程就来读取 , 发现缓存为空 , 则去数据库中读取数据写入缓存 , 此时缓存中为脏数据 。
2.如果先写了库 , 在删除缓存前 , 写库的线程宕机了 , 没有删除掉缓存 , 则也会出现数据不一致情况 。
因为写和读是并发的 , 没法保证顺序,就会出现缓存和数据库的数据不一致的问题 。
如来解决?这里给出两个解决方案 , 先易后难 , 结合业务和技术代价选择使用 。
缓存和数据库一致性解决方案

文章插图
1.第一种方案:采用延时双删策略
在写库前后都进行redis.del(key)操作 , 并且设定合理的超时时间 。
伪代码如下:
public void write(String key,Object data){ redis.delKey(key); db.updateData(data); Thread.sleep(500); redis.delKey(key); }具体的步骤就是:
- 先删除缓存;再写数据库;休眠500毫秒;再次删除缓存 。
需要评估自己的项目的读数据业务逻辑的耗时 。这么做的目的 , 就是确保读请求结束 , 写请求可以删除读请求造成的缓存脏数据 。
当然这种策略还要考虑redis和数据库主从同步的耗时 。最后的的写数据的休眠时间:则在读数据业务逻辑的耗时基础上 , 加几百ms即可 。比如:休眠1秒 。
设置缓存过期时间
从理论上来说 , 给缓存设置过期时间 , 是保证最终一致性的解决方案 。所有的写操作以数据库为准 , 只要到达缓存过期时间 , 则后面的读请求自然会从数据库中读取新值然后回填缓存 。
该方案的弊端
结合双删策略+缓存超时设置 , 这样最差的情况就是在超时时间内数据存在不一致 , 而且又增加了写请求的耗时 。
2、第二种方案:异步更新缓存(基于订阅binlog的同步机制)
技术整体思路:
MySQL binlog增量订阅消费+消息队列+增量数据更新到redis
- 读Redis:热数据基本都在Redis写MySQL:增删改都是操作MySQL更新Redis数据:MySQ的数据操作binlog , 来更新到Redis
1)数据操作主要分为两大块:
- 一个是全量(将全部数据一次写入到redis)一个是增量(实时更新)
推荐阅读
- SpringBoot使用Redis实现 自动缓存 更新 删除
- hr|有经验的HR表示,看到这几类毕业生简历,通常都会直接淘汰
- 车子换轮胎时这个小备件一定要记得检查,同样是橡胶材质都会老化
- 教程 Redis+ flask+vue 在线聊天
- 你知道Redis的字符串是怎么实现的吗?
- 跑步对膝盖真的不好吗
- 健身气功五禽戏
- 如何保证Redis和数据库双写一致性的问题?
- 什么是Redis?为什么我们要用Redis?
- Redis安装与使用之Java连接Redis
