SpringCloud

介绍

SpringCloud=分布式微服务架构的一站式解决方案, 是多种微服务架构落地技术的集合体, 俗称微服务全家桶

版本选型

https://start.spring.io/actuator/info

通过这个网站就可以查看到匹配的cloud版本

另外推荐跟视频中保持版本一致,

cloud停更

dependencyManagement和dependencies

dependencyManagement其实给父工程指定版本号, 让子工程无需指定自己的版本号, 父工程修改一处版本号, 子工程处处生效

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
<!-- 子模块继承之后,提供作用:锁定版本+子modlue不用写groupId和version  -->
<dependencyManagement>
<dependencies>
<!--spring boot 2.2.2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud Hoxton.SR1-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
</dependencyManagement>

新建模块的流程

  1. 建module
  2. 改POM
  3. 写YML
  4. 主启动
  5. 业务类

RestTemplate

使用RestTemplate访问restful接口非常的简单粗暴无脑(url, restMap, ResponseBean.class)这三个参数代表REST请求地址, 请求参数, HTTP响应转换成的对象类型

在config.ApplicationContextConfig 将RestTemplate 注入到spring容器中

1
2
3
4
5
6
7
@Configuration
public class ApplicationContextConfig {
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}

使用RestTemplate传递参数会自动将对象序列化成JSON对象

消费者

1
2
3
4
5
6
7
8
9
10

public static final String PAYMENT_URL = "http://localhost:8001";

@Resource
private RestTemplate restTemplate;

@GetMapping("/consumer/payment/create")
public CommonResult<Payment> create(Payment payment) {
return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment, CommonResult.class);
}

生产者

记得添加@RequestBody注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Resource
private PaymentService paymentService;

@PostMapping("/payment/create")
public CommonResult create(@RequestBody Payment payment) {
int result = paymentService.create(payment);
log.info("插入结果 {}", result);

if (result > 0) {
return new CommonResult(200, "插入数据库成功", result);
} else {
return new CommonResult(444, "插入数据库失败", null);
}
}

开启services(run dashboard)

在配置中随便拷贝一个boot工程就可以显示出来了

另外一种方式通过配置.idea配置文件让其显示出来

参考:

配置Eureka注册中心

创建工程, 引入依赖

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
<dependencies>
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--boot web actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>

配置application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 7001

eureka:
instance:
hostname: localhost #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

编写入口Main

1
2
3
4
5
6
7
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class, args);
}
}

值得注意的是需要添加注解@EnableEurekaServer来标识这个是eureka的服务端

最后启动项目, 访问http://localhost:7001/

引入服务的提供者到注册中心

在pom.xml中配置

1
2
3
4
5

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

application.yml中配置eureka

1
2
3
4
5
6
7
8
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka

最后标记注解@EnableEurekaClient

1
2
3
4
5
6
7
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}

配置完成之后需要重启服务, 就能看见CLOUD-PAYMENT-SERVICE成功的注册到了eureka中

配置eureka集群

引入依赖

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
<dependencies>
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--boot web actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>

修改配置application.yml配置文件

1
2
3
4
5
6
7
8
9
10
11
server:
port: 7005

eureka:
instance:
hostname: eureka7005.com #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/, http://eureka7004.com:7004/eureka/

一般来说服务注册中心不会自己注册自己

但是多个服务注册中心需要互相注册

1 注册 2 3 4 5

2 注册 1 3 4 5

3 注册 1 2 4 5

4 注册 1 2 3 5

5 注册 1 2 3 4

互相注册, 相互守望

编写程序启动类, 记得需要添加@EnableEurekaServer这个注解

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7005 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7005.class, args);
}
}

将消费者注册到注册中心集群

1
2
3
4
5
6
7
8
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka, http://eureka7004.com:7004/eureka, http://eureka7005.com:7005/eureka # 集群版

同样的提供者也是相同的方式注册到集群

将提供者注册到注册中心集群

注意server.port不要跟其他的服务重名了

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
server:
port: 8002

spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.cj.jdbc.Driver # mysql驱动包
url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false&setTimezone=UTC
username: root
password: 123456

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

mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.springcloud.entities # 所有Entity别名类所在包

消费者开启负载均衡@LoadBalanced

1
2
3
4
5
6
7
8
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}

通过http://服务名

进行调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//    public static final String PAYMENT_URL = "http://localhost:8001";
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

@Resource
private RestTemplate restTemplate;

@GetMapping("/consumer/payment/create")
public CommonResult<Payment> create(Payment payment) {
return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment, CommonResult.class);
}

@GetMapping("/consumer/get/{id}")
public CommonResult getPayment(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id, CommonResult.class);
}

actuator微服务信息完善

在application.yml中

instance:
instance-id: payment8003
prefer-ip-address: true #访问路径可以显示IP地址

这段信息就能显示主机名, 并且点击之后就可以显示对应的IP地址

1
2
3
4
5
6
7
8
9
10
11
12
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka, http://eureka7004.com:7004/eureka, http://eureka7005.com:7005/eureka # 集群版
instance:
instance-id: payment8003
prefer-ip-address: true #访问路径可以显示IP地址

配置服务发现Discovery

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Resource
private DiscoveryClient discoveryClient;

@GetMapping("/payment/discovery")
public Object discovery() {
List<String> services = discoveryClient.getServices();
for (String service : services) {
log.info("service: {}", service);
}

List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
log.info("instance: " + instance.getServiceId() + "\t" + instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri());
}

return this.discoveryClient;
}

接着在入口函数添加注解@EnableDiscoveryClient

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class, args);
}
}

测试结果

Eureka自我保护

当出现这个段英文时, EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

就说明eureka进入了自我保护

可能是因为网络状态正常接收某个微服务的心跳

在某时刻某一个微服务不可用了, Eureka不会立刻清理, 依旧会对该微服务的信息进行保存

关闭自我保护

配置了1秒发送心跳, 超过2秒没有收到心跳就剔除这个微服务

THE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.

提供者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka, http://eureka7003.com:7003/eureka, http://eureka7004.com:7004/eureka, http://eureka7005.com:7005/eureka # 集群版
instance:
instance-id: payment8001
prefer-ip-address: true #访问路径可以显示IP地址
#心跳检测与续约时间
#开发时设置小些,保证服务关闭后注册中心能即使剔除服务
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2

注册中心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/, http://eureka7004.com:7004/eureka/, http://eureka7005.com:7005/eureka/
server:
#关闭自我保护机制,保证不可用服务被及时踢除
enable-self-preservation: false
eviction-interval-timer-in-ms: 2000

替换成zookeeper为注册中心

在centos7中启动zookeeper集群

创建一个新的微服务模块

中间排除了zookeeper的某个版本, 其实也可以不用, 因为我排不排除都能正常运行

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
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper3.5.3-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.5.7版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

配置application.yml

1
2
3
4
5
6
7
8
9
10
11
12
#8004表示注册到zookeeper服务器的支付服务提供者端口号
server:
port: 8004

