欢迎关注嘉瑞科技官方网站,江西领先的企业信息化服务商!

业务咨询:18970825117 15180163170

Java技术干货分享:Redis和数据库一致性

  • 发布时间:2019-09-12
  • 作者:
  • 浏览次数:256

1、实时同步

对强一致要求比较高的,应采用实时同步方案,即查询缓存查询不到再从 DB 查询,保存到缓存;

更新缓存时,先更新数据库,再将缓存的设置过期(建议不要去更新缓存内容,直接设置缓存过期)。

为什么不去更新缓存内容,而是设置缓存过期呢?

答:我们先来了解两个概念

1.1.缓存穿透

缓存 穿透是指查询一个数据库中一定不存在的数据,由于缓存是不命中时需要从数据库中查询,查不到数据则不写入缓存,这就将导致这个不存在的数据每次请求都要到数据库中查询,造成缓存穿透。


你可以通俗的理解,直接把缓存穿透了,没有利用到缓存。。。

举例:

If(redis 存在此 key){

查询 redis 值

}else{

查询数据库,如果能查到数据,就写入到 redis 中

}

这一段代码逻辑就会造成缓存穿透的恶果。。

你想,假设这个查询数据库中永远没有值,那么这个 redis 中这个 key 是不是就不会创建,那么代码就永远只走查询数据库这段代码,跟 redis 没关系了。。

解决缓存穿透很简单,就是数据库查询不到也要缓存结果,不过缓存结果赋值为空而已,但它的过期时间会很短,最长不超过五分钟。

If(redis 存在此 key){

Var redisValue= 查询 redis 值

返回值

}else{

查询数据库 .

If( 能查到数据 ){

就写入到 redis 中

}else{

如果查询不到,也写到 redis 中,只不过值是空值

}

}

要注意的点:

第一,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间 ( 如果是攻击,问题更严重 ) ,比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。

第二, 缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为 5 分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。

第三,Insert 时需要事先移除要查询的 key ,否则即便 DB 中有值也查询不到(当然也可以设置空缓存的过期时间)

1.2.缓存雪崩

如果 缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。

这个没有完美解决办法,但可以分析用户行为,尽量让失效时间点均匀分布。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。

解决方法

1. 加锁排队 . 限流 -- 限流算法 . 1. 计数 2. 滑动窗口 3. 令牌桶 Token Bucket 4. 漏桶 leaky bucket [1]

在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。

业界比较常用的做法,是使用 mutex 。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去 load db ,而是先使用缓存工具的某些带成功操作返回值的操作(比如 Redis的 SETNX 或者 Memcache 的 ADD )去 set 一个 mutex key ,当操作返回成功时,再进行 load db 的操作并回设缓存;否则,就重试整个 get 缓存的方法。

SETNX ,是「 SET if Not eXists 」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。

2. 数据预热

可以通过缓存 reload 机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的 key ,设置不同的过期时间,让缓存失效的时间点尽量均匀。

3. 做二级缓存,或者双缓存策略。

A1 为原始缓存, A2 为拷贝缓存, A1 失效时,可以访问 A2 , A1 缓存失效时间设置为短期,A2 设置为长期。

4. 缓存永远不过期

这里的 “永远不过期”包含两层意思:

(1) 从缓存上看,确实没有设置过期时间,这就保证了,不会出现热点 key 过期问题,也就是“物理”不过期。

(2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在 key 对应的 value 里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期 .

从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程 ( 非构建缓存的线程 ) 可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。

1.3.热点Key

热点 key :某个 key 访问非常频繁,当 key 失效的时候有大量的线程来构建缓存,导致负载增加,系统崩溃。

解决办法:

1、 使用锁,单机用 synchronized , lock 等,分布式用分布式锁

2、 缓存过期时间不设置,而是设置在 key 对应的 value 里。如果检测到存的时间超过过期时间则异步更新缓存

3、 在 value 设置一个比过期时间 t0 小的过期时间值 t1 ,当 t1 过期的时候,延长 t1 并做更新缓存操作

4、设置标签缓存,标签缓存设置过期时间,标签缓存过期后,需要异步更新实际缓存

2、异步队列

对于并发程度较高的,可采用异步队列的方式同步,可采用 kafka 等消息中间件处理消息生产和消费。


3、 使用阿里的同步工具 canal

canal 是阿里巴巴旗下的一款开源项目,纯 Java 开发。基于数据库增量日志解析,提供增量数据订阅 & 消费,目前主要支持了 MySQL (也支持 mariaDB )。


起源:早期,阿里巴巴 B2B 公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于 trigger 的方式获取增量变更,不过从 2010 年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅 & 消费的业务,从此开启了一段新纪元。

基于日志增量订阅 & 消费支持的业务:

数据库镜像

数据库实时备份

多级索引 ( 卖家和买家各自分库索引 )

search build

业务 cache 刷新

价格变化等重要业务消息

简单来说, Canal 会将自己伪装成 MySQL 从节点( Slave ),并从主节点( Master )获取 Binlog ,解析和贮存后供下游消费端使用。 Canal 包含两个组成部分:服务端和客户端。服务端负责连接至不同的 MySQL 实例,并为每个实例维护一个事件消息队列;客户端则可以订阅这些队列中的数据变更事件,处理并存储到数据仓库中

原理相对比较简单:

  1. canal 模拟 mysql slave 的交互协议,伪装自己为 mysql slave ,向 mysql master 发送 dump 协议
  2. mysql master 收到 dump 请求,开始推送 binary log 给 slave( 也就是 canal)
  3. canal 解析 binary log 对象 ( 原始为 byte 流 )

联系我们

只要关乎创意 建站 视觉 开发,嘉瑞都能帮您实现

  • 业务咨询

    18970825117 15180163170

    8年专注网站建设

  • QQ咨询

    1633967339

    倾力打造优质产品

  • 微信咨询

    songke810918

    一对一为您量身设计

  • 服务热线

    0791-87963527

    100+用户诚意推荐