背景:
公司采用dubbo的分布式服务,现在业务上遇到了分布式事物的问题,和同事讨论改过几个分布式的开源框架,最终采用了seata,seata的性能相对比较客观,但是回滚情境下,性能下降特别厉害,这个与seata的网络IO和数据IO多次交互相关。具体各种分布式事物管理的开源框架推荐一篇文章可以参考:“若干分布式事务框架”与“我的偏见”( 测试/分析),此处我们部署搭建是最新的1.1.0版本。
采坑之路:
1.Seata Server搭建部署
服务端搭建,可以直接从seata开源社区下载最新版本,下载的文件可以直接在服务器解压部署,自行修改配置文件自己的环境参数主要是下图的file.conf和register.conf
register.conf 主要配置如下:
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
# 注册中心采用的zk
type = "zk"
nacos {
serverAddr = "localhost"
namespace = ""
cluster = "default"
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = "0"
}
zk {
cluster = "default"
# 集群配置采用,隔开
serverAddr = "127.0.0.1:2181,127.0.0.1:2183,127.0.0.1:2183"
session.timeout = 6000
connect.timeout = 2000
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
# 这边的配置没有对接配置中心所以采用file的方式配置,这边配置之后会自动读取file文件的配置
type = "file"
nacos {
serverAddr = "localhost"
namespace = ""
group = "SEATA_GROUP"
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
app.id = "seata-server"
apollo.meta = "http://192.168.1.204:8801"
namespace = "application"
}
zk {
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
file.conf 配置如下
store {
## store mode: file、db
# 存储我们设置为db模式
mode = "db"
## file store property
file {
## store location dir
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
maxBranchSessionSize = 16384
# globe session size , if exceeded throws exceptions
maxGlobalSessionSize = 512
# file buffer size , if exceeded allocate new buffer
fileWriteBufferCacheSize = 16384
# when recover batch read size
sessionReloadReadSize = 100
# async, sync
flushDiskMode = async
}
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "druid"
## mysql/oracle/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.01:3306/seata"
user = "admin"
password = "admin"
minConn = 1
maxConn = 10
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
}
}
修改相关的配置文件之后,可以直接在bin目录下执行相应的shell启动脚本。
2.客户端配置
- 依赖引用
io.seata
seata-spring-boot-starter
1.1.0
# seata zk连接主要依赖zkclient
com.101tec
zkclient
0.11
- yml 配置
seata:
enabled: true
application-id: ${spring.application.name} # 可以自定义
tx-service-group: seller_seata_group # 此处的名称一定要与 vgroup-mapping下配置的参数保持一致
registry:
file:
#因为seata版本驼峰参数映射有问题导致,seata的zk配置参数设置不上导致异常,所以采用了file方式
name: registry-test.conf
service:
grouplist:
# seata seaver的 地址配置,此处可以集群配置是个数组
default: 127.0.0.1:8091
vgroup-mapping:
# key一定要与tx-service-group一致
seller_seata_group: default
# 这边自动代理关掉是因为,, seata源码中SeataDataSourceBeanPostProcessor的初始化要比我的datasource初始话晚,导致datasoure不会被包装为代理类,此处我自己代码做了处理
enableAutoDataSourceProxy: false
配置注意点:
-
tx-service-group配置的值一定要与vgroup-mapping配置下的key保持一致,否则会异常
[2020-03-13 14:44:00,188] ERROR [timeoutChecker_2 ] i.s.c.r.n.NettyClientChannelManager:170 - [ ] no available service 'null' found, please make sure registry config correct
具体异常发生在
NettyClientChannelManager
,在执行void reconnect(String transactionServiceGroup)
时去查询transactionServiceGroup下的服务是查询不到的,所以打印了错误日志,也就是意味着此时seata的事物管理是失效的。 -
最新seata-spring-boot-starter支持在yml中直接配置相关注册中心的一些参数配置,目前1.1.0存在驼峰转化上的异常,这边跟踪源码得出的兵并且已经提了issues,于1.2.0 版本修复,临时解决方案为
seata.registry.file.name=register.conf
注册中心配置为读取file文件register.conf
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa # 注册中心采用的zk type = "zk" zk { cluster = "default" # 集群配置采用,隔开 serverAddr = "127.0.0.1:2181,127.0.0.1:2183,127.0.0.1:2183" session.timeout = 6000 connect.timeout = 2000 } }
-
DataSource需要包装成seat的DataSourceProxy代理,这边
seata-spring-boot-starter
是通过Spring的BeanPostProcessor
后置在类初始化之后去包装DataSource,相关源码可参照SeataDataSourceBeanPostProcessor
,但是我遇到的情况是我的DataSource初始化要在这个SeataDataSourceBeanPostProcessor
之前,public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansExceptio
拦截不到DataSource类型的对象,所以这边我们选择了关闭掉数据源自动代理,(如果开启的情况下,自行去封装代理DataSourceProxy,代码会抛异常)自己去实现代理的封装,此处采用了aop拦截去处理的。主要代码如下:@Configuration @Import({DataSourceAspect.class}) public class DataSourceConfig { @Autowired private DbPasswordCallback dbPasswordCallback; @Bean @ConfigurationProperties("spring.datasource.druid") public DataSource dataSource() { DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setPasswordCallback(dbPasswordCallback); druidDataSource.setConnectionInitSqls(Collections.singletonList("set names utf8mb4;")); return druidDataSource; } @Bean public DataSourceProxy dataSourceProxy(DataSource dataSource){ return new DataSourceProxy(dataSource); } }
@Aspect public class DataSourceAspect { @Resource private ConfigurableEnvironment environment; @Pointcut("@annotation(org.springframework.context.annotation.Bean)") public void pointCut() { } @Around(value = "pointCut() && @annotation(configProperties)") public Object around(ProceedingJoinPoint joinPoint, ConfigurationProperties configProperties) throws Throwable { Object object = joinPoint.proceed(); if (null != object && (object instanceof DataSource)) { DataSource dataSource = (DataSource) object; bindDataSource(dataSource, configProperties.value()); return new DataSourceProxy(dataSource); } return object; } private void bindDataSource(DataSource dataSource, String name) { Binder binder = Binder.get(environment); Bindable
bindable = Bindable.ofInstance(dataSource); binder.bind(name, bindable); } } - 采用最新的seata版本,mysql驱动版本是有要求的,存在版本不兼容的问题,这个文档是没有说明的,我们需要升级到mysql驱动至少在6.0以上,否则遇到时间处理上,会异常我这边提了issues
通过上面的一系列操作,我们就可以正常的开始我们的seata之旅了。