SpringCloud
1.了解SpringCloud
版本问题
首先Spring cloud的版本号是以英文来区分的,而Springboot是以数字来区分的
Spring Cloud的版本用英文来区分的原因是因为根据火车站名来定的
它们俩之间的版本约束
这里需要注意的是:并不是都用最新版的就是最好的,而是有一定的约束条件,根据上下图来选择最佳版本搭配使用
这里也有我们版本的最佳选择
升级引发的惨案
2.配置运行环境
首先搭建一个父工程的maven项目
然后只留一个pom.xml文件,并且引入以下信息
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>SpringCloudStudy</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <!--统一管理jar包版本--> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.12</junit.version> <lombok.version>1.18.18</lombok.version> <log4j.version>1.2.17</log4j.version> <mysql.version>8.0.18</mysql.version> <druid.version>1.1.16</druid.version> <mybatis.spring.boot.version>2.1.1</mybatis.spring.boot.version> </properties> <!--子模块继承之后,提供作用:锁定版本+子module不用谢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 阿里巴巴--> <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> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <!-- druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> </dependency> <!--log4j--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </dependency> </dependencies> </dependencyManagement> <!--bulid是这样的用springboot默认的build方式--> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork> <addResources>true</addResources> </configuration> </plugin> </plugins> </build> </project>
然后创建子工程的maven项目
子工程的pom.xml文件引入以下内容
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>SpringCloudStudy</artifactId> <groupId>org.example</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>payment</artifactId> <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.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</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-test</artifactId> </dependency> </dependencies> </project>
子工程的resource目录下创建一个application.yml文件
server: port: 8001 spring: application: name: payment datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: org.gjt.mm.mysql.Driver url: jdbc:mysql://localhost:3306/user?serverTimezone=UTC username: root password: root mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: springcloud.hwm.entity
建立数据库表
CREATE TABLE `payment` ( `id` BIGINT(20) not NULL auto_increment COMMENT 'ID', `serial` VARCHAR(200) DEFAULT'', primary key(`id`) )ENGINE=INNODB auto_increment=1 DEFAULT CHARSET=utf8;
编写service-dao-controller的一系列代码
3.Eureka
基础知识
什么是服务治理
在传统的RPC远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,所以需要使用服务治理管理其依赖关系,并实现服务调用、负载均衡、容错等,实现服务注册与发现
什么是服务注册与发现
- Eureka Server作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务使用Eureka客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。
- 在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把自己服务器的信息注册到注册中心上,然后消费者、服务提供者就可以以别名的方式获取实际的服务通讯地址,然后再实现本地RPC调用远程RPC框架的思想:远程RPC框架中都会有一个注册中心存放服务地址相关信息
Eureka包含两个组件
- Eureka Server:各个微服务节点通过配置启动后都再Eureka Server中进行注册
- EurekaClient:是一个Java客户端,用于简化Eureka Server的交互。在应用启动后,会向Eureka Server发送心跳,Eureka Server通过心跳对其进行管理,如果在某个周期没有收到心跳,那么它就会将服务注册表中的这个服务节点移除
Eureka自我保护机制:
- 在某一时刻微服务不可用的时候(微服务本身是健康的,但是可能由于网络延迟等其他原因导致没能及时发送心跳数据包),Eureka不会立刻清理该微服务,而是对这个微服务的信息进行保存。
- 属于CAP里面的AP分支
Eureka的使用
Eureka Server端的创建
首先得有得创建一个maven工程,这个工程作为我们的Eureka Server端
在pom.xml中添加下面的依赖
<dependencies> <dependency> <!-这里就是导入Eureka Server的依赖 --> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>org.example</groupId> <artifactId>Common</artifactId> <version>1.0-SNAPSHOT</version> <scope>compile</scope> </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> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> </dependencies>
编写application.yml文件
# 设定端口号 server: port: 7001 eureka: instance: #设定服务端的主机名 hostname: eureka7001.com client: #是否注册自己 register-with-eureka: false fetch-registry: false #服务的URL service-url: defaultZone: http://eureka7003.com:7003/eureka/
编写主启动类
@SpringBootApplication //开启Eureka Server的注解 @EnableEurekaServer public class EurekaMain { public static void main(String[] args) { SpringApplication.run(EurekaMain.class,args); } }
如果服务端创建多个,那么在yml文件中,最后的URL就是映射到其他服务端,有几个,就映射几个
Eureka Client端创建
在pom文件中添加下面依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
编写yam文件
eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka instance: # 显示的是自定义的id地址 instance-id: payment8001 prefer-ip-address: true # 这里因为服务端不是一个,所以url不是一个
主启动类
@SpringBootApplication @EnableEurekaClient @EnableDiscoveryClient public class PaymentMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentMain8001.class,args); } }
编写业务代码
这里注意:如果编写的是消费者的客户端,那么我们可以在自定义配置类中加下面得Bean对象
@Configuration public class SpringConfig { @Bean @LoadBalanced //实现负载均衡 public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
然后在Controller中使用RestTemplate进行转发请求。此时,我们不需要知道请求的路径,因为此时我们的路径映射为
spring: application: name: order
请求直接写成http://order
4.Zookeeper的使用
首先要在Linux系统中,启动Zookeeper注册中心
在项目的pom文件中多添加一个依赖
然后编写yml文件
主启动类
在启动的时候,会报错,是因为Zookeeper依赖里面含有Zookeeper的旧版本,使之与我们在Linux系统安装的Zookeeper版本不匹配导致报错
解决办法:
思考:Zookeeper的服务节点是临时节点还是持久节点
答:是临时节点,它相比较于eureka,没有保护机制,而是在某一段时间内,如果客户端没有发送心跳数据包就直接删除。
5.Consul
简介:
Consul是一套开源的分布式服务发现和配置管理系统,又HashiCorp公司用Go语言开发
提供了微服务系统中的服务治理、配置中心、控制总线、等功能。功能可单独使用,也可以一起使用构建服务网络,总之Consul提供了一种完整的服务网络解决风格
优点:基于raft协议,简洁;支持健康检查,同时支持HTTP和DNS协议,支持跨数据中心的WAN集群 提供图形界面 跨平台支持Linux、Mac、Windows
功能
- 服务发现:提供HTTP和DNS两种发现方式
- 健康检测:支持多种方式:HTTP、TCP、Docker、Shell脚本定制化
- KV存储:KEY Value的存储方式
- 多数据中心: Consul支持多数据中心
- 可视化Web界面
下载安装
- 先去Consul官网下载
- 完成之后打开压缩包,看到Consul.exe文件,在此打开cmd窗口
- 输入命令:consul agent -dev,运行成功
- 访问http://localhost:8500
使用
pom文件中加入以下依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency>
编写yml文件
server: port: 80 spring: application: name: consul-order cloud: consul: host: localhost port: 8500 discovery: service-name: ${spring.application.name}
主启动类
@SpringBootApplication @EnableDiscoveryClient public class Consul_order80 { public static void main(String[] args) { SpringApplication.run(Consul_order80.class,args); } }
编写业务代码,验证注册是否成功,以及是否能访问数据。
6.三大注册中心的异同点
7.Ribbon
简介:
ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具
Ribbon是Netflix发布的开源项目,主要功能是提供 客户端的软件负载均衡算法和服务调用
我们可以很容易的使用Ribbon实现自定义的负载均衡算法
负载均衡
负载均衡是什么?
- 简单的说就是将用户的请求平摊的分配到多个服务器上,从而达到系统HA(高可用)
- 常见的负载均衡又软件Nginx、LVS、硬件F5
Ribbon本地负载均衡客户端 VS Nginx服务端负载均衡
- Nginx是服务器负载均衡,客户端所有的请求都会交给nginx,然后由nginx实现转发请求。即负载均衡是由服务器实现的。
- Ribbon本地负载均衡,在调用微服务接口的时候,会在注册中心上获取注册信息服务列表后缓存到JVM本地,从而在本地实现RPC远程服务调用技术
负载均衡分为两种:集中式LB、进程内LB
集中式LB:在服务的消费方和提供方之间使用独立的LB设施,由该设施负责把访问请求通过某种策略转发至服务的提供方
进程内LB:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器
Ribbon属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取服务器提供方的地址。
Ribbon在工作时分为两步
- 第一步:先选择EurekaServer,它优先选择在同一个区域内负载较少的server
- 第二步:根据用户指定的策略,从server取到的服务注册列表中选择一个地址
- Ribbon提供多种策略:轮询、随机、根据响应时间加权等。
Ribbon的使用
首先我们如果在项目中添加了
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
那么我们就不需要再添加Ribbon的依赖,因为新版的Eureka依赖包中包含了Ribbon依赖,所以我们可以直接使用
实现负载均衡肯定是在集群的环境下进行的,所以我们在80消费者端口的Configuration的Rest Template上面加上@LoadBalancer注解
访问的地址直接填写spring application name 即可
Ribbon的负载均衡详解
- 负载均衡分为哪几种方式
- 负载均衡的算法:
负载均衡的接口与类实现
查看源码,首先进入到IRul接口中,然后CTRL+Alt+b显示其所有的实现类
RoundRobinRule就是轮询负载均衡的类
//这里是对服务地址的选择 public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { //如果lb是空,会提示没有负载均衡 log.warn("no load balancer"); return null; } else { Server server = null; int count = 0; while(true) { if (server == null && count++ < 10) { List<Server> reachableServers = lb.getReachableServers(); List<Server> allServers = lb.getAllServers(); int upCount = reachableServers.size();//这里是第几次请求数 int serverCount = allServers.size();//这里是服务总集群数 if (upCount != 0 && serverCount != 0) { int nextServerIndex = this.incrementAndGetModulo(serverCount); server = (Server)allServers.get(nextServerIndex); if (server == null) { Thread.yield(); } else { if (server.isAlive() && server.isReadyToServe()) { return server; } server = null; } continue; } log.warn("No up servers available from load balancer: " + lb); return null; } if (count >= 10) { log.warn("No available alive servers after 10 tries from load balancer: " + lb); } return server; } } } private int incrementAndGetModulo(int modulo) { int current;//当前的服务端口号 int next;//下一个服务端口号 do { current = this.nextServerCyclicCounter.get(); next = (current + 1) % modulo; } while(!this.nextServerCyclicCounter.compareAndSet(current, next)); return next; }
手写Ribbon轮询负载均衡算法并使用
过程图:
首先去掉在order工程,下面的Configuration类,里面自动注入RestTemplate的@LoadBalanced注解
编写LoadBalancer接口
@Component public interface LoadBalancer { //这里主要是获取所有的微服务端 ServiceInstance instances(List<ServiceInstance> serviceInstances); }
编写MyLB
package springcloud.hwm.LB; @Component public class MyLB implements LoadBalancer { //必须有一个原子型整数,这里设置的初始值为0 private AtomicInteger atomicInteger = new AtomicInteger(0); public final int getAndIncreament() { int current;//当前的访问次数 int next;//下一次访问次数 do { current = this.atomicInteger.get();//当前的访问次数先从这个原子型整数这里获取; //2147483647这个数是整型的最大值,如果大于这个数就从0记起,如果不大于就等于当前访问次数+1 next = current >= 2147483647 ? 0 : current + 1; }while (!this.atomicInteger.compareAndSet(current,next)); //这里的compareAndSet,使用的是CAS原理,CAS相比Synchronized,避免了锁的使用,总体性能比Synchronized高很多. //这里我们分析一下CAS的方法是如何进行使用的: //在这个循环条件下,假设有多个线程在同时执行这段代码,即使有一个线程在产生了新值之后, // 它还需要让它自己产生的新值与旧值比较之后才能决定要不要这个新值。也就是说,如果oldValue是10, // 而产生的新值是20,然后程序还没到while的时候,有其他线程修改了newValue值,那当这个线程到while判断的时候, // 会出现comparAndSet方法的预期值不跟实际值一样,导致方法返回false,直到没有其他线程干扰。这时就确定了新产生的值。 //那么根据以上的原理,只有当预期值和实际值相同的时候,方法返回的是True,那么这个时候while循环内是false,就会跳出循环 并且的到我们的预期值 return next; } @Override //这个就是获取服务端的所有信息,根据信息我们来返回我们想要的值 public ServiceInstance instances(List<ServiceInstance> serviceInstances) { //这里就是轮询算法的关键之处,也就是我们所说的 访问次数 % 服务集群数 = 实际调用服务的下标 int index = getAndIncreament() % serviceInstances.size(); //通过下标选择返回哪一个微服务端 return serviceInstances.get(index); } }
编写OrderController类
@GetMapping("/customer/payment/lb") public String getLB(){ List<ServiceInstance> instances = discoveryClient.getInstances("payment"); if (instances==null&&instances.size()<=0){ return null; } ServiceInstance serviceInstance = loadBalancer.instances(instances); URI uri = serviceInstance.getUri(); log.info("当前的URL为+"+uri+"/payment/lb"); return restTemplate.getForObject(uri+"/payment/lb",String.class); }
8.OpenFeign
简介
Feign是一个声明式的web Service客户端。使用Feign能让编写WebService客户端更加简单,它的使用方法是 定义一个服务接口,然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。spring cloud对Feign进行了封装,使其支持了SpringMVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡
Feign能干什么
Feign旨在使编写javaHttp客户端变得更容易
前面在使用Ribbon+RestTemplate时,利用Rest Template对http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发过程中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由它来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在时一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
Feign集成了Ribbon
利用Ribbon维护了payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用
Feign与OpenFeign的区别
Feign的使用
创建一个消费者项目
编写pom文件
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</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> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> </dependency> <dependency> <groupId>org.example</groupId> <artifactId>Common</artifactId> <version>1.0-SNAPSHOT</version> <scope>compile</scope> </dependency> </dependencies>
编写yam文件
server: port: 80 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka
主启动类
@SpringBootApplication @EnableFeignClients //一定要添加这个注解 public class OrderFeignMain { public static void main(String[] args) { SpringApplication.run(OrderFeignMain.class,args); } }
编写service接口
@Component @FeignClient("payment") public interface FeignService { @GetMapping("/payment/findById/{id}") public CommonResult<payment> findById(@PathVariable("id") long id); }
编写controller
@RestController public class FeignController { @Resource private FeignService service; @GetMapping("/customer/findById/{id}") public CommonResult<payment> findById(@PathVariable("id") long id){ return service.findById(id); } }
注意:当报404错误的时候,一定要看好自己的地址是否正确的编写,细心检查(报错点)
Feign的超时控制
项目启动,Feign客户端默认只等待1s,但是服务端需要超过1s,导致Feign客户端不想等待了,直接返回报错。为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。
案例:
先假设超时:
@GetMapping("/TimeOut")
public String paymentTimeOut(){
//这里设置延迟的时间为3秒
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return serverPort;
}
然后通过消费者去调用:会报错
这时候我需要通过yml进行配置
依然报错:
原因:因为在消费者的pom文件中,导入了Eureka和Feign两个依赖包,他们下面都包含Ribbon依赖包,导致包冲突,并且不能直接通过Ribbon来设置成功
# 设置feign客户端超时时间
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeOut: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeOut: 5000
#这个是不成功的,即使启动也会报错
正确的修改方式:
feign:
client:
config:
default:
#简历连接所用的时间,适用于网络状况正常的情况下,两端连接所需要的时间
ConnectTimeOut: 5000
#指建立连接后从服务端读取到可用资源所用的时间
ReadTimeOut: 10000
日志增强
Feign提供了日志打印功能,可以通过配置来调整日志级别,从而了解Feign中Http请求的细节。说白了就是对Feign接口的调用情况进行监控和输出
使用:
开启日志增强功能:
@Configuration public class Config { //开启详细日志 @Bean Logger.Level Log(){ return Logger.Level.FULL; } }
配置yml文件
logging:
level:
#feign日志以什么级别监控哪个接口
springcloud.hwm.service.FeignService: debug
日志级别:
9.Hystrix
概念
- Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
- “断路器”本身是一种开关装置,当某个服务单元发生故障后,通过断路器的故障监控(类似于熔断保险丝),向调用方法返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方法的线程不会被长时间、不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
- 服务降级:
- 服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback
- 哪些情况会发出降级:
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池、信号量打满也会导致服务降级
- 服务熔断:
- 类比保险丝,达到最大服务访问后直接拒绝访问,拉闸限电,然后调用服务降级方法并返回友好提示
- 服务的降级->进而熔断->恢复调用链路
- 服务限流:
- 秒杀高并发等操作,严禁一窝蜂的过来拥挤,排队有序进行
Hystrix的服务降级使用
首先创建一个消费者客户端(一般都是使用在消费者客户端)
pom文件添加以下依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
yml文件的编写
server: port: 80 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka # 这里设置之后,后面的超时测试老是报错 #原因:关键在于feign:hystrix:enabled: true的作用,官网解释“Feign将使用断路器包装所有方法”,也就是将@FeignClient标记的那个service接口下所有的方法进行了hystrix包装(类似于在这些方法上加了一个@HystrixCommand),这些方法会应用一个默认的超时时间为1s,所以你的service方法也有一个1s的超时时间,service1s就会报异常,controller立马进入备用方法,controller上那个3秒那超时时间就没有效果了。 feign: hystrix: enabled: true #解决: hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 3000 #然后ribbon的超时时间也需加上 ribbon: ReadTimeout: 5000 ConnectTimeout: 5000
主启动类的编写
@SpringBootApplication @EnableFeignClients @EnableHystrix public class FeignHystrixOrderMain { public static void main(String[] args) { SpringApplication.run(FeignHystrixOrderMain.class,args); } }
service层的编写
@Service //这里的fallback就是利用一个实现类,完成所有方法的降级处理,这样可以程序耦合度降低,而不会把所有的降级方法都写在Servcie或者Controller类里面,造成代码膨胀且杂乱 @FeignClient(value = "PAYMENT-HYSTRIX",fallback = FeignHystrixServiceImpl.class) public interface FeignHystrixService { @GetMapping("/payment/hystrix/ok/{id}") public String payment_ok(@PathVariable("id")Integer id); @GetMapping("/payment/hystrix/TimeOut/{id}") public String payment_TimeOut(@PathVariable("id")Integer id); }
Controller类的编写
@RestController @Slf4j //这个是定义全局的降级方法,前提是加了@HystrixCommand注解。 @DefaultProperties(defaultFallback = "globolMethod") public class FeignHystrixOrder80Controller { @Resource private FeignHystrixService feignHystrixService; @HystrixCommand @GetMapping("/customer/payment/hystrix/ok/{id}") public String payment_ok(@PathVariable("id")Integer id){ int a= 10/0; return feignHystrixService.payment_ok(id); } @GetMapping("/customer/payment/hystrix/TimeOut/{id}") //这个是特定的降级方法 @HystrixCommand(fallbackMethod = "TimeOutHandler",commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000") }) public String payment_TimeOut(@PathVariable("id")Integer id){ return feignHystrixService.payment_TimeOut(id); } public String TimeOutHandler(Integer id){ return "系统繁忙,请稍后再试!!!!"+id; } public String globolMethod(){ return "系统出错"; }
服务熔断
熔断机制概述:
熔断机制是应对雪崩效应的一种微服务链路的保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长,会进行服务降级,进而熔断该节点微服务的调用,快速返回错误响应信息,当检测到该节点微服务调用响应正常后,恢复调用链路
在Spring Cloud框架中,熔断机制通过Hystrix实现,Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5s内20次调用失败,就会启动熔断机制。熔断机制的注解@HystrixCommand。
- 熔断过程中的三大状态:
- 熔断的三个重要参数:
- 熔断过程:
- 熔断最后的恢复机制:
Hystrix图形化监控
除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控,Hystrix会持续的继续所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求,多少失败。Netflix通过hystrix-metrics-event-steam项目实现了对以上指标的监控。Spring cloud也提供了HystrixDashboard的整合,对监控内容转化成可视化界面
如何使用:
首先,添加依赖
<!-- 导入图形监控的依赖--> <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-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
yml文件
server: port: 9001
主启动类
@SpringBootApplication @EnableHystrixDashboard public class DashboardMain { public static void main(String[] args) { SpringApplication.run(DashboardMain.class,args); } }
这里在客户端的启动类里面添加
如果不添加网页会报:Unable to connect to Command Metric Stream.
/** * 此配置是为了服务监控而配置的,与服务容错本身无关,spring cloud升级后的坑 * Servlet RegistrationBean 因为springboot的默认路径不是/hystrix.stream, * 只要在自己的项目里配置下面得servlet就可以了 */ @Bean public ServletRegistrationBean getServlet() { HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet(); ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet); registrationBean.setLoadOnStartup(1); registrationBean.addUrlMappings("/hystrix.stream"); registrationBean.setName("HystrixMetricsStreamServlet"); return registrationBean; }
10.GateWay
简介:
- SpringCloud GateWay是Spring Cloud的一个全新项目,基于Spring5.0+SpringBoot2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供一种简单有效的同意的API路由管理方式
- SpringCloud GateWay作为SpringCloud生态系统中的网关,目标是替代Zuul,在SpringCloud2.0以上版本中,没有对新版本的Zuul2.0以上最新高性能版本进行集成,仍然还是使用Zuul1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于web Flux框架实现的,而web Flux框架底层则使用了高性能的Reactor模式通信框架Netty。
- SpringCloud GateWay的目标提供统一的路由方式且基于Filter链的方式提供了网关基本的功能
为什么选择GateWay
- Netflix不太靠谱,zuul2.0一直跳票,迟迟不发布
- 一方面因为Zuul1.0已经进入了维护阶段,而且Gateway是SpringCloud团队研发的,是亲儿子产品,值得信赖。而且很多功能比起Zuul是非常简单便捷的
- GatWay是基于异步非阻塞模型上进行开发的,性能方面不需要担心,虽然Netflix早就发布了最新的Zuul2.x,但Spring cloud貌似没有整合计划。而且Netflix相关组件都宣布维护期,不知前景如何
- SpringCloud GateWay具有如下特性
- 基于Spring5.0+SpringBoot2.0和Project Reactor进行构建
- 动态路由:能够匹配任何请求属性
- 可以对路由指定Predicate(断言)和Filter(过滤器)
- 集成Hystrix的断路器功能
- 集成Spring cloud服务发现功能
- 易于编写的Predicate和Filter
- 请求限流功能
- 支持路径重写
- SpringCloud GateWay于Zuul的区别
- Zuul1.x是一个基于阻塞IO的API Gateway
- Zuul 1.x基于Servlet2.5使用阻塞架构它不支持任何长连接(如web Socket)Zuul的设计模式和Nginx较像,每次IO操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用Java实现,而JVM本身会有第一次加载较慢的情况,使得Zuul的性能相对较差
- Zuul2.x理念更先进,想基于Netty非阻塞和支持长连接,但Spring cloud目前还没有整合。Zuul2.x的性能较Zuul1.x有较大提升。在性能方面,根据官方提供的基准测试,SpringCloud GateWay的RPS(每秒请求数)是Zuul的1.6倍
- SpringCloud GateWay建立在Spring5.0+SpringBoot2.0和Project Reactor之上,使用非阻塞API
- SpringCloud GateWay还支持web Socket,并且与Spring紧密集成拥有更好的开发体验
Zuul模型
Spring cloud中所集成的zuul版本,采用的是Tomcat容器,使用的是传统的Servlet IO处理模型
servlet由servlet container进行生命周期管理
- container启动时构建servlet对象并调用servlet init进行初始化
- container运行时,接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用Service
- container关闭时调用destory销毁
上述模型的缺点
servlet是一个简单的网络IO模型,当请求进入servlet container时,就会为其绑定一个线程,在并发不高的场景下这种模型是适用的,但是一旦高并发(比如抽风用jemeter压),线程数量就会上涨,而线程资源代价是昂贵的(上下文切换,内存消耗大)严重影响请求的处理时间。在一些简单业务场景下,不希望为每个request分配一个线程,只需要1个或者几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势
所以zuul1.x是基于servlet之上的一个阻塞式处理模型,即spring实现了处理所有request请求的一个servlet并由该servlet阻塞式处理,所以Zuul无法摆脱servlet模型的弊端
Gateway的三个核心概念
- 路由:路由时构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
- 断言:开发人员可以匹配HTTP请求中的所有内容(请求头,请求参数),如果请求与断言相匹配则进行路由
- 过滤:指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改
客户端向Spring cloudGateWay发出请求,然后再Mapping中找到与请求相匹配的路由,将其发送到GateWayWebHandler
Handler再通过指定的过滤器链将请求发送到实际的服务执行业务逻辑,然后返回
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前或者之后执行业务逻辑
Filter在”pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,
在”post”类型的过滤器中可以做响应内容、响应头的修改、日志的输出,流量监控等有着非常重要的作用
Gateway的使用
创建一个项目
pom文件导入
<dependencies> <!-- 首先得说明以下,必须把web和actuator这两个依赖除去---> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</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-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-test</artifactId> </dependency> <dependency> <groupId>org.example</groupId> <artifactId>Common</artifactId> <version>1.0-SNAPSHOT</version> <scope>compile</scope> </dependency> </dependencies>
yml文件
server: port: 9527 spring: application: name: gateway cloud: gateway: discovery: locator: enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由 routes: # 这下面不仅可以配置断言,还可以配置filter - id: payment_route #路由的id,没有固定规则但要求唯一,建议配合服务名 # uri: http://localhost:8001 uri: lb://PAYMENT predicates: - Path=/payment/findById/** #断言,路径相匹配的进行路由 - id: payment_route2 uri: lb://PAYMENT # 匹配后提供服务的路由 predicates: - Path=/payment/lb/** eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka instance: hostname: gateway-service
主启动类
@SpringBootApplication @EnableEurekaClient public class GatwayMain { public static void main(String[] args) { SpringApplication.run(GatwayMain.class,args); } }
配置gateway路由的第二种方法
@Configuration public class GatewayConfig { @Bean public RouteLocator customerRouteLocator2(RouteLocatorBuilder builder){ RouteLocatorBuilder.Builder routes =builder.routes(); routes.route("route_payment3",r->r.path("/guoji").uri("http://news.baidu.com/guoji")).build(); return routes.build(); } }
配置全局Filter
@Component @Slf4j public class MyFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("我的过滤器"+ 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; } }
11.spring cloud Config 配置总控中心
是什么?
spring cloud config 为微服务框架中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个 中心化的外部配置
怎么用?
spring cloud config分为 服务端和客户端两部分
服务端也称为 分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息,配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。
能干嘛?
1. 集中管理配置文件
2. 不同环境不同配置,动态化的配置更新,分环境部署。比如dev/test/prod/beta/release
3. 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉去配置自己的信息
4. 当配置发生变动时,服务不需要重启即可感知到配置文件的变化,并应用新的配置
5. 将配置信息以REST接口的形式暴露
操作使用
新建两个工程,一个是config服务端,一个是config客户端
<!-这里是服务端导入的依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <!-这里是客户端导入的依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
yml文件编写
#这里是服务端需要编写的yml文件 server: port: 3344 spring: application: name: config-center cloud: config: server: git: uri: https://gitee.com/hou-wenming/springcloud-config.git #上面的git仓库名字 search-paths: - springcloud-config # skip-ssl-validation: true # username: 156542114@qq.com # password: hwm2000916.. default-label: master eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka instance: hostname: config-center
#这里是客户端编写的yml文件 #命名必须是bootstrap.yml 否则无法获取中心仓库的内容 server: port: 3355 spring: application: name: config-client #config客户端配置 cloud: config: label: master # 分支名称 name: config # 配置文件名称 profile: dev # 读取后缀名称 uri: http://localhost:3344 # 配置中心地址 # discovery: # enabled: true eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka instance: hostname: config-client # 暴漏监控端点 management: endpoints: web: exposure: include: "*"
主启动类
//服务端 @SpringBootApplication @EnableConfigServer public class ConfigCenterMain { public static void main(String[] args) { SpringApplication.run(ConfigCenterMain.class,args); } }
//客户端 @SpringBootApplication @EnableEurekaClient public class ConfigClientMain { public static void main(String[] args) { SpringApplication.run(ConfigClientMain.class,args); } }
controller类
@RestController @RefreshScope//这个是解决配置中心内容修改,不需要重启的问题 public class ConfigClientController { @Value("${server.info}") private String configInfo; @GetMapping("/configInfo") public String getConfig(){ return configInfo; } }
当我们在配置中心修改了文件的时候,这时候服务端的数据可以实时更新,但是客户端不行,只能重启获取数据(如果服务端庞大,重启将会崩毁)
解决办法:暴漏监控端点
# 暴漏监控端点 management: endpoints: web: exposure: include: "*"
//在Controller类上面加上这个注解 @RefreshScope
最后在cmd命令行中发送一个POST请求
curl -X POST “http://localhost:3355/actuator/refresh"
12.bus总线
前提:
bug总线解决了上面我们的spring cloud config最后因为在远程配置中心修改文件的时候,我们的客户端不能实时的更新配置,而是需要重启才行,然后解决方法是发送一个post请求解决问题,但是我们每次更新都需要发送一个请求未免太麻烦了,这时候bus总线就体现了它的使用价值
简介:
spring cloud bus 配合 spring cloud config 使用可以实现配置的动态刷新
spring cloud bus是用来将分布式系统的节点与轻量级消息系统连接起来的框架,它整合了Java的事件处理机制和消息中间件的功能。spring cloud bus目前支持RabbitMQ和Kafka
这个图是bus通过向一个客户端进行发送,然后这个客户端再向其他客户端进行转发
spring cloud bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、时间推送等,也可以当作微服务间的通信通道
为什么被称为操作总线
什么是总线
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来,由于 该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便的广播一些需要让其他连接在该主题上的实例都知道的消息
基本原理
configClient实例都监听MQ中同一个topic(默认是springcloudbus)。当一个服务刷新数据的时候,它会把这个信息放入到topic中,这样其他监听同一topic的服务就能得到通知,然后去更新自身的配置
实际操作使用
配置pom文件
//使用总线,必须在客户端和服务端添加这个依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
配置yam文件
#这个是配置中心,需要用mq的支持 # rabbitmq的相关配置 rabbitmq: host: localhost port: 5672 username: guest password: guest management: endpoints: web: exposure: include: 'bus-refresh'
后面的配置和前面的配置中心一样
如何去操作:
- 首先我们得启动我们的eureka注册中心,以及配置中心,和配置客户端
- 然后修改一下我们的中心配置文件
- 在cmd中发送这个请求:curl -X POST “http://localhost:3344/actuator/bus-refresh"
- 登陆rabbit MQ进行广播
- 成功!
- 也可以针对性的进行广播 在cmd中发送这个请求:curl -X POST “http://localhost:3344/actuator/bus-refresh:config-client:3355"
13.springcloud stream消息中间件
简介:
一句话:屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
官方定义springcloud stream是一个构建消息驱动微服务的框架
应用程序通过inputs或者outputs来与springcloud stream对象交互,通过配置来bingding(绑定),而springcloud stream的binder对象负责与消息中间件交互,所以我们只需要弄清楚如何与springcloud stream交互就可以方便使用消息驱动的方式
通过spring integration来连接消息代理中间件以实现消息事件驱动,springcloud stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布订阅、消费组、分区的三个核心概念
Stream凭什么可以统一底层差异
通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各个不同的消息中间件实现
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离
设计思想
- 标准MQ
- 生产者/消费者之间靠消息媒介传递信息内容 Message
- 消息必须走特定的通道 消息通道Message Channel
- 消息通道里的消息如何被消费呢,谁负责收发处理 消息通道MessageChannel的子接口SubscribleChannel,由MessageHandler消息处理器所订阅
- 为什么用cloudStream?
- 这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候spring cloud stream 给我们提供了一种解耦合的方式
- Stream中的消息通信方式遵循了发布-订阅模式
- Binder:很方便的连接中间件,屏蔽差异
- Channel:通道,是队列的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过channel对队列进行配置
- Source和SInk:简单的可理解为参照对象是SpringCloudStream自身,从Stream发布消息就是输出,接收消息就是输入
SpringCloudStream标准流程套路
如何使用?
创建stream流的服务端和客户端
导入pom文件
<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.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-test</artifactId> </dependency> <dependency> <groupId>org.example</groupId> <artifactId>Common</artifactId> <version>1.0-SNAPSHOT</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--导入stream rabbit依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency> </dependencies>
编写yml文件
server: port: 9902 spring: application: name: stream-customer cloud: stream: binders: # 在此处配置要绑定的rabbitmq的服务信息 defaultRabbit: # 表示定义的名称,用于bingding整合 type: rabbit #消息组件类型 environment: #设置rabbitmq的相关的环境配置 spring: rabbitmq: host: localhost port: 5672 username: guest password: guest bindings: #服务的整合处理 input: #这个名字是一个通道的名称 destination: studyExchange #表示要使用的Exchange名称定义 content-type: application/json #设置消息类型,本次为json,文本则设置text/plain defaultbinder: defaultRabbit #设置要绑定的消息服务的具体设置 group: A eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka instance: lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔(默认是30s) lease-expiration-duration-in-seconds: 5 #如果现在超过了5秒的间隔(默认是90s)
主启动类
提供方的业务类代码
//接口 public interface streamrabbitmqService { public String send(); } //实现类 @EnableBinding(Source.class) public class StreamrabbitmqServiceImpl implements streamrabbitmqService { @Resource private MessageChannel output; @Override public String send() { String serial = UUID.randomUUID().toString(); output.send(MessageBuilder.withPayload(serial).build()); System.out.println("*********"+serial); return null; } } //controller @RestController public class streamrabbitmqController { @Resource private streamrabbitmqService service; @GetMapping("/sendMessage") public String sendMessage(){ return service.send(); } }
消费方的业务代码
@Component @EnableBinding(Sink.class) public class StreamMQCustomerController { @Value("${server.port}") private String serverPort; @StreamListener(Sink.INPUT) public void input(Message<String> message){ System.out.println("消费者9902----->接受到的消息:"+message.getPayload()+"\t +port:"+serverPort); } }
以上的全部完成之后,启动Eureka注册中心,以及消费者和提供者,还有RabbitMQ消息转发中心
然后进行测试,测试地址:http://localhost:9901/sendMessage
当我们访问的时候,提供方会进行消息的发送,然后消费方会收到信息,并在控制台显示,这时候我们的RabbitMQ显示台就会显示我们的信息发送的频率
最后如果我们的消费方有多个,那么就会发生重复消费的问题,如何解决?
- 我们可以在配置文件中加入 grop(组)的概念
- 这样如果不同的组会继续重复消费,而相同的组则会竞争轮询的进行消费
- 但是如果我们消费方这时候断线了,并且其中一个消费者移除了grop这个概念,那么再上线的时候,移除了grop的会收不到未接受的信息,而没有移除的则会收到未接受的信息
14.sleuth +Zipkin
15.Cloud Alibaba
有哪些功能:
- 服务限流降级:默认支持Servlet、Feign、Rest Template、Dubbo和Rocket MQ限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级Metrics监控
- 服务注册与发现:适配SpringCloud服务注册与发现标准,默认集成了Ribbon的支持
- 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新
- 消息驱动能力:基于SpringCloud Stream为微服务应用构建消息驱动能力
- 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务、支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
- 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于Cron表达式)任务调度服务。同时提供分布式的任务执行模型。如网格任务、网格任务支持海量子任务均匀分配到所有Worker上执行。
1. Nacos
简介:
一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台
就是注册中心+配置中心的组合(相当于前面所讲的Eureka+ribbon+bus所继承为一体的管理平台)
使用:
1. 首先启动nacos
2.编写服务提供项目
pom
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
yaml
#服务端口 server: port: 9001 #服务名 spring: application: name: nacos-payment # 注册进nacos的服务地址 cloud: nacos: discovery: server-addr: localhost:8848 #配置nacos地址 #暴露端口 management: endpoints: web: exposure: include: "*"
主启动类
@SpringBootApplication @EnableDiscoveryClient public class AlibabaPaymentMain9001 { public static void main(String[] args) { SpringApplication.run(AlibabaPaymentMain9001.class,args); } }
业务逻辑
@RestController public class PaymentController { @Value("${server.port}") private String serverport; @GetMapping("/nacos/{id}") public String getPayment(@PathVariable("id") Integer id){ return "hello nacos,serverport:"+serverport+" id:"+id; } }
运行
3. 编写配置中心
pom
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
yam
server: port: 3377 spring: application: name: nacos-config-client cloud: nacos: discovery: server-addr: localhost:8848 config: server-addr: localhost:8848 file-extension: yaml # group: test 这里是分组的名字 # namespace: 123456 这里是命名空间的名字
spring: profiles: active: dev #这里是测试开发环境 # active: test
主启动类
@SpringBootApplication @EnableDiscoveryClient public class configAlibabaMain { public static void main(String[] args) { SpringApplication.run(configAlibabaMain.class,args); } }
服务类
@RestController @RefreshScope //动态刷新功能 public class ConfigController { @Value("${config.info}") private String Info; @GetMapping("/config/Info") public String getInfo(){ return Info; } }
启动运行
4. 编写客户端
pom文件
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
yml文件
server: port: 83 spring: application: name: nacos-order cloud: nacos: discovery: server-addr: localhost:8848 server-url: nacos-user-service: http://nacos-payment
主启动类
@SpringBootApplication @EnableDiscoveryClient public class OrderMian83 { public static void main(String[] args) { SpringApplication.run(OrderMian83.class,args); } }
配置类
@Configuration public class Config { @Bean @LoadBalanced public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
服务类
@RestController public class OrderController { @Resource private RestTemplate restTemplate; @Value("${server-url.nacos-user-service}") private String serviceURL; @GetMapping("/customer/nacos/{id}") public String AlibabaNacos(@PathVariable("id") String id){ return restTemplate.getForObject(serviceURL+"/nacos/"+id,String.class); } }
5. 实现集群配置以及持久化(重点)
- 总体架构实现 1 nginx+3 nacos + 1 mysql
- 首先在虚拟机上部署我们的nginx服务器,然后配置nacos服务,最后将nacos自带的嵌入式数据库换成mysql
- 在本机运行,实现多个注册中心同时运行
2.Sentinel
Sentinel持久化
- 首先在客户端添加以下的配置文件
然后在nacos中配置中心配置以下信息
3. Seata
简介:
什么是Seata:
Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata将为用户提供了AT、TCC、SAG和XA事务模式,为用户打造一站式的分布式解决方案。
Seata术语
XID
TC-事务协调者:
维护全局和分支事务的状态,驱动全局事务提交或者回滚
TM-事务管理器、
定义全局事务的范围:开始全局事务、提交或回滚全局事务
RM-资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚
- TM向TC申请开启一个全局事务,全部事务创建成功并生成一个全局唯一的XID
- XID在微服务调用链路的上下文中传播
- RM向TC注册分支事务,将其纳入XID对应全局事务的管辖
- TM向TC发起针对XID的全局提交或者回滚决议
- TC调度XID下管辖的全部分支事务完成提交或回滚请求
详细过程:
- TM开启分布式事务(TM向TC注册全局事务记录)
- 按业务场景、编排数据库、服务等事务内资源(RM向TC汇报资源准备状态)
- TM结束分布式事务,事务一阶段能结束(TM通知TC提交/回滚分布式事务)
- TC汇总事务信息,决定分布式事务是提交还是回滚
- TC通知所有RM提交/回滚资源,事务二阶段结束
其中的事务阶段细节
- 在一阶段,Seata会拦截业务SQL
- 解析SQL语义,找到业务SQL要更新的业务数据,在业务数据被更新前,将其保存成before image
- 执行业务SQL更新业务数据,在业务数据更新之后
- 将其保存成after image,最后生成行锁
- 以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性
- 二阶段回滚:
- 二阶段如果是回滚的话,Seata就需要回滚一阶段已经执行的业务SQL,还原业务数据
- 回滚方式就是用before image还原业务数据;但是还原之前要首先校验脏写,对比数据库当前业务数据和after image
- 如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理