#服务别名----注册zookeeper到注册中心名称
spring:
application:
name: cloud-provider-payment8004
cloud:
zookeeper:
connect-string: hadoop202:2181,hadoop203:2181,hadoop204:2181

编写入口

1
2
3
4
5
6
7
8
9
10
11
12
@Slf4j
@RestController
@EnableDiscoveryClient
public class PaymentController {
@Value("${server.port}")
private String serverPort;

@RequestMapping("/payment/zk")
public String paymentzk() {
return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();
}
}

测试代码

成功配置好了zookeeper注册中心

zookeeper服务节点

zookeeper服务节点是临时节点

当把cloud-provider-payment8004关闭之后, zookeeper就会将cloud-provider-payment8004这个节点给删除, 当cloud-provider-payment8004重新启动之后, zookeeper就会再次把cloud-provider-payment8004注册上

编写相对应的消费者

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
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper3.5.3-->
<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.5.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
1
2
3
4
5
6
7
8
@Configuration
public class ApplicationContextBean {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
@Slf4j
public class OrderZKController {

public static final String INVOKE_URL = "http://cloud-provider-payment8004";

@Resource
private RestTemplate restTemplate;

@GetMapping("/consumer/payment/zk")
public String getPaymentInfo() {
String result = restTemplate.getForObject(INVOKE_URL + "/payment/zk", String.class);
log.info("消费者调用支付服务(zookeeper) => result: {}", result);
return result;
}
}
1
2
3
4
5
6
7
@SpringBootApplication
@EnableDiscoveryClient
public class OrderZK80 {
public static void main(String[] args) {
SpringApplication.run(OrderZK80.class, args);
}
}
1
2
3
4
5
6
7
8
9
10
11
#8004表示注册到zookeeper服务器的支付服务提供者端口号
server:
port: 80

#服务别名----注册zookeeper到注册中心名称
spring:
application:
name: cloud-consumer-order
cloud:
zookeeper:
connect-string: hadoop202:2181,hadoop203:2181,hadoop204:2181

代码测试

启动了一个提供者和消费者

都能正常请求

Consul

下载地址

https://developer.hashicorp.com/consul/downloads?ajs_aid=cc9039da-f9af-4a8f-86e9-a9e6e2942e03&product_intent=consul

查看consul版本

输入./consul agent -dev 启动开发模式

启动完之后可以访问http://localhost:8500/

配置Consul提供者

pom.xml

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
<dependencies>

<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
###consul服务端口号
server:
port: 8006

spring:
application:
name: consul-provider-payment
####consul注册中心地址
cloud:
consul:
host: localhost
port: 8500
discovery:
#hostname: 127.0.0.1
service-name: ${spring.application.name}

Controller

1
2
3
4
5
6
7
8
9
10
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;

@RequestMapping("/payment/consul")
public String paymentzk() {
return "springcloud with consul: "+serverPort+"\t"+ UUID.randomUUID().toString();
}
}

代码测试

配置Consul消费者

pom.xml

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
<dependencies>

<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
###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}

config/ApplicationConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.atguigu.springcloud.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
public class OrderConsulController {
public static final String INVOKE_URL = "http://consul-provider-payment"; //consul-provider-payment

@Autowired
private RestTemplate restTemplate;

@GetMapping(value = "/consumer/payment/consul")
public String paymentInfo()
{
String result = restTemplate.getForObject(INVOKE_URL+"/payment/consul", String.class);
System.out.println("消费者调用支付服务(consule)--->result:" + result);
return result;
}
}

代码测试

三个注册中心异同点

CAP

C: Consistency(强一致性)

A: Availablility (可用性)

P: Partition tolerance(分区容错性)

CAP理论关注粒度是数据, 而不是整体系统设计的策略

AP(Eureka)

CP(Zookeeper/Consul)

Ribbon负载均衡

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

当引入了eureka的依赖之后会自动的添加上ribbon的依赖, 所以自己手动加不加依赖都可以

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

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

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

配置ribbon, 其实就是跟之前一样, 只用添加好@LoadBalanced注解, Controller就可以直接调用

1
2
3
4
5
6
7
8
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
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
@RestController
@Slf4j
public class OrderController {

// public static final String PAYMENT_URL = "http://localhost:8001";
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

@Resource
private RestTemplate restTemplate;

@GetMapping("/consumer/payment/create")
public CommonResult<Payment> create(Payment payment) {
return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment, CommonResult.class);
}

@GetMapping("/consumer/get/{id}")
public CommonResult getPayment(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id, CommonResult.class);
}

@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id) {
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
if (entity.getStatusCode().is2xxSuccessful()) {
log.info("Headers: {}, status: {}", entity.getHeaders(), entity.getStatusCode());
return entity.getBody();
} else {
return new CommonResult<>(444, "操作失败");
}
}
}

一些特定的算法

配置随机的规则

新建package, 注意不要建在Application的当前包及其子包下了

编写如下配置

1
2
3
4
5
6
7
@Configuration
public class MySelfRule {
@Bean
public IRule myRule() {
return new RandomRule();
}
}

启动类中添加注解@RibbonClient

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name="CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}

配置完成之后规则就变成了随机的了

负载均衡算法原理

负载均衡算法: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

如此类推……

使用OpenFeign

pom.xml

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
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
1
2
3
4
5
6
7
8
server:
port: 80

eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

编写一个接口

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

@GetMapping("/payment/get/{id}")
CommonResult getPayment(@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<Payment> getPaymentById(@PathVariable("id") Long id) {
return paymentFeignService.getPayment(id);
}
}

代码测试

修改超时时间

编写提供者的超时方法

1
2
3
4
5
6
7
8
9
10
@GetMapping("/payment/feign/timeout")
public String paymentFeignTimeout() {
log.info("serverPort: {}", serverPort);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}

消费者添加超时方法

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

@GetMapping("/payment/get/{id}")
CommonResult getPayment(@PathVariable("id") Long id);

@GetMapping("/payment/feign/timeout")
String paymentFeignTimeout();
}
1
2
3
4
@GetMapping("/consumer/payment/feign/timeout")
public String paymentFeignTimeout() {
return paymentFeignService.paymentFeignTimeout();
}

这样测试会造成OpenFeign的超时, 默认超时时长是1秒

在application.yml中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 80

eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000

这样就可以增大超时的时长了

OpenFeign开启日志

