Flink实时数仓同步:流水表实战详解

news2024/11/18 9:35:26

一、背景

在大数据领域,初始阶段业务数据通常被存储于关系型数据库,如MySQL。然而,为满足日常分析和报表等需求,大数据平台采用多种同步方式,以适应这些业务数据的不同存储需求。这些同步存储方式包括离线仓库和实时仓库等,选择取决于业务需求和数据特性。

一项常见需求是,大数据分析平台需要能够检索某张业务表的变更记录,并以每天为单位统计每条数据的变更频率。以下是示例:

  1. [Mysql] 业务数据 - 用户表全量数据:
idnamephonegendercreate_timeupdate_time
1jack1112023-06-01 13:00:002023-06-01 13:00:00
2jason2222023-06-01 13:00:002023-06-01 13:00:00
3tom3332023-06-01 13:00:002023-06-01 13:00:00
  1. [Mysql] 2023-06-02 业务数据新增了一名tony用户,且多次更改了tom的手机号,此时表数据如下:
idnamephonegendercreate_timeupdate_time备注
1jack1112023-06-01 13:00:002023-06-01 13:00:00
2jason2222023-06-01 13:00:002023-06-01 13:00:00
3tom5552023-06-01 13:00:002023-06-02 09:00:00(手机号从333->444->555)
4tony6662023-06-02 10:00:002023-06-02 10:00:00(新增tony用户)

加粗为更新/新增数据

  1. [大数据平台] 2023-06-02日业务人员在大数据分析平台中查看用户表实时变更记录,数据如下:
idnamephonegendercreate_timeupdate_timeop(操作类型)before(变更前数据)dt
3tom4442023-06-01 13:00:002023-06-02 08:00:00u{3,tom,333,男,…}2023-06-02
3tom5552023-06-01 13:00:002023-06-02 09:00:00u{3,tom,444,男,…}2023-06-02
4tony6662023-06-02 10:00:002023-06-02 10:00:00cnull2023-06-02

op字段为每条数据的操作类型:r/c/u/d

  1. [大数据平台] 业务人员在大数据分析平台中根据id+姓名维度统计2023-06-02日的变更次数,期望数据如下:
idnamecountdt
3tom22023-06-02
4tony12023-06-02

根据上述需求,我们可以得出需要构建实时流水表以满足业务数据的实时分析需求。尽管离线数仓中存在流水表,但由于以下原因,离线数仓并不符合此需求:

  1. 同步延迟问题: 离线数仓的同步方式通常为T+1,而上述需求要求实时查看当天业务数据的变更情况。
  2. 同步数据源问题: 离线数仓的T+1同步通常是通过 SQL 读取业务表数据,而不是读取 binlog 数据。这导致离线同步只能获取到 Tom 的最后一次更改数据,例如手机号=555,而无法获取所有更改项。

下面将讨论实现方案。

二、实时同步+拉链表实现

2.1、技术选型

鉴于业务数据通常存储在关系型数据库中,我们选择采用Flink-CDC持续读取binlog日志进行实时同步。为了保证实时数据能够高效写入下游并支持用户OLAP查询分析,这里选择了企业中常见的MMP库Doris作为实时数仓的存储层。整体架构如下图所示:

在这里插入图片描述

2.2、数据流转过程

Flink 实时同步程序将捕获到的 MySQL 数据变更事件进行实时处理。对于新增(INSERT)、修改(UPDATE)、删除(DELETE)等操作,构建流水表更新语句同步至下游Doris,数据流转过程如下所示:

在这里插入图片描述

2.3、流水表设计

  1. 实时流水表是在大数据分析场景中常用的数据结构之一,用于记录业务数据的实时变更情况。该表主要用于跟踪数据的变更历史,捕捉每一次更新、插入或删除的操作,以便实时分析和监控业务数据的动态变化。

  2. 背景需求需要实时检索某张业务表的历史变更记录,并以每天为单位统计每条数据的变更频率,因此在Doris中设计表结构时采用了Unique数据模型。建表语句如下,其中以id和update_time为unique key从而保证秒级唯一性,同时采用动态分区按天进行划分,建表语句如下:

