一次奇妙的服务“宕机”排错之旅

3 篇文章 0 订阅
1 篇文章 0 订阅

一次奇妙的服务“宕机”排错之旅

起因

这天,生产上突然传来噩耗,一个服务宕机了,调用该服务都失效了,这可是大问题,第一时间想到的就是别不是这个服务宕机了吧,然后赶紧叫运维看下服务状态,可是发现服务状态是好的,可是为什么前端调用都是超时呢。没办法,现在只能先重启大法好,复制了一份服务和zuul日志下来,然后重启服务,万幸的是服务恢复了。但是为什么呢。甲方也要我们拿出措施来防止下次发生同样的事。

先说下我们的架构,简简单单的zuul+eureka+服务进行通信。

回想下我们当时的排错措施,1.前端调用失败,返回是超时。2.本地服务状态是好的,利用curl来调用,能正常提供服务。3.查看日志,发现服务日志并没有异常报错,查看zuul日志,发现只是一些超时异常。

猜想一,是不是服务挂了

第一思路就是,是不是服务的某个接口有问题,比如说用了太长时间,但是这样会导致服务整个提供服务失败吗。想想也不可能,服务有熔断机制,过长时间会自己熔断掉的。那是不是配置的原因呢,我们的服务某个配置导致失败呢。配置也没改动啊,生产上。这个只是偶发事故。配置也很久没动了。

那我就猜想,服务是怎么提供服务的,是通过线程的啊,那会不会是某个接口,调用了太多的线程,导致线程阻塞了,然后对外提供服务失败,也不对啊,本地调用成功的当时。但是当时就钻这个坑了,是不是某个地方接口开了太多线程,开发这个服务的同事说,自己在这个服务的一个接口里,调用了很多次的fegin,为了优化,又开了线程去调用fegin,但是那时候发现fegin很多直接熔断了,所以进行了个性化配置,会不会出问题在这。

先看下feign的配置。

hystrix:   #熔断器配置
  threadpool:
    default:
      coreSize: 100
  command:
    default:
      fallback:
        isolation:
          semaphore:
            maxConcurrentRequests: 50
      execution:
        timeout:
          enabled: true
        isolation
          thread:
            timeoutInMilliseconds: 600000
      circuitBreaker:
        requestVolumeThreshold: 1000

这个配置也很好理解,fegin的核心线程数100,最大请求50,超时时间和错误多少熔断。

这个时候我就疑问来了,fegin是怎么去调用其他服务的,我理解的是fegin也只是通过eureka来把服务名换取ip,然后自己拼接成http请求去调用服务,但是怎么管理的呢。然后我就去百度配置了。

https://blog.csdn.net/tongtong_use/article/details/78611225

https://www.jianshu.com/p/f7fb59f43485

https://blog.csdn.net/hry2015/article/details/78554846

https://www.cnblogs.com/duan2/p/9302431.html

很明显,这里比较重要的是

semaphore和thread隔离策略的区别

https://blog.csdn.net/icangfeng/article/details/81203490

https://blog.csdn.net/liaojiamin0102/article/details/94394956?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.not_use_machine_learn_pai&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromBaidu-1.not_use_machine_learn_pai

第二个链接就比较清晰易懂了。

但是我这里还是一头雾水,不知道出哪了。

猜想二,zuul网关问题

我们的服务链路是nginx-zuul-服务。这个时候是zuul到服务的链路断了,应该是zuul的问题吧。

但是怎么验证呢。

这时候老大就提出了验证的方案,服务里弄个验证服务接口,接口里睡到超时,然后开个jmeter开一百个线程去调用,看看这时候能通过网关访问服务吗。

说做就做。

第一步:服务里写接口

@GetMapping("/f/test")
public String test() throws InterruptedException {
    Integer next = Mytest.getNext();
    System.out.println("当前第"+next+"线程执行");
    Thread.sleep(40000);
    System.out.println("当前第"+next+"线程执行完成");
    return "success";
}

mytest是一个我写的类,里面有个类常量,用来计数的。

public class Mytest {
    public static  Integer num=0;
    public static synchronized Integer getNext(){
        Mytest.num=Mytest.num+1;
        return Mytest.num;
    }
}

