目录
一、前言
二、环境准备
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。
本文springcloud微服务模块需要依赖的外部模块如下:
服务模块如下:
像mysql,redis的搭建相信很多同学都非常熟悉了,这里就不再赘述了,快速介绍下nacos的单机搭建流程,nacos下载地址:git下载地址
也可以直接在这里下载,nacos 1.4.2安装包
1、解压安装包
tar -zxvf nacos-server-1.4.2.tar.gz
2、进入到bin目录使用脚本启动
sh startup.sh -m standalone
服务正常启动后,可以在浏览器访问nacos的ui界面:http://IP:8848/nacos
默认登录账号和密码:nacos/nacos
最外层添加如下依赖
1.8
1.8
UTF-8
3.1.5
2021.0.5
2021.1
3.5.2
1.1.17
2.2.2
org.springframework.boot
spring-boot-dependencies
2.2.2.RELEASE
pom
import
org.springframework.cloud
spring-cloud-dependencies
Hoxton.SR1
pom
import
com.alibaba.cloud
spring-cloud-alibaba-dependencies
2.1.0.RELEASE
pom
import
com.alibaba.fastjson2
fastjson2
2.0.23
org.apache.skywalking
apm-toolkit-logback-1.x
8.14.0
org.springframework.boot
spring-boot-maven-plugin
模块结构如下
创建一个数据库,并提前准备一张测试使用的表
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', '[email protected]');
用户模块的服务将会使用nacos作为注册中心,所以需要添加nacos的依赖,同时,后面的服务调用需要走统一网关,因此gateway的依赖不可少
org.projectlombok
lombok
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-redis
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.mybatis.spring.boot
mybatis-spring-boot-starter
${mybatis-boot.version}
com.alibaba
druid-spring-boot-starter
${druid.version}
mysql
mysql-connector-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
接口中将会使用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
自定义序列化类
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 implements RedisSerializer {
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private Class clazz;
public FastJson2JsonRedisSerializer(Class 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);
}
}
在这段代码中,添加了一个根据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 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;
}
}
添加一个接口类
@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);
}
}
@MapperScan("com.congge.mapper")
@SpringBootApplication
public class UserApp {
public static void main(String[] args) {
SpringApplication.run(UserApp.class, args);
}
}
启动用户模块的服务,然后调用查询用户的接口,可以正常查到数据库的数据
同时nacos的服务列表中,也能看到当前注册上去的用户服务信息
到这里,用户服务就基本整合完成
模块结构如下
订单模块中,将会通过feign的方式调用user模块的服务,所以这里增加了feign的依赖
org.projectlombok
lombok
org.springframework.boot
spring-boot-starter-web
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.cloud
spring-cloud-starter-loadbalancer
org.springframework.cloud
spring-cloud-starter-openfeign
server:
port: 9003
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: nacos的地位:8848
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);
}
@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;
}
}
@EnableFeignClients
@SpringBootApplication
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class, args);
}
}
启动order模块的服务,然后调用查询订单的接口,可以看到期望的返回结果
同时在nacos的服务列表中存在两个服务信息
上面分别完成了两个服务模块的搭建,测试,以及相互之间的调用,但是并没有通过网关,接下来将服务接口的调用通过网关接入
org.springframework.cloud
spring-cloud-starter-gateway
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.cloud
spring-cloud-starter-loadbalancer
网关模块的搭建,主要是配置文件中涉及的路由规则的配置,需要搞清楚规则配置中的各项含义,否则调用的时候容易出错
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 # 环境标识
@SpringBootApplication
public class GatewayApp {
public static void main(String[] args) {
SpringApplication.run(GatewayApp.class, args);
}
}
启动网关模块的服务,然后在nacos中可以看到网关的服务信息也注册进来了
用户查询接口测试
如果通过网关调用,可以调用接口:localhost:9001/us/user/getById?id=001
订单查询接口测试
如果通过网关调用,可以调用接口:localhost:9001/os/order/getById?id=001
通过上面的步骤,我们完成了springcloud的微服务模块的搭建,和调用效果的测试,接下来,将微服务接入到SkyWalking中,看看SkyWalking是否能够追踪到微服务调用的完整链路信息
对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
先分别启动user和order模块,然后调用查询order的服务接口
调用成功后,等待监控数据上报到Skywalking,然后去Skywalking观察调用链路的信息
拓扑图展现
从拓扑图上可以看到该接口调用的完整链路信息
Trace的信息展现
调用链路的信息就更加的完整了,正好是获取订单详情接口的完整链路,包括最终获取用户走的是redis缓存
按照上面同样的方式,启动网关服务,然后通过网关调用获取订单详情的接口
接口调用成功,此时再去Skywalking监控界面检查调用的拓扑信息,奇怪的是,在调用链路中,并没有显示调用的起始点是网关,这是怎么回事呢?
默认情况下,oap服务并不识别gateway作为服务链路的入口,如果需要支持,可以在下载的Agent的包目录下,找到optional-plugins目录下的gateway的插件包,然后拷贝到plugins目录中
注意,拷贝的jar包版本要与你工程中的包版本对应起来,拷贝完成后,重新重启几个模块的服务
再次调用接口
调用成功后,从Skywalking的拓扑信息,以及Trace链路监控信息来看,此时网关就作为入口能够正常显示出来了
在生产环境中,随着部署的微服务增多,微服务中引用的中间件的增多,一个接口从开始到响应结果,中间的调用链路可能非常复杂,如果不借助外部的可视化工具进行协助排查,这将是一个非常耗时耗力的过程,所以在此情况下,Skywalking在全链路监控这一块提供了一个非常好的选择,本篇到此结束,感谢观看。