添加config[配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.atguigu.springcloud.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignConfig
{
@Bean
Logger.Level feignLoggerLevel()
{
return Logger.Level.FULL;
}
}

然后再application.yml中进行配置

注意这个路径应该是OpenFeign的接口的路径

com.atguigu.springcloud.services.PaymentFeignService: debug

1
2
3
4
logging:
level:
# feign日志以什么级别监控哪个接口
com.atguigu.springcloud.services.PaymentFeignService: debug

搭建Hystrix模块

pom

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
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 8001

spring:
application:
name: cloud-provider-hystrix-payment

eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
defaultZone: http://eureka7001.com:7001/eureka

service

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
package com.atguigu.springcloud.service;

import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class PaymentService {
/**
* 正常访问,一切OK
* @param id
* @return
*/
public String paymentInfo_OK(Integer id)
{
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O";
}

/**
* 超时访问,演示降级
* @param id
* @return
*/
public String paymentInfo_TimeOut(Integer id)
{
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,id: "+id+"\t"+"O(∩_∩)O,耗费3秒";
}
}

Controller

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
package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class PaymentController {
@Autowired
private PaymentService paymentService;

@Value("${server.port}")
public String serverPort;

@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfo_OK(id);
log.info("result: {}", result);
return result;
}

@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) throws InterruptedException
{
String result = paymentService.paymentInfo_TimeOut(id);
log.info("result: {}", result);
return result;
}
}

main

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}

配置Hystrix的服务降级

