工作中的代码使用问题记录1

JPA的save与saveAndFlush

由于是微服务的架构,服务与服务间的解耦与通讯是通过消息队列(MQ)方式进行的。这几天在排查一个线上的偶发问题,服务A在处理业务流程a中发放了MQ消息,服务B接收该消息并进行消费,消费过程中需要反查服务A,而该偶发问题便是在反查过程中发现接口返回的对象状态还未改变。

因为是偶发问题,所以复现就比较难,这时候充分的重新阅读代码就很重要。仔细阅读后发现有两个问题,一是发送MQ的时机过早的问题,二是JPA的save方法不会立即入库的问题。

先说第一点,对于服务B而言,服务A发出的任何MQ消息,都代表着服务A处理完成了某个业务流程,注意,重点是“完成”二字,因此发送MQ必须要确保自身的业务已经执行完成。而原代码中,很多MQ消息的发送是在业务流程中间发出的,也就会导致服务B收到了“服务A已完成了业务流程a”的消息,但实际上,此刻服务A还未将改变后的业务数据持久化到数据库。因此,需要将MQ消息的发送延后至整个业务执行完毕,最好是数据也被持久化到数据库之后进行。

第二个问题其实是延续第一个问题的,即JPA的save方法并不会将数据立即持久化到数据库,而是缓存到内存中,通常是在一个显式事务(如@Transactional)结束时提交的,因此在原有逻辑较为复杂不好改动的情况下,将save方法更换为saveAndFlush方法,使得JPA立即持久化一次数据,也进一步确保了业务数据的时序性。

慎用redis的keys方法

背景是另一个开发组近来发现线上某个业务会出现卡顿的情况,在排查后发现,代码中有一处操作redis的逻辑中使用了keys方法导致的。因为早期数据量不多,所以没有被感知,但随着业务量增多,keys方法会一次性取出所有满足正则条件规则的key,又因为redis本身是单线程的,短时间内IO被keys读取所占用,就导致了程序的短暂停止,而反馈到实际应用中,就会有成百上千个接口请求得不到响应。

正确的做法是使用scan方法,由于scan是通过游标分步进行的,不会阻塞线程,并且可以像接口取分页数据一样通过limit参数控制返回的结果数量。