一线大厂Redis高并发缓存架构实战与性能优化

这篇具有很好参考价值的文章主要介绍了一线大厂Redis高并发缓存架构实战与性能优化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、redis主从架构锁失效问题分析

我们都知道,一般的互联网公司redis部署都是主从结构的,那么复制基本都是异步执行的,那就存在一个问题,当我们设置分布式锁的时候,还没来得及将key复制到从节点,主节点挂了,那么从节点会成为主节点,但是主节点的分布式锁key就会丢失掉,如果新线程进来执行同步代码同样会导致超卖问题
一线大厂Redis高并发缓存架构实战与性能优化,Redis专题,缓存,redis,架构
那么这个问题想解决,其实并没有那么容易

二、从CAP角度剖析redis与zookeeper分布式锁区别

我们知道zk也能实现分布式锁,他是怎么实现的呢?
首先zk会有一个leader节点,还会有多个flow节点(类似于redis的master和slave),当我们在leader节点设置一把分布所锁的时候,leader节点不会立即将设置的结果返回客户端,leader会从其flow节点去复制key,当flow复制成功key返回信息给leader节点的时候,leadfer会统计一个同步的数量,当这个数量超过半数的时候,才会返回给客户端表示这把分布式锁设置成功了。
那么zk就不会存在因为主从节点切换导致的分布式锁生效的问题

从CAP角度看,redis更多满足的是AP(可用性和容错性),zk是CP的(一致性和容错性)

但是redis的性能会比zk好,zk从语义角度更适合作为分布式锁的工具

三、redlock分布式锁原理与存在的问题分析

我相信很多同学都听过网上的很多人说利用红锁去解决redis的主从结构带来的分布式锁失效的问题,其实并没有完全解决!

红锁的实现原理是什么呢?
红锁是基于不是主从节点的redis实现,假设又奇数个redis节点,都是平等的,不存在主从,其实也是跟zk的底层实现机制是一样的,也是基于半数的加锁的原理。
红锁牺牲了一些可用性,因为需要往不同的节点去写key,需要半数以上的节点返回,那么客户端是需要等待一下的。但是在C可用性上更加友好一点
一线大厂Redis高并发缓存架构实战与性能优化,Redis专题,缓存,redis,架构

但是红锁并没有真正解决分布式锁失效问题

如果每个主节点都拖一个从节点(为了高可用),这样还是会有之前说的问题,redis1同步成功,redis2同步失败,从节点变为主节点;那么redis的从节点中依然没有key,其他线程进来依然可以超过半数去设置分布式锁
一线大厂Redis高并发缓存架构实战与性能优化,Redis专题,缓存,redis,架构

那如果不搞从节点,那就可能reids挂了超过一半的节点,那么分布式锁就没法使用了
可能有人会说我们多搞几个节点,总不会那么多节点都挂掉吧,那我们想想,搞那么多节点,redis写key是不是也得消耗很多性能,我们使用redis的初衷就变了,那还不如用zk

然后会存在一个问题,redis持久化(AOF)的时候,我们一般都会设置为1s去持久化,而不是每条写 命令都去持久化。但是这1s的数据有可能会丢失,所以如果加锁redis1,redis2都成功了的时候,刚好在持久化的这1s中,redis2宕机了,那么redis2 的key就会丢失,依然存在问题

所以说红锁并不能100%解决分布式锁问题

四、大促场景如何将分布式锁性能提升100倍

首选考虑锁的粒度,控制锁住的代码块越小越好。
然后可以设置分段锁,比如某个商品1000个,分布式锁会基于这1000的库存去实现;那么利用分段锁,可以将商品分为100一段的十段,利用10个锁去针对这一个商品实现分布式锁,这10把锁相互之间不会存在并发问题。但是每把锁都是基于100的库存,性能会显著提升。(类似于1.7版本的concruuenthashmap底层原理)文章来源地址https://www.toymoban.com/news/detail-716769.html

五、高并发redis架构代码实战

public class ProductService {

    @Autowired
    private ProductDao productDao;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private Redisson redisson;

    public static final Integer PRODUCT_CACHE_TIMEOUT = 60 * 60 * 24;
    public static final String EMPTY_CACHE = "{}";
    public static final String LOCK_PRODUCT_HOT_CACHE_PREFIX = "lock:product:hot_cache:";
    public static final String LOCK_PRODUCT_UPDATE_PREFIX = "lock:product:update:";
    public static Map<String, Product> productMap = new ConcurrentHashMap<>();

