本人将作为一个开发者的角度去思考线上故障的处理和解决方案,鉴于本人是小菜鸡,如有不对,多多请各位大佬指正!感激不尽
一、类型
线上故障类型很多,目前作者就遇到过几种
(1)线上OOM问题
(2)底层资源不足导致某些重要服务被踢出
(3)底层资源不足/网络通信/其他问题 使得底层服务DB崩溃
(4)业务依赖的组件 如MQ、Redis崩溃
二、排查流程
(1)首先是OOM问题
OOM其实算是比较常见的线上问题了,一般来说在发生OOM的时候,紧急措施应该是要立即重启,同时外部流量需要避免再继续打入此服务(不过这个一般是基架团队和网关团队的事情)。对于我们业务团队来说,更多是迅速去拿到这个OOM的Dump文件,对Dump文件进行进一步的分析。
一般来说我们可以利用MAT进行Dump的分析
这个会告诉你,最有可能造成这个OOM的线程可能是哪个
不过实际分析的时候,我们会去看当时是对象哪个占的大,随后有historym去看调用的线程,查看哪个线程发生了问题,追踪线程的操作,当时在干什么,找到问题线程之后,拿当时的相关信息,进行翻译转义,最后结合业务特点进行代码的修改即可
(2)业务依赖的组件 如MQ、Redis崩溃
这个按理来说不属于业务团队的问题,但是其实对于业务来说,完全由能力在代码上进行防御,从而减少对外部组建的依赖,实现故障的自动同步
举个例子:MQ集群崩溃。首先我们要思考 MQ是做什么的 显而易见
八股三件套:异步、削峰、解耦
但是仅仅是会背这三个字是不够滴(主要是你没办法从这个三个字管中窥豹)想了解请移步XXXX(还没写,过段时间再写MQ相关的)
那我们就针对这三个场景看看有没有解决方案吧
首先是共有的,既然不能完全信任MQ,那么我们代码块中一定要有try catch(当然了,主流的MQ客户端一定会有异常抛出的)
分场景,首先是同一个项目中,引入了MQ
那么MQ的作用显而易见(其实也不一定,但是大概率是削峰和异步),那么作为兜底措施完全是可以在catch层降级为同步调用,由此保证可靠性
举个例子
@Component
public class MQProducer {
@Autowired
private MQManager mqManager;
private final Gson gson = new Gson();
private final DefaultMQProducer producer;
private final String producerGroup;
private final String nameServer;
private final String TOPIC;
public MQProducer(String nameServer,String producerGroup,String topic){
this.nameServer = nameServer;
this.producerGroup = producerGroup;
this.producer = new DefaultMQProducer(producerGroup);
this.TOPIC = topic;
}
public void start(){
try{
this.producer.start();
}catch (MQClientException e){
throw new RuntimeException("Failed to XXXXX",e);
}
}
public void shutdown(){
this.producer.shutdown();
}
public void send(MQSendBean mqSendBean){
Message message = new Message();
message.setBody(gson.toJson(mqSendBean).getBytes());
message.setTopic(TOPIC);
message.setTags(mqSendBean.getTag().name());
String messageId;
try{
SendResult sendResult = producer.send(message);
messageId = sendResult.getMsgId();
}catch (Exception e){
// 降级服务
mqManager.process();
}
}
}
这个时候有同学就会问了,主播主播,你的降级确实很有东西,但是跨系统非自产自销的怎么办呢,你不能强制对方给你弄个接口吧?你还有方法吗?
有的兄弟,有的——可以用重试组件讲业务数据失败的记录在册,重试成功再删除数据
public void send(MQSendBean mqSendBean){
Message message = new Message();
message.setBody(gson.toJson(mqSendBean).getBytes());
message.setTopic(TOPIC);
message.setTags(mqSendBean.getTag().name());
String messageId;
try{
SendResult sendResult = producer.send(message);
messageId = sendResult.getMsgId();
}catch (Exception e){
// 降级服务
RertyUtil.record()
}
}
但是兄弟,还是温馨提醒一句,重试不仅会增加系统复杂度,你还应该考虑一个问题
MQ是不是本身就有三次重试?你想想看,要是MQ本身有重试,你这个还是发送失败,这说明甚么?——出大问题了啊兄弟,MQ集群要炸了啊!重试还香吗?我看未必
于是我们经常用的手法可能更倾向于——上报日志
public void send(MQSendBean mqSendBean){
Message message = new Message();
message.setBody(gson.toJson(mqSendBean).getBytes());
message.setTopic(TOPIC);
message.setTags(mqSendBean.getTag().name());
String messageId;
try{
SendResult sendResult = producer.send(message);
messageId = sendResult.getMsgId();
}catch (Exception e){
// 降级服务
LogUtil.log()
}
}
这个时候有小伙伴就会说了
主播主播,我们常说有熔断、限流,你怎么没有说呢?
不是主播不想说,主播接触的常是业务代码,所以熔断其实有业务不一致的风险的,so其实我们一般不会用
但是主播在MQ限流降级确实还有另外一点心得不过跟这个主题——线上故障关系不大了
请移步到XXXXX(还没写)去看看相关知识