重新定义cloud 第16章 全链路监控.md

  • 实例调用关系 几乎是网状
  • 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 系统

    • 追踪
    • 监控
    • 诊断
  • 适合:微服务,云原生,容器

主要功能

  • 分布式 追踪 和 上下文 传输
  • 应用 实例 服务性能 指标分析
  • 根源分析
  • 应用拓扑 分析
  • 应用 和 服务依赖分析
  • 慢服务 检测
  • 性能 优化
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值