service中使用@HystrixCommand, fallbackMethod 指定失败之后的调用的方法

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
@Service
public class PaymentService {
/**
* 正常访问,一切OK
*
* @param id
* @return
*/
public String paymentInfo_OK(Integer id) {
return "线程池:" + Thread.currentThread().getName() + "paymentInfo_OK,id: " + id + "\t" + "O(∩_∩)O";
}

/**
* 超时访问,演示降级
*
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
public String paymentInfo_TimeOut(Integer id) {
int a = 1/0;
// try {
// TimeUnit.SECONDS.sleep(3);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
return "线程池:" + Thread.currentThread().getName() + "paymentInfo_TimeOut,id: " + id + "\t" + "O(∩_∩)O,耗费3秒";
}

public String paymentInfo_TimeOutHandler(Integer id) {
return "/(ㄒoㄒ)/调用支付接口超时或异常:\t"+ "\t当前线程池名字" + Thread.currentThread().getName();
}

}

入口标记@EnableCircuitBreaker注解

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
}

当调用的业务出现异常, 超时等就会调用兜底的方法paymentInfo_TimeOutHandler

配置Hystrix消费者服务降级

pom

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
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

yml

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 80

eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/

feign:
hystrix:
enabled: true

Controller

1
2
3
4
5
6
7
8
9
10
11
12
@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)
{
return paymentHystrixService.paymentInfo_TimeOut(id);
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id)
{
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}

service

1
2
3
4
5
6
7
8
9
10
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService
{
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);

@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}

入口函数,记得 需要标记这个@EnableHystrix这个注解

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class, args);
}
}

代码测试

配置全局服务降级

  1. Controller上面添加@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")

// @HystrixCommand(fallbackMethod = “paymentTimeOutFallbackMethod”,commandProperties = {
// @HystrixProperty(name=”execution.isolation.thread.timeoutInMilliseconds”,value=”1500”)
// })

修改成@HystrixCommand

  1. 编写全局处理方法
1
2
3
public String payment_Global_FallbackMethod() {
return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
}

完整代码:

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
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
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")
// })
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
{
return paymentHystrixService.paymentInfo_TimeOut(id);
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id)
{
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}

public String payment_Global_FallbackMethod() {
return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
}
}

代码测试:

将业务分开

yml配置

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 80

eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/

feign:
hystrix:
enabled: true

让PaymentFallbackService 实现PaymentHystrixService 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.atguigu.springcloud.service;

import org.springframework.stereotype.Component;

@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "服务调用失败,提示来自:cloud-consumer-feign-order80 PaymentFallbackService paymentInfo_OK";
}

@Override
public String paymentInfo_TimeOut(Integer id) {
return "服务调用失败,提示来自:cloud-consumer-feign-order80 PaymentFallbackService paymentInfo_TimeOut";
}
}

在PaymentHystrixService接口上标记注解FeignClient注解, 并且需要指定fallback 是哪一个类

1
2
3
4
5
6
7
8
9
10
11

@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = PaymentFallbackService.class)
public interface PaymentHystrixService
{
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);

@GetMapping("/payment/hystrix/timeout/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}

代码测试

服务熔断

service

这个注解的意思是: 在10秒内, 发送了10个请求, 出现60%的错误, 就开启断路器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//=========服务熔断
@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;
}

controller

1
2
3
4
5
6
7
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id)
{
String result = paymentService.paymentCircuitBreaker(id);
log.info("****result: "+result);
return result;
}

代码测试

总结:

Hystrix图形化Dashboard搭建

pom

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
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

yml

1
2
server:
port: 9001

入口函数,记得添加@EnableHystrixDashboard

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;

@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class, args);
}
}

配置完成之后访问http://localhost:9001/hystrix

凡是要监控一定要添加actuator

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

9001监控8001

填写监控地址http://localhost:8001/hystrix.stream

1:Delay:该参数用来控制服务器上轮询监控信息的延迟时间,默认为2000毫秒,可以通过配置该属性来降低客户端的网络和CPU消耗。

2:Title:该参数对应了头部标题Hystrix Stream之后的内容,默认会使用具体监控实例的URL,可以通过配置该信息来展示更合适的标题。

Open表示断路器开启了

实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康度从绿色<黄色<橙色<红色递减。

该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实例中快速的发现故障实例和高压力实例。

Gateway的配置

pom

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
<dependencies>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--一般基础配置类-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

yml

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
server:
port: 9527

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/**#断言,路径相匹配的进行路由

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

入口函数

1
2
3
4
5
6
7
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GateWayMain9527.class, args);
}
}

另外第一个微服务的controller

1
2
3
4
@GetMapping("/payment/get/{id}")
public CommonResult getPayment(@PathVariable("id") Long id) {}
@GetMapping(value = "/payment/lb")
public String getPaymentLB(){}

代码测试, 使用8001, 9527两个端口调用都可以

网关配置路由

9527端口的微服务配置config

1
2
3
4
5
6
7
8
9
10
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("path_router_atguigu", r -> r.path("/video/BV18E411x7eT").uri("https://www.bilibili.com/video/BV18E411x7eT"))
.route("anime", r->r.path("/anime").uri("https://www.bilibili.com/anime/")).build();
return routes.build();
}
}

访问http://localhost:9527/anime被转发到了B站番剧

虽然没有图片…

动态路由

修改9527服务的yml

开启动态路由

1
2
3
4
5
6
7
8
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由

把原先的uri注释掉, 改成lb://服务名

uri的协议为lb, 表示启用Gateway的负载均衡功能

lb://serviceName 是springcloud Gateway在微服务中自动为我们创建的负载均衡uri

完整代码如下:

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
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

配置好之后, 测试9527就负载均衡了, 请求到了8001和8002

Gateway常用的Predicate

编写一个测试类获取当前时区的时间

1
2
3
4
5
6
7
public class ZonedDateTimeDemo {
@Test
public void testTime() {
ZonedDateTime now = ZonedDateTime.now();
System.out.println(now);
}
}

2023-04-30T11:01:33.030+08:00[Asia/Shanghai]

然后配置yml

predicates下的After, 表示在这个时间点之后才能运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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/** # 断言,路径相匹配的进行路由
- After=2023-04-30T12:01:33.030+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由

时间到了测试

时间还没到测试

断言cookie

1
2
3
4
5
6
7
- id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
# - After=2023-04-30T11:01:33.030+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
- Cookie=username, roudoukouya

断言请求头

1
2
3
4
5
6
7
8
- id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
# - After=2023-04-30T11:01:33.030+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
# - Cookie=username, roudoukouya
- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式

断言主机名Host

1
2
3
4
5
6
7
8
9
- id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
# - After=2023-04-30T11:01:33.030+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
# - Cookie=username, roudoukouya
# - Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
- Host=**.atguigu.com # 断言主机名

断言方法类型

1
2
3
4
5
6
7
8
9
10
- id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
# - After=2023-04-30T11:01:33.030+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
# - Cookie=username, roudoukouya
# - Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
# - Host=**.atguigu.com # 断言主机名
- Method=GET # 断言方法类型

断言参数

1
2
3
4
5
6
7
8
9
10
11
- id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
# - After=2023-04-30T11:01:33.030+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
# - Cookie=username, roudoukouya
# - Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
# - Host=**.atguigu.com # 断言主机名
# - Method=GET # 断言方法类型
- Query=username, \d+ # 要有参数名username并且值还要是整数才能路由

配置自定义过滤器

编写filter过滤器MyLogGateWayFilter

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
package com.atguigu.springcloud.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("指定了自定义的全局过滤器 MyLogGateWayFilter");
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;
}
}

请求中就必须带上uname这个参数, 否则就会被406拒绝

配置中心Config 服务端

创建cloud-config-center-3344项目

引入pom

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
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<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-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

需要提前创建一个gitee仓库, 为啥不用github, 因为我的代理垃圾呜呜呜

代理配置https://blog.csdn.net/qq_41731201/article/details/123527717

yml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server:
port: 3344

spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
uri: https://gitee.com/xialonggui/springcloud-config.git
# uri: https://gitee.com/java-mohan/springcloud-config.git
# ####搜索目录
search-paths:
- springcloud-config
###读取分支
label: master

#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7003.com:7003/eureka/, http://eureka7004.com:7004/eureka/, http://eureka7005.com:7005/eureka/

启动类

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableConfigServer

public class ConfigCenterMain3344 {
public static void main(String[] args) {
SpringApplication.run(ConfigCenterMain3344.class, args);
}
}

代码测试

这几种测试都可以

localhost:3344/master/config-dev.yml

localhost:3344/config-dev.yml

localhost:3344/config/dev/master

Config客户端的配置

创建*cloud-config-client-3355项目*

pom

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

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

注意这里需要创建的是bootstrap.yml

applicaiton.yml是用户级的资源配置项

bootstrap.yml是系统级的,优先级更加高

Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的Application Context的父上下文。初始化的时候,Bootstrap Context负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment

Bootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。 Bootstrap contextApplication Context有着不同的约定,所以新增了一个bootstrap.yml文件,保证Bootstrap ContextApplication Context配置的分离。

要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的,

因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server:
port: 3355

spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址k

#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.atguigu.springcloud.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ConfigClientController
{
@Value("${config.info}")
private String configInfo;

@GetMapping("/configInfo")
public String getConfigInfo() {
return configInfo;
}
}

入口函数

1
2
3
4
5
6
7
@EnableEurekaClient
@SpringBootApplication
public class ConfigClientMain3355 {
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain3355.class, args);
}
}

代码测试

请求3355端口同样能获取Info的信息

当gitee的配置中心的内容发生了变化, 3344可以请求可以立马反应过来,但是3355不能

需要重启3355才可以, 这样每次修改了配置,每次都重启就显得非常蛋疼, 所以就出现了动态刷新配置

动态刷新之手动版

确保pom中包含

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

yml暴露监控端点

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
server:
port: 3355

spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址k

#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka

# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"

controller添加注解@RefreshScope

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
@RefreshScope
public class ConfigClientController
{
@Value("${config.info}")
private String configInfo;

@GetMapping("/configInfo")
public String getConfigInfo() {
return configInfo;
}
}

配置完成之后重启了3355, 发现3355的请求获取info仍然没有缓过来, 3344已经缓过来了

我们需要给3355发送一个post请求提醒一下3355

“嘿, 哥们, 别睡着了”

POST localhost:3355/actuator/refresh

之后3355就缓过来能获取到最新的Info信息了

如果微服务的数量达到了很多,这样做还是很麻烦, 由此就衍生出来了SpringCloud BUS总线Bus

Bus动态刷新全局广播配置实现

首先需要配置RabbitMQ的环境

对每一个*cloud-config-center-3344 ,* cloud-config-client-3355 , cloud-config-client-3366

添加pom

1
2
3
4
5
<!--添加消息总线RabbitMQ支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

3344yml

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
server:
port: 3344

spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
uri: https://gitee.com/xialonggui/springcloud-config.git
# uri: https://gitee.com/java-mohan/springcloud-config.git
# ####搜索目录
search-paths:
- springcloud-config
###读取分支
label: master
#rabbitmq相关配置
rabbitmq:
host: hadoop202
port: 5672
username: admin
password: 123

#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7003.com:7003/eureka/, http://eureka7004.com:7004/eureka/, http://eureka7005.com:7005/eureka/

##rabbitmq相关配置,暴露bus刷新配置的端点
management:
endpoints: #暴露bus刷新配置的端点
web:
exposure:
include: 'bus-refresh'

3355yml

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
server:
port: 3355

spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址k
#rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
rabbitmq:
host: hadoop202
port: 5672
username: admin
password: 123

#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka

# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"

3366yml

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
server:
port: 3366

spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名称
profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心地址
#rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
rabbitmq:
host: hadoop202
port: 5672
username: admin
password: 123

#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka

# 暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"

通知3344去踹醒其他微服务 POST localhost:3344/actuator/bus-refresh

localhost:3344/config-dev.yml

localhost:3355/configInfo

localhost:3366/configInfo

动态刷新顶点通知

只通知3355, 不通知3366

指定具体某一个实例生效而不是全部

公式: http://localhost:配置中心端口号/actuator/bus-refresh/{destination}

/bus/refresh请求不再发送到具体的服务实例上,而是发给config server并通过destination参数类指定需要更新配置的服务或实例

踹醒3355, 不要惊动3366

POST localhost:3344/actuator/bus-refresh/config-client:3355

此时3355更新了, 但是3366没有更新

以上的配置执行的代码会在让rabbitmq的topic生成一个总线

消息驱动生产者

创建模块*cloud-stream-rabbitmq-provider8801*

pom

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
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<!--基础配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

yml

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
server:
port: 8801

spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: hadoop202
port: 5672
username: admin
password: 123
bindings: # 服务的整合处理
output: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置

eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: send-8801.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址

service

1
2
3
4
5
package com.atguigu.springcloud.service;

public interface IMessageProvider {
public String send();
}

接口实现,类名上面不要加@Service注解, 这并不是像之前一样写的业务类

而是添加@EnableBinding

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
package com.atguigu.springcloud.service.impl;

import com.atguigu.springcloud.service.IMessageProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.MessageChannel;

import javax.annotation.Resource;
import java.util.UUID;

@Slf4j
@EnableBinding(Source.class) // 可以理解为是一个消息的发送管道的定义
public class MessageProviderImpl implements IMessageProvider {

@Resource
private MessageChannel output; // 消息的发送管道

@Override
public String send() {
String serial = UUID.randomUUID().toString();
this.output.send(MessageBuilder.withPayload(serial).build()); // 创建并发送消息
log.info("serial: {}", serial);
return null;
}
}

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.atguigu.springcloud.controller;

import com.atguigu.springcloud.service.IMessageProvider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class SendMessageController {
@Resource
private IMessageProvider messageProvider;

@GetMapping("/sendMessage")
public String sendMessage() {
return messageProvider.send();
}
}

入口函数

1
2
3
4
5
6
7
8
9
10
11
package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StreamMQMain8801 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8801.class, args);
}
}

程序运行之后exchanges会出现studyExchange

生产者: localhost:8801/sendMessage

消息驱动消费者

创建模块*cloud-stream-rabbitmq-consumer8802*

pom

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
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--基础配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

yml

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
server:
port: 8802

spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: hadoop202
port: 5672
username: admin
password: 123
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置

eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: receive-8802.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.atguigu.springcloud.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListener {
@Value("server.port")
private String serverPort;

@StreamListener(Sink.INPUT) // 监听接收消息
public void input(Message<String> message) {
log.info("消费者1号 message: {}", message.getPayload());
log.info("消费者1号 port: {}", serverPort);
}
}

入口函数

1
2
3
4
5
6
7
8
9
10
11
package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StreamMQMain8802 {
public static void main(String[] args) {
SpringApplication.run(StreamMQMain8802.class, args);
}
}

GET localhost:8801/sendMessage

8801发送消息, 8802就能接收消息

分组消费

仿造*cloud-stream-rabbitmq-consumer8802相同的模块cloud-stream-rabbitmq-consumer8803*

可以看见当8801发送了一条消息, 8802和8803都会接收到这个消息, 如果是在订单模块的话, 同样的订单被处理了两次,这显然不是我们想要的

8802和8803的队列名称不一样, 需要把这两个归为同一个组

给8802和8803的yml添加group配置, group的值需要保持一致

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: 8802

spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: hadoop202
port: 5672
username: admin
password: 123
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
group: atguiguA

eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: receive-8802.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
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: 8803

spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: hadoop202
port: 5672
username: admin
password: 123
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称,在分析具体源代码的时候会进行说明
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
group: atguiguA

eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: receive-8803.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址

代码测试

8801发送了两次请求, 8802和8803都分别接收到了一次

持久化

停止8802和8803服务

然后去除掉8802的group: atguiguA

保留8803的group: atguiguA

8801发送了4条消息, 先启动8802, 8802没有接收消息, 然后启动8803, 全部在8803接收到了

总结: 添加了group就自动支持了持久化

Sleuth之zipkin搭建安装

https://zipkin.io/pages/quickstart

直接点击latest release点击下载zipkin

下载完成之后直接在控制台输入

java -jar zipkin-server-2.24.1-exec.jar

然后在浏览器中访问http://127.0.0.1:9411/zipkin/

这样就搭建完成了

Nacos的下载

https://github.com/alibaba/nacos

Nacos = Eureka + Config + Bus

下载完成之后解压, 直接使用命令bin/startup.cmd就可以运行

运行完成之后浏览器输入http://localhost:8848/nacos进入控制台

用户名和密码都是nacos

将服务注册到nacos

新建*cloudalibaba-provider-payment9001*模块

父POM

1
2
3
4
5
6
7
8
9
10
11
12
<dependencyManagement>
<dependencies>
<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

子pom

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
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server:
port: 9001

spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址

management:
endpoints:
web:
exposure:
include: '*'

controller

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class PaymentController {
@Value("server.port")
private String serverPort;

@GetMapping(value = "/payment/nacos/{id}")
public String getPayment(@PathVariable("id") Integer id)
{
return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
}
}

入口函数

1
2
3
4
5
6
7
@EnableDiscoveryClient
@SpringBootApplication
public class PaymentMain9001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9001.class, args);
}
}

代码测试GET localhost:9001/payment/nacos/1

服务列表中出现了刚刚注册的微服务

Nacos的负载均衡

与之对应的,创建出来*cloudalibaba-provider-payment9002*模块

再创建一个消费者cloudalibaba-consumer-nacos-order83

pom

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
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- SpringBoot整合Web组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 83

spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848

#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider

config

1
2
3
4
5
6
7
8
@Configuration
public class ApplicationContextBean {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@RestController
public class OrderNacosController {

@Resource
private RestTemplate restTemplate;

@Value("service-url.nacos-user-service")
private String serverURL;

@GetMapping(value = "consumer/payment/nacos/{id}")
public String getPayment(@PathVariable("id") Integer id)
{
return restTemplate.getForObject(serverURL + "/payment/nacos/" + id, String.class);
}
}

入口函数

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableDiscoveryClient
public class OrderNacosMain83 {
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain83.class, args);
}
}

nacos中的依赖自带了ribbon, 所以能够支持负载均衡

代码测试

GET localhost:83/consumer/payment/nacos/1

一次9001,一次9002, 负载均衡实现

Nacos的CP和AP的切换

Nacos默认是AP, 可以使用如下请求切换成CP

curl -X PUT ‘$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP’

Nacos的配置中心

创建模块*cloudalibaba-config-nacos-client3377*

pom

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
<dependencies>
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--nacos-discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--web + actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

bootstrap.yml

注意这个file-extension: yml#指定yaml格式的配置

这个yml跟nacos-config-client-dev.yml这个后缀名要保持一致

要么都叫yaml, 要么都叫yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# nacos配置
server:
port: 3377

spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848#Nacos服务注册中心地址
config:
server-addr: localhost:8848#Nacos作为配置中心地址
file-extension: yml#指定yaml格式的配置

# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
#nacos-config-client-dev.yml

application.yml

1
2
3
spring:
profiles:
active: dev#表示开发环境

controller

1
2
3
4
5
6
7
8
9
10
11
12
// 开启自动刷新
@RefreshScope //在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。
@RestController
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;

@GetMapping("/config/info")
public String getConfigInfo() {
return configInfo;
}
}

main

1
2
3
4
5
6
7
@EnableDiscoveryClient
@SpringBootApplication
public class NacosConfigClientMain3377 {
public static void main(String[] args) {
SpringApplication.run(NacosConfigClientMain3377.class, args);
}
}

创建好模块运行

在nacos的配置列表创建配置

1
2
config:
info: 富哥vivo50看看实力?

代码测试

GET localhost:3377/config/info

以上这些代码是支持动态刷新的, 但是记得Controller头上要添加@RefreshScope注解才会开启自动刷新

Nacos的DataID配置

创建第二个配置文件nacos-config-client-test.yml

配置了什么就是什么环境

1
2
config:
info: 看看你的

将3377的application.yml配置文件换成test

1
2
3
4
spring:
profiles:
# active: dev #表示开发环境
active: test#表示测试环境

postman测试, 成功读取到了test环境的info

Nacos的group分组方案

创建两个group

nacos-config-client-info.yml TEST_GROUP

1
2
config:
info: nacos-config-client-info.yml TEST_GROUP

nacos-config-client-info.yml DEV_GROUP

1
2
config:
info: nacos-config-client-info.yml DEV_GROUP

然后修改application.yml和bootstrap.yml

bootstrap.yml添加group

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# nacos配置
server:
port: 3377

spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
config:
server-addr: localhost:8848 #Nacos作为配置中心地址
file-extension: yml #指定yaml格式的配置
group: TEST_GROUP

# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
#nacos-config-client-dev.yml

application.yml切换active

1
2
3
4
5
spring:
profiles:
# active: dev # 表示开发环境
# active: test # 表示测试环境
active: info # 表示测试环境

当处于DEV组的时候代码测试

当处于TEST组的时候代码测试

Nacos的命名空间namespace

创建一个命名空间dev, 然后创建几个不同环境的配置文件

bootstrap.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# nacos配置
server:
port: 3377

spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
config:
server-addr: localhost:8848 #Nacos作为配置中心地址
file-extension: yml #指定yaml格式的配置
group: TEST_GROUP
namespace: 80f0fdbc-e41a-4b42-8f0b-53b460d261d1

# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
#nacos-config-client-dev.yml

application.yml

1
2
3
4
5
spring:
profiles:
active: dev # 表示开发环境
# active: test # 表示测试环境
# active: info # 表示测试环境

代码测试:

Nacos持久化切换配置

之前用的版本是1.1.4, 貌似不是很适配mysql8,所以我换了个版本

数据库中运行nacos-mysql.sql文件, 数据库创建名称是nacos

然后解开配置文件中的相关配置,让nacos切换成mysql数据库

由于1.4.5的启动方式是默认用的集群模式启动的

此时我们为了简单,可以直接先用单机模式(单机启动)启动一下

1
./startup.cmd -m standalone

启动完成登录进入,随便创建一个配置文件, 到数据库查看, 发现创建出来文件成功的保存到了数据库中

此时的持久化操作配置就完成了

搭建Nacos集群环境

我的linux用的是mysql5.7版本,所以这懒得切换了直接用的是1.1.4版本

https://github.com/alibaba/nacos/releases/tag

下载nacos

1
$ wget https://github.com/alibaba/nacos/releases/download/2.2.2/nacos-server-1.1.4.tar.gz

解压

1
2
$ tar -zxvf nacos-server-1.1.4.tar.gz -C /opt/module/
$ cd /opt/module/nacos/conf

进入mysql,导入nacos的配置文件

1
2
3
4
5
6
7
8
9
10
$ mysql -uroot -p
mysql> create database nacos;
mysql> use nacos;
# 这个命令要在/opt/module/nacos/conf目录下进入的mysql才能直接执行
# source /opt/module/nacos/conf/nacos-mysql.sql 否则要写全路径
mysql> source nacos-mysql.sql
# 查看导入的SQL文件
mysql> show tables;
# 退出mysql
mysql> exit

修改application.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
$ vim application.properties

#*************** Config Module Related Configurations ***************#
### If use MySQL as datasource:
spring.datasource.platform=mysql

### Count of DB:
db.num=1

### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=123456

配置cluster

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ cp cluster.conf.example cluster.conf
$ hostname -i
192.168.1.202
# 记下这个IP, 配置这个IP
$ vim cluster.conf

#it is ip
#example
#192.168.16.101:8847
#192.168.16.102
#192.168.16.103
192.168.1.202:3333
192.168.1.202:4444
192.168.1.202:5555

修改启动脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
57 while getopts ":m:f:s:p:" opt
58 do
59 case $opt in
60 m)
61 MODE=$OPTARG;;
62 f)
63 FUNCTION_MODE=$OPTARG;;
64 s)
65 SERVER=$OPTARG;;
66 p)
67 PORT=$OPTARG;;
68 ?)
69 echo "Unknown parameter"
70 exit 1;;
71 esac
72 done
...
134 nohup $JAVA -Dserver.port=${PORT} ${JAVA_OPT} nacos.nacos >> ${BASE_DIR}/logs/start.out 2>&1 &

启动集群

1
2
3
4
5
6
$ /opt/module/nacos/bin/startup.sh -p 3333
$ /opt/module/nacos/bin/startup.sh -p 4444
$ /opt/module/nacos/bin/startup.sh -p 5555
$ ps -ef | grep nacos | grep -v grep | wc -l
3
# 此时说明启动了三台nacos

配置nginx

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
$ pwd
/opt/module/nginx/conf
$ vim nginx.conf

http {
upstream cluster {
server 127.0.0.1:3333;
server 127.0.0.1:4444;
server 127.0.0.1:5555;
}

server {
listen 1111;
server_name localhost;

location / {
#root html;
#index index.html index.htm;
proxy_pass http://cluster;
}
}
}

$ sudo systemctl stop nginx
$ sudo systemctl start nginx

之后浏览器访问http://192.168.1.202:1111/nacos

说明集群配置成功

将*cloudalibaba-provider-payment9001* 微服务注册注册到nacos

修改yml,换成nginx的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server:
port: 9001

spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
# server-addr: localhost:8848 #配置Nacos地址
server-addr: 192.168.1.202:1111 # 换成nginx的1111端口,做集群

management:
endpoints:
web:
exposure:
include: '*'

可以看见服务列表中出现了刚刚注册进去的微服务

总结

下载Sentinel

下载地址: https://github.com/alibaba/Sentinel/releases/tag/1.7.0

下载之后直接使用命令运行java -jar .\sentinel-dashboard-1.7.0.jar

浏览器访问http://localhost:8080/

账号密码都是sentinel

初始化监控

创建*cloudalibaba-sentinel-service8401*模块

pom

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
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- SpringBoot整合Web组件+actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server:
port: 8401

spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
#Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719

management:
endpoints:
web:
exposure:
include: '*'

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.atguigu.springcloud.alibaba.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FlowLimitController {
@GetMapping("/testA")
public String testA()
{
return "------testA";
}

@GetMapping("/testB")
public String testB()
{
return "------testB";
}
}

入口函数

1
2
3
4
5
6
7
@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401 {
public static void main(String[] args) {
SpringApplication.run(MainApp8401.class, args);
}
}

记得先要启动nacos把服务注册上去,同时也要启动sentinel在后台挂着

流控 - QPS直接失败

对A进行流控

单机阈值设置成1, 表示一秒只能请求一次,多了就会限流

QPS的意思是: 每秒钟的请求数量

限流就会提示Blocked by Sentinel (flow limiting)

流控 - 线程数直接失败

配置流控规则线程数规则,单机阈值为1

然后修改Controller业务代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
public class FlowLimitController {
@GetMapping("/testA")
public String testA()
{
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "------testA";
}

@GetMapping("/testB")
public String testB()
{
return "------testB";
}
}

让请求testA睡上两秒,来了一堆,请求, 但是实际处理线程的数量只有一个

第一个来了的可以立马处理, 第二个第三个.…后面来得请求就不能得到立刻处理, 所以被限流

流控 -关联

有点像连坐

当B犯事了, A也就被限流了

修改业务类Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
public class FlowLimitController {
@GetMapping("/testA")
public String testA()
{
return "------testA";
}

@GetMapping("/testB")
public String testB()
{
return "------testB";
}
}

使用postman进行测试

循环2000次请求, 间隔1毫秒

然后切换到testA给A发送请求, 此时的B已经被限流了

由于这个限流规则是关联的, 所以A也被限流了

流控 - 预热效果

对着浏览器狂点, 前5秒的阈值是10/3=3, 五秒之后的阈值就慢慢的预热到了10

默认 coldFactor 为 3,即请求QPS从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值。

案例,阀值为10+预热时长设置5秒。
系统初始化的阀值为10 / 3 约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10

流控 - 排队等待

配置如下流控规则, 单机阈值是1, 每次只能处理一个请求, 多了的请求就在后面排队等待

等待的时间超过了20秒, 就会超时

模拟并发请求

可以发现testB控制每一秒处理一个请求

降级-RT

配置降级规则

在200毫秒内的响应时间没有响应, 断路器将会在未来的1秒内打开(保险丝跳闸),微服务就不可用了

使用postman发送多个请求,间隔0毫秒

由于testD的请求是1秒中才处理完一个请求, 1秒钟比200毫秒的阈值要大, 所以会被降级

1
2
3
4
5
6
7
8
@GetMapping("/testD")
public String testD()
{
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
log.info("testD 测试RT");
return "------testD";
}

降级 - 异常比例

Controller

1
2
3
4
5
6
7
8
9
10
    @GetMapping("/testD")
public String testD()
{
//暂停几秒钟线程
// try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
// log.info("testD测试RT");
int a = 1/0;
log.info("testD 测试异常比例");
return "------testD";
}

代码测试

发送大量请求,每一次请求都会触发一个异常 , 每秒的请求的数量大于了5,并且异常的比例大于了20%,这必然会触发降级

当stop大量请求之后 , 服务降级就恢复了

降级 - 异常数

Controller

1
2
3
4
5
6
7
@GetMapping("/testE")
public String testE()
{
log.info("testE 测试异常比例");
int age = 10/0;
return "------testE 测试异常比例";
}

配置降级规则

时间窗口期一定要大于等于60秒

代码测试, 连续点击send发送五次请求, 就会触发熔断降级, 因为每一次请求都是会触发异常的

热点Key

热点规则

注意资源名配置的应该是testHotKey, 而不是/testHotKey, 跟sentinel的SentinelResource的value保持一致

参数索引表示是第0个参数(下面Controller中的参数p1), 单机阈值表示QPS每秒请求的数量是1,请求多了就会在1秒的窗口时间降级处理

Controller

1
2
3
4
5
6
7
8
9
10
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2) {
return "------testHotKey";
}

public String dealHandler_testHotKey(String p1, String p2, BlockException exception) {
return "-----dealHandler_testHotKey";
}

代码测试:

localhost:8401/testHotKey?p1=1 限流

localhost:8401/testHotKey?p1=1&p2=1 限流

localhost:8401/testHotKey?p2=1 不限流

狂点send,当QPS超过秒就马上被限流

修改热点规则

当参数值等于p1=5的时候,限流的阈值就不再是1了,而是两百

瞎点send也是—–testHotKey

SentinelResource配置

cloudalibaba-sentinel-service8401模块

pom添加common

1
2
3
4
5
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>

编写一个新的Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
public class RateLimitController
{
@GetMapping("/byResource")
@SentinelResource(value = "byResource",blockHandler = "handleException")
public CommonResult byResource()
{
return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
}
public CommonResult handleException(BlockException exception)
{
return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
}
}

添加流控规则

资源名可以写请求的mapping,也可以填写Sentinel的value

同样的可以按照url进行限流

编写Controller

1
2
3
4
5
6
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl()
{
return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));
}

添加配置

代码测试, 同样可以达到限流的效果

客户自定义限流处理逻辑

编写handler

1
2
3
4
5
public class CustomerBlockHandler {
public static CommonResult handleException(BlockException exception){
return new CommonResult(4444,"自定义的限流处理信息......CustomerBlockHandler");
}
}

编写Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 自定义通用的限流处理逻辑,
blockHandlerClass = CustomerBlockHandler.class
blockHandler = handleException2
上述配置:找CustomerBlockHandler类里的handleException2方法进行兜底处理
*/
/**
* 自定义通用的限流处理逻辑
*/
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = CustomerBlockHandler.class,
blockHandler = "handleException")
public CommonResult customerBlockHandler()
{
return new CommonResult(200,"按客户自定义限流处理逻辑");
}

