找回密码
 立即注册
首页 业界区 业界 糊涂啊!这个需求居然没想到用时间轮来解决。 ...

糊涂啊!这个需求居然没想到用时间轮来解决。

刘凤 6 小时前
你好呀,我是歪歪。
上周不是发布了这篇文章嘛:《也是出息了,业务代码里面也用上算法了。》
里面聊到一个场景,A、B、C 三个平台需要调用下游系统的接口查询数据。
当时下游对该查询接口做了限流,只支持一秒最多一个请求。
其中 A 平台要求每个请求间隔 6s 或者以上。
B,C 平台可以接受一秒一次请求。
如何实现时间的最大化利用?
也就是把 A 平台中的间隔时间利用起来:
1.png
把中间用其他平台的数据塞满:
2.png
我当时灵机一动,想到一个骚操作:加权轮询负载均衡算法。
最终也算是实现了这个需求。
你知道的,在业务代码里面能用到算法还是一件很稀奇的事情,所以我写了上面那篇文章。
文章发布之后,经过读者提醒,我又打开了新思路。
找到了比我这个加权轮询负载均衡算法实现更加优雅的实现方案。
3.jpeg
时间轮

4.png
其实当这个读者提到“一个平台一条时间轮”这句话的时候,我的思路一下就打开了。
5.png
时间轮,这个玩意我也会啊。
但是时间轮这个玩意,我们先按下不表,先分析一下这个读者的思路。
他在描述的时候,对时间轮加了引号,等我理解他的意思之后才知道,这个引号加的很巧妙。
因为他用的并不是真正的时间轮,而是借鉴了时间轮的“时间间隔”思路。
为了我画图方便,我先做一个预设:

  • A 平台的时间间隔为 5s
  • B 平台的时间间隔为 3s
  • C 平台的时间间隔为 1s
然后我们需要一个初始化的时间,假设为 10 点整,这个 10 点整你可以理解为程序跑起来的时间点。
那么整个核心思路大概是这样的。
首先,在程序刚刚启动的时候,A、B、C 3 个平台都是满足条件的。
所以理论上任何一个平台都可以。
对平台进行循环,最先循环到的是 A:
6.png
然后,关键的地方就来了。
平台被选中后,按照专属的时间间隔更新下次执行时间:
7.png
A 平台下次执行时间就变成了 10:00:05。
按照这个逻辑往下推。
前三秒就是这样的:
8.png
继续往下,到第六秒的时候,是这样的:
9.png
到这里,我必须把 10:00:05 这个时间切片单独拿出来给你看看,这个点非常关键:
10.png
你说,这个时候应该是 A 执行还是 C 执行呢,毕竟它们都满足“当前时间大于等于下次执行时间”这个判断条件。
按理来说,代码循环的时候,最新取到的肯定是 A,所以 A 先执行,没毛病。
但是实际上大概率是 C 执行。
为啥呢?
回到 A 的下次执行时间被更新时的这个时间点:
11.png
你仔细分析图中的这句话:
更新平台 A 的下次执行时间:System.currentTimeMillis()+5s
那平台 A 的下次执行时间会正正好好是 10:00:05 吗?
有 System.currentTimeMillis() 的存在,是不是 10:00:05:123 这样带着点毫秒数的时间更加合理点?
所以,当我把毫秒数带着,这样你再看,是不是大概率是 C 执行了:
12.png
那么问题又来了:为什么是大概率 C 执行呢?
在什么情况下可能会把 A 选出来执行呢?
在 GC 抖动的情况下,当前时间可能也会往前偏移一点,可能会把 A 选出来执行。
但是,A、C 谁先谁后,根本不重要,因为在当前的情况下,A、C 谁都可以执行。
整个思路就是这样的。
思路清晰了,代码不就是“呼大模型”而出了嘛:
[code]public class PlatformLaneScheduler {
    // 平台配置类
    static class Platform {
        final String name;
        final long interval; // 执行间隔(毫秒)
        long nextAllowedTime; // 下次允许执行时间
        
        public Platform(String name, long intervalSeconds) {
            this.name = name;
            this.interval = intervalSeconds * 1000;
            this.nextAllowedTime = System.currentTimeMillis(); // 初始可立即执行
        }
    }
    
    // 全局状态
    private final Map platforms = new HashMap();
    private long lastExecutionTime = 0; // 上次执行时间
    private final ScheduledExecutorService scheduler;
    
    public PlatformLaneScheduler() {
        // 初始化调度器(单线程)
        scheduler = Executors.newSingleThreadScheduledExecutor();
        
        // 添加平台配置
        addPlatform("A", 5);
        addPlatform("B", 3);
        addPlatform("C", 1);
    }
    
    public void addPlatform(String name, long intervalSeconds) {
        platforms.put(name, new Platform(name, intervalSeconds));
    }
    
    public void start() {
        // 每100ms巡检一次
        scheduler.scheduleAtFixedRate(this::checkPlatforms, 0, 100, TimeUnit.MILLISECONDS);
    }
    
    public void stop() {
        scheduler.shutdown();
    }
    
    private void checkPlatforms() {
        long now = System.currentTimeMillis();
        // 检查全局限流:1秒内只能执行一次
        if (now - lastExecutionTime 
您需要登录后才可以回帖 登录 | 立即注册