微服务之--spring-cloud-sleuth

相信在看sleuth的小伙伴们肯定已经对微服务有一个大致的了解了,因此这篇文章会主要记录spring-cloud-sleuth,以及在使用过程当中用到的EurekaFeign

调用链

随着服务的拆分,系统的模块变得越来越多,不同的微服务可能是不同的人来维护,也就造成了当我们请求某一个微服务的某一个接口时,可能是调用了多个微服务。基于Google Dapper论文,用户每次请求都会生成一个全局ID(traceId),通过它将不同系统的“孤立”的日志串在一起,重组成调用链。
简单的说调用链就是当我们发起某个请求后,调用的微服务的先后顺序组成的一个调用链路。

记录调用链的益处

一个请求可能会涉及n多个微服务的协同处理,牵扯到多人甚至多个团队的业务系统,一旦中间某个环节出现问题,还得从头查起。如何可以快速的定位到到底是哪个微服务出现的问题,咱们记录下来的调用链既可以帮助我们进行快速的分析。
除此之外还可以根据此分析各个调用环节的性能问题和数据分析(调用链是每次请求的一条完整的业务日志,可以得到用户的行为路径,汇总分析应用的很多业务场景)等。

由于我们今天的主角实际上就是对Zipkin的封装,因此我们先来看下Zipkin的介绍:

Zipkin的设计背景

2010年谷歌发表了其内部使用的分布式跟踪系统Dapper的论文,讲述了Dapper在谷歌内部两年的演变和设计、运维经验,Twitter也根据该论文开发了自己的分布式跟踪系统Zipkin,并将其开源。

Zipkin的设计
图片.png

上图为zipkin官网中的结构图,从上图我们可以看出数据是由各个应用,中间件甚至是数据库将跟踪数据发送到Zipkin服务器,而不是各个服务记录后,当一条调用链路结束后统一发送到Zipkin服务器的,这样由Zipkin来分析汇总变成调用链的好处是可以防止某一次请求调用服务特别多或者很复杂的情况下统一发送造成的性能问题。

简单的服务调用实例
图片.png

上图描述的服务调用场景应该是很常见也很简单的调用场景了,一个请求通过Gateway服务路由到下游的Service1,然后Service1先调用服务Service2,拿到结果后再调用服务Service3,最后组合Service2和Service3服务的结果,通过Gateway返回给用户。我们用①②③④⑤⑥表示了调用的顺序,什么是span?span直译过来是"跨度",在谷歌的Dapper论文中表示跟踪树中树节点引用的数据结构体,span是跟踪系统中的基本数据单元,Dapper的论文中,并没有具体介绍span中的全部细节,但在Zipkin中,每个span中一般包含如下字段:
traceId:全局跟踪ID,用它来标记一次完整服务调用,所以和一次服务调用相关的span中的traceId都是相同的,Zipkin将具有相同traceId的span组装成跟踪树来直观的将调用链路图展现在我们面前。这里直接给出Zipkin官网中的一张Zipkin界面的图:
id:span的id,理论上来说,span的id只要做到一个traceId下唯一就可以,比如说阿里的鹰眼系统巧妙用span的id来体现调用层次关系(例如0,0.1,0.2,0.1.1等),但Zipkin中的span的id则没有什么实际含义。
parentId:父span的id,调用有层级关系,所以span作为调用节点的存储结构,也有层级关系,就像上图所示,跟踪链是采用跟踪树的形式来展现的,树的根节点就是调用的顶点,从开发者的角度来说,顶级span是从接入了Zipkin的应用中最先接触到服务调用的应用中采集的。所以,顶级span是没有parentId字段的,拿上图所展现的例子来说,顶级span由Gateway来采集,Service1的span是它的子span,而Service2和Service3的span是Service1的span的子span,很显然Service2和Service3的span是平级关系。
name:span的名称,主要用于在界面上展示,一般是接口方法名,name的作用是让人知道它是哪里采集的span,不然某个span耗时高我都不知道是哪个服务节点耗时高。
timestamp:span创建时的时间戳,用来记录采集的时刻。
duration:持续时间,即span的创建到span完成最终的采集所经历的时间,除去span自己逻辑处理的时间,该时间段可以理解成对于该跟踪埋点来说服务调用的总耗时。
annotations:基本标注列表,一个标注可以理解成span生命周期中重要时刻的数据快照,比如一个标注中一般包含发生时刻(timestamp)、事件类型(value)、端点(endpoint)等信息,这里给出一个标注的json结构:
{
"timestamp": 1476197069680000,
"value": "cs",
"endpoint": {
"serviceName": "service1",
"ipv4": "xxx.xxx.xxx.111"
}
}
四种事件类型:cs(客户端/消费者发起请求)、cr(客户端/消费者接收到应答)、sr(服务端/生产者接收到请求)和ss(服务端/生产者发送应答)。可以看出,这四种事件类型的统计都应该是Zipkin提供客户端来做的,因为这些事件和业务无关,这也是为什么跟踪数据的采集适合放到中间件或者公共库来做的原因。
binaryAnnotations:业务标注列表,如果某些跟踪埋点需要带上部分业务数据(比如url地址、返回码和异常信息等),可以将需要的数据以键值对的形式放入到这个字段中。

