【微服务】springcloud集成skywalking实现全链路追踪

目录

一、前言

二、环境准备

[2.1 软件环境](#2.1 软件环境)

[2.2 微服务模块](#2.2 微服务模块)

[2.3 环境搭建](#2.3 环境搭建)

[2.3.1 下载安装包](#2.3.1 下载安装包)

[2.3.2 解压并启动服务](#2.3.2 解压并启动服务)

[2.3.3 访问web界面](#2.3.3 访问web界面)

三、搭建springcloud微服务

[3.1 顶层公共依赖](#3.1 顶层公共依赖)

[3.2 用户服务模块](#3.2 用户服务模块)

[3.2.1 准备测试使用数据库](#3.2.1 准备测试使用数据库)

[3.2.2 添加依赖](#3.2.2 添加依赖)

[3.2.3 添加配置文件](#3.2.3 添加配置文件)

[3.2.4 redis的自定义配置类](#3.2.4 redis的自定义配置类)

[3.2.5 核心业务实现类](#3.2.5 核心业务实现类)

[3.2.6 业务测试接口](#3.2.6 业务测试接口)

[3.2.7 启动类](#3.2.7 启动类)

[3.2.8 接口模拟测试](#3.2.8 接口模拟测试)

[3.3 订单服务模块](#3.3 订单服务模块)

[3.3.1 引入依赖](#3.3.1 引入依赖)

[3.3.2 添加配置文件](#3.3.2 添加配置文件)

[3.3.3 添加feign服务](#3.3.3 添加feign服务)

[3.3.4 添加测试接口](#3.3.4 添加测试接口)

[3.3.5 启动类](#3.3.5 启动类)

[3.3.6 接口模拟测试](#3.3.6 接口模拟测试)

[3.4 网关服务模块](#3.4 网关服务模块)

[3.4.1 引入依赖](#3.4.1 引入依赖)

[3.4.2 添加配置文件](#3.4.2 添加配置文件)

[3.4.2 启动类](#3.4.2 启动类)

[3.4.3 功能测试](#3.4.3 功能测试)

四、springcloud接入SkyWalking

[4.1 参数准备](#4.1 参数准备)

[4.2 接口调用](#4.2 接口调用)

[4.3 接入网关](#4.3 接入网关)

[4.3.1 问题分析](#4.3.1 问题分析)

五、写在文末


一、前言

在上一篇,详细分享了skywalking的搭建和使用,以及如何在springboot和dubbo服务中集成skywalking的详细流程。在微服务治理中,springcloud也是技术选型中的一个成熟的解决方案,而且相对dubbo来说,springcloud涉及到的微服务组件更多,调用链路可能更复杂,本文将详细介绍下如何在springcloud中集成skywalking。

二、环境准备

2.1 软件环境

本文springcloud微服务模块需要依赖的外部模块如下:

  • skywalking,监控springcloud的调用链路;
  • nacos,服务注册中心,微服务模块的互相调用也将走nacos;
  • redis,微服务模块中作为缓存使用;
  • mysql,skywalking持久化数据到mysql,以及微服务模块的业务数据存储;

2.2 微服务模块

服务模块如下:

  • gateway,微服务网关;
  • user,用户微服务模块;
  • order,订单微服务模块;

2.3 环境搭建

2.3.1 下载安装包

像mysql,redis的搭建相信很多同学都非常熟悉了,这里就不再赘述了,快速介绍下nacos的单机搭建流程,nacos下载地址:git下载地址

也可以直接在这里下载,nacos 1.4.2安装包

2.3.2 解压并启动服务

1、解压安装包
tar -zxvf nacos-server-1.4.2.tar.gz

2、进入到bin目录使用脚本启动
sh startup.sh -m standalone

2.3.3 访问web界面

服务正常启动后,可以在浏览器访问nacos的ui界面:http://IP:8848/nacos

默认登录账号和密码:nacos/nacos

三、搭建springcloud微服务

3.1 顶层公共依赖

最外层添加如下依赖

java 复制代码
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <dubbo.version>3.1.5</dubbo.version>

        <spring-cloud.version>2021.0.5</spring-cloud.version>
        <spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>

        <!-- mybatis-plus 版本 -->
        <mybatis-plus.version>3.5.2</mybatis-plus.version>
        <druid.version>1.1.17</druid.version>

        <mybatis-boot.version>2.2.2</mybatis-boot.version>

    </properties>

    <dependencyManagement>
        <dependencies>

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.2.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.1.0.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

        </dependencies>
    </dependencyManagement>

    <dependencies>

        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.23</version>
        </dependency>

        <dependency>
            <groupId>org.apache.skywalking</groupId>
            <artifactId>apm-toolkit-logback-1.x</artifactId>
            <version>8.14.0</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

3.2 用户服务模块

模块结构如下

3.2.1 准备测试使用数据库

创建一个数据库,并提前准备一张测试使用的表

sql 复制代码
CREATE TABLE `t_user` (
  `id` varchar(32) NOT NULL,
  `name` varchar(32) DEFAULT NULL,
  `email` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

添加一条测试使用的数据

sql 复制代码
INSERT INTO `db-base`.`t_user`(`id`, `name`, `email`) VALUES ('001', 'jerry', 'jerry@163.com');

3.2.2 添加依赖

用户模块的服务将会使用nacos作为注册中心,所以需要添加nacos的依赖,同时,后面的服务调用需要走统一网关,因此gateway的依赖不可少

java 复制代码
<dependencies>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- spring data redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--nacos服务发现客户端-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis-boot.version}</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>

3.2.3 添加配置文件

java 复制代码
server:
  port: 9002
spring:
  application:
    name: user-service

  cloud:
    nacos:
      discovery:
        server-addr: nacos的地址:8848

  profiles:
    active: dev # 环境标识

  datasource:
    url: jdbc:mysql://数据库连接地址:3306/db-base
    driverClassName: com.mysql.jdbc.Driver
    username: root
    password: 123456

  redis:
    host: localhost
    port: 6379

mybatis:
  mapper-locations: classpath:mapper/*.xml
  #目的是为了省略resultType里的代码量
  type-aliases-package: com.congge.entity
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3.2.4 redis的自定义配置类

接口中将会使用redis作为缓存,需要自定义对缓存的序列化

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public FastJson2JsonRedisSerializer<Object> fastJson2JsonRedisSerializer() {
        return new FastJson2JsonRedisSerializer<>(Object.class);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setHashKeySerializer(RedisSerializer.string());
        redisTemplate.setValueSerializer(fastJson2JsonRedisSerializer());
        redisTemplate.setHashValueSerializer(fastJson2JsonRedisSerializer());
        return redisTemplate;
    }
}

自定义序列化类

java 复制代码
import com.alibaba.fastjson2.JSON;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {

    public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    private Class<T> clazz;

    public FastJson2JsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t).getBytes(DEFAULT_CHARSET);
    }

    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz);
    }
}

3.2.5 核心业务实现类

在这段代码中,添加了一个根据ID查询用户详情的方法,第一次未查到将会走数据库,然后放入缓存,以后相同的请求再过来的时候,如果缓存中有数据将会走缓存

java 复制代码
import com.alibaba.fastjson2.JSON;
import com.congge.entity.User;
import com.congge.mapper.UserMapper;
import com.congge.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class UserServiceImpl  implements UserService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private UserMapper userMapper;

    @Override
    public User getById(String id) {
        log.info("[用户服务] 基于 id 查询用户信息:{}", id);
        String key = "sw:users:" + id;
        Object json = redisTemplate.opsForValue().get(key);
        if (json != null) {
            log.info("[用户服务] redis 中查询到用户信息:key={}, json={}", key, json);
            return JSON.parseObject(json.toString(), User.class);
        }

        User user = userMapper.getById(id);
        if (user != null) {
            log.info("[用户服务] redis 中不存在,从数据库查到数据并缓存:{}", user);
            redisTemplate.opsForValue().set(key, user, 2, TimeUnit.HOURS);
            return user;
        }

        log.warn("[用户服务] 基于 id 查询用户失败,用户不存在:{}", id);
        return null;
    }
}

3.2.6 业务测试接口

添加一个接口类

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    //http:localhost:9002/user/getById?id=001
    @GetMapping("/getById")
    public User getById(@RequestParam String id) {
        return userService.getById(id);
    }
}

3.2.7 启动类

java 复制代码
@MapperScan("com.congge.mapper")
@SpringBootApplication
public class UserApp {
    public static void main(String[] args) {
        SpringApplication.run(UserApp.class, args);
    }

}

3.2.8 接口模拟测试

启动用户模块的服务,然后调用查询用户的接口,可以正常查到数据库的数据

同时nacos的服务列表中,也能看到当前注册上去的用户服务信息

到这里,用户服务就基本整合完成

3.3 订单服务模块

模块结构如下

3.3.1 引入依赖

订单模块中,将会通过feign的方式调用user模块的服务,所以这里增加了feign的依赖

java 复制代码
    <dependencies>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--nacos服务发现客户端-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

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

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

    </dependencies>

3.3.2 添加配置文件

java 复制代码
server:
  port: 9003

spring:
  application:
    name: order-service

  cloud:
    nacos:
      discovery:
        server-addr: nacos的地位:8848

3.3.3 添加feign服务

order模块中对user模块的接口调用,通过下面的接口定义,然后注入到需要调用的类中即可

java 复制代码
import com.congge.entity.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(name = "user-service",path = "/user")
public interface UserFeignService {

    @GetMapping(value = "/getById")
    User getById(@RequestParam("id") String id);

}

3.3.4 添加测试接口

java 复制代码
@RequestMapping("/order")
@RestController
public class OrderController {

    @Autowired
    private OrderService orderService;

    //localhost:9003/order/getById?id=001
    @GetMapping("/getById")
    public Object getById(@RequestParam String id) {
        return orderService.getById(id);
    }
}

接口实现

java 复制代码
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private  UserFeignService userFeignService;

    @Override
    public Map getById(String id) {
        log.info("[订单服务] 基于 id 查询订单详情:{}", id);

        Map map = new HashMap();

        Order order = new Order();
        order.setOrderId("0002");
        order.setProductId("0001");
        order.setProductName("小米手机");
        map.put("order",order);

        User user = userFeignService.getById("001");
        map.put("user",user);
        return map;

    }
}

3.3.5 启动类

java 复制代码
@EnableFeignClients
@SpringBootApplication
public class OrderApp {

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

}

3.3.6 接口模拟测试

启动order模块的服务,然后调用查询订单的接口,可以看到期望的返回结果

同时在nacos的服务列表中存在两个服务信息

3.4 网关服务模块

上面分别完成了两个服务模块的搭建,测试,以及相互之间的调用,但是并没有通过网关,接下来将服务接口的调用通过网关接入

3.4.1 引入依赖

java 复制代码
    <dependencies>

        <!--gateway网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <!--nacos客户端-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

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

    </dependencies>

3.4.2 添加配置文件

网关模块的搭建,主要是配置文件中涉及的路由规则的配置,需要搞清楚规则配置中的各项含义,否则调用的时候容易出错

java 复制代码
server:
  port: 9001
spring:
  application:
    name: api-gateway

  cloud:
    nacos:
      discovery:
        server-addr: nacos的地址:8848

    gateway:
      discovery:
        locator:
          enabled: true # 让gateway可以发现nacos中的微服务

      routes:
        - id: us_route
          uri: lb://user-service
          predicates:
            - Path=/us/**
          filters:
            - StripPrefix=1

        - id: order_route
          uri: lb://order-service
          predicates:
            - Path=/os/**
          filters:
            - StripPrefix=1

#  profiles:
#    active: dev # 环境标识

3.4.2 启动类

java 复制代码
@SpringBootApplication
public class GatewayApp {

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

}

3.4.3 功能测试

启动网关模块的服务,然后在nacos中可以看到网关的服务信息也注册进来了

用户查询接口测试

如果通过网关调用,可以调用接口:localhost:9001/us/user/getById?id=001

订单查询接口测试

如果通过网关调用,可以调用接口:localhost:9001/os/order/getById?id=001

四、springcloud接入SkyWalking

通过上面的步骤,我们完成了springcloud的微服务模块的搭建,和调用效果的测试,接下来,将微服务接入到SkyWalking中,看看SkyWalking是否能够追踪到微服务调用的完整链路信息

4.1 参数准备

对gateway,user,order三个模块,在服务启动时分别添加如下启动参数

gateway模块

-javaagent:E:\code-self\skywalking-agent\skywalking-agent.jar -DSW_AGENT_NAME=service-gateway -DSW_AGENT_COLLECTOR_BACKEND_SERVICES=IP服务地址:11800

user模块

-javaagent:E:\code-self\skywalking-agent\skywalking-agent.jar -DSW_AGENT_NAME=service-user -DSW_AGENT_COLLECTOR_BACKEND_SERVICES=IP服务地址:11800

order模块

-javaagent:E:\code-self\skywalking-agent\skywalking-agent.jar -DSW_AGENT_NAME=service-order -DSW_AGENT_COLLECTOR_BACKEND_SERVICES=IP服务地址:11800

4.2 接口调用

先分别启动user和order模块,然后调用查询order的服务接口

调用成功后,等待监控数据上报到Skywalking,然后去Skywalking观察调用链路的信息

拓扑图展现

从拓扑图上可以看到该接口调用的完整链路信息

Trace的信息展现

调用链路的信息就更加的完整了,正好是获取订单详情接口的完整链路,包括最终获取用户走的是redis缓存

4.3 接入网关

按照上面同样的方式,启动网关服务,然后通过网关调用获取订单详情的接口

接口调用成功,此时再去Skywalking监控界面检查调用的拓扑信息,奇怪的是,在调用链路中,并没有显示调用的起始点是网关,这是怎么回事呢?

4.3.1 问题分析

默认情况下,oap服务并不识别gateway作为服务链路的入口,如果需要支持,可以在下载的Agent的包目录下,找到optional-plugins目录下的gateway的插件包,然后拷贝到plugins目录中

注意,拷贝的jar包版本要与你工程中的包版本对应起来,拷贝完成后,重新重启几个模块的服务

再次调用接口

调用成功后,从Skywalking的拓扑信息,以及Trace链路监控信息来看,此时网关就作为入口能够正常显示出来了

五、写在文末

在生产环境中,随着部署的微服务增多,微服务中引用的中间件的增多,一个接口从开始到响应结果,中间的调用链路可能非常复杂,如果不借助外部的可视化工具进行协助排查,这将是一个非常耗时耗力的过程,所以在此情况下,Skywalking在全链路监控这一块提供了一个非常好的选择,本篇到此结束,感谢观看。

相关推荐
小码农叔叔8 个月前
【微服务】skywalking自定义链路追踪与日志采集
skywalking·skywalking链路追踪·skywalking自定义链路·skywalking接入日志·skywalking追踪日志·skywalking链路·skywalking日志采集