ribbon可以实现服务的调用,为何又出现openFeign 因为以前是ribbon+RestTemplete调用。feign更加容易调用。更利于面向接口编程
openfeign是springcloud在feign的基础上(接口绑定)支持了springmvc @requestmapping注解下的接口通过动态代理方式产生实现类。实现类进行负载均衡和调用其他服务
#编码
1,新建cloud-consumer-feign-order80工程
2,pom.xml
<!--openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.0.1.RELEASE</version> </dependency>3,启动类
4,业务类
service包 PaymentFeignService接口 package com.atguigu.springcloud.service; import com.atguigu.springcloud.entities.CommonResult; import com.atguigu.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}") CommonResult<Payment>getPaymentById(@PathVariable("id")Long id); }controller包
package com.atguigu.springcloud.controller; import com.atguigu.springcloud.entities.CommonResult; import com.atguigu.springcloud.entities.Payment; import com.atguigu.springcloud.service.PaymentFeignService; 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 OrderFeignController { @Resource private PaymentFeignService paymentFeignService; @GetMapping(value="/consumer/payment/get/{id}") public CommonResult<Payment>getpaymentbyid(@PathVariable("id")Long id){ return paymentFeignService.getPaymentById(id); } }它本身这里是支持ribbon负载的。但是目前jar包冲突。解决方法也有,找出jar包冲突,或者直接再写一个ribbon
###openFeign超时控制
1,模拟超时:写一个超时方法,8001 //模拟openfeign超时 @GetMapping(value="/feign/timeout") public String paymentFeignTimeout(){ try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } return serverport; }2,feign的consumer service里
@GetMapping(value="/feign/timeout") String paymentFeignTimeout();3,controller
@GetMapping(value="/consumer/payment/feign/timeout") public String paymentFeignTimeout(){ return paymentFeignService.paymentFeignTimeout(); }测试localhost:8001/payment/feign/timeout 没问题 测试localhost:/consumer/payment/feogm/timeout 会报Readtimeout异常
这时候需要在80 consumer的yml文件下指定openFeign的超时控制
#日志打印
NONE:默认的不显示日志 BASIC:仅记录请求方法,url,响应状态及执行时间 HEADERS:除了basic定义的信息以外。还有请求和响应头信息 FULL:除了HEADERS中的信息以外还有请求和响应正文及元数据
1,在order-feign80里创建config
package com.atguigu.springcloud.config; 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; } }2,application.yml
logging: level: #feign日志以什么级别监控哪个接口 com.atguigu.springcloud.service.PaymentFeignService: debug启动7001和provider-8001 和order-feign80
分布式系统的延迟和容错开源库
hystrix能保证一个依赖出问题的情况下,不会导致整体服务失败。避免级联故障
hystrix重要概念: 服务降级:服务可以用。服务器忙,请稍后再试这种。不让客户端等待,直接返回一个友好提示。称为服务降级 fallback
服务降级导致的原因:exception,timeout,服务熔断导致的服务降级。线程池
服务熔断:类似保险丝,达到最大服务访问,直接拒绝访问,拉闸限电。然后调用服务降级的方法并返回友好提示
服务限流:控制qps,严禁一窝蜂访问
###编码
一,正常情况下未使用降级 1,pom
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>2,yml
server: port: 8001 spring: application: name: cloud-provider-hystrix-payment eureka: client: register-with-eureka: true fetch-registry: true service-url: #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka defaultZone: http://eureka7001.com:7001/eureka3,主启动类
@SpringBootApplication @EnableEurekaClient4,service
package com.atguigu.springcloud.service; import cn.hutool.core.util.IdUtil; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.PathVariable; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * @author wsk * @date 2020/3/14 0:25 */ @Service public class PaymentService { /** * 正常访问 * @param id * @return */ public String paymentInfo_OK(Integer id){ return "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK,id:"+id+"\t"+"O(∩_∩)O哈哈~"; } /** * @HystrixCommand报异常后如何处理: * 一旦调用服务方法失败并抛出了错误信息后, * 会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法 * * @param id * @return */ /* @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = { //设置这个线程的超时时间是3s,3s内是正常的业务逻辑,超过3s调用fallbackMethod指定的方法进行处理 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000") })*/ public String paymentInfo_Timeout(Integer id){ int timeNumber = 5; /*int age = 10/0;*/ try{ TimeUnit.SECONDS.sleep(timeNumber); }catch (InterruptedException e){ e.printStackTrace(); } return "线程池:"+Thread.currentThread().getName()+" paymentInfo_Timeout,id:"+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒):"+timeNumber; }5,controller
@RestController @Slf4j public class PaymentController { @Resource private PaymentService paymentService; @Value("${server.port}") private String serverPort; @GetMapping(value = "/payment/hystrix/ok/{id}") public String paymentInfo_OK(@PathVariable("id") Integer id){ String result = paymentService.paymentInfo_OK(id); log.info("*****result:"+result); return result; } @GetMapping(value = "/payment/hystrix/timeout/{id}") public String paymentInfo_TimeOut(@PathVariable("id") Integer id){ String result = paymentService.paymentInfo_Timeout(id); log.info("*****result:"+result); return result; }6,测试,启动7001,hystrix 8001 访问两个接口
timeout那个明显很慢很卡 另外一个访问正常
###使用jmeter模拟高并发
1,下载jmeter,双击bin目录下的jmeter.bat 2,线程组名称:线程组202002
模拟线程数200,循环100次。20000并发
3,
4,填好以后点击开始按钮
明显发现之前那个ok的接口也变慢了。。在高并发场景下。不做处理是不行的
####80 consumer的加入
1,pom
<!--hystrix--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>2,yml
server: port: 80 eureka: client: fetch-registry: false service-url: defaultZone: http://eureka7001.com:7001/eureka/ feign: hystrix: enabled: true3,启动类
@SpringBootApplication @EnableFeignClients4,业务类
service package com.atguigu.springcloud.service; 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; /** * @author wsk * @date 2020/3/14 10:21 */ @Component @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT") 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); }controller
package com.atguigu.springcloud.controller; import com.atguigu.springcloud.service.PaymentHystrixService; import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; 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; /** * @author wsk * @date 2020/3/14 10:24 */ @RestController @Slf4j 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); } /*public String paymentTimeOutFallBackMethod(@PathVariable("id") Integer id){ return "我是消费者80,对方支付系统繁忙,请稍后再试,o(╥﹏╥)o"; }*/ /** * 全局 fallback 方法 * @return */ /*public String payment_Global_FallbackMethod(){ return "Global异常处理信息,请稍后再试。/(╥﹏╥)/~~"; }*/ }再用jmeter测还是一样。高并发下。未做处理的消费端直接崩了
处理方法 超时导致服务器慢:不在等待 出错:要兜底 1,对方服务超时,调用者不能一直卡死等待,必须有服务降级 2,对方服务down机,调用者不能一直卡死等待,必须有服务降级 3,如果对方服务ok,自己故障或自己不想等待。则自行降级
8001服务降级
1,service
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = { //设置这个线程的超时时间是3s,3s内是正常的业务逻辑,超过3s调用fallbackMethod指定的方法进行处理 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000") }) public String paymentInfo_Timeout(Integer id){ int timeNumber = 5; /*int age = 10/0;*/ try{ TimeUnit.SECONDS.sleep(timeNumber); }catch (InterruptedException e){ e.printStackTrace(); } return "线程池:"+Thread.currentThread().getName()+" paymentInfo_Timeout,id:"+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒):"+timeNumber; } public String paymentInfo_TimeOutHandler(Integer id){ return "线程池:"+Thread.currentThread().getName()+" 系统繁忙,请稍后再试,id:"+id+"\t"+"o(╥﹏╥)o"; }2,主启动类
@EnableCircuitBreaker再启动7001和8001服务 访问两个接口观察情况
这个超过峰值时间直接进入fallback降级
这个正常
###80客户端服务降级
1,yml
feign: hystrix: enabled: true2,主启动类
@EnableHystrix3,业务类 service
package com.atguigu.springcloud.service; 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; /** * @author wsk * @date 2020/3/14 10:21 */ @Component @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT" /*,fallback = PaymentFallbackService.class*/) 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); }controller
package com.atguigu.springcloud.controller; import com.atguigu.springcloud.service.PaymentHystrixService; import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; 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; /** * @author wsk * @date 2020/3/14 10:24 */ @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); } public String paymentTimeOutFallBackMethod(@PathVariable("id") Integer id){ return "我是消费者80,对方支付系统繁忙,请稍后再试,o(╥﹏╥)o"; } /** * 全局 fallback 方法 * @return */ /* public String payment_Global_FallbackMethod(){ return "Global异常处理信息,请稍后再试。/(╥﹏╥)/~~"; }*/ }分别启动7001,8001,80
调用localhost/consumer/payment/hystrix/timeout/1出现降级
现在每个业务对应一个兜底方法,代码膨胀。代码混乱。。
可以直接全局定义控制降级
三个搭配使用
1,
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")2,
@HystrixCommand3,
public String payment_Global_FallbackMethod(){ return "Global异常处理信息,请稍后再试。/(╥﹏╥)/~~"; }###如果指定了局部降级。以局部优先原则。。。如果没指定的。凡是加了@hystrixCommand注解的方法通通使用全局降级
解决服务降级耦合度问题
1,在order80里 service 注解指定实现类
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT" ,fallback = PaymentFallbackService.class)2,创建PaymentFailbackService package com.atguigu.springcloud.service; import org.springframework.stereotype.Component;
@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"; } }可以尝试关闭provider。。。后访问consumer看能不能访问?
直接进入了降级实现类
服务熔断类似保险丝达到最大服务后,直接拒绝访问,拉闸限电。调用服务降级。并返回友好提示
降级----熔断----恢复调用
服务熔断的三个状态:closed,open,half open
#编码 payment-hystrix-8001 1,service
//服务熔断 @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(); //UUID.randomUUID(); return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber; } public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){ return "id 不能负数,请稍后再试,o(╥﹏╥)o id:"+id; }##请求次数为10的请求,如果60%即6个请求失败,则熔断器由默认的closed转为open状态。设置了时间范围,超过这个时间,转为half open状态,这时候熔断器会再次请求服务,如果失败,继续保持open状态。如果成功,则恢复至closed状态
2,controller //服务熔断
@GetMapping("/payment/circuit/{id}") public String paymentCircuitBreaker(@PathVariable("id") Integer id){ String result = paymentService.paymentCircuitBreaker(id); log.info("****result:"+result); return result; }然后启动7001,8001
一直传正数进去访问走的正常。。。。
模拟传负数。。。次数多了。。超过60%请求都是访问的负数
这时候开启熔断机制。
再访问正数。会短暂时间存在下面情况
再多调用一会
涉及短路器的三个重要参数:快照时间窗口,请求总数阈值,错误百分比阈值
请求总数阈值:10秒钟内请求超过20次才有可能打开熔断器。 错误百分比阈值:默认50% 时间默认10秒
histrix dishboard 可视化窗口
1,新建项目cloud-consumer-hystrix-dashboard9001
2,pom
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>3,yml server: port: 9001
4,主启动类
package com.atguigu.springcloud; import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard; import org.springframework.context.annotation.Bean; /** * @author wsk * @date 2020/3/14 22:43 */ @SpringBootApplication @EnableHystrixDashboard public class HystrixDashboardMain9001 { public static void main(String[] args) { SpringApplication.run(HystrixDashboardMain9001.class,args); } }启动9001服务 访问localhost:9001/hystrix
这个图形化监控器可监控全局的服务
修改cloud-provider-hystrix-payment8001
1,pom 必须有这两个依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>2,启动类加一个@bean的配置
@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; }启动7001,8001,9001
点击下面那个 再分别访问两个接口
localhost:8001/payment/circuit/1 localhost:8001/payment/circuit/-1
进入图形化界面可以看到,反复调用正确,circuit:closed。熔断处于关闭状态 反复调用错误接口,circuit:open。会启动熔断器
图形化界面7色:每种颜色代表一种状态
分别和左边的数字对应
是spring的,gateway是zuul1.0的替代
springcloud Gateway是基于webFlux框架实现的。底层使用Netty
gateway是基于filter过滤链的方式,提供网关
基本功能:安全、监控/指标 、限流
外部请求–负载均衡nginx–网关–微服务
gateway是基于nio非阻塞的异步非阻塞模型开发。大大提高并发
zull与gateway的区别 1,zuul阻塞io gateway非阻塞异步io,支持长连接 2,gateway支持webflux。高可用
gateway三大核心: 路由,断言,过滤
路由:构建网关的基本模块,由id,uri一系列的断言和过滤器组成。如果断言为true。则匹配该路由
断言:是jdk1.8的新特性。在java.util.function.predicate。开发人员可以匹配http请求中所有内容(请求头,请求参数)请求与断言匹配则进行路由
过滤:请求被路由前或后对请求进行修改
gateway工作流程 客户端向gateway发出请求,再gatewayHandlerMapping中找到与请求匹配的路由,将其发送gateway web Handler
Handler通过指定的过滤器链将请求发送到实际的服务执行业务逻辑,然后返回。过滤器可能会在发送代理请求之前pre或post之后执行业务逻辑
Filter再pe类型的过滤器可以做:参数校验,权限校验,流量监控,日志输出,协议转换等
在post类型可以做:响应内容,响应头修改。日志输出,流量监控有非常重要的作用
gateway就是路由转发(转发到各个服务执行业务逻辑)+过滤链
####编码,gateway配置 1,新建module cloud-gateway-gateway9527
2,pom新加
<!--gateway--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>3,yml
server: port: 9527 spring: application: name: cloud-gateway cloud: gateway: routes: - id: payment_routh #payment_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名 #uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-provider-service #匹配后提供服务的路由地址 predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由 - id: payment_routh2 #payment_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名 #uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-provider-service #匹配后提供服务的路由地址 predicates: - Path=/payment/lb/** #断言,路径相匹配的进行路由 #- After=2020-03-15T15:35:07.412+08:00[GMT+08:00] #- Cookie=username,zzyy #- Header=X-Request-Id, \d+ #请求头要有X-Request-Id属性并且值为整数的正则表达式 #- Host=**.atguigu.com #- Method=GET #- Query=username, \d+ #要有参数名username并且值还要啥整数才能路由 eureka: instance: hostname: cloud-gateway-service client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka网关配置断言和路由的方式在以上 4,主启动类
@SpringBootApplication @EnableEurekaClient5,启动9527可能会报错。因为gateway不需要web和actutor依赖。去掉这两个依赖
启动7001,payment8001,9527三个服务
访问本身的provider
访问网关
对于gateway网关的配置有yml文件配置和硬编码两种。现在看硬编码
要求:用gateway网关配置硬编码访问到百度新闻 1,新建一个config.GateWayConfig
package com.atguigu.springcloud.config; 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; /** * @author wsk * @date 2020/3/15 13:49 */ @Configuration public class GateWayConfig { /** * 配置一个id为route-name的路由规则, * 当访问地址http://localhost:9527/guonei时会自动转发到地址:http://news.baidu.com/guonei * @param routeLocatorBuilder * @return */ @SuppressWarnings("JavaDoc") @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){ RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes(); routes.route("path_route_atguigu", r -> r.path("/guonei") .uri("http://news.baidu.com/guonei")).build(); return routes.build(); } }这时候启动服务,访问localhost:9527/guonei就可以访问到百度的国内新闻
gateway实现动态路由1,修改yml配置
discovery: locator: enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由2,修改下面的uri,换成提供服务的路由地址
uri: lb://cloud-provider-service启动8001和8002,7001和9527
访问localhost:9527/payment/lb
gateway断言 例如 predicate: - path: 。。。。。。
1,after 创建一个测试类
import java.time.ZonedDateTime; /** * @author wsk * @date 2020/3/15 15:33 */ public class T2 { public static void main(String[] args) { ZonedDateTime zbj = ZonedDateTime.now(); System.out.println(zbj); } }配置如下
id: payment_routh2 #payment_routh #路由的ID,没有固定规则但要求唯一,简易配合服务名 #uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-provider-service #匹配后提供服务的路由地址 predicates: Path=/payment/lb/** #断言,路径相匹配的进行路由After=2020-06-24T23:39:47.672+08:00[Asia/Shanghai]其余before。。。between同理
2,cookie级别的配置
Cookie=username,ljs在cmd下执行
curl http://localhost:9527/payment/lb --cookie "username=ljs" 才能访问此接口如果你的电脑dos命令不能执行curl 1,下载curl http://curl.haxx.se/download/curl-7.33.0-win64-ssl-sspi.zip
2,编辑环境变量CURL_HOME
直接访问则404
或者cookie传错也报错
3,Header配置 必须符合http请求头的要求进入路由
Header=X-Request-Id, \d+ #请求头要有X-Request-Id属性并且值为整数的正则表达式然后再dos下执行
curl http://localhost:9527/payment/lb -H "X-Request-Id:123"4,meThod级别的配置
Method=GET
gateway filterfilter:可以在请求被路由前或者之后对请求进行修改
gateway生命周期两个:pre和post一前一后
种类:gatewayFilter和globalfilter。单一和全局的
常用filter。
在yml里配置 filters: -AddRequestPatameter=X-Request-Id,1024
#过滤器工厂会在匹配请求头加上一对请求头。名称为X-Request-Id,值为1024
自定义全局GlobalFilter
###编码 1,创建filter类
package com.atguigu.springcloud.filter; 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; /** *@author wsk *@date 2020/3/15 18:10 */ @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");//每次进来后判断带不带uname这个key if(uname == null){ log.info("*********用户名为null ,非法用户,o(╥﹏╥)o"); exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE); //uname为null非法用户 return exchange.getResponse().setComplete(); } return chain.filter(exchange); } @Override public int getOrder() { return 0; } }直接启动9527,7001,8001 测试 如果不是访问localhost:9527/payment/lb/uname=xxx,就会报错
配置中心:集中化外部配置支持。git和github。 将公有的配置放在config配置中心
config配置中心分环境部署 dev 开发–test 测试–prod 产品–beta 预发布环境–release 灰度发布环境
github配置配置中心 1,创建一个叫springcloud-config的新仓库。
2,拿到ssh下的git地址
3,在本地新建git仓库并且clone远程代码 创建d:\44\springcloud2020
如果从远程拉取代码到本地提示密钥错误
把这串复制到github上保存
然后再拉取 拉取成功
4,idea新建module:cloud-config-center-3344 5,pom
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>6,application.yml
server: port: 3344 spring: application: name: cloud-config-center cloud: config: server: git: uri: https://github.com/ljsjiansong/springcloud-config.git #github仓库上面的git仓库名字 ##搜索目录 search-paths: - springcloud-config #读取分支 label: master #rabbit相关配置 rabbitmq: host: localhost port: 5672 username: guest password: guest eureka: client: service-url: defaultZone: http://localhost:7001/eureka #注册进eureka #rabbitmq相关配置,暴露bus刷新配置的端点 management: endpoints: #暴露bus刷新配置的端点 web: exposure: include: 'bus-refresh' #凡是暴露监控、刷新的都要有actuator依赖,bus-refresh就是actuator的刷新操作7,主启动类
package com.atguigu.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; /** * @author wsk * @date 2020/3/15 21:35 */ @SpringBootApplication @EnableConfigServer public class ConfigCenterMain3344 { public static void main(String[] args) { SpringApplication.run(ConfigCenterMain3344.class,args); } }8,启动7001再启动3344
服务端config server配置完成。现在实现客户端config client
1,cloud-config-client3355
pom.xml
<!--不带server了,说明是客户端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>2,application.yml不写了。写bootstrap.yml
bootstrap是系统级的。application是用户级。所以boot优先级更高
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 #配置中心地址 #服务注册到eureka地址 eureka: client: service-url: defaultZone: http://localhost:7001/eureka3,主启动类
package com.atguigu.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; /** * @author wsk * @date 2020/3/15 22:14 */ @SpringBootApplication @EnableEurekaClient public class ConfigClientMain3355 { public static void main(String[] args) { SpringApplication.run(ConfigClientMain3355.class,args); } }4,业务层
package com.atguigu.springcloud.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; /** * @author wsk * @date 2020/3/15 22:15 */ @RestController /*@RefreshScope*/ public class ConfigClientController { @Value("${config.info}") private String configInfo; //要访问的3344上的信息 @GetMapping("/configInfo") public String getConfigInfo(){ return configInfo; } }启动7001,3344,3355. 访问controller
如果github上配置文件改了。。。后台代码能跟着改动吗?服务端可以随着github的变化而变化。而客户端不行,必须实现动态更新才行
动态更新客户端这边 1,pom,图形化监控
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>2,修改yml暴露监控端口
#暴露监控端点 management: endpoints: web: exposure: include: "*"3,controller添加注解
@RefreshScope4,然后修改github
5,启动7001,3344,3355依次访问3344,3355
6,发现并没有改动。这时候需要运维发送post请求刷新3355
手动刷新完成
bus称为消息总线,支持两种消息代理:rabbitmq和kafka
bus通过消息中间件广播通知所有需要更新的服务。进行自动刷新
rabbitmq本地下载
1,下载安装erlang
环境变量:配置ERLANG_HOME
2,下载安装rabbitmq http://www.rabbitmq.com/download.html
3,进入到mq的sbin目录 4,执行
5,有可视化插件了
6,访问
自动更新编码1,以3355为模板创建一个3366
2,两种设计思想: (1)利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端配置 (2)利用消息总线触发一个服务端 configserver的/bus/refresh,而刷新所有客户端的配置
一般用第二个。。。
2.1服务端pom
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>yml
server: port: 3344 spring: application: name: cloud-config-center cloud: config: server: git: uri: https://github.com/ljsjiansong/springcloud-config.git #github仓库上面的git仓库名字 ##搜索目录 search-paths: - springcloud-config #读取分支 label: master #rabbit相关配置 rabbitmq: host: localhost port: 5672 username: guest password: guest eureka: client: service-url: defaultZone: http://localhost:7001/eureka #注册进eureka #rabbitmq相关配置,暴露bus刷新配置的端点 management: endpoints: #暴露bus刷新配置的端点 web: exposure: include: 'bus-refresh' #凡是暴露监控、刷新的都要有actuator依赖,bus-refresh就是actuator的刷新操作2.2 客户端 pom
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>yml
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 #配置中心地址 #rabbit相关配置 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: "*"启动7001。3344,3355,3366. 运维只需要改了github再curl一次post请求。所有的服务都改了。这就是动态刷新
动态刷新定点通知不想全部通知,只想定点通知
运维刷新的时候直接后面指定destnation
这里的config-client和这里有关
为什么引入stream。因为消息中间件学习成本高。所以学习stream
stream是什么
没有引入springcloud前
标准的mq 生产者和消费者之间靠媒介来传递内容 Message 消息必须走特定的通道 MessageChannel 消息通道里的消息,如何被消费。谁负责处理?MessageChannel的子接口SubscribableChannel,由MessageHandler来订阅
主要遵循发布订阅模式:topic主题进行广播,在rabbitmq中就是exchage。在kafka中就是topic
####编码部分
消息提供者1,cloud-stream-rabbitmq8801 创建
2,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 password: guest bindings: # 服务的整合处理 output: # 这个名字是一个通道的名称 destination: studyExchange # 表示要使用的exchange名称定义 content-type: application/json # 设置消息类型,本次为json,文本则设为text/plain binder: defaultRabbit # 设置要绑定的消息服务的具体设置 #group: atguiguA eureka: client: 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: receive-8801.com #主机名 prefer-ip-address: true # 显示ip3,主启动类
package com.atguigu.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author wsk * @date 2020/3/17 11:16 */ @SpringBootApplication public class StreamMQMain8801 { public static void main(String[] args) { SpringApplication.run(StreamMQMain8801.class,args); } }4,业务类 service
package com.atguigu.springcloud.service; /** * @author wsk * @date 2020/3/17 11:19 */ public interface IMessageProvider { /** * 消息发送 * @return */ String send(); }serviceImpl 不需要跟以前一样指定@service注解
package com.atguigu.springcloud.service.impl; import com.atguigu.springcloud.service.IMessageProvider; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.cloud.stream.messaging.Source; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.MessageBuilder; import javax.annotation.Resource; import java.util.UUID; /** * @author wsk * @date 2020/3/17 11:20 */ //这不是传统的service,这是和rabbitmq打交道的,不需要加注解@Service //这里不掉dao,掉消息中间件的service //信道channel和exchange绑定在一起 @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()); System.out.println("serial = " + serial); return null; } }controller
package com.atguigu.springcloud.controller; import com.atguigu.springcloud.service.IMessageProvider; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * @author wsk * @date 2020/3/17 13:14 */ @RestController public class SendMessageController { @Resource private IMessageProvider messageProvider; @GetMapping("/sendMessage") public String sendMessage(){ return messageProvider.send(); } }生产者搭建完毕
2,消费者
pom.xml和上面一样
application.yml
server: port: 8802 spring: application: name: cloud-stream-consumer 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: atguiguA eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka instance: lease-renewal-interval-in-seconds: 2 # 设置心跳的间隔时间,默认30 lease-expiration-duration-in-seconds: 5 # 超过5秒间隔,默认90 instance-id: receive-8802.com #主机名 prefer-ip-address: true # 显示ip业务类
controller package com.atguigu.springcloud.controller; 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; import org.springframework.web.bind.annotation.RestController; /** * @author wsk * @date 2020/3/17 14:24 */ @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); } }启动7001,rabbitmq,8801,8802
clone 一份8803
如果都启动了会出现重复消费问题
8802和8803同时收到了8801的发送的消息。。。出现重复消费问题
所以可以分组
解决方法:
###编码
8802 group: atguiguA 8803 group: atguiguA
分到同一个组
点击发送消息三次
持久化当我们的消费方yml文件里配置了group属性。。分组以后,多个消费方在同一个组就避免了重复消费问题。同时解决消息持久化问题。。。。
如果消费方死机。。。没有配置分组的消费方。。。再重启。接受不到mq发送的消息。其他的配置了分组的,可以接受到未消费的消息