接下来进入正题,记录一下spring-cloud-sleuth在工程中的使用:
1.首先创建一个zipkinserver工程,在pom文件中添加如下依赖:

        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-server</artifactId>
        </dependency>

        <dependency>
            <groupId>io.zipkin.java</groupId>
            <artifactId>zipkin-autoconfigure-ui</artifactId>
        </dependency>
//这个是用来自动适配版本的
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Camden.SR6</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

在配置文件中添加如下配置:

server.port=32071
spring.application.name=zipkinService 
#设置采样率,测试时可将此设置为1,表示每条请求都记录下来
spring.sleuth.sampler.percentage=0.2

在主程序入口处添加注解@EnableZipkinServer
2.创建一个helloserver工程,在pom文件中添加如下依赖:

       <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-sleuth-zipkin</artifactId>
        </dependency>
    

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

在配置文件中添加如下配置:

server.port=8988
spring.zipkin.base-url=http://localhost:32071
spring.application.name=service-hello

新建一个接口类,并实现如下方法:

@RestController
@RequestMapping("/servicehello")
public class controller
{
    private static final Logger LOG = Logger.getLogger(controller.class.getName());


    @Autowired
    private RestTemplate restTemplate;

    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

    @RequestMapping("/hello")
    public String callHome(){
        LOG.log(Level.INFO, "calling trace service-hi  ");
        return restTemplate.getForObject("http://localhost:8989/servicehi/hi", String.class);
    }
    @RequestMapping("/test")
    public String info(){
        LOG.log(Level.INFO, "calling trace service-hello ");

        return "i'm service-hello";
    }
    @Bean
    public AlwaysSampler defaultSampler(){
        return new AlwaysSampler();
    }
}

3.创建hiserver工程,在pom文件中添加和2中相同
在配置文件中设置如下:

server.port=8989
spring.zipkin.base-url=http://localhost:32071
spring.application.name=service-hi

添加请求类并实现如下方法:

@RestController
@RequestMapping("/servicehi")
public class controller {
    private static final Logger LOG = Logger.getLogger(ServiceHiApplication.class.getName());


    @Autowired
    private RestTemplate restTemplate;

    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

    @RequestMapping("/hi")
    public String callHome(){
        LOG.log(Level.INFO, "calling trace service-hello  ");
        return restTemplate.getForObject("http://localhost:8988/servicehello/test", String.class);
//        LOG.log(Level.INFO, "calling trace service-new  ");
//        return restTemplate.getForObject("http://localhost:32061/Designer/getPromotionlist/?orgid=283", String.class);
    }
    @RequestMapping("/info")
    public String info(){
        LOG.log(Level.INFO, "calling trace service-hi ");

        return "i'm service-hi";
    }

