挑战!一个人开发Spring Cloud Alibaba微服务(1)SkyWalking实现Dubbo全链路追踪

下一篇:使用Nacos作为分布式配置中心

以前那个Spring Cloud Alibaba系列写的简直太差劲了,项目的依赖乱七八糟,很多无用的依赖也没有去掉。

正好最近参加一个比赛,要写一个比较小型的微服务项目,正好借此机会重新学习Spring Cloud Alibaba,并且在这里记录一下自己遇到的坑。

可能你要问了:为啥这么想不开,要一个人开发一套微服务系统呢?

原因是:项目定位(我们队设计的项目是在有第一群用户时峰值QPS就会达到2000+而且随着用户的增加日均流量和QPS会增长得非常快,而且未来将会不断扩展、不断接入新功能的大型Web应用。架构的横向扩展能力和伸缩性必须非常高)。这就决定了微服务架构是最适合这个项目的选择,而且核心业务不是很复杂,仔细想了想服务提供者+服务消费者大概也就10~20个,不是很多,在众多微服务架构的系统中也算是小型的吧,而且时间比较宽裕且不要求100%完成,一个人开发没有特别大的问题。

其实还是全队暂时只有我可以胜任开发与整合这套微服务系统一些基础组件(如缓存,SSO,中间件整合等)的工作,所以……

因为是比赛用的项目,所以暂时不放出所有代码,等比赛结束后再把代码上传到GitHub供大家学习和参考。

技术栈(随时更新)

  • 架构:微服务架构,前后端分离
  • 服务风格:对内RPC,对外RESTful
  • Java版本:1.8(本来用的是11,结果SkyWalking探针在Java11下居然不能正常工作,其他框架也出现了或多或少的问题,放弃)
  • 微服务框架:Spring Cloud
  • Web框架:Spring Boot
  • RPC框架:Dubbo
  • ORM框架:SpringDataMongoDB
  • 数据库:MongoDB
  • 缓存数据库:Redis
  • 消息队列:Kafka
  • 容器化引擎:Docker
  • 服务注册与发现中心/服务配置中心:Nacos
  • 服务监控:Spring Boot Admin
  • 断路器:Sentinel
  • 服务链路追踪:Skywalking
  • 微服务网关:Spring Cloud Gateway
  • 认证鉴权机制:Spring Cloud Security + OAuth2.0 + JWT
  • 日志采集与分析:ELK

搭建Demo项目

为了避免一上来就踩坑,影响到项目进度,我决定先建立一个Demo项目,把要选择的框架都试一遍,再以这个Demo项目为基准开发其他基础框架和业务逻辑。其实现参考了上次写的那一个——传入一个Message,返回一个包含端口的字符串。

先看一下效果吧:

image
image

父级依赖管理项目

