• 一次编程小练习:根据流量计数动态选择不同的策略


    持续优化一个程序的过程,也是编程技艺提升和编程的乐趣所在。


    引子

    在实际应用中, 往往会承载两种不同的流量。一种是日常流量,比较平稳且持续;一种是极端流量,比较尖锐且短暂。应对这种情况,往往需要不同的策略。比如日常流量下,走普通的逻辑,而在极端流量下,则需要多级缓存等。

    如何根据不同的流量来选择不同的策略呢?


    基本实现

    先定义一个流量策略接口 FlowStrategy:

    
    public interface FlowStrategy {
    
        /**
         * 计算整数的一半
         */
        int half(int a);
    }
    
    

    然后实现两种策略:

    
    public class PlainStrategy implements FlowStrategy {
        @Override
        public int half(int a) {
            System.out.println("PlainStrategy");
            return a / 2 ;
        }
    }
    
    public class ExtremeStrategy implements FlowStrategy {
    
        @Override
        public int half(int a) {
            System.out.println("ExtremeStrategy");
            return a >> 1;
        }
    }
    
    

    再实现一个策略选择器:

    
    public class FlowStrategySelector {
    
        private AtomicBoolean isExtremeFlow = new AtomicBoolean(false);
    
        private PlainStrategy plainStrategy = new PlainStrategy();
        private ExtremeStrategy extremeStrategy = new ExtremeStrategy();
    
        public FlowStrategy select() {
            return isExtremeFlow.get() ? extremeStrategy : plainStrategy;
        }
    
        public void notifyExtreme() {
            isExtremeFlow.compareAndSet(false, true);
        }
    
        public void normal() {
            isExtremeFlow.compareAndSet(true, false);
        }
    }
    
    

    接着写一个简易版的流量计数器:

    
    public class FlowCount implements Runnable {
    
        private FlowStrategySelector flowStrategySelector;
        private int rate;
        private int count;
        private long lastStartTimestamp = System.currentTimeMillis();
    
        public FlowCount(FlowStrategySelector flowStrategySelector) {
            this.flowStrategySelector = flowStrategySelector;
        }
    
        @Override
        public void run() {
            while (true) {
                if (rate > 40) {
                    flowStrategySelector.notifyExtreme();
                } else {
                    flowStrategySelector.normal();
                }
                try {
                    TimeUnit.MILLISECONDS.sleep(1000);
                } catch (InterruptedException e) {
                    //
                }
            }
        }
    
        public void rate() {
            count++;
            if (System.currentTimeMillis() - lastStartTimestamp >= 1000) {
                rate = count;
                lastStartTimestamp = System.currentTimeMillis();
                count = 0;
            }
        }
    }
    
    

    最后写一个用例:

    
    public class FlowStrategyTester {
    
        static Random random = new Random(System.currentTimeMillis());
    
        public static void main(String[] args) throws InterruptedException {
    
            FlowStrategySelector flowStrategySelector = new FlowStrategySelector();
            FlowCount flowCount = new FlowCount(flowStrategySelector);
            new Thread(flowCount).start();
    
            for (int i=0; i < 2000000; i++) {
                int sleepTime = random.nextInt(50);
                TimeUnit.MILLISECONDS.sleep(sleepTime);
                flowStrategySelector.select().half(Integer.MAX_VALUE);
                flowCount.rate(); // 可以通过消息队列来推送消息和计数
            }
    
        }
    
    }
    
    
    

    加点难度

    假设不止有一个方法需要流量策略接口,比如 A 业务也需要, B 业务也需要,怎么办呢 ? 难道是在 FlowStrategy 里新增另一个完全不同业务的方法 ? 这样会导致 FlowStrategy 的实现子类 PlainStrategy, ExtremeStrategy 不断膨胀。

    显然,合适的方法是,给每个业务创建子接口,如下所示。 把 FlowStrategy 的方法拿出去,成为空接口:

    A业务:

    
    public interface ABizFlowStrategy extends FlowStrategy {
    
        /**
         * 计算整数的一半
         */
        int half(int a);
    }
    
    public class APlainStrategy implements ABizFlowStrategy {
        @Override
        public int half(int a) {
            System.out.println("APlainStrategy");
            return a / 2 ;
        }
    }
    
    public class AExtremeStrategy implements ABizFlowStrategy {
    
        @Override
        public int half(int a) {
            System.out.println("AExtremeStrategy");
            return a >> 1;
        }
    }
    
    
    

    B 业务:

    
    public interface BBizFlowStrategy extends FlowStrategy {
    
        /**
         * 计算整数的两倍
         */
        int multi(int a);
    }
    
    public class BPlainStrategy implements BBizFlowStrategy {
        @Override
        public int multi(int a) {
            System.out.println("BPlainStrategy");
            return a * 2 ;
        }
    }
    
    public class BExtremeStrategy implements BBizFlowStrategy {
    
        @Override
        public int multi(int a) {
            System.out.println("BExtremeStrategy");
            return a << 1;
        }
    }
    
    
    

    那么流量策略选择器就修改为:

    
    public class FlowStrategySelector {
    
        private AtomicBoolean isExtremeFlow = new AtomicBoolean(false);
    
        private APlainStrategy aPlainStrategy = new APlainStrategy();
        private AExtremeStrategy aExtremeStrategy = new AExtremeStrategy();
    
        private BPlainStrategy bPlainStrategy = new BPlainStrategy();
        private BExtremeStrategy bExtremeStrategy = new BExtremeStrategy();
    
        public ABizFlowStrategy selectA() {
            return isExtremeFlow.get() ? aExtremeStrategy : aPlainStrategy;
        }
    
        public BBizFlowStrategy selectB() {
            return isExtremeFlow.get() ? bExtremeStrategy : bPlainStrategy;
        }
    
        public void notifyExtreme() {
            isExtremeFlow.compareAndSet(false, true);
        }
    
        public void normal() {
            isExtremeFlow.compareAndSet(true, false);
        }
    }
    
    
    

    显然,FlowStrategySelector 又有膨胀的趋势。 需要把 FlowStrategySelector 也拆分出来。

    建立 FlowStrategySelector 接口。这里之所以要有 notifyExtreme 和 normal, 是因为 selector 必须与 flowCount 交互。

    
    public interface FlowStrategySelector<FS extends FlowStrategy> {
    
        FS select();
    
        void notifyExtreme();
    
        void normal();
    
    }
    
    

    然后可实现不同业务的 FlowStrategySelector:

    
    public class AFlowStrategySelector implements FlowStrategySelector<ABizFlowStrategy> {
    
        private AtomicBoolean isExtremeFlow = new AtomicBoolean(false);
    
        private APlainStrategy aPlainStrategy = new APlainStrategy();
        private AExtremeStrategy aExtremeStrategy = new AExtremeStrategy();
    
        @Override
        public ABizFlowStrategy select() {
            return isExtremeFlow.get() ? aExtremeStrategy : aPlainStrategy;
        }
    
        @Override
        public void notifyExtreme() {
            isExtremeFlow.compareAndSet(false, true);
        }
    
        @Override
        public void normal() {
            isExtremeFlow.compareAndSet(true, false);
        }
    }
    
    
    

    FlowStrategySelector 的工厂:

    
    public class FlowStrategySelectorFactory {
    
        private AFlowStrategySelector aFlowStrategySelector = new AFlowStrategySelector();
        private BFlowStrategySelector bFlowStrategySelector = new BFlowStrategySelector();
    
        Map<Class, FlowStrategySelector> strategyMap = new HashMap() {
            {
                put(ABizFlowStrategy.class, aFlowStrategySelector);
                put(BBizFlowStrategy.class, bFlowStrategySelector);
            }
        };
    
        public FlowStrategySelector getSelector(Class cls) {
            return strategyMap.get(cls);
        }
    
        public <FS extends FlowStrategy> FS select(Class<FS> fsClass) {
            return (FS)getSelector(fsClass).select();
        }
    
    }
    
    

    流量计数器改动:

    
    public class FlowCount implements Runnable {
    
        private FlowStrategySelector flowStrategySelector;
        private int rate;
        private int count;
        private long lastStartTimestamp = System.currentTimeMillis();
    
        public FlowCount(FlowStrategySelector flowStrategySelector) {
            this.flowStrategySelector = flowStrategySelector;
        }
    
        @Override
        public void run() {
            while (true) {
                if (rate > 40) {
                    flowStrategySelector.notifyExtreme();
                } else {
                    flowStrategySelector.normal();
                }
                try {
                    TimeUnit.MILLISECONDS.sleep(1000);
                } catch (InterruptedException e) {
                    //
                }
            }
        }
    
        public void rate() {
            count++;
            if (System.currentTimeMillis() - lastStartTimestamp >= 1000) {
                rate = count;
                lastStartTimestamp = System.currentTimeMillis();
                count = 0;
            }
        }
    }
    
    
    

    用例:

    
    public class FlowStrategyTester {
    
        static Random random = new Random(System.currentTimeMillis());
    
        public static void main(String[] args) throws InterruptedException {
    
            FlowStrategySelectorFactory flowStrategySelectorFactory = new FlowStrategySelectorFactory();
            FlowStrategySelector aflowStrategySelector = flowStrategySelectorFactory.getSelector(ABizFlowStrategy.class);
            FlowCount aflowCount = new FlowCount(aflowStrategySelector);
            new Thread(aflowCount).start();
    
            for (int i=0; i < 2000; i++) {
                int sleepTime = random.nextInt(50);
                TimeUnit.MILLISECONDS.sleep(sleepTime);
                flowStrategySelectorFactory.select(ABizFlowStrategy.class).half(Integer.MAX_VALUE);
                aflowCount.rate(); // 可以通过消息队列来推送消息和计数
            }
    
            FlowStrategySelector bflowStrategySelector = flowStrategySelectorFactory.getSelector(BBizFlowStrategy.class);
            FlowCount bflowCount = new FlowCount(bflowStrategySelector);
            new Thread(bflowCount).start();
    
            for (int i=0; i < 2000; i++) {
                int sleepTime = random.nextInt(50);
                TimeUnit.MILLISECONDS.sleep(sleepTime);
                flowStrategySelectorFactory.select(BBizFlowStrategy.class).multi(Integer.MAX_VALUE);
                bflowCount.rate(); // 可以通过消息队列来推送消息和计数
            }
    
        }
    
    }
    
    

    改进点

    这里其实还有不少改进点。

    • flowStrategySelectorFactory.getSelector(BBizFlowStrategy.class) 这个调用就有点奇怪,按道理应该是 flowStrategySelectorFactory.getSelector(AFlowStrategySelector.class) 更自然一点。 但如果拿到 selector 再调用 select 方法,就始终得到的是 FlowStrategy 接口,无法调用后面的 half 或 multi 方法。这是因为引用的是基类,而不是子类。必须用 <FS extends FlowStrategy> FS select(Class<FS> fsClass) 通过参数的方式指明获取的是哪个子类实例。

    • FlowStrategySelectorFactory 里 bizFlowStrategy 与 FlowStrategySelector 的映射关系,可以做成自动化的,而不必手动配置。在 Spring 应用里,只要将这些标识为 Component 组件,然后在应用启动的时候根据 getBeansOfType 获取到实例,然后构建映射关系即可。

    • FlowStrategySelector 的实现基本相似,且会膨胀,能否做得更简单一些呢?

    • 各个类的职责是否划分明晰?

    • 如果不同的业务要采用不同的流量计数怎么办呢?


    FlowStrategySelector 通用化

    这里,我们发现 FlowStrategySelector 的实现基本相似。可以做一个通用的实现。FlowStrategySelector 实际上跟业务本身没关系,只跟流量策略有关系。因此,可以定义普通流量策略接口 PlainFlowStrategy 和 极限流量策略接口 ExtremeFlowStrategy, 然后让实现 implements 这些接口:

    
    /**
     * 普通流量策略
     */
    public interface PlainFlowStrategy extends FlowStrategy {
    }
    
    /**
     * @Description 极限流量策略
     */
    public interface ExtremeFlowStrategy extends FlowStrategy {
    }
    
    public class APlainStrategy implements ABizFlowStrategy, PlainFlowStrategy { }
    
    public class AExtremeStrategy implements ABizFlowStrategy, ExtremeFlowStrategy { }
    
    public class BPlainStrategy implements BBizFlowStrategy, PlainFlowStrategy { }
    
    public class BExtremeStrategy implements BBizFlowStrategy, ExtremeFlowStrategy { }
    
    

    然后可以实现一个通用的 FlowStrategySelector (CommonFlowStrategySelector 的构造器可以通过 Spring bean 管理实现更加自动化,而不是手动 new):

    
    public class CommonFlowStrategySelector implements FlowStrategySelector {
    
        private AtomicBoolean isExtremeFlow = new AtomicBoolean(false);
    
        private PlainFlowStrategy plainFlowStrategy;
        private ExtremeFlowStrategy extremeFlowStrategy;
    
        public CommonFlowStrategySelector(Class cls) {
            if (cls.getName().equals(ABizFlowStrategy.class.getName())) {
                if (plainFlowStrategy == null) {
                    plainFlowStrategy = new APlainStrategy();
                }
                if (extremeFlowStrategy == null) {
                    extremeFlowStrategy = new AExtremeStrategy();
                }
            }
            else if (cls.getName().equals(BBizFlowStrategy.class.getName())) {
                if (plainFlowStrategy == null) {
                    plainFlowStrategy = new BPlainStrategy();
                }
                if (extremeFlowStrategy == null) {
                    extremeFlowStrategy = new BExtremeStrategy();
                }
            }
        }
    
        @Override
        public FlowStrategy select() {
            return isExtremeFlow.get() ? extremeFlowStrategy : plainFlowStrategy;
        }
    
        @Override
        public void notifyExtreme() {
            isExtremeFlow.compareAndSet(false, true);
        }
    
        @Override
        public void normal() {
            isExtremeFlow.compareAndSet(true, false);
        }
    }
    
    
    

    FlowStrategySelectorFactory 就更简单了:

    
    public class FlowStrategySelectorFactory {
    
        public FlowStrategySelector getSelector(Class cls) {
            return new CommonFlowStrategySelector(cls);
        }
    
        public <FS extends FlowStrategy> FS select(FlowStrategySelector selector, Class<FS> fsClass) {
            return (FS)selector.select();
        }
    
    }
    
    

    流量计数与策略选择

    如果我要根据不同的业务选择不同流量技术策略,怎么办呢 ? 这里就不能直接使用 FlowCount 实现类了。而是要做点改造。

    定义一个流量计数策略接口:

    
    public interface FlowCountStrategy extends Runnable {
    
        /**
         * 流量速率计算
         */
        void rate();
    
        /**
         * 是否极端流量
         */
        boolean isExtreme();
    }
    
    

    流量计数策略实现(注意这里 FlowCount 在并发情形下是有点问题的,聪明的你能看出来么?):

    
    public class FlowCount implements FlowCountStrategy, Runnable {
    
        private FlowStrategySelector flowStrategySelector;
        private AtomicInteger rate = new AtomicInteger(0);
        private int count;
        private long lastStartTimestamp = System.currentTimeMillis();
    
        public FlowCount(FlowStrategySelector flowStrategySelector) {
            this.flowStrategySelector = flowStrategySelector;
        }
    
        @Override
        public void run() {
            while (true) {
                if (isExtreme()) {
                    flowStrategySelector.notifyExtreme();
                } else {
                    flowStrategySelector.normal();
                }
                try {
                    TimeUnit.MILLISECONDS.sleep(1000);
                } catch (InterruptedException e) {
                    //
                }
            }
        }
    
        @Override
        public void rate() {
            count++;
            if (System.currentTimeMillis() - lastStartTimestamp >= 1000) {
                rate.getAndSet(count);
                lastStartTimestamp = System.currentTimeMillis();
                count = 0;
            }
        }
    
        @Override
        public boolean isExtreme() {
            return rate.get() > 40;
        }
    }
    
    

    注意到,这里把 rate > 40 抽离成一个方法 isExtreme(), 是为了更好滴把极限流量和普通流量的判断抽离出来。

    再写一个简易的流量计数策略选择器:

    
    public class FlowCountStrategySelector {
    
        private FlowCount flowCount;
    
        public FlowCountStrategy select(FlowStrategySelector flowStrategySelector) {
            if (flowCount == null) {
                flowCount = new FlowCount(flowStrategySelector);
            }
            return flowCount;
        }
    }
    
    

    那么,用例就可以写成:

    
    FlowStrategySelectorFactory flowStrategySelectorFactory = new FlowStrategySelectorFactory();
    FlowStrategySelector aflowStrategySelector = flowStrategySelectorFactory.getSelector(ABizFlowStrategy.class);
    FlowCountStrategySelector flowCountStrategySelector = new FlowCountStrategySelector();
    FlowCountStrategy aflowCount = flowCountStrategySelector.select(aflowStrategySelector);
    new Thread(aflowCount).start();
    
    for (int i=0; i < 2000; i++) {
        int sleepTime = random.nextInt(50);
        TimeUnit.MILLISECONDS.sleep(sleepTime);
        flowStrategySelectorFactory.select(aflowStrategySelector, ABizFlowStrategy.class).half(Integer.MAX_VALUE);
        aflowCount.rate(); // 可以通过消息队列来推送消息和计数
    }        
    
    

    客户端就跟具体的 FlowCount 实现无关了。


    参数优化

    注意到,这里所有的参数都是传 XFlowStrategy.class ,这个参数感觉比较别扭。 实际上需要突出一个业务名的概念,通过业务名把这些都串起来。

    定义业务枚举及业务名接口和实现:

    
    public enum BizName {
        A,
        B;
    }
    
    public interface FlowStrategy {
    
        String bizName();
    }
    
    public interface ABizFlowStrategy extends FlowStrategy {
    
        /**
         * 计算整数的一半
         */
        int half(int a);
    
        @Override
        default String bizName() {
            return BizName.A.name();
        }
    }
    
    public interface BBizFlowStrategy extends FlowStrategy {
    
        /**
         * 计算整数的两倍
         */
        int multi(int a);
    
        @Override
        default String bizName() {
            return BizName.B.name();
        }
    }
    
    

    flowStrategySelectorFactory.getSelector(ABizFlowStrategy.class) 参数可以用 BizName 代替:

    
    public class FlowStrategySelectorFactory {
    
        public FlowStrategySelector getSelector(BizName biz) {
            return new CommonFlowStrategySelector(biz);
        }
    
    }
    
    

    CommonFlowStrategySelector 的构造器就可以用 BizName 参数来代替。

    
    public interface FlowStrategySelector {
    
        <FS extends FlowStrategy> FS select(Class<FS> cls);
    
        void notifyExtreme();
    
        void normal();
    
    }
    
    public class CommonFlowStrategySelector implements FlowStrategySelector {
    
        private AtomicBoolean isExtremeFlow = new AtomicBoolean(false);
    
        private BizName biz;
        private PlainFlowStrategy plainFlowStrategy;
        private ExtremeFlowStrategy extremeFlowStrategy;
    
        public CommonFlowStrategySelector(BizName biz) {
            this.biz = biz;
            if (biz == BizName.A) {
                if (plainFlowStrategy == null) {
                    plainFlowStrategy = new APlainStrategy();
                }
                if (extremeFlowStrategy == null) {
                    extremeFlowStrategy = new AExtremeStrategy();
                }
            }
            else if (biz == BizName.B) {
                if (plainFlowStrategy == null) {
                    plainFlowStrategy = new BPlainStrategy();
                }
                if (extremeFlowStrategy == null) {
                    extremeFlowStrategy = new BExtremeStrategy();
                }
            }
        }
    
        public FlowStrategy selectInner() {
            return isExtremeFlow.get() ? extremeFlowStrategy : plainFlowStrategy;
        }
    
        @Override
        public <FS extends FlowStrategy> FS select(Class<FS> cls) {
            return (FS)selectInner();
        }
    
        @Override
        public void notifyExtreme() {
            isExtremeFlow.compareAndSet(false, true);
        }
    
        @Override
        public void normal() {
            isExtremeFlow.compareAndSet(true, false);
        }
    }
    
    

    用例可以写成:

    
    FlowStrategySelectorFactory flowStrategySelectorFactory = new FlowStrategySelectorFactory();
    FlowStrategySelector aflowStrategySelector = flowStrategySelectorFactory.getSelector(BizName.A);
    FlowCountStrategySelector flowCountStrategySelector = new FlowCountStrategySelector();
    FlowCountStrategy aflowCount = flowCountStrategySelector.select(aflowStrategySelector);
    new Thread(aflowCount).start();
    
    for (int i=0; i < 1000; i++) {
        int sleepTime = random.nextInt(50);
        TimeUnit.MILLISECONDS.sleep(sleepTime);
        aflowStrategySelector.select(ABizFlowStrategy.class).half(Integer.MAX_VALUE);
        aflowCount.rate(); // 可以通过消息队列来推送消息和计数
    }
    
    

    这里还有一点不足的是: aflowStrategySelector.select(ABizFlowStrategy.class).half(Integer.MAX_VALUE); 还是得用 ABizFlowStrategy.class 作为参数,不然就没法调用 half 接口,因为 select 返回的是 PlainFlowStrategy 或 ExtremeFlowStrategy 并不含业务信息,无法调用业务接口的方法。 目前暂时没有找到如何能够自动转成 ABizFlowStrategy 或 BBizFlowStrategy 的方法。也想过把 half 或 multi 放到 CommonFlowStrategySelector 的方法执行,但这样也会涉及强制类型转换。


    职责与命名

    再推敲下, ABizFlowStrategy, BBizFlowStrategy 的命名是不够准确的。这里只是两种业务的表示,与流量策略并没有关系,是一种平行的关系,而不是包含的关系。因此,这两个类应该命名为 BizStrategy.

    
    /**
    * @Description 业务方法定义
    */
    public interface BizStrategy {
    
       String bizName();
    }
    
    public interface ABizStrategy extends BizStrategy {
    
       /**
        * 计算整数的一半
        */
       int half(int a);
    
       @Override
       default String bizName() {
           return BizName.A.name();
       }
    }
    
    public interface BBizStrategy extends BizStrategy {
    
       /**
        * 计算整数的两倍
        */
       int multi(int a);
    
       @Override
       default String bizName() {
           return BizName.B.name();
       }
    }
    
    public class APlainStrategy implements ABizStrategy, PlainFlowStrategy { // code as before }
    public class AExtremeStrategy implements ABizStrategy, ExtremeFlowStrategy { // code as before }
    public class BPlainStrategy implements BBizStrategy, PlainFlowStrategy { // code as before }
    public class BExtremeStrategy implements BBizStrategy, ExtremeFlowStrategy { // code as before }
    
    

    CommonFlowStrategySelector 做了点微调:

    
    public interface FlowStrategySelector {
    
        // code changed
        <FS extends BizStrategy> FS select(Class<FS> cls);
    
    }
    
    public class CommonFlowStrategySelector implements FlowStrategySelector {
    
        // code changed
        @Override
        public <FS extends BizStrategy> FS select(Class<FS> cls) {
            return (FS)selectInner();
        }
    
    }
    
    

    好的程序

    好的程序是怎样的?

    • 职责与命名清晰,能够准确表达其意图;
    • 方法和交互定义清晰;
    • 可复用和可扩展性良好,考虑到应有的变化;
    • 方法签名的参数比较自然,不别扭;
    • 保证并发情形的准确性。

    要反复斟酌推敲,写出好的程序,才能对编程技艺和设计思维有真正的提升作用。天天重复堆砌相似水平的业务代码是很难有所提升的。


    小结

    根据流量计数来选择不同的策略处理,是建立了一个反馈回路:当接收到请求时,会做一个请求计数处理,这个请求计数处理又会反馈到请求处理的前置环节,选择请求处理的策略。系统思考值得再好好学学。

    此外,流量计数和预测是一个值得去优化和探讨的话题。据说,现在有用 AI 算法来做流量预测的。

    持续优化一个程序的过程,也是编程技艺提升和编程的乐趣所在。



  • 相关阅读:
    vue组件系列-数字滚动组件
    重新振兴自己
    EL表达式
    org.hibernate.PropertyAccessException: Null value was assigned to a property of primitive type sette
    Struts2常用标签总结
    mybatis+strut2+spring整合总结
    hibernate的详细注解以及例子
    Struts2基于注解的Action配置
    【干货】如何通过OPC自定义接口来实现客户端数据的读取?
    即将离职,共享下我的知识库
  • 原文地址:https://www.cnblogs.com/lovesqcc/p/16344850.html
Copyright © 2020-2023  润新知