    @Bean
    public AlwaysSampler defaultSampler(){
        return new AlwaysSampler();
    }

}

依次运行这三个项目,然后访问接口如下结果:

CUF891OOFH@3VO8%D7EQ$KH.png

访问依次这个接口后我们可以看到localhost:32071的页面变成如下样子:

K}SO)@O%CR)R)700V6K_KEJ.png

点击进入详情页:

{5({JH0KDLI)EW_DOYWQHLT.png

如上图可以清晰的看到到底是哪个服务的哪个方法调用的哪个服务的哪个方法。接下来咱们看一下依赖关系(直接点击上面图中最上面一行的Dependentices):

依赖关系.png

上面的例子主要是通过RestTemplate来调用其他服务的,接下来我们看一下如果使用Feign来调用其他服务怎么使用spring-cloud-sleuth来监控器调用过程。

接下来我们使用feign来调用其他服务

修改原来的hiserver,zipkinserver,工程,并新建spring-cloud-eureka,spring-cloud-feign工程。
1.新建spring-cloud-eureka工程
在pom文件中添加依赖如下:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

在配置文件中添加如下配置(application.properties):

server.port=11111
eureka.client.service-url.defaultZone:http://localhost:11111/eureka/
spring.application.name=eureka-server

在程序入口文件中添加注解@EnableEurekaServer后运行该项目后可看到界面如下:

eureka.png

2.新建spring-cloud-feign工程,并在pom文件中添加如下依赖:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-sleuth-zipkin</artifactId>
        </dependency>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

然后在配置文件中增加如下配置:

server.port=11112
#配置eureka地址
eureka.client.service-url.defaultZone:http://localhost:11111/eureka/
spring.application.name=feign-server
#配置zipkin地址
spring.zipkin.base-url=http://localhost:32071
#配置采样率
spring.sleuth.sampler.percentage=1.0

创建一个接口类如下:

//此注解后面的value值是想要调用的服务的名称,也就是在配置文件中的spring.application.name
@FeignClient(value = "service-hi")
public interface schedualservicehi {
//此value后面是你想调用微服务的接口地址
    @RequestMapping(value = "/servicehi/info",method = RequestMethod.GET)
//此接口相当于中间层,通过访问此接口便可以访问到上面配置的服务路径
    String sayHiFromClientOne();
}

接下来创建一个类,在此类中测试方法调用

@RestController
public class controller {
    @Autowired
    schedualservicehi schedualservicehi;
    @RequestMapping(value = "/feign",method = RequestMethod.GET)
    public String sayInfo(){
        return schedualservicehi.sayHiFromClientOne();
    }

最后在工程入口文件中添加注解如下:

@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication

public class SpringcloudfeignApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudfeignApplication.class, args);
    }
}

3.在hiserver工程中做如下修改:
pom文件中除了之前添加的有关zipkin的依赖外再添加如下:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

配置文件中再增加如下:

server.port=8989
spring.zipkin.base-url=http://localhost:32071
spring.application.name=service-hi
eureka.client.service-url.defaultZone:http://localhost:11111/eureka/
spring.sleuth.sampler.percentage=1.0

工程入口文件中添加注解

@SpringBootApplication
@EnableEurekaClient
public class ServiceHiApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceHiApplication.class, args);
    }
}

其他的不用刚修改,再次运行这几个工程:
访问feign的接口:

feign.png

然后再看ereuka的界面,发现其他项目也已经注册上

eureka.png

再来看一下sleuth的界面出现了刚才咱们访问feign的那个接口的记录:

调用链.png

点击一条记录看一下依赖关系:

依赖关系 (2).png

以上结果均正确,说明sleuth可以监控到使用feign来调用服务的调用链路

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容