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

发布时间:2024年01月07日

目录

一、前言

二、环境准备

2.1 软件环境

2.2?微服务模块

2.3 环境搭建

2.3.1 下载安装包

2.3.2?解压并启动服务

2.3.3?访问web界面

三、搭建springcloud微服务

3.1 顶层公共依赖

3.2?用户服务模块

3.2.1 准备测试使用数据库

3.2.2?添加依赖

3.2.3?添加配置文件

3.2.4?redis的自定义配置类

3.2.5?核心业务实现类

3.2.6?业务测试接口

3.2.7?启动类

3.2.8 接口模拟测试

3.3?订单服务模块

3.3.1 引入依赖

3.3.2?添加配置文件

3.3.3?添加feign服务

3.3.4?添加测试接口

3.3.5?启动类

3.3.6?接口模拟测试

3.4?网关服务模块

3.4.1 引入依赖

3.4.2?添加配置文件

3.4.2 启动类

3.4.3?功能测试

四、springcloud接入SkyWalking

4.1 参数准备

4.2?接口调用

4.3?接入网关

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 顶层公共依赖

最外层添加如下依赖

    <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 准备测试使用数据库

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

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;

添加一条测试使用的数据

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

3.2.2?添加依赖

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

<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?添加配置文件

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作为缓存,需要自定义对缓存的序列化


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;
    }
}

自定义序列化类


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查询用户详情的方法,第一次未查到将会走数据库,然后放入缓存,以后相同的请求再过来的时候,如果缓存中有数据将会走缓存


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?业务测试接口

添加一个接口类

@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?启动类

@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的依赖

    <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?添加配置文件

server:
  port: 9003

spring:
  application:
    name: order-service

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

3.3.3?添加feign服务

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

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?添加测试接口

@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);
    }
}

接口实现

@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?启动类

@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 引入依赖

    <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?添加配置文件

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

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 启动类

@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在全链路监控这一块提供了一个非常好的选择,本篇到此结束,感谢观看。

文章来源:https://blog.csdn.net/zhangcongyi420/article/details/135315788
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。