添加配置

代码测试:

当每秒请求的数量大于1了, 通过@SentinelResource注解中的blockHandlerClass属性指定将由哪一个类来处理异常, blockHandler 指定哪一个类的哪一个方法

这样做的好处就是可以将业务代码和自定义的处理方法进行解构, 而不会混在一起,还可以实现全局统一的处理异常

服务熔断-Ribbon

创建cloudalibaba-provider-payment9003``cloudalibaba-provider-payment9004模块

pom

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
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server:
port: 9003

spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址

management:
endpoints:
web:
exposure:
include: '*'

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;

public static HashMap<Long, Payment> hashMap = new HashMap<>();
static
{
hashMap.put(1L,new Payment(1L,"28a8c1e3bc2742d8848569891fb42181"));
hashMap.put(2L,new Payment(2L,"bba8c1e3bc2742d8848569891ac32182"));
hashMap.put(3L,new Payment(3L,"6ua8c1e3bc2742d8848569891xt92183"));
}

@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id)
{
Payment payment = hashMap.get(id);
CommonResult<Payment> result = new CommonResult(200,"from mysql,serverPort: "+serverPort,payment);
return result;
}
}

入口函数

1
2
3
4
5
6
7
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9003.class, args);
}
}

然后创建cloudalibaba-consumer-nacos-order84模块

