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

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

测试场景:

  1. 一个写请求:
  2. 写请求模拟耗时操作:休眠 10 秒
  3. 在写休眠中,来一个读请求

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

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

在数据库插入一条数据

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

# redis 中无数据情况下

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

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

日志信息

2019-04-06 20:55:16.257  INFO 9420 --- [nio-6001-exec-1] c.m.c.e.i.w.ProductInventoryController   : 更新商品库存请求:商品id=1,库存=99
2019-04-06 20:55:16.258  INFO 9420 --- [nio-6001-exec-1] c.e.i.s.i.RequestAsyncProcessServiceImpl : 路由信息:key=1,商品 ID =1,队列 index=1
2019-04-06 20:55:16.375  INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread       : 处理请求:{"forceRfresh":false,"productId":1}
2019-04-06 20:55:16.376  INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread       : 写请求:{"forceRfresh":false,"productId":1}
2019-04-06 20:55:16.440  INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 已删除缓存:商品 ID=1
2019-04-06 20:55:16.440  INFO 9420 --- [pool-1-thread-2] .c.e.i.r.ProductInventoryDBUpdateRequest : 写请求:模拟写耗时操作,休眠 10 秒钟
// 上面开始模拟耗时操作了
2019-04-06 20:55:17.970  INFO 9420 --- [nio-6001-exec-2] c.m.c.e.i.w.ProductInventoryController   : 读取商品库存请求:商品id=1
2019-04-06 20:55:17.971  INFO 9420 --- [nio-6001-exec-2] c.e.i.s.i.RequestAsyncProcessServiceImpl : 路由信息:key=1,商品 ID =1,队列 index=1
2019-04-06 20:55:18.190  INFO 9420 --- [nio-6001-exec-2] c.m.c.e.i.w.ProductInventoryController   : 超时 200 毫秒退出尝试,商品 ID=1
2019-04-06 20:55:18.190  INFO 9420 --- [nio-6001-exec-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 数据库获取商品,商品 ID=1
// 等待超时,并从数据库获取,获取到了 100 的库存
2019-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}
2019-04-06 20:55:18.234  INFO 9420 --- [nio-6001-exec-2] c.e.i.s.i.RequestAsyncProcessServiceImpl : 路由信息:key=1,商品 ID =1,队列 index=1
2019-04-06 20:55:26.497  INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 已更新数据库:商品 ID=1,库存=99
// 写完成之后,开始读请求的处理
2019-04-06 20:55:26.499  INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread       : 处理请求:{"forceRfresh":false,"productId":1}
2019-04-06 20:55:26.499  INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread       : 读请求:{"forceRfresh":false,"productId":1}
2019-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}
2019-04-06 20:55:26.499  INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 数据库获取商品,商品 ID=1
2019-04-06 20:55:26.503  INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 设置缓存:{"inventoryCnt":99,"productId":1}
// 下面的是强制刷新缓存,由于前面耗时操作,导致直接读库并强制刷新缓存操作
2019-04-06 20:55:26.515  INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread       : 处理请求:{"forceRfresh":true,"productId":1}
2019-04-06 20:55:26.515  INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 数据库获取商品,商品 ID=1
2019-04-06 20:55:26.523  INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 设置缓存:{"inventoryCnt":99,"productId":1}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 基于缓存中有库存测试

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

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

日志输出

2019-04-06 21:03:00.913  INFO 9420 --- [nio-6001-exec-6] c.m.c.e.i.w.ProductInventoryController   : 更新商品库存请求:商品id=1,库存=98
2019-04-06 21:03:00.913  INFO 9420 --- [nio-6001-exec-6] c.e.i.s.i.RequestAsyncProcessServiceImpl : 路由信息:key=1,商品 ID =1,队列 index=1
2019-04-06 21:03:00.913  INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread       : 处理请求:{"forceRfresh":false,"productId":1}
2019-04-06 21:03:00.913  INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread       : 写请求:{"forceRfresh":false,"productId":1}
2019-04-06 21:03:00.924  INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 已删除缓存:商品 ID=1
2019-04-06 21:03:00.924  INFO 9420 --- [pool-1-thread-2] .c.e.i.r.ProductInventoryDBUpdateRequest : 写请求:模拟写耗时操作,休眠 10 秒钟
2019-04-06 21:03:02.925  INFO 9420 --- [nio-6001-exec-7] c.m.c.e.i.w.ProductInventoryController   : 读取商品库存请求:商品id=1
2019-04-06 21:03:02.925  INFO 9420 --- [nio-6001-exec-7] c.e.i.s.i.RequestAsyncProcessServiceImpl : 路由信息:key=1,商品 ID =1,队列 index=1
2019-04-06 21:03:03.147  INFO 9420 --- [nio-6001-exec-7] c.m.c.e.i.w.ProductInventoryController   : 超时 200 毫秒退出尝试,商品 ID=1
2019-04-06 21:03:03.147  INFO 9420 --- [nio-6001-exec-7] .m.c.e.i.s.i.ProductInventoryServiceImpl : 数据库获取商品,商品 ID=1
2019-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}
2019-04-06 21:03:03.159  INFO 9420 --- [nio-6001-exec-7] c.e.i.s.i.RequestAsyncProcessServiceImpl : 路由信息:key=1,商品 ID =1,队列 index=1
2019-04-06 21:03:10.937  INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 已更新数据库:商品 ID=1,库存=98
2019-04-06 21:03:10.938  INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread       : 处理请求:{"forceRfresh":false,"productId":1}
2019-04-06 21:03:10.938  INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread       : 读请求:{"forceRfresh":false,"productId":1}
2019-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}
2019-04-06 21:03:10.938  INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 数据库获取商品,商品 ID=1
2019-04-06 21:03:10.945  INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 设置缓存:{"inventoryCnt":98,"productId":1}
2019-04-06 21:03:10.957  INFO 9420 --- [pool-1-thread-2] c.m.c.e.i.r.RequestProcessorThread       : 处理请求:{"forceRfresh":true,"productId":1}
2019-04-06 21:03:10.957  INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 数据库获取商品,商品 ID=1
2019-04-06 21:03:10.962  INFO 9420 --- [pool-1-thread-2] .m.c.e.i.s.i.ProductInventoryServiceImpl : 设置缓存:{"inventoryCnt":98,"productId":1}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

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

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

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

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

日志输出

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

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

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

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

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

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

# 讲师旁白

后续方案,包括本次的方案都是针对我自己遇到过的特殊场景设计出来的, 可能这个方案不一定 100% 适合其他场景,需要改造

再者,可能方案比较复杂,及时之前实施过,可能有一部分细节没有讲解到,会导致一些漏洞, 这个也是可以理解的

本课程主题是缓存架构,主要是架构和设计思想,有些许漏洞希望理解

课程真正最重要的,不是给你一套 100% 包打天下的代码,而是一种设计思想, 多种设计思想组合起来就是某种架构

TIP

该方案目前并未关注分布式情况下的多服务实例场景, 从这个来看,架构上和一定的代码落地,的确是比较明确和清晰, 是不是说在面试中只要掌握了这种架构思想就能加分一点呢?