实战干货|自研数据存储迁移MySQL实战

news2024/12/23 14:35:53

背景

  最近公司内部在做某自研数据存储的下线工作,这里我们暂且化名其为DistributeSQL,由于DistributeSQL不再进行服务支持,需要迁移项目中使用到该存储到其他数据存储中。

  本篇来聊聊这次在数据存储迁移过程中的方案设计思路、实现的大致细节以及对技术组件选型、技术能力储备重要性的理解。

技术调研

  技术选型的思路很清晰,首先,要找到与DistributeSQL技术能力匹配的其他存储进行替换;其次,要对数据迁移的方案进行全面、细致的设计;最终,分阶段进行改造落地和实施。

定位

  接下来需要做数据存储组件来替代DistributeSQLDistributeSQL的自我定位是分布式表格数据库,其本质是支持强一致性、在线事务处理(OLTP)的持久化存储,此次采用MySQL作为存储替代。

原因

  • 1⃣️ 借鉴了DistributeSQL团队迁移建议,调研了其他团队迁移实践案例方案
  • 2⃣️ 此前迁移到DistributeSQL的源数据存储是MySQL,理论上可以支持逆向数据回溯
  • 3⃣️ 结合团队内DistributeSQL数据存储量级不高、更新频率低、业务依赖度不高等现状

使用现状

image.png

  当前使用现状比较清晰,主要是和数据层直接贴缘的应用服务,也是本次要涉及代码改造影响的一部分,交互方式主要是通过DistributeSQL Binlog进行读写,此外由于DistributeSQL也支持数据oplog即类MySQLbinlog能力支持,在业务实际使用中还存在DistributeSQL Binlog读方式交互。

  • DistributeSQL SDK
  • DistributeSQL SDK
  • DistributeSQL Binlog

方案设计

架构图

image.png

根据使用现状进行迁移方案设计,从应用层数据层两个模块分开进行:

  • 应用层

      应用层主要是对贴缘层SDK改造以满足MySQL的读写能力支持,由于之前接入了DistributeSQL binlog读取,因此这部分也需要进行MySQL binlog的读取替代。

    • DistributeSQL SDK

      • 支持MySQL读能力支持
      • 增加路由开关控制
    • DistributeSQL SDK

      • 支持MySQL写能力支持
      • 增加路由开关控制
    • DistributeSQL binlog

      • 支持MySQL binlog读能力支持
      • 增加路由开关控制
  • 数据层

  如果自身服务能够容忍停机迁移,可以直接设计纯离线迁移方案,复杂度较低一些,若不能则需要既考虑存量数据迁移,也要支持DistributeSQL实时数据的同步迁移能力准备,也就是说在不停机的情况下,做到让业务无感知。

  根据业务情况我们选择了做实时和离线迁移的能力支持和方案,这里既有业务的现实不可接受的客观因素,还有很重要的一点在于团队内对于已经对数据层开发有了较多沉淀积累,公司内部提供的数据开发平台能力和工具功能非常强大,也就是说团队成员有能力且有平台能支持我们快速搭建实时与离线链路,再者之前有实践跑通过MySQLClickhouse的数据链路打下较为扎实的技术储备能力。

流程图

DistributeSQL -> MySQL数据同步链路,示意如下:

image.png

  关于DistributeSQLMySQL的数据层链路可以按照离线实时分为两条,并分别进行数据层开发:

  • 离线

  离线链路可以直接使用公司数据平台提供的DistributeSQL2Hive任务进行离线迁移

  • 实时

  实时链路相对复杂一些,这里参考了之前搭建准实时数仓的方式,通过公司数据平台配置Flink Streaming SQL任务,读取DistributeSQL的实时binlog数据即MQ,监听每次增量时在Spark任务中联查离线Hive进行Join,通过数据主键完成数据唯一性对比和去重,保证每次处理数据都是最新数据,最终将结果写入到Kafka中,然后通过数据平台Kafka2MySQL任务完成最终目标数据源写入。

Flink Streaming SQL逻辑可以分为四部分:

[1] 监听增量RocketMQ消息,即DistributeSQL binlog数据

[2] 查询DistributeSQL已经离线的Hive存量数据

[3] 将存量Hive、增量MQ进行去重JOIN得到最新的Row级别数据

[4] 写入到Flink流式中,最终以Kafka消息体形式输出

示例如下:

