码农知识堂 - 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. 相关阅读:
      vue-router 重复跳转路径报错源码分析和解决方案
      3D立体相册不过是冷锋蓝plus版 html+css
      Taurus.MVC 微服务框架 入门开发教程:项目部署:6、微服务应用程序Docker部署实现多开。
      Java ArrayList扩容机制
      ansible ---- ansible.builtin.command
      LintCode 1359: Convert Sorted Array to Binary Search Tree
      轻量化网络--MobileNet V1
      Unity | 以附加模式加载场景,实现多场景叠加及注意事项
      WPF 控件专题 PasswordBox控件详解
      分析类图(相关搜索:图书管理系统)
    18. 原文地址:https://blog.csdn.net/Cceking/article/details/125811619
      • 最新文章
      • 【JVM】编译执行与解释执行的区别是什么?JVM 使用哪种方式?
        用 Hashids 优雅解决 C 端自增 ID 暴露问题
        V8引擎 精品漫游指南--Ignition篇(上) 指令 栈帧 槽位 调用约定 内存布局 基础内容
        LLVM Pass快速入门(四):代码插桩
        milkup:桌面端 markdown AI续写和即时渲染
        基于项目工程构建SBOM(软件物料清单)的研究
        鸿蒙应用开发UI基础第二节:鸿蒙应用程序框架核心解析与实操
        .NET 中如何快速实现 List 集合去重?
        扣子Coze实战:从0到1打造抖音+小红书热点监控智能体
        浅谈数据访问层
      • 热门文章
      • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
        奉劝各位学弟学妹们,该打造你的技术影响力了!
        五年了,我在 CSDN 的两个一百万。
        Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
        面试官都震惊,你这网络基础可以啊!
        你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
        心情不好的时候,用 Python 画棵樱花树送给自己吧
        通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
        13 万字 C 语言从入门到精通保姆级教程2021 年版
        10行代码集2000张美女图,Python爬虫120例,再上征途
      小工具 小游戏
      Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1

      京公网安备 11010502049817号