- 实例调用关系 几乎是网状
- APM application performance management
- 帮助理解系统的行为
- 分析 性能
- 快速定位
- 谷歌的dapper 如下要求
- 性能低损耗
- 对应透明 (非侵入的方式 实现追踪)
- 可伸缩性
- 跟踪数据 可视化 和 迅速反应
- 持续监控
- Spring cloud sleuth 分布式调用链 解决方案
- 借用了:Dapper,Zipkin,HTrace
术语
- span
- 发一次 RPC 就是 一个 新的 Span
- 通过 一个 64 位的ID 标识
- 描述
- 事件 时间戳
- 标签
- 调用它的 Span的 ID
- 处理器ID(IP地址)
- 第一个span 为 root span,ID 和 trace 的 ID 值一样
- Trace
- span 组成的树状 结构
- 即:一次调用请求
- annotation
- 标注,事件的实时状态
- 有如下几种状态
- cs client sent 客户端发起请求,表示一个span的开始
- sr server received 服务方收到,减去 cs 就是 网络延迟
- ss server sent 请求处理完成,相应数据给客户端。 减去 sr 的 时间,就是 服务方 处理时间。
- cr client received 客户端收到服务方的返回值。span结束。减去 cs 就是 一次请求的 完整时间。
sleuth 原理
- sleuth 通过 trace 定义一次业务调用链
brave 和 zipkin
brave
-
brave 捕捉 分布式系统 之间 调用信息的工具库
-
将 这些信息 以,span 的形式发送给zipkin
-
2.0.0 sleuth 用 brave
-
spring.sleuth.http.legacy.enabled=true ,用以前的 自己存储,上下文。
zipkin
- 基于 google dapper 论文,设计的分布式 跟踪系统
- 官网下载,java -jar zipkin.jar
- docker 镜像启动
- 默认端口 9411
sleuth 基本用法
父类依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
sleuth-consumer
-
继承父类依赖
-
@EnableFeignClients //开始 clients
配置类
/**
* 配置类, 用于注册RestTemplate和ExecutorService;
*/
@Configuration
public class ConsumerConfiguration {
@Autowired
BeanFactory beanFactory;
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean // 简单起见, 我们注册固定大小的线程池
public ExecutorService executorService(){
ExecutorService executorService = Executors.newFixedThreadPool(2);
return new TraceableExecutorService(this.beanFactory, executorService);
}
}
controller
/**
* 用户交互接口,分别使用feign,restTemplate,新线程的方式请求服务端
*/
@RestController
public class ConsumerController {
private static final Logger log = LoggerFactory.getLogger(ConsumerController.class);
@Autowired
private HelloService helloService;
@Autowired
private RestTemplate restTemplate;
@Autowired
private ExecutorService executorService;
@GetMapping("/helloByFeign")
public String helloByFeign(String name){
log.info("client sent. Feign 方式, 参数: {}",name);
String result = helloService.sayHello(name);
log.info("client received. Feign 方式, 结果: {}",result);
return result;
}
@GetMapping("/helloByRestTemplate")
public String helloByRestTemplate(String name){
log.info("client sent. RestTemplate方式, 参数: {}",name);
String url = "http://localhost:8082/sayHello?name="+name;
String result = restTemplate.getForObject(url,String.class);
log.info("client received. RestTemplate方式, 结果: {}",result);
return result;
}
@GetMapping("/helloByNewThread")
public String hello(String name) throws ExecutionException, InterruptedException {
log.info("client sent. 子线程方式, 参数: {}",name);
Future future = executorService.submit(() -> {
log.info("client sent. 进入子线程, 参数: {}",name);
String result = helloService.sayHello(name);
return result;
});
String result = (String) future.get();
log.info("client received. 返回主线程, 结果: {}",result);
return result;
}
}
service
@FeignClient(name = "sleuth-provider",url = "localhost:8082")
public interface HelloService {
@RequestMapping("/sayHello")
String sayHello(@RequestParam("name")String name);
}
sleuth-provider
/**
* sleuth-provider 对外服务接口
*/
@RestController
public class ProviderController {
private static final Logger log = LoggerFactory.getLogger(ProviderController.class);
@GetMapping("/sayHello")
public String hello(String name){
log.info("server received. 参数: {}",name);
String result = "hello, "+name;
log.info("server sent. 结果: {}",result);
return result;
}
}
测试
http://localhost:8081/helloByFeign?name=scbook
consumer开始:
INFO [sleuth-consumer,651661de95df4429,651661de95df4429,false] 10364 --- [io-8081-exec-10] : client sent. Feign 方式, 参数: scbook
执行完了,provider,在打印:
INFO [sleuth-consumer,651661de95df4429,651661de95df4429,false] 10364 --- [io-8081-exec-10] : client received. Feign 方式, 结果: hello, scbook
consumer的 所有id 都一样
和 provider 的 第一个ID 都一样。第二个ID,不相同的。
到provider:
INFO [sleuth-provider,651661de95df4429,a3cc62191b8839c4,false] 18056 --- [nio-8082-exec-6] : server received. 参数: scbook
INFO [sleuth-provider,651661de95df4429,a3cc62191b8839c4,false] 18056 --- [nio-8082-exec-6] : server sent. 结果: hello, scbook
http://localhost:8081/helloByRestTemplate?name=scbook
INFO [sleuth-consumer,b66764c5f91dd8cc,b66764c5f91dd8cc,false] 10364 --- [nio-8081-exec-9] : client sent. RestTemplate方式, 参数: scbook
INFO [sleuth-consumer,b66764c5f91dd8cc,b66764c5f91dd8cc,false] 10364 --- [nio-8081-exec-9] : client received. RestTemplate方式, 结果: hello, scbook
和 上面 一样
INFO [sleuth-provider,b66764c5f91dd8cc,0d8c8fe9766b3952,false] 18056 --- [nio-8082-exec-2] : server received. 参数: scbook
INFO [sleuth-provider,b66764c5f91dd8cc,0d8c8fe9766b3952,false] 18056 --- [nio-8082-exec-2] : server sent. 结果: hello, scbook
http://localhost:8081/helloByNewThread?name=scbook
INFO [sleuth-consumer,ac36be5453a81654,ac36be5453a81654,false] 10364 --- [nio-8081-exec-9] : client sent. 子线程方式, 参数: scbook
INFO [sleuth-consumer,ac36be5453a81654,dd5fe5432ba673e1,false] 10364 --- [pool-1-thread-1] : client sent. 进入子线程, 参数: scbook
//两个 主线程 ,的 ID 都是一样的。
INFO [sleuth-consumer,ac36be5453a81654,ac36be5453a81654,false] 10364 --- [nio-8081-exec-9] : client received. 返回主线程, 结果: hello, scbook
//子线程的第一个ID,和调用它的ID 一样
//provider 也是这样
INFO [sleuth-provider,ac36be5453a81654,f9ec233ed160cf54,false] 18056 --- [nio-8082-exec-8] : server received. 参数: scbook
INFO [sleuth-provider,ac36be5453a81654,f9ec233ed160cf54,false] 18056 --- [nio-8082-exec-8] : server sent. 结果: hello, scbook
sleuth 对 feign支持
- sleuth 使用 TracingFeignClient 实现 feign 接口
- 在 执行 http 调用前,在 header中 添加了 Span 信息
@Override
public Response execute(Request request, Request.Options options)
throws IOException {
Map<String, Collection<String>> headers = new HashMap<>(request.headers());
Span span = handleSend(headers, request, null);
if (log.isDebugEnabled()) {
log.debug("Handled send of " + span);
}
Response response = null;
Throwable error = null;
try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) {
return response = this.delegate.execute(modifiedRequest(request, headers), options);
}
catch (IOException | RuntimeException | Error e) {
error = e;
throw e;
}
finally {
handleReceive(span, response, error);
if (log.isDebugEnabled()) {
log.debug("Handled receive of " + span);
}
}
}
sleuth 对 restTemplate的支持
- 临时初始化的 RestTemplate 实例 ,不行。
- 必须注册成 一个bean,我们定义的拦截器才会生效。
@Override public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
Span span = handler.handleSend(injector, request.getHeaders(), request);
ClientHttpResponse response = null;
Throwable error = null;
try (Tracer.SpanInScope ws = tracer.withSpanInScope(span)) {
return response = execution.execute(request, body);
} catch (IOException | RuntimeException | Error e) {
error = e;
throw e;
} finally {
handler.handleReceive(response, error, span);
}
}
sleuth 对 多线程的支持
三种实现
- LazyTrace Executor
- Traceable Executor Service
- Traceable Scheduled Executor Service
sleuth 深入用法
- sleuth 通过 brave 的 tracing 达到 获取 span 信息的 目的
bag gage
- 存储在 span的 上下文的 一组 key/value 键值对
- 中文: 行李
- 一些信息 像行李一样,挂在 sleuth 中
- 沿着 调用链路 一路 往下传递
案例
- consumer 自定义 filter 获取 前端传递的 session id ,放入bag gage
- 通过 feign 传递给 provider
父类pom
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency> #sleuth
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency> #openfeign
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> #web
</dependencies>
consumer
自定义 session Filter
- 将 session 放入 span中
/**
* 自定义过滤器,
* 获取当前的SessionId, 放入Baggage中
* 注意, 因为不是所有的请求都需要往后传递, 所以会对一些请求跳过执行
*/
@Component
@Order(TraceWebServletAutoConfiguration.TRACING_FILTER_ORDER + 1)//在这个类之后 初始化
public class SessionFilter extends GenericFilterBean {//继承 通用filter
//创建 pattern,比如:/actuator.* 等
private Pattern skipPattern = Pattern.compile(SleuthWebProperties.DEFAULT_SKIP_PATTERN);
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
//如果不是 HttpServletRequest 或 HttpServletResponse 扔异常
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
throw new ServletException("Filter just supports HTTP requests");
}
//强转
HttpServletRequest httpRequest = (HttpServletRequest) request;
boolean skip = skipPattern.matcher(httpRequest.getRequestURI()).matches();
//如果 不是 /actuator.*,才添加 SessionId
if (!skip) {
// 将 SessionId 放到 Baggage 中
ExtraFieldPropagation.set("SessionId", httpRequest.getSession().getId());
}
//放行
filterChain.doFilter(request, response);
}
}
bootstrap.yml
server:
port: 8081
spring:
application:
name: sleuth-consumer
sleuth:
baggage-keys: # 注意, Sleuth2.0.0之后, baggage的 key 必须在这里配置才能生效
- SessionId
provider
/**
* sleuth-provider 对外服务接口
*/
@RestController
public class ProviderController {
@GetMapping("/sayHello")
public String hello(String name){
return "hello, "+name+",SessionId is "+ ExtraFieldPropagation.get("SessionId");
}
}
- http://localhost:8081/hello?name=scbook
- 返回:hello, scbook,SessionId is 050B26945475828A68CA86D38CA684E8
skywalking 概述
-
非侵入的 APM — skyWalking 和 pinpoint
-
创建于 2015年,提供分布式 追踪功能
-
5.x 开始,完整功能的 APM 系统
- 追踪
- 监控
- 诊断
-
适合:微服务,云原生,容器
主要功能
- 分布式 追踪 和 上下文 传输
- 应用 实例 服务性能 指标分析
- 根源分析
- 应用拓扑 分析
- 应用 和 服务依赖分析
- 慢服务 检测
- 性能 优化