100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 高并发高可用复杂系统中的缓存架构(十六) 实现缓存与数据库双写一致性保障方案

高并发高可用复杂系统中的缓存架构(十六) 实现缓存与数据库双写一致性保障方案

时间:2019-12-15 20:17:22

相关推荐

高并发高可用复杂系统中的缓存架构(十六) 实现缓存与数据库双写一致性保障方案

再来回顾下之前的思路:

数据更新:根据唯一标识路由到一个队里中,「删除缓存 + 更新数据」

数据读取:如果不在缓存中,根据唯一标识路由到一个队里中,「读取数据 + 写入缓存」

投入队里之后,就等待结果完成,由于同一个标识路由到的是同一个队列中, 所以相当于加锁了。

下面就来实现这个思路,分几步走:

系统初启动时,初始化线程池与内存队列

两种请求对象封装

请求异步执行 service 封装

两种请求 Controller 封装

读请求去重优化

空数据读请求过滤优化

由于该代码核心的就几个地方,其他的都是基础的业务与数据库的常规操作, 故而笔记只记录重点思路地方

系统初启动时,初始化线程池与内存队列

通过 ApplicationRunner 机制,在系统初始化时,对线程池进行初始化操作

/*** 线程与队列初始化**/@Componentpublic class RequestQueue implements ApplicationRunner {private List<ArrayBlockingQueue<Request>> queues = new ArrayList<>();​@Overridepublic void run(ApplicationArguments args) throws Exception {int workThread = 10;ExecutorService executorService = Executors.newFixedThreadPool(workThread);for (int i = 0; i < workThread; i++) {ArrayBlockingQueue<Request> queue = new ArrayBlockingQueue<>(100);executorService.submit(new RequestProcessorThread(queue));queues.add(queue);}}​public ArrayBlockingQueue<Request> getQueue(int index) {return queues.get(index);}}​​/*** 处理请求的线程*/public class RequestProcessorThread implements Callable<Boolean> {private ArrayBlockingQueue<Request> queue;​public RequestProcessorThread(ArrayBlockingQueue<Request> queue) {this.queue = queue;}​@Overridepublic Boolean call() throws Exception {try {while (true) {Request take = queue.take();take.process();}} catch (InterruptedException e) {e.printStackTrace();}return false;}}

两种请求对象封装

数据更新请求

