redis 对单个key进行大数据量incr

本文介绍了一种利用Redis和本地缓存实现超高并发场景下的延迟累加方案,通过多应用部署、本地JVM缓存及定时同步至Redis的方式,有效缓解了Redis的压力并避免了频繁访问。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

利用 redis incr 做超高并发延迟累加器(一亿数量级)


前景提要:

公司有一个字段,需要支持 qps 为 1000万。 但是可以将结果作为延迟结果发送过来,比如 20秒发一次结果。但是不能直接 incr 1000万次,redis即使是集群 也扛不住。

提示: redis只有在网络读取请求利用了多线程,在磁盘io等处理数据方面依然是单线程。

思路: 多应用示例部署,利用多应用本地jvm缓存,来进行分流,各应用本地之间近行多线程本地累加。利用时间段来提交多应用的累加结果,此时再利用incr原子特性进行多应用结果汇合,得到总结果。


我要做什么?

  • 多应用部署不在讨论范围
  • 本机缓存,多线程访问保证高并发的数据正确性!
  • 利用时间间隔去请求incr
  • 是否存在内存泄漏问题?
  • 是否存在遗留问题?

代码实现

总体分析有两个临界资源:

  1. redis针对特定key的value进行累加
  2. 本地缓存,针对特定key的value进行累加

如何解决临界资源并发问题?

  1. redis针对key的value进行累加,用incr或者incrBy即可。
  2. 针对本地缓存,可以考虑使用longadder数据类型来保证。

解释为啥用 longAdder?

  • longadder数据类型可以保证并发安全(CAS),由于我们对临界资源的操作远小于线程上下文切换的时间,所以用CAS收益更好。
  • 它比普通的AtmoicLong要更快,比long更快。利用了禁用cpu cache缓存,用空间换时间方式防止伪共享,详情请看longAdder详解

ok,交代完背景以后,我们来整理下代码思路,应该分以下几点:

  • 声明本地临界资源
  • 声明计时器,间隔一段时间把累加结果同步到redis
  • 将长时间未用到的临界资源释放掉,为了避免内存泄漏

开始编码,先给出分步代码,然后再给出整体代码

声明临界资源:
   
    static final Map<String, LongAdder> countMap = new ConcurrentHashMap<>();

声明计时器:

   private static ScheduledExecutorService executor;

   public void init() {
       executor = Executors.newScheduledThreadPool(1);
       // 30秒执行一次 可以将30秒改为动态的
       executor.scheduleAtFixedRate(new LocalSum(), 0,30, TimeUnit.SECONDS);
   }

本地计算


    public static void incr(String key) {
        try {
            LongAdder longAdder = countMap.get( taskId);
            if (longAdder == null) {
                synchronized (this) {
                    longAdder = countMap.get(taskId);
                    if (longAdder == null) {
                        longAdder = new LongAdder();
                        countMap.put(taskId, longAdder);
                    }
                }
            }
            longAdder.increment();
        } catch (Exception e) {
            log.error("incr 异常了?, e);
        }
    }
计算完成后,对redis进行incr
	
	// 利用多线程处理
    public static class LocalSum implements Runnable {

        @Override
        public void run() {
            for (Map.Entry<String, LongAdder> entry : countMap.entrySet()) {
                try {
                    String key = entry.getKey();
                    LongAdder longAdder = entry.getValue();
                    long count = longAdder.sum();
                    jedis.incrBy(key, count);
                    longAdder.add(-count);
                    ttlMap.remove(key);
                } catch (Exception e) {
                    log.error("异常了, e);
                }
            }
        }
    }


这样有一个问题 countMap 会随着项目代码的增加变的无限大,造成内存泄漏

解决方案: 如果n次执行中,该key没有被用到,则被移除!

修改下对redis进行incr代码

	// 重试次数
    private static Integer ttlCount = 60;
	// 保存key的
    private static Map<String, Integer> ttlCountMap = new ConcurrentHashMap<>();

	// 利用多线程处理
    public static class LocalSum implements Runnable {

        @Override
        public void run() {
            for (Map.Entry<String, LongAdder> entry : countMap.entrySet()) {
                try {
                    String key = entry.getKey();
                    LongAdder longAdder = entry.getValue();
                    long count = longAdder.sum();
                    if (count == 0) {
                        Integer tempTTL= ttlMap.get(key);
                        if (tempTTL== null) {
                            tempTTL= ttlCount ;
                        }
                        ttlMap.put(key, --tempTTL);
                        if (currentTTL == 0) {
                            countMap.remove(key);
                            ttlCountMap .remove(key);
                        }
                        continue;
                    }
                    jedis.incrBy(key, count);
                    longAdder.add(-count);
                    ttlMap.remove(key);
                } catch (Exception e) {
                    log.error("异常了, e);
                }
            }
        }
    }

结束!

代价是 一段时间后才提交记录

收获是避免了redis的频繁访问,redis虽然快,但是也是需要走网络和io的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值