{
    "componentChunkName": "component---src-templates-case-study-detail-ts",
    "path": "/case-studies/38",
    "result": {"pageContext":{"next":{"id":42,"attributes":{"feature":true,"title":"智能电站数据中台建设与应用","content":"## **引言**\n\n \n\n近几年随着计算机技术、通信技术、人工智能技术的发展，电站正逐步向网络化、数字化、智能化迈进。智能电站是以电站生产和管理活动中各种信息的数字化为前提，计算机、网络、通信技术为基础，通过采用数据分析和处理、智能控制、自动决策等技术，使得在各种环境条件下都能够提供经济、环保、安全、稳定的电能并与智能电网高度融合的发电站([1-3])。数据信息资源正逐步成为电站的关键要素资源。由于电站信息系统大多是分阶段、分功能实施的，在电站智能化建设过程中，特别是在数据资源使用过程中暴露出来了许多问题。（1）数据孤岛现象严重。各个业务系统数据各自存储、各自定义，数据无法有效连接，数据分散、指标重复建设、数据实用价值低的现象普遍存在。（2）数据缺乏全生命周期管理。电站设计、基建阶段产生的数据不能传递到生产、运维阶段，影响了后期机组及设备的性能诊断和运行优化分析。（3）数据能力未能积累和沉淀。数据服务化程度低，数据共享和交互上缺乏整体协同性，无法形成有效的电站数据资产框架。\n\n如何唤醒电站沉睡的数据资产、打通数据信息壁垒、消除数据烟囱，把数据融合利用起来，是电站智能化转型过程中的难题。而以构建数据资产体系、释放数据资产价值为核心的数据中台正是这一问题的完美解决方案，对提升电站精益化运行管理水平具有十分重要的意义。\n\n \n\n### **1  数据中台架构设计**\n\n \n\n**1.1  电站数据源**\n\n来自电站不同设备和系统的数据主要是生产数据和管理数据，数据类型包含结构化、半结构化和非结构化3类数据。（1）结构化数据包括时序数据和非时序数据。时序数据一般指控制系统现场传感器数据采集过来的数据，比如火电厂的锅炉设备温度、压力等传感器信号，经过规整后存入工业实时数据库中；非时序数据包括各类关键业务系统数据，如智能检修、智能燃料、智能运行、智能经营等系统数据，这些数据基于关系型数据库存储，表现为二维形式。（2）半结构化数据包括各类系统的配置文件、产生的日志文件等，这类数据一般以XML、JSON或自定义文本文件格式存储。（3）非结构化数据包括电站档案系统、门禁系统、智能摄像头、移动机器人产生的各类技术文档、图像文件、音频文件、视频文件等。电站现有平台的管理数据与生产数据没有有效融合，实时数据库与关系数据库的互联互通也存在一定问题，未能进行数据有效组织管理和数据赋能。\n\n**1.2  数据中台架构**\n\n智能电站数据中台通过构建面向电站应用的数据共享和数据分析智能平台，克服了电站现有系统的技术缺陷。数据中台以数据资产高效管理为基础，沉淀数据通用能力，通过构建规范的、安全的、智能的、全域化的数据处理架构，提供数据采集、数据存储、数据萃取、数据治理、数据服务、数据可视化等全链路一体化的服务([4-9])，其技术架构如图1所示。\n\n \n\n![img](https://u01f1kqxrl.feishu.cn/space/api/box/stream/download/asynccode/?code=ZGEyOTI5OGNmOGY2OGIwZDJmZjM1MjE0MTU3NGE2ZDFfUEgxamM0bWJEWFY4UmllVWNhUktLWVc5SW0xeHVoR1BfVG9rZW46Ym94Y25HYkllUWh2WERZWGtwY25kMWwzNUJmXzE2ODAxNTc3MDQ6MTY4MDE2MTMwNF9WNA)\n\n<center>图1  智能电站数据中台技术架构</center>\n\n<center>Fig.1  Technology architecture of intelligent power station data middle platform</center>\n\n数据采集是数据中台接入数据的入口，把各种异构数据源的数据采集到数据中台进行集中存储；数据萃取与分析将低价值的原始业务数据转换为高价值的数据资产，通过数据资产框架进行治理；基于数据服务体系将数据以服务化方式交付给业务系统，通过数据可视化框架进行数据展示，激活整个数据中台。\n\n**1.3  数据中台价值**\n\n数据中台的价值体现在“存、通、用、统、快”，如图2所示。通过构建智能电站数据中台，打通数据隔阂，统一数据标准，实现一切业务数据化、一切数据业务化、快速响应用户需求。（1）一切业务数据化。通过业务信息系统的建设，将系统中沉淀的业务数据汇聚到数据中台进行存储，实现一切业务数据化。（2）一切数据业务化。数据中台基于业务系统产生的数据进行体系化加工，将结果以服务化方式提供给业务使用，进而反哺业务，实现一切数据业务化。 \n\n![img](https://u01f1kqxrl.feishu.cn/space/api/box/stream/download/asynccode/?code=Y2Y0OGY1YmU1MzhkNzljNTAxNDg2MTM1ZWQxODQ1YmJfRVZkUnNSbXNjQWpXQmN2aFpZU0NRaTJLR1ZpOE9HcFRfVG9rZW46Ym94Y25QR2tWS3JRZzExcVZkaG5BVkpHdWljXzE2ODAxNTc3MDQ6MTY4MDE2MTMwNF9WNA)\n\n<center>图2  数据中台价值</center>\n\n<center>Fig.2  Value of data middle platform</center>\n\n \n\n### **2  数据中台关键技术**\n\n \n\n**2.1  全域数据采集**\n\n全域数据采集是智能电站数据中台建设的第一步，也是非常重要的一步，为数据中台全面汇集异构数据。数据采集平台可以针对不同的数据源，建立不同的数据采集任务，具备数据采集、转换、传输、复制、迁移、同步等能力([10-11])。（1）电站每台机组控制系统大约有10万个传感器测点，时间戳精度为秒级。转子设备振动数据时间戳精度可以达到毫秒级。采用自主研发的实时数据采集传输程序，基于例外发送、数据缓存、断点续传和历史回补机制，实现电站实时数据的快速高效传输，目前支持OPC、Modbus、101~104规约、CDT规约、RTU规约及其他私有TCP/UDP通信协议。（2）电站业务关系数据库众多，其采集与同步可使用Sqoop和Kettle开源程序。抽取模式可采用全量抽取和增量抽取模式，增量数据抽取一般基于时间戳、触发器、更新标识等机制。（3）电站日志系统数据采集可采用Flume。（4）针对电站外部互联网数据，使用网页爬虫方式自动抓取目标数据。\n\n**2.2  数据湖底座建设**\n\n数据湖底座是智能电站全域数据的存储中心，针对不同数据源和不同数据应用分析需求提供灵活、可扩展的混合型存储架构，以分布式存储系统为核心，能够实现任意规模结构化、半结构化和非结构化数据存储，以提高生产力、主动维护设备，为电站的数据融合提供支撑，为智能运行、智能检修等生产决策提供依据。（1）电站机组控制系统的传感器测点数据采集频率和时间戳精度均为秒级，考虑到存储性能和后期查询、统计、分析性能，采用自主研发的睿腾（Rython）工业实时数据库进行存储，数据库系统架构如图3所示。睿腾实时数据库基于高并发网络通信技术、IO资源管控技术、多级高速数据缓存算法、无损压缩和有损压缩相结合的机制，实现海量测点数据的高效传输、存储、管理和访问。 \n\n![img](https://u01f1kqxrl.feishu.cn/space/api/box/stream/download/asynccode/?code=YTVlYjFlN2MxZTE2NWRhZjU1YWU3NjY2ODgyMDY0MjlfTHkxRFhNeGRhbTAwYWQ1SEF0MTBsSjV2SzJnZnhtNWtfVG9rZW46Ym94Y25uVm40WElHdHRiUHJmbFo3bU9kanVnXzE2ODAxNTc3MDQ6MTY4MDE2MTMwNF9WNA)\n\n<center>图3  睿腾实时数据库系统架构</center>\n\n<center>Fig.3  System architecture of Rython real-time database</center>\n\n \n\n睿腾实时数据库可管控的测点规模可达千万级别。测点表基于两级索引构建，其索引结构如图4所示。在此索引结构中，如果已知一个测点的记录号RecNo，就可通过以下步骤在测点表文件中进行索引定位并进行相应的操作：①计算RecNo/10240可得到该测点所属的一级索引表单元位置，并从一级索引表中读取到二级索引块的起始文件块号；②计算RecNo % 10240可得到该测点所属的二级索引块的单元位置，并从二级索引块中读取到测点属性的文件块号；③根据索引得到的测点属性文件块号，可对该测点属性进行创建和编辑操作；如果需要删除一个标签，则只需要将该测点的二级索引块单元中的标签属性文件块号的高位（第31Bits）置为1即可，不需要更新测点属性。\n\n \n\n![img](https://u01f1kqxrl.feishu.cn/space/api/box/stream/download/asynccode/?code=ZWI2NzQ5ZjY2ZGFiYjBlYTUzOTZkYjA5NzgwYzZjODVfbVNlZEI3V2tsZ2VVelROYThBeU9NNURZVldlalhweWdfVG9rZW46Ym94Y25aVTE4eVdCZGthSkV0aTEyYTVhN0tjXzE2ODAxNTc3MDQ6MTY4MDE2MTMwNF9WNA)\n\n<center>图4  睿腾实时数据库测点表索引结构</center>\n\n<center>Fig.4  Tag’s index structure of Rython real-time database</center>\n\n（2）分布式大数据平台基于自主研发的大数据产品，以分布式文件系统为核心，由管理节点和一定数量的工作节点组成大数据存储集群，支撑大数据分析、萃取、挖掘，保存加工处理后的结构化数据和图片、文档等非结构化数据([12])。（3）电站关键业务数据采用分布式关系数据库进行存储。关系数据库平台基于改进的MySql+MHA的部署方案，提供高可用、高数据吞吐能力、扩展性强的关系型数据库引擎。对于分布式数据访问层，采用开源shardingjdbc技术路线。\n\n**2.3  数据萃取与分析**\n\n基于数据开发和算法开发平台，数据萃取层通过电站业务主题域和数据仓库建设，构建完整的智能电站测点数据体系、指标数据体系、设备数据体系、成本数据体系、人员数据体系和文档数据体系。数据开发是将价值不大的原始数据变成对业务有价值的数据资产。数据开发平台产品提供批量计算、流式计算和查询分析能力([13])。（1）批量计算基于MapReduce、Spark等计算框架进行处理，主要用于人机交互较少的批量数据高延时处理，如离线数据仓库的加工、大规模数据清洗与挖掘等，其数据吞吐量大、延时高。（2）流式计算基于Storm、Spark Streaming等计算框架进行处理，数据源为源源不断的流式数据，适用于对数据的加工处理和应用有较高时效性的场景，如设备异常监控告警。（3）查询分析使用ElasticSearch、Kylin、ClickHouse等技术进行处理，适用于历史数据的在线查询、条件过滤和维度分析。算法开发作为一站式的企业机器学习开发平台，包括数据集管理（如数据接入与标注等）、核心算法组件（如回归、聚类、神经网络等）编写、主流算法框架（如TensorFlow、PyTorch、Mahout等）集成、可视化建模，旨在为企业快速提供核心算法服务([14])。基于自主研发的分布式图形化建模计算平台，通过简单的拖拽操作，就可组态出复杂的算法逻辑，为数据中台提供强大的数据建模和计算能力。图形化建模计算平台主要包含以下功能。（1）模型设计器：提供可视化的机理建模、数学建模工具。（2）工程组态：组态工程的编辑、调试、上线、维护管理。（3）算法因子：提供基础通用算法、热力学专用算法、大数据算法等不同种类算法因子。同时可以自定义算法，具备集成第三方算法的能力。（4）计算引擎：基于时间、条件、公式等基础引擎，采用流式计算、离线计算、内存计算等计算模式，进行分布式在线和离线计算。\n\n**2.4  数据资产治理**\n\n数据治理框架，为智能电站提供统一数据标准制定、主数据管理、元数据管理、数据质量管理等能力，通过数据地图和数据血缘实现电站数据360°全链路追踪。框架提供数据全生命周期的管控机制，当发生数据质量问题时，可通过可视化图形方式追溯指标的血缘及逻辑关系、关联指标的数据和权重，进而定位问题([15])。（1）电站资产模型通过设备树的方式进行组织定义，每个设备都用元素表示，元素的关联数据将作为元素的属性进行存储，元素属性数据包括常量、实时数据库测点值、关系数据库引用值、指标公式计算值、图片、文档等。（2）数据治理计算框架基于分布式消息引擎RocketMQ和分布式缓存Redis集群，采用事件触发和周期触发机制，实现设备树海量元素属性数据的层次计算和分析计算。**2.5  数据服务化交付**智能电站数据中台将数据进行服务化交付，企业内部业务系统和第三方应用不必关心底层数据库结构，就可以获得目标数据。通过这样的架构设计，对下屏蔽了异构数据的差异性，对上提供了统一的数据服务接口和标准化数据。当新的业务需求出现时，可以快速进行开发响应。基于自主研发的数据服务网关，可以为业务应用提供以下3种形式的数据服务。（1）OPC UA数据服务。在应用端对数据访问效率要求极高时，如实时性很强的测点数据，推荐使用OPC UA服务。（2）Restful WebAPI数据服务。基于微服务架构和SpringCloud技术体系，提供基于HTTP/HTTP2的服务接口。（3）文件数据服务。基于FastDFS分布式文件系统，为业务应用提供在线文档服务。\n\n**2.6  数据可视化框架**\n\n数据可视化框架基于Vuejs前端开源javascript库和CSS响应式布局模式构建，框架设计器如图5所示。框架组件设计采用MVVM（model view viewmodel）模式进行定义，框架内部基于HTML5、Canvas和WebGL图形绘制技术，实现了大量的2D、3D、VR等图形组件。框架组件间消息通讯采用Weak Event机制实现，这样不仅降低了组件间的业务耦合度，也消除了内存泄漏的风险。开发人员或业务人员使用数据可视化框架能够快速进行生产运营决策驾驶舱的数据准备、指标集建立、业务模型配置、管理视图及可视化图表构建，实现对电站海量数据的汇总统计、多维分析、报表呈现、图形展示，以交互、生动的形式直观、全面、快速地了解电厂生产经营情况并进行辅助决策，进而提升工作效率。 \n\n![img](https://u01f1kqxrl.feishu.cn/space/api/box/stream/download/asynccode/?code=Mjg5OGFlZWUyNTljOTYxMWJjZGRjMWQ1NDhmNGNmNDlfTUhaazU0VTFvT1d6WWt5RjcxNDJVOUtaUGk1R1VTNHFfVG9rZW46Ym94Y25XaUJJUGJNRWdYa1pDOGZJVWkyODliXzE2ODAxNTc3MDQ6MTY4MDE2MTMwNF9WNA)\n\n<center>图5  数据可视化框架设计器</center>\n\n<center>Fig.5  Designer of data visualization framework</center>\n\n \n\n### **3  数据中台应用**\n\n \n\n建设智能电站数据中台的最终目的是高效组织利用数据资源，提升电站精益化运行管理水平。以智能电站建设过程中的3个典型应用场景为例，阐述企业如何应用数据中台，提高数据治理、分析计算和展示能力。\n\n**3.1  设备运行监视**\n\n电站在运行过程中，需要对锅炉、汽轮机、发电机、磨煤机、引风机等关键主辅机设备进行实时监控。设备数据种类复杂、来源众多，以磨煤机为例，主要监视的信息如下。（1）一次测点数据。这些数据来自现场控制系统传感器，如磨煤机电流、功率、进口风量、出口风粉温度等，采集频率为秒级，为时间序列型数据。（2）指标数据。这些数据由不同业务系统产生，如发电量、利用小时、非计划停运次数等指标来自可靠性系统，而发电量、负荷率、耗煤量等指标来自在线经营系统。指标数据均通过一次测点数据不同的运算逻辑统计计算得到。（3）设备基础信息。这些数据主要来自设备管理系统，主要包括设备名称、编码、设备KKS编码、所属专业、设备分类、型号规格、生产厂家、出厂编号、安装地点、安装现场图片等信息。（4）设备检修和缺陷记录。这些信息主要来自ERP系统（如SAP），包括检修发生时间、故障描述、参与人员、耗费人工、使用物料、消缺状态等。在传统信息系统建设方式下，电站的各种系统大多是独立建设的，存在指标重复计算、统计口径不一致、数据信息无法互联互通等问题，也无法很好地应对快速变化的前端业务。通过建设智能电站数据中台，可以消除数据壁垒、统一数据标准，实现异构数据融合和设备运行监视的数据可视化快速配置展示。\n\n**3.2  脱硫优化系统**\n\n当前环保压力日益增大，在保证烟气二氧化硫排放达标的前提下，实现脱硫系统的安全、经济、优化运行日益重要。脱硫优化系统基于智能电站数据中台，采用数据驱动模式和大据挖掘技术，进行机组工况的规律发现、特征抽取、自动识别和智能寻优，给运行人员提供及时精细的操作指导，实现企业效益的最大化([16-18])。（1）确定优化关联指标。对脱硫系统的历史运行原始数据进行多维相关性分析，确定与寻优目标“浆液量”强相关的指标：机组负荷、入口二氧化硫浓度、入口烟气流量、入口烟气温度、入口烟气湿度、吸收塔浆液pH值和出口二氧化硫浓度。（2）采集机组稳定运行工况。一般机组负荷波动不超过15%、至少持续运行1 h认为是一个稳定工况。基于机组运行的海量数据，在数据中台萃取层配置提取规则，进行脱硫系统优化关联指标数据的采集和统计，存储到中台数据湖底座。（3）工况标签化分类。采用中台提供的聚类算法对不同负荷工况进行分类，并对聚类类心进行合理选择。（4）以聚类获得的类心作为训练样本，采用中台提供的有监督机器学习算法建模，如图6所示，建立脱硫系统优化模型([19-21])。 \n\n![img](https://u01f1kqxrl.feishu.cn/space/api/box/stream/download/asynccode/?code=ZTI2NzRmNjIxYjViYWJhOWJhYWQ2ZDRjZGY4Yzk2ZTdfcUp4S1VMV0psektTeVJHMjBwZTNjMTdwcG9Sd3EwVzBfVG9rZW46Ym94Y25VWHp0eWpyVjRFNkM4Y2dyZ3lwTEFlXzE2ODAxNTc3MDQ6MTY4MDE2MTMwNF9WNA)\n\n<center>图6  脱硫系统优化机器学习模型</center>\n\n<center>Fig.6  Machine learning model of desulfurization system optimization</center>\n\n（5）根据实时采集的机组负荷、入口二氧化硫浓度、入口烟气流量、入口烟气温度、入口烟气湿度、吸收塔浆液pH值和出口二氧化硫浓度数据，与稳定工况进行匹配，获得期望的“浆液量”优化目标值。\n\n**3.3  大屏幕和移动应用发布**\n\n大数据时代，电站业务数据场景分析和终端展示呈多样化发展。企业通过大屏幕发布系统，可以建立良好的形象宣传窗口；通过终端移动应用，可以随时随地查询生产实时数据、浏览生产过程图形、监控主要设备状态，对关键运营指标进行分析、钻取和统计分析。基于中台数据治理和可视化框架，采用海量酷炫图表组件和所见即所得数据源配置，用户可以快速进行大屏幕和移动应用功能设计。图7展示了智能电站“发电量分析”移动应用业务场景。 \n\n![img](https://u01f1kqxrl.feishu.cn/space/api/box/stream/download/asynccode/?code=MmZiY2E1OGUxYjNjODliNjNmNWRiMTNjOTI4OTE5YTRfczBFWnhVRVVweGRBcXJTVzIwcWg2TzhYRXhzTVdjYkpfVG9rZW46Ym94Y25yZzV1UU5kZktJRmlHRWh2czdTY2xsXzE2ODAxNTc3MDQ6MTY4MDE2MTMwNF9WNA)\n\n<center>图7  智能电站移动应用</center>\n\n<center>Fig.7  Mobile app of intelligent power station</center>\n\n \n\n### **4  结语**\n\n \n\n（1）针对电站现有系统存在的数据孤岛、数据未能有效利用问题，分析了电站发电过程中产生的海量数据的数据源类型，设计了智能电站数据中台架构，消除了数据烟囱，统一了数据标准，实现了多源异构数据融合。（2）以数据资产高效治理为基础，通过全域数据采集、数据湖底座建设、数据萃取与分析、数据服务化交付、数据可视化等关键技术方案设计，构建和实现了智能电站数据中台。（3）介绍了智能电站数据中台的典型应用场景，阐述了如何应用数据中台提高数据治理、分析计算和展示能力，对于发电企业建设和应用数据中台具有一定的借鉴意义。","date":"2022-04-20","author":"王毅, 王智微, 何新","excerpt":"在电站智能化建设过程中，为解决现有系统的数据孤岛、数据利用率低等问题，设计了数据中台产品。基于全域数据采集、数据湖底座建设、数据萃取与分析、数据服务化交付、数据可视化关键技术，建立了智能电站数据中台，实现了多源异构数据融合、共享和服务能力，有效提升电站精益化运行管理水平。最后介绍了智能电站数据中台的典型应用场景，对发电企业建设和应用数据中台具有一定的参考价值。","metaDescription":null,"createdAt":"2023-03-30T06:40:05.006Z","updatedAt":"2023-03-30T06:41:58.790Z","publishedAt":"2023-03-30T06:41:58.787Z","locale":"zh","caseStudiesType":{"data":null},"cover":{"data":null},"metaLogo":{"data":null},"localizations":{"data":[]}}},"prev":{"id":40,"attributes":{"feature":true,"title":"基于Sharding-JDBC的订单分库⽅案","content":"# **前言** \n\n随着业务的发展，数据量迅速增⻓，单实例 Mysql 数据库的磁盘存储容量很快就会达到上限（阿⾥云 RDB 最⼤上限6T）。因此为了解决⽣产环境的存储瓶颈，需要对数据库进⾏分库设计，加⼤存储空间。**本⽅案基于Apache开源项⽬ ShardingSphere 采 Sharding-JDBC 进⾏订单⽔平分库设计（数据分⽚）缓解存储压⼒。**\n\n \n\n# **方案对比**\n\n⽬前主流的分库分表⽅案就是 Sharding-JDBC 和 Mycat，下⾯主要对⽐这两种。\n\n![img](https://u01f1kqxrl.feishu.cn/space/api/box/stream/download/asynccode/?code=MGZlMTlmM2IyYTNiZDYzMTk5YjI0NWVlYjJlMzE2NmZfRXVzMURvRUdabmYxR1J4MTJxVWFkRGNuVE1GbjJia21fVG9rZW46Ym94Y25EMDBYVHBVUFhuRm51ZVpvS2Rja3ZjXzE2ODAwNzQ5MzU6MTY4MDA3ODUzNV9WNA)\n\n经上述对⽐，可以发现 Sharding-JDBC 是直连数据库、没有中间层的⽅式，性能上更优，同时由于 Sharding-JDBC 集成在应⽤代码内，并不会增加额外的运维成本，使开发者可以专注于⾃⾝代码逻辑。另外，Sharding-JDBC ⽬前仅⽀持 Java，考虑到⽬前团队技术栈主要以 Java 为主，所以采⽤ Sharding-JDBC 的分库⽅案成为了⼀个不错的选择。 下图反映了 Sharding-JDBC 在应⽤中所处的位置：\n\n![img](https://u01f1kqxrl.feishu.cn/space/api/box/stream/download/asynccode/?code=NTY1M2UyZjBmNWE1NThiMzg1YmI0YTczYmNmNDZhMjZfdHJtWU1pSUdNVnRLV3JabHo0cktNUndXbGxEWkdXYXhfVG9rZW46Ym94Y25XVGdhUGdOVFVid21ucHlTWnpFSFNkXzE2ODAwNzQ5MzU6MTY4MDA3ODUzNV9WNA)\n\n \n\n# **原理**\n\nSharding-JDBC 提供了精确分⽚扩展接⼝ org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm. PreciseShardingAlgorithm，**只需要实现其doSharding ⽅法，基于分⽚键做⼀定的算法转换，返回指定的数据源，便可以达到分库的效果。**  ⽬前我们使⽤的是⼀致性 hash 算法，⽤于定位分⽚键所在数据源，同时引⼊虚拟节点，可以避免数据倾斜问题（即有的数据源数据多，有的数据源数据少，导致数据源设备压⼒不均衡）算法原理如图： \n\n![img](https://u01f1kqxrl.feishu.cn/space/api/box/stream/download/asynccode/?code=NWZkMDc1YmUxZTFmMzk5ZTg2ZThlYzhhM2NkZGU5MTRfa2ZzNDdsR3FTbnhJVTJaTENmRGFHMkZBMUN2S3hhN0pfVG9rZW46Ym94Y25qUWtZdWRYTmR0VG9SUHdtcTF5SFVnXzE2ODAwNzQ5MzU6MTY4MDA3ODUzNV9WNA)\n\n **然后基于该分库的分⽚键&分⽚算法，我们还需在 application.yml 中增加 sharding 数据源配置；同时需要增加分表相关的配置信息。**最后项⽬启动时，sharding 会⾃动加载这些配置初始化数据源。在请求时从请求参数中提取到分⽚键，应⽤分⽚算法，找到⽬标数据源，操作该数据源。 \n\n# **实战**\n\n在原有订单数据源 datasource_name 的基础上，新加3台订单数据库实例 datasource_name_1&datasource_name_2&datasource_name_3，扩充后共4台订单数据库实例，磁盘存储最⼤可以达到原有容量的4倍。\n\n![img](https://u01f1kqxrl.feishu.cn/space/api/box/stream/download/asynccode/?code=NjUzZGZlZDA4MmIwNWE4MTRhN2Y1MWIyZWFkNDc3NTVfcUZRMWFIMFNQWnNxR0UxTUVmc21uSmttRElTMjNETnBfVG9rZW46Ym94Y25GZmVJWWphbTJBY3lTOTdCSkZKNURnXzE2ODAwNzQ5MzU6MTY4MDA3ODUzNV9WNA)\n\n**4.1引⼊maven依赖**\n\n```xml\n<dependency>\n\t<groupId>org.apache.shardingsphere</groupId>\n\t<artifactId>sharding-jdbc-spring-boot-starter</artifactId>\n\t<version>4.1.1.001</version>\n</dependency>\n```\n\n这⾥的4.1.1.001版本是在官⽹ sharding 4.1.1版本上⾃⾏扩充了⼀部分内容：本⾝根据业务分⽚键对业务数据进⾏分⽚后，同⼀类型的数据只会在⼀个数据源中，sharding 只能操作这⼀个数据源的数据，但对于⼀部分分⽚键落⼊新数据源后，意味着数据源发⽣了变动，⽽在旧库数据未迁⼊到新库时，会存在⼀定的兼容时期，此时，改造源码实现了如下双操作（除插⼊外）：a、双查询：查询新旧数据源的数据，然后业务项⽬⾥使⽤切⾯进⾏查询汇总去重，根据 id+updateDatetime 进⾏去重，保留最新数据。b、双更新：更新数据时，会同时更新新旧数据源中满⾜条件的数据。c、双删除：删除数据时，会同时删除新旧数据源中满⾜条件的数据。双删的⽬的是，为了避免只 删除了⼀个库的数据，⽽由于双查导致另⼀个库的数据被查询出来误⽤。d、单插⼊：插⼊数据时，只会插⼊新分⽚算法所落⼊的数据源中。这样做的好处是，在不影响业 务的情况下，也减少了⼀部分数据迁移的⼯作量，即分库上线后新数据直接插⼊新库，不⽤做任何处理。\n\n改造源码如下：\n\n```java\npackage org.apache.shardingsphere.core.rule;\n\n/**\n * Databases and tables sharding rule.\n */\n@Getter\npublic class ShardingRule implements BaseRule {\n\n    // 定义：旧的分库分片策略\n    private final ShardingStrategy oldDatabaseShardingStrategy;\n   \n    public ShardingRule(final ShardingRuleConfiguration shardingRuleConfig, final Collection<String> dataSourceNames) {\n        Preconditions.checkArgument(null != shardingRuleConfig, \"ShardingRuleConfig cannot be null.\");\n        Preconditions.checkArgument(null != dataSourceNames && !dataSourceNames.isEmpty(), \"Data sources cannot be empty.\");\n        this.ruleConfiguration = shardingRuleConfig;\n        shardingDataSourceNames = new ShardingDataSourceNames(shardingRuleConfig, dataSourceNames);\n        tableRules = createTableRules(shardingRuleConfig);\n        broadcastTables = shardingRuleConfig.getBroadcastTables();\n        bindingTableRules = createBindingTableRules(shardingRuleConfig.getBindingTableGroups());\n        // 加载旧的分库分片策略\n        ShardingStrategyConfiguration oldDatabaseShardingStrategyConfig = shardingRuleConfig.getOldDatabaseShardingStrategyConfig();\n        if (oldDatabaseShardingStrategyConfig != null) {\n            oldDatabaseShardingStrategy = createDefaultShardingStrategy(oldDatabaseShardingStrategyConfig);\n        } else {\n            oldDatabaseShardingStrategy = null;\n        }\n        defaultDatabaseShardingStrategy = createDefaultShardingStrategy(shardingRuleConfig.getDefaultDatabaseShardingStrategyConfig());\n        defaultTableShardingStrategy = createDefaultShardingStrategy(shardingRuleConfig.getDefaultTableShardingStrategyConfig());\n        defaultShardingKeyGenerator = createDefaultKeyGenerator(shardingRuleConfig.getDefaultKeyGeneratorConfig());\n        masterSlaveRules = createMasterSlaveRules(shardingRuleConfig.getMasterSlaveRuleConfigs());\n        encryptRule = createEncryptRule(shardingRuleConfig.getEncryptRuleConfig());\n    }\n}\n```\n\n\n\n```java\npackage org.apache.shardingsphere.sharding.route.engine;\n\n/**\n * Sharding route decorator.\n */\npublic final class ShardingRouteDecorator implements RouteDecorator<ShardingRule> {\n    \n    @SuppressWarnings(\"unchecked\")\n    @Override\n    public RouteContext decorate(final RouteContext routeContext, final ShardingSphereMetaData metaData, final ShardingRule shardingRule, final ConfigurationProperties properties) {\n        SQLStatementContext sqlStatementContext = routeContext.getSqlStatementContext();\n        // SQL参数集合\n        List<Object> parameters = routeContext.getParameters();\n        ShardingStatementValidatorFactory.newInstance(\n                sqlStatementContext.getSqlStatement()).ifPresent(validator -> validator.validate(shardingRule, sqlStatementContext.getSqlStatement(), parameters));\n        // 获取分片条件\n        ShardingConditions shardingConditions = getShardingConditions(parameters, sqlStatementContext, metaData.getSchema(), shardingRule);\n        boolean needMergeShardingValues = isNeedMergeShardingValues(sqlStatementContext, shardingRule);\n        if (sqlStatementContext.getSqlStatement() instanceof DMLStatement && needMergeShardingValues) {\n            checkSubqueryShardingValues(sqlStatementContext, shardingRule, shardingConditions);\n            mergeShardingConditions(shardingConditions);\n        }\n        ShardingRouteEngine shardingRouteEngine = ShardingRouteEngineFactory.newInstance(shardingRule, metaData, sqlStatementContext, shardingConditions, properties);\n        RouteResult routeResult = null;\n        // ShardingStandardRoutingEngine特殊处理\n        if(shardingRouteEngine instanceof ShardingStandardRoutingEngine) {\n            SQLStatement sqlStatement = sqlStatementContext.getSqlStatement();\n            // 查询/更新/删除操作，开启双操作模式\n            boolean isCompatibleMode = (sqlStatement instanceof SelectStatement) || (sqlStatement instanceof UpdateStatement) || (sqlStatement instanceof DeleteStatement);\n            routeResult = shardingRouteEngine.route(shardingRule, isCompatibleMode);\n        } else {\n            routeResult = shardingRouteEngine.route(shardingRule);\n        }\n        if (needMergeShardingValues) {\n            Preconditions.checkState(1 == routeResult.getRouteUnits().size(), \"Must have one sharding with subquery.\");\n        }\n        return new RouteContext(sqlStatementContext, parameters, routeResult);\n    }\n}\n```\n\n\n\n```java\npackage org.apache.shardingsphere.sharding.route.engine.type.standard;\n\n/**\n * Sharding standard routing engine.\n */\npublic final class ShardingStandardRoutingEngine extends BaseShardingRoutingEngine {\n\n    /**\n     * 寻找sharding路由结点信息\n     * @param shardingRule\n     * @param tableRule\n     * @param databaseShardingValues\n     * @param tableShardingValues\n     * @param isCompatibleMode 兼容模式（即同时操作新旧分片算法定位到的数据库，查询/更新/删除自动开启兼容模式）\n     * @return\n     */\n    private Collection<DataNode> route0(final ShardingRule shardingRule, final TableRule tableRule, final List<RouteValue> databaseShardingValues, final List<RouteValue> tableShardingValues,\n                                        final boolean isCompatibleMode) {\n        Collection<DataNode> result = new LinkedList<>();\n        // 双查 & 双更 & 双删核心逻辑\n        // 1. 加载旧数据节点路由信息\n        ShardingStrategy oldDatabaseShardingStrategy = shardingRule.getOldDatabaseShardingStrategy();\n        Collection<String> oldDataSources = null;\n        if (oldDatabaseShardingStrategy != null && isCompatibleMode) {\n            oldDataSources = routeDataSources(shardingRule, tableRule, databaseShardingValues, true);\n            for (String each : oldDataSources) {\n                result.addAll(routeTables(shardingRule, tableRule, each, tableShardingValues, true));\n            }\n        }\n        // 2. 加载新数据节点路由信息\n        Collection<String> routedDataSources = routeDataSources(shardingRule, tableRule, databaseShardingValues, false);\n        for (String each : routedDataSources) {\n            // 新数据节点未被加载时，加载新数据节点\n            if(oldDataSources == null || !oldDataSources.contains(each)) {\n                result.addAll(routeTables(shardingRule, tableRule, each, tableShardingValues, false));\n            }\n        }\n        return result;\n    }\n    \n    private Collection<String> routeDataSources(final ShardingRule shardingRule, final TableRule tableRule, final List<RouteValue> databaseShardingValues, final boolean isOld) {\n        if (databaseShardingValues.isEmpty()) {\n            return tableRule.getActualDatasourceNames();\n        }\n        // 根据isOld参数，区分加载新/旧分片策略\n        Collection<String> result = new LinkedHashSet<>();\n        if (isOld) {\n            result.addAll(shardingRule.getOldDatabaseShardingStrategy().doSharding(tableRule.getActualDatasourceNames(), databaseShardingValues, this.properties));\n        } else {\n            result.addAll(shardingRule.getDatabaseShardingStrategy(tableRule).doSharding(tableRule.getActualDatasourceNames(), databaseShardingValues, this.properties));\n        }\n        Preconditions.checkState(!result.isEmpty(), \"no database route info\");\n        Preconditions.checkState(tableRule.getActualDatasourceNames().containsAll(result), \n                \"Some routed data sources do not belong to configured data sources. routed data sources: `%s`, configured data sources: `%s`\", result, tableRule.getActualDatasourceNames());\n        return result;\n    }\n\n}\n```\n\n **4.2数据分片**基于业务进⾏分库的分⽚键选择，最好能均匀的将数据拆分到不同数据库实例。本⽅案假设选择⽤⼾ID作为分库的分⽚键，然后⾃定义新的分库分⽚算法，**算法命名为 DatasourceShardingAlgorithmV2**，算法内容如下：\n\n```java\nimport org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;\nimport org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport java.util.*;\n\n/**\n * 新的分库数据分片算法\n *\n * @author jingmin.yang\n * @date 2022/4/18 19:23\n */\npublic class DatasourceShardingAlgorithmV2 implements PreciseShardingAlgorithm<String> {\n    // 排序存储结构：SortedMap<虚拟节点，物理节点>\n    private static final SortedMap<Integer, String> virtualToRealMap = new TreeMap<Integer, String>();\n\n    /**\n     * 基于所有数据源初始化订单分库虚拟节点\n     * @return\n     */\n    static {\n        List<String> datasources = new ArrayList<String>();\n        datasources.add(\"datasource-name\");\n        datasources.add(\"datasource-name-1\");\n        datasources.add(\"datasource-name-2\");\n        datasources.add(\"datasource-name-3\");\n        // 初始化分库虚拟节点数据\n        init(virtualToRealMap, datasources);\n    }\n\n    @Override\n    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<String> preciseShardingValue) {\n        // shardingKey即为分片键值\n        String shardingKey = preciseShardingValue.getValue();\n        return getShardingDatasource(virtualToRealMap, shardingKey);\n    }\n\n    /**\n     * 获取分库物理节点\n     * @param virtualToRealMap 分库虚拟节点\n     * @param shardingKey 分库的分片键标识\n     * @author: jingmin.yang\n     * @create: 2021/9/10 10:41\n     * @return\n     */\n    static String getShardingDatasource(SortedMap<Integer, String> virtualToRealMap, String shardingKey) {\n        int hashValue = hash(shardingKey);\n        // 顺时针找最近的虚拟节点\n        SortedMap<Integer, String> subVirtualToRealMap = virtualToRealMap.tailMap(hashValue);\n        if (subVirtualToRealMap.isEmpty()) {\n            return virtualToRealMap.get(virtualToRealMap.firstKey());\n        }\n        return subVirtualToRealMap.get(subVirtualToRealMap.firstKey());\n    }\n\n    /**\n     * 初始化hash环\n     *\n     * @param: virtualToRealMap\n     * @param: realNodes\n     * @author: jingmin.yang\n     * @create: 2021/9/14 16:29\n     */\n    static void init(SortedMap<Integer, String> virtualToRealMap, List<String> realNodes) {\n        for(String node: realNodes) {\n            virtualToRealMap.put(hash(node), node);\n\n            // 虚拟出指定数量的虚拟节点\n            int count = 0;\n            int i = 0;\n            int virtualNum = realNodes.size();\n            while (count < virtualNum) {\n                i++;\n                String virtualNode = node + \"#\" + i;\n                // 计算hash值\n                int hashValue = hash(virtualNode);\n                if (!virtualToRealMap.containsKey(hashValue)) {\n                    // 虚拟节点放到环上去 数据结构：红黑树\n                    virtualToRealMap.put(hashValue, node);\n                    count++;\n                }\n            }\n        }\n    }\n\n    /**\n     *\n     * MurmurHash是一种非加密型哈希函数，适用于一般的哈希检索操作,与其它流行的哈希函数相比，对于规律\n     * 性较强的key，MurmurHash的随机分布（离散性）特征表现更好，而且具有低碰撞率。Redis对节点进行\n     * shard时采用的是这种算法。\n     *\n     * @param: key 分片键\n     * @author: jingmin.yang\n     * @create: 2021/9/14 19:17\n     */\n    static int hash(String key) {\n        ByteBuffer buf = ByteBuffer.wrap(key.getBytes());\n        int seed = 305441741;\n        ByteOrder byteOrder = buf.order();\n        buf.order(ByteOrder.LITTLE_ENDIAN);\n        int m = 1540483477;\n        byte r = 24;\n\n        int h;\n        int k;\n        for (h = seed ^ buf.remaining(); buf.remaining() >= 4; h ^= k) {\n            k = buf.getInt();\n            k *= m;\n            k ^= k >>> r;\n            k *= m;\n            h *= m;\n        }\n\n        if (buf.remaining() > 0) {\n            ByteBuffer finish = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);\n            finish.put(buf).rewind();\n            h ^= finish.getInt();\n            h *= m;\n        }\n\n        h ^= h >>> 13;\n        h *= m;\n        h ^= h >>> 15;\n        buf.order(byteOrder);\n        if (h < 0) {\n            h = Math.abs(h);\n        }\n        return h;\n    }\n}\n```\n\n测试： \n\n```java\npublic static void main(String[] args) {         \n    System.out.println(DatasourceShardingAlgorithmV2.getShardingDatasource(virtualToRealMap, \"U0000000001\"));\n}\n```\n\n结果：\n\n```Plaintext\ndatasource-name-1\n```\n\n **4.3规则配置**Sharding-JDBC 可以通过 Java, YAML, Spring 命名空间 和 Spring Boot Starter 这 4种⽅式进⾏配置，开发者可根据场景选择适合的配置⽅式。当前⽅案使⽤ YAML ⽅式进⾏规则配置，application.ym 内容如下（**红⾊部分为⾃⾏扩展内容**）： \n\n```yaml\nspring: \n shardingsphere:\n  # sharding数据源配置\n  datasource:\n    # 对应下方自定义的数据源名称\n    names: datasource-name,datasource-name-1,datasource-name-2,datasource-name-3\n    # 数据源基础配置\n    datasource-name:\n     # 数据源驱动类型\n      type: com.alibaba.druid.pool.DruidDataSource\n      # 数据库驱动类\n      driver-class-name: com.mysql.jdbc.Driver\n      # 数据库连接地址\n      url: jdbc:mysql://127.0.0.1:3306/datasource_name_0\n     # 数据库用户名\n      username: username\n     # 数据库用户密码\n      password: password\n    # 自定义第二个数据源（新数据源1）\n    datasource-name-1:\n      type: com.alibaba.druid.pool.DruidDataSource\n      driver-class-name: com.mysql.jdbc.Driver\n      url: jdbc:mysql://127.0.0.1:3306/datasource_name_1\n      username: username\n      password: password\n    # 自定义第三个数据源（新数据源2）\n    datasource-name-2:\n      type: com.alibaba.druid.pool.DruidDataSource\n      driver-class-name: com.mysql.jdbc.Driver\n      url: jdbc:mysql://127.0.0.1:3306/datasource_name_2\n      username: username\n      password: password\n    # 自定义第四个数据源（新数据源3）\n    datasource-name-3:\n      type: com.alibaba.druid.pool.DruidDataSource\n      driver-class-name: com.mysql.jdbc.Driver\n      url: jdbc:mysql://127.0.0.1:3306/datasource_name_3\n      username: username\n      password: password\n  # 采用sharding-jdbc进行分库分表设计\n  sharding:\n    # 默认数据源\n    default-data-source-name: datasource-name\n    # oldDatabaseStrategy是自行扩展配置（可选），用于双操作（双查询&双更新&双删除）中获取原有数据源，pom需要对应引入4.1.1.001自行扩展版本\n    oldDatabaseStrategy:\n      standard:\n        # 分库分片键\n        shardingColumn: sharding_key\n        # 原有的数据源分片算法，本算法返回的是固定的原有数据源datasource-name\n        preciseAlgorithmClassName: com.yang.sharding.DatasourceShardingAlgorithm\n   # 订单分库新的分片算法，根据sharding_key进行分库\n   default-database-strategy:\n      standard:\n        # 分库分片键\n        shardingColumn: sharding_key\n        # 新的分库分片算法\n        preciseAlgorithmClassName: com.yang.sharding.DatasourceShardingAlgorithmV2\n    # 分库分表配置，只有分库&分表的表需要在此配置，不分库不分表不需要配置，数据会存入默认数据源\n    tables:\n      # table_0使用分库+分表\n      table_0:\n        actual-data-nodes: datasource-name.table_0_$->{0..63},datasource-name-$->{1..3}.table_0_$->{0..63}\n        logic-table: table_0\n        # 数据库表策略配置\n        table-strategy:\n          standard:\n            # 表分片键\n            shardingColumn: sharding_key\n            # 表分片算法\n            preciseAlgorithmClassName: com.yang.TableShardingAlgorithm\n      # table_1使用分库，不使用分表\n      table_1:\n        actual-data-nodes: datasource-name.table_1, datasource-name-$->{1..3}.table_1\n        logic-table: table_1\n```\n\n**4.4代码改造准备**由于 sharding 是对分⽚键使⽤分⽚算法，获取到对应的数据源进⾏操作，⽽分⽚键是从请求 sql 中提取⽽来，所以需要在代码层⾯改造⽀持分库，所有 CRUD 操作必须要带上分⽚键参数。\n\n```sql\n<!-- 这里假设数据库是MySQL，分片键是user_id字段 -->\n<!-- 新增操作 -->\nINSERT INTO order_info('id', 'user_id', 'update_time') VALUES('O0000000001', 'U0000000001', NOW());\n<!-- 查询操作 -->\nSELECT * FROM order_info WHERE user_id='U0000000001';\n<!-- 更新操作 -->\nUPDATE order_info SET update_time=NOW() WHERE user_id='U0000000001';\n<!-- 删除操作 -->\nDELETE FROM order_info WHERE user_id='U0000000001';\n```\n\n \n\n# **总结**\n\n**⾄此，基于 Sharding-JDBC 完整的订单分库改造完成，包含选择分库分⽚键、定义分库数据分⽚算法，添加数据源&表配置，代码改造等。**本⽅案已有实际应⽤案例，如有需要的⼩伙伴可作为参考，有问题也可直接联系我，⼀起讨论学习。","date":"2022-04-24","author":"杨敬敏 Ginee后端工程师","excerpt":"本⽅案基于Apache开源项⽬ ShardingSphere 采 Sharding-JDBC 进⾏订单⽔平分库设计（数据分⽚）缓解存储压⼒。","metaDescription":null,"createdAt":"2023-03-29T07:36:03.794Z","updatedAt":"2023-03-29T07:36:04.607Z","publishedAt":"2023-03-29T07:36:04.603Z","locale":"zh","caseStudiesType":{"data":null},"cover":{"data":null},"metaLogo":{"data":null},"localizations":{"data":[]}}},"article":{"id":38,"attributes":{"feature":true,"title":"金鹰系统数据库分库分表策略","content":"为了全面监测区域内企业的金融风险，金鹰系统汇集了工商、舆情、投诉举报等多维度的大量数据。随着时间的推移，积攒的数据量越来越大，对系统的数据处理效率提出了更高的要求。目前金鹰系统单表最大数据量已达到亿级别，传统的数据库性能优化手段已无法满足系统的性能需求。为了减少数据库的负担，提升数据库响应速度，缩短查询时间，防控中心技术团队对数据进行分库分表。\n\n**什么是分库分表？**\n\n分库指从单个数据库拆分成多个数据库的过程，将数据散落在多个数据库中。而分表指从单张表拆分成多张表的过程，将数据散落在多张表内。\n\n**分库分表能解决什么问题？**\n\n如果数据库的查询压力过大，就需要考虑拆库，通过分库来分担单个数据库的连接压力。比如，数据库每秒需处理3500笔请求，假设单库每秒能处理的请求数最大为1000笔，那么就可以考虑拆分成4个库，来分散查询连接压力。\n\n如果单表数据量过大，当数据量超过一定量级后，无论是对于数据查询还是数据更新，在经过索引优化等纯数据库层面的传统优化手段之后，还是可能存在性能问题。这时候就需要去换个思路来解决问题。比如：从数据生产源头、数据处理源头来解决问题，既然数据量很大，那我们就来个分而治之，化整为零。可以通过分表把数据按照一定的规则拆分成多张表，来解决单表环境下无法解决的存取性能问题。\n\n►►►\n\n**金鹰系统数据库分库分表策略**\n\n金鹰系统目前采用ShardingSphere（一款分库分表框架）作为数据库中间件。通过对企业名称、法人等多个关键字进行组合散列运算得到企业ID。以企业ID作为分片键，ShardingSphere将根据企业ID与配置的路由信息决定该企业的数据落于哪个数据库分片、哪张表上，合理的算法使数据均匀分布在各数据库分片上，不同分片保存的数据量大致相同，从而确保数据处理效果能得到最大的优化。 \n\n![img](https://u01f1kqxrl.feishu.cn/space/api/box/stream/download/asynccode/?code=MGM5YWQ1NmU0MzBlNzFiYzhmMjYyN2MyNjk5MTE5ZThfQzlkaERYQzZWTWVWRFBkNkRYTm1WcG13eWtlZXVDZzVfVG9rZW46Ym94Y25QSGdXUEwwaTQwenB6eWRoajFIU2xlXzE2ODAwNzM5MzQ6MTY4MDA3NzUzNF9WNA)","date":"2022-04-21","author":"广州金融科技","excerpt":"金鹰系统通过对监管企业的海量信息数据采用数据分库分表方式治理，提高了数据查询、数据处理效率","metaDescription":null,"createdAt":"2023-03-29T07:12:54.670Z","updatedAt":"2023-03-29T07:12:56.574Z","publishedAt":"2023-03-29T07:12:56.571Z","locale":"zh","caseStudiesType":{"data":null},"cover":{"data":null},"metaLogo":{"data":null},"localizations":{"data":[]}}}}},
    "staticQueryHashes": []}