SpringCloud高级应用(OpenFeign Ribbon Steam Sleuth+Zipkin
SpringCloud高级应用(OpenFeign Ribbon Steam Sleuth+Zipkin)
1 Spring Cloud OpenFeign
Feign [feɪn] 译文 伪装。Feign是一个轻量级的Http封装工具对象,大大简化了Http请求,它的使用方法是定义一个接口,然后在上面添加注解。不需要拼接URL、参数等操作。项目主页:https://github.com/OpenFeign/feign 。
- 集成Ribbon的负载均衡功能
- 集成了Hystrix的熔断器功能
- 支持请求压缩
- 大大简化了远程调用的代码,同时功能还增强啦
- Feign以更加优雅的方式编写远程调用代码,并简化重复代码
1.1业务分析
如上图,我们现在要实现打车用户打车下单,打车下单的时候需要匹配指定司机并更改司机状态,由之前空闲状态改成接单状态。这时候就涉及到hailtaxi-order
服务调用hailtaxi-driver
服务了,此时如果使用HttpClient工具,操作起来非常麻烦,我们可以使用SpringCloud OpenFeign
实现调用。
1.2 OpenFeign应用
使用OpenFeign实现服务之间调用,可以按照如下步骤实现:
1:导入feign依赖
2:编写Feign客户端接口-将请求地址写到该接口上
3:消费者启动引导类开启Feign功能注解
4:访问接口测试
1)导入依赖
在hailtaxi-api
中导入如下依赖:
<!--配置feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
2)创建Feign客户端接口
修改hailtaxi-api
创建com.itheima.driver.feign.DriverFeign
接口,代码如下:
@FeignClient(value = "hailtaxi-driver")//value = "hailtaxi-driver"指定服务的名字
public interface DriverFeign {
/****
* 更新司机信息,该方法和hailtaxi-driver服务中的方法保持一致
*/
@PutMapping(value = "/driver/status/{id}/{status}")
Driver status(@PathVariable(value = "id")String id, @PathVariable(value = "status")Integer status);
}
参数说明:
Feign会通过动态代理,帮我们生成实现类。
注解@FeignClient声明Feign的客户端,注解value指明服务名称
接口定义的方法,采用SpringMVC的注解。Feign会根据注解帮我们生成URL地址
注解@RequestMapping中的/driver,不要忘记。因为Feign需要拼接可访问地址
3)Controller调用
修改hailtaix-order
的下单方法,在下单方法中调用DriverFeign
修改司机状态,代码如下:
@Autowired
private DriverFeign driverFeign;
/***
* 下单
*/
@PostMapping
public OrderInfo add(){
//创建汽车信息
Car car = new Car("1","粤B123","BYD","red",1,1);
//创建司机
Driver driver = new Driver("1", "zhangsan", 5f, car, 2);
//创建订单
OrderInfo orderInfo = new OrderInfo("1", 350, new Date(), "深圳北站", "罗湖港", driver);
//调用OpenFeign修改司机状态
driverFeign.status("1",2);
return orderInfo;
}
4)启用OpenFeign
上面所有业务逻辑写完了,但OpenFeign还并未生效,我们需要在hailtaxi-order
中开启OpenFeign
,只需要在OrderApplication
启动类上添加@EnableFeignClients(basePackages = "com.itheima.driver.feign")
即可。
此时执行测试,效果如下:
后台输出如下:
司机状态变更:Driver(id=1, name=张司机, star=5.0, car=null, status=2)
1.3 数据压缩
用户在网络请求过程中,如果网络不佳、传输数据过大,会造成体验差的问题,我们需要将传输数据压缩提升体验。SpringCloud OpenFeign支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。
通过配置开启请求与响应的压缩功能:
feign:
compression:
request:
enabled: true # 开启请求压缩
response:
enabled: true # 开启响应压缩
也可以对请求的数据类型,以及触发压缩的大小下限进行设置:
feign:
compression:
request:
enabled: true # 开启请求压缩
mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
min-request-size: 2048 # 设置触发压缩的大小下限
#以上数据类型,压缩大小下限均为默认值
response:
enabled: true # 开启响应压缩
1.4 OpenFeign日志配置
通过loggin.level.xx=debug来设置日志级别。然而这个对Feign客户端不会产生效果。因为@FeignClient注解修饰的客户端在被代理时,都会创建一个新的Feign.Logger实例。我们需要额外通过配置类的方式指定这个日志的级别才可以。
实现步骤:
1. 在application.yml配置文件中开启日志级别配置
2. 编写配置类,定义日志级别bean。
3. 在接口的@FeignClient中指定配置类
4. 重启项目,测试访问
1)普通日志等级配置
在hailtaxi-order
的配置文件中设置com.itheima包下的日志级别都为debug:
# com.itheima 包下的日志级别都为Debug
logging:
level:
com.itheima: debug
2)Feign日志等级配置
在hailtaxi-order
启动类OrderApplication
中创建Logger.Level
,定义日志级别:
/***
* 日志级别
* @return
*/
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
日志级别说明:
Feign支持4中级别:
NONE:不记录任何日志,默认值
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
HEADERS:在BASIC基础上,额外记录了请求和响应的头信息
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据
重启项目,即可看到每次访问的日志
2 Spring Cloud Ribbon
什么是Ribbon?
Ribbon是Netflix发布的负载均衡器,有助于控制HTTP客户端行为。为Ribbon配置服务提供者地址列表后,Ribbon就可基于负载均衡算法,自动帮助服务消费者请求。
Ribbon默认提供的负载均衡算法:轮询,随机,重试法,加权。当然,我们可用自己定义负载均衡算法
2.1 Ribbon使用
1)业务分析
如上图,当用户下单调用hailtaxi-order
服务的时候,该服务会调用hailtaxi-driver
,此时如果是抢单过程,查询压力也会很大,我们可以为hailtaxi-driver
做集群,做集群只需要把工程复制多分即可,多个工程如下图:
2)调用测试
此时我们执行http://localhost:8001/order?token=zhangsan
调用,可以发现已经实现负载均衡了,18081
和18084
服务会轮询着调用。
2.2 Ribbon算法
我们上面没做任何相关操作,只是把服务换成了多个就实现了负载均衡,这是因为OpenFeign默认使用了Ribbon的轮询算法,如下图依赖包,引入OpenFeign的时候会传递依赖Ribbon包:
我们如果想改变相关算法,可以直接在application.yml
中配置算法即可。
#修改负载均衡算法,默认是轮询,配置之后变随机
ribbon:
#轮询
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
#随机算法
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
#重试算法,该算法先按照轮询的策略获取服务,如果获取服务失败则在指定的时间内会进行重试,获取可用的服务
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule
#加权法,会根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越大。刚启动时如果同统计信息不足,则使用轮询的策略,等统计信息足够会切换到自身规则。
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.ZoneAvoidanceRule
3 Spring Cloud Stream
Spring Cloud Stream
是一个用来为微服务应用构建消息驱动能力的框架。它可以基于 Spring Boot
来创建独立的、可用于生产的 Spring
应用程序。Spring Cloud Stream
为一些供应商的消息中间件产品提供了个性化的自动化配置实现,并引入了发布-订阅、消费组、分区这三个核心概念。通过使用 Spring Cloud Stream
,可以有效简化开发人员对消息中间件的使用复杂度,让系统开发人员可以有更多的精力关注于核心业务逻辑的处理。目前 Spring Cloud Stream
支持 RabbitMQ 、 Kafka 自动化配置。
3.1 SpringCloud Stream工作流程
通过Stream可以很好的屏蔽各个中间件的API差异,它统一了API,生产者通过OUTPUT向消息中间件发送消息,此时并不需要关心消息中间件是Kafka还是RabbitMQ,不需要关注他们的API,只需要用到Stream的API,这样可以降低学习成本。消费方通过INPUT消费指定的消息,也不需要关注消息中间件的API,架构图如上图:
我们对上图中的对象进行说明:
Application Core:生产者/消费者
inputs:消费者
outputs:生产者
binder:绑定器,主要和消息中间件之间进行绑定操作
Middleware:消息中间件服务
我们项目中真正用应用到Stream,只需要按照如上流程图操作即可。
生产者:
1:使用Source绑定消息输出通道。
2:通过MessageChannel输出消息。
3:通过@EnableBinding开启Binder,将生产者绑定到指定MQ服务。
消费者:
1:通过@EnableBinding绑定到指定MQ。
2:通过Sink绑定输出数据通道。
3:@StreamListener监听指定通道数据。
3.2 SpringCloud Stream实战
如上图,当用户行程结束,用户需进入支付操作,当用户支付完成时,我们需要更新订单状态,此时我们可以让支付系统将支付状态发送到MQ中,订单系统订阅MQ消息,根据MQ消息修改订单状态。我们将使用SpringCloud Stream
实现该功能。
3.2.1 生产者
1)引入依赖
在hailtaxi-pay
中引入依赖:
<!--stream-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
2)配置MQ服务
修改hailtaxi-pay
的application.yml
添加如下配置:
#Stream
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: 192.168.211.145
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
output: # 这个名字是一个通道的名称
destination: payExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
完整配置如下:
server:
port: 18083
spring:
application:
name: hailtaxi-pay
cloud:
#Consul配置
consul:
host: localhost
port: 8500
discovery:
#注册到Consul中的服务名字
service-name: ${spring.application.name}
#Stream
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: 192.168.211.145
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
output: # 这个名字是一个通道的名称
destination: payExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
3)消息输出管道绑定
创建com.itheima.pay.mq.MessageSender
代码如下:
@EnableBinding(Source.class)
public class MessageSender {
@Resource
private MessageChannel output;//消息发送管道
/***
* 发送消息
* @param message
* @return
*/
public Boolean send(Object message) {
//消息发送
boolean bo = output.send(MessageBuilder.withPayload(message).build());
System.out.println("*******send message: "+message);
return bo;
}
}
参数说明:
Source.class:绑定一个输出消息通道Channel。
MessageChannel:消息发送对象,默认是DirectWithAttributesChannel,发消息在AbstractMessageChannel中完成。
MessageBuilder.withPayload:构建消息。
4)消息发送
在com.itheima.pay.controller.TaxiPayController
中创建支付方法用于发送消息,代码如下:
@Autowired
private MessageSender messageSender;
/***
* 支付
* @return
*/
@GetMapping(value = "/wxpay/{id}")
public TaxiPay pay(@PathVariable(value = "id")String id){
//支付操作
TaxiPay taxiPay = new TaxiPay(id,310,3);
//发送消息
messageSender.send(taxiPay);
return taxiPay;
}
3.2.2 消费者
1)修改配置
修改hailtaxi-order
的核心配置文件application.yml
,在文件中配置要监听的MQ信息:
#Stream
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: 192.168.211.145
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称
destination: payExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
2)消息监听
在hailtaxi-order
中创建消息监听对象com.itheima.order.mq.MessageReceiver
,代码如下:
@EnableBinding(Sink.class)
public class MessageReceiver {
@Value("${server.port}")
private String port;
/****
* 消息监听
* @param message
*/
@StreamListener(Sink.INPUT)
public void receive(String message) {
System.out.println("消息监听(增加用户积分、修改订单状态)-->" + message+"-->port:"+port);
}
}
参数说明:
Sink.class:绑定消费者管道信息。
@StreamListener(Sink.INPUT):监听消息配置,指定了消息为application中的input。
我们请求http://localhost:18083/pay/wxpay/1
测试效果如下:
hailtaxi-order
:消息监听(增加用户积分、修改订单状态)-->{"outTradeNo":"1","money":310,"status":3}-->port:18082
3.3 消息分组
消息分组有2个好处,分别是集群合理消费、数据持久化。
3.3.1 集群消费下的分组
1)分组的意义
分组在项目中是有非常重大的意义,通常应用于消息并发高、消息堆积的场景,这些场景服务消费方通常会做集群操作,一旦做集群操作,我们又需要项目中的消费者合理消费,比如用户打车支付完成后,我们需要增加用户积分同时修改订单状态,如果集群环境中有2台服务器都执行该消费操作,此时用户积分会增加两次,就会造成非幂等问题。
此时集群中相同服务应该属于同一个组,同一个组中只允许有一个足节点消费某一个信息,这样就可以避免费幂等问题的出现。
2)分组实战
新增一个hailtaxi-order
消费者节点:
此时运行起来,18082
和18182
节点会同时消费所有数据。
修改hailtaxi-order
的核心配置文件application.yml
,添加分组:
此时再次测试,可以发现消费者不会重复消费数据。
3.3.2 数据持久化
我们把分组去掉,停掉hailtaxi-order
服务,然后请求http://localhost:18083/pay/wxpay/1
发送数据,发送完数据后,再启动hailtaxi-order
服务,此时发现没有数据可以消费,这是因为数据没有持久化,是一种广播模式,如果需要数据持久化,得给每个消费节点添加group组即可。
4 Sleuth+Zipkin链路追踪
在微服务系统中,一个来自用户的请求,请求先达到前端A(如前端界面)然后通过远程调用,到达系统中间件B,C(负载均衡,网关等),最后达到后端服务D,E,后端经过一系列的业务逻辑计算最后将数据返回给用户,对于这样一个请求,经历了这么多个服务,怎么样将它的请求过程的数据记录下来呢?这就需要用到服务链路追踪。
4.1 Sleuth/Zipkin介绍
Zipkin:
是一个开放源代码分布式的跟踪系统,它可以帮助收集服务的时间数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现。每个服务向zipkin报告计时数据,zipkin会根据调用关系通过Zipkin UI生成依赖关系图,展示多少跟踪请求经过了哪些服务,该系统让开发者可通过一个web前端轻松地收集和分析数据,可非常方便的监测系统中存在的瓶颈。
它可以帮助收集服务的时间数据,以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现
每个服务向zipkin报告计时数据,zipkin会根据调用关系通过Zipkin UI生成依赖关系图,展示多少跟踪请求经过了哪些服务,该系统让开发者可通过一个web前端轻松地收集和分析数据,可非常方便的监测系统中存在的瓶颈
Spring Cloud Sleuth:
为服务之间的调用提供链路追踪,通过使用Sleuth可以让我们快速定位某个服务的问题。分布式服务追踪系统包括:数据收集、数据存储、数据展示。通过Sleuth产生的调用链监控信息,让我们可以得知微服务之间的调用链路,但是监控信息只输出到控制台不太方便查看。
Sleuth和Zipkin结合,将信息发送到Zipkin,利用Zipkin的存储来存储信息,利用Zipkin UI来展示信息。
SpringCloudSleuth有4个特点
特点 | 说明 |
---|---|
提供链路追踪 | 通过sleuth可以很清楚的看出一个请求经过了哪些服务, 可以方便的理清服务局的调用关系 |
性能分析 | 通过sleuth可以很方便的看出每个采集请求的耗时, 分析出哪些服务调用比较耗时,当服务调用的耗时 随着请求量的增大而增大时,也可以对服务的扩容提 供一定的提醒作用 |
数据分析 优化链路 | 对于频繁地调用一个服务,或者并行地调用等, 可以针对业务做一些优化措施 |
可视化 | 对于程序未捕获的异常,可以在zipkpin界面上看到 |
4.2 Zipkin安装
关于zipkin在上面我们已经介绍了,我们接下来讲解zipkin的安装。
1)下载
下载地址:http://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/
下载后的文件zipkin-server-2.12.9-exec.jar
2)运行
运行zipkin-server-2.12.9-exec.jar
java -jar zipkin-server-2.12.9-exec.jar
回车即可运行,并访问http://localhost:9411/zipkin/
http://localhost:9411/zipkin/
效果如下:
4.3 Sleuth链路监控
1)引入依赖
引入zipkin
,它自身已经依赖了sleuth
,在hailtaxi-order
中引入依赖包如下:
<!--zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
依赖关系如下图:
2)配置服务地址
修改hailtaxi-order
的配置文件applicatin.yml
添加如下配置:
spring:
zipkin:
#zipkin服务地址
base-url: http://localhost:9411
sleuth:
sampler:
probability: 1 #采样值,0~1之间,1表示全部信息都手机,值越大,效率越低
我们执行一次下单调用http://localhost:18082/order
,再看zipkin控制台:
我们刚才调用的链路如下图:
调用链路如下分析:
1:调用了hailtaxi-order的POST->order()方法,该方法耗时333毫秒。
2:该方法调用了hailtaxi-driver的put /driver/status/{id}/{status}方法,耗时11毫秒。
分布式服务追踪系统包括:数据收集、数据存储、数据展示
通过Sleuth产生的调用链监控信息,让我们可以得知微服务之间的调用链路,但是监控信息只输出到控制台不太方便查看
Sleuth和Zipkin结合,将信息发送到Zipkin,利用Zipkin的存储来存储信息,利用Zipkin UI来展示信息