还是使用一个父级项目来管理所有微服务的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
    </parent>

    <groupId>com.timeline</groupId>
    <artifactId>timeline-web</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>

        <!-- 项目依赖版本管理 -->
        <!-- Spring Cloud 采用最新的H(Hoxton)版 -->
        <spring.cloud.version>Hoxton.RELEASE</spring.cloud.version>

        <!-- Spring Cloud Alibaba 采用最新的2.2.0版本 -->
        <spring.cloud.alibaba.version>2.2.0.RELEASE</spring.cloud.alibaba.version>

        <!-- Spring Boot Admin 采用最新的2.2.2版本,以适配项目使用的 Spring Boot 2.2.5版本 -->
        <spring.boot.admin.version>2.2.2</spring.boot.admin.version>
    </properties>

    <modules>
        <module>timeline-api</module>
        <module>timeline-provider</module>
        <module>timeline-consumer</module>
        <module>timeline-gateway</module>
        <module>timeline-admin-server</module>
    </modules>

    <dependencyManagement>
        <dependencies>
            <!-- Spring Cloud -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Spring Cloud Alibaba -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring.cloud.alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- 公共API模块 -->
            <dependency>
                <groupId>com.timeline</groupId>
                <artifactId>timeline-api</artifactId>
                <version>${project.version}</version>
            </dependency>

            <!-- Spring Boot Admin -->
            <!-- Server -->
            <dependency>
                <groupId>de.codecentric</groupId>
                <artifactId>spring-boot-admin-starter-server</artifactId>
                <version>${spring.boot.admin.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

相比之前那个,删除了所有可以被代替或者被包含的依赖。综合算下来,其实只有Spring CloudSpring Cloud Alibaba的父级依赖和Spring Boot Admin

其实Spring Boot Admin的依赖可以单独放在相应的模块中,但是为了统一管理依赖版本方便,就放在父级pom.xml里了。

服务提供者

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.timeline</groupId>
        <artifactId>timeline-web</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>timeline-provider</artifactId>

    <dependencies>
        <!-- 公共API模块 -->
        <dependency>
            <groupId>com.timeline</groupId>
            <artifactId>timeline-api</artifactId>
        </dependency>

        <!-- Dubbo -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-dubbo</artifactId>
        </dependency>

        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- Nacos Discovery -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- Nacos Config -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <!-- Alibaba Spring Context Support -->
        <dependency>
            <groupId>com.alibaba.spring</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
    </dependencies>

</project>

解释一下这里为什么没有spring-boot-admin-starter-client

除了使用Spring Boot Admin Client能把微服务注册到Spring Boot Admin Server之外,Spring Boot Admin在和Spring Cloud结合使用时,Spring Boot Admin Server可以使用每个微服务的服务注册信息来注册和管理服务,就不需要再使用Client了,再使用Client就会把一个实例识别成两个了。服务启动后可以在控制台看到,服务向Spring Boot Admin注册使用的是向Nacos注册的元数据:

image

Spring Boot Admin Server的实现很简单,就是一个注解的事,网上也有很多教程,就不放出来了。

(搞不懂为什么要这么实现,跟Nacos和Consul一样做成独立部署、对应用无侵入的不好嘛?)

bootstrap.yml:

server:
  port: 10222

spring:
  application:
    name: timeline-provider
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        # 注册到TIMELINE_DEMO分组
        group: TIMELINE_DEMO
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yml
  main:
    allow-bean-definition-overriding: true

# 服务健康检查
management:
  endpoints:
    web:
      exposure:
        include: "*"
dubbo:
  registry:
    address: spring-cloud://localhost:8848
  scan:
    base-packages: com.timeline.provider.service
  # 使用dubbo协议,端口从20880开始自增以防重复
  protocol:
    name: dubbo
    port: -1
  cloud:
    subscribed-services: "*"

timeline:
  value: 2

包含了Nacos的一些信息和Dubbo的信息。其实有一部分配置可以迁移到Nacos Config中,这样方便统一管理,比如Dubbo的部分配置。咱们之后再实现这个。

业务代码与上一个差不多,想必用过Dubbo的各位大佬也很容易看出来是怎么实现的,就不赘述了。

服务消费者

pom.xml与服务提供者一模一样,就是artifactId不同。

bootstrap.yml:

server:
  port: 10200

spring:
  application:
    name: timeline-consumer
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        # 注册到TIMELINE_DEMO分组
        group: TIMELINE_DEMO
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yml
  main:
    allow-bean-definition-overriding: true

# 服务健康检查
management:
  endpoints:
    web:
      exposure:
        include: "*"
dubbo:
  registry:
    # 注册到 Spring Cloud 注册中心(Nacos)
    address: spring-cloud://localhost:8848
  scan:
    base-packages: com.timeline.consumer.service
  # 使用dubbo协议,端口号为-1表示从20880开始自增端口,以防重复
  protocol:
    name: dubbo
    port: -1
  cloud:
    # 订阅 timeline-provider 服务提供者
    subscribed-services: timeline-provider

主要的不同点就是subscribed-services,声明了订阅timeline-provider

微服务网关

据说这个东西在真正的微服务系统中不常用,好像是因为像网关这样流量大的中心节点很容易全部挂掉而导致单点故障。但是这次为了系统的完善和前端调用方便,我们还是把它实现一下吧,反正又不会真的上线啦。

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>timeline-web</artifactId>
        <groupId>com.timeline</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>timeline-gateway</artifactId>

    <dependencies>
        <!-- Spring Cloud Gateway -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!-- Nacos Discovery -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>

</project>

bootstrap.yml:

server:
  port: 10100
spring:
  application:
    name: timeline-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        # 注册到TIMELINE_DEMO分组
        group: TIMELINE_DEMO
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
      routes:
        - id: timeline-consumer
          # lb表示开启负载均衡,timeline-consumer是应用名称
          uri: lb://timeline-consumer
          predicates:
            - Path=/timeline/**

management:
  endpoints:
    web:
      exposure:
        include: "*"

主要是配置了路由信息,将所有/timeline/开头的请求都转发到timeline-consumer里去。

同样,有很多配置是可以移动到Nacos Config中的。

使用docker-compose搭建SkyWalking环境

“链路追踪”是微服务中比较重要的一个组成部分,但是在用过了CatZipkin之后,我发现还是SkyWalking最好:

  1. SkyWalking使用javaagent埋点,对应用无任何侵入。
  2. Zipkin虽然能与Spring Cloud无缝集成,但是在Dubbo当前版本2.7.4.1上不能正常使用,按照能找到的资料里的方法进行配置,会导致服务消费者无法启动(可能是我使用的方法不对?)。
  3. Cat虽然表现比Zipkin好一些,但是与Spring Cloud配合不佳、参考资料太少。
  4. SkyWalking生成的报表比较丰富,对服务状态、调用状态的展示比较清楚。
  5. 据说跟Cat一样是国人开发的,666666!

docker-compose启动单机SkyWalking

单独搭建SkyWalking比较繁琐,所以还是使用docker来搭建。

SkyWalking官方已经给出了docker-compose文件供我们方便地搭建环境,我们使用官方的文件就可以了。GitHub地址:https://github.com/apache/skywalking-docker

具体步骤也很简单:

git clone git@github.com:apache/skywalking-docker.git    # 需要配置SSH
cd skywalking-docker/6/6.6/compose-es7
docker-compose -f docker-compose.yml up -d

启动完毕后就可以在localhost:8080看到SkyWalking控制台了。

在IDEA中部署SkyWalking应用探针

打开右上角的Edit Configurations...,在每个服务的VM options里都加上这么一段:

-javaagent:path to/skywalking-agent.jar
-Dskywalking.agent.service_name=你的应用名称
-Dskywalking.collector.backend_service=SkyWalking OAP的地址,我这里是localhost:11800

例如:

image

然后启动应用,可以发现控制台输出了SkyWalking Agent相关的内容:

image

使用SkyWalking监测服务调用

回到浏览器中刷新几次,就可以在SkyWalking中看到服务调用的信息了(可能因为我电脑比较渣,导致它反应比较慢,如果没反应可以等一会):

image

可以发现,除了我们直接进行的HTTP调用,Dubbo的RPC调用也可以正常被检测到。不过为啥Spring Cloud Gateway没被写进来呢(我调用的时候是通过了Gateway的)?

SkyWalking还有一个很有意思的功能:拓扑图。因为SkyWalking把健康检测也算成用户对服务的调用了,所以Usertimeline-provider之间也有线,但实际上是没有直接调用的。这里服务太少了,这个图看起来没啥意思,不知为何Spring Cloud Gateway没有检测到(虽然其实它是不是被检测好像并没有太大的意义,因为Gateway一旦挂了,那无论请求什么都是500了):

image

踩坑记录

坑1:项目启动时抛出"Connection Refused"异常

描述:在启动项目时,莫名其妙地抛出了Conncetion Refused的异常,但是功能一切正常。

解决:这是Dubbo一些莫名其妙的默认行为的问题。通过断点调试后,发现如果不配置dubbo.registry.address: spring-cloud://localhost,在读取配置的时候就会自动加上这一条,并且端口指明为9090(咱也不知道为啥要这么干,在我印象中除了Eureka默认是需要自己指定以外,好像没有什么注册中心默认端口是9090),然后在启动时会自动连接localhost:9090。因为我们的注册中心是跑在8848端口上的,9090没有任何东西,所以自然会Connection Refused。解决方法就是配置dubbo.registry.address: spring-cloud://localhost:8848,如下图。这样配置好就可以了。

并且,由于这样配置的含义是“把Dubbo的注册信息挂载到Spring Cloud的注册中心(在这里就是Nacos)”,所以Nacos服务列表里显示的信息就不包含Dubbo的元数据了,更加精简。

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

推荐阅读更多精彩内容