一、服务注册中心
Eureka
-
eureka-client
- 服务发现:从注册中心上获取服务信息
- 服务注册:将服务信息注册进注册中心
- 依赖引入
<!--EurekaClient端依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
-
eureka-service
- 提供服务注册服务
- 依赖引入
<!--eureka-server--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
-
eureka-client的YML文件配置
eureka: instance: # 服务名称修改 instance-id: payment8001 # 访问信息有IP信息提示 prefer-ip-address: true client: #表示是否将自己注册进Eurekaserver默认为true。 register-with-eureka: true #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 fetch-registry: true service-url: # 单机注册 # defaultZone: http://localhost:7001/eureka # 集群注册 defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
-
eureka-service的YML文件配置
eureka: instance: #eureka服务端的实例名称 hostname: eureka7001.com # 单机配置 # hostname: localhost client: #false表示不向注册中心注册自己。 register-with-eureka: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务 fetch-registry: false service-url: #设置与Eureka server交互的地址查询服务和注册服务都需要依赖这个地址。 #单机就是7001自己 # defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #集群指向其它eureka defaultZone: http://eureka7002.com:7002/eureka/ server: #关闭自我保护机制,保证不可用服务被及时踢除 enable-self-preservation: false #设置心跳检测时间 eviction-interval-timer-in-ms: 2000
-
自我保护机制∶
默认情况下EurekaClient定时向EurekaServer端发送心跳包。如果Eureka在server端在一定时间内(默认90秒)没有收到EurekaClient发送心跳包,便会直接从服务注册列表中剔除该服务,但是在短时间( 90秒中)内丢失了大量的服务实例心跳,这时候Eurekaserver会开启自我保护机制,不会剔除该服务(该现象可能出现在如果网络不通但是EurekaClient为出现宕机,此时如果换做别的注册中心如果一定时间内没有收到心跳会将剔除该服务,这样就出现了严重失误,因为客户端还能正常发送心跳,只是网络延迟问题,而保护机制是为了解决此问题而产生的)。
使用eureka.server.enable-self-preservation = false
可以禁用自我保护模式。eureka: ... server: #关闭自我保护机制,保证不可用服务被及时踢除 enable-self-preservation: false eviction-interval-timer-in-ms: 2000
Consul
-
Consul安装与使用
-
查看版本
consul -v
-
开发模式启动
consul agent -dev
-
-
Consul引入
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency>
-
YML文件配置
###consul服务端口号 server: port: 8006 spring: application: name: consul-provider-payment ####consul注册中心地址 cloud: consul: host: localhost port: 8500 discovery: #hostname: 127.0.0.1 service-name: ${spring.application.name}
Zookeeper
-
zookeeper依赖引入
<!-- SpringBoot整合zookeeper客户端 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId> <!--先排除自带的zookeeper3.5.3 防止与3.5.10起冲突--> <exclusions> <exclusion> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> </exclusion> </exclusions> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper --> <!--引入与zookeeper服务端一致的版本--> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.5.10</version> </dependency>
-
YML文件配置
#服务别名----注册zookeeper到注册中心名称 spring: application: name: order-consumer-service cloud: zookeeper: #zookeeper连接地址 connect-string: 101.34.61.89:2181
-
使用@EnableDiscoveryClient注解开启注册中心功能
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient//该注解用于向使用consul或者zookeeper作为注册中心时注册服务 public class PaymentMain8004 { public static void main(String[] args) { SpringApplication.run(PaymentMain8004.class, args); } }
三个注册中心异同点
组件名 | 语言CAP | 服务健康检查 | 对外暴露接口 | Spring Cloud集成 |
---|---|---|---|---|
Eureka | Java | AP | 可配支持 | HTTP |
Consul | Go | CP | 支持 | HTTP/DNS |
Zookeeper | Java | CP | 支持客户端 | 已集成 |
CAP:
- C:Consistency (强一致性)
更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致,不能存在中间状态。 - A:Availability (可用性)
系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。 - P:Partition tolerance (分区容错性)
分布式系统在遇到任何网络分区故障时,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
二、负载均衡
Ribbon
-
依赖引入
<!--EurekaClient端依赖 此依赖包含ribbon--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
spring-cloud-starter-netflix-eureka-client自带了spring-cloud-starter-ribbon引用,如需单独引入ribbon:
<dependency> <groupld>org.springframework.cloud</groupld> <artifactld>spring-cloud-starter-netflix-ribbon</artifactid> </dependency>
-
RestTemplate
- getForObject() / getForEntity() - GET请求方法
- getForObject():返回对象为响应体中数据转化成的对象,基本上可以理解为Json。
- getForEntity():返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等。
- postForObject() / postForEntity() - POST请求方法
-
使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class ApplicationContextConfig { @Bean @LoadBalanced//使用@LoadBalanced注解赋予RestTemplate负载均衡的能力 public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
-
Ribbon默认自带的负载规则
- RoundRobinRule 轮询
- RandomRule 随机
- RetryRule 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试
- WeightedResponseTimeRule 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
- BestAvailableRule 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
- AvailabilityFilteringRule 先过滤掉故障实例,再选择并发较小的实例
- ZoneAvoidanceRule 默认规则,复合判断server所在区域的性能和server的可用性选择服务器
三、服务远程调用
OpenFeign
-
依赖引入
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
OpenFeign服务调用使用
-
主启动类上使用@EnableFeignClients
-
consumer业务逻辑接口+@FeignClient配置调用provider服务
import com.lun.springcloud.entities.CommonResult; import com.lun.springcloud.entities.Payment; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @Component @FeignClient(value = "CLOUD-PAYMENT-SERVICE") public interface PaymentFeignService { @GetMapping(value = "/payment/get/{id}") public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id); }
-
-
OpenFeign超时控制
OpenFeign客户端一般默认等待1秒钟,超时将会跳出错误Spring Boot默认错误页面,主要异常:
feign.RetryableException:Read timed out executing GET http://CLOUD-PAYMENT-SERVCE/payment/feign/timeout
#设置feign客户端超时时间(OpenFeign默认支持ribbon)(单位:毫秒) ribbon: #指的是建立连接后从服务器读取到可用资源所用的时间 ReadTimeout: 5000 #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间 ConnectTimeout: 5000
-
OpenFeign日志增强
4.1 日志级别
- NONE:默认的,不显示任何日志;
- BASIC:仅记录请求方法、URL、响应状态码及执行时间;
- HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
- FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。
4.2 配置日志bean
import feign.Logger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FeignConfig { @Bean Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } }
4.3 YML文件里需要开启日志的Feign客户端
logging: level: # feign日志以什么级别监控哪个接口 com.lun.springcloud.service.PaymentFeignService: debug
四、服务降级、服务熔断
Hystrix
-
Hystrix是什么?
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
-
Hystrix的作用
-
服务降级:
服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback -
服务熔断:
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。
-
服务限流:
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。
-
-
hystrix依赖引入
<!--hystrix--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
-
hystrix使用
4.1 主启动类增加注解
@EnableHystrix
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.hystrix.EnableHystrix; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableFeignClients @EnableHystrix public class OrderHystrixMain80 { public static void main(String[] args) { SpringApplication.run(OrderHystrixMain80.class,args); } }
4.2 配置开启hystrix
#开启 feign: hystrix: enabled: true
4.3 使用
@HystrixCommand
开启局部服务降级配置import com.lun.springcloud.service.PaymentHystrixService; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @Slf4j public class OrderHystirxController { @Resource private PaymentHystrixService paymentHystrixService; @GetMapping("/consumer/payment/hystrix/timeout/{id}") //fallbackMethod:指向具体的降级方法名 //commandProperties:配置服务降级触发规则 @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = { @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500") }) public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { //int age = 10/0; String result = paymentHystrixService.paymentInfo_TimeOut(id); return result; } //善后方法 public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){ return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o"; } }
4.4 使用
@DefaultProperties
开启全局服务降级,跳转到统一处理结果页面//在需要开启全局服务降级的类上增加注解 @DefaultProperties(defaultFallback = "payment_Global_FallbackMethod") // 下面是全局fallback方法 public String payment_Global_FallbackMethod() { return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~"; }
4.5 配合OpenFeign实现消费端服务降级
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @Component //value:调用注册中心指定服务名的服务 //fallback:服务降级处理类 @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT" , fallback = PaymentFallbackService.class)//指定PaymentFallbackService类 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); }
import org.springframework.stereotype.Component; /* * 实现Feign调用接口,重写对应方法的降级处理逻辑 */ @Component public class PaymentFallbackService implements PaymentHystrixService { @Override public String paymentInfo_OK(Integer id) { return "-----PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o"; } @Override public String paymentInfo_TimeOut(Integer id) { return "-----PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o"; } }
-
服务熔断
5.1 服务熔断的三个参数:
- 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
- 请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次7,即使所有的请求都超时或其他原因失败,断路器都不会打开。
- 错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。
5.2 代码实现
//=====服务熔断 @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) { ... }
5.3 hystrix的服务熔断自动恢复功能
当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
5.4 hystrix的所有相关配置
@HystrixCommand(fallbackMethod = "fallbackMethod", groupKey = "strGroupCommand", commandKey = "strCommand", threadPoolKey = "strThreadPool", commandProperties = { // 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离 @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"), // 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数) @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"), // 配置命令执行的超时时间 @HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"), // 是否启用超时时间 @HystrixProperty(name = "execution.timeout.enabled", value = "true"), // 执行超时的时候是否中断 @HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"), // 执行被取消的时候是否中断 @HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"), // 允许回调方法执行的最大并发数 @HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"), // 服务降级是否启用,是否执行回调函数 @HystrixProperty(name = "fallback.enabled", value = "true"), // 是否启用断路器 @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), // 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"), // 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过 circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50, 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"), // 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,如果成功就设置为 "关闭" 状态。 @HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"), // 断路器强制打开 @HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"), // 断路器强制关闭 @HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"), // 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间 @HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"), // 该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。 // 比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常 @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"), // 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。 @HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"), // 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。 @HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"), // 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。 @HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"), // 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数, // 就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行, // 那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。 @HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"), // 该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。 @HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"), // 是否开启请求缓存 @HystrixProperty(name = "requestCache.enabled", value = "true"), // HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中 @HystrixProperty(name = "requestLog.enabled", value = "true"), }, threadPoolProperties = { // 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量 @HystrixProperty(name = "coreSize", value = "10"), // 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,否则将使用 LinkedBlockingQueue 实现的队列。 @HystrixProperty(name = "maxQueueSize", value = "-1"), // 该参数用来为队列设置拒绝阈值。 通过该参数, 即使队列没有达到最大值也能拒绝请求。 // 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。 @HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"), } ) public String doSomething() { ... }
五、网关
Gateway
-
Gateway的作用
- 方向代理
- 鉴权
- 流量控制
- 熔断
- 日志监控
-
三大核心概念
- Route(路由) - 路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如断言为true则匹配该路由;
- Predicate(断言) - 参考的是Java8的java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由;
- Filter(过滤) - 指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
-
Gateway依赖引入
<!--gateway--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
-
配置路由的第一种方式:YML配置
#############################新增网关配置########################### cloud: gateway: routes: - id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: http://localhost:8001 #匹配后提供服务的路由地址 #uri: lb://cloud-payment-service #匹配后提供服务的路由地址 predicates: - Path=/payment/get/** # 断言,路径相匹配的进行路由 - id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: http://localhost:8001 #匹配后提供服务的路由地址 #uri: lb://cloud-payment-service #匹配后提供服务的路由地址 predicates: - Path=/payment/lb/** # 断言,路径相匹配的进行路由 ####################################################################
-
配置路由的第二种方式:配置类
import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class GateWayConfig { @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) { RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes(); /** * 参数 * id:路由的唯一id * fn:函数式接口apply */ routes.route("path_route_atguigu", //路由路径匹配规则 r -> r.path("/guonei") //路由匹配后跳转的地址 .uri("http://news.baidu.com/guonei")).build(); return routes.build(); } }
-
Gateway配置动态路由
默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能(不写死一个地址)
修改YML配置文件:#############################新增网关配置########################### cloud: gateway: discovery: locator: enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由 routes: - id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名 #uri: http://localhost:8001 #匹配后提供服务的路由地址 #在gateway中配置uri配置有三种方式,包括 #第一种:ws(websocket)方式: uri: ws://localhost:8001 #第二种:http方式: uri: http://localhost:8001/ #第三种:lb(注册中心中服务名字)方式: uri: lb://cloud-payment-service uri: lb://cloud-payment-service #匹配后提供服务的路由地址 predicates: - Path=/payment/get/** # 断言,路径相匹配的进行路由 - id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名 #uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-payment-service #匹配后提供服务的路由地址 predicates: - Path=/payment/lb/** # 断言,路径相匹配的进行路由 ####################################################################
-
GateWay常用的Predicate
server: port: 9527 spring: application: name: cloud-gateway cloud: gateway: discovery: locator: enabled: true #开启从注册中心动态创建路由的功能,利用服务名进行路由 routes: - id: provider_routh #路由ID,没有固定规则但是要求唯一,建议配合服务名 #uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-payment-service #lb://设置服务名 predicates: - Path=/provider/hystrix/** #断言:路径相匹配进行路由 - id: provider_routh2 #路由ID,没有固定规则但是要求唯一,建议配合服务名 #uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-payment-service #lb://设置服务名 predicates: - Path=/provider/hello/** #断言:路径相匹配进行路由 - Before=2021-08-25T12:53:46.101+08:00[Asia/Shanghai] #匹配这个时间之前的请求 - After=2021-08-25T12:53:46.101+08:00[Asia/Shanghai] #匹配这个时间之后的请求 - Between=2021-08-25T12:53:46.101+08:00[Asia/Shanghai],2021-09-25T12:53:46.101+08:00[Asia/Shanghai] #匹配这个两个时间的请求 - Cookie=username,zzyy - Header=X-Request-Id,\d+ #请求头要有 X-Request-Id 属性并且值为整数的正则表达式 - Host=**.atguigu.com #host地址必须以.atguigu.com结尾才能匹配路由 - Method=GET #请求方式是 GET - Query=username,\d+ #要有参数名 username 并且值还要是正数才能路由
-
GateWay的Filter
8.1 官方为我们内置了多种路由过滤器
8.2 自定义全局过滤器
-
作用:
-
全局日志记录
-
同一网关鉴权
-
-
需要实现的接口:
-
GlobalFilter
-
Ordered
-
-
代码实现:
import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.Date; @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,非法用户,o(╥﹏╥)o"); exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } //过滤器权重 @Override public int getOrder() { //权重值在Integer.MIN_VALUE和Integer.MAX_VALUE之间 //权重值越小,权重越大 return 0; } }
-
六、配置中心
Config
-
SpringCloud Config分为服务端和客户端两部分。
- 服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口
- 客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。
-
Config服务端配置
-
依赖引入
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>
-
YML配置
server: port: 3344 spring: application: name: cloud-config-center #注册进Eureka服务器的微服务名 cloud: config: server: git: #配置中心的地址 uri: https://gitee.com/xxxxx/spring-cloud-config.git ####仓库名称 search-paths: - springcloud-config # git用户名 username: xxxxxxxxxxxxx # git密码 password: xxxxxxxxxxxxxx ####读取分支 label: master #服务注册到eureka地址 eureka: client: service-url: defaultZone: http://localhost:7001/eureka
-
主启动类添加注解
@EnableConfigServer
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; @SpringBootApplication @EnableConfigServer public class ConfigCenterMain3344 { public static void main(String[] args) { SpringApplication.run(ConfigCenterMain3344.class, args); } }
-
配置读取规则
/{label}/{application}-{profile}.yml(推荐)
-
-
Config客户端配置
-
依赖引入
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
-
增加bootstrap.yml配置文件
applicaiton.yml是用户级的资源配置项
bootstrap.yml是系统级的,优先级更加高
bootstrap.yml优先级高于application.ymlserver: port: 3355 spring: application: name: config-client cloud: #Config客户端配置 config: label: master #分支名称 name: config #配置文件名称 profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://localhost:3344/master/config-dev.yml uri: http://localhost:3344 #配置中心服务端的地址 #服务注册到eureka地址 eureka: client: service-url: defaultZone: http://localhost:7001/eureka
-
-
Config动态刷新之手动版
-
修改客户端YML
# 暴露所有的监控端点 management: endpoints: web: exposure: include: "*"
-
在读取配置的类加上@RefreshScope注解
import org.springframework.cloud.context.config.annotation.RefreshScope; ... @RestController @RefreshScope//开启自动刷新功能 public class ConfigClientController { ... }
-
更改github配置文件内容,发送post请求刷新
curl -X POST "http://localhost:3355/actuator/refresh"
-
七、Bus消息总线
BUS
-
bus的作用
- Spring Cloud Bus 配合Spring Cloud Config 使用可以实现配置的动态刷新。
- Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道。
-
bus整合RabbitMQ实现动态刷新全局广播
-
config客户端依赖引入
<!--添加消息总线RabbitMQ支持--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
-
config客户端YML配置
server: port: 3366 spring: application: name: config-client cloud: #Config客户端配置 config: label: master #分支名称 name: config #配置文件名称 profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://localhost:3344/master/config-dev.yml uri: http://localhost:3344 #配置中心地址 #rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口 rabbitmq: host: localhost #主机地址 port: 5672 #端口号 username: guest password: guest #服务注册到eureka地址 eureka: client: service-url: defaultZone: http://localhost:7001/eureka # 暴露监控端点 management: endpoints: web: exposure: include: "*"
-
config客户端Controller代码
import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** */ @RestController @RefreshScope public class ConfigClientController { @Value("${server.port}") private String serverPort; //获取git上的配置 @Value("${config.info}") private String configInfo; @GetMapping("/configInfo") public String configInfo() { return "serverPort: "+serverPort+"\t\n\n configInfo: "+configInfo; } }
-
config服务端依赖引入
<!--添加消息总线RabbitNQ支持--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amap</artifactId> </dependency> <dependency> <groupId>org-springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
-
config服务端YML修改
#在原有的配置上加上rabbitmq相关配置 #rabbitmq相关配置<-------------------------- rabbitmq: host: localhost port: 5672 username: guest password: guest ##rabbitmq相关配置,暴露bus刷新配置的端点<-------------------------- management: endpoints: #暴露bus刷新配置的端点 web: exposure: include: 'bus-refresh'
-
测试,启动配置中心服务端和两个客户端
-
修改Git上配置文件内容
config: info: "master branch,spring-cloud-config/application-dev.yml version=6"
-
发送POST请求:
curl -X POST "http://localhost:3344/actuator/bus-refresh"
-
客户端获取配置信息,配置信息会更新
-
-
-
bus动态刷新定点通知
- 如果不想通知全部的客户端刷新配置信息,只想通知指定的客户端
- 公式:http://localhost:3344/actuator/bus-refresh/{destination}
- /bus/refresh请求不再发送到具体的服务实例上,而是发给config server通过destination参数类指定需要更新配置的服务或实例。例:
curl -X POST "http://localhost:3344/actuator/bus-refresh/config-client:3355
- 如果不想通知全部的客户端刷新配置信息,只想通知指定的客户端
八、消息驱动
Stream
-
Cloud Stream的作用
比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange,kafka有Topic和Partitions分区。
这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,Spring Cloud Stream通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离,从而做到屏蔽底层消息中间件的差异,降低开发维护成本,统一消息编程的模型。
Binder:- INPUT对应于消费者
- OUTPUT对应于生产者
-
Stream标准流程
- Binder - 很方便的连接中间件,屏蔽差异。
- Channel - 通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置。
- Source和Sink - 简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入。
-
编码API和常用注解
组成 说明 Middleware 中间件,目前只支持RabbitMQ和Kafka Binder Binder是应用与消息中间件之间的封装,目前实行了Kafka和RabbitMQ的Binder,通过Binder可以很方便的连接中间件,可以动态的改变消息类型(对应于Kafka的topic,RabbitMQ的exchange),这些都可以通过配置文件来实现 @Input 注解标识输入通道,通过该输乎通道接收到的消息进入应用程序 @Output 注解标识输出通道,发布的消息将通过该通道离开应用程序 @StreamListener 监听队列,用于消费者的队列的消息接收 @EnableBinding 指信道channel和exchange绑定在一起 -
生产者模块
-
依赖引入
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency>
-
YML配置
server: port: 8801 spring: application: name: cloud-stream-provider cloud: stream: binders: # 在此处配置要绑定的rabbitmq的服务信息; defaultRabbit: # 表示定义的名称,用于于binding整合 type: rabbit # 消息组件类型 environment: # 设置rabbitmq的相关的环境配置 spring: rabbitmq: host: localhost port: 5672 username: guest #rabbitmq初始密码 password: guest #rabbitmq初始密码 bindings: # 服务的整合处理 output: # 这个名字是一个通道的名称 destination: studyExchange # 表示要使用的Exchange名称定义 content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain” binder: defaultRabbit # 设置要绑定的消息服务的具体设置 eureka: client: # 客户端进行Eureka注册的配置 service-url: defaultZone: http://localhost:7001/eureka instance: lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒) lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒) instance-id: send-8801.com # 在信息列表时显示主机名称 prefer-ip-address: true # 访问的路径变为IP地址
-
定义发送消息的接口
public interface IMessageProvider { public String send(); }
-
发送消息接口实现类
import com.lun.springcloud.service.IMessageProvider; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.messaging.Source; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.MessageChannel; import javax.annotation.Resource; import java.util.UUID; @EnableBinding(Source.class) //定义消息的推送管道 public class MessageProviderImpl implements IMessageProvider { @Resource private MessageChannel output; // 消息发送管道 @Override public String send() { String serial = UUID.randomUUID().toString(); // 消息生产者服务构建(build)一个Message消息对象,然后传递给Source池,再通过管道binding输出给消息中间件MQ // 最后消费者服务的Sink池中可以获取到Message对象 output.send(MessageBuilder.withPayload(serial).build()); System.out.println("*****serial: "+serial); return null; } }
-
-
消费者模块
-
依赖引入
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency>
-
YML配置
server: port: 8802 spring: application: name: cloud-stream-provider cloud: stream: binders: # 在此处配置要绑定的rabbitmq的服务信息; defaultRabbit: # 表示定义的名称,用于于binding整合 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” binder: defaultRabbit # 设置要绑定的消息服务的具体设置 eureka: client: # 客户端进行Eureka注册的配置 service-url: defaultZone: http://localhost:7001/eureka instance: lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔(默认是30秒) lease-expiration-duration-in-seconds: 5 #如果现在超过了5秒的间隔(默认是90秒) instance-id: send-8802.com # 在信息列表时显示主机名称 ip-address: true # 访问的路径变为IP地址
-
消费消息代码实现
import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.annotation.StreamListener; import org.springframework.cloud.stream.messaging.Sink; import org.springframework.messaging.Message; import org.springframework.stereotype.Component; @Component @EnableBinding(Sink.class) public class ReceiveMessageListenerController { @Value("${server.port}") private String serverPort; @StreamListener(Sink.INPUT) public void input(Message<String> message) { System.out.println("消费者1号,----->接受到的消息: "+message.getPayload()+"\t port: "+serverPort); } }
-
-
消息重复消费问题
- 启动一个消息生产者服务,两个消息消费者服务
- 消息生产者发送消息,两个消息消费者都能收到消息,存在重复消费的问题
- 除了消息重复消费外,还需要解决消息持久化问题
-
group解决消息重复消费和消息持久化问题
-
修改消费者和生产者的YML配置
spring: application: name: cloud-stream-provider cloud: stream: binders: # 在此处配置要绑定的rabbitmq的服务信息; defaultRabbit: # 表示定义的名称,用于于binding整合 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” binder: defaultRabbit # 设置要绑定的消息服务的具体设置 #不同的组是可以重复消费的,同一个组内会发生竞争关系,只有其中一个可以消费。 group: a_group #配置消息分组,解决消息重复消费和消息持久化问题,不配置分组,服务宕机会导致消息丢失
-
消费者和生产者group设置为相同时,生产者发送的消息,两个消费者是轮询方式消费消息的,每次只能有一个消费者获取到
-
消费者和生产者group设置为不同时,两个消费者都能消费当前消息,还是重复消费
-
两个消费者,一个消费者去掉分组属性配置,生产者发送消息后,启动消费者服务,无分组属性配置的消费者不能消费到消息,有分组属性配置的消费者可以获取到生产者发送的消息(消息持久化体现)
-
九、服务调用链路跟踪
Sleuth
-
zipkin搭建
-
下载zipkin
-
运行jar,启动zipkin
java -jar zipkin-server-2.23.9-exec.jar
-
运行控制台
http://localhost:9411/zipkin/
-
-
相关术语
完整的调用链路
表示一请求链路,一条链路通过Trace ld唯一标识,Span标识发起的请求信息,各span通过parent id关联起来
—条链路通过Trace ld唯一标识,Span标识发起的请求信息,各span通过parent id关联起来。
整个链路的依赖关系如下:
名词解释:
- Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识
- span:表示调用链路来源,通俗的理解span就是一次请求信息
-
sleuth实现链路监控
-
服务提供者依赖引入
<!--包含了sleuth+zipkin--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
-
修改YML配置文件
server: port: 8001 spring: application: name: payment-provide-service #sleuth请求链路监控配置 zipkin: base-url: http://localhost:9411 #监控zipkin中心地址 sleuth: sampler: #采样率值介于 0 到 1 之间,1 则表示全部采集 probability: 1
-
编写Controller
@RestController @Slf4j public class PaymentController { ... @GetMapping("/payment/zipkin") public String paymentZipkin() { return "hi ,i'am paymentzipkin server fall back,welcome to here, O(∩_∩)O哈哈~"; } }
-
在创建一个服务消费者,配置同上,编写消费者Controller
// ====================> zipkin+sleuth @GetMapping("/consumer/payment/zipkin") public String paymentZipkin() { String result = restTemplate.getForObject("http://localhost:8001"+"/payment/zipkin/", String.class); return result; } }
-
启动服务提供者和消费者,进行服务调用,zipkin控制台展示调用链路详情
-