    @Transactional
    public Product create(Product product) {
        Product productResult = productDao.create(product);
        redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), JSON.toJSONString(productResult),
                genProductCacheTimeout(), TimeUnit.SECONDS);//写入数据库之后,redis写缓存,并设置超时时间
        // (超时时间设置为1天+随机5h以内的时间,目的是为了了防止那些批量上架的商品同时过期,避免缓存失效(击穿)导致同时有大量请求打到数据库)
        return productResult;
    }

    @Transactional
    public Product update(Product product) {
        Product productResult = null;
        //RLock updateProductLock = redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX + product.getId());
        RReadWriteLock readWriteLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + product.getId());//针对更新方法设置分布式锁(分布式写锁)
        RLock writeLock = readWriteLock.writeLock();
        writeLock.lock();//保证了在更新数据库和更新缓存之间不会有其他线程过来更新操作,保证双写一致
        try {
            productResult = productDao.update(product);
            redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), JSON.toJSONString(productResult),
                    genProductCacheTimeout(), TimeUnit.SECONDS);
            productMap.put(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), product);//往jvm本地缓或者ehcache存放一份数据(为了应对百万并发场景,redis最多支持10w并发
            //如果redis挂了,会导致雪崩 )
        } finally {
            writeLock.unlock();
        }
        return productResult;
    }

    public Product get(Long productId) throws InterruptedException {
        Product product = null;
        String productCacheKey = RedisKeyPrefixConst.PRODUCT_CACHE + productId;

        product = getProductFromCache(productCacheKey);//先从缓存拿数据
        if (product != null) {
            return product;//拿到了就直接返回,需要跟前端沟通,如果是空的商品就 友好提示
        }
        //DCL 针对冷门数据突然变热的场景
        RLock hotCacheLock = redisson.getLock(LOCK_PRODUCT_HOT_CACHE_PREFIX + productId);//为了针对热点商品设置的分布式锁锁
        //因为大量请求过来,第一次缓存肯定没数据,都会去请求DB,那就不合理;加锁只让一个线程去访问数据库,将数据写入缓存,其他线程在锁释放之后会直接去访问缓存
        hotCacheLock.lock();
        //boolean result = hotCacheLock.tryLock(3, TimeUnit.SECONDS);
        try {
            product = getProductFromCache(productCacheKey);//其余线程进来从缓存拿到数据
            if (product != null) {
                return product;
            }

            //RLock updateProductLock = redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX + productId);
            RReadWriteLock readWriteLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + productId);//读写锁是为了 如果都是读请求的话能保证并行执行,只有写操作才会阻塞
            RLock rLock = readWriteLock.readLock();//同样是为了查询数据库和更新缓存保证不被其他线程影响
            rLock.lock();//读锁的原理是 利用的锁重入的方法,每次都+1
            try {
                product = productDao.get(productId);
                if (product != null) {
                    redisUtil.set(productCacheKey, JSON.toJSONString(product),
                            genProductCacheTimeout(), TimeUnit.SECONDS);
                    productMap.put(productCacheKey, product);
                } else {
                    redisUtil.set(productCacheKey, EMPTY_CACHE, genEmptyCacheTimeout(), TimeUnit.SECONDS);//设置空缓存,防止黑客
                }
            } finally {
                rLock.unlock();
            }
        } finally {
            hotCacheLock.unlock();
        }
        return product;
    }


    private Integer genProductCacheTimeout() {
        return PRODUCT_CACHE_TIMEOUT + new Random().nextInt(5) * 60 * 60;
    }

    private Integer genEmptyCacheTimeout() {
        return 60 + new Random().nextInt(30);
    }

    private Product getProductFromCache(String productCacheKey) {
        Product product = productMap.get(productCacheKey);//从缓存拿数据之前 先从jvm内存呢拿数据,针对百万并发场景
        if (product != null) {
            return product;
        }

        String productStr = redisUtil.get(productCacheKey);
        if (!StringUtils.isEmpty(productStr)) {
            if (EMPTY_CACHE.equals(productStr)) {//如果拿到的是空的数据,说明是为了防止恶意请求导致缓存穿透而设置的
                redisUtil.expire(productCacheKey, genEmptyCacheTimeout(), TimeUnit.SECONDS);//那就刷新过期时间
                return new Product();//返回空的商品信息
            }
            product = JSON.parseObject(productStr, Product.class);
            redisUtil.expire(productCacheKey, genProductCacheTimeout(), TimeUnit.SECONDS); //读延期,热门的数据会一直在缓存中,冷门的数据到时间就过期了,实现了简单了数据冷热分离
        }
        return product;
    }

}