CREATE TABLE `example_user_stream`
(
    `id` largeint(40) NOT NULL COMMENT '用户id',
    `update_time` datetime NULL COMMENT '用户更新时间',
    `dt` date NULL COMMENT '流水日期',
    `create_time` datetime NULL COMMENT '用户注册时间',
    `name` varchar(50) NOT NULL COMMENT '用户昵称',
    `phone` largeint(40) NULL COMMENT '手机号',
    `gender` varchar(5) NULL COMMENT '用户性别',
    `op` varchar(4) NOT NULL COMMENT '每条数据的操作类型:r/c/u/d',
    `before` STRING NULL COMMENT '变更前数据',
    `binlog` STRING NULL COMMENT 'binlog全量日志'
) ENGINE=OLAP
UNIQUE KEY(`id`, `update_time`, `dt`)
COMMENT '用户流水表'
PARTITION BY RANGE(dt)()
DISTRIBUTED BY HASH(id) BUCKETS 8
PROPERTIES
(
    "dynamic_partition.enable" = "true",
    "dynamic_partition.time_unit" = "DAY",
    "dynamic_partition.start" = "-90",
    "dynamic_partition.end" = "3",
    "dynamic_partition.prefix" = "p",
    "dynamic_partition.buckets" = "8"
);

该表利用了Doris的动态分区功能,将分区粒度设置为天级,并采取了预先建立3天分区的策略,同时设定了90天的过期时间;更多信息可参考Doris动态分区介绍

2.4、实时同步逻辑

  1. 首先,由于实时流水表同步使用Flink-cdc读取关系型数据库,flink-cdc提供了四种模式: “initial”,“earliest-offset”,“latest-offset”,“specific-offset” 和 “timestamp”。本文使用的Flink-connector-mysq是2.3版本,这里简单介绍一下这四种模式:

    • initial (默认):在第一次启动时对受监视的数据库表执行初始快照,并继续读取最新的 binlog。
    • earliest-offset:跳过快照阶段,从可读取的最早 binlog 位点开始读取
    • latest-offset:首次启动时,从不对受监视的数据库表执行快照, 连接器仅从 binlog 的结尾处开始读取,这意味着连接器只能读取在连接器启动之后的数据更改。
    • specific-offset:跳过快照阶段,从指定的 binlog 位点开始读取。位点可通过 binlog 文件名和位置指定,或者在 GTID 在集群上启用时通过 GTID 集合指定。
    • timestamp:跳过快照阶段,从指定的时间戳开始读取 binlog 事件。
  2. 由于流水表只需知晓更改过程而无需记录全量数据,故采用timestamp模式从指定的时间戳【今日零点】开始读取 binlog 事件开始读取;这里之所以没有选择earliest-offset模式是因为doris在建表时分区大于或等于今日,而binlog可能保存多天以前的数据而无法存放至doris分区中,故采用timestamp模式.

  3. 此外,由于实时流水表同步需要对 binlog 数据进行解析及判断更新操作类型,因此,Flink CDC SQL 方式的表建立不再满足我们的要求。为了更好地实现这一功能,我们需要采用 API 方式来构建解决方案,代码如下:

    import org.apache.flink.api.common.eventtime.WatermarkStrategy;
    import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
    import com.ververica.cdc.debezium.JsonDebeziumDeserializationSchema;
    import com.ververica.cdc.connectors.mysql.source.MySqlSource;
    
    public class MySqlSourceExample {
      public static void main(String[] args) throws Exception {
        MySqlSource<String> mySqlSource = MySqlSource.<String>builder()
            .hostname("yourHostname")
            .port(yourPort)
            .databaseList("yourDatabaseName") // 设置捕获的数据库, 如果需要同步整个数据库,请将 tableList 设置为 ".*".
            .tableList("yourDatabaseName.yourTableName") // 设置捕获的表
            .username("yourUsername")
            .password("yourPassword")
            .startupOptions(StartupOptions.timestamp(1685548800000L)) // 从2023-06-01零点处读取binlog
            .deserializer(new JsonDebeziumDeserializationSchema()) // 将 SourceRecord 转换为 JSON 字符串
            .build();
    
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
    
        // 设置 3s 的 checkpoint 间隔
        env.enableCheckpointing(3000);
    
        env
          .fromSource(mySqlSource, WatermarkStrategy.noWatermarks(), "MySQL Source")
          // 设置 source 节点的并行度为 4
          .setParallelism(4)
          .print().setParallelism(1); // 设置 sink 节点并行度为 1 
    
        env.execute("Print MySQL Snapshot + Binlog");
      }
    }
    

    代码摘自mysql-cdc-connector官网示例

  4. 这里我们以2023-06-02日的[Mysql]业务数据为例,新增了一名tony用户,且多次更改了tom的手机号,此时表数据如下:

idnamephonegendercreate_timeupdate_time备注
1jack1112023-06-01 13:00:002023-06-01 13:00:00
2jason2222023-06-01 13:00:002023-06-01 13:00:00
3tom5552023-06-01 13:00:002023-06-02 09:00:00(手机号从333->444->555)
4tony6662023-06-02 10:00:002023-06-02 10:00:00(新增tony用户)

加粗为更新/新增数据

  1. 此时Flink应用获取到的数据如下:
# 新增tony变更数据如下
{
	"before": null,
	"after": {
		"id": 4,
		"name": "tony",
		"phone": "666",
		"gender": "男",
		"create_time": "2023-06-02T02:00:00Z",
		"update_time": "2023-06-02T02:00:00Z"
	},
	"source": {
		# 元数据信息忽略
	},
	"op": "c", # 操作类型
	"ts_ms": 1706768344113,
	"transaction": null
}
# tom手机号333->444变更数据如下
{
	"before": {
		"id": 3,
		"name": "tom",
		"phone": "333",
		"gender": "男",
		"create_time": "2023-06-01T05:00:00Z",
		"update_time": "2023-06-01T05:00:00Z"
	},
	"after": {
		"id": 3,
		"name": "tom",
		"phone": "444",
		"gender": "男",
		"create_time": "2023-06-01T05:00:00Z",
		"update_time": "2023-06-01T23:00:00Z"
	},
	"source": {
		# 元数据信息忽略
	},
	"op": "u", # 操作类型
	"ts_ms": 1706768454904,
	"transaction": null
}
# tom手机号444->555变更数据如下
{
	"before": {
		"id": 3,
		"name": "tom",
		"phone": "444",
		"gender": "男",
		"create_time": "2023-06-01T05:00:00Z",
		"update_time": "2023-06-01T23:00:00Z"
	},
	"after": {
		"id": 3,
		"name": "tom",
		"phone": "555",
		"gender": "男",
		"create_time": "2023-06-01T05:00:00Z",
		"update_time": "2023-06-02T00:00:00Z"
	},
	"source": {
		# 元数据信息忽略
	},
	"op": "u", # 操作类型
	"ts_ms": 1706768521452,
	"transaction": null
}
  1. 我们使用 Flink CDC MySQL 同步数据时可以获取到binlog数据中的 op 字段。op 字段是用来记录每条数据的操作类型的标志。具体的操作类型如下:

    • op=d 代表删除操作
    • op=u 代表更新操作
    • op=c 代表新增操作
    • op=r 代表全量读取,而不是来自 binlog 的增量读取
  2. 当 Flink 同步程序接收到 op=c 表示新增 Tony 的数据时,首先解析 binlog 日志,提取其中的 opbeforeafter 数据。接着,从 after 中截取 update_time 的日期值,并将这些信息拼装成 Doris 的 INSERT 语句,通过该语句将数据插入到 Doris 中。这一过程完成后,即成功记录了这条流水数据,sql语句如下:

INSERT INTO example_user_stream (id, update_time, dt, create_time, name, phone, gender, op, `before`, `binlog`)
VALUES 
(4, '2023-06-02 10:00:00', '2023-06-02', '2023-06-02 10:00:00', 'tony', 555, '男', 'c', null, '{"before":null,"after":{"id":4,"name":"tony","phone":"666","gender":"男","create_time":"2023-06-02T02:00:00Z","update_time":"2023-06-02T02:00:00Z"},"source":{"version":"1.6.4.Final","connector":"mysql","name":"mysql_binlog_source","ts_ms":1706768344000,"snapshot":"false","db":"yushu_dds","sequence":null,"table":"user","server_id":2307031958,"gtid":"71221bfd-56e8-11ee-8275-fa163e4ecceb:33719321","file":"3509-binlog.000191","pos":643757739,"row":0,"thread":null,"query":null},"op":"c","ts_ms":1706768344113,"transaction":null}');
  1. 当 Flink 同步程序接收到op=u修改tom手机号的数据时,和上述步骤一样提取binlog日志,需要注意的是此时要将before数据写入到doris数据中,sql语句如下:
# tom手机号333->444:
INSERT INTO example_user_stream (id, update_time, dt, create_time, name, phone, gender, op, `before`,`binlog`)
VALUES 
(3, '2023-06-02 08:00:00', '2023-06-02', '2023-06-02 13:00:00', 'tom', 444, '男', 'u', '{"id":3,"name":"tom","phone":"333","gender":"男","create_time":"2023-06-01T05:00:00Z","update_time":"2023-06-01T05:00:00Z"}', '{"before":{"id":3,"name":"tom","phone":"333","gender":"男","create_time":"2023-06-01T05:00:00Z","update_time":"2023-06-01T05:00:00Z"},"after":{"id":3,"name":"tom","phone":"444","gender":"男","create_time":"2023-06-01T05:00:00Z","update_time":"2023-06-01T23:00:00Z"},"source":{"version":"1.6.4.Final","connector":"mysql","name":"mysql_binlog_source","ts_ms":1706768454000,"snapshot":"false","db":"yushu_dds","sequence":null,"table":"user","server_id":2307031958,"gtid":"71221bfd-56e8-11ee-8275-fa163e4ecceb:33719761","file":"3509-binlog.000191","pos":692873739,"row":0,"thread":null,"query":null},"op":"u","ts_ms":1706768454904,"transaction":null}');

# tom手机号444->555:
INSERT INTO example_user_stream (id, update_time, dt, create_time, name, phone, gender, op, `before`,`binlog`)
VALUES 
(3, '2023-06-02 09:00:00', '2023-06-02', '2023-06-02 13:00:00', 'tom', 555, '男', 'u', '{"id":3,"name":"tom","phone":"444","gender":"男","create_time":"2023-06-01T05:00:00Z","update_time":"2023-06-01T23:00:00Z"}', '{"before":{"id":3,"name":"tom","phone":"444","gender":"男","create_time":"2023-06-01T05:00:00Z","update_time":"2023-06-01T23:00:00Z"},"after":{"id":3,"name":"tom","phone":"555","gender":"男","create_time":"2023-06-01T05:00:00Z","update_time":"2023-06-02T00:00:00Z"},"source":{"version":"1.6.4.Final","connector":"mysql","name":"mysql_binlog_source","ts_ms":1706768521000,"snapshot":"false","db":"yushu_dds","sequence":null,"table":"user","server_id":2307031958,"gtid":"71221bfd-56e8-11ee-8275-fa163e4ecceb:33720328","file":"3509-binlog.000191","pos":806151026,"row":0,"thread":null,"query":null},"op":"u","ts_ms":1706768521452,"transaction":null}');
  1. [大数据平台] 2023-06-02日业务人员在大数据分析平台中查看用户表实时变更记录,sql查询及结果如下:
SELECT * FROM example_user_stream PARTITION p20230602;
idupdate_timedtcreate_timenamephonegenderopbeforebinlog(部分省略)
42023-06-02 10:00:002023-06-022023-06-02 10:00:00tony555cNULL{“before”:null,“after”:{…}…}
32023-06-02 08:00:002023-06-022023-06-02 13:00:00tom444u{“id”:3,“name”:“tom”,“phone”:“333”,“gender”:“男”,“create_time”:“2023-06-01T05:00:00Z”,“update_time”:“2023-06-01T05:00:00Z”}{“before”:null,“after”:{…}…}
32023-06-02 09:00:002023-06-022023-06-02 13:00:00tom555u{“id”:3,“name”:“tom”,“phone”:“444”,“gender”:“男”,“create_time”:“2023-06-01T05:00:00Z”,“update_time”:“2023-06-01T23:00:00Z”}{“before”:null,“after”:{…}…}
  1. [大数据平台] 业务人员在大数据分析平台中根据id+姓名维度统计2023-06-02日的变更次数,sql查询及结果如下:
SELECT id,name,count(*) as 'count',dt FROM example_user_stream PARTITION p20240201 group by dt,id,name;
idnamecountdt
3tom22023-06-02
4tony12023-06-02

至此流水表的实时同步逻辑结束,删除操作与前述的修改操作类似,实现方式基本一致。

三、总结

FlinkCDC与流水表的结合:

  1. 实时同步: FlinkCDC通过实时捕获数据库变更,结合Flink的处理能力,将变更逻辑应用到流水表中,实现实时同步。
  2. 数据一致性: 结合Flink的状态管理,可以确保对数据变更的精确追踪和一致性处理,避免数据丢失或不一致。
  3. 删除操作: FlinkCDC同样适用于删除操作,将删除事件同步到流水表,确保流水表记录了所有的数据变更历史。
  4. 动态分区支持: FlinkCDC与动态分区的流水表结合,可以更灵活地管理和查询历史数据,使得数据的存储和查询更为高效。