-- ********************************************************************
-- Author: guanjian
-- CreateTime: 2023-01-04 18:02:30
-- Description: 
-- Update: Task Update Description
-- ********************************************************************

-- 【引入用到的函数和资源】
CREATE  LEGACY FUNCTION nanoTime AS 'com.xxx.stream.NanoTime';

CREATE  function TIMESTAMP_TO_LONG AS 'com.xxx.flink.time.TimestampToLong';

ADD     Resources flink_connector_custom_11;

--【这里对标DistributeSQL的binlog,是以RMQ形式接入的】  
-- [1] 增量实时 DistributeSQL binlog,即 RocketMQ
CREATE  TABLE delta_rmq_data (
            id          ROW<before_value BIGINT, after_value BIGINT, after_updated BOOLEAN>,
            number      ROW<before_value INT, after_value INT, after_updated BOOLEAN>,
            time        ROW<before_value TIMESTAMP, after_value INT, after_updated BOOLEAN>,
            string      ROW<before_value VARCHAR, after_value VARCHAR, after_updated BOOLEAN>
        )
        WITH (
            'scan.startup-mode' = 'timestamp',
            'connector' = 'rocketmq',
            'cluster' = 'your cluster',
            'topic' = 'youer topic',
            'group' = 'your topic group', --消费者组,自定义即可
            'format' = 'binlog',
            'tag' = 'your tag', --自定义
            'binlog.target-table' = 'your table', --自定义
            'scan.force-auto-commit-enabled' = 'true',
            'scan.startup.timestamp-millis' = '1638288000000' --2021-12-01 00:00:00 每次重新上线可以不修改,因为后续会去重,修改会减少计算量
        );

