ribbon可以实现服务的调用,为何又出现openFeign
因为以前是ribbon+RestTemplete调用。feign更加容易调用。更利于面向接口编程
openfeign是springcloud在feign的基础上(接口绑定)支持了springmvc @requestmapping注解下的接口通过动态代理方式产生实现类。实现类进行负载均衡和调用其他服务
#编码
1,新建cloud-consumer-feign-order80工程
2,pom.xml
org.springframework.cloud
spring-cloud-starter-openfeign
2.0.1.RELEASE
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}")
CommonResultgetPaymentById(@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 CommonResultgetpaymentbyid(@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
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
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/eureka
3,主启动类
@SpringBootApplication
@EnableEurekaClient
4,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并发
4,填好以后点击开始按钮
明显发现之前那个ok的接口也变慢了。。在高并发场景下。不做处理是不行的
####80 consumer的加入
1,pom
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
2,yml
server:
port: 80
eureka:
client:
fetch-registry: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
feign:
hystrix:
enabled: true
3,启动类
@SpringBootApplication
@EnableFeignClients
4,业务类
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: true
2,主启动类
@EnableHystrix
3,业务类
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,
@HystrixCommand
3,
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
org.springframework.cloud
spring-cloud-starter-netflix-hystrix-dashboard
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
必须有这两个依赖
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-actuator
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新加
org.springframework.cloud
spring-cloud-starter-gateway
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
@EnableEurekaClient
5,启动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);
}
}
配置如下
其余before。。。between同理
2,cookie级别的配置
在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请求头的要求进入路由
然后再dos下执行
curl http://localhost:9527/payment/lb -H "X-Request-Id:123"
4,meThod级别的配置
Method=GET
gateway filter
filter:可以在请求被路由前或者之后对请求进行修改
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 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
org.springframework.cloud
spring-cloud-config-server
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);
}
}
服务端config server配置完成。现在实现客户端config client
1,cloud-config-client3355
pom.xml
org.springframework.cloud
spring-cloud-starter-config
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/eureka
3,主启动类
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,图形化监控
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-actuator
2,修改yml暴露监控端口
#暴露监控端点
management:
endpoints:
web:
exposure:
include: "*"
3,controller添加注解
@RefreshScope
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
org.springframework.cloud
spring-cloud-starter-bus-amqp
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
org.springframework.cloud
spring-cloud-starter-bus-amqp
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
为什么引入stream。因为消息中间件学习成本高。所以学习stream
stream是什么
标准的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 # 显示ip
3,主启动类
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();
}
}
生产者搭建完毕
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 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发送的消息。其他的配置了分组的,可以接受到未消费的消息