微服务笔记

注册中心

基础知识:

  1. 访问注册中心到服务时将以前到地址变成服务名
  2. Eureka现在已停止更新
  3. Eureka 支持AP,Zookeeper与Consul支持CP
  4. 在启动类可以开启**@EnableDiscoveryClient**服务发现
  5. 在RestTemplate配置类上要加@LoadBalanced来实现负载均衡(默认轮询)

Eureka

一、客户端:主启动类上配置**@EnableEurekaClient**开启服务

  1. 依赖
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  1. 配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 80

spring:
application:
name: cloud-order-service
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7001.com:7001/eureka/ # 集群版

二、服务端: 主启动类上配置**@EnableEurekaServer**开启服务

  1. 依赖
1
2
3
4
5
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
  1. 配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
server:
port: 8001
spring:
application:
name: cloud-payment-service
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# 单机版
# defaultZone: http://localhost:7001/eureka
# 集群版
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7001.com:7001/eureka/
instance:
# 设置服务名称
instance-id: payment8001
# 是否显示服务ip
prefer-ip-address: true
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2

Zookeeper

zookeeper需要安装到linux当中,没有ui界面,java语言开发

  1. 依赖

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.4.9版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>

服务端

1
2
3
4
5
<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
  1. 配置文件
1
2
3
4
5
6
7
8
9
10
#8004表示注册到zookeeper服务器的支付服务提供者端口号
server:
port: 8004
#服务别名----注册zookeeper到注册中心名称
spring:
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: 127.0.0.1:2181 # zookeeper服务器地址

Consul

Consulr需要安装到linux当中,有ui界面,go语言开发

  1. 依赖
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
  1. 配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# consul服务端口号
server:
port: 80
spring:
application:
name: cloud-consumer-order
# consul注册中心地址
cloud:
consul:
host: localhost
port: 8500
discovery:
#hostname: 127.0.0.1
service-name: ${spring.application.name}

负载均衡(LB)

负载均衡优点:高可用

Ribbon

客户端的负载均衡工具,提供负载均衡算法和服务调用

Ribbon本地负载均衡与Nginx服务端负载均衡区别

Nginx是集中式的,客户端的所有请求都会请求nginx,由nginx转发,负载均衡由服务端实现,可以理解为挡在最前面的

Ribbon是进程内的,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

  1. 依赖 注意:新版的Eureka自带了Ribbon可以不用引入
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
一、RestTemplate的使用
  1. getForObject方法/getForEntity方法

  2. postForObject/postForEntity

Object 返回对象为响应体中数据转化成的对象,基本上可以理解为Json

Entity 返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等

二、Ribbon默认自带的负载规则

IRule:根据特定算法中从服务列表中选取一个要访问的服务

  1. 轮询
  2. 随机
  3. 重试:先轮询失败就重试
  4. 重试扩展:先轮询失败就重试,响应速度越快权重越大,越容易被选择

image-20250330000111861

三、负载规则替换

注意:这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下

所以在启动类包之外可以再建一个规则包,放配置类

1
2
3
4
5
6
7
@Configuration
public class MyselfRule {
@Bean
public IRule myRule(){
return new RandomRule();//定义为随机
}
}

主启动类添加**@RibbonClient**(name = “CLOUD-PAYMENT-SERVICE”,configuration = MyselfRule.class)

参数说明:

  1. name 要去访问那个服务
  2. configuration配置的负载规则
四、Ribbon轮询算法原理

负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标 ,每次服务重启动后rest接口计数从1开始。

List instances = discoveryClient.getInstances(“CLOUD-PAYMENT-SERVICE”);

如: List [0] instances = 127.0.0.1:8002

List [1] instances = 127.0.0.1:8001

8001+ 8002 组合成为集群,它们共计2台机器,集群总数为2, 按照轮询算法原理:

当总请求数为1时: 1 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001

当总请求数位2时: 2 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002

当总请求数位3时: 3 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001

当总请求数位4时: 4 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002

如此类推……

五、源码分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.netflix.loadbalancer;

import com.netflix.client.config.IClientConfig;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RoundRobinRule extends AbstractLoadBalancerRule {
private AtomicInteger nextServerCyclicCounter;
private static final boolean AVAILABLE_ONLY_SERVERS = true;
private static final boolean ALL_SERVERS = false;
private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

public RoundRobinRule() {
this.nextServerCyclicCounter = new AtomicInteger(0);
}

public RoundRobinRule(ILoadBalancer lb) {
this();
this.setLoadBalancer(lb);
}

public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
} else {
Server server = null;
int count = 0;

while(true) {
if (server == null && count++ < 10) {
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
if (upCount != 0 && serverCount != 0) {
int nextServerIndex = this.incrementAndGetModulo(serverCount);
server = (Server)allServers.get(nextServerIndex);
if (server == null) {
Thread.yield();
} else {
if (server.isAlive() && server.isReadyToServe()) {
return server;
}

server = null;
}
continue;
}

log.warn("No up servers available from load balancer: " + lb);
return null;
}

if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: " + lb);
}

return server;
}
}
}

