当前位置: 首页 > news >正文

记一次 MySQL 主从同步异常的排查记录,百转千回

本文主要内容如下:

一、现象

最近项目的测试环境遇到一个主备同步的问题:

备库的同步线程停止了,无法同步主库的数据更改。

备库报错如下:

完整的错误信息:

Relay log read failure: Could not parse relay log event entry. The possible reasons are: the master's binary log is corrupted (you can check this by running 'mysqlbinlog' on the binary log), the slave's relay log is corrupted (you can check this by running 'mysqlbinlog' on the relay log), a network problem, or a bug in the master's or slave's MySQL code. If you want to check the master's binary log or slave's relay log, you will be able to know their names by issuing 'SHOW SLAVE STATUS' on this slave.

上面的报错信息是什么意思呢?

翻译一下就是主库的 binlog 或者从库的 relay log 损坏了,造成这个问题的原因:

  • 可能是网络问题。
  • 也可能是主库或备库的代码 bug。

首先我们还是得复习下主从同步的原理才能更好地分析原因。

二、主从同步的原理

首先我们还是得复习下主从同步的原理才能更好地分析原因。

  • 从库会生成两个线程,一个 I/O 线程,名字叫做 Slave_IO_Running,另外一个是 SQL 线程,名字叫做 Slave_SQL_Running;
  • 从库的 I/O 线程会去请求主库的 binlog 日志文件,并将得到的 binlog 日志文件写到本地的 relay-log (中继日志)文件中;
  • 主库会生成一个 dump 线程,用来给从库 I/O 线程传 binlog;
  • 从库 SQL 线程,会读取 relay log 文件中的日志,并解析成 SQL 语句逐一执行。

三、排查思路

3.1 分析从库的同步状态

我们可以打印下从库的同步状态,看到如下几个关键信息:

Master_Log_File: mysql-bin.000956,代表从库读到的主库的 binlog file,

Read_Master_Log_Pos: 528071913,代表从库读到的主库的 binlog file 的日志偏移量

Relay_Log_File: relay-bin.000094,代表从库执行到了哪一个 relay log

Relay_Log_Pos: 123408769,代表从库执行的 relay log file 的日志偏移量

Relay_Master_Log_File: mysql-bin.000955,代表从库已经重放到了主库的哪个 binlog file。

Exec_Master_Log_Pos: 123408556,代表从库已经重放到了主库 binlog file 的偏移量。

Slave_IO_Running: Yes,说明 I/O 线程正在运行,可以正常获取 binlog 并生成 relay log。

Slave_SQL_Running: No,说明 SQL 线程已经停止运行,不能正常解析 relay log,也就不能执行主库上已经执行的命令。

Master_Log_File 和 Read_Master_Log_Pos 这两个参数合起来表示的是读到的主库的最新位点。

Relay_Master_Log_File 和 Exec_Master_Log_Pos,这两个参数合起来表示的是从库执行的最新位点。

如果红色框起来的两个参数:Master_Log_File 和 Relay_Master_Log_File 相等,则说明从库读到的最新文件和主库上生成的文件相同,这里前者是 mysql-bin.000956,后者是 mysql-bin.000955,说明两者不相同,存在主从不同步。

如果蓝色框起来的两个参数 Read_Master_Log_Pos 和 Exec_Master_Log_Pos 相等,则说明从库读到的日志文件的位置和从库上执行日志文件的位置相同,这里不相等,说明主从不同步。

当上面两组参数都相等时,则说明主从同步正常,且没有延迟。只要有任意一组不相等,则说明主从不同步,可能是从库停止同步了,或者从库存在同步延迟。由于上面的 SQL 线程已经停止了,说明是从库同步出现问题了。

从库同步出现的问题在最开始的报错信息里面已经提到了,可能是网络问题导致,还有可能是 binlog 或 relay log 损坏。

3.2 重启万能大法

先通过重启来恢复从库的 SQL 线程试试看?重启方式就是两种:

  • 方式一:从库重新开启同步。就是执行 stop slave; 和 start slave; 命令。
  • 方式二:重启从库实例。就是重启 mysql 实例或 mysql 容器。

