目前很多业务使用微服务架构,服务模块划分有这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; /** * Created by yanshao on 2019-09-04. */ 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; /** * Created by yanshao on 2019-09-04. */ @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; /** * Created by yanshao on 2019-09-04. */ @EnableDubbo(scanBasePackages = "com.cn.dl*") @PropertySource("classpath:/dubbo-provider.properties") @SpringBootApplication public class ProviderMain{ private static final Logger log = LoggerFactory.getLogger(ProviderMain.class); /** * 启动dubbo服务,不需要提供web服务,但是默认有8080端口,通过一下方式可以不暴露web服务 * * 1、在application.properties加上一下配置 * * spring: * main: * allow-bean-definition-overriding: true * web-application-type: none * * 2、修改启动类 * new SpringApplicationBuilder(ProviderMain .class) * .web(WebApplicationType.NONE) * .run(args) * */ 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; /** * Created by yanshao on 2019-09-04. */ 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; /** * Created by yanshao on 2019-09-04. */ @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; /** * Created by yanshao on 2019-09-04. */ @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; /** * Created by yanshao on 2019-09-04. */ 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; /** * Created by yanshao on 2019-09-04. */ 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; /** * Created by yanshao on 2019-09-04. */ 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; /** * Created by yanshao on 2019-09-04. */ @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; /** * Created by yanshao on 2019-09-04. */ @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; /** * Created by yanshao on 2019-09-04. */ @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; /** * Created by yanshao on 2019-09-04. */ @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("/*"); registration.setName("webTraceFilter"); registration.setOrder(1); return registration; } }
8.2、dubbo.properties
dubbo.application.name=consumer-service dubbo.registry.address=zookeeper://127.0.0.1:2181 dubbo.consumer.timeout=5000
8.3、DemoWebController
package com.cn.dl.controller; import com.alibaba.dubbo.config.annotation.Reference; import com.cn.dl.ConsumerService; import org.springframework.web.bind.annotation.*; /** * Created by yanshao on 2019-09-04. */ @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; /** * Created by yanshao on 2019-09-04. */ @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
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。