pom

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
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

yml

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

spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719

#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
nacos-user-service: http://nacos-payment-provider

config

1
2
3
4
5
6
7
8
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}

controller

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
@RestController
@Slf4j
public class CircleBreakerController
{
public static final String SERVICE_URL = "http://nacos-payment-provider";

@Resource
private RestTemplate restTemplate;

@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback")

public CommonResult<Payment> fallback(@PathVariable Long id)
{
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id, CommonResult.class,id);

if (id == 4) {
throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
}else if (result.getData() == null) {
throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
}

return result;
}
}

入口函数

1
2
3
4
5
6
7
@SpringBootApplication
@EnableDiscoveryClient
public class OrderNacosMain84 {
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain84.class, args);
}
}

请求1正常

请求4显示非法参数异常

请求5显示空指针异常

可以发现当出现Error的时候, 出现的提示信息有点太多了,不够友好

Sentinel服务熔断只配置fallback

修改controller为

在CommonResult方法上给@SentinelResource注解添加fallback

当方法内部出现了异常, 就会去执行指定的fallback

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
@RestController
@Slf4j
public class CircleBreakerController
{
public static final String SERVICE_URL = "http://nacos-payment-provider";

@Resource
private RestTemplate restTemplate;

@RequestMapping("/consumer/fallback/{id}")
// @SentinelResource(value = "fallback")
@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback负责业务异常
public CommonResult<Payment> fallback(@PathVariable Long id)
{
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id, CommonResult.class,id);

if (id == 4) {
throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
}else if (result.getData() == null) {
throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
}

return result;
}