这两种方式试了后,都不能恢复从库的 SQL 线程。

3.3 查看 binlog

再来看下 binlog 是否有损坏,在主库上通过这个命令打开 mysql-bin.000955 文件。

mysqlbinlog /var/lib/mysql/log/mysql-bin.000955

没有报错信息,如下图所示:

3.4 查看 relay log

看到从库同步的 Relay_Log_File 到 relay-bin.00094 就停止同步了,如下图所示,可能是这个文件损坏了。

在从库上通过 mysqlbinlog 命令打开这个文件

mysqlbinlog /var/lib/mysql/log/relay-bin.000094

可以看到有个报错信息:

ERROR: Error in Log_event::read_log_event(): 'read error', data_len: 7644, event_type: 31
ERROR: Could not read entry at offset 243899899: Error in log format or read error.

这段文字翻译过来就是读取错误,数据长度 7644,在读取偏移量为 243899899 的日志时发生了错误,可能是日志文件格式错误或是读取文件错误。

3.5 找原因

3.5.1 猜测事务日志太大

根据这个报错信息可以知道这个事务日志数据太长了,data_len: 7644,而导致读取错误。

而且上面还有很多 Update_rows 的操作。

猜测:会不会是主库执行了一个大事务,造成该事务生成的一条 binlog 日志太大了,从库生成的对应的一条 relay log 日志也很大, SQL 线程去解析这条 relay log 日志解析报错。

3.5.2 验证

到主库上查看下 binlog 日志里面有没有在那个时间点做特殊操作。

感觉快找到原因了。执行以下命令来查看

mysqlbinlog File --stop-datetime=T --start-datetime=T

stop-datetime 指定为读取 relay log 报错的时刻 2023-04-04 16:47:16,

start-datetime 指定为读取 relay log 报错的时刻 2023-04-04 16:47:30。

发现并没有找到 Update_rows 的操作。继续把时间往后加一点,经过多次尝试,把时间锁定在了 2023-04-04 17:00:30~17:00:31。这 1s 内能找到 2023-04-04 16:47:16 的操作日志。

日志如下,这个命令会打印 N 多日志,直接把屏幕打满了!!

难道真的 binlog 对应的这条事务日志太大了吗???

存疑: 2023-04-04 16:47:16 时刻对数据库中的表做了某个大事务的操作,造成该事务对应的这条 binlog 日志很大很大。生成的 relay log 也很大,SQL 线程解析 relay log 报错。

3.6 这是真相吗?

问了下熟悉这张表的同事,有没有在这个时刻做什么大事务操作。

同事看了下代码,发现有个批量插入的操作,一次执行 400 条,难道是 400 条太多了???这不应该是真正的原因,400 条也不多。

不经意间问了下这张表的数据量有多大,该同事在 4月4号 16:45:25 做了一个手动备份 xx_dance 表的操作,这张表有 25 万条数据。

这个备份操作是在一个事务里面执行的,生成的一条 binlog 日志很大。

这里只是一个猜测,还未得到验证,文末会说明真正的原因。

如果真的是这样,那我可以先恢复从库的同步,备份表的操作在从库上其实不需要。

3.7 GTID

不知道细心的你是否有发现上面的 binlog 里面有一个GTID

'c5d74746-d7ec-11ec-bf8f-0242ac110002:8634832

记住 GTID 中的数字 8634832,后面恢复从库同步时要用到。

我们再来看下从库的状态,发现也有一个 GTID,如下图所示,值为 8634831正好相差 1,感觉这两个 GTID 值之间有不可告人的秘密。

那么从库 SQL 线程停止运行的原因就是卡在 8634832 这里了,我们可否跳过这个 GTID 呢?

你可能对 GTID 的原理很感兴趣,可以查看之前悟空写的一篇文章:

MySQL 主从模式采用 GTID 的实践

四、GTID 同步方式的原理

这里还是把主从同步采用 GTID 方式的流程拿出来看下,帮助大家快速回顾下,熟悉的同学可以跳过本节内容。

GTID 方案:主库计算主库 GTID 集合和从库 GTID 的集合的差集,然后主库推送差集 binlog 给从库。

