概念
Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
新版架构图
dependencyManagement与dependencies区别
父工程用来管理版本,子工程不继承依赖,继承版本,子工程导入不需要写版本号
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.12</junit.version> <log4j.version>1.2.17</log4j.version> <lombok.version>1.18.0</lombok.version> <mysql.version>5.1.47</mysql.version> <druid.version>1.1.16</druid.version> <mybatis.spring.boot.version>1.3.2</mybatis.spring.boot.version> </properties> <!-- 子模块继承之后,提供作用:锁定版本+子模块不用写groupId和version --> <dependencyManagement> <dependencies> <!-- springboot 2.2.2 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.2.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!-- spring cloud Hoxton.SR1 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR1</version> <type>pom</type> <scope>import</scope> </dependency> <!-- spring cloud alibaba 2.1.0.RELEASE --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <optional>true</optional> </dependency> </dependencies> </dependencyManagement>
RestTemplate
package com.wl.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class ApplicationContextConfig { @Bean public RestTemplate getRRestTemplate(){ return new RestTemplate() ; } }
package com.wl.controller; import com.wl.entities.CommonResult; import com.wl.entities.Payment; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; @RestController @RequestMapping("/order") @Slf4j public class OrderController { public static final String PAYMENT_URL="http://localhost:8001"; @Resource private RestTemplate restTemplate; @PostMapping(value = "/create") public CommonResult create(Payment payment){ return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class); } @GetMapping(value = "/get/{id}") public CommonResult get(@PathVariable("id") Long id){ return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id, CommonResult.class); } }
Eureka注册中心
- 服务端
server: port: 7001 eureka: instance: hostname: localhost client: fetch-registry: false #不注册自己 register-with-eureka: false # 不检索自己 service-url: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
主启动类加:@EnableEurekaServer
- 客户端
eureka: client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:7001/eureka
![]()
对应
注册中心集群搭建
步骤:
- 找到C:\Windows\System32\drivers\etc路径下的hosts文件
- 修改注册中心yml
- 修改服务的yml
服务集群搭建
步骤:
- 复制一个服务工程,只修改端口
- 修改调用者的URL
- 使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
服务名称修改与显示端口
修改前
修改内容
修改后
服务发现Discovery(对于注册eureka里面的微服务,可以通过服务发现来获得该服务的信息)
启动类加:@EnableDiscoveryClient
效果:
eureka自我保护(属于CAP里面的AP分支)
出产默认自我保护机制是开启的
关闭:
CAP理论
Ribbon(负载均衡)
Eureka已经集成了ribbon依赖
getForObject方法/getForEntity方法
Ribbon核心组件IRule
- com.netflix.loadbalancer.RoundRobinRule(轮询,默认)
- com.netflix.loadbalancer.RandomRule(随机)
- com.netflix.loadbalancer.RetryRule(先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务)
- WeightedResponseTimeRule(对RoundRobinRule的扩展,响应速度越快的实例选择权重越多大,越容易被选择)
- BestAvailableRule(会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务)
- AvailabilityFilteringRule(先过滤掉故障实例,再选择并发较小的实例)
- ZoneAvoidanceRule(默认规则,复合判断server所在区域的性能和server的可用性选择服务器)
替换负载均衡规则
- 新建package(不能与主启动类同级)
- 新建类
import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RoundRobinRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MySelfRule { @Bean public IRule myRule() { // 定义为随机 return new RoundRobinRule(); } }
- 主启动类添加@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
轮询的原理
OpenFeign(远程调用)
- 加依赖
<!--openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
- 创建接口(注意注解)
package com.wl.service; import com.wl.entities.CommonResult; 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; @FeignClient(value = "cloud-payment-service") @Component public interface PaymentService { @GetMapping(value = "/payment/get/{id}") public CommonResult get(@PathVariable("id") Long id) ; }
- 主启动类加注解:@EnableFeignClients
- 调用
package com.wl.controller; import com.wl.entities.CommonResult; import com.wl.entities.Payment; import com.wl.service.PaymentService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; @RestController @RequestMapping("/order") @Slf4j public class OrderController { public static final String PAYMENT_URL = "http://localhost:8001"; @Resource private RestTemplate restTemplate; @Autowired private PaymentService paymentService; @PostMapping(value = "/create") public CommonResult create(Payment payment) { return restTemplate.postForObject(PAYMENT_URL + "/payment/create", payment, CommonResult.class); } @GetMapping(value = "/get/{id}") public CommonResult get(@PathVariable("id") Long id) { return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class); } @GetMapping(value = "/get1/{id}") public CommonResult get1(@PathVariable("id") Long id) { return paymentService.get(id); } }
OpenFeign超时控制(OpenFeign默认等待1秒钟,超过后报错)
server: port: 80 eureka: client: register-with-eureka: false fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 设置feign客户端超时时间(OpenFeign默认支持ribbon) ribbon: # 指的是建立连接所用的时间,适用于网络状态正常的情况下,两端连接所用的时间 ReadTimeout: 5000 # 指的是建立连接后从服务器读取到可用资源所用的时间 ConnectTimeout: 5000
OpenFeign日志打印功能
- 配置日志bean及yml
import feign.Logger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FeignConfig { /** * feignClient配置日志级别 * * @return */ @Bean public Logger.Level feignLoggerLevel() { // 请求和响应的头信息,请求和响应的正文及元数据 return Logger.Level.FULL; } }
server: port: 80 eureka: client: register-with-eureka: false fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 设置feign客户端超时时间(OpenFeign默认支持ribbon) ribbon: # 指的是建立连接所用的时间,适用于网络状态正常的情况下,两端连接所用的时间 ReadTimeout: 5000 # 指的是建立连接后从服务器读取到可用资源所用的时间 ConnectTimeout: 5000 logging: level: # feign日志以什么级别监控哪个接口 com.wl.service.PaymentService: debug
- 后台日志查看
Hystrix熔断器
<!--hystrix--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
服务降级(设置自身调用超时时间的峰值,峰值内可以正常运行, 超过了需要有兜底的方法处理,做服务降级fallback)
方法1:自己业务类(service)
@HystrixCommand(fallbackMethod = "payment_TimeOutHandler", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")//超时3秒异常
主启动类:@EnableCircuitBreaker
方法2:远程调用类(controller)
@GetMapping("/consumer/payment/hystrix/timeout/{id}") @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500") }) public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { //int age = 10/0; return paymentHystrixService.paymentInfo_TimeOut(id); } public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) { return "我是消费者80,对方支付系统繁忙请10秒种后再试或者自己运行出错请检查自己,o(╥﹏╥)o"; }
配置yml
server: port: 80 eureka: client: register-with-eureka: false fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka feign: hystrix: enabled: true
主启动类:@EnableHystrix
方法3:统一调用失败方法
import com.atguigu.springcloud.service.PaymentHystrixService; import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; 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 @DefaultProperties(defaultFallback = "payment_Global_FallbackMethod") public class OrderHystrixController { @Resource private PaymentHystrixService paymentHystrixService; @GetMapping("/consumer/payment/hystrix/ok/{id}") public String paymentInfo_OK(@PathVariable("id") Integer id) { return paymentHystrixService.paymentInfo_OK(id); } @GetMapping("/consumer/payment/hystrix/timeout/{id}") /*@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500") })*/ @HystrixCommand public String paymentInfo_TimeOut(@PathVariable("id") Integer id) { //int age = 10/0; return paymentHystrixService.paymentInfo_TimeOut(id); }
方法4:实现远程调用接口
package com.atguigu.springcloud.service; import com.atguigu.springcloud.service.fallback.PaymentFallbackHystrixService; 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-PROVIDER-PAYMENT",fallback = PaymentFallbackHystrixService.class) public interface PaymentHystrixService { @GetMapping("payment/info/ok/{id}") String paymentInfo_OK(@PathVariable("id") Integer id); @GetMapping("payment/info/error/{id}") String paymentInfo_ERROR(@PathVariable("id") Integer id); }
package com.atguigu.springcloud.service.fallback; import com.atguigu.springcloud.service.PaymentHystrixService; import org.springframework.stereotype.Component; @Component //将fallback接口添加到容器中 public class PaymentFallbackHystrixService implements PaymentHystrixService { @Override public String paymentInfo_OK(Integer id) { return "-------PaymentFallbackService fall back-paymentInfo_OK,o(╥﹏╥)o"; } @Override public String paymentInfo_ERROR(Integer id) { return "-------PaymentFallbackService fall back-paymentInfo_TimeOut,o(╥﹏╥)o"; } }
server: port: 80 eureka: client: register-with-eureka: false fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka feign: hystrix: enabled: true
服务熔断
实现
服务限流
服务监控hystrixDashboard
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
修改application.yml,暴露数据监控流
management: endpoints: web: exposure: include: hystrix.stream #访问/actuator/hystrix.stream能看到不断更新的监控流
可视化
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
@SpringBootApplication @EnableHystrixDashboard public class HystrixDashboardMain9001 { public static void main(String[] args) { SpringApplication.run(HystrixDashboardMain9001.class); } /** * 此配置是为了服务监控而配置,与服务容错本身无观,springCloud 升级之后的坑 * ServletRegistrationBean因为springboot的默认路径不是/hystrix.stream * 只要在自己的项目中配置上下面的servlet即可 * @return */ @Bean public ServletRegistrationBean getServlet(){ HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet(); ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(streamServlet); registrationBean.setLoadOnStartup(1); registrationBean.addUrlMappings("/hystrix.stream"); registrationBean.setName("HystrixMetricsStreamServlet"); return registrationBean; } }
Gateway新一代网关
三大核心概念
- Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如断言为true则匹配该路由
- Predicate(断言):开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
- Filter(过滤):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改.
Gateway网关路由有两种配置方式:
方式1:
server: port: 9527 spring: application: name: cloud-gateway-service cloud: gateway: routes: - id: payment_routh # uri: http://localhost:8001 uri: lb://cloud-payment-service predicates: - Path=/payment/get/** - id: payment_routh2 # uri: http://localhost:8001 uri: lb://cloud-payment-service predicates: - Path=/payment/lb/** discovery: locator: enabled: true #开启从注册中心动态生成路由的功能,用微服务名进行路由 eureka: instance: hostname: cloud_gateway_service client: service-url: defaultZone: http://www.eureka7001.com:7001/eureka/ register-with-eureka: true fetch-registry: true
方式2:
import org.springframework.cloud.gateway.route.Route; 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 { //访问localhost:9527/guonei 就是访问https://www.baidu.com @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { RouteLocatorBuilder.Builder routes = builder.routes(); routes.route("path_route",r->r.path("/guonei").uri("https://www.baidu.com")).build(); return routes.build(); } }
通过服务名实现动态(需要注意的是uri的协议lb,表示启用Gateway的负载均衡功能.)
网关配置的步骤:
- 引入依赖
- 配置yml(参考上面配置方式)
- 访问
常用的Route Predicate
- After Route Predicate
- Before Route Predicate
- Between Route Predicate
- Cookie Route Predicate
- Header Route Predicate
- Host Route Predicate
- Method Route Predicate
- Path Route Predicate
- Query Route Predicate
Filter的使用
自定义全局GlobalFilter
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.http.server.reactive.ServerHttpRequest; 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 global filter: {}", new Date()); ServerHttpRequest request = exchange.getRequest(); String uname = request.getQueryParams().getFirst("uname"); if (uname == null) { log.info("用户名为null,非法用户"); exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE); return exchange.getResponse().setComplete(); } // 放行 return chain.filter(exchange); } /** * 过滤器加载的顺序 越小,优先级别越高 * * @return */ @Override public int getOrder() { return 0; } }
SpringCloud config分布式配置中心
Springcloud config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供一个中心化的外部配置。
Config服务端配置与测试
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>
server: port: 3344 eureka: client: service-url: defaultZone: http://www.eureka7001.com:7001/eureka/ instance: ip-address: 127.0.0.1 prefer-ip-address: true lease-renewal-interval-in-seconds: 1 lease-expiration-duration-in-seconds: 2 spring: application: name: cloud-config-center cloud: config: server: git: uri: https://gitee.com/cunjinFS/springcloud-config.git search-paths: - springcloud-config username: password: label: master rabbitmq: host: localhost port: 5672 username: guest password: guest #rabbitmq相关配置,暴露bus刷新配置的端点 management: endpoints: #暴露bus刷新配置的端点 web: exposure: include: 'bus-refresh'
]
Config客户端配置与测试
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
server: port: 3355 spring: application: name: config-client cloud: config: label: master #分支名称 name: config #配置文件名称 profile: dev #读取后缀名称 上述三个综合http://localhost:3344/master/config-dev.yml uri: http://localhost:3344 #配置中心的地址 eureka: client: service-url: defaultZone: http://localhost:7001/eureka
Config客户端只动态刷新
修改YML,暴露监控接口
management: endpoints: web: exposure: include: "*"
@RefreshScope业务类Controller修改
SpringCloud Bus消息总线
SpringCloud Bus配合Springcloud Config使用可以实现配置的动态刷新
服务端
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
server: port: 3344 eureka: client: service-url: defaultZone: http://www.eureka7001.com:7001/eureka/ instance: ip-address: 127.0.0.1 prefer-ip-address: true lease-renewal-interval-in-seconds: 1 lease-expiration-duration-in-seconds: 2 spring: application: name: cloud-config-center cloud: config: server: git: uri: https://gitee.com/cunjinFS/springcloud-config.git search-paths: - springcloud-config username: password: label: master rabbitmq: host: localhost port: 5672 username: guest password: guest management: endpoints: web: exposure: include: 'bus-refresh'
客户端
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
server: port: 3355 spring: application: name: config-client cloud: config: label: master #分支名称 name: config #配置文件名称 profile: dev #读取后缀名称 上述三个综合http://localhost:3344/master/config-dev.yml uri: http://localhost:3344 #配置中心的地址 rabbitmq: host: localhost port: 5672 username: guest password: guest eureka: client: service-url: defaultZone: http://www.eureka7001.com:7001/eureka/ management: endpoints: web: exposure: include: "*"
发送POST请求 curl -X POST "http://localhost:3344/actuator/bus-refresh"
部分通知 curl -X POST "http://localhost:3344actuator/bus-refresh/config-clent:3355"
SpringCloud Stream消息驱动
是什么
屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
编码API和常用注解
存在的问题
- 重复消费问题(分组解决,默认不同组;同一组会产生竞争关系,只会消费一次)
- 消息持久化问题(分组)