数据库与redis缓存双写数据不一致方案

数据库与redis缓存双写数据不一致方案
数据库与缓存不一致只有在出现并发读写时才会出现,如果每天上亿的流量,每秒并发读是几万,每秒只要有更新缓存的请求,
就很可能会发生数据库与缓存不一致的情况。
解决方案。
1、简单方案 先更新数据库,再删除缓存, 延时再删除缓存。

2、异步mq重试删除。

3、数据库和缓存更新读取异步串行化
  更新数据的时候,根据数据的唯一标识,将操作路由后,发送到一个jvm内部队列,读取数据的时候,如果发现数据不在缓存中,那么将读取数据操作,
  根据数据的唯一标识,将操作路由发送到相同jvm的内部队列,一个队列对应一个工作线程,每个工作线程串行执行一条条请求数据,这样的化一个变更操作,先执行更新数据库,再删除缓存,
  在变更操作没有执行完之前,一个读请求将在队列中积压,自悬等待更新操作完成,如果在预期时间范围内更新完成,则返回,如果超时,则从数据库中读取返回,
  队列可以优化同时多个更新请求,只积压一个。
  方案缺点
    1、读请求长时阻塞
    2、读请求并发量过高
    3、多服务器实例部署的请求路由
    4、热点商品的请求路由问题,导致数据倾斜
  1、线程池加内存队列,通过listener进行初始化。单例线程池
  2、两种请求对象封装。
  3、请求异步执行封装
  4、两种请求Controller封装
  5、读请求去重优化
  6、空数据请求过滤优化
  
实现步骤
1、 InitListener 继承 ServletContextListener 监听ServletContext对象的生命周期,实际上就是监听Web应用的生命周期
 contextInitialized方法 当Servlet容器启动Web应用时调用该方法,调用此方法后,容器再对Filter初始化,并且对那些在
 Web应用启动时就需要被初始化的Servlet进行初始化。
   作用:contextInitialized方法中调用RequestProcessorThreadPool的init方法进行初始化。
2、RequestProcessorThreadPool 请求处理线程池 单例模式
属性: ExecutorService threadPool  线程池一个队列对应一个线程进行处理,10个队列对应10个线程,提交到threadPool池中执行
构造方法中进行队列与线程创建,一个队列绑定一个线程进行处理,新建的线程submit方法提交线程池,接受带返回值结果
3、RequestProcessorThread:执行请求工作线程,传入一个ArrayBlockingQueue阻塞队列,通过死循环里从
 队列获取Request 请求,判断请求是否为强制刷新请求,如果非强制,获取所有的RequestQueue,获取flagMap 每个商品对应
 读写请求标识,判断request类型,如果为ProductInventoryDBUpdateRequest DB更新请求 则将商品id对应标识位设置为true
 如果是读缓存刷新请求,获取商品id对应标识位boolean值,假如为空,说明第一次读请求,将商品id对应标识位设置为false
 如果boolean标识不为空且为true,说明之前存在更新DB请求,将商品id标识更新为false;假如标识不为空且标识为false
 则说明之前已存在读请求阻塞,对于此请求过来直接返回true,不进行处理,读请求等待队列中处理完返回前端结果。
 如果是强制刷新请求或者前面标识验证通过则进行request的process请求处理
属性:ArrayBlockingQueue queue 当前线程监控的内存队列
4、RequestQueues:请求内存队列集合,单例模式
属性: 
 List<ArrayBlockingQueue<Request>> queues 请求内存队列集合,默认初始化10个,根据IDhash值取摸进行请求处理队列定位,
 将请求添加到内存阻塞队列。
 Map<Integer, Boolean> flagMap 并发安全Map 保存每个id对应的请求标识位
  true:标识存在updateDb请求
  null:标识不存在任何请求
  false:标识存在读请求,或者读请求更新请求同时存在
方法
 addQueue:添加一个内存队列
 size:内存队列集合大小
 getQueue(int index):根据列表下标获取一个内存队列
 getFlagMap:获取id对应请求标识
5、Request:接口请求对象封装    
方法
 process 处理请求
 getRouteKey 获取请求路由key
 isForceRefresh 是否强制刷新缓存
DbUpdate:数据库更新请求封装,实现请求request 
属性:更新服务service
方法:
 process 先通过service更新db业务,再通过redisDao清理缓存
CacheRefresh:读请求缓存刷新服务,实现请求request    
属性:查询服务service
方法:
 process 先通过service查询DB业务,再通过redisDao设置缓存    
AsyncProcess:异步请求处理服务,实现请求request    
方法:
 process 
   1、先通过RequestQueues单例获取所有请求内存阻塞队列
   2、再通过getRouteKey获取路由key,将值的hash结果与请求队列大小取摸获取请求队列下标
   3、通过路由队列下标获取请求阻塞队列。
   4、将入参request put到路由队列,交由队列对应绑定线程异步处理请求。   
Controller     
属性
 AsyncProcess 异步处理服务
 service   业务处理服务
方法
 update 更新方法    
  1、先创建一个DbUpdate request请求,包装原始更新服务
  2、将请求交给异步处理服务process
  3、返回响应结果
 get 根据id获取详情方法    
  1、先创建一个CacheRefresh request请求,包装原始查询服务
  2、将请求交给异步处理服务process
  3、等待从redis返回响应结果
     1、将请求交给异步处理服务process后需要while(true) 自悬一会,需要尝试等待前面有更新的操作,同时缓存
       刷新的操作,将最新的数据刷新到缓存中。
       先初始化 1、startTime 开始执行时间为当前系统时分毫秒 2、entTime 执行结束时长 0 3、waitTime 等待时长 0
       循环(获取到结果或者等待超时)
          循环里面先判断waitTime 等待是否超过200毫秒,如果超过则break结束循环等待。
          先通过业务服务从redis缓存中尝试获取一次缓存数据,如果获取到则返回响应
          如果没有获取到数据,那么等待几十毫秒,设置entTime 为当前时分毫秒
          waitTime = entTime - startTime;
    
        等待200毫秒没有从redis获取到数据,那么直接通过业务服务从数据库获取数据
        获取之后刷新缓存,返回响应结果
    其他条件返回异常结果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值