文章目录
服务调用Ribbon
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具.
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用.Ribbon客户端组件提供一系列完善的配置项如连接超时\重试等.简单的说,就是在配置文件中列出Load Balancer(LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)区连接这些机器.我们很容易使用Ribbon实现自定义的负载均衡算法.
getForEntity
restTemplate.getForEntity()功能与restTemplate.getForObject()相似,但它返回的是一个ResponseEntity对象,可以存放多种详细的信息,如请求结果\请求头等
@GetMapping("/consumer/payment/getEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id) {
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
if (entity.getStatusCode().is2xxSuccessful()) {
return entity.getBody();
} else {
return new CommonResult<>(444, "操作失败");
}
}
CommonResult
是自己定义的那个存储信息的类
Ribbon的负载均衡
Eureka依赖中存在Ribbon的包,因此Eureka的负载均衡算法就使用了Ribbon的
Ribbon中有七种负载均衡的实现
- com.netflix,loadlancer.RoundRobinRule: 轮询
- com.netflix,loadlancer.RandomRule: 随机
- com.netflix,loadlancer.RetryRule: 轮询策略获取服务,如果获取服务失败则在指定时间内进行重试
- WeightedResponseTimeRule: 对RoundRobinRule轮询的扩展,响应速度越快的实例选择权重越大,越容易被选择
- BestAvailableRule: 过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
- AvailabilitFilteringRule: 先过滤掉故障实例,再选择并发量较小的实例.
- ZoneAvoidanceRule: 默认哦恩过则,复合判断server所在区域的性能和server的可用性选择服务器
更换负载均衡算法
-
new一个需要的负载均衡算法
@Configuration public class MyRuleConfig { @Bean public IRule myRule() { return new RandomRule();//负载均衡策略: 随机 } }
这个配置类不能存放在
@ComponentScan
注解的所在包及其子包
下,而主启动类上的@SpringBootApplication
就存在@ComponentScan
注解,因此这个类不能放在主启动类所在包和子包下以下演示负载均衡包的位置
-
启用自己选择的负载均衡算法
在主启动类中使用
@RibbonClient
@SpringBootApplication @EnableEurekaClient //更换负载均衡策略 @RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MyRuleConfig.class) public class OrderMain8080 { public static void main(String[] args) { SpringApplication.run(OrderMain8080.class, args); } }
手写一个轮询算法
轮询算法的原理: rest接口请求次数%服务器据群总数 = 实际调用服务器位置下标,每次服务重启后rest接口计数从1开始
-
写一个接口
public interface LoadBalancer { ServiceInstance instance(List<ServiceInstance> serviceInstances); }
-
写一个实现类
@Component public class MyLB implements LoadBalancer { private AtomicInteger atomicInteger = new AtomicInteger(0); public final int getAndIncrement() { int current; int next; do { current = this.atomicInteger.get(); next = current >= Integer.MAX_VALUE ? 0 : current + 1; } while (!this.atomicInteger.compareAndSet(current, next)); return next; } @Override public ServiceInstance instance(List<ServiceInstance> serviceInstances) { int index = getAndIncrement() % serviceInstances.size(); return serviceInstances.get(index); } }
思路就是取得注册中心中所有服务后,按照轮询的规则返回一个服务
-
调用自己写的算法
@GetMapping("/consumer/payment/lb") public String getPaymentLB() { List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE"); if (instances == null || instances.size() <= 0) { return null; } ServiceInstance instance = loadBalancer.instance(instances); URI uri = instance.getUri(); return restTemplate.getForObject(uri + "/payment/lb", String.class); }
服务调用OpenFeign
Feign是一个声明式WebService客户端.使用Feign能让编写WebService客户端更加简单.它的使用方法是定义一个服务接口然后在上面添加注解.Feign支持可插拔式的编码器和解码器.SpringCloud对Feign进行了封装,使其支持了springMVC标准注解和HTTPMessageConverters.Feign可以与Eureka和Ribbon组合使用,以支持负载均衡.
Feign与OpenFeign的区别
OpenFeign的使用
-
pom
<!--openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
yaml: 默认使用OpenFeign是不需要使用yaml的配置的,这里配置了Eureka的服务地址
server: port: 8080 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
-
主启动类:使用
@EnableFeignClients
打开Feign@SpringBootApplication // 打开Feign @EnableFeignClients public class OrderFeignMain8080 { public static void main(String[] args) { SpringApplication.run(OrderFeignMain8080.class, args); } }
-
service层: 这是使用Feign的核心
@Component @FeignClient(value = "CLOUD-PAYMENT-SERVICE") public interface PaymentFeignService { @GetMapping("/payment/get/{id}") CommonResult<Payment> getPaymentById(@PathVariable("id") Long id); }
这是一个接口,这个接口中所有方法都要与生产者服务的方法保持一致,使用
@FeignClient()
找到服务,Feign会自动按照生产者中的方法实现这个接口,以后使用生产者的方法就直接调用该接口中的方法即可.注意需要使用
@Component
将这个接口注册进Spring中 -
controller层: 调用service层
@RestController @Slf4j public class PaymentFeignController { @Resource private PaymentFeignService paymentFeignService; @GetMapping("/consumer/payment/get/{id}") public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) { log.info("================================================================================="); return paymentFeignService.getPaymentById(id); } }
OpenFeign的超时控制
默认情况下Openfein的超时时间是1秒,也就是说1秒内服务端没有响应就会超时报错
超时时间可以通过如下配置设置
#Feign默认内置Ribbon,因此使用Ribbon进行超时控制
ribbon:
# 建立连接允许的最长时间
ReadTimeout: 5000
# 从服务器读取资源允许的最长时间
ConnectTimeout: 5000
Feign的日志增强
日志增强就是控制日志输出,让Feign打印更详细的日志
-
config层
@Configuration public class FeignConfig { @Bean Logger.Level feignLoggerLevel() { // 打印全日志 return Logger.Level.FULL; } }
-
yaml配置
#为Feign接口进行日志增强 logging: level: # 日志的打印级别 pero.fisher.springcloud.service.PaymentFeignService: debug
服务降级Hystrix
Hystrix的功能
- 服务降级
- 服务熔断
- 接近实时的监控
Hystrix的服务降级
当服务无法访问时,返回一个友好的响应(FallBack),不强行让用户等待
产生服务降级的情况
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池\信号量大满
生产者的服务降级
-
pom
<!--hystrix--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
-
yaml: 不需要关于hystrix配置
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
-
主启动类: 由于一会要使用hystrix的服务降级功能,因此把
@EnableCircuitBreaker
注解打开@SpringBootApplication @EnableEurekaClient //打开熔断 @EnableCircuitBreaker public class PaymentHystrixMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentHystrixMain8001.class, args); } }
-
service层: 进行服务降级
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000") }) public String paymentInfo_TimeOut(Integer id) { int time = 5; try { TimeUnit.SECONDS.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } return "线程池: " + Thread.currentThread().getName() + "timeout==========>id: " + id; } public String paymentInfo_TimeOutHandler(Integer id) { return "线程池: " + Thread.currentThread().getName() + "timeoutHandler==========>宕了"; }
使用
@HystrixCommand()
注解进行服务降级其中
fallbackMethod
参数是服务降级后调用的方法commandProperties
则规定超时多久算服务超时而触发服务降级,当没有超时降级,就不需要这个参数
消费者的服务降级
-
pom导包与生产者相同
-
yaml: 消费者使用类Feign,需要添加一个让Feign支持Hysrtix的配置
server: port: 8080 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka/ # 让feign支持hystrix feign: hystrix: enabled: true
-
主启动类
@SpringCloudApplication //这里使用Feign @EnableFeignClients @EnableCircuitBreaker public class OrderHystrixMain8080 { public static void main(String[] args) { SpringApplication.run(OrderHystrixMain8080.class, args); } }
-
controller层: 这里直接就在controller层进行服务降级,注解的使用方法与之前生产者的相同
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = { @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500") }) @GetMapping("/consumer/payment/hystrix/timeout/{id}") String timeout(@PathVariable("id") Integer id) { int a = 1 / 0; String res = paymentHystrixService.timeout(id); return res; } public String paymentInfo_TimeOutHandler(Integer id) { return "消费端繁忙"; }
全局服务降级
上述服务降级只是一个方法一个方法的降级,是一种局部的降级,还可以进行局部降级,就是说对整个类配置一个降级方法
@RestController
@Slf4j
//全局服务降级
@DefaultProperties(defaultFallback = "paymentGlobalFallback")
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
String ok(@PathVariable("id") Integer id) {
String res = paymentHystrixService.ok(id);
return res;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand
String timeout(@PathVariable("id") Integer id) {
int a = 1 / 0;
String res = paymentHystrixService.timeout(id);
return res;
}
public String paymentGlobalFallback() {
return "功能异常";
}
}
使用
@DefaultProperties(defaultFallback)
注解进行全局服务降级
通配服务降级
还有一种利用Feign的服务降级方法
-
实现Feign接口, 这个实现类中的方法就是以后服务降级调用的方法
@Component public class PaymentFallbackService implements PaymentHystrixService { @Override public String ok(Integer id) { return "ok宕了"; } @Override public String timeout(Integer id) { return "timeout宕了"; } }
-
Feign接口注解
@FeignClient
加入fallback
参数@Service @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", fallback = PaymentFallbackService.class) public interface PaymentHystrixService { @GetMapping("/payment/hystrix/ok/{id}") String ok(@PathVariable("id") Integer id); @GetMapping("/payment/hystrix/timeout/{id}") String timeout(@PathVariable("id") Integer id); }
当这个Feign接口中的任何一个方法触发了服务降级,都会调用它的实现类的对应方法
服务熔断
当服务的访问量达到最大后,服务器将拒绝访问,然后调用服务降级的方法,并返回一个友好的响应
Hystrix熔断器存在半打开状态.当一段时间内出现了大量的错误访问,Hystrix会为了保护主机而打开断路器,此时任何访问都不予回应,而是返回一个友好的响应,类似于服务降级.一段时间后,断路器会处于半打开状态,此时,hystrix会尝试响应几个请求,如果这些请求的正确率达到一定程度,就将断路器关闭,响应回复正常.
熔断的配置如下
@HystrixCommand(fallbackMethod = "paymentCircuitBreak_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")//失败率, 达到失败率会跳闸
})
//上面的配置的含义就是在10秒之内,访问10次的失败率是60%,则打开断路器.此后一段时间内,所有访问都将视为错误访问进入paymentCircuitBreak_fallback方法
public String paymentCircuitBreak(Integer id) {
if (id < 0) {
throw new RuntimeException("id不可用");
}
String serialNum = IdUtil.simpleUUID();
return Thread.currentThread().getName() + "获得流水号" + serialNum;
}
public String paymentCircuitBreak_fallback(Integer id) {
return "id不可用 请重试";
}
服务限流
杀死高并发等操作,避免过度拥挤,规定一秒运行多少操作并有序进行
Hystrix监控的图形化
-
pom依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency>
每个被监控的服务都要导入
actuator
依赖,也就是<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
-
yaml: 不需要多余的配置
server: port: 9001
-
主启动类: 开启
@EnableHystrixDashboard
注解@SpringBootApplication @EnableHystrixDashboard public class HystrixDashboardMain9001 { public static void main(String[] args) { SpringApplication.run(HystrixDashboardMain9001.class, args); } }
访问http://host:port/hystrix
就可以进入Hystrix的监控界面
服务监控
-
在现在使用的SpringCloud版本,也就是
Hoxton.SR1
版本,要想将服务让Hystrix监控到,必须在主启动类中进行如下配置:@SpringBootApplication @EnableEurekaClient //打开熔断 @EnableCircuitBreaker public class PaymentHystrixMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentHystrixMain8001.class, args); } //ServletRegistrationBean的默认路径不是/hystrix.stream,Hystrix不能找到这个服务,因此要手动修改路径 @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; } }
另外别忘导入
actuator
依赖 -
Web图形化监控端
-
点击
Monitor Stream
会进入该监控界面
以后这个服务的每一个操作状态都会在这里显示出来
服务网关Gateway
Gateway是在Spring生态系统之上构建的ApI网关服务,基于Spring 5,SpringBoot 2和Project Reactor等技术,使用非阻塞API.
Gateway旨在提供一种简单而有效的方式来对API进行路由,一级提供一些强大的过滤器功能,例如:熔断\先流\重试等.
Spring Cloud Gateway是基于WEbFlux框架
实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty
.
Spring Cloud GateWay的目标是提供统一的路由方式,且基于Filter链的方式提供了网关基本功能,如: 安全,监控/指标,限流.
Spring Cloud GateWay还支持WebSocket
Gateway核心理念
- Route(路由): 路由是构建网关的基本模块,他由ID,目标URI,一系列的断言和过滤器组成,如果断言为true,则匹配该路由.
- Predicate(断言): 匹配HTTP请求中所有的内容,如果请求与断言相匹配则进行路由
- Filter(过滤): 就是Spring框架中的GatewayFilter实例,使用过滤器,可以在请求被路由前或后对请求进行修改
Gateway工作流程
Gateway配置
-
pom
<!--gateway--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
这是Gateway的配置,当然还需要eureka客户端的包和一些通用的包,注意不需要加web相关的包
-
yaml
server: port: 9527 spring: application: name: cloud-gateway cloud: gateway: discovery: locator: enabled: true # 开启从注册中心利用为服务名进行路由功能 routes: - id: payment_route_01 # 路由ID uri: http://localhost:8001 # 提供服务的地址 predicates: - Path=/payment/get/** # 断言,路径匹配则进行路由 - id: payment_route_02 uri: http://localhost:8001 predicates: - Path=/payment/lb/** eureka: instance: hostname: cloud-gateway-service client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://eureka7001.com:7001/eureka
-
主启动类
@SpringBootApplication @EnableEurekaClient public class GateWayMain9527 { public static void main(String[] args) { SpringApplication.run(GateWayMain9527.class, args); } }
配置完成,启动后注册进注册中心,访问Gateway网关端口9527,网关就会路由到配置的端口,也就是8001
动态路由
上述配置将路由配置死了,其实还可以通过服务名的方式配置动态路由,访问Gateway端口后会路由到相同服务的不同端口中
只需要更改yaml配置
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 开启从注册中心利用为服务名进行路由功能
routes:
- id: payment_route_01 # 路由ID
uri: lb://CLOUD-PAYMENT-SERVICE
# uri: http://localhost:8001 # 提供服务的地址
predicates:
- Path=/payment/get/** # 断言,路径匹配则进行路由
- id: payment_route_02
uri: lb://CLOUD-PAYMENT-SERVICE
# uri: http://localhost:8001
predicates:
- Path=/payment/lb/**
eureka:
instance:
hostname: cloud-gateway-service
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
注意uri的更改
断言
断言(predicates)的作用是通过Gateway访问时,需要满足所有断言,否则不予通过
yaml配置如下
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 开启从注册中心利用为服务名进行路由功能
routes:
- id: payment_route_01 # 路由ID
uri: lb://CLOUD-PAYMENT-SERVICE
# uri: http://localhost:8001 # 提供服务的地址
predicates:
- Path=/payment/get/** # 断言,路径匹配则进行路由
- After=2020-05-12T02:40:17.806+08:00[Asia/Shanghai] # 某时间点之后断言成功
- Cookie=username,yb # 存在session的断言 session名,正则
- Header=X-request-Id,\d+ # 请求头的断言 头名称,正则(\d+表示整数)
- Host=**.host.com # 访问主机的断言
- Method=GET # 访问方法的断言
- Query=id,\d+ # 参数的断言
- id: payment_route_02
uri: lb://CLOUD-PAYMENT-SERVICE
# uri: http://localhost:8001
predicates:
- Path=/payment/lb/**
eureka:
instance:
hostname: cloud-gateway-service
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
注意看
predicates
参数,这就是断言
过滤器
Gateway中存在这内置过滤器,功能与断言相似,一般不太需要
还可以自定义过滤器,与譬如SpringBoot、Servlet中的过滤器功能相似
@Component
@Slf4j
public class MyLogGatewayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("执行过滤器0==========>" + new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname == null) {
log.info("用户名非法");
// 返回状态码
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
// 过滤器的优先级 数字越小优先级越高
public int getOrder() {
return 0;
}
}
过滤器需要实现
GlobalFilter
和Ordered
接口过滤器可以设置多个,然后一级一级过滤,
getOrder()
方法就是用来设置过滤器的优先级的注意一定要使用
@Component
注解,将这个过滤器注册进Spring中
SpringCloud Config分布式配置中心
微服务意味着要将单体应用中的业务拆分成一个个字符无,每个服务的粒度相对较小,因此系统中会出现大量的服务.由于每个服务都需要必要的配置信息才能运行,所以一套集中式的\动态的配置管理设施是必不可少的.SpringCloud提供了ConfigServer来解决这个问题.
springCloud Config为微服务架构中的为服务提供集中化的外部配置支持,配置服务器为各个不同为服务应用的所有环境提供了一个中心化的外部配置.
Config的服务端配置
-
首先需要一个远程仓库存放各种配置文件,我在GitHub中名为
springcloud-config
的仓库中存放了如下配置文件
里面的内容是这样的config: info: "master branch,springcloud-config/config-dev.yml version=3"
-
pom
<!--springcloud-config-server--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency>
这是config服务端的依赖,还需要eureka等常规配置
-
yaml
server: port: 3344 spring: application: name: cloud-config-center cloud: config: server: git: # GitHub仓库 uri: https://github.com/borzj/springcloud-config.git # 搜索目录 search-paths: - springcloud-config force-pull: true username: xxx password: xxx # 选择分支 label: master eureka: client: service-url: defaultZone: http://localhost:7001/eureka
重点是配置远程仓库
-
主启动类
@SpringBootApplication @EnableConfigServer public class ConfigCenterMain3344 { public static void main(String[] args) { SpringApplication.run(ConfigCenterMain3344.class, args); } }
然后,访问http://host:port/filename
就可以访问到github中对应文件的内容
Config客户端
-
pom: 这里只给出了config客户端的依赖
<!--springcloud-config-client--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
-
yaml
在这里用到的yaml叫做
bootstrap.yaml
bootstrap.yaml是系统级配置文件,优先级比application.yaml高
初始化时,bootstrap负责从外部源加载配置属性并解析.
客户端配置bootstrap文件后,会首先从客户端读取配置
server: port: 3355 spring: application: name: config-client cloud: config: label: master # 分支 name: config # 文件名 profile: dev #后缀 uri: http://localhost:3344 # 服务器uri eureka: client: service-url: defaultZone: http://localhost:7001/eureka
-
主启动类
@SpringBootApplication @EnableEurekaClient public class ConfigClientMain3355 { public static void main(String[] args) { SpringApplication.run(ConfigClientMain3355.class, args); } }
-
controller层: 使用从Config服务端拿到的配置
@RestController public class ConfigClientController { @Value("${config.info}") private String configInfo; @GetMapping("/configInfo") public String getConfigInfo() { return configInfo; } }
然后访问controller层即可看到拿到的配置信息
Config动态刷新
按照上述配置中,如果远程仓库中的配置文件发生了改变,需要将微服务重启一遍才能更新到配置,这是一种灾难.因此我们需要使用动态刷新,实现热刷新
-
在yaml中
server: port: 3355 spring: application: name: config-client cloud: config: label: master # 分支 name: config # 文件名 profile: dev #后缀 uri: http://localhost:3344 # 服务器uri eureka: client: service-url: defaultZone: http://localhost:7001/eureka # 动态刷新,暴露监控端口 management: endpoints: web: exposure: include: "*"
注意加入了
management
,用于对外暴露端口 -
controller中加入
@RefreshScope
注解,用于刷新配置@RestController // 刷新配置 @RefreshScope public class ConfigClientController { @Value("${config.info}") private String configInfo; @GetMapping("/configInfo") public String getConfigInfo() { return configInfo; } }
-
当远程配置文件改变后,需要post请求客户端,让它重新拉取配置
使用命令
curl -X POST "http://localhost:3355/actuator/refresh"
Bus消息总线
Config的动态刷新需要分别对每一个客户端都进行一次刷新,操作太过复杂.
Spring Cloud Bus配合Spring Cloud Config使用可以实现配置的自动动态刷新,也就是一行命令刷新所有客户端
Bus是用来将分布式系统的节点与轻量级消息系统连接起来的框架,整合了Java的事件处理机制和消息中间件功能.Spring Cloud Bus目前支持RabbitMQ和Kafka
Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道.
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有的微服务实例都连接上来,由于该主题中产生消息会被所有实例监听和消费,所以称它为消息总线.
安装RabbitMQ
我们选用RabbitMQ作为Bus的中间件,安装与配置流程如下
-
安装, RabbitMQ运行需要一个Erlang,所以安装RabbitMQ之前先将它装上
Manjaro系统下直接使用
sudo pacman -S rabbitmq
,它会将RabbitQM依赖的包都下载号,包括Erlang -
启动管理功能
sudo rabbitmq-plugins enable rabbitmq_management
-
启动RabbitQM服务器
sudo rabbitmq-server start
-
通过
http://localhost:15672/
访问RabbitMQ的可视化界面帐号和密码都是
guest
Bus全局广播
服务端的配置
-
pom: 要加入RabbitMQ的依赖
<!--添加消息总线RabbitMQ支持--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
-
yaml: 加入RabbitMQ的配置,并暴露端口
server: port: 3344 spring: application: name: cloud-config-center cloud: config: server: git: # GitHub仓库 uri: https://github.com/borzj/springcloud-config.git # 搜索目录 search-paths: - springcloud-config force-pull: true username: xxx password: xxx # 选择分支 label: master eureka: client: service-url: defaultZone: http://localhost:7001/eureka #RabbitMQ相关配置,15672是Web管理界面的端口,5672是MQ的访问端口 rabbitmq: host: localhost port: 5672 username: guest password: guest #暴露bus刷新配置端口 management: endpoints: web: exposure: include: "bus-refresh"
客户端配置
-
pom: 也需要加入RabbitMQ依赖
<!--添加消息总线RabbitMQ支持--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
-
yaml: 配置RabbitMQ
server: port: 3355 spring: application: name: config-client cloud: config: label: master # 分支 name: config # 文件名 profile: dev #后缀 uri: http://localhost:3344 # 服务器uri #RabbitMQ相关配置,15672是Web管理界面的端口,5672是MQ的访问端口 rabbitmq: host: localhost port: 5672 username: guest password: guest eureka: client: service-url: defaultZone: http://localhost:7001/eureka # 动态刷新,暴露监控端口 management: endpoints: web: exposure: include: "*"
当远程配置文件改变是,现在只需要一个命令: curl -X POST http://localhost:3344/actuator/bus-refresh
所有配置过bus的客户端都可以动态刷新配置
Bus的定点通知
如果你不想刷新所有的客户端,只需要刷新某一个客户端,Bus提供了定点通知的功能,命令如下:
curl -X POST http://localhost:3344/actuator/bus-refresh/服务名:端口号
SpringCloud Stream消息驱动
pringCloud Stream是一个构建消息驱动微服务的框架.用于屏蔽消息中间件的差异,同一消息编程模型
应用程序通过inputs和output与Stream中的binder交互.通过定义绑定器(binder)作为中间件完美地实现了应用程序与消息中间件细节之间的隔离.通过向应用程序暴露同一的Channel通道,使得应用程序不需要再考录各种不同的消息中间件实现.
tream中的消息通信方式遵循了发布-订阅模式
消息驱动的生产者
-
pom:这里绑定RabbitMQ
<!--stream-rabbit--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency>
-
yaml
server: port: 8801 spring: application: name: cloud-stream-provider cloud: stream: binders: # 配置bindermq的服务信息 defaultRabbit: # 自定义binder名称 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 # 要绑定的消息名称 eureka: client: service-url: defaultZone: http://localhost:7001/eureka
-
主启动类
@SpringBootApplication public class StreamMQMain8801 { public static void main(String[] args) { SpringApplication.run(StreamMQMain8801.class, args); } }
-
service层: 定义发送消息方法
接口:
public interface IMessageProvider { String send(); }
实现类:
//消息的推送管道 @EnableBinding(Source.class)//org.springframework.cloud.stream.messaging.Source包下的 @Slf4j public class IMessageProviderImpl implements IMessageProvider { @Resource private MessageChannel output; @Override public String send() { String s = UUID.randomUUID().toString(); output.send(MessageBuilder.withPayload(s).build()); log.info("=======================send" + s); return null; } }
这里的service不再用@Service注解而是用
@EnableBinding
将消息推送到管道 -
controller层: 调用service层发送消息
@RestController public class SendMessageController { @Resource private IMessageProvider messageProvider; @GetMapping("/sendMessage") public String sendMessage() { return messageProvider.send(); } }
消息驱动的消费者
-
pom: 也是stream-rabbit
<!--stream-rabbit--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency>
-
yaml
server: port: 8802 spring: application: name: cloud-stream-provider cloud: stream: binders: # 配置bindermq的服务信息 defaultRabbit: # 自定义binder名称 type: rabbit # 消息组件类型 environment: # rabbitmq的环境 spring: rabbitmq: host: localhost port: 5672 username: guest password: guest bindings: # 服务的整合 input: destination: studyExchange # exchange名称 content-type: application/json # 消息类型,这是json类型,"text/plain"是文本类型 binder: defaultRabbit # 要绑定的消息名称 eureka: client: service-url: defaultZone: http://localhost:7001/eureka
绑定的
destination
应与生产者的相同 -
主启动类
@SpringBootApplication public class StreamMQMain8802 { public static void main(String[] args) { SpringApplication.run(StreamMQMain8802.class, args); } }
-
controller层: 仍然需要
@EnableBinding
过去管道消息@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("消费者" + serverPort + message.getPayload()); } }
重复消费问题
在有多个微服务消费同一个消息时,在同一个分组中,组内会发生竞争,只能消费一次.在不同的分组中可以被消费多次.默认每个服务的分组是不同的.因此要想解决重复消费问题,就将多个微服务放在同一个组中.
spring:
cloud:
stream:
bindings: # 服务的整合
input:
destination: studyExchange # exchange名称
content-type: application/json # 消息类型,这是json类型,"text/plain"是文本类型
binder: defaultRabbit # 要绑定的消息名称
#分组 解决重复消费问题
group: streamconsumer
group
设置分组名
消费持久化
配置group
后,会将停机时生产者发来的没有消费的信息重新消费,这就是消息的持久化
Spring Cloud Sleuth分布式请求链路跟踪
在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的服务节点调用来协同产生最后的请求结果,每个前段请求都会形成一个复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败.
Spring Cloud Sleuth就是用来进行服务跟踪和链路监控的.
zipkin的安装
zipkin可以可视化展示Sleuth的监控
下载完成,是一个jar包,使用java -jar
运行jar包
zipkin默认使用了9411端口,通过http://localhost:9411
访问
了解几个名词
- Trace Id: 唯一标识一个链路的Id
- Span: 标识发起的请求
Sleuth的使用
生产者
-
pom
<!--sleuth+zipkin--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
这个依赖中包含了sleuth和zipkin
-
yaml: 配置zipkin和sleuth
spring: # 配置zipkin和sleuth zipkin: # zipkin的地址 base-url: http://localhost:9411 sleuth: # 设置采样率, 介于0-1之间 sampler: probability: 1
-
主启动类: 不需要另外添加新的注解
-
controller(象征性写了个controller用来测试)
@GetMapping("/payment/zipkin") public String zipkin() { return "调用了payment_zipkin"; }
消费者
-
pom: 也是zipkin的包
<!--sleuth+zipkin--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency>
-
yaml
spring: # 配置zipkin和sleuth zipkin: # zipkin的地址 base-url: http://localhost:9411 sleuth: # 设置采样率, 介于0-1之间 sampler: probability: 1
-
主启动类: 不需要另外加什么注解
-
controller: 调用生产者的服务
@GetMapping("/consumer/payment/zipkin") public String zipkin() { return restTemplate.getForObject("http://localhost:8001" + "/payment/zipkin/" , String.class); }
Sleuth的测试
访问几次生产者的服务,在Zipkin的Web界面中就可以查看这个访问经过的链路