当从库设置完同步参数后,假定主库 A 的GTID 集合记为集合 x,从库 B 的 GTID 集合记为 y。

从库同步的逻辑如下:

  • 从库 B 指定主库 A,基于主备协议建立连接。
  • 从库 B 把集合 y 发给主库 A。
  • 主库 A 计算出集合 x 和集合 y 的差集,也就是集合 x 中存在,集合 y 中不存在的 GTID 集合。比如集合 x 是 1~100,集合 y 是 1~90,那么这个差集就是 91~100。这里会判断集合 x 是不是包含有集合 y 的所有 GTID,如果不是则说明主库 A 删除了从库 B 需要的 binlog,主库 A 直接返回错误。
  • 主库 A 从自己的 binlog 文件里面,找到第一个不在集合 y 中的事务 GTID,也就是找到了 91。
  • 主库 A 从 GTID = 91 的事务开始,往后读 binlog 文件,按顺序取 binlog,然后发给 B。
  • 从库 B 的 I/O 线程读取 binlog 文件生成 relay log,SQL 线程解析 relay log,然后执行 SQL 语句。

GTID 同步方案和位点同步的方案区别是:

  • 位点同步方案是通过人工在从库上指定哪个位点,主库就发哪个位点,不做日志的完整性判断。
  • 而 GTID 方案是通过主库来自动计算位点的,不需要人工去设置位点,对运维人员友好。

五、恢复从库的同步

5.1 查看从库执行 GTID 的进度

在从库上执行 show slave status \G来查看 GTID 集合。

Retrieved_Gtid_Set 表示从库收到的所有日志的 GTID 集合。

Executed_Gtid_Set 表示从库已经执行完成的 GTID 集合。

如果 Executed_Gtid_Set 集合是包含 Retrieved_Gtid_Set,则表示从库接收到的日志已经同步完成。

这里 Executed_Gtid_Set 的集合为 1-8634831,而 Retrieved_Gtid_Set 为 1-9101426,说明从库有些 GTID 是没有执行的。从库已经执行到了 8634831,下一个要执行的 GTID 为 8634832。

因为我们采用的同步方式是 GTID 方式,所以只要让从库跳过这个 GTID ,从下一个 GTID 开始同步就行。

带来的问题就是这个 GTID 对应的事务没有执行。因为报错的操作是从库备份一张大表,所以从库跳过这个备份操作也是可以接受的。

5.2 手动设置 GTID

来,手动设置一把 GTID 试下。

5.2.1 重置从库进度

首先重置下从库同步的进度,这条命令会把 relog 给清理掉,不过重新开启同步后,主库会计算主库 GTID 集合和从库 GTID 的集合的差集,然后主库推送差集 binlog 给从库。

stop slave;
reset slave;

5.2.1 设置 GTID 为一个值

执行以下命令设置 GTID 为下一个值。

set gtid_next='c5d74746-d7ec-11ec-bf8f-0242ac110002:8634832';
begin;
commit;
set gtid_next=automatic;
start slave;

gtid_next 表示设置下一个 GTID = 8634832,这个值是在原来的 8634831 加 1。后面的 begin 和 commit 是提交了一个空事务,把这个 GTID 加到从库的 GTID 集合中。那么从库的 GTID 集合就变成了

'c5d74746-d7ec-11ec-bf8f-0242ac110002:1-8634832';

5.2.2 查看当前 GTID 集合

我们可以通过 show master status\G 命令来查看从库的 GTID 集合。下方截图是执行上述命令之前的。GTID集合为 1-8634831。另外 GTID 集合 为 1 和 GTID 集合为 1-4 的可以忽略,因为它们前面的 Master_UUID 不是当前主库的 uuid。

也可以通过 show slave status\G 命令来查看 GTID 集合,结果也是一样的。

5.3 开启从库同步

再次启动从库的同步(start slave 命令),I/O 线程和 SQL 线程的状态都为 YES,说明启动成功了。

而且查看从库的同步状态时,观察到从库的同步是存在延迟的。通过观察这个字段 Seconds_Behind_Master 在不断减小,说明主从同步的延迟越来越小了。

