目前很多业务使用微服务架构,服务模块划分有这2种方式:
- 服务功能划分
- 业务划分
不管哪种方式,一次接口调用都需要多个服务协同完成,其中一个服务出现问题,都会导致最终失败,虽然有logback + kafka + ELK 这样的神器架构,但是定位问题也很麻烦,如果在整个链路中,可以通过一个唯一ID(traceId)跟踪本次服务调用,就可以在ELK中查找当前traceId来定位问题。
一、案例
1、案例结构
pratices-demo-provider-core
:定义服务接口pratices-demo-provider
:具体实现pratices-demo-consumer-core
:服务消费者,同时也是服务提供者pratices-demo-consumer
:具体实现pratices-demo-web
:提供http服务pratices-demo-trace
:本案例的核心模块,在服务调用时拦截,设置traceId,跟踪本次服务调用
2、pratices-demo
2.1、pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<modules>
<module>pratices-demo-consumer</module>
<module>pratices-demo-provider</module>
<module>pratices-demo-provider-core</module>
<module>pratices-demo-consumer-core</module>
<module>pratices-demo-web</module>
<module>pratices-demo-trace</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cn.dl</groupId>
<artifactId>pratices-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>pratices-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.0</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</exclusion>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
</exclusion>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.10</version>
</dependency>
<!--Exception in thread "main" java.lang.NoClassDefFoundError: org/I0Itec/zkclient/IZkStateListener-->
<!--Caused by: java.lang.ClassNotFoundException: org.I0Itec.zkclient.IZkStateListener-->
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
<exclusions>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</pluginRepository>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
3、pratices-demo-provider-core
3.1、ProviderService
package com.cn.dl;
public interface ProviderService {
String sayHello(String name);
}
4、pratices-demo-provider
4.1、ProviderServiceImpl
package com.cn.dl.provider.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.cn.dl.ProviderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
@Service
public class ProviderServiceImpl implements ProviderService {
private static final Logger log = LoggerFactory.getLogger(ProviderServiceImpl.class);
@Override
public String sayHello(String name) {
log.info("providerServiceImpl 服务提供 traceId:{},sayHello:{}", MDC.get("traceId"),name);
return "hello " + name ;
}
}
4.2、dubbo-provider.properties 配置文件
# dubbo-provider.properties
dubbo.application.name=service2
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=50010
dubbo.consumer.timeout=5000
4.3、ProviderMain服务启动类
注意:启动dubbo服务不需要暴露http服务
package com.cn.dl;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.PropertySource;
import java.util.concurrent.locks.LockSupport;
@EnableDubbo(scanBasePackages = "com.cn.dl*")
@PropertySource("classpath:/dubbo-provider.properties")
@SpringBootApplication
public class ProviderMain{
private static final Logger log = LoggerFactory.getLogger(ProviderMain.class);
public static void main(String[] args) {
new SpringApplicationBuilder(ProviderMain.class).web(WebApplicationType.NONE).run(args);
log.info("ProviderMain 启动了");
LockSupport.park();
}
}
@EnableDubbo(scanBasePackages = "com.cn.dl*")
扫描Dubbo的服务提供者以及Dubbo的服务消费者,一定要注意@EnableDubbo和@SpringBootApplication的先后次序;
@PropertySource("classpath:/dubbo-provider.properties")
加载配置文件到上下文环境变量。
5、pratices-demo-consumer-core
5.1、ConsumerService
package com.cn.dl;
public interface ConsumerService {
String toSayHello(String name);
int getRandomInt();
}
6、pratices-demo-consumer
6.1、ConsumerServiceImpl
package com.cn.dl.consumer.impl;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.annotation.Service;
import com.cn.dl.ConsumerService;
import com.cn.dl.ProviderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.util.Random;
@Service
public class ConsumerServiceImpl implements ConsumerService {
private static final Logger log = LoggerFactory.getLogger(ConsumerServiceImpl.class);
@Reference
private ProviderService providerService;
@Override
public String toSayHello(String name) {
String sayHello = providerService.sayHello(name);
log.info("ConsumerServiceImpl >>>> traceId:{},sayHello:{}", MDC.get("traceId"),sayHello);
return sayHello;
}
@Override
public int getRandomInt() {
return new Random().nextInt(100);
}
}
6.2、dubbo-consumer.properties
dubbo.application.name=service1
dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=50020
dubbo.consumer.timeout=5000
6.3、ConsumerMain
package com.cn.dl;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.PropertySource;
import java.util.concurrent.locks.LockSupport;
@EnableDubbo(scanBasePackages = "com.cn.dl*")
@PropertySource("classpath:/dubbo-consumer.properties")
@SpringBootApplication
public class ConsumerMain {
public static void main(String[] args) {
new SpringApplicationBuilder(ConsumerMain.class).web(WebApplicationType.NONE).run(args);
LockSupport.park();
}
}
7、pratices-demo-web
7.1、WebTraceFilter
定义web拦截器,拦截所有请求,生成唯一ID
package com.cn.dl.webTrace;
import com.cn.dl.utils.TraceUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static com.cn.dl.config.TraceConfig.TRACE_ID;
public class WebTraceFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(WebTraceFilter.class);
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (! (servletRequest instanceof HttpServletRequest) || ! (servletResponse instanceof HttpServletResponse)) {
throw new ServletException("只支持http请求");
}
try {
String traceId = TraceUtil.getTraceId();
log.info("WebTraceFilter traceId:{}",traceId);
MDC.put(TRACE_ID,traceId);
filterChain.doFilter(servletRequest, servletResponse);
} finally {
MDC.remove(TRACE_ID);
}
}
}
7.2、TraceUtil
package com.cn.dl.utils;
import java.util.UUID;
public class TraceUtil {
public static String getTraceId(){
return UUID.randomUUID().toString().replace("-","");
}
public static void main(String[] args) {
System.out.println(getTraceId());
}
}
7.3、TraceConfig
package com.cn.dl.config;
public interface TraceConfig {
String TRACE_ID = "traceId";
}
7.4、RpcProviderInterceptor
package com.cn.dl.rpcTrace;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.*;
import com.cn.dl.utils.TraceUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.util.StringUtils;
import java.util.Map;
import static com.cn.dl.config.TraceConfig.TRACE_ID;
@Activate(group = Constants.PROVIDER)
public class RpcProviderInterceptor implements Filter {
private static final Logger log = LoggerFactory.getLogger(RpcProviderInterceptor.class);
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Result result;
try {
Map<String, String> at = invocation.getAttachments();
MDC.put(TRACE_ID, ! StringUtils.isEmpty(at.get(TRACE_ID)) ? at.get(TRACE_ID): TraceUtil.getTraceId());
result = invoker.invoke(invocation);
} catch (Exception e) {
log.error("RpcProviderInterceptor 异常",e);
throw e;
} finally {
MDC.remove(TRACE_ID);
}
return result;
}
}
7.5、RpcConsumerInterceptor
package com.cn.dl.rpcTrace;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.*;
import com.cn.dl.utils.TraceUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.util.Map;
import static com.cn.dl.config.TraceConfig.TRACE_ID;
@Activate(group = Constants.CONSUMER)
public class RpcConsumerInterceptor implements Filter {
private static final Logger log = LoggerFactory.getLogger(RpcProviderInterceptor.class);
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Result result;
try {
Map<String, String> at = invocation.getAttachments();
if (MDC.get(TRACE_ID) == null) {
MDC.put(TRACE_ID,TraceUtil.getTraceId());
}
at.put(TRACE_ID, MDC.get(TRACE_ID));
result = invoker.invoke(invocation);
}catch (Exception e){
log.error("RpcConsumerInterceptor 异常",e);
throw e;
}
return result;
}
}
7.6、RpcProviderInterceptor
package com.cn.dl.rpcTrace;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.Activate;
import com.alibaba.dubbo.rpc.*;
import com.cn.dl.utils.TraceUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.util.StringUtils;
import java.util.Map;
import static com.cn.dl.config.TraceConfig.TRACE_ID;
@Activate(group = Constants.PROVIDER)
public class RpcProviderInterceptor implements Filter {
private static final Logger log = LoggerFactory.getLogger(RpcProviderInterceptor.class);
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
Result result;
try {
Map<String, String> at = invocation.getAttachments();
MDC.put(TRACE_ID, ! StringUtils.isEmpty(at.get(TRACE_ID)) ? at.get(TRACE_ID): TraceUtil.getTraceId());
result = invoker.invoke(invocation);
} catch (Exception e) {
log.error("RpcProviderInterceptor 异常",e);
throw e;
} finally {
MDC.remove(TRACE_ID);
}
return result;
}
}
然后在resources下创建META-INF/dubbo/com.alibaba.dubbo.rpc.Filter,将扩展的拦截器添加到dubbo调用链中
consumerTraceFilter=com.cn.dl.rpcTrace.RpcConsumerInterceptor
providerTraceFilter=com.cn.dl.rpcTrace.RpcProviderInterceptor
8、pratices-demo-web
8.1、TraceInterceptor注册web拦截器
package com.cn.dl.config;
import com.cn.dl.webTrace.WebTraceFilter;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import javax.annotation.Resource;
import javax.servlet.Filter;
@SpringBootConfiguration
public class TraceInterceptor {
@Bean(name = "webTraceFilter")
public WebTraceFilter getWebTraceFilter(){
return new WebTraceFilter();
}
@Bean
@Resource
public FilterRegistrationBean traceFilterRegistration(Filter webTraceFilter) {
FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
registration.setFilter(webTraceFilter);
registration.addUrlPatterns("
@RestController
public class DemoWebController {
@Reference
private ConsumerService consumerService;
@PostMapping("sayHello")
public String sayHello(@RequestParam("name") String name){
return consumerService.toSayHello(name);
}
@GetMapping("getRandomInt")
public int getRandomInt(){
return consumerService.getRandomInt();
}
}
8.4、StartWeb
package com.cn.dl;
import com.alibaba.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.PropertySource;
@EnableDubbo(scanBasePackages = "com.cn.dl*")
@PropertySource("classpath:/dubbo.properties")
@SpringBootApplication
public class StartWeb {
public static void main(String[] args) {
SpringApplication.run(StartWeb.class,args);
}
}
9、分别启动providerMain、consumerMain、startWeb
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。