二. 微服务的高级进阶
时间:2023-06-17 00:07:00
二. 先进的微服务
1. Ribbon API负载均衡算法
1. Ribbon API
Ribbon 是用于远程接口调用的独立组件,代码如下
@Slf4j @Service @Scope(proxyMode = ScopedProxyMode.INTERFACES) public class UserServiceImpl implements UserService {
public static String SERVIER_NAME = "micro-order"; @Autowired private RestTemplate restTemplate; @Override public List<String> queryContents() {
List<String> results = restTemplate.getForObject("http://" SERVIER_NAME "/user/queryContent", List.class); log.info("#results = {}", results); return results; } }
通过 getForObject 方法可以掉到用处 micro-order 服务的,queryUser 接口。然后在调用期间。 负载均衡,micro-order 服务对应的服务实例有几个,会根据负载均衡算法选择一个 调用。
Get 请求
getForEntity:该方法有三种重载形式:
getForEntity(String url, Class responseType)
getForEntity(String url, Class responseType, Object… uriVariables)
getForEntity(String url, Class responseType, Map
getForEntity(URI url, Class responseType)
注意此方法返回包装对象 ResponseEntity其中 T 为 responseType 传入类型,
需要使用这个包装对象才能获得返回类型。 getBody()方法
getForObject:这种方法也有三种重载形式 getForEntity 方法相同:
getForObject(String url, Class responseType)
getForObject(String url, Class responseType, Object… uriVariables)
getForObject(String url, Class responseType, Map
getForObject(URI url, Class responseType)
注:该方法返回的对象类型为 responseType 传入类型
Post 请求
post 请求和 get 请求都有ForEntity 和ForObject 除了这两种方法,参数列表也有所不同
除了方法,还有一种 postForLocation 方法,其中 postForLocation 以 post 要求提交资源并返还
回新资源的 URI
postForEntity:该方法有三种重载形式:
postForEntity(String url, Object request, Class responseType, Object… uriVariables)
postForEntity(String url, Object request, Class responseType, Map
postForEntity(URI url, Object request, Class responseType)
注意此方法返回包装对象 ResponseEntity其中 T 为 responseType 传入类型,
需要使用这个包装对象才能获得返回类型。 getBody()方法
postForObject:这种方法也有三种重载形式 postForEntity 方法相同:
posForObject(String url, Object request, Class responseType, Object… uriVariables)
postForObject(String url, Object request, Class responseType, Map
postForObject(URI url, Object request, Class responseType)
注意:此方法返回的对象类型为 responseType 传入类型
postForLocation:此方法中同样有三种重载形式,分别为:
postForLocation(String url, Object request, Object… uriVariables)
postForLocation(String url, Object request, Map
postForLocation(URI url, Object request)
注意:此方法返回的是新资源的 URI,相比 getForEntity、getForObject、postForEntity、
postForObject 方法不同的是这个方法中无需指定返回类型,因为返回类型就是 URI,通过
Object… uriVariables、Map
2. 负载均衡算法
@Bean
public IRule ribbonRule() {
//线性轮询
new RoundRobinRule();
//可以重试的轮询
new RetryRule();
//根据运行情况来计算权重
new WeightedResponseTimeRule();
//过滤掉故障实例,选择请求数最小的实例
new BestAvailableRule();
// 随机
return new RandomRule();
}
3. Ribbon 配置
application.properties 配置 (eureka-web)
############ ribbon 配置
# 关闭ribbon访问注册中心Eureka Server发现服务,但是服务依旧会注册。
#true使用eureka false不使用
ribbon.eureka.enabled=true
#指定调用的节点 localhost:8765 localhost:8764 localhost:8763
micro-order.ribbon.listOfServers=localhost:8765,localhost:8764,localhost:8763
#单位ms ,请求连接超时时间
micro-order.ribbon.ConnectTimeout=1000
#单位ms ,请求处理的超时时间
micro-order.ribbon.ReadTimeout=2000
micro-order.ribbon.OkToRetryOnAllOperations=true
#切换实例的重试次数
micro-order.ribbon.MaxAutoRetriesNextServer=2
#对当前实例的重试次数 当Eureka中可以找到服务,但是服务连不上时将会重试
micro-order.ribbon.MaxAutoRetries=2
micro-order.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
micro-order.ribbon.NFLoadBalancerPingClassName=com.netflix.loadbalancer.PingUrl
启动三个micro-order实例
java -jar netflix-eureka-order-1.0-SNAPSHOT.jar --server.port=8763
java -jar netflix-eureka-order-1.0-SNAPSHOT.jar --server.port=8764
java -jar netflix-eureka-order-1.0-SNAPSHOT.jar --server.port=8765
注意要配置src/main/resources过滤, 不然打出来的包运行没有配置, 可能会出错
<resources>
<resource>
<directory>src/main/javadirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>
<resource>
<directory>src/main/resourcesdirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
includes>
<filtering>falsefiltering>
resource>
resources>
web的代码配置
使用@RibbonClients 加载配置
/* * 这个是针对 micro-order服务的 ribbon配置 * */
@Configuration
@RibbonClients(value = {
@RibbonClient(name = "micro-order", configuration = RibbonLoadBalanceMicroOrderConfig.class)
})
public class LoadBalanceConfig {
}
这个配置类只针对 micro-order 服务,微服务系统里面有很多服务,这就可以区别化配置。
配置 configuration 配置类的时候,一定要注意,配置类不能被@ComponentScan 注解扫描到, 如果被扫描到了则该配置类就是所有服务共用的配置了。
配置类
/* * 这个类最好不要出现在启动类的@ComponentScan扫描范围 * 如果出现在@ComponentScan扫描访问,那么这个配置类就是每个服务共用的配置了 * */
@Configuration
public class RibbonLoadBalanceMicroOrderConfig {
// @RibbonClientName
private String name = "micro-order";
@Bean
@ConditionalOnClass
public IClientConfig defaultClientConfigImpl() {
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.loadProperties(name);
config.set(CommonClientConfigKey.MaxAutoRetries,2);
config.set(CommonClientConfigKey.MaxAutoRetriesNextServer,2);
config.set(CommonClientConfigKey.ConnectTimeout,2000);
config.set(CommonClientConfigKey.ReadTimeout,4000);
config.set(CommonClientConfigKey.OkToRetryOnAllOperations,true);
return config;
}
/* * 判断服务是否存活 * 不建议使用 * */
// @Bean
public IPing iPing() {
//这个实现类会去调用服务来判断服务是否存活
return new PingUrl();
}
@Bean
public IRule ribbonRule() {
//线性轮训
new RoundRobinRule();
//可以重试的轮训
new RetryRule();
//根据运行情况来计算权重
new WeightedResponseTimeRule();
//过滤掉故障实例,选择请求数最小的实例
new BestAvailableRule();
return new RandomRule();
}
}
4. Ribbon 单独使用
Ribbon 是一个独立组件,可以脱离 springcloud 使用的
@SpringBootTest(classes = EurekaWebApp.class)
@WebAppConfiguration
public class RibbonTest {
/* * ribbon作为调用客户端,可以单独使用 * */
@Test
public void test1() {
try {
//myClients 随便取值
ConfigurationManager.getConfigInstance().setProperty("myClients.ribbon.listOfServers", "localhost:8001,localhost:8002");
RestClient client = (RestClient) ClientFactory.getNamedClient("myClients");
HttpRequest request = HttpRequest.newBuilder().uri(new URI("/user/queryContent")).build();
for (int i = 0; i < 10; i++) {
HttpResponse httpResponse = client.executeWithLoadBalancer(request);
String entity = httpResponse.getEntity(String.class);
System.out.println(entity);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
需要依赖两个 jar
Maven: com.netflix.servo:servo-core:0.12.21
Maven: com.netflix.ribbon:ribbon-httpclient:2.3.0
2. Hystrix
1. 服务雪崩
雪崩是系统中的蝴蝶效应导致其发生的原因多种多样,有不合理的容量设计,或者是高并发 下某一个方法响应变慢,亦或是某台机器的资源耗尽。从源头上我们无法完全杜绝雪崩源头 的发生,但是雪崩的根本原因来源于服务之间的强依赖,所以我们可以提前评估。当整个微 服务系统中,有一个节点出现异常情况,就有可能在高并发的情况下出现雪崩,导致调用它 的上游系统出现响应延迟,响应延迟就会导致 tomcat 连接耗尽,导致该服务节点不能正常的接收到正常的情况,这就是服务雪崩行为。
2. 服务隔离
如果整个系统雪崩是由于一个接口导致的,由于这一个接口响应不及时导致问题,那么我们 就有必要对这个接口进行隔离,就是只允许这个接口最多能接受多少的并发,做了这样的限制后,该接口的主机就会空余线程出来接收其他的情况,不会被哪个坏了的接口占用满。 Hystrix 就是一个不错的服务隔离框架。
Hystrix 的starter
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
启动类开启 hystrix 功能
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker // 开启短路器 /ˈsɜːkɪt/
//@EnableHystrix // 或者通过开启hystrix也可以
public class EurekaWebApp {
public static void main(String[] args) {
SpringApplication.run(EurekaWebApp.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
代码使用
@HystrixCommand
@Override
public String queryTicket() {
return "queryTicket";
}
3. Hystrix 服务隔离策略
1、线程池隔离
THREAD 线程池隔离策略 独立线程接收请求,默认采用的就是线程池隔离
代码配置
/** * Command属性 * execution.isolation.strategy 执行的隔离策略 * THREAD 线程池隔离策略 独立线程接收请求 * SEMAPHORE 信号量隔离策略 在调用线程上执行 *
* execution.isolation.thread.timeoutInMilliseconds 设置HystrixCommand执行的超时时间,单位毫秒 * execution.timeout.enabled 是否启动超时时间,true,false * execution.isolation.semaphore.maxConcurrentRequests 隔离策略为信号量的时候,该属性来配置信号量的大小,最大并发达到信号量时,后续请求被拒绝 *
* circuitBreaker.enabled 是否开启
断路器功能 * circuitBreaker.requestVolumeThreshold 该属性设置在滚动时间窗口中,断路器的最小请求数。默认20,如果在窗口时间内请求次数19,即使19个全部失败,断路器也不会打开 * circuitBreaker.sleepWindowInMilliseconds 该属性用来设置当断路器打开之后的休眠时间,休眠时间结束后断路器为半开状态,断路器能接受请求,如果请求失败又重新回到打开状态,如果请求成功又回到关闭状态(半开状态就是下一个请求成功与否决定下一个状态是什么) * circuitBreaker.errorThresholdPercentage 该属性设置断路器打开的错误百分比。在滚动时间内,在请求数量超过circuitBreaker.requestVolumeThreshold,如果错误请求数的百分比超过这个比例,断路器就为打开状态 * circuitBreaker.forceOpen true表示强制打开断路器,拒绝所有请求 * circuitBreaker.forceClosed true表示强制进入关闭状态,接收所有请求 *
* metrics.rollingStats.timeInMilliseconds 设置滚动时间窗的长度,单位毫秒。这个时间窗口就是断路器收集信息的持续时间。断路器在收集指标信息的时会根据这个时间窗口把这个窗口拆分成多个桶,每个桶代表一段时间的指标,默认10000 * metrics.rollingStats.numBuckets 滚动时间窗统计指标信息划分的桶的数量,但是滚动时间必须能够整除这个桶的个数,要不然抛异常 *
* requestCache.enabled 是否开启请求缓存,默认为true * requestLog.enabled 是否打印日志到HystrixRequestLog中,默认true *
* '@HystrixCollapser 请求合并 * maxRequestsInBatch 设置一次请求合并批处理中允许的最大请求数 * timerDelayInMilliseconds 设置批处理过程中每个命令延迟时间 * requestCache.enabled 批处理过程中是否开启请求缓存,默认true *
* threadPoolProperties * threadPoolProperties 属性 * coreSize 执行命令线程池的最大线程数,也就是命令执行的最大并发数,默认10 */ @HystrixCommand(fallbackMethod = "queryContentsFallback", commandKey = "queryContents", groupKey = "querygroup-one", commandProperties = { @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "100"), @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"), // SEMAPHORE, THREAD @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000000000") }, threadPoolKey = "queryContentshystrixHgypool", threadPoolProperties = { // @HystrixProperty(name = "coreSize", value = "100") // 默认线程池中线程大小是10 }) @Override public List<ConsultContent> queryContents() { log.info(Thread.currentThread().getName() + "========queryContents========="); s.incrementAndGet(); return restTemplate.getForObject("http://" + SERVIER_NAME + "/user/queryContent", List.class); }
线程池隔离策略,hystrix 是会单独创建线程的, 单元测试如下
http://localhost:8766/user/queryUser
但我没不开启hystrix时候, 则是通过http-nio线程去发起请求的
hystrix测试类
@SpringBootTest(classes = EurekaWebApp.class)
@WebAppConfiguration
public class MyTest {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final Integer count = 11;
private final CountDownLatch cdl = new CountDownLatch(count);
@Autowired
UserService userService;
@Test
public void hystrixTest() {
for (int i = 0; i < count; i++) {
new Thread(() -> {
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info(Thread.currentThread().getName() + "==>" + userService.queryContents());
}).start();
cdl.countDown();
}
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
@PerfTest(invocations = 11,threads = 11)
public void hystrixTest2() {
logger.info(Thread.currentThread().getName() + "==>" + userService.queryContents());
}
/* * ribbon作为调用客户端,可以单独使用 * */
@Test
public void test1() {
try {
ConfigurationManager.getConfigInstance().setProperty("myClients.ribbon.listOfServers","localhost:8001,localhost:8002");
RestClient client = (RestClient)ClientFactory.getNamedClient("myClients");
HttpRequest request = HttpRequest.newBuilder().uri(new URI("/user/queryContent")).build();
for (int i = 0; i < 10; i++) {
HttpResponse httpResponse = client.executeWithLoadBalancer(request);
String entity = httpResponse.getEntity(String.class);
System.out.println(entity);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试代码测试
可以看到,单元测试中的线程和业务类中的线程不是一样的,单独开启线程。
2、信号量隔离
信号量隔离是采用一个全局变量来控制并发量,一个请求过来全局变量加 1,单加到跟配置 中的大小相等是就不再接受用户请求了。
代码配置
execution.isolation.semaphore.maxConcurrentRequests
这参数是用来控制信号量隔离级别的并发大小的。
测试类测试
可以看到,单元测试中的线程和业务类中的线程是一样的,没有单独开启线程。
3. Hystrix 服务降级
服务降级是对服务调用过程的出现的异常的友好封装,当出现异常时,我们不希 望直接把异常原样返回,所以当出现异常时我们需要对异常信息进行包装,抛一 个友好的信息给前端。
代码示例
@HystrixCommand(fallbackMethod = "queryContentsFallback", // 服务降级的回调方法
commandKey = "queryContents",
groupKey = "querygroup-one",
commandProperties = {
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "100"),
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"), // SEMAPHORE, THREAD
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000000000")
},
threadPoolKey = "queryContentshystrixHgypool", threadPoolProperties = {
// @HystrixProperty(name = "coreSize", value = "100")
})
@Override
public List<ConsultContent> queryContents() {
log.info(Thread.currentThread().getName() + "========queryContents=========");
s.incrementAndGet();
return restTemplate.getForObject("http://"
+ SERVIER_NAME + "/user/queryContent", List.class);
}
定义降级方法,降级方法的返回值和业务方法的方法值要一样
public List<ConsultContent> queryContentsFallback() {
f.incrementAndGet();
log.info("===============queryContentsFallback=================");
return null;
}
4. Hystrix 数据监控
Hystrix 进行服务熔断时会对调用结果进行统计,比如超时数、bad 请求数、降 级数、异常数等等都会有统计,那么统计的数据就需要有一个界面来展示, hystrix-dashboard 就是这么一个展示 hystrix 统计结果的服务。
依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
插件
<build> <finalName>micro-dashboardfinalName> <plugins> <plugin> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-maven-pluginartifactId> <version