public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(444,"兜底异常handlerFallback,exception内容 "+e.getMessage(),payment);
}

}

代码测试, 这样的错误信息看起来比之前的更为友好一些

Sentinel服务熔断只配置blockHandler

修改controller

配置@SentinelResource注解拥有blockHandler

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
@RestController
@Slf4j
public class CircleBreakerController
{
public static final String SERVICE_URL = "http://nacos-payment-provider";

@Resource
private RestTemplate restTemplate;

@RequestMapping("/consumer/fallback/{id}")
// @SentinelResource(value = "fallback")
// @SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback负责业务异常
@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler负责在sentinel里面配置的降级限流
public CommonResult<Payment> fallback(@PathVariable Long id)
{
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id, CommonResult.class,id);

if (id == 4) {
throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
}else if (result.getData() == null) {
throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
}

return result;
}

public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(444,"兜底异常handlerFallback,exception内容 "+e.getMessage(),payment);
}

public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
}

}

配置Sentinel降级

狂点发送异常请求, 此时请求异常数过大进入熔断降级

未进入降级状态, 显示Java运行的异常

Sentinel服务熔断fallback和blockHandler都配置

修改controller

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
@RestController
@Slf4j
public class CircleBreakerController {
public static final String SERVICE_URL = "http://nacos-payment-provider";

@Resource
private RestTemplate restTemplate;

@RequestMapping("/consumer/fallback/{id}")
//@SentinelResource(value = "fallback")
//@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback负责业务异常
// @SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler负责在sentinel里面配置的降级限流
@SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler")
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);

