【七日打卡】落地高并发场景的缓存与数据库双写一致性保障方案

815 阅读3分钟

准备工作

开发环境:

  1. JDK 8
  2. Apache Maven

依赖的jar:

  1. MySQL jdbc驱动
  2. MyBatis
  3. jedis
  4. MySQL数据库连接池

pom中引入依赖的jar包,搭建出一个Spring Boot应用即可。

线程池和内存队列初始化

线程池+内存队列初始化

  • Servlet容器启动时,创建一个线程池,往线程池中提交线程
  • 每个线程关联一个内存队列
  • 后面的读请求和写请求只需要分发到队列中即可,线程就会从队列中获取并处理请求

两种请求对象的封装

public interface Request {

    void process();
}
  1. 更新数据库的请求对象
    1. 删除缓存
    2. 更新数据库
  2. 读取数据库的请求对象

请求异步Service封装

  1. 做请求的路由,根据每个请求的商品id,路由到对应的内存队列中

    千万不能同一条数据,这一次路由到A队列,下一次路由到B队列,这样的话,异步串行化内存队列处理就毫无意义,只有每一次都路由到固定队列中才能使方案真正生效。

            String key  = String.valueOf(productId);
            int    h;
            int    hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    
    		// 对hash值取模,将hash值路由到指定的内存队列中,比如内存队列大小8
            // 用内存队列的数量对hash值取模之后,结果一定是在0~7之间
            // 任何一个商品id都会被固定路由到同样的一个内存队列中去的
    		...
    
  2. 将请求放入对应的队列中,完成路由操作

工作线程封装

  1. 关联某个内存队列
  2. 处理队列中的请求

两种请求Controller封装

写请求比较简单,我们这里只说一下读请求。

读取数据请求Controller

  1. 尝试从Redis中读取一次商品库存的缓存
  2. 如果读取到了结果,那么直接返回
  3. 否则等待一段时间,继续1的步骤
  4. 如果等待时间内(200ms)都没读取到,那就直接从数据库中获取数据,直接返回

读请求去重优化

对一个商品的库存的数据库更新操作已经在内存队列中了;然后对这个商品的库存的读取操作,要求读取数据库的库存数据,然后更新到缓存中,多个读;这多个读,其实只要有一个读请求操作压到队列里就可以了。

其他的读操作,全部都wait那个读请求的操作,刷新缓存,就可以读到缓存中的最新数据了。

反思

代码实现的上述步骤可能还有一些地方需要去完善,可能会存在漏洞,bug什么的。但基本的思想应该是得到了体现。下图列出了一个详细的落地方案,给出了一些漏洞的思考和解决方法。

在库存服务中实现缓存与数据库双写一致性保障方案

图片地址:liutianruo-2019-go-go-go.oss-cn-shanghai.aliyuncs.com/%E4%BA%BF%E…