微服务虽然解决了传统单体式应用各个模块之间强耦合的缺点,但同时也引出了新问题,由于微服务各个服务之间是独立部署的,并且一般情况下一个服务往往会依赖多个其他服务,并且服务之间的调用更多的是依赖不稳定的网路,所以对于微服务架构来说,服务之间相互调用的稳定性就显的更为重要,为了保证系统能更稳定运行,我们常会对服务做一些保护措施,常用的措施有负载均衡、熔断、限流、降级、重试等。
一、 负载均衡
1.概念
1.1.对于负载均衡这个概念我们应该不陌生,负载均衡就是分发请求流量到不同的服务器,负载均衡分为两种:
1.1.1.服务端负载:服务器端负载均衡是对客户透明的,用户请求到LB服务器,真正的Application服务器是由LB服务器分发控制的,目前的实现有软件(ngnix,HA Proxy等)和硬件(F5等)。
1.1.2客户端负载:是客户端软件的一部分,客户端获知到可用的服务器列表按一定的均衡策略,分发请求。
我们这次重点看下客户端负载,客户端软负载核心:
服务发现,发现依赖服务的列表。
服务选择规则,在多个服务中如何选择一个有效服务。
服务监听,检测失效的服务,高效剔除失效服务。
1.2.由于我们本次采用Spring Cloud体系,因此负载均衡组件就采用Spring Cloud体系下的 Ribbon, Spring Cloud Ribbon 是一个客户端负载均衡的组件,主要提供客户端的软件负载均衡算法
负载均衡策略:
1.2.1.RandomRule:随机策略,从服务实例清单中随机选择一个服务实例。获得可用实例列表upList和所有实例列表allList,并通过rand.nextInt(serverCount)函数来获取一个随机数,并将该随机数作为upList的索引值来返回具体实例。
1.2.2.RoundRobinRule:轮询策略,按照线性轮询的方式依次选择每个服务实例。通过AtomicInteger nextServerCyclicCounter对象实现,每次进行实例选择时通过调用incrementAndGetModulo函数实现递增。
1.2.3.RetryRule:重试策略,具备重试机制的实例选择。内部定义了RoundRobinRule,并实现了对RoundRobinRule进行反复尝试的策略,若期间能够选择到具体的服务实例就返回,若选择不到就根据设置的尝试结束时间为阈值,当超过该阈值后就返回null。
1.2.4.WeightedResponseTimeRule:权重策略,根据实例的运行情况来计算权重,并根据权重来挑选实例,以达到更优的分配效果。通过定时任务为每个服务进行权重计算,平均响应时间小的权重区间(总平均响应时间-实例平均响应时间)就大,实力选择根据权重范围随机选择,落在哪个区间则选择哪个实例。
1.2.5.BestAvailableRule:最佳策略,通过遍历负载均衡器中维护的所有服务实例,会过滤掉故障的实例,并找出并发请求数最小的一个,选出最空闲的实例。
1.2.6.AvailabilityFilteringRule:可用过滤策略:先过滤出故障的或并发请求大于阈值一部分服务实例,然后再以线性轮询的方式从过滤后的实例清单中选出一个。
1.2.7.ZoneAvoidanceRule:区域感知策略:使用主过滤条件(区域负载器,选择最优区域)对所有实例过滤并返回过滤后的实例清单,依次使用次过滤条件列表中的过滤条件对主过滤条件的结果进行过滤,判断最小过滤数(默认1)和最小过滤百分比(默认0),满足条件则使用RoundRobinRule选择实例。
2.实操
负载均衡主要是在消费端进行配置,消费端我们采用feignClient进行调用服务
2.1. pom中添加引用
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
2.2. 启动类添加下面注解
@EnableEurekaClient
@EnableFeignClients
2.3. 消费者客户端配置
@FeignClient(value="image-server-service")
public interface IImageServerClient {
@RequestMapping(value = "/upload")
UploadImageResponseModel upload(@RequestBody UploadImageRequestModel request);
}
在使用@FeignClient注解的时候 是默认使用了ribbon进行客户端的负载均衡的,那么如果我们想要更改策略的话,可以在config里面添加配置,根据自己需求进行配置,如下:
@Bean
public IRule ribbonRule(){
//AbstractLoadBalancerRule
//AvailabilityFilteringRule
//BestAvailableRule
//ClientConfigEnabledRoundRobinRule
//PredicateBasedRule
//RandomRule
//RetryRule
//RoundRobinRule
//WeightedResponseTimeRule
//ZoneAvoidanceRule
return new RoundRobinRule();
}
2.4.0节点热插拔测试
下面服务启动两个实例,我们进行请求,发现可以根据负载策略进行请求,
然后关闭其中一个实例,系统可以正常调用,然后再重新启动关闭的实例,系统也会重新进行分配请求过去
注:默认配置下,客户端跟注册中心的心跳时间比较长,如果不修改的话,会造成短时间内负载会继续分配请求到关闭实例下面,建议可以对下面配置根据具体情况进行重新配置,保证服务异常情况下能及时从注册中心移除
注册中心配置:
# 是否开启保护模式,默认为true
eureka.server.enable-self-preservation=false
# eureka server清理无效节点的时间间隔,默认60000毫秒,即60秒
eureka.server.eviction-interval-timer-in-ms=4000
消费端配置:
#每间隔1s,向服务端发送一次心跳,证明自己依然”存活“默认为30秒
eureka.instance.lease-renewal-interval-in-seconds=1
#告诉服务端,如果我2s之内没有给你发心跳,就代表我“死”了,将我踢出掉。默认为90秒
eureka.instance.lease-expiration-duration-in-seconds=2
二、 熔断
1.概念
在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。服务雪崩效应是一种因“服务提供者”的不可用导致“服务消费者”的不可用,并将不可用逐渐放大的过程。
Spring cloud 体系下的断路器为Hystrix,下面我们就只看下Hystrix的特点:
1.1.断路器机制
断路器很好理解, 当Hystrix Command请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务. 断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN). 这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN). Hystrix的断路器就像我们家庭电路中的保险丝, 一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力.
1.2.Fallback
Fallback相当于是降级操作. 对于查询操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值. fallback方法的返回值一般是设置的默认值或者来自缓存.
2.实操
因为熔断只是作用在服务调用这一端,因此我们只需要改动消费端项目相关代码就可以。因为,Feign中已经依赖了Hystrix所以在maven配置上不用做任何改动。
2.1.开启熔断机制
在启动类上添加下面注解:
@EnableHystrix
2.2.创建回调类
创建HelloRemoteHystrix类继承与HelloRemote实现回调的方法
@Component
public class HelloRemoteHystrix implements HelloRemote{
@Override
public String hello(@RequestParam(value = "name") String name) {
return "hello" +name+", this messge send failed ";
}
}
2.3.添加fallback属性
在HelloRemote类添加指定fallback类,在服务熔断的时候返回fallback类中的内容。
@FeignClient(name= "spring-cloud-producer",fallback = HelloRemoteHystrix.class)
public interface HelloRemote {
@RequestMapping(value = "/hello")
public String hello(@RequestParam(value = "name") String name)
}
2.4. 测试
断开服务,进行调用,系统会走降级方法
然后重新启动服务,系统自动正常调用