到了这里,关于一线大厂Redis高并发缓存架构实战与性能优化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Redis缓存设计与性能优化

    缓存穿透是指查询一个根本不存在的数据, 缓存层和存储层都不会命中 , 通常出于容错的考虑, 如果从存储层查不到数据则不写入缓存层。 缓存穿透将导致不存在的数据每次请求都要到存储层去查询, 失去了缓存保护后端存储的意义。 造成缓存穿透的基本原因有两个:

    2024年02月07日
    浏览(35)
  • Java架构师缓存性能优化

    想学习架构师构建流程请跳转:Java架构师系统架构设计

    2024年02月07日
    浏览(30)
  • 6. Redis缓存设计与性能优化

    本文是按照自己的理解进行笔记总结,如有不正确的地方,还望大佬多多指点纠正,勿喷。 课程内容: 1、多级缓存架构详解 2、缓存穿透缓存击穿缓存雪崩详解 3、热点缓存key重建优化 4、缓存与数据库双写不一致终极解决 5、Redis开发规范与性能优化 ngnix到Lua到web层,到re

    2024年02月11日
    浏览(37)
  • 49.Redis缓存设计与性能优化

    缓存与数据库双写不一致 小概率事件 //线程1 写数据库stock = 5 ---------------》更新缓存 //线程2 写数据库stock = 4 -----》更新缓存 //线程1 ------》写数据库stock = 10 -----》删除缓存 //线程2 -----------------------------------------------------------------------------------------------》写数据库stock = 9 -

    2024年02月08日
    浏览(30)
  • 【案例实战】高并发业务的多级缓存架构一致性解决方案

    我们在高并发的项目中基本上都离不开缓存,那么既然引入缓存,那就会有一个缓存与数据库数据一致性的问题。 首先,我们先来看看高并发项目里面Redis常见的三种缓存读写模式。 Cache Aside 优点 读取效率高,缓存命中率高,写操作与数据库同步,数据一致性较高,实现较

    2024年02月13日
    浏览(32)
  • Redis 7.0性能大揭秘:如何优化缓存命中率?

    Redis 7.0,这货不仅仅是一个简单的缓存工具,它更是一款高性能的数据结构服务器。现在,大家都知道缓存命中率对性能影响特别大,但怎么优化它呢? 本文,已收录于,我的技术网站 ddkk.com,有大厂完整面经,工作技术,架构师成长之路,等经验分享 Redis的数据结构和键的

    2024年02月03日
    浏览(36)
  • 【Redis】电商项目秒杀问题之下单接口优化:Redis缓存、MQ以及lua脚本优化高并发背景下的秒杀下单问题

    目录 一、优化思路 二、缓存库存与订单 1、库存缓存的redis数据结构 2、订单信息缓存的redis数据结构 三、整体流程 四、lua脚本确保权限校验操作的原子性 【Redis】电商项目秒杀问题之超卖问题与一人一单问题_1373i的博客-CSDN博客 https://blog.csdn.net/qq_61903414/article/details/1305689

    2024年02月05日
    浏览(31)
  • Redis 性能管理/优化 双一致性问题 缓存雪崩/击穿/穿透

    used_memory_rss:是Redis向操作系统申请的内存。 used_memory:是Redis中的数据占用的内存。 mem_fragmentation_ratio:内存碎片率。 used_memory_peak:redis内存使用的峰值。 内存碎片如何产生的? Redis内部有自己的内存管理器,为了提高内存使用的效率,来对内存的申请和释放进行管理。

    2024年02月11日
    浏览(35)
  • 从程序员到架构师:大数据量、缓存、高并发、微服务、多团队协同等核心场景实战书籍

    相信大家都对未来的职业发展有着憧憬和规划,要做架构师、要做技术总监、要做CTO。对于如何实现自己的职业规划也都信心满满,努力工作、好好学习、不断提升自己。 相信成为一名优秀的架构师是很多程序员的目标,架构师的工作包罗万象,从开发基础框架到设计软件架

    2024年02月05日
    浏览(36)
  • 一线互联网架构师360°全方面性能调优,android适配器ui

    为什么要使用多进程 对于进程的概念,来到这里的都是编程修仙之人,就不再啰嗦了,相信大家倒着、跳着、躺着、各种姿势都能背出来。 相信很多同学在实际开发中,基本都不会去给app划分进程,而且,在Android中使用多进程,还可能需要编写额外的进程通讯代码,还可能

    2024年04月13日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包