文档参考:书名:《从程序员到架构师:大数据量、缓存、高并发、微服务、多团队协同等核心场景实战》-王伟杰
前文如下:
1.全链路日志
1.1 业务场景:这个请求到底经历了什么
当时公司的某一个业务线本来是基于自研的微服务架构,刚刚迁移到Spring Cloud。因为公司原来的微服务架构是基于ZooKeeper做注册发现的,为了复用原来的中间件,迁移到Spring Cloud后,服务的注册发现基于Spring Cloud ZooKeeper实现,不过组件方面只使用了Spring Cloud的服务间调用(Feign)。
迁移到微服务后,就得考虑日志跟踪的事情了。
之前只是简单地把日志打印到本地文件上,然后使用ELK(ElasticSearch、Logstash和Kibana)进行日志收集和分析,因此日志记录比较随意,且没有形成一个统一的规范。
没有统一的规范,在做线上问卷调查的时候,难度非常大。比如有一次碰到了某一个用户总是登录失败的问题。服务调用链路是这样的:UserAPI→AuthService→UserService在UserAPI中还能找到登录信息,因为日志里面打印出了用户名,而后根据相应的时间点可以找到线程ID,那么这个时间点的这个线程ID下所有的日志就都属于要跟踪的活动了。
但是要去AuthService查找这个请求的下一个服务的日志时就复杂了。因为同一时间点有多个服务器节点,每个节点有多个线程ID在活动,所以无法判断哪个服务器节点的哪个线程ID是用来处理UserAPI中在调查的那次请求的。
那怎么办?等没流量的时候运维人员又重试了几次,最终才定位到AuthSer vice中相应的日志。后来调查到的问题根源是,UserAPI调用AuthService的时候,有个参数因为含有特殊字符而被Tomcat自动摒弃了,导致AuthService收不到那个参数的值。
项目组商量后,决定把日志进一步规范化,于是总结了以下3点需求。
1)记录什么时候调用了缓存、MQ、ES等中间件,在哪个类的哪个方法中耗时多久。
2)记录什么时候调用了数据库,执行了什么SQL语句,耗时多久。
3)记录什么时候调用了另一个服务,服务名是什么,方法名是什么,耗时多久。
一般来说,一个请求会跨多个服务节点,针对这种情况又梳理了两条重要需求。
1)把同一个请求在全部服务中的以上所有记录进行串联,最终实现一个树状的记录。
2)设计一个基于这些基础数据的查询统计功能。
需求确定后,就需要选择一款合适的开源技术进行方案实现,这就涉及技术选型过程。
2.技术选型
在进行技术选型时,可以对照表9-1中的全链路日志中间件对比。
2.1 日志数据结构支持OpenTracing
平时日志行都是独立记录的,只能通过线程ID把它们关联起来。因此需要一个数据结构把每个请求在全部服务中的相关日志关联起来。
目前已经有一种比较通用的全链路数据格式——OpenTracing,它的标准和API是由一个开源组织Cloud Native Computing Foundation(云原生计算基金会)进行维护的.
OpenTracing通过提供一个与平台/厂商无关的API,使得开发人员能够更方便地添加(或更换)追踪系统,这样即使之前引入的全链路日志不好用,以后想换掉也是非常方便的。
接下来解释一下OpenTracing标准,它主要包含两个概念:一个是Trace,一个是Span。
从图9-3中可以看到一个客户端调用Order API的请求时经历的整个流程
(①⑩),即一个Trace;Order API调用了Produc Service的整个过程(②⑤),
这就是一个Span。每个Span代表Trace中被命名且被计时的连续性执行片段。
通过图9-3还能发现,Span中又包含了一个子Span,比如调用Product Service的过程中,Product Service会访问一次数据库(③④),这也是一个Span。因此可以得出,一个Span可以包含多个子Span,而Span与Span之间的关系就叫Reference。
2.2 支持Elasticsearch作为存储系统
诚然,因为流量大的原因,导致记录的日志数据量也很大,这就要求存储这些日志的系统必须支持海量数据且保证查询高效。最终,因为公司运维人员对Elasticsearch比较熟悉,所以提出可以使用Elasticsearch对日志进行存储。
2.3 保证日志的收集对性能无影响
当服务在记录日志时,需要确保日志的记录与收集对服务器的性能不会产生影响。比如之前调研过Pinpoint,当服务在记录日志时,Pinpoint的并发数达到一定数量时整体吞吐量少了一半,对服务器的性能影响很大,这是不能接受的。
2.4 查询统计功能的丰富程度
一般来说,查询统计功能越丰富越好,但必须首先满足一个基础功能:支持每个请求树状结构的全链路日志(如图9-4和图9-5所示),比如SkyWalking的功能就非常适用。查询统计系统除了满足基本功能以外,也要实现监控报警、指标统计等功能,以此帮助减轻二次开发的工作量。如何以最小的业务代码侵入性引入这些功能?
项目组希望日志数据的收集过程对写业务代码的人保持透明,因此,一种比较理想的解决方案是使用Java的探针,通过字节码加强的方式进行埋点。 不过,这种方式对系统性能也会产生一定影响。
而且在实际业务中,公司都会把访问数据库、Redis、MQ的代码进行封装,无法通过字节码加强的方式实现埋点,就只能尝试在封装的代码中实现,这样对开发业务代码的人来说同样透明。
2.5 最终选择
根据以上问题剖析及性能测试结果分析,可以发现SkyWalking比较符合需求。
项目组做性能测试时发现,对于500线程压力以下的服务,是否使用SkyWalking对其吞吐量影响不大,一般相差不超过10%。SkyWalking官方测试报告中也提到:假如有500个并发用户,每个用户的每次请求间隔是10毫秒,TPS基本没什么变化,如图9-6所示。
3.注意事项
3.1 SkyWalking的数据收集机制
中间件在收集日志的时候,不可能是同步的。为什么呢?如果每次记录日志都要发一个请求到中间件,等中间件返回结果以后,才算日志记录完成,进入下一个动作,那么这个请求的响应时间肯定变慢。而且这种情况下,业务系统和日志系统是耦合的,业务系统要保证绝对高可用,而日志系统只是用来为研发人员调研问题提供方便的,对可用性的要求没有那么高。也不可能让高可用的系统依赖中可用的系统。
所以这个日志收集的过程必须是异步的,和业务流程解耦。
**SkyWalking的数据收集机制是这样的:服务中有一个本地缓存,把收集的所有日志数据先存放在这个缓存中,然后后台线程通过异步的方式将缓存中的日志发送给SkyWalking服务端。**这种机制使得在日志埋点的地方无须等待服务端接收数据,也就不影响系统性能。
3.2 如果SkyWalking服务端宕机了,会出现什么情况
如果服务端宕机了,理论上日志缓存中的数据会出现没人消费的情况,这样会不会导致数据越积越多,最终超出内存呢?在SkyWalking中会设置缓存的大小,如果这部分数据超出了缓存大小,Trace不会保存,也就不会超出内存了。
3.3 流量较大时,如何控制日志的数据量
流量大时,不可能收集每个请求的日志,否则数据量会过大。那SkyWalking如何控制采样比例呢?SkyWalking会在每个服务器上配置采样比例,比如设置为100,代表1%的请求数
样就可以通过sampleRate来控制采样比例了。一般而言,流量越大,采样比例越小。 不过,这里有两点需要特别注意。
1)一旦启用forceSampleErrorSegment,出现错误时就会收集所有的数据,此时sampleRate对出错的请求不再适用。
2)所有相关联服务的sampleRate最好保持一致,如果A调用B,然后A、B的采样比例不一样,就会出现一个Trace串不起来的情况。
3.4 日志的保存时间
一般来说,日志不需要永久保存,通常是保存3个月的数据,关于这一点大家结合公司的实际情况进行配置即可。按照以前的设计方案,需要自己设计一个工具将数据进行定时清理,不过此时可以直接使用SkyWalking进行配置,代码如下所示。
3.5 集群配置:如何确保高可用
先来看看SkyWalking官方文档给出的SkyWalking架构,如图9-7所示。在此架构中,需要关注SkyWalking的收集服务(Receiver)和聚合服务(Aggregator),它们支持集群模式。同时,在集群服务里,多个服务节点又需要一些协调服务来协调服务间的关系,它们支持Kubernetes、ZooKeeper、Consul、etcd、Nacos(开源的协调服务基本都支持)。