今天,由于凌晨1点开始,数据库出现了问题,这边数据库两个结点,其中一个结点出现了问题。
然后,由于业务中有一个job,每4分钟查找一个中间表,然后将表中的记录发到queue中去执行。
queue端处理逻辑是,先执行删除这个中间表,如果没有删除任何记录,则抛出异常。
如果删除中间表记录成功后,接着处理完这个消息对应的逻辑。
由于数据库挂了,因此,当queue处理时,去找对应数据库中间表记录,这时会找不到,因此抛出一个异常,最终这个异常被 父类catch住,最终执行rollback。
但由于数据库原因,这个rollback最终也抛出了异常,这时被spring的ListenerContainer捕捉到。
重点在于rabbitMq的requeReject之前被设置成true。应该客户端会发送抛出异常的消息给服务器,因此服务器又重新放入了这条message(客户端先发Nack,告诉服务器要删除原有队列中的消息,然后客户端将本地message重新交给服务器,服务器会放入到queue)
之前讲到的job,之后还是不断查找中间表,然后发往queue。这样queue就不断的累积重复的消息。
之后,数据库恢复,但由于n个重复的业务消息只会被处理成功一次,其它n-1个都会抛出业务异常,这样服务器不断产生日志,空间被占满。
最后,将rabbitMq的requeReject设置成false(这时要注意当服务器关掉时,由于spring组件的销毁顺序,先销毁业务组件,然后 MessageListener还是存在,这时就有可能会丢失消息)。
还有一个,就是在queue的业务处理时,对于已经被删除的中间表记录,只需记个log就可,不需要抛出异常,这需要保证在业务开始前做检查。(删除记录会锁住当前记录,独占),即只要检查通过,就可以处理业务。每个业务消息(相同业务号)只被处理一次,需要由在业务前做删除检查保证。
总结:
数据库异常,在访问量大,或者有密集的任务运行时,会产生相当大的日志产生。最好日志文件能够及时的转移到别的机子上。对于数据库异常要相当重视,建立报警机制,服务器的磁盘空间,服务器状态都需要有报警机制。
业务处理时,对于job轮循中间表处理业务时,也可以对中间表记录加个状态。job取出记录时将其置为待处理。queue消息端,处理成功后,将其置为success,如果碰到数据库异常,这种需要再次处理,这时需要设置requeReject等于true,之后会重新消息这个消息。这种方案,会存在,发送消息给rabbitmq时已经丢了,这时导致中间表对应记录始终处于待处理状态。
设计编码时,需要考虑全面,对于应用的技术,要相当了解。细节特别重要!