private int incrementAndGetModulo(int modulo) {
int current;
int next;
do {
current = this.nextServerCyclicCounter.get();
next = (current + 1) % modulo;
} while(!this.nextServerCyclicCounter.compareAndSet(current, next));

return next;
}

public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}

public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
六、手写轮训
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

package cn.org.alan.cloud.lb;

import org.springframework.cloud.client.ServiceInstance;

import java.util.List;

/**
* 说明:
* 自定义负载均衡
*
* @Author Alan
* @Version 1.0
* @Date 2025/3/30 10:45 AM
*/
public interface LoadBalancer {
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
package cn.org.alan.cloud.lb;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
* 说明:
*
* @Author Alan
* @Version 1.0
* @Date 2025/3/30 10:47 AM
*/
@Component
public class MyLB implements LoadBalancer {
private AtomicInteger atomicInteger = new AtomicInteger(0);
public final int getAndIncrement(){
int current;
int next;
do{
current = this.atomicInteger.get();
next =current >= 2147483647 ? 0 : current + 1;

}while (!this.atomicInteger.compareAndSet(current,next));
System.out.println("next:"+next);
return next;
}
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances) {

int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
@GetMapping("/consumer/payment/lb")
public String getPaymentLB(){
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if(instances==null||instances.size()<=0){
return null;
}
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri+"/payment/lb",String.class);
}

服务调用

OpenFeign

一、使用
  1. 依赖
1
2
3
4
5
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  1. 开启OpenFeign

在主启动类上添加**@EnableFeignClients**注解

接口+注解:微服务调用接口+@FeignClient(“要访问的服务名称”)

举例:

Service

1
2
3
4
5
6
7
@Component
@FeignClient("CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {

@GetMapping(value = "/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id);
}

controller

1
2
3
4
5
6
7
8
9
10
@RestController
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;

@GetMapping(value = "/consumer/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id) {
return paymentFeignService.getPaymentById(id);
}
}
二、超时控制

OpenFeign默认等待1秒钟,超过后报错

配置超时

1
2
3
4
5
6
#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000 # 5秒
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000 # 5秒
三、日志打印功能

Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。

  1. 日志级别
1
2
3
4
NONE:默认的,不显示任何日志;
BASIC:仅记录请求方法、URL、响应状态码及执行时间;
HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;
FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。

配置步骤

  1. 配置文件开启日志
1
2
3
4
logging:
level:
# feign日志以什么级别监控哪个接口
cn.org.alan.cloud.service.PaymentFeignService: debug
  1. 配置Bean
1
2
3
4
5
6
7
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}

服务降级

Hystrix

一、重要概念

  1. 服务降级(fallback)

    服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback

    会发生降级的情况

    ​ a. 程序运行异常

    ​ b. 超时

    ​ c. 服务熔断触发服务降级

    ​ d. 线程池/信号量打满也会导致服务降级

  2. 服务熔断(break)

    类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示

    服务的降级->进而熔断->恢复调用链路

  3. 服务限流(flowlimit)

    秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行

二、使用

  1. 依赖
1
2
3
4
5
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
  1. 服务端,降级配置 @EnableCircuitBreaker与**@HystrixCommand**

    2.1 先在主启动类上加 @EnableCircuitBreaker注解激活hystrix

    2.2 然后在方法上加@HystrixCommand设置降级方法与规则

    1
    2
    3
    4
    5
    // fallbackMethod 出问题调用那个方法
    // @HystrixProperty 规则
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
    @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
    })
  2. 配置消费端**@EnableHystrix**和 @HystrixCommand

    3.1 添加配置文件

    1
    2
    3
    feign:
    hystrix:
    enabled: true

    3.2 启动类添加注解**@EnableHystrix**

    3.3 服务添加**@HystrixCommand**

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @RestController
    @Slf4j
    public class OrderHystirxController {
    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id) {
    String result = paymentHystrixService.paymentInfo_OK(id);
    return result;
    }

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
    })
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
    String result = paymentHystrixService.paymentInfo_TimeOut(id);
    return result;
    }

    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
    return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
    }

    }
  • 解决上面@HystrixCommand膨胀度问题
1
2
3
4
5
// fallbackMethod 出问题调用那个方法
// @HystrixProperty 规则
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
})

引入新标签**@DefaultProperties(defaultFallback = “”)**:添加默认defaultFallback,defaultFallback是上面HystrixCommand中fallbackMethod方法

当我引入DefaultProperties后,我们在方法上就可以标注@HystrixCommand不用写后面的一堆内容,这是通用方法,如果需要单独配置的就使用上面的@HystrixCommand(fallbackMethod=””)

  • 解决代码混乱问题

降级处理在客户端80实现完成

根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理

1
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)

