一、服务容错

1、高并发带来的问题

现在我们有了订单微服务、商品微服务、用户微服务,服务与服务之间可以相互调用,但是由于网络或者自身原因,微服务不能保证100%都是可用,如果其中一个服务出现问题,那么调用它的服务就会出现网络延迟,此时如果有大量网络涌入,会形成任务堆积,最终导致服务瘫痪。

如何解决微服务之间失败重试的幂等问题 微服务异常_限流

如图所示,serviceB出现故障,此时serviceA中的a1()方法去调用b1()方法,得不到响应信息,
一直调用,如果按照tomcat最大线程数400来说,那么service中a1()方法一直拿到400位置,
如果此时a2()方法想要去调用b2()方法,发现没有可用连接,也会等待。导致其他方法也不可用,
最终导致整个serviceA不可用,那么调用serviceA的服务最终也不可用,这就是服务雪崩,
发生雪崩时没有一片雪花是无辜的!!!

2、模拟并发场景
原有下单过程,线程睡上5s

@RestController
@Slf4j
@RequestMapping("/test")
public class OrderController2 {
    @Autowired
    private IOrderService orderService;

    @Autowired
    private IProductService productService;

    //下单 使用feign调用远程服务
    @RequestMapping("/createOrder/{pid}")
    public Order createOrder(@PathVariable Integer pid) {
        //使用微服务的名字,从注册中心Nacos中获取
        Product product = productService.findProdutById(pid);
        try {
            Thread.sleep(5000);
            log.info("模拟并发场景,网络延时");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Order order = new Order();
        order.setNumber(2);
        order.setUid(1);
        order.setUsername("chenchen");
        order.setPid(product.getPid());
        order.setPname(product.getPname());
        order.setPprice(product.getPprice());
        return order;
    }

    @RequestMapping("/show")
    public String show() {
        return "测试并发:"+new SimpleDateFormat("yyy-MM-dd HH:mm:ss").format(new Date());
    }
}

配置文件中修改tomcat连接数

server:
  port: 8092
  tomcat:
    max-threads: 10

使用压测工具JMeter测试

如何解决微服务之间失败重试的幂等问题 微服务异常_微服务_02


如何解决微服务之间失败重试的幂等问题 微服务异常_java_03


由于createOrder()方法堆积了大量请求,再去请求show()方法,可以看到请求阻塞…

如何解决微服务之间失败重试的幂等问题 微服务异常_java_04


3、服务雪崩

简单来说在分布式系统中,由于网络或者自身原因,服务一般无法保证100%可用。如果一个服务出现了问题,调用的这个服务就会出现线程阻塞的情况,此时若有大量的请求涌入,就会出现多条线程阻塞等待,进而导致服务瘫痪。正是服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成毁灭性的后果,也就是常说的雪崩效应,如图所示。

如何解决微服务之间失败重试的幂等问题 微服务异常_微服务_05


服务发生雪崩的原因有很多,比如不合理的容量设计(service B只能接受10个线程访问,但是给了20个线程),或者并发下一个方法响应变慢,或者某个服务资源耗尽等,正常情况下无法杜绝源头的发生,只能做好足够的容错,保证一个服务发生问题,而不影响其他服务正常运行,雪落而不雪崩,

也可以总结三句话:

1、不被上游服务压垮(service A有大量请求过来时,保证自身(service B)始终是可用的)
2、不被下游服务拖垮(service C挂掉时,自身不被影响)
3、外部环境保证可用(保证机器正常运行,不能运行着突然断电等情况发生)

4、常见容错方案

隔离、超时、限流、熔断、降级

4.1 隔离

指的是系统按照一定原则划分若干个服务模块,各个模块之间相对独立、无强依赖,当有故障发生时,
	能将问题和影响隔离在某个模块内部,而不扩散风险,不波及其他模块,不影响整体的系统服务,
	如线程池隔离和信号量隔离

如何解决微服务之间失败重试的幂等问题 微服务异常_限流_06


4.2 超时

指的是在上游服务调用下游服务的时候,设置一个最大响应时间,如果超过这个时间,下游未作出反应,
就断开请求,释放掉线程,达到自我保护的目的。

如何解决微服务之间失败重试的幂等问题 微服务异常_微服务_07


4.3 限流

限制系统的输入和输出流量,达到保护系统目的。为了保证系统的稳固运行,一旦达到设置的阈值,就需要限流。

如何解决微服务之间失败重试的幂等问题 微服务异常_如何解决微服务之间失败重试的幂等问题_08


4.4 熔断

如果下游服务因为访问压力过大而影响变慢或者失败,上游服务为了保证系统整体可用性,
可以暂时切断对下游服务的调用,这种局部牺牲,保全整体的措施就是熔断。

如何解决微服务之间失败重试的幂等问题 微服务异常_微服务_09

服务熔断的三种状态
 1、熔断关闭 closed
 	熔断没有故障时,熔断器所处的状态,对于调用方不做任何限制
 2、熔断开启 open
    后续对该服务接口的调用不在经过网络,直接执行本地fallback方法
 3、半熔断Half-Open
 	尝试恢复调用,允许有限的流量调用该服务,并且监控调用成功率,如果成功率达到预期,则说明服务已经恢复,进入熔断关闭,如果成功率很低,则重新进入熔断开启。

4.5 降级

降级是为服务提供一个托底方案,一旦服务无法正常调用,就使用托底(备用)方案。

如何解决微服务之间失败重试的幂等问题 微服务异常_如何解决微服务之间失败重试的幂等问题_10