springcloud集成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下载地址
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.3 启动类
@SpringBootApplication public class GatewayApp { public static void main(String[] args) { SpringApplication.run(GatewayApp.class, args); } }
3.4.4 功能测试
启动网关模块的服务,然后在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在全链路监控这一块提供了一个非常好的选择,本篇到此结束,感谢观看。
以上就是springcloud集成skywalking实现全链路追踪的详细内容,更多关于springcloud集成skywalking的资料请关注脚本之家其它相关文章!