目的
- 在log4j2的基础上集成sleuth,进行日志调用链跟踪,进行日志分析
- 改造项目接口,将原有/task/id,改成通过gateway-->client-->server的链子,方便测试,并同时改造原有gateway(参考上篇文章zuul实战)的验签方式
简介
在Peter Deutsch的《The Eight Fallacies of Distributed Computing》中指出八个分布式计算的误区:
- 网络可靠
- 延迟为零
- 带宽无限
- 网络绝对安全
- 网络拓扑不会变
- 必须有一个管理员
- 传输成本为零
- 网络同质化
总结下上述问题,重点出在网路问题。网络常常十分脆弱,而我们部署了微服务,系统变多,网络传输增多,对我们排查问题提出了挑战。sleuth的作用就是解决这个问题,进行调用跟踪,形成调用链,方便快速找出问题所在。
sleuth有几个专业术语:
- span(跨度):一次调用链每个应用的唯一id
- trace (跟踪):一次调用链所有应用共享的唯一id
- annotation (标注):记录事件,定义请求为止:
- CS 客户端发送请求
- SR 服务端收到请求
- SS 服务处理完请求,发送返回信息
- CR 客户端收到请求
开工
- 添加sleuth依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
- log4j2配置文件中添加sleuth配置,暂时还用的STDOUT模式,打印到控制台,方便测试,通过自定义sleuth参数
[%X{X-B3-TraceId},%X{X-B3-SpanId},%X{X-B3-ParentSpanId},%X{X-Span-Export}]
进行打印,其中TraceId为此次调用链共享id,SpanId本应用唯一id,ParentSpanId为上级应用唯一id,X-Span-Export是否是发送给Zipkin(后边我们将会用到,本章不讨论)
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<File name="FileAppender" fileName="./log/logFile.log" append="true">
<PatternLayout pattern="%d %-5p %c:%L [%t] - %m%n" />
</File>
<Console name="STDOUT" target="SYSTEM_OUT">
//重点----自定义sleuth参数
<PatternLayout pattern="%d [%X{X-B3-TraceId},%X{X-B3-SpanId},%X{X-B3-ParentSpanId},%X{X-Span-Export}] %-5p %c:%L [%t] - %m%n" />
</Console>
<RollingFile name="RollingFileInfo" fileName="./log/bee-client.log"
filePattern="./log/bee-client-gz/$${date:yyyy-MM}/bee-client-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<PatternLayout pattern="%d %-5p %c:%L [%t] - %m%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="200 M"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="com.opensymphony.xwork2.ognl.OgnlValueStack" level="ERROR" />
<Logger name="open.template" level="DEBUG" />
<Logger name="org.springframework.cloud.netflix" level="DEBUG" />
<Logger name="com.alisoft.xplatform.asf" level="WARN" />
<Logger name="com.mbi" level="ERROR" />
<Logger name="net.mlw" level="INFO" />
<Logger name="java.sql" level="INFO" />
<Logger name="org.hibernate.type" level="ERROR" />
<Logger name="com.opensymphony.webwork" level="ERROR" />
<Logger name="org.apache" level="INFO" />
<Logger name="org.jgroups" level="WARN" />
<Logger name="org.jboss.axis" level="INFO" />
<Logger name="org.jboss.management" level="INFO" />
<Logger name="org.apache.commons.httpclient" level="ERROR" />
<Logger name="com.alibaba.dubbo" level="WARN" />
<Logger name="com.mchange.v2.resourcepool" level="ERROR" />
<Logger name="org.mybatis.spring" level="ERROR" />
<Logger name="org.apache.ibatis" level="ERROR" />
<Root level="INFO"><!-- 缺省日志级别,如果package有定制级别,则按package的定制级别走,即使package级别更低 -->
<AppenderRef ref="STDOUT" />
<!--<AppenderRef ref="FileAppender" />-->
<!--<AppenderRef ref="RollingFileInfo" />-->
</Root>
</Loggers>
</Configuration>
- 改造gateway、udm-client、udm-server服务,同样的方法,集成sleuth,改造日志配置,在gateway路由规则中添加udm-client配置,进行链式测试
zuul:
#配置zuul统一前缀
prefix: /api
#禁止所有eureka服务通过服务名直接访问
ignored-services:
"*"
routes:
#将微服务需要暴露服务进行路由映射
udm-server: /udm/**
udm-client: /udc/**
- 改造gateway验签,暂时替换为md5方式,首先在commons中添加md5工具类
public class Md5Sign {
public static String sign(String s) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(s.getBytes("utf-8"));
return toHex(bytes);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static String toHex(byte[] bytes) {
final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
StringBuilder ret = new StringBuilder(bytes.length * 2);
for (int i = 0; i < bytes.length; i++) {
ret.append(HEX_DIGITS[(bytes[i] >> 4) & 0x0f]);
ret.append(HEX_DIGITS[bytes[i] & 0x0f]);
}
return ret.toString();
}
public static void main(String[] args) {
StringBuffer stringBuffer=new StringBuffer();
stringBuffer.append("ddd");
stringBuffer.append("sdfadflakjsfd;aljfda;dkfja;dfk");
String sign = sign(stringBuffer.toString());
System.out.println(sign);
}
}
然后修改gateway项目中添加feign依赖,用于调用获取用户信息方法,添加对应service
//添加pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
......
//启动类添加feign扫包
@SpringBootApplication
@EnableZuulProxy
@EnableFeignClients(basePackages = "open.template.work.gateway.service")
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
......
//添加接口service
@FeignClient(name = CloudServerDirectory.UDM_SERVER)
public interface AuthorizationService {
/**
* 调用获取用户密钥
* @param appKey
* @return
*/
@RequestMapping("/user/key")
String getUserKey(String appKey);
}
最后在AuthorizationFilter中注入此AuthorizationService,并修改run方法
@Override
public Object run() {
RequestContext currentContext = RequestContext.getCurrentContext(); // 获取当前请求的上下文
Map<String, Object> params = null;
try {
//1.获取请求参数,支持json格式数据,通过inputstream解析数据
try {
params = HttpParser.requestJsonParser(currentContext.getRequest().getInputStream());
} catch (IOException e) {
throw new BaseTemplateException(ExceptionCodeEnum.Z88888);
}
LOGGER.info("获取授权参数data={}", params);
//2.通过md5验签,生产环境可以根据需求调整更复杂的算法,为了测试简单暂时通过客户端的treemap进行排序,将所有参数生成字符串,然后拼接用户密钥
//生成md5作为签名
if (params.get("appKey") != null) {
String key = authorizationService.getUserKey(params.get("appKey").toString());
if (StringUtils.isEmpty(key)) {
throw new BaseTemplateException(ExceptionCodeEnum.Z88887);
}
Object sign = params.get("sign");
if (sign == null) {
throw new BaseTemplateException(ExceptionCodeEnum.Z88888);
}
StringBuffer stringBuffer = new StringBuffer();
Set<String> paramsSet = params.keySet();
for (String param : paramsSet) {
if (!param.equals("sign")) {
stringBuffer.append(params.get(param));
}
}
//本地加签
String localSign = Md5Sign.sign(stringBuffer.append(key).toString());
if (!localSign.equals(sign)) {
throw new BaseTemplateException(ExceptionCodeEnum.Z88888);
}
}
} catch (BaseTemplateException e) {
currentContext.setSendZuulResponse(false);
currentContext.setResponseStatusCode(200);
currentContext.setResponseBody("{\"result \":\""+e.getMessage()+"\"}");
currentContext.getResponse().setContentType("text/html;charset=UTF-8");
}
return null;
}
- udm-server添加模拟获取用户密钥信息接口
@RestController
public class UserApi {
/**
* 根据appKey获取用户密钥
* @param appKey
* @return
*/
@RequestMapping("/user/key")
public String getUserKey(String appKey){
return "sdfadflakjsfd;aljfda;dkfja;dfk";
}
}
- 重启相应服务,通过curl模拟json格式post请求(因为参数中有对象参数,索所以需要requestBody注解,必须post的请求,其中sign参数通过md5多工具类生成),查看日志
curl -H "Content-Type:application/json" -X POST --data '{"appKey":"ddd","sign":"6CBC57C961FCA12D89FB724400E87326"}' http://localhost:8061/api/udm/task/get
完工!
网友评论