一、概述
在微服务架构中,涉及服务与服务间的依赖关系非常复杂,SpringCloud 官方推荐 sleuth + zipkin 方式实现链路追踪,zipkin web 管理页面中,可以每个请求的依赖关系、请求时间、返回时间、服务与服务间的依赖图等。
在本文中主要介绍两种服务依赖情况
- 微服务调用微服务
- 微服务通过RabbitMQ发送消息
环境依赖:
名称 | 值 | 备注 |
---|---|---|
JDK | 1.8 | |
RabbitMQ | 3.5.6 | 在演示中需要启用RabbitMQ,详细介绍 https://blog.csdn.net/qq_36918149/article/details/100006373 |
SpringCloud | Greenwich.SR1 | |
Consul | 1.5.2 | 注册中心,Consul安装及介绍 https://mp.csdn.net/mdeditor/95372805# |
zipkin | 最新版 | zipkin安装配置 https://mp.csdn.net/mdeditor/100068323# |
演示数据流转图:
customer-service 请求下单
waiter-service 生产订单,MQ通知barista-service制作咖啡,咖啡制作完成后MQ通知customer-service取coffee
barista-service 制作coffee,制作完成后MQ通知waiter-service
二 、代码
2.1 zipkin 启动并连上RabbitMQ
java -jar zipkin.jar --zipkin.collector.rabbitmq.addresses=100.61.11.211:5672
2.2 customer-service (顾客服务)
1 ) 主要pom依赖
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<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>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.7</version>
</dependency>
</dependencies>
2 ) 配置文件
application.properties
spring.application.name=customer-service
server.port=8090
customer.name=spring-${server.port}
#zipkin 地址
spring.zipkin.base-url=http://127.0.0.1:9411/
#zipkin 日志采样比例
spring.sleuth.sampler.probability=1.0
#zipkin 推送方式
spring.zipkin.sender.type=web
# spring总是输出日志
spring.output.ansi.enabled=ALWAYS
#rabbitmq配置
spring.rabbitmq.host=100.61.11.211
spring.rabbitmq.port=5672
spring.rabbitmq.username=1234567
spring.rabbitmq.password=1234567
# 名称为:notifyOrders 的Exchange绑定消费端路由key为spring-8090
spring.cloud.stream.rabbit.bindings.notifyOrders.consumer.binding-routing-key=${customer.name}
3 ) CoffeeOrderService (rpc调用waiter-service接口)
@FeignClient(name = "waiter-service", contextId = "coffeeOrder")
public interface CoffeeOrderService {
@PostMapping("/order/{id}")
boolean createOrder(@PathVariable("id") Long id);
}
4 ) CustomerController(客户订单触发接口)
@RestController
@RequestMapping("/customer")
@Slf4j
public class CustomerController {
@Autowired
private CoffeeOrderService coffeeOrderService;
@PostMapping(value = "/create/order/{id}")
public Boolean createOrder(@PathVariable("id") Long id){
log.info("customer create order no: {}",id);
return coffeeOrderService.createOrder(id);
}
5 ) Waiter (服务启动时消息绑定接口)
public interface Waiter {
String NOTIFY_ORDERS = "notifyOrders";
@Input(NOTIFY_ORDERS)
SubscribableChannel notification();
}
6 ) NotificationListener(接受coffee订单用餐通知)
@Component
@Slf4j
public class NotificationListener {
@StreamListener(Waiter.NOTIFY_ORDERS)
public void takeOrder(@Payload Long id) {
log.info("Order No: {} is Fish, I'll take it.", id);
}
}
7 ) CustomerServiceApplication 启动类
@SpringBootApplication
@Slf4j
@EnableDiscoveryClient
@EnableFeignClients
@EnableAspectJAutoProxy
@EnableBinding(Waiter.class)
public class CustomerServiceApplication {
public static void main(String[] args) {
SpringApplication.run(CustomerServiceApplication.class, args);
}
@Bean
public CloseableHttpClient httpClient() {
return HttpClients.custom()
.setConnectionTimeToLive(30, TimeUnit.SECONDS)
.evictIdleConnections(30, TimeUnit.SECONDS)
.setMaxConnTotal(200)
.setMaxConnPerRoute(20)
.disableAutomaticRetries()
.setKeepAliveStrategy(new CustomConnectionKeepAliveStrategy())
.build();
}
}
备注:@EnableBinding(Waiter.class) 绑定了MQ接口
2.3 waiter-service(服务员服务)
1 ) 主要pom依赖
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<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>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
</dependencies>
2 ) 配置文件
application.properties
spring.application.name=customer-service
server.port=8080
#zipkin 地址
spring.zipkin.base-url=http://127.0.0.1:9411/
#zipkin 日志采样比例
spring.sleuth.sampler.probability=1.0
#zipkin 推送方式
spring.zipkin.sender.type=web
# spring总是输出日志
spring.output.ansi.enabled=ALWAYS
#rabbitmq配置
spring.rabbitmq.host=100.61.11.211
spring.rabbitmq.port=5672
spring.rabbitmq.username=1234567
spring.rabbitmq.password=1234567
#Exchange 交换机: finishedOrders 绑定到Queue 队列: finishedOrders.waiter-service
spring.cloud.stream.bindings.finishedOrders.group=waiter-service
#notifyOrders 生产者路由根据 headers.customer
spring.cloud.stream.rabbit.bindings.notifyOrders.producer.routing-key-expression=headers.customer
3 ) Barista(服务启动时消息绑定接口)
public interface Barista {
String NEW_ORDERS = "newOrders";
String FINISHED_ORDERS = "finishedOrders";
@Input
SubscribableChannel finishedOrders();
@Output
MessageChannel newOrders();
}
4 ) Customer(服务启动时消息绑定接口)
public interface Customer {
String NOTIFY_ORDERS = "notifyOrders";
@Output(NOTIFY_ORDERS)
MessageChannel notification();
}
5 ) CoffeeOrderController(提供rpc下单接口,及给barista发下单MQ)
@RestController
@RequestMapping("/order")
@Slf4j
public class CoffeeOrderController {
@Autowired
private Barista barista;
@PostMapping("/{id}")
public boolean createOrder(@PathVariable( "id") Long id){
// 通知咖啡师有新的coffee有新的订单
barista.newOrders().send(MessageBuilder.withPayload(id).build());
return true ;
}
}
6 ) OrderListener(监听咖啡完成MQ及给customer-service 发取餐MQ)
@Component
@Slf4j
public class OrderListener {
@Autowired
private Customer customer;
@Autowired
private CoffeeOrderService orderService;
@StreamListener(Barista.FINISHED_ORDERS)
public void listenFinishedOrders(Long id) {
log.info("We've finished an order no [{}].", id);
String customerName = "张三";
Message<Long> message = MessageBuilder.withPayload(id)
.setHeader("customer", customerName)
.build();
log.info("Notify the customer: {}", customerName);
customer.notification().send(message);
}
}
7 ) WaiterServiceApplication启动类(绑定启动了对应MQ接口)
@SpringBootApplication
@EnableDiscoveryClient
@EnableBinding({ Barista.class, Customer.class })
public class WaiterServiceApplication implements WebMvcConfigurer {
public static void main(String[] args) {
SpringApplication.run(WaiterServiceApplication.class, args);
}
}
备注:@EnableBinding({ Barista.class, Customer.class }) 这里是在绑定相应的MQ
2.4 barista-service(制作coffee服务)
1 ) 主要pom依赖
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-binder-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
</dependencies>
2 ) 配置文件
application.properties
#服务名称
spring.application.name=barista-service
#服务端口,0表示随机端口
server.port=0
#总是推送链路到zipkin中
spring.output.ansi.enabled=ALWAYS
#zipkin中日志抽样比例
spring.sleuth.sampler.probability=1.0
#zipkin中采集日志的模式
spring.zipkin.sender.type=rabbit
#actuator 相关配置
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
spring.rabbitmq.host=100.61.11.211
spring.rabbitmq.port=5672
spring.rabbitmq.username=1234567
spring.rabbitmq.password=1234567
#生成名称为newOrders的exchange,并绑定到 名称为 newOrders.barista-service 的Queue 上
spring.cloud.stream.bindings.newOrders.group=barista-service
3 ) Waiter (服务启动时消息绑定接口)
public interface Waiter {
String NEW_ORDERS = "newOrders";
String FINISHED_ORDERS = "finishedOrders";
@Input(NEW_ORDERS)
SubscribableChannel newOrders();
@Output(FINISHED_ORDERS)
MessageChannel finishedOrders();
}
4 ) OrderListener (消息监听及发送类)
@Component
@Slf4j
public class OrderListener {
@Autowired
@Qualifier(Waiter.FINISHED_ORDERS)
private MessageChannel finishedOrdersMessageChannel;
@StreamListener(Waiter.NEW_ORDERS)
public void processNewOrder(Long id) {
log.info("Barista start make coffer order no {} .", id);
try {
Thread.sleep(200L);
} catch (Exception e) {
}
finishedOrdersMessageChannel.send(MessageBuilder.withPayload(id).build());
log.info("Barista notify Waiter finish coffee order no {} .", id);
}
}
5 ) BaristaServiceApplication(启动类)
@EnableJpaRepositories
@SpringBootApplication
@EnableBinding(Waiter.class)
public class BaristaServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BaristaServiceApplication.class, args);
}
}
注意:@EnableBinding(Waiter.class) 是绑定消息接口。
三、结果演示
3.1 客户下单
url :http://localhost:8090/customer/create/order/230
3.2 服务调用链路
3.3 服务依赖关系图
3.4 rabbitMQ
四、总结
1)在项目中,可以通过http 向zipkin推送链路信息, 但是如果在生成环境数据量太大势必会有性能问题;
2)zipkin 关于海量日志数据如何保存, 还需在后期研究一下。