什么?Lombok有坑?

事情是这样的,前段时间有个线上bug需要我去排查:
1

首先看了CAT的调用链,并没有发现什么问题,所有的服务都是正常处理结束。

于是我要了一个traceId,去排查日志,也是没有发现什么问题。

我拉下了生产版本的代码,走查了一下逻辑,并没有发现什么大问题,所以这注定是一个细节问题。

由于这是一个可以复现的问题,所以让测试大佬在测试环境模拟了下有问题的数据,于是我开始Debug了。

一行一行代码的看,一个一个变量的watch,最终发现了问题。

2

首先是同事新增了如下代码:

3

然后数据的OwnerId都是null:

4

紧接着执行了这个removeAll:

5

可是这为什么会出问题呢?

原来是list中的对象,是同事新加的,继承了原有的对象:

6

并且使用了Lombok的Data注解。这个注解会生成equals方法:

7

removeAll是调用的equals判断对象是否相同,equals方法重写后,导致只要这两个对象的ownerId都为空就会认为相同。

可以看到一个@Data注解相当于加了6个注解:

8

怎么解决这个问题呢?既然问题出在equals,那么有没有什么办法让子类生成的equals中也能包含基类的属性呢?

是有的:

1
@EqualsAndHashCode(callSuper = true)

加上这个注解就可以了。

可以看到@Data注解做的事情太多了,会发生你意想不到的情况,实际是不太推荐无脑使用的,建议还是自己组合需要的注解。

Dubbo调用超时那些事儿

其实之前很早就看过dubbo源码中关于超时这部分的处理逻辑,但是没有记录下来,最近在某脉上看到有人问了这个问题,想着再回顾一下。

开始

从dubbo的请求开始,看看dubbo(2.6.6)在超时这块是怎么处理的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchangeChannel#request(java.lang.Object, int) 
@Override
public ResponseFuture request(Object request, int timeout) throws RemotingException {
if (closed) {
throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
}
// create request.
Request req = new Request();
req.setVersion(Version.getProtocolVersion());
req.setTwoWay(true);
req.setData(request);
DefaultFuture future = new DefaultFuture(channel, req, timeout);
try {
channel.send(req);
} catch (RemotingException e) {
future.cancel();
throw e;
}
return future;
}
阅读更多

Java双亲委派机制的妙用

最近在项目中看到一段通过easyexcel导出动态表头的实现,开始我以为是easyexcel官方的实现,其中有这样一段代码:

1
2
3
4
5
6
7
8
//将动态表头上传至ThreadLocal
saveToThreadLocal(clz, result);

private <T> void saveToThreadLocal(Class<T> clz, List<String> result) {
Map<Class,List<String>> paramMap = new ConcurrentHashMap<>();
paramMap.put(clz, result);
ThreadLocalUtil.FIELD_CACHE_MAP.set(paramMap);
}
阅读更多

Mybatis Mapper 源码分析

天天都在用的Mybatis,为啥调用一个Mapper接口就能执行SQL,你有没有想过这个问题?

这一切都得从 @MapperScan 这个注解开始说起。打开这个注解定义可以看到:

阅读更多

Lambda应用与浅析

引入

在Java8之前创建一个线程的写法(之一):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class LambdaTest {



public static void main(String[] args) {

new Thread(new Runnable() {

@Override

public void run() {

System.out.println("hello inner class!");

}

});

}

}
阅读更多

Netty中FastThreadLocal源码解析

简介

ThreadLocal一个特殊变体,当从FastThreadLocalThread访问时,可获得更高的访问性能。
在内部, FastThreadLocal在数组中使用常量索引来查找变量,而不是使用哈希码和哈希表。 尽管看似非常微妙,但与使用哈希表相比,它在性能上却有一点优势,并且在经常访问时很有用。
要利用此线程局部变量,您的线程必须是FastThreadLocalThread或其子类型。 由于这个原因,默认情况下, DefaultThreadFactory创建的所有线程均为FastThreadLocalThread
请注意,只有在扩展FastThreadLocalThread线程上才可以使用快速路径,因为它需要一个特殊的字段来存储必要的状态。 任何其他类型的线程的访问都回退到常规ThreadLocal

上面这段描述来自FastThreadLocal源码中的文档,从中可以知道FastThreadLocal必须和FastThreadLocalThread或其子类型一起使用才可以达到Fast的效果。

阅读更多

记一次线上死锁排查

前段时间偶尔会收到线上MySQL死锁告警通知,由于有补偿机制,最终业务会处理成功,所以没太关心。

1
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction

最近又收到了相同的告警,可能不是偶然事件,于是开始排查。
首先翻看了日志,结合代码,没有发现什么问题。事发时应该也没有什么大批量并发事件。

阅读更多

利用mybatis标签替换硬编码

建议

利用mybatis标签替换硬编码

背景

在项目中偶尔看到这样的代码:

1
2
3
4
5
6
7
8
9
<sql id="querySqlString">
<where>
1=1
<if test="fundsOrderIdList != null and fundsOrderIdList.size()>0">
and funds_order_id IN
<foreach collection="fundsOrderIdList" item="id" index="index" open="(" close=")" separator=",">
#{id}
</foreach>
</if>

这段代码中有个1=1很扎眼,这个不是bug,也没有什么性能问题,只是程序员世代传承下来的一个习惯。

阅读更多

RocketMQ RMQ_SYS_TRANS_HALF_TOPIC 爆掉的问题

现象

SaaS项目东郭反应,项目中发的事务消息一直在RMQ_SYS_TRANS_HALF_TOPIC中,并且不断增长。随即我们查看RocketMQ日志发现如下情况:

这个本来是RocketMQ正常的逻辑,发送事务消息后没有提交状态的话,当达到超时时间后,RocketMQ会回查本地事务状态。这里显示的是回查的次数超限,消息被移到了TRANS_CHECK_MAXTIME_TOPIC中。

不正常的是REAL_TOPIC变成了RMQ_SYS_TRANS_HALF_TOPIC,正常应该是原始的业务消息TOPIC才对。于是我们带着这个问题开始排查起来。

阅读更多