目录
前言
官方中文文档
使用版本
spring
Spring Boot 3.0
中间件
使用到的组件与功能
环境安装
虚拟机
nexus
nacos
集成过程
工程搭建
父工程搭建
子工程
服务集成
nacos集成
配置文件
服务注册与发现-discovery
服务注册
启动
服务发现
测试
配置管理-config
新增配置
测试
Sentinel集成
官方文档
集成步骤
测试
SpringCloud Gateway集成
配置文件修改
参数说明
测试
拓展
SpringCloud OpenFeign集成
集成过程
测试
Seata 整合实现分布式事务
概念介绍及Server搭建
集成过程
数据库脚本
业务表脚本
代码模块改造
POM依赖
配置文件
详细代码
client集成openFeign实现远程调用
测试
Knife4J整合gateway接口文档
需求
官方文档
集成
网关模块修改
业务模块修改
测试
Rocketmq消息队列集成
服务安装
前置准备
Spring Cloud Stream基础
配置文件修改
业务代码
provider
consumer
测试
进阶
消息丢失问题
tag与key的设置
Sleuth+Zipkin集成分布式链路追踪
zipkin可视化服务安装
业务集成
问题及大坑
只介绍如何集成及使用,概念性的东西就不细说了。慢慢更新
spring-cloud-alibaba/README-zh.md at 2022.x · alibaba/spring-cloud-alibaba · GitHub
我这边使用的都是最新版
spring.cloud.alibaba.version | 2022.0.0.0-RC2 |
spring.cloud.version | 2022.0.0 |
spring.boot.version | 3.0.2 |
spring-cloud-alibaba、spring-cloud、springboot 三者的版本关系说明
https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
因为需要使用新版本的springboot及spring.cloud.alibaba。
springboot3.0版本特性:
nacos: 2.2.1
sentinel:1.8.6
组件关系
服务注册与发现:Nacos
分布式事务:Seata
网关:Spring Cloud Gateway
服务调用:OpenFeign
我是用的VMware虚拟机里面跑的。
具体怎么装,看下面
(312条消息) VMware16安装 CentOS7_ricardo.M.Yu的博客-CSDN博客
我这边搭建了一个nexus maven私服
(310条消息) docker-compose 搭建maven私服 nexus与配置_ricardo.M.Yu的博客-CSDN博客
(310条消息) docker-compose安装nacos 2.2.1及配置_ricardo.M.Yu的博客-CSDN博客
我这边使用的是springboot多模块项目,最终效果如下
POM文件
4.0.0
org.example
cloud-alibaba
1.0-SNAPSHOT
pom
common
file
auth
gateway
admin
data
biz
log
consumer
17
17
UTF-8
UTF-8
2022.0.0.0-RC2
2022.0.0
3.0.2
2.5.1
1.18.20
1.2.73
org.springframework.boot
spring-boot-starter-parent
3.0.2
org.springframework.boot
spring-boot-dependencies
${spring.boot.version}
pom
import
org.springframework.cloud
spring-cloud-dependencies
${spring.cloud.version}
pom
import
com.alibaba.cloud
spring-cloud-alibaba-dependencies
${spring.cloud.alibaba.version}
pom
import
de.codecentric
spring-boot-admin-starter-server
${spring.boot.admin.version}
de.codecentric
spring-boot-admin-starter-client
${spring.boot.admin.version}
org.projectlombok
lombok
${lombok.version}
com.alibaba
fastjson
${fastjson.version}
prod
prod
true
dev
dev
org.springframework.boot
spring-boot-maven-plugin
none
execute
repackage
部分可以参考官方的示例
Nacos Spring Cloud 快速开始
这边我定义了4组配置文件,说一下他们的作用:
引导类配置;
bootstrap.yaml:定义当前生效的配置
spring:
profiles:
active: dev
bootstrap-dev.yaml:定义引导类当前生效的参数
spring:
cloud:
nacos:
server-addr: http://192.168.1.115:8108/
discovery:
group: dev
应用级配置:
application.yaml
应用级别的基础配置
logging:
level:
root: info
com:
sy: debug
application-dev.yaml
定义应用的名称以及端口等信息
server:
port: 9110
spring:
application:
name: biz
引入依赖
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.cloud
spring-cloud-starter-loadbalancer
启动后,查看nacos控制台服务列表,已经能看到这个实例
需要在新建一个consumer服务,来测试
分别在两个模块建两个controller, biz模块下:
@RestController
@RefreshScope
@RequestMapping("/")
public class BizController {
@GetMapping(value = "/echo/{string}")
public String echo(@PathVariable String string) {
return "Hello Nacos Discovery " + string;
}
}
consumer模块下
@Slf4j
@RestController
@RequestMapping("/")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@Value("${spring.application.name}")
private String appName;
@GetMapping("/echo/app-name")
public String echoAppName() {
//使用 LoadBalanceClient 和 RestTemolate 结合的方式来访问
ServiceInstance serviceInstance = loadBalancerClient.choose("biz");
String url = String.format("http://%s:%s/echo/%s", serviceInstance.getHost(), serviceInstance.getPort(), appName);
System.out.println("request url:" + url);
String result = restTemplate.getForObject(url, String.class);
log.info("result -> {}", result);
return result;
}
}
用consumer下的http测试接口,发现已经能正常返回数据,appName即为consumer
配置管理测试,主要集成从nacos拉取公共配置并测试,
biz模块下新增依赖和配置文件
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
增加配置中心config配置,文件扩展为yaml
spring:
cloud:
nacos:
server-addr: http://192.168.1.115:8108/
discovery:
group: dev
#配置中心
config:
server-addr: http://192.168.1.115:8108/
group: dev
file-extension: yaml
# 共享配置
# shared-configs:
# - data-id: application-dev.yaml
# group: dev
# extension-configs:
# - dataId: test01.yml
# group: dev
application:
name: biz
nacos web页面配置列表新增一个配置,命名格式和group要对,dataId格式
${spring.application.name}-${profile}.${file-extension:properties}
示例: biz-dev.yaml
我这边建了个示例
修改 BizController 增加方法:
@Value("${username}")
private String username;
@GetMapping("/get")
public String get() {
return username;
}
http测试:可以看到username的值已经为 nacos的设置值
quick-start | Sentinel (sentinelguard.io)
demo
spring-cloud-alibaba/readme-zh.md at 2022.x · alibaba/spring-cloud-alibaba · GitHub
配置文件修改
修改bootstrap-dev.yaml, 增加 sentinel相关配置,
dashboard即为 sentinel控制台的地址,
port为当前项目需要暴露的接口,与控制台通信使用
spring:
cloud:
nacos:
server-addr: http://192.168.1.115:8108/
discovery:
group: dev
sentinel:
transport:
dashboard: localhost:9988
port: 8899
启动biz项目,调用下之前的 get接口,然后刷新 sentinel控制台,已经看到了biz的相关监控数据,其他详细的功能,可以看官方文档
nacos整合Spring Cloud Gateway 实现路由与服务动态发现
pom集成
org.springframework.cloud
spring-cloud-starter-gateway
com.alibaba.cloud
spring-cloud-alibaba-sentinel-gateway
gateway模块配置文件增加
spring:
main:
web-application-type: reactive
cloud:
nacos:
server-addr: http://192.168.1.115:8108/
discovery:
group: dev
sentinel:
transport:
dashboard: 192.168.1.115:9988
port: 8899
gateway:
# discovery:
# locator:
# enabled: true
routes:
- id: consumer-service
uri: https://www.qq.com
predicates:
- Method=GET,POST
- Path=/consumer-service/**
# - id: biz
# uri: http://192.168.1.125:9110
# predicates:
# - Method=GET,POST
# - Path=/biz/**
- id: biz
uri: lb://biz
predicates:
- Method=GET,POST
- Path=/biz/**
id:路由的ID,名称可以随意定义,但必须保证唯一
uri: 目标URI,路由到微服务的地址
上面的配置中第一个是直接转发到 qq 网站;
下面是 采用 LoadBalanceClient 方式请求,以 lb:// 开头,后面的是注册在 Nacos 上的服务名
order:路由的优先级,数字越小,优先级越高。
predicates:配置断言,通过PredicateDefinition类进行接收配置
discovery:
locator: # 是否与服务发现组件结合,通过serviceId转发到具体服务实例
enabled: true # 是否开启基于服务发现的路由规则
如:
上面的方式中:lb://biz 配置即可找到 nacos中的biz服务
postman测试
已经收到了回复的消息
关于路由规则,过滤器、自定义的规则等知识,详细见官网。
Spring Cloud Gateway
引入依赖
org.springframework.cloud
spring-cloud-starter-openfeign
biz模块新增一个API
@GetMapping(value = "/info")
public Map getInfo(@RequestParam String username) {
HashMap map = new HashMap<>();
map.put("username", username);
map.put("password", "123456");
return map;
}
consumer模块启动类增加注解@EnableFeignClients
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
consumer模块新增一个 client,意思是调用biz模块的上面方法
@Component
@FeignClient(value = "biz")
public interface BizClient {
@GetMapping("/biz/info")
Object getInfo(@RequestParam String username);
}
consumer模块controller新增一个方法供调用
@Resource
private BizClient bizClient;
@GetMapping("/username")
public Object getUserInfo(@RequestParam String username) {
return bizClient.getInfo(username);
}
postmant调用 consumer的方法,效果如下:
可以看到已经成功调用
Seata 是 阿里巴巴 开源的 分布式事务中间件,以 高效 并且对业务 0 侵入 的方式,解决 微服务 场景下面临的分布式事务问题。
有些麻烦,放在了另一处,链接
(319条消息) Spring Cloud Alibaba 整合Seata 之概念介绍及Seata-server搭建_ricardo.M.Yu的博客-CSDN博客
我使用的是seata默认的AT模式,可以通过注解的方式无侵入的方式实现集成,需要额外的一张表如下
Seata AT 模式 需要使用到 undo_log 表。
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
几个测试表,提供测试
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for account_tbl
-- ----------------------------
DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`money` int(11) NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of account_tbl
-- ----------------------------
INSERT INTO `account_tbl` VALUES (11, '1001', 981);
-- ----------------------------
-- Table structure for order_tbl
-- ----------------------------
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`commodity_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`count` int(11) NULL DEFAULT 0,
`money` int(11) NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of order_tbl
-- ----------------------------
INSERT INTO `order_tbl` VALUES (6, '1001', '2001', 1, 5);
INSERT INTO `order_tbl` VALUES (8, '1001', '2001', 1, 5);
INSERT INTO `order_tbl` VALUES (9, '1001', '2001', 1, 5);
INSERT INTO `order_tbl` VALUES (10, '1001', '2001', 1, 5);
-- ----------------------------
-- Table structure for stock_tbl
-- ----------------------------
DROP TABLE IF EXISTS `stock_tbl`;
CREATE TABLE `stock_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`count` int(11) NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `commodity_code`(`commodity_code`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of stock_tbl
-- ----------------------------
INSERT INTO `stock_tbl` VALUES (2, '2001', 11107);
之前的biz模块修改为provider模块,新建1个新的父模块 biz,并在下面建4个子模块 biz-account,biz-order,biz-stock,biz-web,新的结构如下
在biz父模块引入依赖
com.sy
common
1.0-SNAPSHOT
mysql
mysql-connector-java
org.mybatis.spring.boot
mybatis-spring-boot-starter
com.alibaba
druid-spring-boot-starter
com.alibaba.cloud
spring-cloud-starter-alibaba-seata
子模块pom,这里我为了方便,使用了 knife4j的最新接口文档,支持springboot3.0
4.0.0
com.sy
biz
1.0-SNAPSHOT
biz-web
17
17
UTF-8
com.github.xiaoymin
knife4j-openapi3-jakarta-spring-boot-starter
4.1.0
每个文件都添加seata的配置,如下
spring:
cloud:
nacos:
server-addr: http://192.168.1.115:8108/
discovery:
group: dev
sentinel:
transport:
dashboard: 192.168.1.115:8109
port: 8900
application:
name: provider
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: my-tx-group
service:
vgroup-mapping:
my-tx-group: seata-server
grouplist:
seata-server: 192.168.1.115:8091
完整的就不弄了,到时下下来源码去看,主要说几个重要的
controller,提供接口
@RequestMapping("/api/business")
@RestController
public class BusinessController {
@Autowired
private BusinessService businessService;
/**
* 购买下单,模拟全局事务提交
*
* @return
*/
@GetMapping("/purchase/commit")
public Boolean purchaseCommit(HttpServletRequest request) {
businessService.purchase("1001", "2001", 1);
return true;
}
/**
* 购买下单,模拟全局事务回滚
*
* @return
*/
@GetMapping("/purchase/rollback")
public Boolean purchaseRollback() {
try {
businessService.purchase("1002", "2001", 1);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
}
service提供多个服务的接口调用,方便测试
@Slf4j
@Service
public class BusinessService {
@Autowired
private StockClient stockClient;
@Autowired
private OrderClient orderClient;
/**
* 减库存,下订单
*
* @param userId
* @param commodityCode
* @param orderCount
*/
@GlobalTransactional
public void purchase(String userId, String commodityCode, int orderCount) {
log.info("purchase begin ... xid: " + RootContext.getXID());
stockClient.deduct(commodityCode, orderCount);
orderClient.create(userId, commodityCode, orderCount);
}
}
@FeignClient(value = "biz-stock")
public interface StockClient {
@GetMapping("/api/stock/deduct")
Object deduct(@RequestParam String commodityCode, @RequestParam Integer count);
}
mapper及xml文件不一一列出了,下载源码去看,结构如下
knife4j配置
@Configuration
@EnableKnife4j
public class SwaggerConfiguration {
@Value("${spring.application.name}")
private String appName;
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title(appName + "系统API")
.version("1.0")
.description( "Knife4j集成springdoc-openapi示例")
.termsOfService("http://doc.xiaominfo.com")
.license(new License().name("Apache 2.0")
.url("http://doc.xiaominfo.com")));
}
}
打开biz-web的接口
http://192.168.1.125:9203/doc.html
测试
如果打断点,undo_log这个表会有相关的信息
事务完成后,会删除
biz-web调用控制台效果图
seata-server端打印的日志
因为开发需求,需要整合各个服务的接口文档,发现了knife4j最新版本(2023.3)出来了一个好用的插件,可以方便整合 SpringCloud Gateway 与各个业务服务的接口文档,不需要之前我们再硬编码写各种过滤器和拦截器去手动整合了。
Spring Cloud Gateway网关聚合 | Knife4j (xiaominfo.com)
在之前的网关模块新增依赖
com.github.xiaoymin
knife4j-gateway-spring-boot-starter
4.1.0
修改网关模块的 bootstrap-dev.yaml配置文件,增加 knife4j的配置
excluded-services:即为排除的网关服务的名称,
此处 strategy: discover 是依赖 nacos的注册中心的服务发现模式,可以自动发现注册的业务服务
knife4j:
gateway:
enabled: true
strategy: discover
discover:
version: openapi3
enabled: true
excluded-services:
- gateway
在上面集成seata模块的过程中,每个服务都集成knife4j的配置
启动4个业务服务即一个网关服务,访问网关的接口文档,发现每个业务模块都被集成了进来,如下图
测试一下接口,调用以下之前biz-web模块下的方法,发现已经正常与网关集成了
集成完毕
参考下面另一篇文章
(324条消息) docker-compose安装 rocketmq server、dashboard_ricardo.M.Yu的博客-CSDN博客
准备两个服务:基于之前的改造:需要provider(最为生产者)以及consumer(作为消费者)
依赖
com.alibaba.cloud
spring-cloud-starter-stream-rocketmq
2022.0.0.0-RC2
com.github.xiaoymin
knife4j-openapi3-jakarta-spring-boot-starter
因为rocketmq依赖Spring Cloud Stream,先介绍下Spring Cloud Stream。
注意,因为我们使用最新版本一来的 stream版本为最新的 4.0,需要注意3.0以后的版本,写法有很大区别,一些遗留问题,如@EnableBInding、@StreamListener都已经不再使用(deprecated)
SpringCloud Stream是一个构建消息驱动微服务的框架,应用程序通过inputs或者 outputs来与SpringCloud Stream中的binder进行交互,我们可以通过配置来binding ,而 SpringCloud Stream 的binder负责与中间件交互,弄清楚两个概念:
注意:
bindings:命名 格式 {name}-out-0 这是是 4.x 的一种约定
这边我用了两个组,两个topic来测试
provider下 bootstrap-dev.yaml,增加 rocketmq配置
spring:
application:
name: provider
cloud:
nacos:
server-addr: http://192.168.1.115:8108/
discovery:
group: dev
#配置中心
config:
server-addr: http://192.168.1.115:8108/
group: dev
file-extension: yaml
# 共享配置
# shared-configs:
# - data-id: application-dev.yaml
# group: dev
# extension-configs:
# - dataId: test01.yml
# group: dev
sentinel:
transport:
dashboard: 192.168.1.115:8109
port: 8900
# rocket mq 配置
stream:
rocketmq:
binder:
name-server: 192.168.1.115:9876
bindings:
test-out-0:
destination: test-topic
content-type: application/json
group: test-group
dev-out-0:
destination: dev-topic
content-type: application/json
group: dev-group
consumer下 bootstrap-dev.yaml,增加 rocketmq配置
spring:
cloud:
nacos:
server-addr: http://192.168.1.115:8108/
discovery:
group: dev
sentinel:
transport:
dashboard: 192.168.1.115:9988
port: 8899
# rocket mq 配置
stream:
rocketmq:
binder:
name-server: 192.168.1.115:9876
function:
definition: test;dev
bindings:
test-in-0:
destination: test-topic
content-type: application/json
group: test-group
consumer:
instance-count: 3
concurrency: 5
dev-in-0:
destination: dev-topic
content-type: application/json
group: dev-group
consumer:
instance-count: 3
concurrency: 5
写一个person的测试类
@Data
public class Person {
private String name;
}
controller
@RestController
@Tag(name = "消息-消息队列发送")
@RequestMapping("/message/send")
public class MqSendController {
@Resource
private MqSendService mqSendService;
@Operation(summary = "发送")
@PostMapping
public void send(@RequestParam String channel, @RequestParam String message) {
mqSendService.send(channel, message);
}
}
service
@Service
public class MqSendService {
@Resource
private StreamBridge streamBridge;
public void send(String channel, String message) {
Person person = new Person();
person.setName(message);
Message build = MessageBuilder.withPayload(person).build();
streamBridge.send(channel, build);
}
}
service
@Slf4j
@Service
public class MqSubService {
@Bean
public Consumer> test() {
return message -> {
MessageHeaders headers = message.getHeaders();
Person payload = message.getPayload();
log.info("消息 test:" + payload + "__" + headers);
};
}
@Bean
public Consumer> dev() {
return message -> {
MessageHeaders headers = message.getHeaders();
Person payload = message.getPayload();
log.info("消息 dev:" + payload + "__" + headers);
};
}
}
调用provider的controller方法,
注意,channel必须和配置文件里面的 destination 一致,否则收不到消息。
consumer打印:
web界面
已经正常集成完毕。
几个问题,一个是多组多订阅的问题,即一个组下的consumer订阅的topic要一直,不然消费组订阅关系不一致,会导致消息丢失问题,下面这个文章结束的比较好
RocketMQ的tag还有这个“坑”! - 知乎 (zhihu.com)
通过 stream 的header设置,来设置这两个参数
public void send(String channel, String message, String tag, String keys) {
Person person = new Person();
person.setName(message);
Message build = MessageBuilder.withPayload(person)
.setHeader(MessageConst.PROPERTY_TAGS, tag).setHeader(MessageConst.PROPERTY_KEYS, keys).build();
streamBridge.send(channel, build);
}
可以看到,这两个参数已经被设置上
Zipkin是Twitter开源的分布式实时数据跟踪系统,主要功能是收集系统的时序数据,从而追踪微服务架构的系统延时等问题,从而达到链路调用监控跟踪作用,还提供了一个非常友好的UI界面,来帮助分析追踪数据。
安装的链接在这里
(291条消息) docker-compose 搭建 zipkin 服务端_ricardo.M.Yu的博客-CSDN博客
事先没做调研,从maven仓库选择了个最新的依赖
org.springframework.cloud
spring-cloud-starter-zipkin
2.2.8.RELEASE
修改 bootstrap-dev.yaml
增加配置,
zipkin:
base-url: http://192.168.1.115:8112/
sender:
type: web
enabled: true
sleuth:
sampler:
probability: 1
启动后
看着有点变化,测试后没用,一通找文档
最后到了官方文档
Spring Cloud Sleuth Reference Documentation
sleuth停止支持, springboot 3.0.x之后的版本已经不再支持了,
Spring Cloud Sleuth will not work with Spring Boot 3.x onward. The last major version of Spring Boot that Sleuth will support is 2.x.
待寻找其他方案。