Spring Cloud
微服务架构介绍
微服务架构是一种架构模式,它提倡将单一应用程序分成一组小的服务,服务之间互相协调、互相配合,为用户提供最终价值。每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制相互协作(通常是基于HTTP协议的RESTful API)。每个服务都围绕着具体业务进行构建,并且能够被独立的部署到生产环境、类生产环境等。另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。
SpringCloud 简介
SpringCloud=分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶
SpringBoot和SpringCloud的依赖关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wfwgUH5f-1620878286674)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210428170423731.png)]
Maven配置
Maven中的DependencyManagerment和Dependencies的区别:
Maven使用dependencyManagement 元素来提供了一种管理依赖版本号的方式,通常会在一个组织或者项目的最顶层的父POM 中看到dependencyManagement 元素。
使用pom.xml 中的dependencyManagement 元素能让所有在子项目中引用一个依赖而不用显式的列出版本号。Maven 会沿着父子层次向上去,直到找到一个拥有dependencyManagement 元素的项目,然后它就会使用这个 dependencyManagement 元素中指定的版本号。
这样做的好处就是:如果有多个子项目都引用同一样依赖,则可以避免在每个使用的子项目里都声明一个版本号,这样当想升级或者切换到另一个版本时,只需要在顶层父容器里更新,而不需要一个一个子项目的修改;另外如果某个子项目需要另外的一个版本,只需要声明version即可。
DependencyManagerment里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖
如果子项目中指定了版本号,那么会使用子项目中指定的jar版本
父工厂创建完成执行mvn:install将父工厂发布到仓库方便子工程继承
支付模块构建
编写业务类
1、建库:
`CREATE TABLE `payment` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`serial` varchar(200) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
application.yaml
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2019
username: root
password: root123
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.yutou.springcloud.entities
2、entities
主实体Payment
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable {
private Long id;
private String serial;
}
Json封装体CommonResult
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message){
this(code, message, null);
}
}
3、dao
@Mapper
public interface PaymentDao {
public int create(Payment payment);
public Payment getPaymentById(@Param("id") Long id);
}
4、service
public interface PaymentService {
public int create(Payment payment);
public Payment getPaymentById(@Param("id") Long id);
}
@Service
public class PaymentServiceImpl implements PaymentService {
@Resource
private PaymentDao paymentDao;
public int create(Payment payment){
return paymentDao.create(payment);
}
public Payment getPaymentById(Long id){
return paymentDao.getPaymentById(id);
}
}
5、controller
@Slf4j
@RestController
public class PaymentController {
@Resource
private PaymentService paymentService;
@PostMapping(value = "/payment/create")
public CommonResult create(Payment payment){
int result = paymentService.create(payment);
log.info("****插入结果"+ result);
if (result > 0){
return new CommonResult(200,"插入数据库成功",result);
}else{
return new CommonResult(444,"插入数据库失败",null);
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id){
Payment payment = paymentService.getPaymentById(id);
log.info("****插入结果"+ payment);
if (payment != null){
return new CommonResult(200,"查询成功",payment);
}else {
return new CommonResult(444,"没有对应记录,查询id"+ id,null);
}
}
}
6、测试
在浏览器地址栏中输入:localhost:8001/payment/get/31
浏览器对post请求不支持,使用Postman
工具模拟post请求
消费者订单模块
RestTemplate
RestTemplate提供了多种便捷访问远程Http服务的方法
是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集。
使用restTemplate访问restful接口
(url, requestMap, ResponseBean.class)这三个参数分别代表REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。
实际编写:
主类:
@SpringBootApplication
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class, args);
}
}
ApplicationConfig类:
@Configuration
public class ApplicationConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
Controller类:
@RestController
@Slf4j
public class OrderController {
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);
}
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
}
}
工程重构
将共同的实体类放入cloud-api-commons中
再在80端口和8001端口中加入依赖
<dependency>
<groupId>com.yutou.springboot</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
Eureka
服务治理
Spring Cloud 封装了 netfix 公司开发的 Euraka模块来实现服务治理
在传统的rpc远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
服务注册
Euraka 采用了CS的设计架构,Eureka Server 作为服务注册功能的服务器,它是服务注册中心,而系统中的其他微服务,使用Euraka的客户端连接到 Euraka Server并维持心跳连接,这样系统的维护人员就可以通过 Euraka Server来监控系统中各个微服务是否正常运行。
在服务注册与发现中,有一个注册中心,当服务器启动的时候,会把当前自己服务器的信息比如服务地址通讯地址等以别名方式注册到注册中心上,另一方(消费者)服务器提供者,以该别名的方式去注册中心上获取到实际的服务通讯地址,然后再实现本地RPC调用RPC远程调用框架核心设计思想,在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理理念),在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HIOWHRg1-1620878286677)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210429205756766.png)]
两个组件
Euraka包含两个组件:Euraka Server和Euraka Client
Euraka Server提供服务注册服务
各个微服务节点通过配置启动后,会在EurakaServer中进行注册,这样EurakaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
EurakaClient通过注册中心进行访问
是一个Java客户端,用于简化Euraka Server的交互,客户端同时也具备一个内置的、使用轮询负载算法的负载均衡器。在应用启动后,将会向Euraka Server发生心跳(默认周期为30秒)。如果Euraka Server在多个心跳周期内没有接收到某个节点的心跳,EurakaServer将会从服务注册表中把这个服务节点移除(默认90秒)
建立服务注册中心
1、建module
2、改POM
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
3、写YML
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/
4、主启动
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class,args);
}
}
5、测试
输入网址Eureka
将其他端注册进注册中心
8001注册provider
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
添加以下代码进入application.yaml
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true,单节点无所五,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
运行8001
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UBDx9hQD-1620878286679)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210429220243123.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TsSaNtEB-1620878286682)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210429220332344.png)]
自我保护机制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ycTiQhgL-1620878286684)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210429220359432.png)]
80注册成consumer
80注册client
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置yaml
server:
port: 80
spring:
application:
name: cloud-order-service
eureka:
client:
#表示是否将自己注册进eurekaServer默认为True
register-with-eureka: true
#是否从eurekaServer抓取已有的注册信息,默认为true,单节点无所谓。集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
主启动类上加上@EnableEurekaClient
注解
启动时可能会遇上80端口被占用的情况
打开CMD,输入netstat -ano 找到80端口对应的PID
在任务管理器找到PID对应的进程终结
运行后输入localhost:7001转到以下页面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lDZUmzyQ-1620878286685)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210430180136942.png)]
Eureka集群
同eureka7001一样,创建7002端口服务器
更改7001的yaml文件如下:
server:
port: 7001
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/
7002的yaml文件为:
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
hostname不能相同从host文件中更改注册
下载了switchhost软件 更改host文件加入
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
通过以下网址访问:
将订单支付两个微服务注册进Eureka集群:
更改80和8001端口的yaml文件
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
登录7001和7002发现注册成功
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jxzmJMsS-1620878286686)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210430202616499.png)]
支付微服务集群配置
新建模块8002,同8001的步骤一样
启动发现80端口只用了8001的服务
更改config文件,使用@LoadBalanced注解,赋予RestTemplate负载均衡的能力
@Configuration
public class ApplicationConfig {
@Bean
@LoadBalanced //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
测试发现8001和8002端口交替出现
Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号,且该服务还有负载能力了
actuator微服务信息完善
主机名称:服务名称的修改
更改eureka页面中的主机名称:服务名称形式
在application.yaml中的eureka中加入
instance:
instance-id: payment8001
效果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hMMAv0EM-1620878286687)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210502152428761.png)]
访问信息有IP信息提示
在application.yaml中加入以下代码:
instance:
instance-id: payment8002
prefer-ip-address: true #访问路径显示IP
服务发现 Discovery
对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息
在controller类中加入以下代码:
@Resource
private DiscoveryClient discoveryClient;
@GetMapping(value = "/payment/discovery")
public Object discovery(){
List<String> services = discoveryClient.getServices();
for (String element : services){
log.info("*****element:"+element);
}
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for(ServiceInstance instance : instances){
log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}
return this.discoveryClient;
}
输入网页localhost:8001/payment/discovery
可以得到其他服务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OCTLSsuw-1620878286688)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210502155514709.png)]
eureka 自我保护理论
某时刻某一个服务不可用了,Eureka不会立即清理,依旧会对该服务的信息进行保存
属于CAP里面的AP分支
产生机制原因
为了防止EurekaClient可以正常运行,但是与EurekaServer网络不通情况下,EurekaSever不会立即将EurekaClient服务剔除
什么是自我保护模式
默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为服务本身其实是健康的,此时不应该注销这个微服务,Eureka通过"自我保护模式"了解决这个问题——当EurekaServer节点在短时间内丢失过多的客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。
怎么禁止自我保护
对于7001而言
在application.yaml文件中加入
server:
#关闭自我保护机制,保证不可用服务被及时剔除
enable-self-preservation: false
eviction-interval-timer-in-ms: 2000
对于8001而言:
在application.yaml文件中加入
#Eureka客户端向服务端发送心跳的时间间隔,单位为s(默认为30S)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳等待时间上限,单位为s(默认是90s),超时将剔除服务
lease-expiration-duration-in-seconds: 2
Zookeeper
新建8004服务
新建cloud-provider-payment8004模块
application.yaml中配置代码为:
server:
port: 8004
spring:
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: 192.168.1.118:2181
在Controller中加入:
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@RequestMapping("/payment/zk")
public String paymentzk(){
return "springcloud with zookeeper:" +serverPort+"\t"+ UUID.randomUUID().toString();
}
}
主启动类为:
@SpringBootApplication
@EnableDiscoveryClient //该注解用于向使用consul或者zookeeper作为注册中心时注册服务
public class PaymentMain8004 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8004.class,args);
}
}
使用docker运行zookeeper
1、下载zookeer
[root@localhost118 ~]# docker pull zookeeper
2、使用docker查看镜像并注册
[root@localhost118 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
zookeeper latest c130c80fcb5b 5 days ago 269MB
mycentos 0.1 5020d1aaf03b 4 weeks ago 291MB
yutou/centos latest dcd5cd189ffc 4 weeks ago 209MB
tomcat 1.0 d84a37dfc310 4 weeks ago 653MB
rabbitmq management e2d949b43dc1 5 weeks ago 187MB
mysql 5.7 a70d36bc331a 3 months ago 449MB
redis latest 621ceef7494a 3 months ago 104MB
tomcat 9.0 040bdb29ab37 3 months ago 649MB
tomcat latest 040bdb29ab37 3 months ago 649MB
nginx latest f6d0b4767a6c 3 months ago 133MB
portainer/portainer-ce latest 980323c8eb3f 3 months ago 196MB
centos latest 300e315adb2f 4 months ago 209MB
hello-world latest bf756fb1ae65 16 months ago 13.3kB
kibana 7.4.2 230d3ded1abc 18 months ago 1.1GB
elasticsearch 7.4.2 b1179d41a7b4 18 months ago 855MB
nginx 1.10 0346349a1a64 4 years ago 182MB
webcenter/activemq latest 3af156432993 4 years ago 422MB
[root@localhost118 ~]# docker run -d --name zookeeper -p 2181:2181 -d zookeeper
bb043d8ab2620464240ae85220d412caad8e79fd45fae01cd0804c432f414411
3、进入zookeeper中并查看注册的服务
root@bb043d8ab262:/apache-zookeeper-3.7.0-bin# cd bin
root@bb043d8ab262:/apache-zookeeper-3.7.0-bin/bin# ls
README.txt zkCli.sh zkServer-initialize.sh zkSnapShotToolkit.cmd zkSnapshotComparer.sh
zkCleanup.sh zkEnv.cmd zkServer.cmd zkSnapShotToolkit.sh zkTxnLogToolkit.cmd
zkCli.cmd zkEnv.sh zkServer.sh zkSnapshotComparer.cmd zkTxnLogToolkit.sh
root@bb043d8ab262:/apache-zookeeper-3.7.0-bin/bin# zkCli.sh
[zk: localhost:2181(CONNECTED) 0] ls /services
[cloud-provider-payment]
新建80端口
xml和yaml文件与8004一致,更改端口号即可
主启动类:
@SpringBootApplication
@EnableDiscoveryClient
public class OrderZKMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderZKMain80.class,args);
}
}
config:
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
controller类
@RestController
@Slf4j
public class OrderZKController {
public static final String INVOKE_URL = "http://cloud-provider-payment";
@Resource
private RestTemplate restTemplate;
@GetMapping(value = "/consumer/payment/zk")
public String paymentInfo(){
String result = restTemplate.getForObject(INVOKE_URL+"/payment/zk",String.class);
return result;
}
}
启动主启动类后,查看zookeeper中是否注册成功
[zk: localhost:2181(CONNECTED) 1] ls /services
[cloud-provider-order, cloud-provider-payment]
Consul
Consul是一套开源的分布式服务发现和配置管理系统,由HashiCorp用Go语言开发
提供了微服务系统中的服务治理、配置中心、控制总线等功能,这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网络,总之,Consul提供了一种完整的服务网络解决方案
它具有很多优点,包括:基于raft协议,比较简洁,支持健康检查,同时支持HTTP和DNS协议,支持跨数据中心的WAN集群,提供图形界面跨平台,支持Linux、Mac、Windows
安装consul
官网下载并安装consul
启动consul:在consul安装的目录下打开cmd
输入consul agent -dev
服务提供者注册进consul
新建8006模块
xml文件中引入
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
application,yaml中加入以下代码:
server:
port: 8006
spring:
application:
name: cloud-provider-payment
cloud:
consul:
host: localhost
port: 8500
discovery:
#hostname 127.0.0.1
service-name: ${spring.application.name]
controller类与8004端口的一致
输入localhost:8500即可查看
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-psMIeSJo-1620878286689)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210502204416866.png)]
服务消费者注册进consul
同之前一样
三个注册中心的异同
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4YyAIGGu-1620878286690)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210502205757003.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m94nhwiL-1620878286691)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210502205850123.png)]
之前redis讲过CAP,C是强一致性,A是可用性,P是分区容错性
AP(Eureka):当网络分区出现后,为了保证可用性,系统可用返回旧值
CP(Zookeeper/Consul):当网络分区出现后,为了保证一致性,必须拒接请求
Ribbon
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如来凝结超时、重试等。简单来说,就是在配置文件中列出Load Balancer后面所有的机器,RIbbon会自动的帮助你基于某种规则(如简单轮询、随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
负载均衡
负载均衡是将用户的请求平摊的分配到多个服务上,从而达到系统HA(高可用)。
常见的负载均衡有软件Nginx、LVS、硬件F5等
Ribbon本地负载均衡客户端 VS Nginx服务端负载均衡区别:
Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求,即负载均衡是由服务器实现的
Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存JVM本地,从而在本地实现RPC远程服务调用技术。
进程内LB:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
架构说明
Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例
pom
spring-cloud-starter-netflix-eureka-client自带了spring-cloud-starter-ribbon引用
restTemplate的getForEntity方法
返回对象为ResponseEnitity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等。
@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()){
return entity.getBody();
}else {
return new CommonResult<>(444,"操作失败");
}
}
IRule核心组件
IRule根据特定算法中从服务列表中选取一个要访问的服务
- RoundRobinRule 轮询
- RandomRule 随机
- RetryRule 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
- WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
- BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
- AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例
- ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器
负载规则替换
1、新建package com.yutou.myrule 如果和主启动类放在一起就会失去特殊性
2、新建MySelfRule规则类
@Configuration
public class MySelfRule {
@Bean
public IRule MyRule(){
return new RandomRule();//定义为随机
}
}
3、主启动类添加@RibbonClient
@SpringBootApplication
@EnableEurekaClient
@RibbonClient
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class, args);
}
}
OpenFeign
Feign
Feign是一个声明式的web服务客户端,让编写web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可
前面在使用Ribbon+RestTemplate,利用RestTemplate请求的封装处理,形成了一套模板化的调用方法,但是在实际开发中,由于对服务的依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端来包装这些依赖接口并在接口上添加注解即可服务的调用。
Feign在此基础上做了进一步封装,我们只需创建一个接口并使用注解的方式来配置它(以前Dao接口上面标注的Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用SprIng Cloud Ribbon时,自动封装服务调用客户端的开发量。
Feign集成了Ribbon
通过feign只需要定义服务绑定接口且以声明式的方法,实现服务调用
Feign和OpenFeign的区别
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-435uJhtu-1620878286692)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210505142355152.png)]
OpenFeign服务调用
pom.xml
新增下面这个,其余的和80之前的一样
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
主启动类:
@SpringBootApplication
@EnableFeignClients
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class,args);
}
}
业务类:
servcie
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id]")
public CommonResult<Payment> getPaymentById(@Param("id") Long id);
}
controller
@RestController
@Slf4j
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping(value = "/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
return paymentFeignService.getPaymentById(id);
}
}
测试报错
status 405 reading
Hystrix
服务雪崩
如果微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的"扇出"。如果扇出的链路上某个微服务的调用响应过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的雪崩效应
Hystrix是什么
是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,Hystrix能够保证一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
断路器本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个符合预期的、可处理的备选响应,而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要的占用,从而避免了故障在分布式系统中的蔓延、乃至雪崩。
作用
- 服务降级
- 服务熔断
- 接近实时的监控
服务降级概念
服务器忙,请稍后再试,不让客户端等待并立即返回一个友好提示
哪些情况会触发降级:
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
服务熔断
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
服务降级->进而熔断->恢复调用链路
服务限流
秒杀高并发等操作,严禁一窝蜂的过来拥挤,排队有序进行
Hystrix支付微服务构建
@Service
public class PaymentService {
//正常访问
public String paymentInfo_OK(Integer id){
return "线程池" + Thread.currentThread().getName()+" paymentInfo_OK,id:" +id;
}
public String paymentInfo_TimeOut(Integer id){
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
return "线程池" + Thread.currentThread().getName()+" paymentInfo_TimeOut,id:" +id;
}
}
controller类:
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private 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){
String result = paymentService.paymentInfo_TimeOut(id);
log.info("****result"+ result);
return result;
}
}
测试时报错Error starting Tomcat context
发现是pom文件中 加入了zookeeper包:
修正后,输入localhost:8001/payment/hystrix/ok/31测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L4Dz5OK0-1620878286693)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210505194939521.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OPtvGQud-1620878286694)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210505195009025.png)]
Hystrix订单微服务构建
主启动类:
@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
service:
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
controller:
@RestController
@Slf4j
public class OrderHystrixController {
@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("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
}
高并发测试
使用JMeter测试
降级容错解决的维度要求
8001超时,80不能一直卡死等待,必须要有服务降级
8001宕机了,80不能一直卡死等待,必须要有服务降级
8001OK,但是80自己出故障或者有自我要求(自己的等待时间小于服务提供者),自己处理降级
服务降级支付侧 fallback
8001先从自身找问题:设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback
修改代码:
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
public String paymentInfo_TimeOut(Integer id){
try {
TimeUnit.SECONDS.sleep(5);
}catch (InterruptedException e){
e.printStackTrace();
}
return "线程池" + Thread.currentThread().getName()+" paymentInfo_TimeOut,id:" +id;
}
//兜底方案
public String paymentInfo_TimeOutHandler(Integer id){
return "线程池" + Thread.currentThread().getName()+" paymentInfo_TimeOutHandler,id:" +id;
}
服务降级订单侧 fallback
服务降级一般放在客户端
修改yaml
feign:
hystrix:
enabled: true
主启动类加注解@EnableHystrix
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
//兜底方案
public String paymentInfo_TimeOutHandler(@PathVariable("id") Integer id){
return "80,对方支付系统繁忙,情稍后再试或者自己运行错误情检查自己";
}
全局服务降级
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
//全局fallback
public String payment_Global_FallbackMethod(){
return "Global异常处理信息,情稍后再试";
}
通配服务降级
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
@Component
public class PaymentFallbackService implements PaymentHystrixService{
@Override
public String paymentInfo_OK(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_OK";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "----PaymentFallbackService fall back-paymentInfo_TimeOut";
}
}
客户端在服务器宕机时也能收到提示,避免服务挂起耗死服务器
服务熔断理论
熔断机制概述:
熔断机制是应对雪崩效应的一种微服务链路保护机制,当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务响应正常后,恢复调用链路。
在Spring Cloud框架里,熔断机制通过Hystrix实现,Hystrix会监控服务间调用的状况,当失败的调用到一定阙值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand
服务熔断案例
//服务熔断
@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 不能是负数,请稍后再试" +id;
}
Controller
//服务熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
String result = paymentService.paymentCircuitBreaker(id);
log.info("***result:"+result);
return result;
}
测试:
[localhost:8001/payment/circuit/31
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oUntvpCB-1620878286695)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210506155521792.png)]
localhost:8001/payment/circuit/-31
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MRmld21d-1620878286696)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210506155509310.png)]
多次错误,然后慢慢正确,发现刚开始不满足条件,就算是正确的访问地址也不能进行
服务熔断总结
熔断类型
熔断打开:请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态
熔断关闭:熔断关闭不会对服务器进行熔断
熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
断路器在什么时候起作用
涉及到断路器的三个重要参数:快照时间窗、请求总数阙值、错误百分比阙值。
1、快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒
2、请求总数阙值:在快照时间窗内,必须满足请求总数阙值才有资格熔断,默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
3、错误百分比阙值:当请求总数在快照时间窗内超过了阙值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设置50%阙值的情况下,这时候就会将断路器打开。
断路器开启或者关闭的条件
- 当满足一定的阙值的时候(默认10秒内超过20个请求次数)
- 当失败率达到一定的时候(默认10秒内超过50%的请求失败)
- 到达以上的阙值,断路器将会开启
- 当开启的时候,所有请求都不会进行转发
- 一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启,重复4和5
断路器打开之后
再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback,通过断路器,实现了自动地发现错误并降级逻辑切换为主逻辑,减少响应延迟的效果。
原来的主逻辑要如何恢复
当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑。
当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
Hystrix图形化Dashboard搭建
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
主启动类:
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class,args);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rDDakEt9-1620878286699)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210506170505961.png)]
测试:
先访问正确地址,再访问错误地址,再正确地址,会发现图示断路器都是慢慢放开的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HKwqBYbK-1620878286700)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210506172008338.png)]
实心圈:共有两种含义,它通过颜色的变化代表了实例的健康程度,它的健康度从绿色<黄色<橙色<红色递减
该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆越大。所以通过该实心圆的展示,就可以再大量的实例中快速的发现故障实例和高压力实例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tdQbmvWF-1620878286701)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210506173105892.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RtvbPLvs-1620878286702)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210506173114061.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z2z9m4UU-1620878286703)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210506173127257.png)]
Gateway
Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;但在2.x版本中,Zuul的升级一直跳票,SpringCloud最后自己研发了一个网关替代Zuul,就是SpringCloud Gateway
Getway旨在提高一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能
三大核心概念
1、路由:路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
2、断言:开发人员可以匹配HTTP请求中的所有内容,如果请求与断言相匹配则进行路由
3、过滤:使用过滤器,可以在请求被路由前或者之后对请求进行修改
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bkOxv3E4-1620878286704)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210506194600506.png)]
web请求,通过一些匹配条件,定位到真正的服务节点,并在这个转发过程的前后,进行一些精细化控制。
predicate就是我们的请求条件,而filter,就可以理解为一个无所不能的拦截器,有了这两个元素,再加上目标uri,就可以实现一个具体的路由了。
入门配置
方式1:yaml配置
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
yaml
server:
port: 9527
spring:
application:
name: cloud.gateway
cloud:
gateway:
routes:
- id: payment_routh #payment_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2 #payment_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
defaultZone: http://eureka7001.com:7001/eureka
register-with-eureka: true
fetch-registry: true
主启动类:
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class GatewayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GatewayMain9527.class,args);
}
}
方式2:
从9527端口访问百度新闻
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route_yutou",r -> r.path("/guonei")
.uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
Getway配置动态路由
routes:
- id: payment_routh #payment_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2 #payment_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
Gateway常用的Predicate
Spring Cloud Gateway包括许多内置的Route Predicate工厂,所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合。
Spring Cloud Gateway 创建 Route 对象时, 使用RoutePredicateFactory创建 Predicate对象,Predicate对象可以赋值给Route。
- Cookie Route Predicate
需要两个参数,一个是Cookie name,一个是正则表达式。路由规则会通过获取对应的 Cookie name值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行。
- Header-X-Request-Id, \d+
请求头要有X-Request-Id属性并且值为整数的正则表达式
Gateway的Filter
路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。
Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。
自定义过滤器:
作用:全局日志记录,统一网关鉴权
@Component
@Slf4j
public class MyLogGatewayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("*****come in MyLogGateWayFilter:" + new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname == null){
log.info("*****用户名为Null,非法用户");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
Config
分布式配置中心介绍
SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置
作用:
- 集中管理配置文件
- 不同环境不同配置,动态化的配置更新,分环境部署
- 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
- 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置
- 将配置信息以REST接口的形式暴露
配置总控中心搭建
server:
port: 3344
spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
uri: https://gitee.com/qtayu/test.git #gitee上的git仓库名字
search-paths:
- springcloud-config
username: wyxhslhs@163.com #gitee账户
password: ****** #密码
label: master
#服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
读取gitee上面的文件
localhost:3344/master/README.md
Config客户端配置与测试
application.yml是用户级的资源配置项
SpringCloud会创建一个Bootstrap Context,作为Spring应用的Application Context的父上下文。初始化的时候,Bootstrap Context 负责从外部源加载配置属性并解析配置,这两个上下文共享一个从外部获取的Enviroment
bootstrap.yml是系统级的,优先级更高,所以要将Client模块下的application.yml改为bootstrap.yml
server:
port: 3355
spring:
application:
name: config-client
cloud:
#Config客户端配置
config:
label: master #分支名称
name: config #配置文件名字
profile: dev #读取后缀名称
uri: http://localhost:3344 #配置中心地址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
controller类
@RestController
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo(){
return configInfo;
}
}
测试:
可以访问到gitee仓库种中config-dev文件中的信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vHbiGtHr-1620878286705)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210507221522891.png)]
成功实现了客户端3355访问Spring Cloud Config3344通过Gitee获取配置信息
Config动态刷新之手动版
yaml增加以下代码:
#暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
Controller类上加入@RefreshScope注解
Bus消息总线
Spring Cloud Bus配合 Spring Cloud Config 使用可以实现配置的动态刷新
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dkXgdooc-1620878286706)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210508104759134.png)]
总线
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来,由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便的广播一些需要让其他连接在该主题上的实例都知道的消息。
基本原理:
ConfigClient实例都监听MQ中同一个Topic默认是SprIngCloudBsu,当一个服务刷新数据的时候,它会把这个信息放入Topic中,这样其他监听同一Topic的服务就能得到通知,然后去更新自身的配置。
rabbitMQ
docker启动安装
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jyEYgoyW-1620878286707)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210508110954024.png)]
bus动态刷新全局广播
设计思想:
1、利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
2、利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置
思想一不适合的原因:
打破了微服务的职责单一性,因为微服务本来是业务模块,它本不应该承担配置刷新的职责
破坏了微服务各节点的对等性
有一定的局限性,例如,微服务在迁移时,它的网络地址常常会发生变化
3344加入:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
yaml中加入
rabbitmq:
host: 192.168.1.118
port: 15672
username: guest
password: guest
management:
endpoints:
web:
exposure:
include: 'bus-refresh'
3355加入
xml跟3344一样
yaml加入
rabbitmq:
host: 192.168.1.118
port: 15672
username: guest
password: guest
动态刷新 定点通知
http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}
Stream
Spring Cloud Stream是一个构建消息驱动微服务的框架,为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。
目前仅支持RabbitMQ、Kafka
标准MQ:
生产者/消费者之间靠消息媒介传递信息内容 Message
消息必须走特定的通道 消息通道 MessageChannel
消息通道里的消息如何被消费,谁负责收发处理 消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅
Stream的设计思想
通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。
Binder:INPUT对应于消费者,OUTPUT对应于生产者
Stream中的消息通信方式遵循了发布-订阅模式
Topic主题进行广播
- 在RabbitMQ就是Exchange
- 在Kakfa中就是Topic
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P0QlMGGQ-1620878286708)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210508152040110.png)]
Spring消息驱动之生产者
server:
port: 8801
spring:
application:
name: cloud-stream-provider
rabbitmq:
host: 192.168.1.118
port: 5672
username: qtayu
password: qtayu
cloud:
stream:
binders:
defaultRabbit:
type: rabbit
bindings:
output:
destination: cruiiExchange
content-type: application/json
binder: defaultRabbit
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
fetch-registry: false
register-with-eureka: false
instance:
lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔 (默认是30s)
lease-expiration-duration-in-seconds: 5 #如果现在超过了5s的间隔(默认是90s)
instance-id: send-8801.com #在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
启动后报错:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PP83oRZa-1620878286709)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210509152703302.png)]
Sleuth
在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的分布式服务调用链路,链路中任何一环出现高延或错误都会引起整个请求最后的失败。
Spring Cloud Sleuth提供了一套完整的服务跟踪的解决方案,在分布式系统中提供追踪解决方案并且兼容支持了zipkin
docker安装zipkin
Sleuth 链路监控展现
8001中加入:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
yaml中加入:
spring:
application:
name: cloud-payment-service
zipkin:
base-url: http://192.168.1.118:9411/
sleuth:
sampler:
# 采样率介于0到1之间,1表示全部采集
probability: 1
Controller类中加入:
@GetMapping("/payment/zipkin")
public String paymentZipkin(){
return "hi,I am paymentzipkin server fail back, welcome";
}
80的xml中加入:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
yaml中加入:
spring:
application:
name: cloud-order-service
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
probability: 1
Controller类中加入:
@GetMapping("/consumer/payment/zipkin")
public String paymentZipkin(){
String result = restTemplate.getForObject("http://localhost:8001"+"/payment/zipkin",String.class);
return result;
}
测试:
点击localhost/consumer/payment/zipkin
再输入网址:
Cloud Alibaba
作用
- 服务限流降级:默认支持Servlet、Feign、RestTemplate、Bubbo和RocketMQ限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级Metrics监控
- 服务注册与发现:适配SpringCloud服务注册与发现标准,默认集成了Ribbon的支持
- 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新
- 消息驱动能力:基于Spring Cloud Stream 为微服务应用构建消息驱动能力
- 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务,支持在任何应用、任何时间、任何地点存储和访问任意类型的数据
- 分布式任务调度:提供秒级、精准、高可靠、高可用的定时任务调度服务,同时提供分布式的任务执行模型
Nacos
一个更易于构建云原生应用的动态服务发现,配置管理和服务管理平台
可以替代Eureka做服务注册中心,替代Config做服务配置中心
docker安装nacos
运行成功后访问:Nacos
服务提供者注册
POM:
父POM
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
主模块POM
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
YML:
server:
port: 9001
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: 192.168.1.118 #配置nacos地址
management:
endpoints:
web:
exposure:
include: '*'
主启动类:
@EnableDiscoveryClient
@SpringBootApplication
public class PaymentMain9001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9001.class,args);
}
}
controller:
@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 +"id"+id;
}
}
测试:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lIb5lOGe-1620878286710)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210511195907177.png)]
类似上面的内容,建立9002端口服务者
服务消费者注册和负载
Yaml:
server:
port: 83
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: 192.168.1.118:8848
service-url:
nacos-user-service: http://nacos-payment-provider
Config:
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
Controller类:
@RestController
@Slf4j
public class OrderNacosController {
@Resource
private RestTemplate restTemplate;
@Value("${service-url.nacos-user-service}")
private String serverURL;
@GetMapping(value = "/consumer/payment/nacos/{id}")
public String paymentInfo(@PathVariable("id") Long id){
return restTemplate.getForObject(serverURL+"/payment/nacos" + id,String.class);
}
}
能启动但是测试时报错
注册中心对比
Nacos支持AP模式和CP模式的切换
服务配置中心
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
Nacos同Spirngcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取。
拉取配置之后,才能保证项目的正常运行。
Springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application
application.yaml文件:
spring:
profiles:
active: dev #表示开发环境
bootstrap.yaml文件:
# nacos配置
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: 192.168.1.118:8848 #Nacos服务注册中心地址
config:
server-addr: 192.168.1.118:8848 #Nacos作为配置中心
file-extension: yaml #指定yaml格式的配置
主启动类:
@EnableDiscoveryClient
@SpringBootApplication
public class NacosConfigClientMain3377 {
public static void main(String[] args) {
SpringApplication.run(NacosConfigClientMain3377.class,args);
}
}
controller类:
@RestController
@RefreshScope //支持nacos的动态刷新功能
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/config/info")
public String getConfigInfo(){
return configInfo;
}
}
在nacos中进行配置:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1chR7N3l-1620878286711)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210511215946269.png)]
对应关系:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mXF8Pie5-1620878286712)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210511220358122.png)]
nacos中的配置详情
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O6GwKPbk-1620878286713)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210511220911655.png)]
测试:
在nacos中把version=1改成version=2,刷新页面,可以获得version=2
Namespace+Group+Data ID
三者关系:
类似Java里面的Package名和类名,最外层的namespace是可以用于区分部署环境的,Group和DataID逻辑上区分两个目标对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gK1yWxza-1620878286714)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210511222520838.png)]
默认情况:
NameSpace=public,Group=DEFAULT_GROUP,默认Cluster是DEFAULT
Namspace主要用来实现隔离,Group可以把不同的微服务划分到同一个分组里面去,Service就是微服务,一个Service可以包含多个Cluster(集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟部分、
最后Instance就是微服务的实例
DataID 配置
指定spring.profile.active和配置文件的DataID来使不同环境下读取不同的配置
新增配置test
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eYKSkPfN-1620878286717)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512100541210.png)]
更改yaml文件中的代码,配置是什么就加载哪个
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CxCgBpmc-1620878286718)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512100519384.png)]
group分组方案
新建group
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yjDEcIDd-1620878286719)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512101012504.png)]
在bootstrap.yaml中添加group分组
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4NveLSi8-1620878286720)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512101133589.png)]
application.yaml中读取info
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kY2oEdh3-1620878286721)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512101201668.png)]
Namespace方案
新建namespace
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ScLJ04Bo-1620878286723)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512102154596.png)]
在bootstrap.yaml中加入:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7sMvycC7-1620878286724)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512102122866.png)]
nacos集群
默认Nacos使用嵌入式数据库实现数据的存储,所以,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储
docker安装的nacos数据持久化,参考文章:
集群配置:
- Linux服务器上mysql配置
- application.properties 配置
- Linux服务器上nacos的集群配置cluster.conf
- 编辑Nacos的启动脚本startup.sh,使它能够接收不同的启动端口
- Nginx的配置,由它作为负载均衡器
- 截止到此,1个Nginx+3个nacos注册中心+1个Mysql
Sentinel
随着微服务的流行,服务和服务之间的稳定性变得越来越重要,Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
使用docker安装sentinel:
docker pull bladex/sentinel-dashboard:1.7.0
docker run --name sentinel -d -p 8858:8858 blade/sentinel-dashboard:1.7.0
初始化监控
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
yaml
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: 192.168.1.118:8848 #Nacos服务注册中心地址
sentinel:
transport:
dashboard: 192.168.1.118:8858 #配置sentinel dashboard地址
port: 8719 # 默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至扎到未被占用的端口
management:
endpoints:
web:
exposure:
include: '*'
主启动类:
@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401 {
public static void main(String[] args) {
SpringApplication.run(MainApp8401.class,args);
}
}
controller
@RestController
public class FlowLimitController {
@GetMapping("/testA")
public String testA(){
return "----testA";
}
@GetMapping("/testB")
public String testB(){
return "----testB";
}
}
测试:
Sentinel采用的懒加载,需要访问一次即可
流控规则
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pJzziBsx-1620878286724)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512144049128.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uA1BT1dz-1620878286725)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512144446250.png)]
预热:
阙值除以coldFactor(默认值为3),经过预热时长后才会达到阙值
排队等待:
匀速排队方式会严格控制请求通过的间隔时间,也就是让请求以均匀的速度通过,对应的是漏桶算法
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
降级规则
熔断降级概述:
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-APD88L5l-1620878286726)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512154021702.png)]
Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
Sentinei的断路器是没有类似Hystrix半开状态的。(Sentinei 1.8.0 已有半开状态)
半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。
Sentinel降级-RT
平均响应时间(DEGRADE_GRADE_RT):当1s内持续进入5个请求,对应时刻的平均响应时间(秒级)均超过阈值( count,以ms为单位),那么在接下的时间窗口(DegradeRule中的timeWindow,以s为单位)之内,对这个方法的调用都会自动地熔断(抛出DegradeException )。注意Sentinel 默认统计的RT上限是4900 ms,超出此阈值的都会算作4900ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx来配置。
慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
异常比例:
异常比例(DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值( DegradeRule中的 count)之后,资源进入降级状态,即在接下的时间窗口( DegradeRule中的timeWindow,以s为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[0.0, 1.0],代表0% -100%
异常数:
异常数( DEGRADE_GRADF_EXCEPTION_COUNT ):当资源近1分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若timeWindow小于60s,则结束熔断状态后码可能再进入熔断状态。
热点Key限流
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false)String p1,
@RequestParam(value = "p2",required = false)String p2){
return "----testHotKey";
}
public String deal_testHotKey(String p1, String p2, BlockException exception){
return "----deal_testHotKey";
}
方法testHotKey里面第一个参数只要QPS超过每秒1次,马上降级处理,用自己定义的方法处理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-npFpnpPe-1620878286727)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512162238097.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-beVXrmuQ-1620878286728)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512162244219.png)]
系统规则
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EZXmorx4-1620878286729)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512162632411.png)]
@SentinelResource
@GetMapping("/byResource")
@SentinelResource(value = "byResource",blockHandler = "handleException")
public CommonResult byResource(){
return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
}
public CommonResult handlerException(BlockException exception){
return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
}
兜底方案面临的问题:
- 系统默认的,没有体现我们自己的业务要求
- 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观
- 每个业务方法都添加一个兜底的,使代码膨胀加剧
- 全局统一的处理方法没有体现
解决方法:
创建CustomerBlockHandler类用于自定义限流处理逻辑
自定义限流处理类:
public class CustomerBlockHandler {
public static CommonResult handlerException(BlockException exception){
return new CommonResult(4444,"按客户自定义,global handlerException---1");
}
public static CommonResult handlerException2(BlockException exception){
return new CommonResult(4444,"按客户自定义,global handlerException----2");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hXh7RviM-1620878286730)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512170646630.png)]
服务熔断
无配置:
给客户error页面不友好
@RestController
@Slf4j
public class CircleBreakerController {
private 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;
}
}
只配置fallback
@RestController
@Slf4j
public class CircleBreakerController {
private static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
@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;
}
/**
* 本例是fallback
*/
public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(444,"兜底异常handlerFallback,exception内容 "+e.getMessage(),payment);
}
}
只配置blcokHandler
@RestController
@Slf4j
public class CircleBreakerController {
private static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
@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;
}
/**
* 本例是blockHandler
*/
public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
Payment payment = new Payment(id, "null");
return new CommonResult<>(445, "blockHandler-sentinel限流,无此流水: blockException " + blockException.getMessage(), payment);
}
}
fallback和blockHandler都配置:
@RestController
@Slf4j
public class CircleBreakerController {
private static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler")
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;
}
/**
* 本例是fallback
*/
public CommonResult handlerFallback(@PathVariable Long id, Throwable e) {
Payment payment = new Payment(id, "null");
return new CommonResult<>(444, "兜底异常handlerFallback,exception内容 " + e.getMessage(), payment);
}
/**
* 本例是blockHandler
*/
public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
Payment payment = new Payment(id, "null");
return new CommonResult<>(445, "blockHandler-sentinel限流,无此流水: blockException " + blockException.getMessage(), payment);
}
}
若blockHandler和fallback都进行不配置,则被限流降级而抛出BlockException时只会进入blockHandler处理逻辑
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B2vXE3t9-1620878286732)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512190551243.png)]
持久化规则
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CDTvCP43-1620878286733)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512200144066.png)]
YML配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zmsk8gpM-1620878286734)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512200311750.png)]
Nacos业务配置:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n9vO2MkU-1620878286735)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512200155775.png)]
分布式事务
单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成,此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证
Seata
分布事务处理的1ID+三组件模型
Transaction ID 全局唯一的事务XID
组件概念:
- Transaction Coordinator:事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚
- Transaction Manager:控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议
- Resource Manager:控制分支事务,负责分支注册,状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚
处理过程:
1、TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID
2、XID在微服务调用链路的上下文中传播
3、RM向TC注册分支事务,将其纳入XID对应全局事务的管辖
4、TM向TC发起针对XID的全局提交或回滚决议
5、TC调度XID下管辖的全部分支事务完成提交或回滚请求
ockHandler(@PathVariable Long id, BlockException blockException) {
Payment payment = new Payment(id, “null”);
return new CommonResult<>(445, "blockHandler-sentinel限流,无此流水: blockException " + blockException.getMessage(), payment);
}
}
fallback和blockHandler都配置:
```java
@RestController
@Slf4j
public class CircleBreakerController {
private static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler")
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;
}
/**
* 本例是fallback
*/
public CommonResult handlerFallback(@PathVariable Long id, Throwable e) {
Payment payment = new Payment(id, "null");
return new CommonResult<>(444, "兜底异常handlerFallback,exception内容 " + e.getMessage(), payment);
}
/**
* 本例是blockHandler
*/
public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
Payment payment = new Payment(id, "null");
return new CommonResult<>(445, "blockHandler-sentinel限流,无此流水: blockException " + blockException.getMessage(), payment);
}
}
若blockHandler和fallback都进行不配置,则被限流降级而抛出BlockException时只会进入blockHandler处理逻辑
[外链图片转存中…(img-B2vXE3t9-1620878286732)]
持久化规则
[外链图片转存中…(img-CDTvCP43-1620878286733)]
YML配置
[外链图片转存中…(img-zmsk8gpM-1620878286734)]
Nacos业务配置:
[外链图片转存中…(img-n9vO2MkU-1620878286735)]
分布式事务
单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成,此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证
Seata
分布事务处理的1ID+三组件模型
Transaction ID 全局唯一的事务XID
组件概念:
- Transaction Coordinator:事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚
- Transaction Manager:控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议
- Resource Manager:控制分支事务,负责分支注册,状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚
处理过程:
1、TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID
2、XID在微服务调用链路的上下文中传播
3、RM向TC注册分支事务,将其纳入XID对应全局事务的管辖
4、TM向TC发起针对XID的全局提交或回滚决议
5、TC调度XID下管辖的全部分支事务完成提交或回滚请求
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BmRt0c4S-1620878286736)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210512204128114.png)]