1. 高并发缓存架构——雪崩解决方案
- 网易云课堂课程地址
- https://study.163.com/course/courseLearn.htm?courseId=1006355036#/learn/live?lessonId=1053884737&courseId=1006355036
Tip
性能(为什么要用缓存)
1. MySQL
- MySQL官方测试报告的机器性能比较完美,实际可以看阿里云性能白皮书MySQL版
- https://help.aliyun.com/document_detail/53638.html?spm=a2c4g.11186623.6.1333.5c896fddmFtkF6
- 常用的8核16G、32G,TPS在1k左右,QPS在2w左右(TPS:是TransactionsPerSecond的缩写,也就是事务数/秒;QPS:Queries Per Second意思是“每秒查询率”。)
2. Redis
- Redis 性能官方文档
- https://redis.io/topics/benchmarks
- Intel® Xeon® CPU E5520 @ 2.27GHz (without pipelining) 单核 12w/s
缓存失效的两种场景(12306余票查询为例)
- 高峰期大面积缓存key失效(所有车次查询全部依赖数据库)
- 解决方法简单,对不同车次设置不同的缓存失效时间不同
- 局部高峰期,热点缓存key失效(某一趟车次的海量请求直击数据库)
缓存雪崩
- 因为缓存服务挂掉或者热点缓存失效,所有请求都去查数据库,导致数据库连接不够或者数据库处理不过来,从而导致整个系统不可用。
解决方案
锁
// Java中代码实现的锁
Lock lock = new ReentrantLock();
// 查询方法中,缓存失效时
lock.lock();
try {
// 再次判断缓存是否存在
// 查询数据库,重建缓存
} finally {
lock.unlock();
}
- 优点:简单有效、适用范围广
- 缺点:阻塞其他线程,锁的颗粒度太粗
细粒度的锁
// 每个车次一个锁
ConcurrentHashMap<String, String> maplock = new ConcurrentHashMap<>();
// 查询方法中,缓存失效时
boolean lock = false;
try {
lock = maplock.putIfAbsent(ticketSeq, "true") == null;
if(lock) {
// 再次判断缓存是否存在
// 查询数据库,重建缓存
} else {
// 没拿到锁的怎么办?——缓存降级,根据业务需要降级
// 方案一,返回固定值
// 方案二,读备份缓存(操作主缓存时双写,备份缓存不设置过期时间)
}
} finally {
if(lock) {
maplock.remove(tiketSeq);
}
}
降级策略
- 优点:灵活多变,方便使用
- 缺点:需要开发人员掌控业务,增加维护复杂度
多线程单元测试
private static final int THREAD_NUM = 1000;
private CountDownLatch countDownLatch = new CountDownLatch(THREAD_NUM);
@Test
public void test() throws InterruptedException {
Thread[] threads = new Thread[THREAD_NUM];
for(int i=0; i<THREAD_NUM; i++) {
Thread thread = new Thread(() -> {
try {
countDownLatch.await();
ticketService.queryTicketStock("G290");
} catch (Exception e) {
e.printStackTrace();
}
});
threads[i] = thread;
thread.start();
// 倒计时计数器减1,代表又一个线程就绪了
countDownLatch.countDown();
}
// 等待上面所有线程执行完毕后,结束测试
for (Thread thread : threads) {
thread.join();
}
}