J~杰's Blog

人生就一条路,走一步有一步的景观

0%

现象

下面我把异常的现象给大家描述一下,小伙伴建了一张表,表的主键是id BigINT,用来存储雪花算法生成的ID,嗯,这个没有问题!

1
2
3
4
5
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
#其他字段省略
);

使用Long 类型对应数据库ID数据。嗯,也没有问题,雪花算法生成的就是一串数字,Long类型属于标准答案!

1
2
3
4
@Data
public class User {
private Long id;
//其他成员变量省略

在后端下断点。看到数据响应以JSON响应给前端,正常

1
2
3
4
{
id:1297873308628307970,
//其他属性省略
}

最后,这条数据返回给前端,前端接收到之后,修改这条数据,后端再次接收回来。奇怪的问题出现了:后端重新接收回来的id变成了:12978733086283000000,不再是1297873308628307970

Read more »

1.背景

Jedis 是一个很老牌的 Redis 的 Java 开发包,使用很稳定,使用范围最广的 Redis 开发包。但是 Jedis 比较推出时间比较早,整个设计思路比较传统,例如不支持异步操作,接口设计比较繁琐老套(相比其他开发包而已),使用连接池占用很多的物理连接资源。当然,这个是可以理解的,比较一个比较早期的开发包,相对其做大的结构调整是很难的,而且用户也不一定会接受。

相比较 Jedis ,我觉得 Lettuce 的优点有如下几个方面:

  • 更加直观、结构更加良好的接口设计
  • 基于 Netty NIO 可以高效管理 Redis 连接,不用连接池方式
  • 支持异步操作(J2Cache 暂时没用到这个特性)
  • 文档非常详尽

LettuceConnectionFactory 类里面有个参数 shareNativeConnection,默认为 true,意思是共用这一个连接,所以默认情况下 lettuce 的连接池是没有用的;如果需要使用连接池,shareNativeConnection 设置为 false 就可以了。

spring-data-redis中的luttucefactory 默认情况是复用一个redis连接的,如果以下情况,则是会新生成一个connection

