一、总览:
1 SpringCloud组件说明:
一、服务注册中心:
1、Eureka(基本停用)
2、Zookeeper
3、Consul
4、Nacos
二、服务调用1:
1、Ribbon
2、LoadBalancer
三、服务调用2:
1、Feign(基本停用)
2、OpenFeign
四、服务降级:
1、Hystrix(基本停用)
2、resilience4j
3、sentienl
五、服务网关:
1、Zuul(基本停用)
2、Zuul2(基本无望)
3、Gateway
六、服务配置:
1、Config(基本停用)
2、Nacos
七:服务总线:
1、Bus(基本停用)
2、Nacos
总结:
Nacos作为重点学习,功能强大
2 dependency和dependencyManagement的区别:
1、dependency用于子工程dependencyManagement用于父工程
2、父工程依赖的版本号子工程可以继承,换句话说,子工程可以不用在pom文件中制定依赖的版本号可以直接找到父工程的依赖版本号用于自身
3、以至于以后想要进行整体依赖的版本升级只需要在父工程的依赖中修改版本号即可,如果某一个子工程想要进行版本升级,只需要在所在子工程的依 赖中制定的版本号即可
4、注意:dependencyManagment只负责定义整个项目的依赖版本号如果子项目想要引入相关依赖还需要具体项目具体引入,换句话说,子项目不会集成 父项目的依赖
3 项目流程:
1 建module
2 改pom
3 写yml
4 主启动
5 业务类:
1 建sql表
2 entities
3 dao
4 service
5 controller
4 @PathVariable
用于接受请求路径中的占位符的值
5 关于热部署:在不重新启动springboot项目的情况下,修改项目内容
1 先要加入热部署的依赖
2 加入热部署的plugin
3 修改相关的idea配置
4 重启idea
二、服务注册中心:
1 关于消费者端
1 消费者模块不需要写具体的业务逻辑只需要写controller、配置类、实体类即可
2 消费者端需要用到resttemplate,这个东西用于消费者端的http请求
3 controller中先将RestTemplate注入,然后在相应的方法中使用此工具访问拿到相应的数据即可
2 关于生产者端
1 消费者和生产者都启动成功后,查询数据可以正常进行但是插入数据,检查数据库却为null,这是因为没有在生产者端的controller中相应方法的参 数加入@ResponseBody,这个注解用于接收并处理从消费者传过来的参数的数据
3 工程重构
1 关于多个module中存在很多相同代码的问题
2 需要在父工程下新建一个moudle,里面放置那些重复的代码,然后将依赖引入到相应的模块中,写入gav的坐标即可
4 Eureka注册中心
1 Eureka有两大组件
1 eureka-server(eureka服务端,注册中心端)对于自己来说就是服务端,注册中心本身就是服务端,它为注册进来的客户提供管理服务
2 eureka-client(eureka客户端,服务提供端)对于注册中心来说是客户端,无论是生产者还是消费者都是客户端
2 传统消费者调用生产者,在比较少的调用的时候,可以直接一对一调用,关系比较简单,但是一旦出现大量的消费者去调用生产者,这时候关系就变 得复杂,这时候需要一个管理中心来管理这种调用关系,所以出现了Eureka
3 在这个体系中,存在生产者、消费者、Eureka注册中心三方,这三方构成了三角关系,服务提供者(生产者)可能有多个,注册中心可能有多个,在注 册中心和服务提供者之间会存在心跳机制,用于保证服务提供者可以提供服务,一旦心跳机制检测到生产者不能提供服务,注册中心就会将该服务( 生产者)从注册中心删除掉
4 将服务注册进注册中心,实质上就是一堆key和value值
key:服务名称
value:服务的地址
5 搭建单机版的Eureka
1 建设Eureka模块
1 建moudle
2 改pom:因为是Eureka注册中心,所以要新加入Eureka-server依赖,注意这里是新版本的Eureka依赖,老版本的依赖不能区分是Eureka-serv er还是Eureka-client
3 写yml:
port:7001
instance:
hostname:localhost(Eureka服务端的实例名称)
client:
register-with-eureka:false(是否将自己(注册中心)注册进来,一般来说是否,它本身就是注册中心)
fetch-registry:false(不需要自己检测自己的服务)
service-url:
defaultZone:http://${eureka.instance.hostname}:${server.port}/eureka/ (这是Eureka进行交互的网络地址)
4 主启动类
1 这个主启动类上不仅要有@SpringBootApplication还要加上@EnableEurekaServer,表明这是Eureka-server的身份
5 不需要写业务类
2 将生产者(服务提供者)注册到注册中心
想要将相应的eureka-client注册进eureka中,需要这几个步骤:
1 修改服务提供者的pom文件,加入一个netflix-eureka-client依赖
2 在服务提供者的主启动类加上@EnableEurekaClient注解,表明这是eureka-client端
3 修改yml文件
spring:
application:
name:cloud-payment-service(这是这个服务端的名称,在入驻注册中心时需要用到)
eureka:
client:
register-with-eureka:true(是否将自己注册到注册中心)
fetch-registry:true(需要自己检测自己的服务)
service-url:
defaultZone:http://localhost:7001/eureka (这是和Eureka注册中心进行交互的网络地址,服务端需要知道注册中心在哪)
4 测试一下:
1 先启动Eureka-server(注册中心)端
2 再启动Eureka-client(服务提供)端,这时候就可以在注册中心看到服务提供者被注册进来了
3 最后将消费者注册进来:
1 加入netflix-eureka-client依赖
2 改yml
spring:
application:
name:cloud-order-service(这是这个服务端的名称,在入驻注册中心时需要用到)
eureka:
client:
register-with-eureka:true(是否将自己注册到注册中心)
fetch-registry:true(需要自己检测自己的服务)
service-url:
defaultZone:http://localhost:7001/eureka (这是和Eureka注册中心进行交互的网络地址,服务端需要知道注册中心在哪)
3 在消费者的主启动类加上@EnableEurekaClient注解,表明这是eureka-client端
6 搭建Eureka集群
1 搭建集群是避免一个Eureka挂掉之后,导致整个系统瘫痪,另外保证服务的负载均衡,效率更高
2 搭建集群的本质就是互相注册,相互守望
1 一个Eureka框架中搞很多Eureka节点,每个节点中含有其他每个节点的信息,就完成了集群的搭建
3 其实很简单,就是继续新建Eureka的moudle,作为节点使用,新建过程和7001的那几个步骤相同
4 不过需要修改一下windows的etc目录下的配置文件,因为本地localhost已经对应了7001服务,现在又加了几个(7002、7003...),所以这些节点 需要和本地localhost对应起来,这样才能访问到相应的Eureka节点。
详情如下:
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
127.0.0.1 eureka7003.com
5 然后就是修改相应节点的yml文件:
1 将7001模块的hostname由原来的localhost修改为eureka7001.com
2 再将7001模块的defaultZone由原来的http://${eureka.instance.hostname}:${server.port}/eureka/ 修改为http://eureka7002.com:7002/eureka/,再将其他节点(7002、7003模块)的yml文件做相应修改即可,目的是做到相互守望
3 验证集群是否搭建成功:
1 先后启动各个集群节点
2 从浏览器访问节点地址http://eureka7001.com:7001,http://eureka7002.com:7002,http://eureka7003.com:7003,每个节点中都可 以看到其他节点被注册进来,至此就做到了Eureka集群的搭建。
7 将其他Eureka-client服务注册进Eureka集群中,实现集中管理
1 将服务的yml文件中的eureka地址修改为集群版本即可:
将defaultZone:http://localhost:7001eureka修改为集群版http://eureka7001.com:7001,http://eureka7002.com:7002,http://eureka7003.com:7003,说白了,就是将集群的各个节点的地址放到每个需要注册的服务的yml文件中,让eureka-client知道有哪些集群节点,好让它去连接注册中心各个节点
2 测试一下是否注册成功:
1 想启动eureka-server的各个节点
2 再启动eureka-client的服务(先启动这个服务中的生产者,再启动消费者)
3 最后从浏览器访问相应的注册中心节点,看看节点中是否已将需要注册的服务注册进来,节点中的服务分为两大模块,分别为集群模块和服务模块
举例:7001集群节点中应该出现以下这几个模块
1 集群模块
1 eureka7002.com
2 eureka7003.com
2 服务模块
1 cloud-payment-service
2 cloud-order-service
8 生产者集群的搭建(provider集群的搭建)
1 新建的provider生产者和原来的生产者基本相同。还是那几个步骤,建moudle,改pom,改yml,主启动,将业务代码拷贝过来,将代码做细微的修改
2 重要的是controller中做相应的修改,使它成为provider集群的关键,加入以下代码:这一段代码可以获取本服务模块的端口号
@Value("${server.port}")
private String serverPort;
*然后下面业务代码中返回数据的时候,可以加上端口号数据,这样在消费者消费的时候就可以知道自己的数据是provider集群中哪个生产者节点生产的数据
9 出现问题,通过消费者访问生产者集群,总是访问到8001的节点
1 这是因为在消费者的controller中,PAYMENT_URL的地址是写死的(http://localhost:8001),现在需要把它写活:
将http://localhost:8001修改为http://CLOUD-PAYMENT-SERVICE ,就是将注册中心的服务提供者的名称放到这个地址中即可,不用去关心它的ip和端口,因为它会自己去找生产者集群中的某一个生产者,使用它提供的服务,但是此时还不知道找哪一个生产者节点
2 这时候就需要负载均衡
在消费者的配置类中,找到RestTemplate请求处理工具,在此工具上加一个@LoadBalanced注解,就可做到负载均衡
10 actuator微服务信息完善
1 命名规范的完善:隐藏注册中心中服务的主机名称:
在注册中心中,被注册进来的服务的Status(id)是含有主机名称的,其实可以隐藏掉
在provider8001的yml文件中的eureka下加入:
instance:
instance-id:payment8001
这样就可以去掉注册中心中相应服务id的主机名了
2 访问路径显示ip地址:在生产者yml文件中加入以下代码即可,这样就可以显示服务提供者所在的服务器ip地址
作用:后期如果服务出现问题,可以方便查看服务的ip,服务名称,端口号,快速定位服务的故障
instance:
prefer-ip-address:true
11 服务发现Dscovery
1 在消费者访问的过程中,消费者需要知道服务提供者的相关信息,比如,ip、服务名称、端口号等信息,那么怎样才能将服务信息暴露出来呢
2 在服务提供者的controller中添加以下代码:
@Resource
private DiscoveryClient discoveryClient;
3 在主启动上添加@EnableDiscoveryClient注解
4 再写一个方法测试一下暴露了哪些服务信息:
@GetMapping(value = "/payment/discovery")
public Object discovery(){
List<String> services = discveryClient.getServices();//这一步可以获取所有在注册中心注册的服务名称
for(String element : srevices){
log.info("****element:" + element);//将服务名称遍历出来
}
//获取某一项服务的所有服务实例(某一服务集群中的所有服务节点)
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
//遍历输出
for(ServiceInstance instance : instances){
//输出服务节点的id、主机、端口、uri地址
log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}
return this.discoveryClient;
}
5 通过这个服务发现Discovery就可以得到相关的服务信息了
12 Eureka的自我保护机制
1 在eureka实际运行过程中,有时候Eureka-client与Eureka-server连接不通,心跳机制检测到不能正常提供服务,这时候,eureka不会将eureka-cli ent立即删除掉,而是会暂时保存,这就是Eureka的自我保护机制,它不会轻易地注销服务实例
2 Eureka的自我保护机制默认是开启的,那么怎么禁用自我保护机制
1 在Eureka-server端的yml文件中添加以下代码:
eureka:
server:
enable-self-preservation: false //将自我保护机制改为false
eviction-interval-timer-in-ms: 2000 //将心跳机制间隔时间改为2000毫秒
2 在Eureka-client端yml文件中添加以下代码:
eureka:
instance:
lease-renewal-interval-in-seconds:1 //表示rureka客户端向服务端发送心跳的时间间隔,单位是秒
lease-expiration-duration-in-seconds:2 //表示服务端在收到最后一次心跳后的等待时间上限,单位是秒
13 Zookeeper
1 zookeeper注册中心安装
zookeeper和eureka不同,zookeeper没有像eureka那样的可视化网络界面,它是在linux系统中安装的服务端,需要提前安装
2 将相关的客户端注册进zookeeper中,实现管理
和eureka的操作差不多,将需要注册的客户端进行相关的配置(zookeeper依赖引入,yml文件编写,主启动类编写,注意这里的主启动类不需要eurek ad的注解),controller中进行服务发现相关的代码编写,方便查询客户端相关信息,这样就可以将客户端注册进zookeeper中了
3 测试
1 先启动zookeeper服务,提前将centOS服务器的防火墙关掉,否则本地与linux无法正常连接
2 再启动客户端程序,这时候客户端就注册进zookeeper中了
3 但是可能出现启动客户端报错,因为linux中安装的zookeeper版本与本地客户端引入的zookeeper依赖版本可能冲突(不一致),导致启动失败,这 时候解决jar包冲突即可,在pom中将本地依赖jar包剔除,重新引入与linux中zookeeper版本相同的依赖即可,代码如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--排除自身冲突依赖-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--引入与linux中zookeeper版本一致的依赖-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>xxx</version>
</dependency>
4 再去linux中进入到zookeeper服务,发现zookeeper服务只有一个根目录,根目录下有services,services下有相关的被注册进来的客户端,如:
(cloud-provider-payment)
14 zookeeper的临时节点和持久节点
1 临时节点:
服务被注册进来,如果创建的是临时节点,那么就是个临时的session,服务关闭或者心跳检测不到就会立即删除该服务(session)
2 持久节点:
服务被注册进来,如果创建的是持久节点,那么该节点不会被自动删除,只有手动delete才能删除
3 在zookeeper中,通过心跳机制检测不到服务运行,会立即将服务清除掉(这一点和Eureka不同),直到服务重新连接,被zookeeper的检测机制检测到
15 将消费者端注册进zookeeper中
1 和Eureka的技术路线几乎相同
2 建moudle,改pom,改yml,消费者端口号一般还是80,主启动,业务类(配置类-这里还是用Template来转发请求),controller,总之,和Eureka的 consumer注册服务差不多
3 测试一下,是否真正注册成功,并从浏览器测试路径是否访问成功(从本地localhost:80进入,经过template转接,看是否能获取到provider的数据)
4 这里先不讨论集群版的zookeeper(其实和搭建集群版的Eureka几乎一样),因为实际应用很少用到,暂且不展开
16 Consul的安装和运行
1 Consul也是一种微服务的解决方案,和Eureka和zookeeper差不多,不同的是,Consul和Eureka都有天生的web的图形化显示界面,而zookeeper没有
2 从官网下载Consul的64位安装包(当然也有Linux版本的),直接用dev模式运行,保持窗口的开启即可运行服务
3 从浏览器访问localhost:8500即可访问到consul的图形化界面
17 将provider注册进Consul
1 大同小异,还是那5步,建一个新的8006端口的provider,将Consul的部分进行相应的修改即可
2 启动Consul服务模块和provider8006模块后,在图形化界面可以看到被注册进来的服务(cloud-provider-payment),点进去可以看到相应的服务信 息,从浏览器也可以访问到provider8006的数据
18 将consul-order注册进Consul,并测试
1 大同小异,还是那几步,建一个新的consul-order80的消费者,将消费者注册进Consul,并且使用template请求处理工具
2 最后,启动各方服务,在Consul的web图形界面可以看到consul-order80被注册进来,并且从浏览器访问order的80端口可以拿到provider的数据
19 Eureka、Zookeeper、Consul三者从CAP维度的分析(异同)
1 Eureka属于AP,不保证数据的一致性,但确保服务的持续性,即使服务出现问题,Eureka也不会立即清除该服务
2 Zookeeper、Consul属于CP,只要数据不一致或服务出现问题,就返回error或立即清除该服务,绝不提供错误的数据
三、服务调用:
1 Ribbon负载均衡、服务调用
1 之前在学习Eureka时,讲到搭建生产者集群,通过order80访问provider集群的时候,加入负载均衡的概念,就是在消费者端加入@LoadBalanced注解,即可做到负载均衡,这一章讲到的Ribbon,就会完善负载均衡和服务调用服务的,所以和客户端(消费者端)有关
2 Nginx服务端负载均衡 VS Ribbon本地负载均衡
1 nginx服务端负载均衡:所有过来的请求都会通过这里,在服务器端就会搞好负载均衡,不占用本地资源
2 Ribbon本地负载均衡:从nginx过来的请求,在本地JVM通过Ribbon进行服务分配,达到负载均衡
3 负载均衡的分类:
1 集中式的:集中式的负载均衡(比如,nginx),是处在消费方与生产方之间的LB设施
2 进程内的:进程内的负载均衡(比如,Ribbon),是处在消费方内部的LB设施,消费方通过Ribbon获取服务提供方的访问地址,然后选择 合适的地址
4 关于依赖引入:
1 在order-client引入netflix-eureka-client依赖时,这个依赖本身就集成了Ribbon依赖,也就是说eureka天生可以做到负载均衡
2 getForObject与getForEntity的区别:
1 在之前order中有一个getForObject,作用是restTmplate请求返回的object数据,在浏览器上反映为json数据
2 现在出现一个getForEntity,它的返回数据比getForObject更加详细,包括请求头,请求体,状态码之类的信息都会有,这些数据会被封装在entity中
3 相同点:这两个方法都是restTemplate下面获取数据的方法
3 Ribbon自带的7种负载均衡规则(算法)
1 RoundRobinRule:轮询
2 RandomRule:随机
3 RetryRule:先按照轮询机制(RoundRobinRule)获取服务,如果获取失败,则会在指定时间内进行重试
4 WeightedResponseTimeRule:这是对随机均衡(RandomRule)的扩展,当某一服务节点响应速度很快时,就会加大这个服务节点的服务权重
5 BestAvailableRule:先过滤掉由于多次访问故障而处于断路跳闸状态的服务节点,然后选择一个并发量最小的服务进行访问
6 AvailabilityFilteringRule:先过滤掉故障实例,进而选择并发量较小的实例
7 ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性进而选择服务器
4 负载规则的配置或替换
1 这些负载均衡规则也要有一个专门的rule配置类,但是注意,官网提示这个规则配置类不允许被SpringBootApplication下的扫描包扫描到,就是说不能 在一个锅里吃饭,所以需要在com.wangyakun下新建一个myrule包,包里面是MyselfRule配置类,当然没有这个类的时候默认是轮询规则,现在在这个 规则配置类中就可以加入自己想要的负载均衡规则了
2 MySelfRule规则配置类代码如下:
@Configuration
public class MySelfRule{
@Bean
public IRule myRule(){
//替换为随机
return new RandomRule();
}
}
3 然后在主启动类上加@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration=MySelfRule.class)
表示对 CLOUD-PAYMENT-SERVICE 这项服务实行新的负载规则
4 最后,测试一下是否替换成功
从浏览器访问localhost:80的order,看返回的数据,调用的服务提供者端口是不是随机的就ok了
5 负载均衡算法详解
1 默认算法(轮询算法)
公式:rest接口第N次请求 % 服务器集群节点数 = 实际调用服务器位置下标,服务重新启动,rest接口N从1开始算
2 手写一个轮询算法,加深印象
在springcloud包下建负载均衡的包,里面写一个轮询算法的接口,再写一个该接口的实现类,实现类上加一个@Component注解,让这个包能被扫描 到,然后在这个类中详细写轮询算法的代码即可【ServiceInstance可以获取服务实例,放到List中,可以获取实例的数量】
6 OpenFeign
1 OpenFeign也是做服务调用的工具,在原来的Feign上进行了加强,用在消费者端,即可做到服务调用处理,它也集成了Ribbon的功能
2 搭建和使用:
1 还是那几步,建moudle,改pom(加入openfeign的依赖),改yml,主启动(加@EnableFeignClients注解进行激活)
2 业务类:
1 由于OpenFeign服务依赖生产者的服务接口,和服务提供者的接口一一对应,所以在消费者端也要有一个专门的OpenFeign接口,实现服务 细化调用
2 在springcloud包下新建一个service包,用于存放OpenFeign的相关服务调用代码
3 在service包中建一个PaymentFeignService的接口,用于对应生产者的相关调用方法,在接口上加@Component和@FeignClient(value = "CLOUD-PAYMENT-SERVICE"),代码如下:
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService{
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}
4 springcloud包下新建controller包,新建OrderFeignControllrt类,将PaymentFeignService注入进来:
@RestController
@Slf4j
public class OrderFeignController{
@Resource
private PaymenFeignService paymentFeignService;
@GetMapping(value = "/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVarible("id") Long id){
return paymentFeignService.getPaymentById(id);
}
}
3 测试:
1 启动Eureka注册中心(这里用的还是Eureka作为注册中心),启动provider8001和provider8002的消费者集群,再启动Feign80的消费者
2 从浏览器访问FeignOrder80的端口,调用服务,看返回的数据显示是否做到了负载均衡(8001与8002消费者节点交替提供服务)
4 OpenFeign超时控制
1 说明:OpenFeign的默认超时时间为1秒,也就是说通过OpenFeign调用生产者服务时,必须在1秒之内返回数据,否则报超时错误
2 那么,有时生产者确实需要一些时间才能返回数据,这时候OpenFeign就要做好准备,迎接服务调用的超时,修改默认的timeout配置
3 由于OpenFeign中整合了Ribbon,而超时配置又是由Ribbon控制,所以:
怎么修改超时配置,在消费者端(FeignOrder80)修改yml配置即可:
#设置feign客户端超时时间
ribbon:
#指建立连接后,获得服务器返回数据所用时间,超过5000毫秒报读取超时异常 Java.net.SocketException: read time out
ReadTimeOut: 5000
#建立连接所用时间,连接用时超过5000毫秒报连接超时异常 java.net.SocketException: connetct time out
ConnectTimeOut: 5000
5 OpenFeign的日志增强
1 说明,OpenFeign提供了日志打印的功能,方便查看OpenFeign内部接口调用的细节
2 OpenFeign日志级别
1 NONE:默认级别,不打印
2 BASIC:仅记录请求方法、URL、响应状态码和执行时间
3 HEADERS:打印BASIC定义的信息、请求和响应的头信息
4 FULL:打印HEADERS定义的信息、请求和响应的正文及元数据
3 怎么配置日志增强:
1 在集成了OpenFeign的order80中,在springcloud下新建一个config包,放置OpenFeign的日志配置类
2 代码如下:
@Configuration
public class FeignConfig{
@Bean
Logger.Level feignLoggerLevel(){
return Logger.level.FULL;
}
}
3 然后,在FeignOrder80的yml中加上如下代码:
logging:
level:
#Feign日志以什么级别监控哪个接口
com.wangyakun.springcloud.service.PaymentFeignService: debug
四、服务降级:
1 Hystrix:
1 说明:处理分布式系统的延迟和容错的开源库
在传统的服务架构中,往往是依赖与依赖之间互相依靠,一旦一个依赖(服务)出问题,就容易导致整体服务系统出现雪崩,所有服务都不能用了,这时候就需要断路器,当一个服务出现问题,马上进行相应的处置,不至于出现雪崩,并且当某一个服务出现问题,返回一个备选响应,而不是直接报无法处理的异常
2 停止更新进入维护阶段
3 理论知识:
1 服务降级(fallback),服务器出现问题,给出一个友好提示,以下四种情况会出现服务降级:
1 程序运行异常
2 超时
3 服务熔断出发服务降级
4 线程池/信号量打满
2 熔断(break),服务访问量过大,为了保护服务器,直接中断服务拒绝访问,相当于保险丝的作用,流程如下:
1 服务降级-->熔断-->恢复服务
3 限流(flowlimit),当处在高并发状态,需要让过来的请求排队进入,或者直接不提供服务
2 Hystrix案例:支付微服务
1 说明:在原来Eureka的基础上加入Hystrix功能
2 搭建流程:(这个案例中没有使用注册中心集群,只用了一个Eureka-7001注册中心)
1 建生产者moudle(springcloud-provider-hystrix-payment8001)
2 改pom,新加入netflix-hystrix依赖模块
<dependency>
<groupId>org.springframework.cloud<groupId>
<artifacted>spring-cloud-starter-netflix-hystrix<artifacted>
<dependency>
3 改yml,name变为:cloud-provider-hystrix-payment
4 主启动
5 业务类:写两个方法,进行压测,看在高并发状态下服务器的反映
6 结论:在只有注册中心Eureka7001、生产者provider8001参与,高并发状况下,本来无压力的请求也被拖垮,线程被占用了
3 加入order80消费者cloud-consumer-feign-hystrix-order80,用了Feign;消费者端和生产者端都可以加hystrix依赖,但是一般加在消费者端
1 建moudle,cloud-consumer-feign-hystrix-order80
2 改pom
3 改yml
4 主启动
5 业务类,和以前的Feign的书写相同,加入Feign的接口,使它能够起到服务调用的作用
6 最后,再从浏览器访问localhost80的consumer端口,从consumer调用provider,看看请求能否被调通,再用Jmeter压测工具测试一下,发 现出现问题,服务响应变慢,所以需要服务降级
4 什么情况下会服务降级:
1 对方服务8001响应超时,80不可能一直等待,这时候需要服务降级
2 对方服务8001服务出错,80不能一直卡死,这时候需要服务降级
3 8001的服务正常,但是80调用者对响应时间的要求小于服务提供者8001的实际响应时间,调用者等不及了,这时候调用者80需要自己服务降级
3 怎么做服务降级
1 服务降级的整体思路是哪里需要哪里加,可以从8001服务提供者下手,也可以从80消费者下手
2 从8001生产者下手:
比如,在上述的8001服务中,有一个耗时的方法,这个方法容易在访问时出现问题,那就在这个方法加上服务降级的功能
在具体的方法上加上注解:
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
})
兜底方法:
public String paymentInfo_TimeOutHandler(Integer id){
return "线程池: " + Thread.currentThread().getName()+" paymentInfo_TimeOutHandler,id "+id;
}
解释:paymentInfo_TimeOutHandler表示服务降级的兜底方法,value="3000",表示8001自身调用超时时间峰值,如果超时就进入兜底方 法,在这个示例中,只在注解中加入超时处理代码,但是发现就算在服务不超时的情况下,但是方法中程序运行出错,也会进入兜底方法 ,进行服务降级处理
注意:关于热部署与@HystrixCommand注解属性冲突问题,有时候修改注解中某一属性java代码不会自动重启(热部署不生效),这时候需 要手动重启一下服务保证代码改动生效
3 从80消费者下手:
1 现在模拟80端出错或者等待时间小于8001的响应时间,进而进入80的兜底方法
2 在需要服务容错的80yml中加入以下代码:
feign:
hystrix:
# 表示hystrix在feign中生效
enabled: true
3 controller业务类中加入和8001兜底几乎相同的代码(注解和兜底方法)
4 出现问题:
上面无论是在8001端还是在80端做服务容错降级,都是每个业务方法对应一个fallback兜底方法,代码很乱,怎样做一个统一的服务容错,让需要的业务方法对应过来,使代码更加简洁高效(可以加在8001中也可以加在80中)
1 使用@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod"),将这个注解加到Controller类上,类中写一个方法:
这个方法是统一的兜底方法:
public String payment_Global_FallbackMethod(){
return "Global异常信息处理,请稍后尝试";
}
说明:当业务方法出现问题需要容错时,优先寻找自身的量身定做的兜底方法,如果没有就走到统一的兜底方法中
5 又出现一个问题:代码耦合度太高,业务类代码和服务容错代码混淆在一起,怎么解决:
1 这里从80消费者端入手,在80端用到了Openfeign,所以就有Feign的统一服务调用接口,从接口入手,统一的将服务容错管理起来,就可以解决代 码耦合问题,所以:
2 写一个Feign服务调用接口的实现类,在这个实现类中处理服务容错即可(实现类上别忘了加@Component注解),然后在原来feign接口的
@FeignClient注解的属性中加上fallback = PaymentFallbackService.class加上这个属性表示出现问题需要容错时找这个类进行fallback
------------------------------------------
6 熔断:
1 熔断和服务降级是完全不同的两个概念,熔断是应对雪崩效应的一种微服务链路保护机制,当某一个微服务响应时间过长或者短时间内调用失 败次数达到某一阈值,就会触发Hystrix的熔断机制,而后当Hystrix检测到该服务正常时再恢复调用链路
2 中间状态:当达到熔断条件时,触发熔断,这时候会尝试进入半熔断的状态,看看访问量是否处于安全范围之内,就这样慢慢尝试恢复到正常状态
3 怎么做熔断:
1 还是在刚才80端的feign的服务调用接口的实现类中做文章,在需要熔断处理的方法上使用@HystrixCommand注解,其中有很多属性控制
代码如下:
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
// 开启断路器
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
// 断路器的窗口期内触发断路的请求阈值,默认为20
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
// 断路器的快照时间窗,也叫做窗口期。可以理解为一个触发断路器的周期时间值,默认为5秒(5000)
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),
// 断路器的窗口期内能够容忍的错误百分比阈值,默认为50(也就是说默认容忍50%的错误率)
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60")
})
2 注意这里的熔断方法一定要有对应的兜底降级方法,因为熔断的处理逻辑就是先降级,再熔断,最后再慢慢恢复链路
3 熔断再理解:是否熔断的条件实际上是判断在一定时间内或一定次数内请求失败的概率是否达到某一个阈值,这个阈值由注解中的属性值决定
4 熔断半开状态再理解(Half Open):熔断之后,尝试放过去一些请求,判断这些请求的成功率是否达到允许全开的标准,这就是半熔断
7 服务限流:这里的Hystrix先不展开,后期用alibaba的Sentinel做限流
8 Hystrix图形化界面Dashborard的搭建
这是一个监控服务hystrix降级、熔断、限流各项指标信息的图形化面板
1 建moudle(consumer-hystrix-dashboard9001)
2 改pom,要有actuator依赖
3 改yml,只有一个端口号配置即可
4 主启动,加@EnableHystrixDashboard注解
5 访问localhost:9001/hystrix,即可显示初始化监控面板
6 在初始化监控面板中添加需要被监控的地址,比如(localhost:8001/hystrix.stream),还有其他相关信息(Delay,Title)
7 进入之后,即可看到熔断机制是否开启,以及其他的监控信息(流量大小、请求状态、成功数、熔断数、错误请求数、超时数等等)
五、服务网关:
1 基本概念:
1 微服务架构整体流程:手机、浏览器等客户端-->负载均衡-->网关-->微服务
2 那么网关就是在负载均衡与微服务之间的一道门,犹如医院的分诊台,将过来的请求分发给各个微服务
3 网关的历史发展:一开始有netflix公司的zuul系列网关,但是由于zuul内部的原因,迟迟不能更新,Springcloud自己开发了一套gateway网关
4 SpringCloudGateway是基于异步非阻塞模型开发的
5 gateway的组成:
1 Route(路由)
一种请求的匹配规则;当请求到达网关之后,网关里的路由根据自己的路由规则分配请求到各个微服务
2 Predicate(断言)
一种验证请求信息的匹配标准;请求到达网关之后,请求相关的信息(请求头、请求参数等)符合断言标准,则让请求进入路由
3 Filter(过滤)
一种路由前后施加的过滤条件;在请求被路由前、后,经过Filter实例可以对请求的相关信息做进一步修改
2 搭建流程:
1 建moudle(springcloud-gateway-gateway9527)
2 改pom,引入gateway依赖和eureka依赖,gateway作为一种微服务也需要被注册:
**并且,网关的pom中不需要spring-boot-web和spring-boot-actuator两个依赖,否则网关启动不起来
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
3 改yml:
spring:
application:
name: cloud-gateway
cloud:
# gateway需要配置相关信息,让网关知道自己对哪个地址的哪个方法起作用(这里就对provider8001的两个方法配了网关)
gateway:
routes:
# 路由的ID
- id: payment_routh
# 路由地址,如果过来的请求符合断言,则自动拼接上路由地址,一并转发访问
uri: http://localhost:8001
predicates:
# 断言,过来的请求符合这个断言标准,就会自动被转发到 http://localhost:8001/payment/get/**
- Path=/payment/get/**
- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/lb/**
4 主启动
5 业务类,不需要,gateway没啥业务
3 测试
1 依次启动eureka7001、provider8001、gateway9527三个模块,可以看到,8001和9527成功的被注册进了7001中
2 浏览器访问localhost:8001/payment/get/31 和 localhost:9527/payment/get/31 都可以成功访问到数据,说明gateway搭建成功
4 gateway网关的两种配置方式
1 gateway网关yml文件
2 gateway网关配置类
1 在springcloud包下新建一个配置类的包config,包中新建GateWayConfig网关配置类,加上注解@Configration即可
2 类中书写网关路由的配置代码,一个路由对应一个Bean,代码如下:
@Configration
public class GateWayConfig{
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
// 创建路由定位器
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route_payment_get", //路由ID
r -> r.path("/payment/get/**") //断言
.uri("http://localhost:8001/payment/get/**")).build(); //路由地址
return routes.build();
}
}
5 动态路由
1 在微服务架构中,往往有许多服务节点,比如在这个例子中有(payment8001,payment8002、eureka7001)
2 只需要在gateway的yml中加上,开启动态路由:
spring:
cloud:
gateway:
discovery:
locator:
# 开启动态路由
enabled: true
并且,原来routes中的uri变为 uri: lb://cloud-payment-service ,这是服务的统一路由地址,gateway动态路由可以自动实现负载均衡
3 predicate(断言)配置
1 在gateway网关的yml文件中,predicate的相关配置,可以对过来的请求进行判断,符合要求的可以通过,比如:
predicates:
# 路径
- Path:xxx
# 时间,在这个时间之后进来的请求会通过
- After:xxx
- Before:xxx
- Between:xxx
- Cookie:key,value
6 Filter(过滤)配置:
1 需要在gateway的yml中加上:
routes:
filters:
# 示例:过滤器会在匹配过来的请求头上加上一对这种请求头
- AddRequestParameter=X-Request-ID,1024
2 自定义过滤器:
1 在gateway9527项目springcloud包下新建filter包,包中新建过滤器:
@Component
public class MyLogGateWayFilter implements GlobalFilter,Ordered{
//里面可以对过来的请求进行一系列的判断,符合条件放行,否则给一些回应
}
六、服务配置:
1 分布式配置中心介绍:
1 名称:SpringCloud Config
2 作用:
1 集中管理各个微服务的配置
2 动态配置更新
3 更新配置中心文件之后,微服务不需要重启即可感知使用最新版的配置信息
3 SpringCloud Config分为两种:
1 服务端:就是配置中心,这是一个独立的微服务应用
2 客户端:就是各个需要被管理配置的微服务,启动时从配置中心(服务端)获取配置信息
2 配置总控中心搭建:
1 git上建一个仓库,里面有prod(生产)、dev(开发)、test(测试)三种yml配置文件
2 本地新建config服务端模块moudle (springcloud-config-center-3344)
3 改pom,新加入 spring-cloud-config-server 依赖,表明这是一个springcloud配置中心,还有eureka的依赖,配置中心也需要被注册中心管理
4 改yml:
spring:
cloud:
config:
server:
git:
# 刚才新建的git仓库的名字
uri: git@github.com:wangyakun/springcloud-config.git
# 搜索目录
serch-paths:
- springcloud-config
# 读取的哪个分支
label: master
5 主启动
6 测试是否搭建成功
通过浏览器访问(实现做好host文件映射) http://config-3344/master/config-dev.yml 即可获取git仓库里的配置文件内容
3 config客户端搭建
知识点:
1 application.yml:用户级的配置文件
2 bootstrap.yml:系统级的配置文件,优先级更高(出现在config客户端中)
3 这两个配置文件共同构成config客户端的配置
1 建moudle(springcloud-config-client-3355)
2 改pom,依赖是spring-cloud-starter-config
3 改bootstrap.yml:
spring:
application:
name: config-client
cloud:
# config客户端配置
config:
label: master
name: config
profile: dev
uri: http://localhost:3344
4 主启动
5 业务类,controller类,代码如下:
@RestController
public class ConfigClientController{
@Value("${config.info}")
private String configInfo; //配置信息注入
@GetMapping("/configInfo"){
public String getConfigInfo(){
//获取配置信息
return configInfo;
}
}
}
6 测试
1 从浏览器访问config客户端3355的controller,可以打通到config服务端3344,进而连接到远程git仓库的配置文件,返回相关配置数据
4 config客户端读取配置动态刷新-手动版
1 在修改git上的配置信息之后,访问config服务端可以拉取到正确的修改后的配置,但是从config客户端拉取的话,读取到的还是旧的信息
2 这样的话,每次修改github配置后都需要重启config客户端3355,客户端才能拿到最新的配置信息,怎么避免客户端重启呢
3 怎么配置:
1 在config客户端加入spring-boot-actuator监控依赖
2 在yml中加入暴露监控端点的配置代码:
management:
endpoints:
web:
exposure:
include: "*"
3 在controller上加@RefreshScope注解,使具备刷新能力
4 让运维人员用POST请求访问一下3355的配置路径,激活刷新功能
5 这时候就可以从config客户端3355获取到最新的配置文件内容了
5 Bus消息总线
1 在上面有手动刷新配置的方法,但是,如果config客户端数量很多,不可能全部通过手动POST刷新的方式一一激活刷新功能,这时候就需要Bus
2 什么是Bus消息总线
1 就是springcloud为了完成事件的监听、通知的一种工具,Bus消息总线支持RabbitMQ和Kafka两种消息中间件
2 消息总线的出现,就可以解决上述的问题,集中统一的激活各个config客户端的配置刷新功能
3 设计思想:
1 利用消息总线触发其中一个config客户端,从而刷新全部的客户端配置
2 利用消息总线触发config服务端,从而刷新全部的客户端配置
3 Bus动态刷新全局广播(这里结合RabbitMQ为例子)
1 基础环境
1 RabbitMQ下载、安装、配置、启动、测试访问(localhost:15672)、弹出可视化页面后登录看是否成功
2 以3355为模板,再建一个3366的config客户端moudle,增加广播复杂度,创造广播环境
2 这里用消息总线的第二种设计思想(触发congfig服务端)做为例子
4 配置实现
1 在config服务端3344添加消息总线MQ支持依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
2 在3344yml添加配置:
spring:
# mq配置
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
# 暴露bus刷新配置端点
management:
endpoints:
web:
exposure:
# 这里的 "bus-refresh" 是与bus刷新路径相对应的
include: "bus-refresh"
3 对config客户端(3355、3366),也要做相应改动
1 pom添加消息总线 MQ的依赖
2 yml中添加rabbitmq的配置信息
4 测试是否实现bus动态刷新
1 修改github测试信息
2 运维工程师后台向3344发送POST请求激活刷新(触发config服务端3344,达到全局动态刷新)
3 访问config客户端,看是否是最新的配置信息
4 此处基本流程:
后台运维向config服务端发送POST请求,激活动态刷新功能,这时候,RabbitMQ从config服务端拿到消息,存入topic(SpringCloudBus),凡是订阅了这个topic的config客户端都可以拿到刷新信息,进行自动刷新,获取到config服务端最新配置信息
5 Bus动态刷新定点通知
1 如果只想通知其中某一个config客户端怎么办
2 只需要后台运维发送POST请求时,在请求地址末尾添加具体的目的地(config客户端)即可,就不用再触发config服务端3344了
3 举例:只通知3355,不通知3366
curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355"
6 Springcloud Stream消息驱动
1 是什么
在后台开发与大数据交互过程中,会使用到各种消息中间件(RabbitMQ、RocketMQ、ActiveMQ、Kafka),但是消息中间件种类繁多,开发人员没有精力去掌握,所以出现了Springcloud Stream消息驱动,直接屏蔽掉底层消息中间件的差异,降低切换成本,统一消息的编程模型
2 支持类型
1 目前SpringCloud Stream消息驱动只支持以下两种消息中间件:
2 RabbitMQ
3 Kafka
3 Binder对象
Springcloud Stream消息驱动的使用实质上就是使用其中的Binder(绑定)对象作为中间层,使用这个对象与中间件进行交互,进而处理消息
4 设计思想
1 发布-订阅模式
2 使用Topic主题进行广播
3 Stream的几个关键词:
1 Binder:绑定对象
2 Channel:通道,实现存储和转发的媒介,通过Channel对队列进行配置
3 Source和Sink:输入和输出,从Stream发送消息就是输出,Stream接收消息就是输入
5 关于Stream的几个注解
1 @Input:输入通道注解,消息从该通道进入程序
2 @Output:输出通道注解,消息从该通道离开程序
3 @StreamListener:监听队列,用于消费者的队列消息接收
4 @EnableBinding:信道Channel和exchange绑定在一起
6 Stream案例搭建
注册中心用的是Eureka,消息中间件用的是RabbitMQ
1 Stream消息驱动之生产者
1 新建moudle(springcloud-stream-rabbitmq-provider8801),没错,端口设置为8801
2 改pom
加入新的依赖 spring-cloud-starter-stream-rabbit
3 yml
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: #在此处理绑定Rabbitmq的服务信息
defaultRabbit:
type: rabbit #消息组件类型
environment:
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: #服务的整合处理
output: #表示这个模块是一个消息的生产者、输出者、发送者
#目的地通道的名字,定义进行信息交互通道的名字,整个信息交互过程都在这个通道上进行
destination: studyExchange
content-type: application/json #发送消息的格式
binder: defaultRabbit #绑定的消息驱动服务的具体配置
4 主启动
5 业务类
1 com.wangyakun.springcloud包下新建service包,包中新建IMessageProvider接口用于发送消息:
public interface IMessageProvider{
public String send();
}
2 再建impl包,实现上面的接口,注意,这个实现类不用加@Service注解,因为这里不再跟传统的增删改查打交道,而是跟消息驱动打交道 ,所以,这里需要加@EnableBinding(Source.class),这个注解作用是定义消息的推送管道:
@EnableBinding(Source.class)
public class MessageProviderImpl implements IMessageProvider{
@Resource
private MessageChannel output; //消息发送管道
@Override
public String send(){
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build()); //将需要发送的消息构建好了通过管道发送过去
return null;
}
}
6 controller
controller中写一个发送消息的方法
7 测试
1 依次启动7001、rabbitmq、8801,然后访问8801的controller路径
2 访问rabbitmq的可视化界面(localhost:15672),可以看到有消息流量的曲线波动
2 Stream消息驱动之消费者
1 新建moudle(springcloud-stream-rabbitmq-consumer8802)
2 改pom
3 改yml
output变为input,信息通道还是StudyExchange
4 主启动
5 controller
1 加 @Component 和 @EnableBinding(Sink.class) 两个注解
2 写一个用于接收消息的方法
6 测试
访问消息生产者8801,使它发送消息到通道中,这时候,消费者会自动接收到通道中的消息,因为消费者已经连接订阅了这个消息通道,只要有服务发送消息到通道中,消息的消费者就可以发现消息并消费,这时候,就可以从RabbitMQ的可视化界面中看到消息的流通情况
7 Stream消息重复消费
1 有多个消费者连接同一消息通道时,出现消息重复消费的问题,比如,生产者产生一个订单消息,放入信息通道中,这时候,消费者1和消费者2 都拿到了订单消息,开始处理订单并进行后续的流程。这是因为,Stream为消费者1和2自动分配为两个group,不同的group可以消费同一条消 息,造成了重复消费的问题,解决的方法就是将这两个消费者放到同一组中,使形成竞争关系,组中有一个拿到消息,这条消息就需要被锁住
2 依照8002,再建一个消息消费者模块(springcloud-stream-rabbitmq-consumer8803),目的是演示消息重复消费问题
3 那么,怎么将相关的消费者放到同一个组中呢
只需要在需要放到同一组中的消费者(8802、8803)的yml中作如下修改即可:
bindings:
input:
group: wangyakunA #这个group名称可以自己随便取
8 Stream消息持久化
将8802的yml保留group属性,8803的yml去除group属性,然后依次启动7001、8801,然后操作8801生产者发送几条消息到消息通道中(这时候还没有启动8802、8803),然后再启动8802和8803,发现8802依然可以从消息通道中拿到8801产生的几条消息,但是8803却拿不到,这是因为消费者yml文件中的group属性有消息持久化的功能,如果没有这个属性,消费者就无法拿到消息通道中错过的消息,这也说明了消费者yml文件中group属性的重要性
7 Springcloud Sleuth分布式请求链路跟踪
1 在分布式系统架构中,多个微服务之间调用复杂度极高,我们需要一个请求链路跟踪系统来跟踪请求走过了多少服务节点,每个节点花费多长时间, 以及清晰的调用轨迹,这样在某一节点出问题的时候,就可以很快的找到病根加以医治
2 Springcloud Sleuth一般与Zipkin(请求链路跟踪图形化显示界面) 一起用,且Springcloud从F版后,已经整合了Zipkin,不用再单独安装Zipkin,只需要下载jar包启动服务即可,然后再运行控制台( localhost:9411/zipkin/),就可以看到zipkin的图形化界面
3 关键词:
1 Trace:表示一个请求链路
2 span:表示一个请求链路中的某一个服务调用
4 Sleuth链路监控展现
1 现在用Eureka7001、provider8001、order80作为例子来展示Sleuth的监控展现功能
2 在需要被监控的服务节点(provider8001、order80)作相应的配置,即可实现监控
3 对provider8001
1 pom加入Sleuth、Zipkin的依赖(spring-cloud-starter-zipkin)
2 yml:
spring:
application:
name: cloud-payment-service
# 配置zipkin
zipkin:
# 告诉zipkin将相关监控数据传送到哪个地方(zipkin可视化控制台)
base-url: http://localhost:9411
sleuth:
sampler:
# 采样率值介于 0 到 1 之间,1 则表示全部采集
probability: 1
4 对order80也做同样的修改即可
5 测试链路监控
依次启动Eureka7001、order80、provider8001,然后在order80的controller中写一个相应的测试访问方法,看看打到zipkin9411的监控数据链路是怎样的,zipkin可以展现链路的深度,每次span的时间,很详细
七、SpringCloud Alibaba
1 简介
由于一开始做微服务解决方案的是Netflix,后来Netflix内部停更之后,被SpringCloud整合吸收,后来SpringCloud内部停更之后,alibaba公司出台自己的微服务解决方案,但是却吃不掉SpringCloud,由于SpringCloud太庞大了,所以两个公司只能握手言和,SpringCloud将Alibaba的相关技术纳入到自己的孵化空间,所以就形成了SprongCloud Alibaba技术体系,综合来看,SpringCloud Alibaba比单纯的SpringCloud好用
2 Nacos
1 简介
Nacos是 Naming、Configuration、Service的缩写,表示命名注册配置服务,相当于之前讲的注册中心+配置中心(Eureka+Config+Bus)
2 下载、安装、登录
1 基础环境:jdk、maven
2 到官网下载安装包(这里用的是windows版本的安装包),下载到本地运行nacos服务,然后访问localhost:8848/nacos登录控制台即可
3 nacos控制台登录账号和密码都是nacos
3 特点
nacos支持AP和CP模式的切换,这一点与Eureka(AP)、Zookeeper(CP)、Consul(CP)只支持一种模式不同
3 新建生产者并注册进nacos
1 新建moudle(cloudalibaba-provider-payment9001)
2 在父工程pom中引入spring-cloud-alibaba-dependencies的依赖,因为后续的新建的子工程都要用到这个依赖,避免重复引入
3 本pom中引入spring-cloud-starter-alibaba-nacos-discovery依赖
4 yml
spring:
application:
name: nacos-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 告诉本服务模块nacos注册中心的地址在哪
managment:
endpoints:
web:
expoure:
include: * # 暴露监控
5 主启动
只加上@SpringBootApplication和@EnableDiscoveryClient
6 业务类
controller中写一个用于访问的方法即可,和eureka8001的差不多
service...
总之业务类就是一个服务提供者的moudle,和8001功能相同
7 测试
依次启动nacos服务、provider9001,这时候从nacos控制台就可以看到nacos-payment-provider被注册进了nacos
8 另外,再依照9001,新建一个9002的provider,注册进nacos,形成生产者集群,用于演示nacos的自带的负载均衡等功能
4 新建消费者并注册进nacos
1 新建moudle(cloudalibaba-consumer-nacos-order83)
2 pom
3 yml
除了常规的配置代码,新加入一个新的东西:
这个东西是用于在controller中动态的配置需要访问的生产者服务节点地址,这样就不用再写URL常量了
service-url:
nacos-user-service: http://nacos-payment-provider
4 主启动
5 业务类
和一开始的eureka-order80相同,集成RestTemplate请求调用接口,用于访问provider生产者
controller:除了常规代码之外,还有一个新的东西,可以将服务地址注册进来,不再使用定义常量的方式
@Value("${service-url.nacos-user-service}")
private String serverURL;
5 Nacos服务配置中心
1 新建nacos配置中心moudle(cloudalibaba-config-nacos-client3377)
2 pom,除了nacos-discovery,还要加入nacos-config依赖
3 bootstrap.yml
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 # 告诉本服务模块nacos注册中心的地址在哪
config:
server-addr: localhost:8848 # nacos作为配置中心的地址
file-extension: yaml # 指定配置信息的格式
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
# nacos-config-client-dev.yaml
所以,在nacos控制台的配置列表选项卡中:
1 新建一个配置信息模块,Data Id就是nacos-config-client-dev.yaml
2 配置文件格式就是yaml
3 配置内容:
config: #这里有一个空格
info: nacos config center,version=1
4 application.yml
spring:
profiles:
active: dev # 表示开发环境
5 主启动
6 业务类
7 测试:
和前边SpringCloud Config测试一样,从浏览器输入测试链接(localhost:3377/config/info),看看是否能拿到nacos注册中心的配置信息,
nacos自带配置的动态刷新功能,不用再加入Bus;现在修改nacos注册中心的配置信息,浏览器可以自动拿到更新后的配置信息
6 nacos三种方案加载配置:
1 Data Id方案
2 Group方案
3 Namespace方案
说明:
为了读取到nacos中心特定的配置信息,nacos按照Data Id、Group、Namespace三种方案,将配置信息进行了分类,用户只需要按照这几种分类
方案,选定自己需要的那一种,将配置信息配置好,然后在nacos-client的yml文件中做好相应的配置,就可以从nacos-client读取到相应的配置信息了
7 Nacos集群
1 架构
nacos在实际应用中,使用的是集群,每个nacos节点都自带一个数据库,用于将自己节点的配置信息持久化,放置配置的丢失,但是在nacos集群的时候,每个节点都有自己的信息,不能保证配置信息数据的统一,每个节点的配置信息都要相同,所以外部搭建一个mysql数据库,将统一的配置信息持久化到mysql中,每个nacos节点都可以读到相同的配置信息
2 搭建nacos外部mysql数据库
1 执行nacos自带的mysql数据库脚本,创建nacos_config数据库
2 修改nacos自带的conf中的application.properties配置文件信息,将数据库配置加进去,让nacos服务启动的时候可以连接到nacos_config
3 重启nacos服务,这时候,nacos读取到的配置信息就是nacos_config数据库的信息了,就从derby转换到nacos_config了
3 在linux环境中,搭建nacos集群
1 需要将linux版的nacos先安装好
2 将里面的nacos_config数据库放到linux的mysql中
3 修改nacos的配置文件,形成集群环境
4 Nginx搭建,因为需要用到Nginx负载均衡分配选用哪一台nacos节点提供服务
八、Sentinel
1 简介
sentinel是alibaba做限流、降级、熔断、速率控制的组件,和之前的Hystrix差不多,只不过Hystrix需要自己手动搭建单独的服务模块,比较麻烦,而 这个Sentinel是一个单独的组件,并且有自带的界面化显示,更加方便高效
2 安装运行
1 下载Sentinel的jar包,启动服务,再访问localhost:8080即可跳转到Sentinel控制台,登录即可(登录名和密码都是sentinel)
3 Sentinel的使用
1 sentinel配合nacos的使用
2 新建一个服务提供者模块(cloudalibaba-sentinel-service8401)
3 pom
pom中除了加入nacos的相关依赖,再加入sentinel的依赖
4 yml
新加入sentinel的相关配置
5 主启动
6 业务类
7 测试
1 先后启动nacos服务,服务提供者模块,Sentinel服务
2 打开Sentinel的控制台
3 这时候访问8401服务提供者,就可以在Sentinel的监控台看到相关的监控情况
4 流量控制
1 流控规则
Sentinel有关于流量控制的规则,需要自己在控制条进行配置
规则:
1 阈值(QPS)
当访问流量超过这个阈值后,会报错,在前台返回相应的信息
2 线程
同时只能处理一个请求,当很多请求进来后,出现排队情况就会报错,在前台返回相应的信息
2 流控模式
模式:
1 直接
当某一请求达到限流条件,直接限流该请求
2 关联
当请求A达到限流条件后,将与请求A共享资源的请求B挂掉,以保证请求A的正常响应
3 链路
3 流控效果
效果:
1 快速失败
当达到限流条件后,快速往前台返回信息,通知用户被限流
2 Warm Up(预热)
一个服务平时可能无人问津,然后突然访问量暴增,这会给服务器很大压力,所以需要预热
预热规则:
前提:该请求使用的流控规则是阈值(QPS)模式
描述:
开启Warm Up后,Sentinel会自动将访问阈值设置为较低水平,这个水平=正常阈值/冷加载因子(默认为3),且将这个水平保持一段时间(这个时间需手动设置),当过了这个时间后,再恢复到正常阈值,这就是Warm Up
3 排队等待
当大量请求过来后,开启排队等候模式,设置每秒钟处理多少请求,其他的请求排队等候,其实是漏桶算法
5 熔断降级
当请求达到某一临界条件后,实现熔断,随后降级,再恢复
降级策略:
1 RT
在请求频率满足相关条件的前提下,请求需要在特定的RT(请求处理时间长度)内完成,否则Sentinel就会进行熔断
2 异常比例
在请求频率满足相关条件的前提下,每秒钟的请求中出现异常的比例达到某一阈值,Sentinel会进行熔断,当过了时间窗口之后,会再恢复正常
3 异常数
一分钟内的请求异常数达到特定阈值,Sentinel会进行熔断,时间窗口一般大于60秒
6 热点Key
描述:
Sentinel会分析请求中的参数,进行验证,看是否满足用户Sentinel的相关参数配置
1 请求参数不满足Sentinel中配置的规则,比如,这个参数超过请求的频率阈值,就会降级返回错误页面,如果写了兜底的方法,就会进入兜底方法, 这里用到了@SentinelResource
2 参数例外项:
在基本热点key的限流规则基础之上,需要对这个参数限流更加灵活,比如,当这个参数的值是某一个特定值的时候,有特定的阈值规则,当这个参数的值是特定之外的值的时候,继续遵守一般的热点key限流规则,这就是热点key的参数例外项限流规则
7 系统规则
1 概述:
相对于上边Sentinel更加细粒度的流量控制,Sentinel系统规则是宏观上的对应用级别的流量控制
2 模式
1 Load自适应
2 CPU usage
3 平均RT
4 并发线程数
3 入口QPS
8 @SentinelResource
1 解耦+自定义流控兜底方法
在上述流控案例中,触发流控熔断降级后,可以进入兜底的方法,返回相关的提示数据,但是每一个流控接口可能都需要一个兜底方法,不能全都在同一个controller中,耦合度太高,这就又回到前面的Hystrix的兜底方案了,也是搞一个单独的Handler,将所有自定义的兜底方法全都放在里面,这样就解耦了,只需要在具体接口@SentinelResource注解上配置指定兜底方法即可
9 Sentinel案例讲解fallback和blockHandler
1 fallback
@SentinelResource注解中的fallback属性,管理的是方法中的运行异常(RunTimeException),进而进入兜底方法
2 blockHandler
@SentinelResource注解中的blockHandler属性,管理的是Sentinel控制台的限流熔断降级规则,进而进入兜底方法
3 如果注解中两个参数都进行了配置,那么blockHandler优先级高于fallback,也就是说Sentinel流控规则起作用
4 exceptionsToIgnore
eg: exceptionsToIgnore = {NullPointException.class}
注解中还有这个属性,属性值是某一个异常,表示忽略对应的运行异常,不再进行fallback,不进入兜底方法,而是直接返回默认的异常数据
10 Sentinel整合OpenFeign
1 之前消费者调用生产者用的是RestTemplate请求风格,这里将Sentinel和OpenFeign整合起来,使用Feign进行调用
2 跟之前讲OpenFeign差不多,也是引入OpenFeign的依赖,搞一个Feign的接口,接口上使用@FeignClient注解,注解中有一个fallback属性,对应 着兜底方法,兜底方法就是这个接口的实现类,在实现类中定义兜底方法的细节,返回什么样的数据
11 Sentinel规则持久化
1 问题
在Sentinel控制台配置好流控规则后,当服务重启或者关机之后,Sentinel数据丢失了,当服务众多的时候,还需要重新配置Sentinel数据,这 就不现实,所以需要将Sentinel持久化
2 将Sentinel流控规则配置到Nacos中,让服务读取到即可做到持久化
3 怎么做
1 在生产者的pom中引入sentinel-datasource-nacos依赖
2 然后在生产者yml中加上Sentinel持久化的相关配置,告诉服务提供者去Nacos读取Sentinel的数据
3 在Nacos中配置上Sentinel的流控规则,这样服务启动就可以读取到Nacos的Sentinel数据,然后Sentinel控制台就可以通过服务拿到数据
九、Seata
1 简介
1 Seata需要Nacos环境为基础
2 为了解决多个微服务之间数据一致性问题,保证各个系统之间统一调度,出现了分布式事务(SpringCloud Alibaba Seata)
3 模式
Seata有六大模式,针对不同开发需求选择相应的开发模式即可,这里是用的是AT模式(低侵入模式)
2 架构
1 分布式事务唯一ID
Transaction ID XID
2 三组件模型
1 Transaction Coordinator(TC) 事务协调器,事务的控制者,seata服务器
2 Transaction Manager(TM) 控制全局事务边界,事务的发起者,@GlobalTransactional加在这一方
3 Resource Manager(RM) 控制分支事务,事务的资源方、参与方,比如各个分布式数据库
3 级别先后
TM--->TC--->RM
3 Seata原理
1 流程
1 事务第一阶段
1 TM开启分布式事务(TM向TC注册全局事务记录)
2 RM向TC汇报资源准备情况
3 TM结束分布式事务,事务一阶段结束(TM通知TC提交或者回滚分布式事务)
2 事务第二阶段
1 TC汇总事务信息,决定分布式事务提交还是回滚
2 TC通知所有RM,提交或者回滚资源,事务二阶段结束
2 原理
在某一个服务执行sql之前seata会形成这个数据的前置快照(before pic),修改之后会形成后置快照(after pic),当TC收到TM的回滚消息的时候,TC就会按照这些快照向RM发出回滚指令,让RM进行反向sql操作,在避免脏数据的前提下恢复数据,如果有脏数据,则提交人工操作
4 下载安装配置
1 到Seata官网下载安装包
2 将安装包解压到相关目录
3 将conf中的file.conf文件做相应修改
1 自定义事务组名称
2 事务日志存储模式db
3 数据库连接信息
4 在本地mysql中新建seata数据库,将conf中db_store.sql执行到seata数据库即可
5 修改conf中registry.conf文件,将其中的registry的type改为nacos,相应的下边nacos模块中serverAddr属性值为本地nacos地址
5 数据库准备
1 业务案例
这里模拟订单、库存、账户分布式操作
2 数据库
1 seata_order(订单数据库)
2 seata_storage(库存数据库)
3 seata_account(账户数据库)
3 数据库中各自创建相应的表,并且都要有回滚日志记录表(undo_log)
6 业务需求
下订单--》 减库存--》 扣余额 --》 修改订单状态,使用OpenFeign、Nacos、Seata
7 搭建3个moudle
1 seata-order-service2001(订单模块)
1 pom
加入nacos-discovery和springcloudalibaba-seata依赖
2 yml
加入nacos、seata相关配置
3 file.conf
在resource目录下新建file.conf文件,将seata安装包中的file.conf文件内容拷贝过来,其中有相关的数据库配置
4 registry.conf
在resource目录下新建registry.conf文件,将seata安装包中的registry.conf文件内容拷贝过来,其中有相关的nacos配置
5 domain(entity)
1 CommonResoult
2 Order
6 dao
OrderDao,其中有两个接口,创建订单的接口,修改订单状态的接口
7 service
1 三个service接口
1 OrderService
2 StorageService
3 AccountService
2 实现类
OrderServiceImpl,使用@FeignClient注解实现请求调用,实现类中创建订单,同时进行减库存,扣钱,修改订单状态三个服务
8 controller
写一个创建订单的方法即可
9 配置类
DataSourceProxyConfig,这是配置项目的Seata数据源
10 主启动
2 seata-storage-service2002(库存模块)
同理
3 seata-account-service2003(账户模块)
同理
8 @GlobalTransactional
1 这是Seata的分布式事务注解,作用于业务方法上,全局回滚
2 这里模拟订单服务出现异常,导致整体服务不同步(订单创建成功,库存扣减成功,账户扣减成功,但是订单状态还是未支付)
3 解决方法就是在业务方法上添加@GlobalTransactional,如果其中一个服务失败,则全局回滚,避免全局服务的不同步
十、Snowflake雪花算法
1 简介
这是Twitter研究的用于生成唯一id的算法
2 选型过程
1 基础选型过程
1 UUID
最初使用java自带的UUID工具类即可生成唯一的id,但是由于位数太长,大量插入数据库的时候效率太低,浪费资源,当并发量较小的时候可以用
2 Mysql
如果使用Mysql集群生成唯一id,需要设置步长,这样会造成数据库资源的浪费,不符合高并发场景
3 Redis
使用Redis集群生成唯一id,可以解决高并发场景带来的问题,但是操作比较麻烦,比如设置哨兵模式,还有后期维护问题,只是为了得到一个唯一的id,大费周章
4 snowflake
所以后来Twitter公司开源了Snowflake,针对性的解决了上述的问题,只需要简单的操作,就可以生成唯一的id,还能适应高并发场景,并且可以自定义生成id,操作更加灵活
3 使用
1 使用方法
1 在服务中引入hutool依赖,直接使用其中的api即可
2 自己定义生成规则,比如使用哪个机器号和机器码,自定义snowflake类,定义生成id的具体方法
2 其他版本的snowflake
1 因为snowflake依靠机器本身的时钟机制生成唯一id,只要机器时钟正常,不发生时钟回拨,就可以保证id的唯一性
2 即使在分布式场景中,发生了时钟的不一致,需要进行时钟的回拨,有两个公司给出了相应的解决方法
1 百度的UIDGenerator
2 美团的Leaf-snowflake