基于Disruptor时间滑窗统计redis热度(简约篇)

大家好,我叫大鸡腿,大家可以关注下我,会持续更新技术文章还有人生感悟,感谢~

在这里插入图片描述

redis热点key实现方案

大家可以看下里面的思路:

  1. 就是get的时候如果是热点key走本地缓存,如果不是走redis集群。
  2. 热点key发现就是get的时候进行上报统计,以及利用时间滑窗进行统计
  3. set,expire,del会进行相应的保存起来,如果是热点key就进行监听,进行相应的处理。
  4. 本地缓存跟redis缓存一致性问题,就是如果是主动删除的话最好两边都删除。如果是过期又是热点key就不要让它去访问redis集群了,一旦后台有更新,顺便把本地缓存更新即可。

时间滑窗统计热度

这个我还是蛮感兴趣的,尝试了一下如何去实现这个功能。

首先是时间轮的数据结构吧,让我想起Disruptor ,美团技术篇也有介绍。

代码

import com.lmax.disruptor.*;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadFactory;

public class DisruptorMain {
    public static void main(String[] args) throws Exception {
        // 队列中的元素
        class Element {

            private Map<String, Integer> map = new ConcurrentHashMap<>(100);

            public Map<String, Integer> get() {
                return map;
            }

            public void set(String key) {
                if (map.containsKey(key)) {
                    map.put(key, map.get(key) + 1);
                } else {
                    map.put(key, 1);
                }
            }

        }

        // 生产者的线程工厂
        ThreadFactory threadFactory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "simpleThread");
            }
        };

        // RingBuffer生产工厂,初始化RingBuffer的时候使用
        EventFactory<Element> factory = new EventFactory<Element>() {
            @Override
            public Element newInstance() {
                return new Element();
            }
        };

        // 处理Event的handler
        EventHandler<Element> handler = new EventHandler<Element>() {
            @Override
            public void onEvent(Element element, long sequence, boolean endOfBatch) throws InterruptedException {
                //Thread.sleep(1000);
                System.out.println(LocalDateTime.now() + "----" + "Element: " + element.get());
            }
        };

        // 阻塞策略
        BlockingWaitStrategy strategy = new BlockingWaitStrategy();

        // 指定RingBuffer的大小
        int bufferSize = 16;

        // 创建disruptor,采用单生产者模式
        Disruptor<Element> disruptor = new Disruptor(factory, bufferSize, threadFactory, ProducerType.SINGLE, strategy);

        // 设置EventHandler
        disruptor.handleEventsWith(handler);

        // 启动disruptor的线程
        disruptor.start();

        RingBuffer<Element> ringBuffer = disruptor.getRingBuffer();

        LocalDateTime time = LocalDateTime.now();
        // 获取下一个可用位置的下标
        long sequence = ringBuffer.next();
        for (int i = 0; i < 1000; i++) {
            try {
                LocalDateTime time1 = LocalDateTime.now();
                Duration duration = Duration.between(time, time1);
                if (duration.getSeconds() >= 3) {
                    ringBuffer.publish(sequence);
                    sequence = ringBuffer.next();
                    time = LocalDateTime.now();
                }
                // 返回可用位置的元素
                Element event = ringBuffer.get(sequence);
                // 设置该位置元素的值
                event.set(String.valueOf(new Random().nextInt(20)));
            } finally {
                Thread.sleep(100);
            }
        }

    }

}

这里主要基于有赞TMC设计的3秒为维度进行统计的需求。

在这里插入图片描述

讲解

		LocalDateTime time = LocalDateTime.now(); 1
        // 获取下一个可用位置的下标
        long sequence = ringBuffer.next(); 2
        for (int i = 0; i < 1000; i++) {
            try {
                LocalDateTime time1 = LocalDateTime.now(); 3
                Duration duration = Duration.between(time, time1); 4
                if (duration.getSeconds() >= 3) {
                    ringBuffer.publish(sequence); 5
                    sequence = ringBuffer.next(); 
                    time = LocalDateTime.now(); 
                }
                // 返回可用位置的元素
                Element event = ringBuffer.get(sequence);
                // 设置该位置元素的值
                event.set(String.valueOf(new Random().nextInt(20))); 6
            } finally {
                Thread.sleep(100);
            }
        }

第一步:获取 当前时间点
第二步:获取下一个节点
第三,四步:获取插入是时候的时间点,判断之间的时间相差秒数
第五步:如果相差超过3秒,则需要把之前的publish,然后获取下一个节点,还有时间也换成当前时间点。
第6步:模拟一下redis key查询,set到这个时间滑窗里头

打印结果

2020-08-16T14:16:21.343----Element: {11=2, 12=2, 13=1, 14=4, 15=1, 16=3, 17=2, 18=1, 19=1, 0=2, 1=1, 2=1, 3=1, 5=2, 6=2, 7=1, 8=1, 9=2}
2020-08-16T14:16:24.359----Element: {10=2, 13=1, 15=2, 16=3, 17=3, 18=2, 19=2, 0=3, 2=2, 3=1, 4=1, 5=4, 6=1, 7=2, 8=1}
2020-08-16T14:16:27.375----Element: {10=1, 12=3, 14=2, 16=4, 17=1, 18=1, 19=1, 0=2, 2=2, 3=1, 5=1, 6=2, 7=2, 8=4, 9=3}
2020-08-16T14:16:30.396----Element: {10=2, 12=1, 13=3, 14=1, 15=2, 16=1, 18=1, 19=1, 0=3, 1=2, 2=1, 3=1, 4=1, 5=2, 6=2, 7=3, 8=2, 9=1}
2020-08-16T14:16:33.411----Element: {10=3, 11=1, 13=3, 14=1, 15=3, 16=1, 19=1, 0=1, 1=4, 2=1, 3=4, 4=1, 5=2, 6=2, 7=1, 9=1}
2020-08-16T14:16:36.426----Element: {10=3, 11=1, 12=4, 13=2, 14=1, 15=2, 19=2, 0=1, 1=2, 2=3, 4=2, 5=2, 6=2, 7=1, 8=2}
2020-08-16T14:16:39.440----Element: {10=2, 11=1, 12=4, 13=1, 14=1, 15=3, 16=1, 17=1, 19=1, 0=1, 1=1, 2=1, 4=3, 6=4, 7=1, 8=2, 9=2}

就是每3秒打印下,然后消费者进行统计。

优化点

就是在消费者端,可以使用kafka或者mq等等消费能力高的,进行后续的统计记录。

参考文章

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值