微服务高可用技术
大型复杂的分布式系统中,高可用相关的技术架构非常重要。高可用架构非常重要的一个环节,就是如何将分布式系统中的各个服务打造成高可用的服务,从而足以应对分布式系统环境中的各种各样的问题,,避免整个分布式系统被某个服务的故障给拖垮.
比如:服务间的调用超时、服务间的调用失败
要解决这些棘手的分布式系统可用性问题,就涉及到了高可用分布式系统中的很多重要的技术,包括:
资源隔离
限流与过载保护
熔断
优雅降级
容错
超时控制
监控运维
服务降级、熔断、限流概念
服务学崩效应
服务雪崩效应产生与服务堆积在同一个线程池中,因为所有的请求都是同一个线程池进行处理,这时候如果在高并发情况下,所有的请求全部访问同一个接口,这时候可能会导致其他服务没有线程进行接受请求,这就是服务雪崩效应效应。
总结为:雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐步放大的过程。
服务降级
在高并发情况下,防止用户一直等待,使用服务降级方式(直接返回一个友好的提示给客户端,调用fallBack方法)
服务熔断
熔断机制目的为了保护服务,在高并发的情况下,如果请求达到一定极限(可以自己设置阈值)如果流量超出了设置阈值,让后直接拒绝访问,保护当前服务。使用服务降级方式返回一个友好提示,服务熔断和服务降级一起使用
服务隔离
因为默认情况下,只有一个线程池会维护所有的服务接口,如果大量的请求访问同一个接口,达到线程池默认极限,可能会导致其他服务无法访问。
解决服务雪崩效应:使用服务隔离机制(线程池方式和信号量),使用线程池方式实现隔离的原理: 相当于每个接口(服务)都有自己独立的线程池,因为每个线程池互不影响,这样的话就可以解决服务雪崩效应。
线程池隔离:
每个服务接口,都有自己独立的线程池,每个线程池互不影响。
信号量隔离:
使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,当请求进来时先判断计数器的数值,若超过设置的最大线程个数则拒绝该请求,若不超过则通行,这时候计数器+1,请求返 回成功后计数器-1。
服务限流
服务限流就是对接口访问进行限制,常用服务限流算法令牌桶、漏桶。计数器也可以进行粗暴限流实现
概念介绍完了,现在直接上代码!
我这边有两个服务,一个是消费者服务(consumer),另一个是提供者服务(provider),这两个服务都是注册在Eureka中,关于Eureka的使用在这不做过多的介绍,可以查看我的这篇文章springboot+springcloud+eureka实现高可用注册中心
Provider服务
application.yml
###服务启动端口号
server:
port: 8007
###服务名称(服务注册到eureka名称)
spring:
application:
name: zxf-lancoo-zxfprovider
###集群地址
eureka:
client:
registryFetchIntervalSeconds: 5
service-url:
defaultZone: http://127.0.0.1:8100/eureka,http://127.0.0.1:8200/eureka
###因为该应用为注册中心,不会注册自己
register-with-eureka: true
###是否需要从eureka上获取注册信息
fetch-registry: true
pom.xml
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot整合eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
controller
@RequestMapping("/getProviderInfo")
public String getProviderInfo() {
// TODO Auto-generated method stub
try {
//会员服务产生1500毫秒的等待
Thread.sleep(15000); //假设处理业务所需时间为1.5秒,因为hystrix默认超时为1秒
} catch (Exception e) {
// TODO: handle exception
}
return "consumer服务调用provider服务成功...";
//return setResultSuccess("订单服务接口调用会员服务接口成功...");
}
启动类AppProvider
@SpringBootApplication
@EnableEurekaClient
public class AppProvider {
public static void main(String[] args) {
SpringApplication.run(AppProvider.class, args);
}
}
consumer服务
application.yml
###服务启动端口号
server:
port: 8005
###服务名称(服务注册到eureka名称)
spring:
application:
name: lancoo-zxf-zxfconsumer
#单个服务注册
# boot:
# admin:
# client:
# url: "http://localhost:8000"
###服务注册到eureka集群
eureka:
client:
registryFetchIntervalSeconds: 5
service-url:
defaultZone: http://127.0.0.1:8100/eureka,http://127.0.0.1:8200/eureka
###因为该应用为注册中心,不会注册自己
register-with-eureka: true
###是否需要从eureka上获取注册信息
fetch-registry: true
#开启Hystrix断路器 20190620
feign:
hystrix:
enabled: true
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- SpringBoot整合eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- hystrix断路器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
controller
//没有解决服务雪崩效应
@RequestMapping("/consumeToProvider")
public String consumeToProvider() {
// TODO Auto-generated method stub
System.out.println("RPC远程服务地址:"+getProviderServer());
String providerUrl = getProviderServer()+"/getProviderInfo";// 生产端getProviderInfo地址
System.out.println("访问远程服务getProviderInfo地址:"+providerUrl);
//ServiceInstance serviceInstance = loadBalancerClient.choose("zxf-lancoo-zxfprovider");
RestTemplate restTemplate = getRestTemplate();
String result = restTemplate.getForObject(providerUrl, String.class);
//String result = restTemplate.getForObject(serviceInstance.getUri()+"/getProviderInfo", String.class);
System.out.println("访问远程服务结果:"+result);
System.out.println("consumeToProvider:"+"线程池名字:"+Thread.currentThread().getName());
return result+"-------"+"线程池名字:"+Thread.currentThread().getName();
}
//解决雪崩效应
/**
* 解决服务雪崩效应 第一种方式:直接写
* fallbackMethod 服务降级执行
* hystrix:
* @HystrixCommand 默认开启线程池隔离方式、服务降级、服务熔断
* 设置禁止hystrix超时时间 command.default.execution.timeout.enabled: false 业务逻辑能执行,浏览器响应,所以走服务降级
* @return
*/
@HystrixCommand(fallbackMethod = "consumeToProviderHystrixFallBack")
@RequestMapping("/consumeToProviderHystrix")
public String orderToMemberUserInfoHystrix() {
// TODO Auto-generated method stub
String providerUrl = getProviderServer()+"/getProviderInfo";// 生产端getProviderInfo地址
System.out.println("访问远程服务getProviderInfo地址:"+providerUrl);
RestTemplate restTemplate = getRestTemplate();
String result = restTemplate.getForObject(providerUrl, String.class);
System.out.println("consumeToProviderHystrix:"+"线程池名字:"+Thread.currentThread().getName());
return result+"-------"+"线程池名字:"+Thread.currentThread().getName();
}
public String consumeToProviderHystrixFallBack() {
return "返回一个友好的提示:服务降级,服务器忙,请稍后重试!";
}
启动类AppConsumer
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix//开启Hystrix
public class AppConsumer {
public static void main(String[] args) {
SpringApplication.run(AppConsumer.class, args);
}
@Bean
@LoadBalanced //@LoadBalanced就能让这个RestTemplate在请求时拥有客户端负载均衡的能力
RestTemplate restTemplate() {
return new RestTemplate();
}
}
访问:http://127.0.0.1:8005/consumeToProvider 模仿consumer调用provider中的 consumeToProvider 方法 等15秒之后才响应
访问:http://127.0.0.1:8005/consumeToProviderHystrix
之所以出现这样的情况是因为,在consumer服务调用provider服务中的consumeToProviderHystrix方法时用了Hystrix,而provider服务中的consumeToProviderHystrix休眠了15后才能返回结果,但是Hystrix默认超时时间为1秒钟,所以consumer启动了服务降级。可以将consumeToProviderHystrix的休眠时间改为1秒以内或者是在consumer的yml中加入如下配置等待15秒过后出现结果。
### hystrix禁止服务超时时间
hystrix:
command:
default:
execution:
timeout:
enabled: false
从两个线程名可以看得出Hystrix默认开启用线程池隔离来解决服务雪崩效应的!