过一段时间后,执行的 GTID 等于收到的 GTID 集合,Seconds_Behind_Master = 0,说明主从完全同步了。

六、原因

上面的推测:* *备份大表造成 binlog 的一条日志太大,relay log 也跟着变大,SQL 线程无法正常解析**。

但这是真相吗?

虽然从库重新开启了同步,且跳过了这条日志,但带来的是从库上就不会出现这个备用表 xx_dance_0404 。

但出现了两个奇怪的问题:

问题 1:从库开启同步后,居然出现了这个备份表 xx_dance_0404。不是跳过这个备份操作了吗?目前没想到原因。

问题 2:为了重现这个问题,我到主库上做了一个备份表的操作,表名为 xx_dance_0412,从库也同步了这个新的备份表 xx_dance_0412。而且 binlog 出现的日志现象也是一样的,对应的这条 binlog 日志也很大,但是从库同步正常。我又备份了一张 300 万的大表,依然没重现。

通过问题 2 可以说明上面的推测是错误的,备份大表并不会影响主从同步

那么 relay log 报错的原因是什么?

只有一个原因了,relay log 文件真的是损坏的,从库的状态上也说明了原因,relay log is corrupted(损坏)。SQL 线程去解析 relay log 时报错了,导致 SQL 线程停止,从库不能正常执行同步。

小结: relay log 损坏了,导致从库的 SQL 线程解析 relay log 时出现异常。从库恢复方式是通过手动设置当时出错的 GTID 的下一个值,让从库不从主库同步这个 GTID,最后从库就能正常同步这个 GTID 之后的 binlog 了,后续 SQL 线程也能正常解析 relay log 了。

相关文章:

记一次 MySQL 主从同步异常的排查记录,百转千回

本文主要内容如下: 一、现象 最近项目的测试环境遇到一个主备同步的问题: 备库的同步线程停止了,无法同步主库的数据更改。 备库报错如下: 完整的错误信息: Relay log read failure: Could not parse relay log even…...

Cpython的多线程技术之痛

历史原因 在Python官网下载的默认解释器是采用C语言编写的Cpython解释器。在Python语言开发之初,计算机都是单核CPU,每个单核CPU同一时刻只能运行一个线程。为了模拟多线程工作,这里采用了模拟机制,让不同线程根据时间片段&#…...

NDK OpenGL离屏渲染与工程代码整合

NDK​系列之OpenGL离屏渲染与工程代码整合,本节主要是对上一节OpenGL渲染画面效果代码进行封装设计,将各种特效代码进行分离解耦,便于后期增加其他特效。 实现效果: 实现逻辑: 1.封装BaseFilter过滤器基类&#xff0c…...

Python基础入门编程代码练习(二)

一、求1~100之间不能被3整除的数之和 循环条件&#xff1a;i<100循环操作 实现代码如下&#xff1a; def sums():sum 0for num in range(1, 101):if num % 3 ! 0:sum numprint("1~100之间不能被3整除的数之和为&#xff1a;%s" % (sum))sums() print("1~…...

C# | 对象池

对象池 文章目录 对象池前言什么是对象池对象池的优点对象池的缺点 实现思路示例代码 结束语 前言 当我们开发一个系统或者应用程序时&#xff0c;我们通常需要创建很多的对象&#xff0c;这些对象可能是线程、内存、数据库连接、文件句柄等等。在某些情况下&#xff0c;我们需…...

CSS小技巧之圆形虚线边框

虚线相信大家日常都用的比较多&#xff0c;常见的用法就是使用 border-style 控制不同的样式&#xff0c;比如设置如下边框代码&#xff1a; border-style: dotted dashed solid double;这将设置顶部的边框样式为点状&#xff0c;右边的边框样式为虚线&#xff0c;底部的边框样…...

QString与QByteArray互相转换的方法

QString与QByteArray互相转换的方法 [1] QString与QByteArray互相转换的方法QString转QByteArray方法QByteArray转QString方法QByteArray类同样不以’\0’为结尾QByteArray转QString&#xff0c;主要用buf.toHex()即可 [2] Qt开发串口通讯软件中的数据转换问题1.读取串口命令-Q…...