  • 1.请求批量下发,即禁止调用命令后立即flush
  • 2.使用BLPOP这种阻塞命令
  • 3.事务操作
  • 4.有多个数据库的情况

但在升级之前,我们还需确认lettuce的性能如何,下面就开始lettuce和jedis的性能测试对比。

Read more »

背景

在给业务方用otter做数据迁移时,发现数据库经常出现死锁问题,数据迁移Load阶段的同步性能比较低下,每隔几批就会出现load阶段执行时间超过3s。

同步日志如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Jul  9 13:57:28 otter2 otter-node-prd[235942]: 2021-07-09 13:57:28.867 [pipelineId = 1,taskName = SelectWorker] WARN  com.alibaba.otter.node.etl.select.SelectTask - cost processId:502379 Select耗时:10 数量:1000
Jul 9 13:57:32 otter-chd otter-node-prd[28036]: 2021-07-09 13:57:32.230 [pipelineId = 1,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:502375 Load耗时:3009
Jul 9 13:57:32 otter2 otter-node-prd[235942]: 2021-07-09 13:57:32.673 [pipelineId = 1,taskName = SelectWorker] WARN com.alibaba.otter.node.etl.select.SelectTask - cost processId:502380 Select耗时:14 数量:1000
Jul 9 13:57:35 otter-chd otter-node-prd[28036]: 2021-07-09 13:57:35.926 [pipelineId = 1,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:502376 Load耗时:3008
Jul 9 13:57:36 otter2 otter-node-prd[235942]: 2021-07-09 13:57:36.369 [pipelineId = 1,taskName = SelectWorker] WARN com.alibaba.otter.node.etl.select.SelectTask - cost processId:502381 Select耗时:14 数量:1000
Jul 9 13:57:37 toh6 otter-node-prd[5575]: 2021-07-09 13:57:37.285 [pipelineId = 4,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:263714 Load耗时:3
Jul 9 13:57:39 otter-chd otter-node-prd[28036]: 2021-07-09 13:57:39.626 [pipelineId = 1,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:502377 Load耗时:3008
Jul 9 13:57:40 otter2 otter-node-prd[235942]: 2021-07-09 13:57:40.136 [pipelineId = 1,taskName = SelectWorker] WARN com.alibaba.otter.node.etl.select.SelectTask - cost processId:502382 Select耗时:10 数量:1000
Jul 9 13:57:41 toh6 otter-node-prd[5575]: 2021-07-09 13:57:41.288 [pipelineId = 4,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:263715 Load耗时:2
Jul 9 13:57:43 toh6 otter-node-prd[5575]: 2021-07-09 13:57:43.280 [pipelineId = 4,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:263716 Load耗时:3
Jul 9 13:57:43 otter-chd otter-node-prd[28036]: 2021-07-09 13:57:43.393 [pipelineId = 1,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:502378 Load耗时:3007
Jul 9 13:57:43 otter2 otter-node-prd[235942]: 2021-07-09 13:57:43.892 [pipelineId = 1,taskName = SelectWorker] WARN com.alibaba.otter.node.etl.select.SelectTask - cost processId:502383 Select耗时:12 数量:1000
Jul 9 13:57:53 otter-chd otter-node-prd[28036]: 2021-07-09 13:57:53.226 [pipelineId = 1,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:502379 Load耗时:9012
Jul 9 13:57:54 otter2 otter-node-prd[235942]: 2021-07-09 13:57:53.955 [pipelineId = 1,taskName = SelectWorker] WARN com.alibaba.otter.node.etl.select.SelectTask - cost processId:502384 Select耗时:13 数量:1000
Jul 9 13:57:57 toh6 otter-node-prd[5575]: 2021-07-09 13:57:57.291 [pipelineId = 4,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:263717 Load耗时:3
Jul 9 13:57:57 otter-chd otter-node-prd[28036]: 2021-07-09 13:57:57.221 [pipelineId = 1,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:502380 Load耗时:3010
Jul 9 13:57:57 otter2 otter-node-prd[235942]: 2021-07-09 13:57:57.696 [pipelineId = 1,taskName = SelectWorker] WARN com.alibaba.otter.node.etl.select.SelectTask - cost processId:502385 Select耗时:12 数量:1000
Jul 9 13:57:59 toh6 otter-node-prd[5575]: 2021-07-09 13:57:59.303 [pipelineId = 4,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:263718 Load耗时:2
Jul 9 13:58:01 otter-chd otter-node-prd[28036]: 2021-07-09 13:58:00.997 [pipelineId = 1,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:502381 Load耗时:3009
Jul 9 13:58:01 toh6 otter-node-prd[5575]: 2021-07-09 13:58:01.287 [pipelineId = 4,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:263719 Load耗时:2
Jul 9 13:58:01 otter2 otter-node-prd[235942]: 2021-07-09 13:58:01.464 [pipelineId = 1,taskName = SelectWorker] WARN com.alibaba.otter.node.etl.select.SelectTask - cost processId:502386 Select耗时:13 数量:1000
Jul 9 13:58:03 toh6 otter-node-prd[5575]: 2021-07-09 13:58:03.303 [pipelineId = 4,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:263720 Load耗时:3
Jul 9 13:58:04 otter-chd otter-node-prd[28036]: 2021-07-09 13:58:04.728 [pipelineId = 1,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:502382 Load耗时:3009
Jul 9 13:58:05 otter2 otter-node-prd[235942]: 2021-07-09 13:58:05.205 [pipelineId = 1,taskName = SelectWorker] WARN com.alibaba.otter.node.etl.select.SelectTask - cost processId:502387 Select耗时:6 数量:1000
Jul 9 13:58:05 toh6 otter-node-prd[5575]: 2021-07-09 13:58:05.301 [pipelineId = 4,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:263721 Load耗时:3
Jul 9 13:58:08 otter-chd otter-node-prd[28036]: 2021-07-09 13:58:08.475 [pipelineId = 1,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:502383 Load耗时:3011
Jul 9 13:58:09 otter2 otter-node-prd[235942]: 2021-07-09 13:58:08.949 [pipelineId = 1,taskName = SelectWorker] WARN com.alibaba.otter.node.etl.select.SelectTask - cost processId:502388 Select耗时:11 数量:1000
Jul 9 13:58:12 otter-chd otter-node-prd[28036]: 2021-07-09 13:58:12.225 [pipelineId = 1,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:502384 Load耗时:3015
Jul 9 13:58:12 otter2 otter-node-prd[235942]: 2021-07-09 13:58:12.595 [pipelineId = 1,taskName = SelectWorker] WARN com.alibaba.otter.node.etl.select.SelectTask - cost processId:502389 Select耗时:12 数量:1000
Jul 9 13:58:15 toh6 otter-node-prd[5575]: 2021-07-09 13:58:15.301 [pipelineId = 4,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:263722 Load耗时:3
Jul 9 13:58:15 otter-chd otter-node-prd[28036]: 2021-07-09 13:58:15.851 [pipelineId = 1,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:502385 Load耗时:3009
Jul 9 13:58:16 otter2 otter-node-prd[235942]: 2021-07-09 13:58:16.326 [pipelineId = 1,taskName = SelectWorker] WARN com.alibaba.otter.node.etl.select.SelectTask - cost processId:502390 Select耗时:12 数量:1000
Jul 9 13:58:17 toh6 otter-node-prd[5575]: 2021-07-09 13:58:17.304 [pipelineId = 4,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:263723 Load耗时:2
Jul 9 13:58:19 otter-chd otter-node-prd[28036]: 2021-07-09 13:58:19.553 [pipelineId = 1,taskName = LoadWorker] WARN com.alibaba.otter.node.etl.load.LoadTask - cost processId:502386 Load耗时:3009
Jul 9 13:58:20 otter2 otter-node-prd[235942]: 2021-07-09 13:58:20.089 [pipelineId = 1,taskName = SelectWorker] WARN com.alibaba.otter.node.etl.select.SelectTask - cost processId:502391 Select耗时:13 数量:1000
Read more »

Saga简介

Saga 是一种补偿协议,在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。

分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。
状态图如下:

状态图

Saga 模式下分布式事务通常是由事件驱动的,各个参与者之间是异步执行的,Saga 模式是一种长事务解决方案。

事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,可以使用 Saga 模式。

Saga模式的优势是:

  • 一阶段提交本地数据库事务,无锁,高性能;
  • 参与者可以采用事务驱动异步执行,高吞吐;
  • 补偿服务即正向服务的“反向”,易于理解,易于实现;

Saga模式缺点:

Saga 模式由于一阶段已经提交本地数据库事务,且没有进行“预留”动作,所以不能保证隔离性。

基于状态机引擎的 Saga 实现

基本原理:

  1. 基于json格式定义服务调用状态图;
  2. 状态图的一个节点可以是一个服务,节点可以配置补偿节点;
  3. 状态图json由状态机执行引擎驱动执行,当出现异常状态时状态机引擎执行反向补偿任务将事物回滚;
  4. 异常状态发生时是否进行补偿由用户自定义决定;
  5. 可以实现服务编排的需求,支持单项选择、并发、异步、子状态机调用、参数转换、参数映射、服务执行状态判断、异常捕获等功能;

状态图

Read more »

TCC简介

在2PC(两阶段提交)协议中,事务管理器分两阶段协调资源管理,资源管理器对外提供了3个操作,分别是一阶段的准备操作,二阶段的提交操作和回滚操作;

TCC服务作为一种事务资源,遵循两阶段提交协议,由业务层面自定义,需要用户根据业务逻辑编码实现;其包含Try、Confirm 和 Cancel 3个操作,其中Try操作对应分布式事务一阶段的准备,Confirm操作对应分布式事务二阶段提交,Cancel对应分布式事务二阶段回滚:

  • Try:资源的检查和预留;

  • Comfirm:使用预留的资源,完成真正的业务操作;要求Try成功Confirm 一定要能成功;

  • Cancel:释放预留资源;

TCC的3个方法均由用户根据业务场景编码实现,并对外发布成微服务,供事务管理器调用;事务管理器在一阶段调用TCC的Try方法,在二阶段提交时调用Confirm方法,在二阶段回滚时调用Cancel方法。

seata tcc实现

TCC服务由用户编码实现并对外发布成微服务,目前支持3种形式的TCC微服务,分别是:

  • SofaRpc服务-蚂蚁开源:用户将实现的TCC操作对外发布成 SofaRpc 服务,事务管理器通过订阅SofaRpc服务,来协调TCC资源;
  • Dubbo服务:将TCC发布成dubbo服务,事务管理器订阅dubbo服务,来协调TCC资源;
  • Local TCC:本地普通的TCC Bean,非远程服务;事务管理器通过本地方法调用,来协调TCC 资源;

目前荐于我司使用的微服务是spring cloud组件,微服务调用的rpc是feign(http协议),故tcc选取用local tcc模式即可!!!

Read more »

seata AT模式简介

AT模式是 Seata 主推的分布式事务解决方案,它使得应用代码可以像使用本地事物一样使用分布式事物,完全屏蔽了底层细节,主要有以下几点:

  • AT模式依赖全局事物注解和代理数据源,其余代码不需要变更,对业务无侵入、接入成本低;
  • AT模式的作用范围在于底层数据,通过保存操作行记录的前后快照和生成反向SQL语句进行补偿操作,对上层应用透明;
  • AT模式需借助全局锁和GlobalLock注解来解决全局事务间的写冲突问题,如果一阶段分支事物成功则二阶段一开始全局锁即被释放,否则需要等到分支事务二阶段回滚完成才能释放全局锁;

seata AT工作流程

seata AT工作流程

概括来讲,AT 模式的工作流程分为两阶段。一阶段进行业务 SQL 执行,并通过 SQL 拦截、SQL 改写等过程生成修改数据前后的快照(Image),并作为 UndoLog 和业务修改在同一个本地事务中提交。

如果一阶段成功那么二阶段仅仅异步删除刚刚插入的 UndoLog;如果二阶段失败则通过 UndoLog 生成反向 SQL 语句回滚一阶段的数据修改。

Read more »

背景

我们公司在升级Apollo1.8.1版本之后,发Item表的创建时间和更新时间字段与portal上展示差了13个小时,现象如下:

Item表结构:

排查过程

  1. 刚开始我们想到肯定是数据库的时区与Adminservice服务器时间的时区不一致导致的。

    Adminservice所在的服务器时区是CST中国标准时间:

    Mysql服务器的时区如下:

    发现服务器和mysql服务器的时区是一致的,在加上和运维沟通后他们最近也没有升级服务器相关,应该不是这里的问题。

    Read more »

背景

在线下环境给业务方同步全量同步数据中,写目标库的时候发生了写入失败,报错信息如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
pid:1 nid:1 exception:setl:com.alibaba.otter.node.etl.load.exception.LoadException: java.util.concurrent.ExecutionException: com.alibaba.otter.node.etl.load.exception.LoadException: com.alibaba.otter.node.etl.load.exception.LoadException: com.alibaba.otter.node.etl.load.exception.LoadException: org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [insert into `drugs`.`user_pro_device_bind_log`(`userProDeviceId` , `userProDeviceBindLogType` , `processDate` , `uuid` , `appDeviceType` , `createDate` , `modifyDate` , `id`) values (? , ? , ? , ? , ? , ? , ? , ?) on duplicate key update `userProDeviceId`=values(`userProDeviceId`) , `userProDeviceBindLogType`=values(`userProDeviceBindLogType`) , `processDate`=values(`processDate`) , `uuid`=values(`uuid`) , `appDeviceType`=values(`appDeviceType`) , `createDate`=values(`createDate`) , `modifyDate`=values(`modifyDate`) , `id`=values(`id`)]; Data truncation: Incorrect datetime value: '0000-00-00 00:00:00' for column 'createDate' at row 1; nested exception is com.mysql.jdbc.MysqlDataTruncation: Data truncation: Incorrect datetime value: '0000-00-00 00:00:00' for column 'createDate' at row 1
at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:101)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:80)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:603)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:812)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:868)
at com.alibaba.otter.node.etl.load.loader.db.DbLoadAction$DbLoadWorker$2.doInTransaction(DbLoadAction.java:655)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:130)
at com.alibaba.otter.node.etl.load.loader.db.DbLoadAction$DbLoadWorker.doCall(DbLoadAction.java:647)
at com.alibaba.otter.node.etl.load.loader.db.DbLoadAction$DbLoadWorker.call(DbLoadAction.java:574)
at com.alibaba.otter.node.etl.load.loader.db.DbLoadAction.doTwoPhase(DbLoadAction.java:485)
at com.alibaba.otter.node.etl.load.loader.db.DbLoadAction.doLoad(DbLoadAction.java:279)
at com.alibaba.otter.node.etl.load.loader.db.DbLoadAction.load(DbLoadAction.java:165)
at com.alibaba.otter.node.etl.load.loader.db.DbLoadAction$$FastClassByCGLIB$$d932a4cb.invoke()
at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:191)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:618)
at com.alibaba.otter.node.etl.load.loader.db.DbLoadAction$$EnhancerByCGLIB$$80fd23c2.load()
at com.alibaba.otter.node.etl.load.loader.db.DataBatchLoader$2.call(DataBatchLoader.java:192)
at com.alibaba.otter.node.etl.load.loader.db.DataBatchLoader$2.call(DataBatchLoader.java:183)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)

从业务方了解到,源库是mysql5.6版本,字段类型默认DEFAULT ‘0000-00-00 00:00:00’ 处理,目标库是mysql5.7,表结构如下:

1
2
3
4
5
6
7
8
CREATE TABLE `tanlb...` (
// .......
`fromDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '起始的过期时间',
`toDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '在经过相关业务以后过期时间,业务类型取决于@userProStatLogType',
`createDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '激活时间',
`modifyDate` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '过期时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=153233 DEFAULT CHARSET=utf8mb4;

解决方案

连接数据库转化为对象出错的解决办法为在数据库连接后面加上参数zeroDateTimeBehavior=convertToNull 这样如果碰到 ‘0000-00-00:00:00:00’的日期类型时,将会转化为null值

1
jdbcurl=jdbc:mysql://192.168.1.52:3306/db?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull

针对数据插入数据‘0000-00-00:00:00:00’ 数据本身不接受的解决办法为,用root用户登录,重新设置数据库的模式(尽量使用root用户 要不然 GLOBAL设置不成功,但是可以设置SESSION的)

  1. 查询数据库现有的模式

    1
    select @@sql_mode;
  2. 把NO_ZERO_IN_DATE,NO_ZERO_DATE去掉,然后重新设置

    1
    SET GLOBAL sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'

    这样关闭数据库客户端的连接,重新登录,然后再执行那种比较操蛋的插入语句即可正确的插入。

背景

最近业务团队需要实时同步Mysql—>ES数据的需求,本来想基于canal-adapter直接开发业务,后来觉得业务方可能有对binlog的订阅共性,最终我们决定还是在otter上做改造,支持Rocket-Mq输出。

整理的需求点如下:

  • 支持输出平台RocketMQ服务/云上RocketMQ服务;

  • 支持业务方指定某些表,某些表可以指定事件(Insert、Update、Delete事件);

  • 直接全量数据迁移至RocketMQ Topic;

  • 支持跨机房传输到异地RocketMQ;

架构图

Read more »