总体而言,FlinkCDC与流水表的结合为实时数据同步提供了可靠的解决方案,既能满足实时性要求,又能保证数据变更历史的完整性和一致性。

四、相关资料

  • Doris 数据模型
  • MySQL CDC Connector
  • Flink实时数仓同步:拉链表实战详解
  • 深入数仓离线数据同步:问题分析与优化措施

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

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

相关文章

蓝桥杯---垒骰子

赌圣atm晚年迷恋上了垒骰子&#xff0c;就是把骰子一个垒在另一个上边&#xff0c;不能歪歪扭扭&#xff0c;要垒成方柱体。经过长期观察&#xff0c;atm 发现了稳定骰子的奥秘&#xff1a;有些数字的面贴着会互相排斥&#xff01;我们先来规范一下骰子&#xff1a;1的对面是4&…

【BIAI】Lecture 9-Motor system 1

Motor System 专业词汇 skeletal muscle 骨骼肌 smooth muscle 平滑肌 cardiac muscle 心肌 flexor reflex 屈曲反射 central pattern generators 中央模式生成器 bio-inspired bipedal robots 仿生双足机器人 myotatic stretch reflex 肌肉自伸展反射 Cortex optic nerve 视皮…

vite和vue-cli实现原理和优化及区别

Vite&#xff1a; 1. 实现原理&#xff1a; Vite 是一个基于 ESModule 的构建工具。它利用原生 ESModule 的特性&#xff0c;将每个文件作为一个模块&#xff0c;通过浏览器去解析和执行&#xff0c;而不需要提前将文件打包成一个单独的 bundle。Vite 利用浏览器的原生 ESMod…

【android】对于google-webrtc的性能中, memory leak

目录 zlmediakit->webrtcplay->app webrtcutil1/3 测试程序等 zlmediakit->webrtcplay->app 编译sdk 32 有时候会从开始新增5M&#xff0c;就稳定在一个值了 webrtcutil1/3 测试程序等 编译sdk 30

带着问题读源码——Spring MVC是怎么找到接口实现类的?

引言 我们的产品主打金融服务领域&#xff0c;以B端客户为我们的核心合作伙伴&#xff0c;然而&#xff0c;我们的服务最终将惠及C端消费者。在技术实现上&#xff0c;我们采用了公司自主研发的微服务框架&#xff0c;该框架基于SpringBoot&#xff0c;旨在提供高效、可靠的服…

架构学习(二):原生scrapy如何接入scrapy-redis,初步入局分布式

原生scrapy如何接入scrapy-redis&#xff0c;实现初步入局分布式 前言scrpy-redis分布式碎语 实现流程扩展结束 前言 scrpy-redis分布式 下图是scrpy-redis官方提供的架构图&#xff0c;按我理解&#xff0c;与原生scrapy的差异主要是把名单队列服务器化&#xff0c;也是存储…

Ps:Photomerge

Ps菜单&#xff1a;文件/自动/Photomerge Automate/Photomerge Photomerge 命令可用于自动合并多张照片成为一个无缝的全景图像&#xff0c;特别适用于景观摄影、建筑摄影&#xff0c;以及任何需要将多张图片合并为单一宽幅图像的场景。 执行 Photomerge 命令之后&#xff0c;首…

undo log 和 redo log的区别

undo log 和 redo log的区别 缓冲池&#xff08;Buffer Pool&#xff09;是MySQL用于存储数据页的内存区域&#xff0c;它用于减少对磁盘的读写操作&#xff0c;提高数据库的访问速度。在MySQL中&#xff0c;数据被分为多个固定大小的数据页&#xff08;通常为16KB&#xff09…

第十一篇【传奇开心果系列】Python的OpenCV技术点案例示例:三维重建

传奇开心果短博文系列 系列短博文目录Python的OpenCV技术点案例示例系列 短博文目录一、前言二、OpenCV三维重建介绍三、基于区域的SGBM示例代码四、BM&#xff08;Block Matching&#xff09;算法介绍和示例代码五、基于能量最小化的GC&#xff08;Graph Cut&#xff09;算法介…

【机器学习】科学库使用手册第2篇:机器学习任务和工作流程(已分享,附代码)

