同步分段锁在实际开发中的应用之:缓存击穿

4 篇文章 0 订阅
4 篇文章 0 订阅

楔子

        在“解决缓存与数据库同步下的同步锁问题之分段锁-CSDN博客”一文中我简略的介绍了为何使用分段锁的原因,同时简短的阐述了缓存的实现逻辑;但是,在实际开发中如何将分段锁应用在缓存上呢?

       缓存的一致性可以通过延迟双删的方案来减少不一致的几率,但是,如果缓存已经过期,需要从数据中读取最新的数据时,如何解决多个请求同时读取缓存引起的击穿?(【数据库技术】缓存穿透,缓存雪崩,缓存击穿的超详解 - 知乎)在多个请求首先得保证从数据库加载数据时是由一个线程完成,后续进入的线程则是等待最新进入的那个线程执行完毕后再继续执行;那么这里就涉及到了一个同步锁的问题,但又因为同步锁本身细粒度问题只能应用于代码块或方法之上,无法针对不同的资源ID进行加锁,这样一来就导致无论访问的是不是同一资源的ID的都会进入同步锁的情况;这样一来该方法的性能必定会大大下降,因为同步锁的原因所有线程都会进入阻塞状态,那么,如何保证多线程数据安全的情况又能保持一定的性能呢?

        这里我们可以使用分段同步锁的方式,针对不同的资源ID来进行加锁,而不是一股脑的针对这个方法或者代码块进行加锁,将加锁的范围下沉到资源ID,这样一来就可以解决加锁后带来的性能开销问题

说了这么多,来看看我在实际业务开发中如何使用分段段解决缓存击穿的问题的吧!

业务介绍

        延续上篇业务功能需求,上篇是计算项目的费用之后需要将费用持久化到数据库中,后续的同时会利用该费用数据来拼接各种各样的字符提示,最后将拼接完的字符存储Redis缓存中,后续的通过请求只需要查询缓存即可,免去之前拼接大量字符的性能开销;那么该流程就应该如下图所示

        这里使用读写锁和双检机制来确定性能的并发,读写锁的目的,读读允许、读写互斥。保证多个请求情况下的数据安全;双检机制是用来确保后续阻塞的请求不会重复执行查询数据库的操作避免缓存击穿。

Codes

大致代码逻辑如下

             String cacheKey = CACHE_KEY_SITE_COST + ":" + projectId;
            Supplier<String> cacheSupplier = () -> {
                String cacheSiteCost = jedisUtil.get(cacheKey, String.class);
                return cacheSiteCost;
            };

            // 先使用共享锁查询Redis是否存在缓存数据,如果,存在则直接返回
            try {
                // 获取并发读锁,在多线程读临界资源时,读锁等于共享锁不互斥,读读并行,读写互斥
                // 使用分段读写锁针对项目ID加锁
                readWriteLock.readLock(projectId);
                String cacheChars = cacheSupplier.get();
                if (StringUtils.isNotBlank(cacheChars)) {
                    return cacheChars;
                }
            } finally {
                // 避免死锁
                readWriteLock.releaseReadLock(projectId);
            }

            // Redis中不存在缓存数据,尝试获取独占锁,从数据库中获取数据并放入Redis中;独占锁的细粒度为项目ID,降低多线程冲突,提高并发性能
            try {
                // 获取并发写锁,在多线程写临界资源时,写锁等于独占锁互斥,读写互斥、写读互斥
                readWriteLock.writeLock(projectId);
                // 双检锁
                String cacheChars = cacheSupplier.get();
                if (null != cacheChars) {
                    return cacheChars;
                }
                // TODO 查询数据操作
                // 将获取到缓存返回
                String result = supplier.get();
                return result;
            } finally {
                // 避免死锁
                readWriteLock.releaseWriteLock(projectId);
            }
        }
        return null;

  • 18
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值