if (id == 4) {
throw new IllegalArgumentException("IllegalArgumentException,非法参数异常....");
} else if (result.getData() == null) {
throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
}

return result;
}

public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(444,"fallback,无此流水,exception "+e.getMessage(),payment);
}
public CommonResult blockHandler(@PathVariable Long id,BlockException blockException) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
}

}

添加限流规则

请求4, 会出现Java运行异常

多次请求则被限流控制

同样的请求id为5也是一样的

多次请求

如果同时配置了blockHandler和fallback

方法内部出现了异常则会走fallback 兜底方法

请求多了达到了限流则会走blockHandler处理

Sentinel服务熔断exceptionsToIgnore

修改controller为

1
2
3
4
@SentinelResource(value = "fallback",
fallback = "handlerFallback",
blockHandler = "blockHandler",
exceptionsToIgnore = {IllegalArgumentException.class})

这个exceptionsToIgnore 的意思就是如果出现了IllegalArgumentException异常

就不会进入fallback兜底的方法, 没有服务降级的效果了

代码测试:

Sentinel服务熔断OpenFeign

pom添加依赖

1
2
3
4
5
<!--SpringCloud openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

yml

1
2
3
4
# 激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true

编写service

1
2
3
4
5
6
@FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class)
public interface PaymentService {
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}

1
2
3
4
5
6
7
8
9
@Component
public class PaymentFallbackService implements PaymentService {
@Override
public CommonResult<Payment> paymentSQL(Long id) {
return new CommonResult<>(444, "服务降级返回,没有该流水信息",
new Payment(id, "errorSerial......"));
}
}

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
//==================OpenFeign
@Resource
private PaymentService paymentService;

@GetMapping(value = "/consumer/openfeign/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id)
{
if(id == 4)
{
throw new RuntimeException("没有该id");
}
return paymentService.paymentSQL(id);
}

代码测试:

然后关闭payment提供者微服务, 再次请求, 观察84微服务自动降级, 不会被耗死

规则持久化

修改cloudalibaba-sentinel-service8401

pom

1
2
3
4
5
<!--SpringCloud ailibaba sentinel-datasource-nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

yml

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
server:
port: 8401

spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
#Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow

management:
endpoints:
web:
exposure:
include: '*'

然后在nacos的配置列表添加配置

DataId cloudalibaba-sentinel-service

内容配置

1
2
3
4
5
6
7
8
9
10
11
[
{
"resource": "/rateLimit/byUrl",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
1
2
3
4
5
6
7
resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
clusterMode:是否集群。

然后启动8401访问localhost:8401/rateLimit/byUrl

多次刷新, 说明流控配置起效了

上Sentinel查看一下, 刚刚配置的json配置生效了, 这个流控规则是nacos读取json配置文件给我们加载进来的

Seata

这部分懒得做笔记了

核心注解@GlobalTransactional

粘上一段核心代码

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
@Service
@Slf4j
public class OrderServiceImpl implements OrderService
{
@Resource
private OrderDao orderDao;

@Resource
private StorageService storageService;

@Resource
private AccountService accountService;

/**
* 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
* 简单说:
* 下订单->减库存->减余额->改状态
*/
@Override
@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
public void create(Order order) {
log.info("------->下单开始");
//本应用创建订单
orderDao.create(order);

//远程调用库存服务扣减库存
log.info("------->order-service中扣减库存开始");
storageService.decrease(order.getProductId(),order.getCount());
log.info("------->order-service中扣减库存结束");

//远程调用账户服务扣减余额
log.info("------->order-service中扣减余额开始");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("------->order-service中扣减余额结束");

//修改订单状态为已完成
log.info("------->order-service中修改订单状态开始");
orderDao.update(order.getUserId(),0);
log.info("------->order-service中修改订单状态结束");

log.info("------->下单结束");
}
}

Failed to fetch schema of order的解决办法

jdbc:mysql://localhost:3306/orders

jdbc:mysql://localhost:3306/orders?useInformationSchema=false

给所有的微服务都添加上?useInformationSchema=false, 然后重启

参考:


SpringCloud
https://xiamu.icu/Java/SpringCloud/
作者
肉豆蔻吖
发布于
2023年5月5日
许可协议