当前位置: 代码网 > it编程>数据库>Redis > 一线大厂Redis高并发缓存架构实战与性能优化

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

2024年08月02日 Redis 我要评论
首先zk会有一个leader节点,还会有多个flow节点(类似于redis的master和slave),当我们在leader节点设置一把分布所锁的时候,leader节点不会立即将设置的结果返回客户端,leader会从其flow节点去复制key,当flow复制成功key返回信息给leader节点的时候,可能有人会说我们多搞几个节点,总不会那么多节点都挂掉吧,那我们想想,搞那么多节点,redis写key是不是也得消耗很多性能,我们使用redis的初衷就变了,那还不如用zk。但是这1s的数据有可能会丢失,所以。

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

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

二、从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可用性上更加友好一点
在这里插入图片描述

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

如果每个主节点都拖一个从节点(为了高可用),这样还是会有之前说的问题,redis1同步成功,redis2同步失败,从节点变为主节点;那么redis的从节点中依然没有key,其他线程进来依然可以超过半数去设置分布式锁
在这里插入图片描述

那如果不搞从节点,那就可能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底层原理)

五、高并发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;
    }

}
(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com