本系列文章md笔记&#xff08;已分享&#xff09;主要讨论人工智能相关知识。主要内容包括&#xff0c;了解机器学习定义以及应用场景&#xff0c;掌握机器学习基础环境的安装和使用&#xff0c;掌握利用常用的科学计算库对数据进行展示、分析&#xff0c;学会使用jupyter note…

Django响应式图像库django-pictures

什么是响应式图像&#xff1f; 响应式设计是指网页在不同尺寸的设备上都有良好的显示效果。响应式设计的网页图像&#xff0c;就是响应式图像。 django-pictures是使用现代代码&#xff08;如 AVIF 和 WebP&#xff09;的响应式跨浏览器图像库。 特点 使用 Picture 标签的响应…

微信小程序新手入门教程二:认识JSON配置文件

在上一篇我们介绍了微信小程序的注册和基本使用方式&#xff0c;并且写出了一个简单的页面&#xff0c;但是依然没有解释目录中的各种.json文件是做什么的。这篇我们就来认识一下各种JSON配置文件及其配置项。 一 认识JSON 首先先来认识一下JSON是什么。 JSON 指的是 JavaScri…

开源大数据集群部署(九)Ranger审计日志集成(solr)

作者&#xff1a;櫰木 1、下载solr安装包并解压包 tar -xzvf solr-8.11.2.gz cd solr-8.11.2 执行安装脚本 ./bin/install_solr_service.sh /opt/solr-8.11.2.tgz安装后&#xff0c;会在/etc/default/ 下生成solr.in.sh文件。 2、在rangeradmin下生成solr相关配置 cd /opt…

比瓴科技入围软件供应链安全赛道!为关键信息基础设施安全建设注入新动力

1月20日&#xff0c;中关村华安关键信息基础设施安全保护联盟会员大会暨关键信息基础设施安全保护论坛在北京成功举办&#xff0c;比瓴科技作为会员单位受邀出席。 本次论坛发布了《关键信息基础设施安全保护支撑能力白皮书&#xff08;2023&#xff09;》&#xff0c;比瓴科技…

C++进阶--C++11 lambda表达式

C进阶--C11 lambda表达式 一、lambda表达式的概念二、lambda表达式的语法2.1 lambda表达式语法格式2.2 lambda表达式捕获列表说明 三、lambda表达式交换两个数3.1 标准写法3.2 利用捕捉列表进行捕捉3.3 利用捕捉列表进行捕捉 四、lambda表达式的底层原理4.1 底层原理4.2 lambda…

flutter开发实战-Camera自定义相机拍照功能实现

flutter开发实战-Camera自定义相机拍照功能实现 一、前言 在项目中使用image_picker插件时候&#xff0c;在android设备上使用无法默认设置前置摄像头&#xff08;暂时不清楚什么原因&#xff09;&#xff0c;由于项目默认需要使用前置摄像头&#xff0c;所以最终采用自定义…

完整的 HTTP 请求所经历的步骤及分布式事务解决方案

1. 对分布式事务的了解 分布式事务是企业集成中的一个技术难点&#xff0c;也是每一个分布式系统架构中都会涉及到的一个东西&#xff0c; 特别是在微服务架构中&#xff0c;几乎可以说是无法避免。 首先要搞清楚&#xff1a;ACID、CAP、BASE理论。 ACID 指数据库事务正确执行…

【Java程序设计】【C00207】基于(JavaWeb+SSM)的宠物领养管理系统(论文+PPT)

基于&#xff08;JavaWebSSM&#xff09;的宠物领养管理系统&#xff08;论文PPT&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于ssm的宠物领养系统 本系统分为前台系统、管理员、收养者和寄养者4个功能模块。 前台系统&#xff1a;游客打开系统…

八、访存顺序(Memory Ordering)

前言 这部分的内容比较抽象&#xff0c;很多内容我无法理解&#xff0c;都是直接翻译过来的。虽然难&#xff0c;但是不可不看&#xff0c;如果遇到无法理解的都直接跳过&#xff0c;那后面都无法学习下去了。觉得无法理解是因为目前的知识还很欠缺&#xff0c;到后面具备了这…

大创项目推荐 题目:基于深度学习的手势识别实现

文章目录 1 前言2 项目背景3 任务描述4 环境搭配5 项目实现5.1 准备数据5.2 构建网络5.3 开始训练5.4 模型评估 6 识别效果7 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习的手势识别实现 该项目较为新颖&#xff0c;适合作为竞赛课题…