总结:就是将之前的service接口去写一个类去实现,然后在openfeign注解中设置这个类为fallback,当方法出问题时就会去调用各自的实现方法

服务熔断

参数可以在HystrixCommandProperties接口中找到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"), // 是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), // 请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),// 失败率达到多少后跳闸
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
if (id < 0)
{
throw new RuntimeException("******id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();

return Thread.currentThread().getName() + "\t" + "调用成功,流水号: " + serialNumber;
}

public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " + id;
}
  • 全部配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
//========================All
@HystrixCommand(fallbackMethod = "str_fallbackMethod",
groupKey = "strGroupCommand",
commandKey = "strCommand",
threadPoolKey = "strThreadPool",

commandProperties = {
// 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
// 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 配置命令执行的超时时间
@HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
// 是否启用超时时间
@HystrixProperty(name = "execution.timeout.enabled", value = "true"),
// 执行超时的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
// 执行被取消的时候是否中断
@HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
// 允许回调方法执行的最大并发数
@HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 服务降级是否启用,是否执行回调函数
@HystrixProperty(name = "fallback.enabled", value = "true"),
// 是否启用断路器
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
// 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,
// 如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
// 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过
// circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50,
// 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
// 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,
// 会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,
// 如果成功就设置为 "关闭" 状态。
@HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
// 断路器强制打开
@HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
// 断路器强制关闭
@HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
// 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
@HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
// 该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据
// 设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。
// 比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
// 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。
@HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
// 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
@HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
// 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。
@HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
// 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,
// 就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行,
// 那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
@HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
// 该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。
@HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
// 是否开启请求缓存
@HystrixProperty(name = "requestCache.enabled", value = "true"),
// HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中
@HystrixProperty(name = "requestLog.enabled", value = "true"),
},
threadPoolProperties = {
// 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
@HystrixProperty(name = "coreSize", value = "10"),
// 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,
// 否则将使用 LinkedBlockingQueue 实现的队列。
@HystrixProperty(name = "maxQueueSize", value = "-1"),
// 该参数用来为队列设置拒绝阈值。 通过该参数, 即使队列没有达到最大值也能拒绝请求。
// 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue
// 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),
}
)
public String strConsumer() {
return "hello 2020";
}

public String str_fallbackMethod()
{
return "*****fall back str_fallbackMethod";
}

服务限流,后面alibaba的sentinel记录

  • 服务监控hystrixDashboard
  1. 依赖
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
  1. 启动类添加**@EnableHystrixDashboard**激活hystrixDashboard

服务启动类中添加, 不过要确保添加@EnableCircuitBreaker开启了Hystrix

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
* ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
* 只要在自己的项目里配置上下面的servlet就可以了
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}

网关

Gateway网关

三大核心概念:

  1. Route(路由)
  2. Predicate(断言)
  3. Filter(过滤)

Gateway工作流程:

客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。

Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。

过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。

Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,

在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

使用
  1. 依赖
1
2
3
4
5
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

小细节:

引入了gateway就不用引入web和actuator依赖,不然会报错

  1. 配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由

- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由

配置文件的两种配置方式

  1. 直接在配置文件yml中配置如上,
  2. 代码中注入RouteLocator的Bean
1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customrouteLocator(RouteLocatorBuilder routeLocatorBuilder){
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route",
r->r.path("/wiki")
.uri("https://alan.org.cn")).build();
return routes.build();
}
}

上面存在的问题:

  1. 地址写死
  2. 微服务中不可能只有一台机器,还需要负载均衡
配置动态路由

动态路由其实就是把写死的地址换成我们注册中心的服务名

更改后的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34


server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://CLOUD-PAYMENT-SERVICE #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由

- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://CLOUD-PAYMENT-SERVICE #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由

eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka

常用的Route Predicate

img

image-20250330195830549

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

predicates:
# Path Route Predicate
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
# After Route Predicate
- After=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
# Before Route Predicate
- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
# Between Route Predicate
- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai]
# Cookie Route Predicate
- Cookie=username,zzyy
# Header Route Predicate
- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
# Host Route Predicate
- Host=**.alan.org.cn
# Method Route Predicate
- Method=GET
# Query Route Predicate
- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
Filter
常用的GatewayFilter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35


server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://CLOUD-PAYMENT-SERVICE #匹配后提供服务的路由地址
filters:
- AddRequestParameter=X-Request-Id,1024 #过滤器工厂会在匹配的请求头加上一对请求头,名称为X-Request-Id值为1024
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由

- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://CLOUD-PAYMENT-SERVICE #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由

eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
自定义过滤器

主要两个接口implements GlobalFilter,Ordered

案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("Come in MyLogGateWayFilter"+new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if(uname==null){
log.info("用户名为null,非法用户");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();

}
return chain.filter(exchange);
}

@Override
public int getOrder() {
// 加载过滤器的顺序,数字越小顺序越高
return 0;
}
}