-- [2] 全量离线 DistributeSQL已经离线的Hive数据
CREATE  TABLE base_hive_data (
            id          BIGINT,
            number      INT,
            time        TIMESTAMP,
            string      VARCHAR
        )
        WITH (
            'connector' = 'xxx',
            'query' = 'SELECT   CAST(id          AS BIGINT   )  ,
                                CAST(number      AS INT      )  ,
                                CAST(time        AS TIMESTAMP)  ,
                                CAST(string      AS VARCHAR  )
                       FROM    LF_HL_HIVE.hive_database.hive_table
                       WHERE   p_date = ''${date}''',
            'base_path' = 'hdfs://xxx.db/', 
            'conf' = 'set yarn.cluster.name=xxx;set mapreduce.job.queuename=xxx;' --yarn集群、队列
        );

-- [3] union all 全量
CREATE  VIEW union_data AS
SELECT  *
FROM    (
            SELECT  *,
                    ROW_NUMBER() OVER(
                        PARTITION BY
                                id
                        ORDER BY
                                main_order DESC,
                                ts DESC
                    ) AS rn
            FROM    (
                        SELECT  id.after_value AS id,
                                number.after_value AS number,
                                time.after_value AS time,
                                string.after_value AS string,
                                1 AS main_order,
                                nanoTime() AS ts
                        FROM    delta_rmq_data
                        WHERE   binlog_body.event_type = 'INSERT'
                        OR      binlog_body.event_type = 'UPDATE'
                        UNION ALL
                        SELECT  id,
                                number,
                                time,
                                string, 
                                0 AS main_order,
                                nanoTime() AS ts
                        FROM    base_hive_data
                    )
        )
WHERE   rn = 1;

-- [4] 写入到kafka
CREATE  TABLE data_bmq_sink (
            id          BIGINT,
            number      INT,
            time        TIMESTAMP,
            string      VARCHAR,
            p_date      BIGINT
        )
        WITH (
            'properties.request.timeout.ms' = '120000',
            'json.timestamp-format.standard' = 'RFC_3339',
            'connector' = 'kafka-0.10',
            'properties.cluster' = 'your kafka cluster', --kafka 集群名
            'topic' = 'your kafka topic', --kafka topic名
            'parallelism' = '9',
            'format' = 'json',
            'sink.partitioner' = 'row-fields-hash',
            'sink.partition-fields' = 'id'
        );

INSERT INTO data_bmq_sink
SELECT  id,
        number,
        time,
        string,
        TIMESTAMP_TO_LONG(LOCALTIMESTAMP) AS p_date
FROM    union_data;

落地流程

开发&上线步骤

image.png

  • 开发

      这一阶段可以分开进行,主要是应用服务的代码SDK改造和数据层数据平台任务开发以及配置等相关工作。SDK改造是对最终接入数据源MySQL的读写支持,并在业务代码中增加路由开关为后续切换做准备,还有就是通过数据平台能力搭建离线、实时数据链路为数据迁移和同步做准备。
  • 数据链路上线

      当数据层开发完毕后可以先行投产,将存量数据进行同步并服役实时数据链路保持热更新效果,这些操作是完全独立的数据链路搭建和储备,对线上业务完全没有影响。
  • 代码上线

      当代码上线后,意味着应用层已经具备双数据存储的SDK读写能力,此时仍然对业务没有丝毫影响。
  • 路由切换

      此环节是最为重要的一环,也是对本次改造产生变化的影响的部分,切换成功后就意味着数据读写开始使用新存储架构进行承载,标志着方案已经成功落地,这部分的一些问题探讨可以参考下面部分。
  • 下线

      该部分为最终收尾环节,对于线上业务理论不存在任何影响,是对资源回收的处理。

读写一致性剖析

  关于数据迁移最重要的是要保证尽量业务层无感知,通过较为完备的技术方案将所有变更带来的影响全部拦截在系统层面进行治理,核心之重充分考虑数据读写一致性问题,

阶段读写逻辑变化问题解决方案
开发[1] 业务数据读写链路:DistributeSQL Write/Read---
数据链路上线[1] 业务数据读写链路:DistributeSQL Write/Read
[2] 业务数据同步链路:DistributeSQL -> MySQL
业务数据源未发生切换,此时业务对数据同步链路无感知--
代码上线[1]业务数据同步链路:DistributeSQL -> MySQL业务数据源未发生切换,此时业务对数据同步链路无感知;
此时具备MySQL、DistributeSQL读写能力
--
路由切换[1]业务数据读写链路:MySQL Write/Read
[2]业务数据同步链路:DistributeSQL->MySQL
业务数据读写链路从DistributeSQL切换到MySQL数据链路切换后,存在读写不一致的可能见下方
下线- 业务数据读写链路:MySQL Write/Read下线业务数据同步链路--

  路由切换导致问题的解决方案:

  • [1] 若业务接受停机,可以短时间停止DistributeSQL写入,等待最后一次DistributeSQL写入及同步完成立刻全量切MySQL独立读写

  • [2] 若业务不要求数据强一致,可以不用关心写入间隙的不一致问题,全量切MySQL且同步链路留存数据完成后最终一致

  • [3] 若业务不接受停机且要求数据强一致性,需要增加数据源双读支持,若是单点离散数据可支持,若是分页或全量数据则需要做方案进行兼容或者降级能力挺过路由切换阶段带来的数据延迟风险,这部分需要更为精细化技术方案,充分评估风险并进行报备寻找资源支持

项目思考

  在日常的研发工作中除了持续的业务需求迭代,还会伴随衍生出很多技术需求。

  面对业务需求,除了端饭碗的技术基本功能让你完成需求任务,还需要一定程度日积月累的业务理解、敏感度甚至专业度,从而让业务需求完成的更合理、成熟,既满足当前业务需求的同时,又能由这个需求点到整个系统面来全盘思考,让一次次的迭代都尽可能完美,保持系统的健壮和稳定。

  面对技术需求,情况也许更复杂一些,如果说业务需求是在和业务成本博弈,那技术需求更多的是在和自身技术储备能力博弈,既在对别人或者自身技术实践的反思,也是在对自己技术深度和广度的一次历练和考验。想一想,如果自身或者团队的能力已经用尽十八般武艺来进行技术实践,那么当前的产出物从一定程度上已经代表了最高水平,很难有突破提升的空间。

  讲到这里,结合我自身的项目经历的确深有感触,就在大概两年前,我也经历过类似的项目背景,当时能力水平和如今比还是有着很大差距的,因此技术方案在今天看来非常吃力,可那已经代表了当年自身最高水平和能力,想想当年的技术方案实践真是血淋淋的教训,所有一切完全是在应用层处理,开发、上线、出问题、修问题…让人叫苦不迭。相比今日再现类似项目机会,技术思路非常清晰,能够做出合理分层,不仅仅如往日单调的应用层开发,还能引入大数据以及数据能力的开发支持。如今技术方案是有进步的,这完全得益于对数据组件能力的了解和过往实践沉淀的经验,这两年期间悉心拜读了DDI神作,不仅开拓了技术视野,还对系统理解有了新的认知,跳出了舒适区开始吃力啃大数据组件,手里的工具多了,技术方案的选择更合理,一切也就会向好,对项目、对团队、对合作伙伴、更是对自身受益匪浅。

  最后想说的是,技术之路漫漫,学习不能停止,提升的过程很孤独甚至是痛苦的,但它会反哺你,让你在工作中特别是遇到困难问题时会毫不费力,不必再像往日那样身陷囹圄、耗时耗力在每一块绊脚石上,为你节省出更多时间来做更有意义的事情,让你的工作、生活变得美好起来,加速你的成长。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/147409.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中老年服装电商小程序开发

近年来&#xff0c;随着网络的发展&#xff0c;中老年服装电商小程序开发有了很大的进步。这种平台不仅可以方便用户购买到最新最时尚的产品&#xff0c;而且还能帮助商家提高销售业绩。 1&#xff1a;中老年服装电商小程序开发的优势 中老年人对商品信息需求大、容易接受新鲜…

实验⼀:Windows主机漏洞利⽤攻击实践

永恒之蓝简介 永恒之蓝&#xff08;Eternal Blue&#xff09;爆发于2017年4月14日晚&#xff0c;是一种利用Windows系统的SMB协议漏洞来获取系统的最高权限&#xff0c;以此来控制被入侵的计算机。甚至于2017年5月12日&#xff0c; 不法分子通过改造“永恒之蓝”制作了wannacry…

【ROS】—— ROS重名问题(九)

文章目录前言1. ROS工作空间覆盖2. ROS节点名称重名2.1 rosrun设置命名空间与重映射2.1.1 rosrun设置命名空间2.1.2 rosrun名称重映射2.1.3 rosrun命名空间与名称重映射叠加2.2 launch文件设置命名空间与重映射2.3 编码设置命名空间与重映射2.3.1 重映射2.3.2 C 实现:命名空间3…

Maven基础学习——依赖配置(1):配置同一项目下的三个工程

依赖配置一、前言二、创建第一个工程三、新建第二个工程四、创建第三个工程五、配置1.每个工程的.xml文件2.文件配置六、结语一、前言 在讲述依赖配置时&#xff0c;需要使用实例来说明&#xff0c;在B站黑马课程&#xff08;第12小节&#xff09;中没有讲到如何配置基础的三个…

[Effective Objective] 熟悉Objective-C

了解 Objective-C Objective_C 是一种面向对象的语言。但与jave、C等语言不同&#xff0c;它使用了消息结构&#xff08;messaging structure&#xff09;而非函数调用&#xff08;function calling&#xff09;。Objective-C由Smalltalk演化而来&#xff0c;后者是消息语言的…

React 学习笔记总结(六)

文章目录1. redux 介绍2. redux 工作流程3. redux 的使用4. redux 完整结构补充5. redux的 异步action6. react-redux库 与 redux库7. react-redux库的 实战8. react-redux的connect 最精简写法9. react-redux的 Provider组件作用10. react-redux 整合UI组件 和 容器组件11. re…

webgl图形平移、缩放、旋转

文章目录前言平移图示代码示例缩放图示代码示例旋转公式推导代码示例总结前言 在webgl中将图形进行平移、旋转、缩放的操作称为变换或仿射变换&#xff0c;图形的仿射变换涉及到顶点位置的修改&#xff0c;通过顶点着色器是比较直接的方式。本文通过着色器实现对webgl图形的仿…

ArcGIS基础实验操作100例--实验65按字段调整点符号方向

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验65 按字段调整点符号方向 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff0…

计算机组成原理_总线

计算机组成原理总目录总线概述 1. 总线介绍 我们知道计算机中有CPU、主存、辅存&#xff0c;以及打印机、键盘、鼠标等等的一些外设 那么各个设备之间肯定是要进行数据传输的&#xff0c;这就需要许多线路将它们连接起来 第一种方法&#xff1a;两两相联 外设数量越多&#xf…

35、基于STM32的电子钟(DS1302)

编号&#xff1a;35 基于STM32的电子钟&#xff08;DS1302&#xff09; 功能描述&#xff1a; 本设计由STM32单片机液晶1602按键DS1302时钟组成。 1、采用STM32F103最小系统。 2、利用DS1302芯片提供时钟信号 3、液晶1602实时显示年月日、时分秒、星期等信息。 4、三个按键可…

隐形AR眼镜厂商Mojo Vision裁员75%,专注Micro LED技术

1月7日青亭网报道&#xff0c;隐形AR眼镜厂商Mojo Vision官方宣布了一项重大调整&#xff0c;其中因为产品进展问题&#xff0c;同时还有融资进展受阻等面临大裁员&#xff0c;将进行一系列中心调整&#xff0c;据了解本次裁员比例高达75%。重点关注&#xff1a;1&#xff0c;M…

【Day5】力扣第328题,奇偶链表

前言&#xff1a; 大家好&#xff0c;我是良辰丫&#x1f680;&#x1f680;&#x1f680;&#xff0c;今天带大家刷一个力扣链表题&#xff0c;有人可能会说&#xff0c;一道题够嘛&#xff0c;刚开始刷题别着急&#xff0c;毕竟&#xff0c;心急吃不了热豆腐&#xff0c;&…

Mathorcup数学建模竞赛第六届-【妈妈杯】B题:小区车位分布的评价和优化模型(附特等奖获奖论文和Java代码)

赛题描述 随着现代社会经济的快速发展,房地产成为国家经济发展中重要的经济增长点之一。而小区内汽车停车位的分布对于小区居民的上下班出行影响很大。请建立数学模型,解决下列问题: 问题1:分析评判小区汽车停车位分布是否合理的几个关键指标,建立评判车位分布合理的数学…

嵌入式Linux-对子进程的监控

1. 进程的诞生与终止 1.1 进程的诞生 一个进程可以通过 fork()或 vfork()等系统调用创建一个子进程&#xff0c;一个新的进程就此诞生&#xff01;事实上&#xff0c;Linux系统下的所有进程都是由其父进程创建而来&#xff0c;譬如在 shell 终端通过命令的方式执行一个程序./…

leetcode 1658. 将 x 减到 0 的最小操作数[python3 双指针实现与思路整理]

题目 给你一个整数数组 nums 和一个整数 x 。每一次操作时&#xff0c;你应当移除数组 nums 最左边或最右边的元素&#xff0c;然后从 x 中减去该元素的值。请注意&#xff0c;需要 修改 数组以供接下来的操作使用。 如果可以将 x 恰好 减到 0 &#xff0c;返回 最小操作数 &a…

HTML与CSS基础(四)—— CSS基础(选择器进阶、Emmet语法、背景属性、元素显示模式、三大特性)

一、选择器进阶目标&#xff1a;能够理解 复合选择器 的规则&#xff0c;并使用 复合选择器 在 HTML 中选择元素1. 复合选择器1.1 后代选择器&#xff1a;空格作用&#xff1a;根据 HTML 标签的嵌套关系&#xff0c;选择父元素 后代中 满足条件的元素 选择器语法&#xff1a;选…

第二章JavaWeb基础学习路线

文章目录什么是Java WebJava Web基础的技术栈关于我们的客户端与服务端&#xff08;BS&#xff09;我们客户端的形式**PC端网页****移动端**服务端应用程序关于请求&#xff08;request&#xff09;和响应(response)类比生活中的请求和响应服务器中的请求和响应项目的逻辑构成架…

CSS权威指南(六)文字属性

1.缩进和行内对齐 &#xff08;1&#xff09;缩进文本&#xff08;text-indent&#xff09; text-indent属性把元素的第一行文本缩进指定的长度&#xff0c;缩进的长度可以可以是负值。这个属性通常用于缩进段落的第一行。text-indent作用于块级元素之上&#xff0c;缩进将沿着…

config:配置中心

Spring Cloud Config 为分布式系统中的外部配置提供服务器端和客户端支持。使用 Config Server&#xff0c;您可以集中管理所有环境中应用程序的外部配置。 Spring Cloud Config就是一个配置中心&#xff0c;所有的服务都可以从配置中心取出配置&#xff0c;而配置中心又可以从…

mmap(内存映射)、sendfile() 与零拷贝技术

内存映射&#xff08;Memory-mapped I/O&#xff09;是将磁盘文件的数据映射到内存&#xff0c;用户通过修改内存就能修改磁盘文件。 RocketMQ为什么快&#xff1f;kafka为什么快&#xff1f;什么是mmap&#xff1f;这些问题都逃不过一个点&#xff0c;就是零拷贝。 虽然还有其…