码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 知识管理——从缓存击穿谈起


    最近对编程知识点进行管理,发现关键还是知识的关联,就是理清知识的关系,对知识点建立关联,从而搭建知识体系。

    以高并发系统中常见的缓存击穿作为例子记录。

    缓存击穿,指的是当请求落到缓存时,缓存失效,请求穿过缓存直接访问数据库。

    解决缓存击穿的方法,关键在于缓存失效时缓存要如何更新,保证缓存是有效的。

    解决方法有两个,关键点分别是锁和异步,目的都是为了保证并发下单线程的写操作:

    一是使用互斥锁,使用锁写缓存。当缓存失效时,只允许一个线程从数据库加载数据,更新缓存,其他线程只能等待获取缓存;

    二是设置缓存永不过期,或者异步线程不断更新缓存,设置失效时间。

    方法一的关键是锁,当多个http请求读缓存时,只能有一个htttp对应的线程负责写缓存。从锁可以关联的知识点,双重检测锁、分布式锁。

    方法二的关键是异步,多个http请求只负责读缓存,缓存的更新和请求无关,后台会有异步线程不断写缓存。从异步可以关联,异步消费模式、AQS。

    此时知识点就会变成知识线,缓存击穿 —— 分布式锁 或 异步消费 。

    下面就是具体的知识点。

    双重检测锁,最常见单例模式,通过双重检测对象是否为空实现

    1. private Object mux = new Object(); // 锁
    2. private Object instance; // 单例对象
    3. private void init() {
    4. if (instance == null) {
    5. synchronized (mux) {
    6. if (instance == null) {
    7. instance = new Object();
    8. }
    9. }
    10. }
    11. }

    分布式锁,一般使用redis或者zookeeper实现。

    redis分布式锁,用set+exist/setnx和expire两命令实现,值为线程id或者是过期时间命令setnx + expire分开写的lua实现,setnx用set和exist组合代替

    1. -- 加锁
    2. local lockname = KEYS[1];
    3. local threadId = ARGV[1];
    4. local releaseTime = ARGV[2];
    5. -- 首次请求
    6. if(redis.call('exists', lockname) == 0) then
    7. redis.call('set', lockname, threadId);
    8. redis.call('expire', lockname, releaseTime);
    9. return 1;
    10. end;
    11. -- 重复请求(此处没记录持有锁的线程的重入次数,所以不支持可重入)
    12. if(redis.call('exists', lockname) == 1) then
    13. redis.call('expire', lockname, releaseTime);
    14. return 0;
    15. end;
    16. return -1;
    17. -- 解锁
    18. local lockname = KEYS[1];
    19. local threadId = ARGV[1];
    20. -- lockname、threadId不存在
    21. if (redis.call('hexists', lockname, threadId) == 0) then
    22. return 0;
    23. end;
    24. redis.call('del', key);
    25. return 1;

    set的扩展命令(set ex px nx)的java实现

    1. // 加锁
    2. public Boolean tryLock(String lockName, String threadId, long timeout, TimeUnit unit) {
    3. return redisTemplate.opsForValue().setIfAbsent(lockName, threadId, timeout, unit);
    4. }
    5. // 解锁,防止删错别人的锁,以uuid为value校验是否自己的锁
    6. public void unlock(String lockName, String threadId) {
    7. if(threadId.equals(redisTemplate.opsForValue().get(lockName)){
    8. redisTemplate.opsForValue().del(lockName);
    9. }
    10. }

    异步消费模式(使用AQS实现)

    1. LinkedBlockingQueue eventQueue = new LinkedBlockingQueue<>();
    2. public void push(Object event){
    3. eventQueue.add(event);
    4. }
    5. public void run(){
    6. // 异步线程
    7. while(true){
    8. try {
    9. Object event = this.eventQueue.poll(3000, TimeUnit.MILLISECONDS);
    10. // 异步消费
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }
    14. }
    15. }
    16. 在redission中,也有异步线程更新缓存的实现,那就是大名鼎鼎的看门狗watchdog

      1. private RFuture tryAcquireOnceAsync(long waitTime, long leaseTime,
      2. TimeUnit unit, long threadId) {
      3. if (leaseTime != -1L) {
      4. return this.tryLockInnerAsync(waitTime, leaseTime, unit,
      5. threadId, RedisCommands.EVAL_NULL_BOOLEAN);
      6. } else {
      7. RFuture ttlRemainingFuture = this.tryLockInnerAsync(waitTime,
      8. this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),
      9. TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
      10. ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
      11. if (e == null) {
      12. if (ttlRemaining) {
      13. this.scheduleExpirationRenewal(threadId);
      14. }
      15. }
      16. });
      17. return ttlRemainingFuture;
      18. }
      19. }

    17. 相关阅读:
      2023.9.2 关于 JVM 垃圾回收机制(GC)
      触控笔有必要买吗?便宜好用的手写笔推荐
      JavaEE--线程基础(中)
      解读 * 台工程,DevOps真的死了吗?不,它只是换了个马甲而已,弥补了DevOps空心理论,让DevOps继续发展壮大
      mac安装并使用wireshark
      java多线程中的Fork和Join
      如何应对红帽不再维护 CentOS
      STM32H743的FDCAN使用方法(2):STM32CubeMX初始化代码修改
      打假Yolov7的精度,不是所有的论文都是真实可信
      蚂蚁链牵头两项区块链国际标准在ITU成功立项
    18. 原文地址:https://blog.csdn.net/Cceking/article/details/125811619
      • 最新文章
      • 攻防演习之三天拿下官网站群
        数据安全治理学习——前期安全规划和安全管理体系建设
        企业安全 | 企业内一次钓鱼演练准备过程
        内网渗透测试 | Kerberos协议及其部分攻击手法
        0day的产生 | 不懂代码的"代码审计"
        安装scrcpy-client模块av模块异常,环境问题解决方案
        leetcode hot100【LeetCode 279. 完全平方数】java实现
        OpenWrt下安装Mosquitto
        AnatoMask论文汇总
        【AI日记】24.11.01 LangChain、openai api和github copilot
      • 热门文章
      • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
        奉劝各位学弟学妹们,该打造你的技术影响力了!
        五年了,我在 CSDN 的两个一百万。
        Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
        面试官都震惊,你这网络基础可以啊!
        你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
        心情不好的时候,用 Python 画棵樱花树送给自己吧
        通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
        13 万字 C 语言从入门到精通保姆级教程2021 年版
        10行代码集2000张美女图,Python爬虫120例,再上征途
      Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
      正则表达式工具 cron表达式工具 密码生成工具

      京公网安备 11010502049817号