架构(十七)翻译监控

11 篇文章 2 订阅
3 篇文章 0 订阅

一、引言

    作者最近做的一个功能是需要监控一个翻译转换,根据国家和语言进行分组,然后定时把监控情况放到ck里面。为什么是分组和定时监控呢?因为调用比较高的系统的qps在单机一万多,70台机器,可怕的高频调用注定他不能实时分析。

二、方案设计

1、需求

    先明确一下功能点,翻译监控很多读者可能不太理解,大家可以类比redis缓存监控,只不过由于高频不能做实时监控,要根据国家、语言把redis缓存进行分组监控,方便监控一段时间内的国家语言维度对应key的缓存访问频率。

2、分析

    首先要增加切点,因为这种监控肯定要方便发布、无需更新pom,所以要在字节码增强里面做

    要实现这样的监控,首先要有一个存储,把国家+语言+key作为唯一键,存储他的频率,这种肯定是需要一个map了。

    还需要定时将map存储的数据发送到ck,那就需要开启一个可配置频率的定时任务。定时任务的开启也是一个问题,因为在javaagent里面使用不了Spring的定时包,原因之前的文章说过。所以必须使用jdk原生的工具,jdk倒是使用延时队列和lock阻塞实现了一个ScheduledThreadPoolExecutor。

    使用ScheduledThreadPoolExecutor要注意它的无界队列,但是他没有提供更改队列数量的方法,那我们有两个方案:一个是使用信号量进行阻塞,防止大量任务进入队列;另外一种是封闭这个线程池,只在开启一次,队列不进行二次任务进入

    这里就带来了并发问题,map在被定时任务读取发送到ck的时候,还有高频的写入操作,加锁就太影响性能了。那么有什么巧妙的设计可以规避吗?可以设置两个map,一个用来读一个用来写,在定时任务读取的时候设置标志位,切换map的使用。

三、代码    

    分析完了还是要在代码中实践,会发现更多问题

1、map存储

   使用了一个AtomicBoolean标识目前使用的map

    

public class MonitorMapHandleUtils {
    private static final ConcurrentHashMap<String, Integer> firstMap = new ConcurrentHashMap<>();

    private static final ConcurrentHashMap<String, Integer> secondMap = new ConcurrentHashMap<>();


    private static AtomicBoolean useFirst = new AtomicBoolean(true);

    private static final String SEMICOLON = ";";

    /**
     * country+ key + locale维度分组
     * 
     * @param key
     */
    public static void pushMap(Object key, Object locale, String country) {

        String key = country + SEMICOLON + key + SEMICOLON + locale;
        if (useFirst.get()) {
            firstMap.compute(key, (k, v) -> (v == null) ? 1 : v + 1);
            return;
        }
        secondMap.compute(key, (k, v) -> (v == null) ? 1 : v + 1);
    }

    public static void runSendMap() {
        if (useFirst.get()) {
            sendMap(firstMap);
            return;
        }
        sendMap(secondMap);
    }

    /**
     * 控制分钟级别,不会有useFirst连续设置的风险
     * 
     * @param map
     */
    protected static void sendMap(ConcurrentHashMap<String, Integer> map) {
        useFirst.set(!useFirst.get());
        map.forEach((k, v) -> MonitorMapHandleUtils.sendCk(k, v));
        map.clear();
    }

    private static void sendCk(String key, Integer count) {
    }
}

2、定时发送

    这里其实是不断的在取延时多久执行,配置中心都是推送客户端缓存的,所以这里的检查不会有多少性能损耗

    使用start开启定时任务,可以思考下为什么要这样做。比如有的同事说为什么不在static静态块里面处理?

    首先即使在静态块处理,这个类也必须是主动使用的,否则不会被按需加载,类的主动使用包括以下几种情况:

创建类的实例。

访问类的静态方法。

访问类的静态字段,除了声明为final的字段,它们是编译时常量。

使用java.lang.reflect包的方法对类进行反射调用。

初始化一个类的子类(首先会初始化父类)。

   加载的时候必须保证所需的包已经加载好了,这里有什么操作?配置中心的首次检查,更新延时时间,所以我们必须让这个定时类的方法被调用,而且是在配置中心包加载好之后才能调用。

    这样的话就要把MonitorMapHandleUtils使用的变量或者方法放在MonitorDynamicScheduledTask,然后调用,这样才能保证配置中心一定是已经加载好的,这取决于调用的时机,只有被调用到的时候这些类才会按需加载,这样代码结构看起来就会比较诡异,互相依赖

    定时类只放开了一个方法,并且调用一次之后就不再允许处理,避免多次调用,所以就不会有无界队列过多任务的风险


public class MonitorDynamicScheduledTask {

    private static final ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1);
    /**
     * 初始延迟时间,单位为分钟
     */
    private static volatile int currentDelay = 240;

    private static AtomicBoolean startFlag = new AtomicBoolean(false);

    /**
     * 开启仅执行一次,并发执行多次也没关系,瞬时不影响
     */
    public static void start() {
        if (startFlag.get()) {
            return;
        }
        MonitorDynamicScheduledTask.checkDelay();
        scheduleTask(currentDelay);
        startFlag.set(true);
    }

    /**
     * 进入队列,不会有递归调用的方法栈问题
     * 
     * @param delayInHours
     */
    private static void scheduleTask(int delayInHours) {
        scheduler.schedule(() -> {
            if (Config.monitorRun()) {
                MonitorMapHandleUtils.runSendMap();

                MonitorDynamicScheduledTask.checkDelay();

                // 重新调度下一次执行
                MonitorDynamicScheduledTask.scheduleTask(currentDelay);
            }
        }, delayInHours, TimeUnit.MINUTES);
    }

    private static void checkDelay() {
        // 检查配置中心是否有更新
        int newDelay = Config.monitorInterval();
        if (newDelay != currentDelay && newDelay > 0) {
            currentDelay = newDelay;
        }
    }
}

四、总结

    翻译监控不难,难的是在agent无侵入的情况下去监控,要考虑性能损耗、类加载时机、包加载时机,即使是简单的并发也要考虑不能用锁造成性能损耗

图片

  • 21
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖当当技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值