目前很多业务使用微服务架构,服务模块划分有这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
4.0.0 pom pratices-demo-consumer pratices-demo-provider pratices-demo-provider-core pratices-demo-consumer-core pratices-demo-web pratices-demo-trace org.springframework.boot spring-boot-starter-parent 2.1.4.RELEASE com.cn.dl pratices-demo 0.0.1-SNAPSHOT pratices-demo Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-autoconfigure com.alibaba dubbo 2.6.0 org.springframework spring ch.qos.logback logback-core ch.qos.logback logback-access ch.qos.logback logback-classic org.slf4j slf4j-api org.apache.zookeeper zookeeper 3.4.10 com.101tec zkclient 0.10 slf4j-log4j12 org.slf4j org.springframework.boot spring-boot-maven-plugin spring-milestones Spring Milestones https://repo.spring.io/milestone spring-snapshots Spring Snapshots https://repo.spring.io/snapshot true spring-milestones Spring Milestones https://repo.spring.io/milestone spring-snapshots Spring Snapshots https://repo.spring.io/snapshot true
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 { Mapat = 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 { Mapat = 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 { Mapat = 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) { FilterRegistrationBeanregistration = 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
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。