Seata
seata是什么?
seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。
本次拆分新生电子报道用的是 1.4.2
版本的 seata,是最新的一个版本
在项目中 Seata 具体搭建
- 首先我们需要现在官网上下载源码资源包
seata-1.4.2.zip
以及服务端包seata-server-1.4.2
-
服务端前期工作
-
- 为 seata 建立数据库,导入资源包中的 sql 文件
- 修改配置文件
file.conf
和registry.conf
- 在源码包中修改
config.txt
,并导入nacos
配置中心,(单独给 seata 建立一个命名空间) - 给需要进行全局事务控制的数据库导入
undo_log
表 - 双击
seata-server.bat
启动
为 seata 建立数据库,导入资源包中的 sql 文件
- 在数据库中建立一个名字为 seata 的数据库,导入 sql 文件运行
script\server\db\mysql.sql
修改配置文件 file.conf
和 registry.conf
file.conf
,主要修改
mode = "db"
db {
datasource = "druid"
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url ="jdbc:mysql://localhost:3306/seata?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true"
user = "root"
password = "li12345"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
registry.conf
文件
registry {
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = "ab8ba48b-27f0-4051-8c4d-9118256a4faa"
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = "ab8ba48b-27f0-4051-8c4d-9118256a4faa"
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
}
其中namespace = "ab8ba48b-27f0-4051-8c4d-9118256a4faa"
是单独在 nacos 中建立的一个命名空间,用于后续导入 seata 配置,导入之后如下图所示
在源码包中修改 config.txt
,并导入 nacos
配置中心
- 修改源码包中的
script/config-center/config.txt
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=li12345
# 添加上此配置
service.vgroupMapping.fsp_tx_group=default
- 在项目中配置
tx-service-group:
后面的值需要的nacos 里面TC 集群保持一致,例如 nacos 里有上面代码块中添加的service.vgroupMapping.fsp_tx_group=default
- 修改完之后打开
Git Bash
打开到目录script\config-center\nacos
下,运行
sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t ab8ba48b-27f0-4051-8c4d-9118256a4faa -u nacos -w nacos
- 其中
-t
后是命名空间,如果使用默认的 public 去掉该参数即可
给需要进行全局事务控制的数据库导入 undo_log
表
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`branch_id` bigint NOT NULL,
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int NOT NULL,
`log_created` datetime(0) NOT NULL,
`log_modified` datetime(0) NOT NULL,
`ext` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;SET FOREIGN_KEY_CHECKS = 1;
双击运行 seata-server.bat 启动
项目配置文件
seata:
#事务群组(可以每个应用独立取名,也可以使用相同的名字),要与服务端nacos-config.txt中service.vgroup_mapping中存在,并且要保证多个群组情况下后缀名要保持一致-tx_group
enabled: true
application-id: ${spring.application.name}
tx-service-group: fsp_tx_group #要与配置文件中的vgroupMapping一致
service:
vgroup-mapping:
fsp_tx_group: default
disable-global-transaction: false
registry: #registry根据seata服务端的registry配置
type: nacos #默认为file
nacos:
application: seata-server #配置自己的seata服务
server-addr: ${spring.cloud.nacos.server-addr} #根据自己的seata服务配置
group: SEATA_GROUP #根据自己的seata服务配置
namespace: ab8ba48b-27f0-4051-8c4d-9118256a4faa
username: nacos #根据自己的seata服务配置
password: nacos #根据自己的seata服务配置
config:
type: nacos #默认file,如果使用file不配置下面的nacos,直接配置seata.service
nacos:
server-addr: ${spring.cloud.nacos.server-addr} #配置自己的nacos地址
group: SEATA_GROUP #配置自己的dev
namespace: ab8ba48b-27f0-4051-8c4d-9118256a4faa #改为自己的nacos的namespace,这里填写的是刚才创建seata命名空间的id
username: nacos #配置自己的username
password: nacos #配置自己的password
配置之后运行时遇到的问题
2022-04-29 14:22:29.536 ERROR 81060 --- [eoutChecker_2_1] i.s.c.r.netty.NettyClientChannelManager : 0101 can not connect to 10.102.176.175:8091 cause:can not register RM,err:can not connect to services-server.
踩坑
百度查询时候,说的是 在启动 seata 时是以内网运行的,与nacos 连接出现了问题,解决了好长时间,并没有解决
造成这个问题的原因:我的 nacos 是配置在服务器上的,seata 是在本地启动的,两者之间出现了问题,最后将 nacos 也在本地启动,问题解决。之后在开发中,要在本地就全部在本地配置响应的内容,要么就全部在服务器上,否则有可能会出现意想不到的错误
Sentinel
Sentinel 是什么?
Sentinel 是面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
- sentinel 是以 jar 包的形式启动的,启动之后如下所示
application.yml
配置文件
spring:
application:
name: API-DATA
cloud:
nacos:
server-addr: 127.0.0.1:8848
sentinel:
transport:
# 配置Sentinel dashboard地址
dashboard: localhost:8080
# 默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
datasource:
ds1:
nacos:
server-addr: ${spring.cloud.nacos.server-addr}
dataId: ${spring.application.name}
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
配置 sentinel 持久化,对于有些接口需要进行服务降级/熔断,但是如果不配置持久化,我们只在 sentinel 上添加一个配置之后,项目一旦重启,这些配置的降级/熔断策略就没有,需要重新再次配置,这样很不友好,因此,持久化是非常必要的
- 在 nacos 的配置列表中进行持久化配置
gateway配置全局过滤器
请求服务时首先是通过 gateway 然后再进行路由转发,因此再 gateWay 这里配置是最合适的
package com.example.filter.factory;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.example.entity.Admin;
import com.example.feign.AdminFeign;
import com.example.response.ResponseResult;
import com.example.utils.AdminUtils;
import com.example.utils.TokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.List;
/**
* description:
*
* @author: lixianghong
* @date: 2022/4/27 8:39
*/
@Slf4j
@Component // 代表在工厂中创建对象 @configuration 配置
public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory<TokenGatewayFilterFactory.Config> {
@Resource
private AdminFeign adminFeign;
@Value("${token.requestHeader}")
private String requestHeader;
@Value("${token.startWith}")
private String startWith;
public TokenGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("进入 filter");
// 1. 获取 token 信息
List<String> authorization = exchange.getRequest().getHeaders().get(requestHeader);
// 获取响应体
ServerHttpResponse response = exchange.getResponse();
// 判断请求头的内容是否为空:y:返回验证失败信息
if (authorization == null) {
return unAuthorizationResponse(response,HttpStatus.UNAUTHORIZED,"令牌验证失败");
}
String token = authorization.get(0);
// 判断 token 是否为空,token 是否以规定字符开头 返回格式是否正确信息
if (StrUtil.isEmpty(token) || !token.startsWith(startWith)) {
return unAuthorizationResponse(response,HttpStatus.UNAUTHORIZED,"令牌格式错误");
}
token = token.substring(startWith.length());
// 截取之后 token 如果为空,则返回格式错误信息
if (StrUtil.isEmpty(token)) {
return unAuthorizationResponse(response,HttpStatus.UNAUTHORIZED,"令牌格式错误");
}
// 判断 token 是否过期,如过期则返回过期信息
if (TokenUtil.isExpire(token)) {
return unAuthorizationResponse(response,HttpStatus.UNAUTHORIZED,"令牌已过期");
}
Admin admin = adminFeign.getAdmin(token);
// 将当前用户存储先下来
AdminUtils.setLoginUser(admin);
// 放行
return chain.filter(exchange);
}
};
}
public static Mono<Void> unAuthorizationResponse(ServerHttpResponse response, HttpStatus status, String message) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
ResponseResult<Void> result = new ResponseResult<>();
result.setMessage(message);
result.setStatus(401);
DataBuffer dataBuffer = response.bufferFactory().wrap(JSONObject.toJSONString(result).getBytes());
return response.writeWith(Mono.just(dataBuffer));
}
public List<String> shortcutFieldOrder() {
return super.shortcutFieldOrder();
}
// 内部类
public static class Config {
}
}
在 application.yml
中配置,上面创建的类表示我们时自定义 token 工厂,使用 @Component
注解代表在工厂中创建对象,class:TokenGatewayFilterFactory
gateway:
routes:
- id: data_router
uri: lb://API-DATA
predicates:
- Path=/api/ScreenData/**
filters:
- Token
给过滤其中 配置上 filters: - Token 表示我们这个模块中的接口访问需要走过滤器这一步
配置完成之后进行接口测试
出现的问题
在使用 openfeign 组件通信时,因为需要调用其他模块的接口,但是有没有token,出现报错异常
feign.codec.DecodeException: No qualifying bean of type 'org.springframework.boot.autoconfigure.http.HttpMessageConverters' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at feign.AsyncResponseHandler.decode(AsyncResponseHandler.java:119) ~[feign-core-10.10.1.jar:na]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ HTTP GET "/java/sayHi" [ExceptionHandlingWebHandler]
解决方法:创建gateWay配置
之前没有遇到过,记录一下。从报错的内容来看是找不到装配的bean,既然找不到就尝试手动注入,代码如下:
@Bean
@ConditionalOnMissingBean
public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
}
放入configuration注解的配置类中,问题解决。
权限管理
权限这块搞了一天没有弄进去,这块的内容还需要好好学习一下,这个阶段就先不做了。