# SpringCloud2020学习笔记 ## 技术选型 | 依赖 | 版本 | | ------------------- | ------------- | | SpringCloud | Hoxton.SR1 | | SpringBoot | 2.2.2.RELEASE | | SpringCloud Alibaba | 2.1.0.RELEASE | ## 工具汇总 | 名称 | 版本 | 备注 | 下载地址 | | ------------- | -------- | -------------------- | ------------------------------------------------------------ | | Java | 8 | | [官网下载](https://www.oracle.com/java/technologies/javase-jdk8-downloads.html) | | Maven | 3.5+ | | [官网下载](http://maven.apache.org/download.cgi) | | MySQL | 5.7+ | | [官网下载](https://dev.mysql.com/downloads/mysql/5.7.html) | | IntelliJ IDEA | 2019.3.3 | | [官网下载](https://www.jetbrains.com/idea/download/) | | zookeeper | 3.4.14 | | [官网下载](https://zookeeper.apache.org/releases.html) | | nacos-server | 1.2.0 | nacos服务端 | [Github下载](https://github.com/alibaba/nacos/releases/tag/1.2.0) | | consul-server | 1.7.2 | consul服务端 | [官网下载](https://www.consul.io/downloads.html) | | zipkin-server | 2.12.9 | zipkin链路监控服务端 | [第三方下载](https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/) | | postman | 7.20.1 | 接口请求测试 | [官网下载](https://www.postman.com/downloads/) | | Seata | 0.9.0 | 分布式事务服务端 | [官网下载](https://seata.io/zh-cn/blog/download.html) | > 项目中使用的数据库文件在项目根目录resouces/SQL/下,阶段代码直接查看提交记录 > > 配套视频:https://www.bilibili.com/video/av93813318 ## 项目结构说明 | 项目名 | 用途 | 所用技术 | | --------------------------------------- | :---------------------------------------------- | ------------------------------------------------------------ | | cloud-api-commons | 公共代码部分(返回结果集、实体等) | \ | | cloud-consumer-consul-order80 | 消费方通过Consul调用服务方 | 注册中心(Consul)、远程调用(RestTemplate) | | cloud-consumer-feign-order80 | 消费方通过feign远程调用服务方 | 服务调用(Openfeign)、注册中心(EurekaClient) | | cloud-consumer-order80 | 消费方通过Eureka调用服务方等示例 | 注册中心(Eureka)、远程调用(RestTemplate)、链路追踪(Sleuth) | | cloud-consumer-zk-order80 | 消费方通过Zk调用服务方 | 注册中心(Zk)、远程调用(RestTemplate) | | cloud-eureka-server7001 | Eureka服务端1 | 注册中心(EurekaServer) | | cloud-eureka-server7002 | Eureka服务端2 | 注册中心(EurekaServer) | | cloud-provider-consul-payment8006 | 服务方通过Consul注册服务 | 注册中心(Consul) | | cloud-provider-payment8001 | 服务方通过Eureka注册服务 | 注册中心(Euraka)、链路追踪(Sleuth) | | cloud-provider-payment8002 | 服务方通过Eureka注册服务 | 注册中心(Euraka) | | cloud-provider-zk-payment8004 | 服务方通过Zk注册服务 | 注册中心(Zk) | | cloud-provider-hystrix-payment8001 | 服务方通过Hystrix服务降级和熔断 | 服务降级和熔断(Hystrix)、服务监控(Actuator) | | cloud-consumer-feign-hystrix-order80 | 消费方消费方通过feign远程调用服务方带降级和服务 | 服务降级和熔断(Hystrix)、服务调用(Openfeign) | | cloud-consumer-hystrix-dashboard9001 | Hystrix监控仪表盘 | 服务监控(Hystrix) | | cloud-gateway-gateway9527 | 网关服务GateWay | 网关(GateWay) | | cloud-config-center-3344 | 配置中心服务端 | 配置中心(Config-Server)、服务总线(BUS) | | cloud-config-client-3355 | 配置中心客户端1 | 配置中心(Config)、服务总线(BUS) | | cloud-config-client-3356 | 配置中心客户端1 | 配置中心(Config)、服务总线(BUS) | | cloud-stream-provider-rabbitmq8801 | 消息驱动发送方 | 消息驱动(Stream) | | cloud-stream-provider-rabbitmq8802 | 消息驱动接受方1 | 消息驱动(Stream) | | cloud-stream-consumer-rabbitmq8803 | 消息驱动接受方2 | 消息驱动(Stream) | | cloudalibaba-config-nacos-client3377 | 配置中心客户端Nacos | 配置中心(Nacos)、服务注册(Nacos) | | cloudalibaba-provider-nacos-payment9001 | 服务方通过Nacos注册服务1 | 服务注册(Nacos) | | cloudalibaba-provider-nacos-payment9002 | 服务方通过Nacos注册服务2 | 服务注册(Nacos) | | cloudalibaba-provider-nacos-payment9003 | 服务方通过Nacos注册服务模拟查询1 | 服务注册(Nacos) | | cloudalibaba-provider-nacos-payment9004 | 服务方通过Nacos注册服务模拟查询2 | 服务注册(Nacos) | | cloudalibaba-sentinel-service8401 | 服务限流和熔断Sentinel示例 | 服务注册(Nacos)、服务限流和熔断(Sentinel) | | cloudalibaba-consumer-nacos-order84 | 消费方通过整合Feign和Sentinel | 服务注册(Nacos)、服务限流和熔断(Sentinel)、服务调用(Openfeign)、负载均衡(Rabbin) | | cloudalibaba-seata-order-service12001 | 分布式事务商品下单微服务 | 分布式事务(Seata)、服务注册(Nacos)、服务调用(Openfeign)、负载均衡(Rabbin) | | cloudalibaba-seata-storage-service12002 | 分布式事务库存微服务 | 分布式事务(Seata)、服务注册(Nacos)、服务调用(Openfeign)、负载均衡(Rabbin) | | cloudalibaba-seata-account-service12003 | 分布式事务账户微服务 | 分布式事务(Seata)、服务注册(Nacos)、服务调用(Openfeign)、负载均衡(Rabbin) | ## 知识点整理 ### 一、服务注册与发现 #### 1、使用Eureka服务端 1)导入依赖 ```xml org.springframework.cloud spring-cloud-starter-netflix-eureka-server org.springframework.boot spring-boot-devtools runtime true ``` 2)application.yml示例 ```yaml server: port: 7001 eureka: instance: hostname: eureka7001.com #Eureka服务端的实例名称 client: #false表示不向注册中心中注册自己 register-with-eureka: false #false表示自己就是注册中心,我的职责就是维护服务实例,并不需要去检索服务 fetch-registry: false service-url: #设置与Eureka Server之间交互的地址,查询服务和注册服务都需要依赖这个地址 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #单机版,指向自己 #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版 server: #关闭Eureka自我保护机制,保证不可用服务被及时剔除 enable-self-preservation: false eviction-interval-timer-in-ms: 2000 ``` 3)启动类添加注解@EnableEurekaServer ```java @SpringBootApplication @EnableEurekaServer public class EurekaMain7001 { public static void main(String[] args) { SpringApplication.run(EurekaMain7001.class, args); } } ``` 4)验证 访问:http://localhost:7001 > 对应项目:cloud-eureka-server7001、cloud-eureka-server7002 #### 2、使用Eureka客户端 1)导入依赖 ```xml <!--引入eureka客户端eureka-client --> org.springframework.cloud spring-cloud-starter-netflix-eureka-client ``` 2)application.yml示例 ```yaml server: port: 8001 spring: application: name: cloud-payment-service eureka: client: #表示是否将自己注册进EurekaServer,默认为true register-with-eureka: true #是否从EurekaServer中抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡 fetchRegistry: true service-url: defaultZone: http://localhost:7001/eureka #单机版 #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版 instance: instance-id: payment8001 #修改Status名称 prefer-ip-address: true #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认30) lease-renewal-interval-in-seconds: 10 #Eureka服务端在收到最后一次心跳后等待的时间上限,单位为秒(默认90),超时剔除服务 lease-expiration-duration-in-seconds: 60 ``` 3)启动类添加注解@EnableDiscoveryClient(推荐)或者@EnableEurekaClient ```java @SpringBootApplication @EnableDiscoveryClient //Discovery服务发现 public class PaymentMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentMain8001.class, args); } } ``` > 对应项目:cloud-provider-payment8001、cloud-provider-payment8002等 #### 3、使用Zookeeper注册服务 1)导入依赖 ```xml org.springframework.cloud spring-cloud-starter-zookeeper-discovery org.apache.zookeeper zookeeper org.apache.zookeeper zookeeper 3.4.14 ``` 2)application.yml示例 ```yaml server: port: 8004 #服务别名——注册到zookeeper注册中心的名称 spring: application: name: cloud-provider-payment cloud: zookeeper: connect-string: 127.0.0.1:2181 ``` 3)启动类添加注解@EnableDiscoveryClient ```java @SpringBootApplication @EnableDiscoveryClient //该注解用于向使用zookeeper作为注册中心时注册服务 public class ZkPaymentMain8004 { public static void main(String[] args) { SpringApplication.run(ZkPaymentMain8004.class, args); } } ``` > 对应项目:cloud-provider-zk-payment8004 #### 4、使用Consul注册服务 1)导入依赖 ```xml org.springframework.cloud spring-cloud-starter-consul-discovery ``` 2)application.yml示例 ```yaml 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} ``` 3)启动类添加注解@EnableDiscoveryClient ```java @SpringBootApplication @EnableDiscoveryClient public class ConsulPaymentMain8006 { public static void main(String[] args) { SpringApplication.run(ConsulPaymentMain8006.class, args); } } ``` > 对应项目:cloud-provider-consul-payment8006 ### 二、负载均衡调用 #### 1、使用Ribbon负载均衡 默认eureka集成了rabbin,配置即可使用,默认是轮训策略。 1)新建规则类MySelfRule.java ```java /** * MySelfRule * * @author lcry * 自定义Ribbon默认轮训规则类 */ @Configuration public class MySelfRule { @Bean public IRule myRule() { return new RandomRule();//定义为随机 } } ``` > IRule实现类有如下: > > com.netflix.loadbalancer.RoundRobinRule:轮询 > > com.netflix.loadbalancer.RandomRule:随机 > > com.netflix.loadbalancer.RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务 > > WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越多大,越容易被选择 > > BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务 > > AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例 > > ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器 2)启动类添加注解`@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)//启用Ribbon` ```java @SpringBootApplication @EnableDiscoveryClient @RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)//启用Ribbon public class OrderMain80 { public static void main(String[] args) { SpringApplication.run(OrderMain80.class, args); } } ``` 若使用restTemplate直接使用轮训规则,可以直接在配置类上@LoadBalanced注解 ```java /** * ApplicationContextConfig * * @author lcry * @date 2020/03/14 12:05 * 配置RestTemplate */ @Configuration public class ApplicationContextConfig { @Bean //相当于Spring中applicationContext.xml中 @LoadBalanced //使用此注解赋予RestTemplate负载均衡的能力,测试自定义算法注释掉 public RestTemplate getRestTemplate() { return new RestTemplate(); } } ``` > 对应项目:cloud-consumer-order80 #### 2、使用OpenFeign 1)导入依赖 ```xml org.springframework.cloud spring-cloud-starter-openfeign ``` 2)application.yml示例 ```yaml server: port: 80 eureka: client: register-with-eureka: false service-url: defaultZone: http://localhost:7001/eureka #单机版 #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版 #OpenFeign的超时控制配置:默认只等待1秒,超时则报错 #设置Feign客户端超时时间(OpenFeign默认支持Ribbon) ribbon: #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间 ReadTimeout: 5000 #指的是建立连接后从服务器读取到可用资源所用的时间 connectTimeout: 5000 #开启Feign的日志客户端 logging: level: #Feign日志以什么级别监听哪个接口 com.lcry.springcloud.service.PaymentFeignService: debug ``` 3)启动类添加注解@EnableFeignClients ```java @SpringBootApplication //启用Feign @EnableFeignClients public class FeignOrderMain80 { public static void main(String[] args) { SpringApplication.run(FeignOrderMain80.class, args); } } ``` 4)配置日志打印信息 FeignConfig.java ```java /** * FeignConfig * * @author lcry * @date 2020/03/14 22:04 * Feign日志控制 */ @Configuration public class FeignConfig { @Bean Logger.Level feignLoggerLevel() { /** * 日志级别: * NONE :不记录任何日志(默认) * BASIC:仅记录请求方法、URL、响应状态代码以及执行时间 * HEADERS:记录BASIC级别的基础上,记录请求和响应的header * FULL:记录请求和响应的header,body和元数据 */ return Logger.Level.FULL; } } ``` 5)远程调用服务 ```java /** * PaymentFeignService * * @author lcry */ @Component @FeignClient(value = "CLOUD-PAYMENT-SERVICE") //需要调用的目标微服务应用名称 public interface PaymentFeignService { //Feign接口中的方法要与目标服务中的Controller中的方法完全一致,可以直接copy @GetMapping(value = "/payment/get/{id}") public CommonResult getPaymentById(@PathVariable("id") Long id); @GetMapping(value = "/payment/feign/timeout") public String paymentFeignTimeout(); } ``` > 对应项目:cloud-consumer-feign-order80 ### 三、服务熔断与降级 #### 1、使用HyStrix熔断和降级 1)导入依赖 ```xml org.springframework.cloud spring-cloud-starter-netflix-hystrix ``` 2)application.yml示例 ```yaml server: port: 8001 spring: application: name: cloud-provider-hystrix-payment eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://127.0.0.1:7001/eureka #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka ``` 3)启动类添加注解@EnableHystrix ```java @SpringBootApplication @EnableEurekaClient @EnableHystrix //服务端开启Hystrix或者使用@EnableCircuitBreaker public class HystrixPaymentMain8001 { public static void main(String[] args) { SpringApplication.run(HystrixPaymentMain8001.class, args); } } ``` 4)服务降级示例代码,@HystrixCommand注解实现 ```java /** * 模拟超时3s * * @param id * @return */ @Override @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler", commandProperties = { //超时5s或者异常直接降级 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000") }) public String paymentInfo_TimeOut(Integer id) { //int age = 10/0; int timenum = 3; try { TimeUnit.SECONDS.sleep(timenum); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池: " + Thread.currentThread().getName() + " id: " + id + "\t" + "O(∩_∩)O哈哈~" + " 耗时(秒): " + timenum; } public String paymentInfo_TimeOutHandler(Integer id) { return "线程池: " + Thread.currentThread().getName() + " 8001系统繁忙或者运行报错,请稍后再试,id: " + id + "\t" + "o(╥﹏╥)o"; } ``` 5)服务熔断示例代码 ```java /** * 以下内容属于服务熔断 * * @param id * @return */ @Override @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = { @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),// 是否开启断路器 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),// 请求次数 @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 时间窗口期 @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),// 失败率达到多少后跳闸 }) public String paymentCircuitBreaker(@PathVariable("id") Integer id) { if (id < 0) { throw new RuntimeException("id 不能负数"); } String serialNumber = IdUtil.simpleUUID(); return Thread.currentThread().getName() + "\t" + "调用成功,流水号: " + serialNumber; } public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) { return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " + id; } ``` > 对应项目:cloud-provider-hystrix-payment8001、cloud-consumer-feign-order80 #### 2、使用HystrixDashboard监控仪表盘 1)导入依赖 ```xml org.springframework.cloud spring-cloud-starter-netflix-hystrix-dashboard org.springframework.boot spring-boot-starter-actuator ``` 2)application.yml示例 ```yaml server: port: 9001 ``` 3)启动类添加注解@EnableHystrixDashboard ```java /** * HystrixDashboardMain9001 * * @author lcry */ @SpringBootApplication @EnableHystrixDashboard //启用注解 public class HystrixDashboardMain9001 { public static void main(String[] args) { SpringApplication.run(HystrixDashboardMain9001.class, args); } } ``` 4)被监控端配置 - 引入依赖 ```xml org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator ``` - 启动类注入 ```java /** * 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑 * ServletRegistrationBean因为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; } ``` 5)验证 访问hystrix服务监控平台http://localhost:9001,被监控填写:http:// ip:端口/hystrix.stream > 对应项目:cloud-consumer-hystrix-dashboard9001、cloud-provider-hystrix-payment8001 ### 四、路由网关 #### 1、使用Zuul 忽略,直接使用Gateway新一代网关 #### 2、使用SpringCloud Gateway 1)导入依赖 ```xml org.springframework.cloud spring-cloud-starter-gateway org.springframework.cloud spring-cloud-starter-netflix-eureka-client com.lcry cloud-api-commons ${project.version} ``` 2)通过application.yml配置路由网关示例(推荐) ```java server: port: 9527 spring: application: name: cloud-gateway cloud: gateway: discovery: locator: enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由 routes: - id: payment_route #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名 #uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-payment-service #匹配后提供服务的路由地址 predicates: - Path=/payment/get/** # 断言,路径相匹配的进行路由 - id: payment_route2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名 #uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-payment-service #匹配后提供服务的路由地址 predicates: - Path=/payment/lb/** # 断言,路径相匹配的进行路由 #- After=2020-02-21T15:51:37.485+08:00[Asia/Shanghai] #- Cookie=username,zzyy #- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式 eureka: instance: hostname: cloud-gateway-service client: #服务提供者provider注册进eureka服务列表内 service-url: register-with-eureka: true fetch-registry: true defaultZone: http://localhost:7001/eureka ``` 3)通过编码方式实现路由网关 ```java /** * 通过编码方式实现路由 */ @Configuration public class GateWayConfig { @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) { RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes(); routes.route("path_route_baidunews", r -> r.path("/guonei") .uri("http://news.baidu.com/guonei")).build(); return routes.build(); } } ``` 4)自定义过滤逻辑示例 MyLogGateWayFilter.java ```java /** * 自定义过滤器示例-一般用于鉴权或者全局日志 */ @Component @Slf4j public class MyLogGateWayFilter implements GlobalFilter, Ordered { @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("***********come in MyLogGateWayFilter: " + new Date()); //请求参数必须带username,并且不能为空 String uname = exchange.getRequest().getQueryParams().getFirst("username"); if (StrUtil.isEmpty(uname)) { log.info("*******用户名为空,非法用户,o(╥﹏╥)o"); exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE); return exchange.getResponse().setComplete(); } return chain.filter(exchange); //放行给下一条过滤链 } @Override public int getOrder() { //执行顺序,越小越先执行 return 0; } } ``` > 对应项目:cloud-gateway-gateway9527 ### 五、分布式配置中心 #### 1、使用SpringCloud Config服务端 1)导入依赖 ```xml org.springframework.cloud spring-cloud-config-server ``` 2)application.yml示例 ```yaml server: port: 3344 spring: application: name: cloud-config-center #注册进Eureka服务器的微服务名 cloud: config: server: git: uri: git@gitee.com:lcry/springcloud-config.git #git仓库地址 ####搜索目录 search-paths: - springcloud-config ####读取分支 label: master #服务注册到eureka地址 eureka: client: service-url: defaultZone: http://localhost:7001/eureka ``` 3)启动类添加注解@EnableConfigServer ```java @SpringBootApplication @EnableConfigServer //启用注册中心服务端 public class ConfigCenterMain3344 { public static void main(String[] args) { SpringApplication.run(ConfigCenterMain3344.class, args); } } ``` > 对应项目:cloud-config-center-3344 #### 2、使用SpringCloud Config客户端 1)导入依赖 ```xml org.springframework.cloud spring-cloud-starter-config ``` 2)application.yml示例 ```yaml server: port: 3355 spring: application: name: config-client cloud: #Config客户端配置 config: label: master #分支名称 name: config #配置文件名称 profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml uri: http://localhost:3344 #配置中心地址server: port: 3355 spring: application: name: config-client cloud: #Config客户端配置 config: label: master #分支名称 name: config #配置文件名称 profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml uri: http://localhost:3344 #配置中心地址 ``` > 对应项目:cloud-config-client-3355、cloud-config-client-3356 ### 六、消息总线 #### 1、使用SpringCloud Bus刷新配置 1)导入依赖 ```xml org.springframework.cloud spring-cloud-starter-bus-amqp ``` 2)SpringCloud Config服务端application.yml示例 ```yaml #rabbitmq相关配置 rabbitmq: host: 192.168.179.150 port: 5672 username: admin password: admin #全局刷新POST请求 http://注册中心地址/actuator/bus-refresh #定点通知:(只通知部分)http://注册中心地址/actuator/refresh/{服务名称:端口} #如:http://localhost:3344/actuator/bus-refresh/config-client:3355 ##rabbitmq相关配置,暴露bus刷新配置的端点 management: endpoints: #暴露bus刷新配置的端点 web: exposure: include: 'bus-refresh' ``` 3)SpringCloud Config客户端application.yml示例 ```yaml #rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口 rabbitmq: host: 192.168.179.150 port: 5672 username: admin password: admin # 手动刷新地址POST http://客户端访问地址/actuator/refresh # curl -X POST "http://localhost:3355/actuator/refresh" # 暴露监控端点 management: endpoints: web: exposure: include: "*" ``` 4)刷新配置注解@RefreshScope ```java @RestController @RefreshScope //刷新配置生效 public class ConfigClientController { @Value("${server.port}") private String serverport; @Value("${config.info}") private String configInfo; // 直接获取配置文件 @GetMapping("/configInfo") public String getConfigInfo() { return configInfo + "服务端口:"+serverport; } } ``` > 对应项目:cloud-config-center-3344、cloud-config-center-3355、cloud-config-center-3356 ### 七、消息驱动 #### 1、使用Stream发送方 1)导入依赖 ```xml org.springframework.cloud spring-cloud-starter-stream-rabbit ``` 2)application.yml示例 ```yaml server: port: 8801 spring: application: name: cloud-stream-provider cloud: stream: binders: # 在此处配置要绑定的rabbitmq的服务信息; defaultRabbit: # 表示定义的名称,用于于binding整合 type: rabbit # 消息组件类型 environment: # 设置rabbitmq的相关的环境配置 spring: rabbitmq: host: 192.168.179.150 port: 5672 username: admin password: admin 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: cloud-stream-provider8801 # 在信息列表时显示主机名称 prefer-ip-address: true # 访问的路径变为IP地址 ``` 3)定义推送管道 ```java /** * //定义消息的推送管道 */ @EnableBinding(Source.class) //定义消息的推送管道,生产者固定写法 @Slf4j public class MessageProviderImpl implements IMessageProvider { @Resource private MessageChannel output; // 消息发送管道 @Override public String send() { String serial = UUID.randomUUID().toString(); output.send(MessageBuilder.withPayload(serial).build()); log.info(String.format("生成的流水号:{%s}", serial)); return serial; } } ``` > 对应项目:cloud-stream-provider-rabbitmq8801 #### 2、使用Stream接受方 1)导入依赖 ```xml org.springframework.cloud spring-cloud-starter-stream-rabbit ``` 2)application.yml示例 ```yaml server: port: 8802 spring: application: name: cloud-stream-consumer cloud: stream: binders: # 在此处配置要绑定的rabbitmq的服务信息; defaultRabbit: # 表示定义的名称,用于于binding整合 type: rabbit # 消息组件类型 environment: # 设置rabbitmq的相关的环境配置 spring: rabbitmq: host: 192.168.179.150 port: 5672 username: admin password: admin bindings: # 服务的整合处理 input: # 这个名字是一个通道的名称 destination: studyExchange # 表示要使用的Exchange名称定义 content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain” binder: defaultRabbit # 设置要绑定的消息服务的具体设置 group: receiveclientA #分组避免重复消费和持久化问题 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: cloud-stream-consumer8802 # 在信息列表时显示主机名称 prefer-ip-address: true # 访问的路径变为IP地址 ``` 3)接收消息逻辑代码 ```java @Component @EnableBinding(Sink.class) public class ReceiveMessageListenerController { @Value("${server.port}") private String serverPort; @StreamListener(Sink.INPUT) //固定的写法,对应yml配置的通道名称 public void input(Message message) { System.out.println("消费者1号,----->接受到的消息: " + message.getPayload() + "\t port: " + serverPort); } } ``` > 对应项目:cloud-stream-consumer-rabbitmq8802、cloud-stream-consumer-rabbitmq8802 ### 八、分布式链路追踪 #### 1、使用sleuth客户端 1)导入依赖 ```xml org.springframework.cloud spring-cloud-starter-zipkin org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator ``` 2)application.yml示例 ```yaml spring: application: name: cloud-payment-service #链路监控配置,配置链路监控服务端地址 zipkin: base-url: http://localhost:9411 sleuth: sampler: #采样率值介于 0 到 1 之间,1 则表示全部采集 probability: 1 ``` > 对应项目:cloud-provider-payment8001、cloud-consumer-order80 ### 九、SpringCloud alibaba #### 1、使用Nacos服务注册 1)导入依赖 ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery ``` 2)application.yml示例 ```yaml server: port: 9001 spring: application: name: nacos-payment-provider cloud: nacos: discovery: server-addr: localhost:8848 #配置Nacos地址,注册服务到Nacos management: endpoints: web: exposure: include: '*' ``` 3)启动类添加注解@EnableDiscoveryClient ```java /** * NacosPaymentMain9001 * * @author lcry * @date 2020/03/16 18:03 */ @EnableDiscoveryClient @SpringBootApplication public class NacosPaymentMain9001 { public static void main(String[] args) { SpringApplication.run(NacosPaymentMain9001.class,args); } } ``` > 对应项目:cloudalibaba-provider-nacos-payment9001、cloudalibaba-provider-nacos-payment9002 #### 2、使用Nacos注册中心 1)导入依赖 ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator ``` 2)application.yml示例 ```yaml #激活配置文件选项 spring: profiles: active: dev # 表示开发环境 #active: test # 表示测试环境 #active: prod # 表示生产环境 # nacos配置 server: port: 3377 spring: application: name: nacos-config-client cloud: nacos: discovery: server-addr: localhost:8848 #Nacos服务注册中心地址 config: server-addr: localhost:8848 #Nacos作为配置中心地址 namespace: Test-NameSpace-HA1 #命名空间ID,nacos1.2可以自定义 group: DEV_GROUP #分组ID file-extension: yaml #指定yaml格式的配置 # #命名规范: # ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension} # nacos-config-client-dev.yaml ``` 3)启动类添加注解@EnableDiscoveryClient ```java /** * ConfigNacosMain3344 * * @author lcry * @date 2020/03/16 17:49 */ @SpringBootApplication @EnableDiscoveryClient public class ConfigNacosMain3377 { public static void main(String[] args) { SpringApplication.run(ConfigNacosMain3377.class, args); } } ``` 4)业务类添加注解@RefreshScope ```java /** * NacosClientController * * @author lcry */ @RestController @RefreshScope //支持Nacos的动态刷新功能。 public class NacosClientController { @Value("${config.info}") //通过nacos配置中心获取文件 private String configInfo; @GetMapping("/config/info") public String getConfigInfo() { return configInfo; } } ``` > 对应项目:cloudalibaba-config-nacos-client3377 #### 3、使用Sentiel限流 1)导入依赖 ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.csp sentinel-datasource-nacos com.alibaba.cloud spring-cloud-starter-alibaba-sentinel org.springframework.cloud spring-cloud-starter-openfeign ``` 2)application.yml配置示例 ```yaml #测试端口 server: port: 8401 spring: application: name: cloudalibaba-sentinel-service cloud: nacos: discovery: server-addr: localhost:8848 #Nacos服务注册中心地址 sentinel: transport: dashboard: localhost:8080 #配置Sentinel dashboard地址 port: 8719 #随机找一个地址,如果冲突就往后+1 #持久化配置 # datasource: # ds1: # nacos: # server-addr: localhost:8848 # dataId: cloudalibaba-sentinel-service # groupId: DEFAULT_GROUP # data-type: json # rule-type: flow management: endpoints: web: exposure: include: '*' feign: sentinel: enabled: true # 激活Sentinel对Feign的支持 ``` 3)接口先请求一次,然后在sentinel控制台进行相应限流模式操作 4)自定义限流异常页面等,使用注解@SentinelResource ```java @GetMapping("/testHotKey") @SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey") public String testHotKey(@RequestParam(value = "p1", required = false) String p1, @RequestParam(value = "p2", required = false) String p2) { //int age = 10/0; return "------testHotKey"; } public String deal_testHotKey(String p1, String p2, BlockException exception) { //sentinel系统默认的提示:Blocked by Sentinel (flow limiting) return "------deal_testHotKey,o(╥﹏╥)o"; } @GetMapping("/rateLimit/byResource") @SentinelResource(value = "byResource", blockHandler = "handleException") public CommonResult byResource() { return new CommonResult(200, "按资源名称限流测试OK", new Payment(2020L, "serial001")); } public CommonResult handleException(BlockException exception) { return new CommonResult(444, exception.getClass().getCanonicalName() + "\t 服务不可用"); } @GetMapping("/rateLimit/byUrl") @SentinelResource(value = "byUrl") public CommonResult byUrl() { return new CommonResult(200, "按url限流测试OK", new Payment(2020L, "serial002")); } @GetMapping("/rateLimit/customerBlockHandler") @SentinelResource(value = "customerBlockHandler", //资源名称 blockHandlerClass = CustomerBlockHandler.class, //异常自定义 blockHandler = "handlerException1") //调用自定义类某个方法 public CommonResult customerBlockHandler() { return new CommonResult(200, "按客戶自定义", new Payment(2020L, "serial003")); } ``` ```java /** * @author lcry * @date 2020/03/17 14:51 * 用户自定义处理异常类 */ public class CustomerBlockHandler { public static CommonResult handlerException1(BlockException exception) { return new CommonResult(4444, "按客戶自定义,global handlerException----1"); } public static CommonResult handlerException2(BlockException exception) { return new CommonResult(4444, "按客戶自定义,global handlerException----2"); } } ``` > 对应项目:cloudalibaba-sentinel-service8401 #### 4、使用Sentinel服务熔断 1)导入依赖 ```xml org.springframework.cloud spring-cloud-starter-openfeign com.alibaba.csp sentinel-datasource-nacos com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-sentinel ``` 2)启动类添加启用Openfeign注解@EnableFeignClients ```java @EnableDiscoveryClient @SpringBootApplication @EnableFeignClients //整合feign public class NacosOrderMain84 { public static void main(String[] args) { SpringApplication.run(NacosOrderMain84.class, args); } } ``` 3)熔断和降级示例业务代码 ```java /** * CircleBreakerController * * @author lcry * 使用示例: */ @RestController @Slf4j public class CircleBreakerController { public static final String SERVICE_URL = "http://nacos-payment-provider"; // 整个rabbin - restTemplate @Resource private RestTemplate restTemplate; // 整合sentinel @RequestMapping("/consumer/fallback/{id}") //@SentinelResource(value = "fallback") //没有配置 //@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常 //@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规 // @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler", //分开自定义 // exceptionsToIgnore = {IllegalArgumentException.class}//排除异常不限流 // ) @SentinelResource(value = "fallback", //,fallback = "handlerFallback", blockHandlerClass = CustomerBlockHandler.class, //异常自定义 blockHandler = "handlerException1" //分开自定义 ) public CommonResult fallback(@PathVariable Long id) { CommonResult result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id); if (id == 4) { throw new IllegalArgumentException("IllegalArgumentException,非法参数异常...."); } else if (result.getData() == null) { throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常"); } return result; } //本例是fallback public CommonResult handlerFallback(@PathVariable Long id, Throwable e) { Payment payment = new Payment(id, "null"); return new CommonResult<>(444, "兜底异常handlerFallback,exception内容 " + e.getMessage(), payment); } //本例是blockHandler public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) { Payment payment = new Payment(id, "null"); return new CommonResult<>(445, "blockHandler-sentinel限流,无此流水: blockException " + blockException.getMessage(), payment); } //OpenFeign调用 @Resource private PaymentService paymentService; @GetMapping(value = "/consumer/paymentSQL/{id}") public CommonResult paymentSQL(@PathVariable("id") Long id) { return paymentService.paymentSQL(id); } /* 测试自定义限流 */ @GetMapping(value = "/consumer/paymentSQL") @SentinelResource(value = "customerBlockHandler", //资源名称 blockHandlerClass = CustomerBlockHandler.class, //异常自定义 blockHandler = "handlerException1") //调用自定义类某个方法 public CommonResult paymentSQL2() { return paymentService.paymentSQL(1L); } } ``` > 对应项目:cloudalibaba-consumer-nacos-order84 #### 5、使用分布式事务Seata 1)引入依赖 ```xml com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-seata seata-all io.seata io.seata seata-all 0.9.0 org.springframework.cloud spring-cloud-starter-openfeign ``` 2)application.yml配置示例 ```yaml #微服务端口 server: port: 12001 spring: application: name: seata-order-service cloud: alibaba: seata: #自定义事务组名称需要与seata-server中的对应 tx-service-group: fsp_tx_group nacos: discovery: server-addr: localhost:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: lcry #禁用feign feign: hystrix: enabled: false #OpenFeign的超时控制配置:默认只等待1秒,超时则报错 #设置Feign客户端超时时间(OpenFeign默认支持Ribbon) ribbon: #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间 ReadTimeout: 5000 #指的是建立连接后从服务器读取到可用资源所用的时间 connectTimeout: 5000 #日志级别 logging: level: io: seata: info #mybatis映射 mybatis: mapperLocations: classpath:mapper/*.xml ``` 3)配置file.conf和register.conf,可以直接从Seata上直接拷贝到resources目录下做部分改动 4)业务代码添加注解@GlobalTransactional开启全局事务 ```java /** * 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态 * 简单说:下订单->扣库存->减余额->改状态 */ @Override @GlobalTransactional(name = "tx-create-order", rollbackFor = Exception.class) public void create(Order order) { log.info("----->开始新建订单"); //1 新建订单 orderDao.create(order); //2 扣减库存 log.info("----->订单微服务开始调用库存,做扣减Count"); storageService.decrease(order.getProductId(), order.getCount()); log.info("----->订单微服务开始调用库存,做扣减end"); //3 扣减账户 log.info("----->订单微服务开始调用账户,做扣减Money"); accountService.decrease(order.getUserId(), order.getMoney()); log.info("----->订单微服务开始调用账户,做扣减end"); //4 修改订单状态,从零到1,1代表已经完成 log.info("----->修改订单状态开始"); orderDao.update(order.getUserId(), 0); log.info("----->修改订单状态结束"); log.info("----->下订单结束了,O(∩_∩)O哈哈~"); } ``` > 对应项目:cloudalibaba-seata-order-service12001、cloudalibaba-seata-storage-service12002、cloudalibaba-seata-account-service12003 **完结~**