接口写好了,然后就是jmeter了,不了解的百度下(https://www.cnblogs.com/monjeo/p/9330464.html)

看下zuul网关的设置

ribbon:
  ReadTimeout: 30000
  ConnectTimeout: 30000
  MaxAutoRetries: 1
  MaxAutoRetriesNextServer: 2
  eureka:
    enabled: true
hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 360000

可以看出,zuul网关应该也是用了熔断的,然后连接超时是30s,读超时是30s(应用执行超时)。然后熔断是360s。因为设置了最大重试,(30+30)*(1+2)

在*2=360

然后这又一个知识点。eureka:enabled表明zuul用的是eureka的ribbon客户端。ribbon是支持替换底层的通信框架的,本来用的原来的javaconnect。然后你可以改为http-client。

先不管其他的先测试,先测试能不能正常访问服务的其他接口,能。然后设置test接口为读超时,40s。然后jmeter一开,emm。立刻访问服务的其他的服务,发现,服务跟zuul的连接断了,问题重现了。

问题重现就好办了,我们先来分析下我们的测试,我们只是开了100线程,去访问一个读超时的接口,然后在继续访问就失败了,

那我们分析下zuul的实现,他是怎么做到请求转发的呢,其实就是调用ribbon来转发请求,分为自己管理线程来转发请求和信号量管理,那会不会是因为调用了太多线程,而线程卡在了超时的接口上,并没有超时,导致线程没有空出来,而后续的请求这个服务的都阻塞了。

然后我发现了一个细节,我调用的jemeter设置的线程数是100,而测试类的打印线程数只有50。那么思路就有了,是不是zuul设置了服务的并发量为50,超过就拒绝。思路有了,就百度。

http://www.chinacion.cn/article/4126.html

又涉及到了thread和semaphore。

然后可以发现我们zuul用的是semaphore,那是不是单个服务实例的信号量设置的过小,而一些过长时间接口,用户以为失效,然后点了过多,然后zuul的请求就阻塞这几个了,然后线程数越来越少,最后直接熔断了,拒绝服务了。

基本就是这样了,那就设置下单个服务的最大并发请求,然后设置下读超时。

ribbon:
  ReadTimeout: 120000
  ConnectTimeout: 30000
  MaxAutoRetries: 1
  MaxAutoRetriesNextServer: 2
  MaxConnectionsPerHost: 500  #单个后端微服务实例能接收的最大请求并发数  
  MaxTotalConnections: 2000
  eureka:
    enabled: true
hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 900000

然后用jemeter测试,现在100个线程,控制台就打印出100个了

然后不影响通过网关调用服务的其他接口了。

https://blog.csdn.net/liuminglei1987/article/details/103676945

https://blog.csdn.net/weixin_36647532/article/details/89225800

那线程执行完毕会恢复访问吗,我在测试后,再通过网关调用服务的其他接口,恢复通信了。

那说明是不是等服务执行完毕,他们zuul不能调用的就会恢复,但是现在暂时不能验证,毕竟生产环境又不等你。

emm,生产上又出现这个问题了,改了配置上去没有用,emm,甲方要杀人了。

再排查问题,还是用上面的测试,改为1000的线程,发现,好像只能最多一次打印200次调用。

我明明设置了500的,为什么只有两百,然后我在想,是不是其他地方又限制导致上不去。首先先的是不是服务方的对外的线程就200?

测试的接口不从网关调用,直接本地端口调用,确实是200.

然后是找到springboot设置线程的值。

server:
  tomcat:
    max-threads: 1000

确实默认值是200,然后修改,然后发现,测试改为zuul,还是200,是不是zuul网关也有这个设置。

设置了以后确实测试是1000了,但是问题又来了,当我占满了zuul的线程,其实通过网关调用其他服务是失败的,但是生产上的话,只是这个模块与网关的调用失效了。

重新总结下现象。

一。zuul服务日志报超时

二。网关调用其他服务是成功的

三。本地调用是成功的,但是是在发生熔断后的。

怀疑点,1.zuul网关的并发信号量最大请求设置有问题 。2.zuul网关的线程数设置有问题。3。服务方的线程数设置有问题。

4.怀疑是不是服务心跳到eureka失败了。

后来老大怀疑是不是前端某个地方在疯狂调用这个服务,导致服务挂着了,倒真的找到了个地方。一次调用200次接口,500ms调一次。

这个肯定不行的,先改这个吧。看看后续会不会出现问题。

解决方案

现在能做的就是,zuul服务部署多个,保证一个不行,其他能行,nginx分发,(但是这样不会一次请求好,一次请求失败?)

设置单个服务的最大并发量。

优化长时间时接口,转为异步接口。

优化前端的调用设置,长时间接口调用按钮,一次后置为不可用。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值