高并发缓存架构——雪崩解决方案 (网易云课堂学习笔记)

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();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章