准备工作
开发环境:
- JDK 8
- Apache Maven
依赖的jar:
- MySQL jdbc驱动
- MyBatis
- jedis
- MySQL数据库连接池
pom中引入依赖的jar包,搭建出一个Spring Boot应用即可。
线程池和内存队列初始化
- Servlet容器启动时,创建一个线程池,往线程池中提交线程
- 每个线程关联一个内存队列
- 后面的读请求和写请求只需要分发到队列中即可,线程就会从队列中获取并处理请求
两种请求对象的封装
public interface Request {
void process();
}
- 更新数据库的请求对象
- 删除缓存
- 更新数据库
- 读取数据库的请求对象
请求异步Service封装
-
做请求的路由,根据每个请求的商品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都会被固定路由到同样的一个内存队列中去的 ... -
将请求放入对应的队列中,完成路由操作
工作线程封装
- 关联某个内存队列
- 处理队列中的请求
两种请求Controller封装
写请求比较简单,我们这里只说一下读请求。
读取数据请求Controller
- 尝试从Redis中读取一次商品库存的缓存
- 如果读取到了结果,那么直接返回
- 否则等待一段时间,继续1的步骤
- 如果等待时间内(200ms)都没读取到,那就直接从数据库中获取数据,直接返回
读请求去重优化
对一个商品的库存的数据库更新操作已经在内存队列中了;然后对这个商品的库存的读取操作,要求读取数据库的库存数据,然后更新到缓存中,多个读;这多个读,其实只要有一个读请求操作压到队列里就可以了。
其他的读操作,全部都wait那个读请求的操作,刷新缓存,就可以读到缓存中的最新数据了。
反思
代码实现的上述步骤可能还有一些地方需要去完善,可能会存在漏洞,bug什么的。但基本的思想应该是得到了体现。下图列出了一个详细的落地方案,给出了一些漏洞的思考和解决方法。
图片地址:liutianruo-2019-go-go-go.oss-cn-shanghai.aliyuncs.com/%E4%BA%BF%E…