Springboot +Flowable,设置流程变量的方式(一)

一.简介 为什么需要流程变量。 举个例子&#xff0c;假设有如下一个流程&#xff0c;截图如下&#xff1a; 这是一个请假流程&#xff0c;那么谁请假、请几天、起始时间、请假理由等等&#xff0c;这些都需要说明&#xff0c;不然领导审批的依据是啥&#xff1f;那么如何传递…...

机器学习13(正则化)

文章目录 简介正则化经验风险和结构风险过拟合正则化建模策略 逻辑回归逻辑回归评估器 练习评估器训练与过拟合实验评估器的手动调参 简介 这一节详细探讨关于正则化的相关内容&#xff0c;并就 sklearn 中逻辑回归&#xff08;评估器&#xff09;的参数进行详细解释由于 skle…...

并发编程学习(十一):原子数组、

1、数组类型的原子类 原子数组类型&#xff0c;这个其实和AtomicInteger等类似&#xff0c;只不过在修改时需要指明数组下标。 CAS是按照来根据地址进行比较。数组比较地址&#xff0c;肯定是不行的&#xff0c;只能比较下标元素。而比较下标元素&#xff0c;就和元素的…...

递归到动态规划:省去枚举行为

如果在动态规划的过程中没有枚举行为&#xff0c;那严格位置依赖和傻缓存的方式并没有太大区别&#xff0c;但是当有枚举行为的时候&#xff08;一个位置依赖于多个位置&#xff09;&#xff0c;那严格位置依赖是有优化空间的&#xff0c;枚举行为也许可以省去&#xff0c;题目…...

服务(第二十一篇)mysql高级查询语句(二)

①视图表&#xff1a; 视图表是虚拟表&#xff0c;用来存储SQL语句的定义 如果视图表和原表的字段相同&#xff0c;是可以进行数据修改的&#xff1b; 如果两者的字段不通&#xff0c;不可以修改数据。 语法&#xff1a; 创建&#xff1a;create view 试图表名 as ... 查…...

MYSQL高可用配置(MHA)

1、什么是MHA MHA&#xff08;Master High Availability&#xff09;是一套优秀的MySQL高可用环境下故障切换和主从复制的软件。 MHA 的出现就是解决MySQL 单点的问题。 MySQL故障切换过程中&#xff0c;MHA能做到0-30秒内自动完成故障切换操作。 MHA能在故障切换的过程中最大…...

单精度浮点数与十进制数据相互转换

一、float基础&#xff1a; Float类型占4个字节,也就是32bit,其中最高位是符号位,2~9位是指数位,后边的23bit是数值位.如下所示 大部分数据的二进制形式都可以用科学计数法表示,即1.m*2^n这种形式,只要知道m和n,就能确定一个数值 二、小数位如何转变为二进制&#xff1a; 下面…...

PMP敏捷-4大价值观、12原则

宣言及4大价值观 个体及互动 胜于 流程和工具 以人为本 工作的软件 胜于 完整的文档 以价值为导向 客户合作 胜于 合同谈判 合作共赢 应对变更 胜于 遵循计划 拥抱变化 12原则 工作原则&#xff1a;精益、至简&#xff0c;实现这种原则的方式是“定期反省”。9、10、12 …...

K8S—Helm

一、Helm介绍 helm通过打包的方式&#xff0c;支持发布的版本管理和控制&#xff0c;很大程度上简化了Kubernetes应用的部署和管理。 Helm本质就是让k8s的应用管理&#xff08;Deployment、Service等&#xff09;可配置&#xff0c;能动态生成。通过动态生成K8S资源清单文件&a…...

ALSA内部函数调用流程

ALSA内部函数调用流程 一直都有这样的一个疑问 就是在linux系统中我们调用snd_pcm_open后&#xff0c;就不知道alsa内部是怎么运行的了 用户的pcm_open()相当于先对ASoC各个驱动模块startup()&#xff0c;再做hw_params()。 pcm_open()pcm->fd open("/dev/snd/pcm…...

