有个开发需求是日志每天只发送一次,现在遇到个bug,测试环境没有发送数据,我在本地启动后有新的日志产生,kafka没看到错误日志,而代码里没有其他更多的逻辑,所以我猜测就是每天只发送一次的这个逻辑发生了错误

因为我前面日志都是用的流计算kafka stream统计ip频繁查询次数,key为ip,value为次数,为此这里我定义了一个static的map来判断当前key是否有重新发送

private static Map<String, Integer> ipMap = new ConcurrentHashMap<>();
//下面是流处理的伪代码
stream
.groupByKey(ip)
.windowedBy()
.count().
.toStream()
.foreach((key,value)->{
    if(value.intValue() >= 20 ) {//这里是超过20次
        if(ipMap.get(key.key()) == null || value.intValue() < ipMap.get(key.key())) { //这里实现一天最多发一条
            ipMap.put(key.key(), value.intValue());
            //...业务逻辑
        }
    }
})

代码的具体逻辑就是我首先判断当前次数是否超过20次了,才预警,然后下面的ipMap逻辑就是为了处理一天只发送一条的实现,我会把value次数存起来,因为我这个时间窗口范围是一天,所以其实每个key的value预计的情况是
image.png
每天同一个key的value都是从0开始不断增长的,但是到第二天的时候,value又会从0开始累加,我依照这个特点实现了上述代码,当现在的value小于上一次的value时,说明是新的一天了,这时候就可以执行业务逻辑发送

(为什么不用获取当前时间来判断呢?考虑到可能日志是昨天发的,但是由于执行或者其他导致延迟,聚合事件的时候已经是第二天了,所以是不可靠的)

但是应该是有问题的,本地我新启动的话又没问题,因为我的static的ipMap是空的了,所以我借助Arthas来在线查看测试环境的内容

我直接下载好后打开arthas,执行java -jar arthas-boot.jar,这里我还遇到了一个问题,如果启动说3658被占用,是因为上一次选择进程进行连接没有正常退出,arthas会保存上一次监听进程,导致本次选择新进程进行连接时,与监听中记录的进程id不同,结果出现错误,继续选择上一个进程进行连接,执行成功后执行 stop 命令结束连接

然后我通过getstatic命令 查看到这个ipMap字段的内容,发现这个map的当前ip的key的value内容为20,所以上面代码有两种情况不会执行预警逻辑

一是流计算的value.intValue() >= ipMap.get(key.key()),这种情况有没有可能发生呢,是有可能的,因为这个value.intValue()是kafka进行key(ip)聚合后的总数,然而这个方法并不是每次进来一条数据就会触发一次,而是一定的策略才发生触发(或者由于各种原因导致消息堆积),有可能kafka stream一下子处理了超过20条的记录然后再聚合发送到下游时.foreach((key,value) -> {})这里的value的值就会出现比上一次ipMap里的value值要大,就不会执行逻辑,

二是今天的次数刚好是20,昨天的次数也刚好是20,因为我写的value.intValue() < ipMap.get(key.key())所以这种情况也不会执行

为了查看流返回的value值,我们继续使用arthas进行查看,首先我们需要找到该方法:因为kafka stream的逻辑都是用Lambda表达式来写的,实际上是一个匿名方法,所以我首先在本地通过javap -p 类.class来查看这个方法的匿名方法名是什么(我这里看了一下是lambda$null$8,匿名方法的顺序好像和其他方法的顺序正好相反,是写在越前面的编译后的位置越在后面),然后我通过arthas的tt -t class method查看进入方法的入参值,结果也如我所料,这个value的值是57,大于20

所以我这个代码是有问题的,解决方案最后还是用时间来进行判断的,不过不是取当前系统时间来判断,而是通过聚合后的.foreach((key,value)->{})中的key,不仅包含了自定义的ip,其中的key.window()会包含是属于哪个窗口的数据,这样的话我还是用一个map存起来,然后存的是这个窗口的时间,使用这个窗口的时间进行判断是不是同一个就知道是不是今天了,是的话说明已经发送过了

这里的map不会进行回收,后面我会换成可过期key的缓存即可,过期时间设置成1天就好了


我不是码农
3 声望1 粉丝

java开发码农