/*** 数据更新请求**/public class ProductInventoryDBUpdateRequest implements Request {private ProductInventory productInventory;private ProductInventoryService productInventoryService;​public ProductInventoryDBUpdateRequest(ProductInventory productInventory, ProductInventoryService productInventoryService) {this.productInventory = productInventory;this.productInventoryService = productInventoryService;}​@Overridepublic void process() {//1. 删除缓存productInventoryService.removeProductInventoryCache(productInventory.getProductId());//2. 更新库存productInventoryService.updateProductInventory(productInventory);}}

数据刷新请求

/*** 缓存刷新请求*/public class ProductInventoryCacheRefreshRequest implements Request {​private Integer productId;private ProductInventoryService productInventoryService;​public ProductInventoryCacheRefreshRequest(Integer productId, ProductInventoryService productInventoryService) {this.productId = productId;this.productInventoryService = productInventoryService;}​@Overridepublic void process() {// 1. 读取数据库库存ProductInventory productInventory = productInventoryService.findProductInventory(productId);// 2. 设置缓存productInventoryService.setProductInventoryCache(productInventory);}}​

请求异步执行 service 封装

@Servicepublic class RequestAsyncProcessServiceImpl implements RequestAsyncProcessService {@Autowiredprivate RequestQueue requestQueue;​@Overridepublic void process(Request request) {try {// 1. 根据商品 id 路由到具体的队列ArrayBlockingQueue<Request> queue = getRoutingQueue(request.getProductId());// 2. 放入队列queue.put(request);} catch (InterruptedException e) {e.printStackTrace();}}​private ArrayBlockingQueue<Request> getRoutingQueue(Integer productId) {// 先获取 productId 的 hash 值String key = String.valueOf(productId);int h;int hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);​// 对hash值取模,将hash值路由到指定的内存队列中,比如内存队列大小8// 用内存队列的数量对hash值取模之后,结果一定是在0~7之间// 所以任何一个商品id都会被固定路由到同样的一个内存队列中去的int index = (requestQueue.queueSize() - 1) & hash;return requestQueue.getQueue(index);}}

两种请求 Controller 封装

/*** 商品库存**/@RestControllerpublic class ProductInventoryController {@Autowiredprivate RequestAsyncProcessService requestAsyncProcessService;@Autowiredprivate ProductInventoryService productInventoryService;​/*** 更新商品库存*/@RequestMapping("/updateProductInventory")public Response updateProductInventory(ProductInventory productInventory) {try {ProductInventoryDBUpdateRequest request = new ProductInventoryDBUpdateRequest(productInventory, productInventoryService);requestAsyncProcessService.process(request);return new Response(Response.SUCCESS);} catch (Exception e) {e.printStackTrace();return new Response(Response.FAILURE);}}​@RequestMapping("/getProductInventory")public ProductInventory getProductInventory(Integer productId) {try {// 异步获取ProductInventoryCacheRefreshRequest request = new ProductInventoryCacheRefreshRequest(productId, productInventoryService);requestAsyncProcessService.process(request);ProductInventory productInventory = null;​long startTime = System.currentTimeMillis();long endTime = 0L;long waitTime = 0L;// 最多等待 200 毫秒while (true) {if (waitTime > 200) {break;}// 尝试去redis中读取一次商品库存的缓存数据productInventory = productInventoryService.getProductInventoryCache(productId);​// 如果读取到了结果,那么就返回if (productInventory != null) {return productInventory;}// 如果没有读取到结果,那么等待一段时间else {Thread.sleep(20);endTime = System.currentTimeMillis();waitTime = endTime - startTime;}}// 直接尝试从数据库中读取数据productInventory = productInventoryService.findProductInventory(productId);if (productInventory != null) {return productInventory;}} catch (Exception e) {e.printStackTrace();}return new ProductInventory(productId, -1L);}}

读请求去重优化

核心思路是通过:map 来保存写标志

@Servicepublic class RequestAsyncProcessServiceImpl implements RequestAsyncProcessService {@Autowiredprivate RequestQueue requestQueue;​@Overridepublic void process(Request request) {try {Map<Integer, Boolean> flagMap = requestQueue.getFlagMap();​// 如果是一个更新数据库请求if (request instanceof ProductInventoryDBUpdateRequest) {flagMap.put(request.getProductId(), true);} else if (request instanceof ProductInventoryCacheRefreshRequest) {Boolean flag = flagMap.get(request.getProductId());// 系统启动后,就没有写请求,全是读,可能导致 flas = nullif (flag == null) {flagMap.put(request.getProductId(), false);}// 已经有过读或写的请求 并且前面已经有一个写请求了if (flag != null && flag) {// 读取请求把,写请求标志冲掉flagMap.put(request.getProductId(), false);}// 如果是读请求,直接返回,等待写完成即可else if (flag != null && !flag) {return;}}​// 1. 根据商品 id 路由到具体的队列ArrayBlockingQueue<Request> queue = getRoutingQueue(request.getProductId());// 2. 放入队列queue.put(request);} catch (InterruptedException e) {e.printStackTrace();}}

空数据读请求过滤优化

上面的逻辑,会让这种场景下的请求不执行,但是在 getProductInventory 中,如果从缓存中没有读取到,则最终会走一次数据库。

// 系统启动后,就没有写请求,全是读,可能导致 flas = nullif (flag == null) {flagMap.put(request.getProductId(), false);}

这里就存在一个 bug 了,会导致缓存一直被穿透,如果没有写请求的话,读请求被去重了,一直请求数据库。

修复这个 bug 的话,最简单的办法就是在读取数据库后,直接写入缓存中,如果不考虑并发问题的话,直接在 getProductInventory 中读取数据库后写入缓存即可。

那么就还有一个场景会导致缓存会穿透:数据库中没有这个数据,就会一直走查库的操作,这个问题后续会有解决方案;

深入解决去读请求去重优化

上面的代码存在几个问题:

RequestAsyncProcessServiceImpl.process 判定与设置 flag 值在并发情况下会导致 flag 值问题

查库之后直接写缓存在并发情况下会导致数据不一致的情况(多个请求写数据,队列无意义了)

在 ProductInventoryController 中只要走了数据库后,就强制请求刷新缓存

// 直接尝试从数据库中读取数据productInventory = productInventoryService.findProductInventory(productId);if (productInventory != null) {// 读取到了数据,强制刷新缓存ProductInventoryCacheRefreshRequest forceRfreshRequest = new ProductInventoryCacheRefreshRequest(productId, productInventoryService, true);requestAsyncProcessService.process(forceRfreshRequest);return productInventory;}

每个工作线程,自己处理自己队列的读去重请求

/*** 处理请求的线程** @author : zhuqiang* @date : /4/3 22:38*/public class RequestProcessorThread implements Callable<Boolean> {private ArrayBlockingQueue<Request> queue;/*** k: 商品 id v:请求标志: true : 有更新请求*/private Map<Integer, Boolean> flagMap = new ConcurrentHashMap<>();​public RequestProcessorThread(ArrayBlockingQueue<Request> queue) {this.queue = queue;}​@Overridepublic Boolean call() throws Exception {try {while (true) {Request request = queue.take();// 非强制刷新请求的话,就是一个正常的读请求if (!request.isForceRfresh()) {// 如果是一个更新数据库请求if (request instanceof ProductInventoryDBUpdateRequest) {flagMap.put(request.getProductId(), true);} else if (request instanceof ProductInventoryCacheRefreshRequest) {Boolean flag = flagMap.get(request.getProductId());if (flag == null) {flagMap.put(request.getProductId(), false);}// 已经有过读或写的请求 并且前面已经有一个写请求了if (flag != null && flag) {// 读取请求把,写请求标志冲掉// 本次读会正常的执行,组成 1+1 (1 写 1 读)// 后续的正常读请求会被过滤掉flagMap.put(request.getProductId(), false);}// 如果是读请求,直接返回,等待写完成即可else if (flag != null && !flag) {continue;}}}request.process();}} catch (InterruptedException e) {e.printStackTrace();}return false;}}

这样一改造之后,并发的地方,就利用队列串行起来了,那么此代码还存在以下场景的缺陷:

当大量请求超过 200 毫秒未获取到缓存,会导致大量请求汇聚到数据库

这种情况的发生场景有:

大量的写请求在前面,导致后续的大量读请求超时,直接读库

数据库压根就没有这个商品,导致缓存被穿透,一直读库

当大量请求超过 200 毫秒后,在数据库获取到了,并请求强制刷新缓存,导致大量请求又回去到数据库了

这种情况是由于增加了强制刷新标志,导致的另外一个 bug,这个时候的思路可以再增加一个强制刷新队列来做强制读请求去重

总结

异步串行化的实现核心思路:

使用队列来避免数据竞争

删除缓存 + 更新数据库 封装成一个写请求

读取数据库 + 写缓存 封装成一个读请求

根据商品 id 路由到同一个队列中(此方案暂未考虑多服务实例的场景)

有写 1+1(1 写 1 读)时,需要过滤掉大量的读请求

这部分正常读请求如不过滤掉,会进入数据库,且库存并未更新,浪费性能资源与缓存穿透(有数据,且数据已经进入了缓存,但是队列中还一直去数据库执行并刷新缓存)

等待读请求需要超时机制,一旦超时则从数据库获取

此类场景出现的时候可能的原因有如下几点:

每个读或写请求测试耗时不准确

测试不准确导致服务实例不够(当然此章节并未解决多服务实例怎么路由或者解决并发的问题)

缓存被穿透,使用不存在的数据一致访问

库存服务代码调试以及打印日志观察服务的运行流程是否正确

创建数据库 product_inventory,两个字段 Integer product_id、Long inventory_cnt

测试场景:

一个写请求:

写请求模拟耗时操作:休眠 10 秒

在写休眠中,来一个读请求

观察日志,是否按照预想流程和结果进行;

在这之前,需要再关键位置添加日志打印,笔记就不贴代码了;

在数据库插入一条数据

INSERT INTO `eshop`.`product_inventory` (`product_id`, `inventory_cnt`) VALUES ('1', '100');

redis 中无数据情况下

在以上场景的基础下,先来模拟 redis 中无数据的情况下的流程是否正确,因为刚好往数据库中增加了数据,还没有往 redis 中增加数据。 正好测试这个场景

// 写请求http://localhost:6001/updateProductInventory?productId=1&inventoryCnt=99// 读请求http://localhost:6001/getProductInventory?productId=1

日志信息

-04-06 20:55:16.257 INFO 9420 --- [nio-6001-exec-1] c.m.c.e.i.w.ProductInventoryController : 更新商品库存请求:商品id=1,库存=99-04-06 20:55:16.258 INFO 9420 --- [nio-6001-exec-1] c.e.i.s.i.RequestAsyncProcessServiceImpl : 路由信息:key=1,商品 ID =1,队列 index=1-04-06 20:55:16.375 INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 处理请求:{"forceRfresh":false,"productId":1}-04-06 20:55:16.376 INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 写请求:{"forceRfresh":false,"productId":1}-04-06 20:55:16.440 INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 已删除缓存:商品 ID=1-04-06 20:55:16.440 INFO 9420 --- [pool-1-thread-2] .c.e.i.r.ProductInventoryDBUpdateRequest : 写请求:模拟写耗时操作,休眠 10 秒钟// 上面开始模拟耗时操作了-04-06 20:55:17.970 INFO 9420 --- [nio-6001-exec-2] c.m.c.e.i.w.ProductInventoryController : 读取商品库存请求:商品id=1-04-06 20:55:17.971 INFO 9420 --- [nio-6001-exec-2] c.e.i.s.i.RequestAsyncProcessServiceImpl : 路由信息:key=1,商品 ID =1,队列 index=1-04-06 20:55:18.190 INFO 9420 --- [nio-6001-exec-2] c.m.c.e.i.w.ProductInventoryController : 超时 200 毫秒退出尝试,商品 ID=1-04-06 20:55:18.190 INFO 9420 --- [nio-6001-exec-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 数据库获取商品,商品 ID=1// 等待超时,并从数据库获取,获取到了 100 的库存-04-06 20:55:18.234 INFO 9420 --- [nio-6001-exec-2] c.m.c.e.i.w.ProductInventoryController : 缓存未命中,在数据库中查找,商品 ID=1,结果={"inventoryCnt":100,"productId":1}-04-06 20:55:18.234 INFO 9420 --- [nio-6001-exec-2] c.e.i.s.i.RequestAsyncProcessServiceImpl : 路由信息:key=1,商品 ID =1,队列 index=1-04-06 20:55:26.497 INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 已更新数据库:商品 ID=1,库存=99// 写完成之后,开始读请求的处理-04-06 20:55:26.499 INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 处理请求:{"forceRfresh":false,"productId":1}-04-06 20:55:26.499 INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 读请求:{"forceRfresh":false,"productId":1}-04-06 20:55:26.499 INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 1+1 达成,1 写 1 读:{"forceRfresh":false,"productId":1}-04-06 20:55:26.499 INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 数据库获取商品,商品 ID=1-04-06 20:55:26.503 INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 设置缓存:{"inventoryCnt":99,"productId":1}// 下面的是强制刷新缓存,由于前面耗时操作,导致直接读库并强制刷新缓存操作-04-06 20:55:26.515 INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 处理请求:{"forceRfresh":true,"productId":1}-04-06 20:55:26.515 INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 数据库获取商品,商品 ID=1-04-06 20:55:26.523 INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 设置缓存:{"inventoryCnt":99,"productId":1}

基于缓存中有库存测试

与上面例子一样,只不过通过了一次测试,现在缓存中有数据了,再继续执行相同的测试操作,观察日志, 库存由 99 变成 98

// 写请求http://localhost:6001/updateProductInventory?productId=1&inventoryCnt=98// 读请求http://localhost:6001/getProductInventory?productId=1

日志输出

-04-06 21:03:00.913 INFO 9420 --- [nio-6001-exec-6] c.m.c.e.i.w.ProductInventoryController : 更新商品库存请求:商品id=1,库存=98-04-06 21:03:00.913 INFO 9420 --- [nio-6001-exec-6] c.e.i.s.i.RequestAsyncProcessServiceImpl : 路由信息:key=1,商品 ID =1,队列 index=1-04-06 21:03:00.913 INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 处理请求:{"forceRfresh":false,"productId":1}-04-06 21:03:00.913 INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 写请求:{"forceRfresh":false,"productId":1}-04-06 21:03:00.924 INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 已删除缓存:商品 ID=1-04-06 21:03:00.924 INFO 9420 --- [pool-1-thread-2] .c.e.i.r.ProductInventoryDBUpdateRequest : 写请求:模拟写耗时操作,休眠 10 秒钟-04-06 21:03:02.925 INFO 9420 --- [nio-6001-exec-7] c.m.c.e.i.w.ProductInventoryController : 读取商品库存请求:商品id=1-04-06 21:03:02.925 INFO 9420 --- [nio-6001-exec-7] c.e.i.s.i.RequestAsyncProcessServiceImpl : 路由信息:key=1,商品 ID =1,队列 index=1-04-06 21:03:03.147 INFO 9420 --- [nio-6001-exec-7] c.m.c.e.i.w.ProductInventoryController : 超时 200 毫秒退出尝试,商品 ID=1-04-06 21:03:03.147 INFO 9420 --- [nio-6001-exec-7] .m.c.e.i.s.i.ProductInventoryServiceImpl : 数据库获取商品,商品 ID=1-04-06 21:03:03.159 INFO 9420 --- [nio-6001-exec-7] c.m.c.e.i.w.ProductInventoryController : 缓存未命中,在数据库中查找,商品 ID=1,结果={"inventoryCnt":99,"productId":1}-04-06 21:03:03.159 INFO 9420 --- [nio-6001-exec-7] c.e.i.s.i.RequestAsyncProcessServiceImpl : 路由信息:key=1,商品 ID =1,队列 index=1-04-06 21:03:10.937 INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 已更新数据库:商品 ID=1,库存=98-04-06 21:03:10.938 INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 处理请求:{"forceRfresh":false,"productId":1}-04-06 21:03:10.938 INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 读请求:{"forceRfresh":false,"productId":1}-04-06 21:03:10.938 INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 1+1 达成,1 写 1 读:{"forceRfresh":false,"productId":1}-04-06 21:03:10.938 INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 数据库获取商品,商品 ID=1-04-06 21:03:10.945 INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 设置缓存:{"inventoryCnt":98,"productId":1}-04-06 21:03:10.957 INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 处理请求:{"forceRfresh":true,"productId":1}-04-06 21:03:10.957 INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 数据库获取商品,商品 ID=1-04-06 21:03:10.962 INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 设置缓存:{"inventoryCnt":98,"productId":1}

对于在 200 毫秒内无法返回的数据,无论 redis 中是否存在初始库存数据,流程都一样

基于 200 毫秒内返回写操作完成测试

修改休眠时间,让写操作在 200 毫秒内能正常完成

// 写请求http://localhost:6001/updateProductInventory?productId=1&inventoryCnt=97// 读请求http://localhost:6001/getProductInventory?productId=1

日志输出

-04-06 21:07:42.997 INFO 20676 --- [nio-6001-exec-1] c.m.c.e.i.w.ProductInventoryController : 更新商品库存请求:商品id=1,库存=97-04-06 21:07:42.998 INFO 20676 --- [nio-6001-exec-1] c.e.i.s.i.RequestAsyncProcessServiceImpl : 路由信息:key=1,商品 ID =1,队列 index=1-04-06 21:07:43.209 INFO 20676 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 处理请求:{"forceRfresh":false,"productId":1}-04-06 21:07:43.209 INFO 20676 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 写请求:{"forceRfresh":false,"productId":1}-04-06 21:07:43.242 INFO 20676 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 已删除缓存:商品 ID=1-04-06 21:07:43.242 INFO 20676 --- [pool-1-thread-2] .c.e.i.r.ProductInventoryDBUpdateRequest : 写请求:模拟写耗时操作,休眠 100 毫秒-04-06 21:07:43.302 INFO 20676 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 已更新数据库:商品 ID=1,库存=97-04-06 21:07:43.622 INFO 20676 --- [nio-6001-exec-2] c.m.c.e.i.w.ProductInventoryController : 读取商品库存请求:商品id=1-04-06 21:07:43.624 INFO 20676 --- [nio-6001-exec-2] c.e.i.s.i.RequestAsyncProcessServiceImpl : 路由信息:key=1,商品 ID =1,队列 index=1-04-06 21:07:43.629 INFO 20676 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 处理请求:{"forceRfresh":false,"productId":1}-04-06 21:07:43.630 INFO 20676 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 读请求:{"forceRfresh":false,"productId":1}-04-06 21:07:43.630 INFO 20676 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 1+1 达成,1 写 1 读:{"forceRfresh":false,"productId":1}-04-06 21:07:43.630 INFO 20676 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 数据库获取商品,商品 ID=1-04-06 21:07:43.658 INFO 20676 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 设置缓存:{"inventoryCnt":97,"productId":1}-04-06 21:07:43.687 INFO 20676 --- [nio-6001-exec-2] c.m.c.e.i.w.ProductInventoryController : 在缓存中找到,商品 ID=1

由于上面是休眠 100 毫秒,200 毫秒超时,所以人工请求没法模拟出来。

下面的日志输出是休眠 5 秒,10 秒超时

-04-06 21:12:22.725 INFO 21740 --- [nio-6001-exec-1] c.m.c.e.i.w.ProductInventoryController : 更新商品库存请求:商品id=1,库存=97-04-06 21:12:22.726 INFO 21740 --- [nio-6001-exec-1] c.e.i.s.i.RequestAsyncProcessServiceImpl : 路由信息:key=1,商品 ID =1,队列 index=1-04-06 21:12:22.830 INFO 21740 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 处理请求:{"forceRfresh":false,"productId":1}-04-06 21:12:22.831 INFO 21740 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 写请求:{"forceRfresh":false,"productId":1}-04-06 21:12:23.006 INFO 21740 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 已删除缓存:商品 ID=1-04-06 21:12:23.006 INFO 21740 --- [pool-1-thread-2] .c.e.i.r.ProductInventoryDBUpdateRequest : 写请求:模拟写耗时操作,休眠 5 秒钟-04-06 21:12:23.939 INFO 21740 --- [nio-6001-exec-2] c.m.c.e.i.w.ProductInventoryController : 读取商品库存请求:商品id=1-04-06 21:12:23.940 INFO 21740 --- [nio-6001-exec-2] c.e.i.s.i.RequestAsyncProcessServiceImpl : 路由信息:key=1,商品 ID =1,队列 index=1-04-06 21:12:28.047 INFO 21740 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 已更新数据库:商品 ID=1,库存=97-04-06 21:12:28.050 INFO 21740 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 处理请求:{"forceRfresh":false,"productId":1}-04-06 21:12:28.050 INFO 21740 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 读请求:{"forceRfresh":false,"productId":1}-04-06 21:12:28.050 INFO 21740 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread : 1+1 达成,1 写 1 读:{"forceRfresh":false,"productId":1}-04-06 21:12:28.050 INFO 21740 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 数据库获取商品,商品 ID=1-04-06 21:12:28.070 INFO 21740 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 设置缓存:{"inventoryCnt":97,"productId":1}-04-06 21:12:28.086 INFO 21740 --- [nio-6001-exec-2] c.m.c.e.i.w.ProductInventoryController : 在缓存中找到,商品 ID=1

可以看到,在等待超时中,会不断获取缓存中的信息,找到则返回。

此时查看数据库和缓存中的数据都是一致的。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。