Python正则表达式详解,保姆式教学,0基础也能掌握正则

正则作为处理字符串的一个实用工具&#xff0c;在Python中经常会用到&#xff0c;比如爬虫爬取数据时常用正则来检索字符串等等。正则表达式已经内嵌在Python中&#xff0c;通过导入re模块就可以使用&#xff0c;作为刚学Python的新手大多数都听说”正则“这个术语。 今天来给…...

ChatGPT 接入飞书教程,创建自己的聊天机器人

ChatGPT 接入飞书教程,创建自己的聊天机器人 一、飞书进入开发者平台。点击创建应用。二、打开Aircode,点击创建应用,上面输入名字,下面选择Node.js v16三、配置环境,点击Environments,创建四个变量,全部要大写本教程收集于: AIGC从入门到精通教程 首先,准备三个账号…...

JS生成随机数(多种解决方案)

JS生成随机数 概述 随机数是编程语言中的重要组成部分。在JavaScript中&#xff0c;生成随机数是一项简单的任务。本文将介绍生成随机数的各种方法。 Math.random() Math.random()是JavaScript中生成随机数最常见的方法。该方法返回介于0和1之间的随机数。例如&#xff0c;…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

LeetCode - 394. 字符串解码

题目 394. 字符串解码 - 力扣&#xff08;LeetCode&#xff09; 思路 使用两个栈&#xff1a;一个存储重复次数&#xff0c;一个存储字符串 遍历输入字符串&#xff1a; 数字处理&#xff1a;遇到数字时&#xff0c;累积计算重复次数左括号处理&#xff1a;保存当前状态&a…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

Xela矩阵三轴触觉传感器的工作原理解析与应用场景

Xela矩阵三轴触觉传感器通过先进技术模拟人类触觉感知&#xff0c;帮助设备实现精确的力测量与位移监测。其核心功能基于磁性三维力测量与空间位移测量&#xff0c;能够捕捉多维触觉信息。该传感器的设计不仅提升了触觉感知的精度&#xff0c;还为机器人、医疗设备和制造业的智…...

五子棋测试用例

一.项目背景 1.1 项目简介 传统棋类文化的推广 五子棋是一种古老的棋类游戏&#xff0c;有着深厚的文化底蕴。通过将五子棋制作成网页游戏&#xff0c;可以让更多的人了解和接触到这一传统棋类文化。无论是国内还是国外的玩家&#xff0c;都可以通过网页五子棋感受到东方棋类…...

C++--string的模拟实现

一,引言 string的模拟实现是只对string对象中给的主要功能经行模拟实现&#xff0c;其目的是加强对string的底层了解&#xff0c;以便于在以后的学习或者工作中更加熟练的使用string。本文中的代码仅供参考并不唯一。 二,默认成员函数 string主要有三个成员变量&#xff0c;…...

精益数据分析(98/126):电商转化率优化与网站性能的底层逻辑

精益数据分析&#xff08;98/126&#xff09;&#xff1a;电商转化率优化与网站性能的底层逻辑 在电子商务领域&#xff0c;转化率与网站性能是决定商业成败的核心指标。今天&#xff0c;我们将深入解析不同类型电商平台的转化率基准&#xff0c;探讨页面加载速度对用户行为的…...

开源 vGPU 方案:HAMi,实现细粒度 GPU 切分

本文主要分享一个开源的 GPU 虚拟化方案&#xff1a;HAMi&#xff0c;包括如何安装、配置以及使用。 相比于上一篇分享的 TimeSlicing 方案&#xff0c;HAMi 除了 GPU 共享之外还可以实现 GPU core、memory 得限制&#xff0c;保证共享同一 GPU 的各个 Pod 都能拿到足够的资源。…...

timestamp时间戳转换工具

作为一名程序员&#xff0c;一款高效的 在线转换工具 &#xff08;在线时间戳转换 计算器 字节单位转换 json格式化&#xff09;必不可少&#xff01;https://jsons.top 排查问题时非常痛的点: 经常在秒级、毫秒级、字符串格式的时间单位来回转换&#xff0c;于是决定手撸一个…...