从 MySQL 到 ClickHouse 实时数据同步 —— Debezium + Kafka 表引擎

news2024/11/19 2:22:52

目录

一、总体架构

二、安装配置 MySQL 主从复制

三、安装配置 ClickHouse 集群

四、安装 JDK

五、安装配置 Zookeeper 集群

六、安装配置 Kafaka 集群

七、安装配置 Debezium-Connector-MySQL 插件

1. 创建插件目录

2. 解压文件到插件目录

3. 配置 Kafka Connector

(1)配置属性文件

(2)分发到其它节点

(3)以 distributed 方式启动 Kafka connect

(4)确认 connector 插件和自动生成的 topic

4. 创建 source connector

(1)Debezium 三个必要的配置说明

(2)创建源 mysql 配置文件

(3)创建 mysql source connector

八、在 ClickHouse 中创建库表、物化视图和视图

1. 建库

2. 创建 Kafka 表

3. 创建主表

4. 创建消费者物化视图

5. 创建视图

6. 验证

参考:


        本文介绍从 MySQL 作为源到 ClickHouse 作为目标的整个过程。MySQL 数据库更改通过 Debezium 捕获,并作为事件发布在到 Kafka 上。ClickHouse 通过 Kafka 表引擎按部分顺序应用这些更改,实时并保持最终一致性。相关软件版本如下:

  • MySQL:8.0.16
  • ClickHouse:24.1.8
  • JDK:11.0.22
  • zookeeper:3.9.1
  • Kafka:3.7.0
  • debezium-connector-mysql:2.4.2

        这种方案的优点之一是可以做到 ClickHouse 与 MySQL 的数据最终严格一致。

一、总体架构

        总体结构如下图所示。

        ClickHouse 是由四个实例构成的两分片、每分片两副本集群,票选和协调器使用 ClickHouse 自带的 keeper 组件。分片、副本、keeper 节点、Zookeeper集群、Kafaka集群、Debezium-Connector-MySQL 插件的部署如下表所示。

IP

主机名

实例角色

ClickHouse

Keeper

Zookeeper

Kafka

Debezium

Connector

MySQL

172.18.4.126

node1

分片1副本1

*

172.18.4.188

node2

分片1副本2

*

*

*

*

172.18.4.71

node3

分片2副本1

*

*

*

*

172.18.4.86

node4

分片2副本2

*

*

*

二、安装配置 MySQL 主从复制

        配置好主从复制后,在主库创建测试库表及数据:

-- 建库
create database test;

-- 建表
create table test.t1 (
  id bigint(20) not null auto_increment,
  remark varchar(32) default null comment '备注',
  createtime timestamp not null default current_timestamp comment '创建时间',
  primary key (id));

-- 插入三条测试数据
insert into test.t1 (remark) values ('第一行:row1'),('第二行:row2'),('第三行:row3');
commit;

三、安装配置 ClickHouse 集群

四、安装 JDK

五、安装配置 Zookeeper 集群

六、安装配置 Kafaka 集群

七、安装配置 Debezium-Connector-MySQL 插件

        在 node2 上执行以下步骤。

1. 创建插件目录

mkdir $KAFKA_HOME/plugins

2. 解压文件到插件目录

cd ~
# debezium-connector-mysql
unzip debezium-debezium-connector-mysql-2.4.2.zip -d $KAFKA_HOME/plugins/

3. 配置 Kafka Connector

(1)配置属性文件

# 先备份
cp $KAFKA_HOME/config/connect-distributed.properties $KAFKA_HOME/config/connect-distributed.properties.bak
# 编辑 connect-distributed.properties 文件
vim $KAFKA_HOME/config/connect-distributed.properties

        内容如下:

bootstrap.servers=node2:9092,node3:9092,node4:9092
group.id=connect-cluster
key.converter=org.apache.kafka.connect.json.JsonConverter
value.converter=org.apache.kafka.connect.json.JsonConverter
key.converter.schemas.enable=false
value.converter.schemas.enable=false
offset.storage.topic=connect-offsets
offset.storage.replication.factor=3
offset.storage.partitions=3
config.storage.topic=connect-configs
config.storage.replication.factor=3
status.storage.topic=connect-status
status.storage.replication.factor=3
status.storage.partitions=3
offset.flush.interval.ms=10000
plugin.path=/root/kafka_2.13-3.7.0/plugins

(2)分发到其它节点

scp $KAFKA_HOME/config/connect-distributed.properties node3:$KAFKA_HOME/config/
scp $KAFKA_HOME/config/connect-distributed.properties node4:$KAFKA_HOME/config/
scp -r $KAFKA_HOME/plugins node3:$KAFKA_HOME/
scp -r $KAFKA_HOME/plugins node4:$KAFKA_HOME/

(3)以 distributed 方式启动 Kafka connect

connect-distributed.sh -daemon $KAFKA_HOME/config/connect-distributed.properties 
# 确认日志是否有 ERROR
grep ERROR ~/kafka_2.13-3.7.0/logs/connectDistributed.out

(4)确认 connector 插件和自动生成的 topic

        查看连接器插件:

curl -X GET http://node2:8083/connector-plugins | jq

        从输出中可以看到,Kafka connect 已经识别到了 MySqlConnector source 插件:

[root@vvml-yz-hbase-test~]#curl -X GET http://node2:8083/connector-plugins | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   403  100   403    0     0   3820      0 --:--:-- --:--:-- --:--:--  3838
[
  {
    "class": "io.debezium.connector.mysql.MySqlConnector",
    "type": "source",
    "version": "2.4.2.Final"
  },
  {
    "class": "org.apache.kafka.connect.mirror.MirrorCheckpointConnector",
    "type": "source",
    "version": "3.7.0"
  },
  {
    "class": "org.apache.kafka.connect.mirror.MirrorHeartbeatConnector",
    "type": "source",
    "version": "3.7.0"
  },
  {
    "class": "org.apache.kafka.connect.mirror.MirrorSourceConnector",
    "type": "source",
    "version": "3.7.0"
  }
]
[root@vvml-yz-hbase-test~]#

        查看 topic:

kafka-topics.sh --list --bootstrap-server node2:9092,node3:9092,node4:9092

        从输出中可以看到,Kafka connect 启动时自动创建了 connect-configs、connect-offsets、connect-status 三个 topic:

[root@vvml-yz-hbase-test~]#kafka-topics.sh --list --bootstrap-server node2:9092,node3:9092,node4:9092
__consumer_offsets
connect-configs
connect-offsets
connect-status
[root@vvml-yz-hbase-test~]#

4. 创建 source connector

(1)Debezium 三个必要的配置说明

        Debezium 是一个众所周知的用于读取和解析 MySQL Binlog 的工具。它将 KafkaConnect 作为一个连接器进行集成,并对 Kafka 主题进行每一次更改。

  • 只记录后状态

        默认情况下,Debezium 会向 Kafka 发出每个操作的前状态和后状态的每条记录,这很难被 ClickHouse Kafka 表解析。此外,在执行删除操作的情况下(Clickhouse 同样无法解析),它会创建 tombstone 记录,即具有 Null 值的记录。下表展示了这个行为。

操作

操作前

操作后

附加记录

Create

Null

新纪录

-

Update

更新前的记录

更新后的记录

-

Delete

删除前的记录

Null

墓碑记录

        在 Debezium 配置中使用 ExtractNewRecod 转换器来处理此问题。由于有了这个选项,Debezium 只为创建/更新操作保留 after 状态,而忽略 before 状态。但缺点是,它删除了包含先前状态的 Delete 记录和墓碑记录,换句话说就是不再捕获删除操作。紧接着说明如何解决这个问题。

"transforms": "unwrap",
"transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState"
  • 重写删除事件

        要捕获删除操作,必须添加如下所示的重写配置:

"transforms.unwrap.delete.handling.mode":"rewrite"

        Debezium 使用此配置添加字段 __deleted,对于 delete 操作为 true,对于其他操作为 false。因此,删除将包含以前的状态以及 __deleted:true 字段。

  • 处理非主键更新

        在提供上述配置的情况下,更新记录(主键除外的每一列)会发出一个具有新状态的简单记录。通常在关系数据库系统中,更新后的记录会替换前一个记录,但在 ClickHouse 不行。出于性能考虑,ClickHouse 将行级更新变为多版本插入。在本示例中,MySQL 中的 test.t1 表以 id 列为主键,如果更新了 remark 列,在 ClikHouse 中,最终会得到重复的记录,这意味着 id 相同,但 remark 不同!

        幸运的是有办法应付这种情况。默认情况下,Debezium 会创建一个删除记录和一个创建记录,用于更新主键。因此,如果源更新 id,它会发出一个带有前一个 id 的删除记录和一个带有新 id 的创建记录。带有 __deleted=ture 字段的前一个记录将替换 CH 中的 stall 记录。然后,可以在视图中过滤暗示删除的记录。可以使用以下选项将此行为扩展到其他列:

"message.key.columns": "test.t1:id;test.t1:remark;test.t1:createtime"

        注意:
        通过更改连接器的键列,Debezium 将这些列用作主键,而不是源表的默认主键。因此,与数据库的一条记录相关的不同操作可能最终会出现在 Kafka 中的其他分区。由于记录在不同分区中失去顺序,除非确保 ClickHouse 顺序键和 Debezium 消息键相同,否则可能会导致 Clikchouse 中的数据不一致。

        经验法则如下:

  1. 根据想要的表结构来设计分区键和排序键。
  2. 提取分区和排序键的来源,假设它们是在物化过程中计算的。
  3. 合并所有这些列。
  4. 将步骤 3 的结果定义为 Debezium 连接器配置中的 message.column.keys。
  5. 检查 Clickhouse 排序键是否包含所有这些列。如果没有则添加它们。

        现在,通过将上述所有选项和常用选项放在一起,将拥有一个功能齐全的 Debezium 配置,能够处理 ClickHouse 所需的任何更改。

(2)创建源 mysql 配置文件

# 编辑文件
vim $KAFKA_HOME/plugins/source-mysql.json

        内容如下:

{
 "name": "mysql-source-connector",
 "config": {
     "connector.class": "io.debezium.connector.mysql.MySqlConnector",
     "database.hostname": "172.18.16.156",
     "database.port": "3307",
     "database.user": "dba",
     "database.password": "123456",
     "database.server.id": "1563307",
     "database.server.name": "dbserver1",
     "database.include.list": "test",
     "table.include.list": "test.t1",
     "topic.prefix": "mysql-clickhouse-test",
     "schema.history.internal.kafka.bootstrap.servers": "node2:9092,node3:9092,node4:9092",
     "schema.history.internal.kafka.topic": "schemahistory.mysql-clickhouse-test",
     "message.key.columns": "test.t1:id;test.t1:remark;test.t1:createtime",
     "transforms":"unwrap",
     "transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState",
     "transforms.unwrap.delete.handling.mode": "rewrite"
     }
 }

(3)创建 mysql source connector

# 创建 connector
curl -X POST -H 'Content-Type: application/json' -i 'http://node2:8083/connectors' -d @"/root/kafka_2.13-3.7.0/plugins/source-mysql.json"; echo
# 查看 connector 状态
curl -X GET http://node2:8083/connectors/mysql-source-connector/status | jq
# 查看 topic
kafka-topics.sh --list --bootstrap-server node2:9092,node3:9092,node4:9092

        从输出中可以看到,mysql-source-connector 状态为 RUNNING,并自动创建了三个 topic:

[root@vvml-yz-hbase-test~]#curl -X POST -H 'Content-Type: application/json' -i 'http://node2:8083/connectors' -d @"/root/kafka_2.13-3.7.0/plugins/source-mysql.json"; echo
HTTP/1.1 201 Created
Date: Thu, 25 Apr 2024 03:47:26 GMT
Location: http://node2:8083/connectors/mysql-source-connector
Content-Type: application/json
Content-Length: 818
Server: Jetty(9.4.53.v20231009)

{"name":"mysql-source-connector","config":{"connector.class":"io.debezium.connector.mysql.MySqlConnector","database.hostname":"172.18.16.156","database.port":"3307","database.user":"dba","database.password":"123456","database.server.id":"1563307","database.server.name":"dbserver1","database.include.list":"test","table.include.list":"test.t1","topic.prefix":"mysql-clickhouse-test","schema.history.internal.kafka.bootstrap.servers":"node2:9092,node3:9092,node4:9092","schema.history.internal.kafka.topic":"schemahistory.mysql-clickhouse-test","message.key.columns":"test.t1:id;test.t1:remark;test.t1:createtime","transforms":"unwrap","transforms.unwrap.type":"io.debezium.transforms.ExtractNewRecordState","transforms.unwrap.delete.handling.mode":"rewrite","name":"mysql-source-connector"},"tasks":[],"type":"source"}
[root@vvml-yz-hbase-test~]#curl -X GET http://node2:8083/connectors/mysql-source-connector/status | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   182  100   182    0     0  24045      0 --:--:-- --:--:-- --:--:-- 26000
{
  "name": "mysql-source-connector",
  "connector": {
    "state": "RUNNING",
    "worker_id": "172.18.4.188:8083"
  },
  "tasks": [
    {
      "id": 0,
      "state": "RUNNING",
      "worker_id": "172.18.4.188:8083"
    }
  ],
  "type": "source"
}
[root@vvml-yz-hbase-test~]#kafka-topics.sh --list --bootstrap-server node2:9092,node3:9092,node4:9092
__consumer_offsets
connect-configs
connect-offsets
connect-status
mysql-clickhouse-test
mysql-clickhouse-test.test.t1
schemahistory.mysql-clickhouse-test
[root@vvml-yz-hbase-test~]#

八、在 ClickHouse 中创建库表、物化视图和视图

        ClickHouse 可以利用 Kafka 表引擎将 Kafka 记录放入一个表中。需要定义三个对象:Kafka 表、主表和消费者物化视图。

1. 建库

create database db2 on cluster cluster_2S_2R;

2. 创建 Kafka 表

CREATE TABLE db2.kafka_t1 on cluster cluster_2S_2R
(
    `id` Int64,
    `remark` Nullable(String),
    `createtime` String,
    `__deleted` String
)
ENGINE = Kafka('node2:9092,node3:9092,node4:9092', 'mysql-clickhouse-test.test.t1', 'clickhouse', 'JSONEachRow');

3. 创建主表

        主表具有源结构和 __deleted 字段。这里使用的是 ReplicatedReplacingMergeTree,因为需要用已删除或更新的记录替换 stall 记录。

-- 创建本地表
CREATE TABLE db2.stream_t1 on cluster cluster_2S_2R
(
    `id` Int64,
    `remark` Nullable(String),
    `createtime` timestamp,
    `__deleted` String
)
ENGINE = ReplicatedReplacingMergeTree(
    '/clickhouse/tables/{shard}/db2/t1',
    '{replica}'
)
ORDER BY (id, createtime)
SETTINGS index_granularity = 8192;

-- 创建分布式表,以源表的主键 id 作为分片键,保证同一 id 的数据落在同一分片上
create table db2.t1_replica_all on cluster cluster_2S_2R
as db2.stream_t1
engine = Distributed(cluster_2S_2R, db2, stream_t1, id);

4. 创建消费者物化视图

        在创建物化视图前,先停止MySQL从库的复制。从库停止复制,不影响主库的正常使用,也就不会影响业务。此时从库的数据处于静止状态,不会产生变化,这使得获取存量数据变得轻而易举。然后创建物化视图时会自动将数据写入 db2.t1_replica_all 对应的本地表中。之后在 ClickHouse 集群中的任一实例上,都能从物化视图中查询到一致的 MySQL 存量数据。

-- MySQL 从库停止复制
stop slave;

        Kafka 表的每一条记录只读取一次,因为它的消费者组会改变偏移量,不能读取两次。因此,需要定义一个主表,并通过物化视图将每个 Kafka 表记录具化到它:

-- 注意时间戳的处理
CREATE MATERIALIZED VIEW db2.consumer_t1 on cluster cluster_2S_2R
TO db2.t1_replica_all
(
    `id` Int64,
    `remark` Nullable(String),
    `createtime` timestamp,
    `__deleted` String
) AS
SELECT id, remark, addHours(toDateTime(substring(createtime,1,length(createtime)-1)),8) createtime, __deleted FROM db2.kafka_t1;

5. 创建视图

        最后需要过滤每个被删除的记录,并拥有最新的记录,以防不同的记录具有相同的排序键。可以定义一个简单的视图来隐式完成这项工作:

CREATE VIEW db2.t1 on cluster cluster_2S_2R
(
    `id` Int64,
    `remark` Nullable(String),
    `createtime` String,
    `__deleted` String
) AS
SELECT *
FROM db2.consumer_t1
FINAL
WHERE __deleted = 'false';

6. 验证

        从 clickhouse 视图查询存量数据:

vvml-yz-hbase-test.172.18.4.126 :) select * from db2.t1;

SELECT *
FROM db2.t1

Query id: 2a51fd5e-6b4f-4b78-b522-62b7be32535b

┌─id─┬─remark───────┬─createtime──────────┬─__deleted─┐
│  2 │ 第二行:row2 │ 2024-04-25 11:51:07 │ false     │
└────┴──────────────┴─────────────────────┴───────────┘
┌─id─┬─remark───────┬─createtime──────────┬─__deleted─┐
│  1 │ 第一行:row1 │ 2024-04-25 11:51:07 │ false     │
│  3 │ 第三行:row3 │ 2024-04-25 11:51:07 │ false     │
└────┴──────────────┴─────────────────────┴───────────┘

3 rows in set. Elapsed: 0.007 sec. 

vvml-yz-hbase-test.172.18.4.126 :) 

        可以看到,存量数据已经与 MySQL 同步。

-- MySQL 主库修改数据
insert into test.t1 (remark) values ('第四行:row4');
update test.t1 set remark = '第五行:row5' where id = 4;
delete from test.t1 where id =1;
insert into test.t1 (remark) values ('第六行:row6');
 
-- MySQL 从库启动复制
start slave;

        此时 MySQL 的数据如下:

mysql> select * from test.t1;
+----+------------------+---------------------+
| id | remark           | createtime          |
+----+------------------+---------------------+
|  2 | 第二行:row2     | 2024-04-25 11:51:07 |
|  3 | 第三行:row3     | 2024-04-25 11:51:07 |
|  4 | 第五行:row5     | 2024-04-25 11:56:29 |
|  5 | 第六行:row6     | 2024-04-25 11:56:29 |
+----+------------------+---------------------+
4 rows in set (0.00 sec)

        从 clickhouse 视图查询增量数据:

vvml-yz-hbase-test.172.18.4.126 :) select * from db2.t1;

SELECT *
FROM db2.t1

Query id: b34bb37b-091b-490e-b55b-a0e9eedf5573

┌─id─┬─remark───────┬─createtime──────────┬─__deleted─┐
│  2 │ 第二行:row2 │ 2024-04-25 11:51:07 │ false     │
└────┴──────────────┴─────────────────────┴───────────┘
┌─id─┬─remark───────┬─createtime──────────┬─__deleted─┐
│  4 │ 第五行:row5 │ 2024-04-25 11:56:29 │ false     │
└────┴──────────────┴─────────────────────┴───────────┘
┌─id─┬─remark───────┬─createtime──────────┬─__deleted─┐
│  3 │ 第三行:row3 │ 2024-04-25 11:51:07 │ false     │
└────┴──────────────┴─────────────────────┴───────────┘
┌─id─┬─remark───────┬─createtime──────────┬─__deleted─┐
│  5 │ 第六行:row6 │ 2024-04-25 11:56:29 │ false     │
└────┴──────────────┴─────────────────────┴───────────┘

4 rows in set. Elapsed: 0.008 sec. 

vvml-yz-hbase-test.172.18.4.126 :) 

        可以看到,增量数据已经与 MySQL 同步,现在从 ClickHouse 视图查询的数据与 MySQL 一致。

        查看 Kafka 消费:

kafka-consumer-groups.sh --bootstrap-server node2:9092,node3:9092,node4:9092 --describe --group clickhouse

        输出如下:

[root@vvml-yz-hbase-test~]#kafka-consumer-groups.sh --bootstrap-server node2:9092,node3:9092,node4:9092 --describe --group clickhouse

GROUP           TOPIC                         PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG             CONSUMER-ID                                                                                  HOST            CLIENT-ID
clickhouse      mysql-clickhouse-test.test.t1 0          8               8               0               ClickHouse-vvml-yz-hbase-test.172.18.4.126-db2-kafka_t1-26e6aa8e-1f08-4491-8af7-f1822f1a7e94 /172.18.4.126   ClickHouse-vvml-yz-hbase-test.172.18.4.126-db2-kafka_t1
[root@vvml-yz-hbase-test~]#

        可以看到,最后被消费的消息偏移量是8,MySQL 的存量、增量数据都已经通过 Kafka 消息同步到了 ClickHouse。

参考:

  • Apply CDC from MySQL to ClickHouse
  • New Record State Extraction
  • 基于 HBase & Phoenix 构建实时数仓(5)—— 用 Kafka Connect 做实时数据同步
  • Greenplum 实时数据仓库实践(5)——实时数据同步

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

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

相关文章

快速搭建产品原型

今天要推荐的是一款能协助大家快速搭建产品原型的网站。对应产品经理来说,还真是个利器。 墨刀 https://modao.cc/brand 让想法快速呈现。 画APP原型图,很方便。 快速创建第一个原型文件。 在免费模版上进行修改。 感兴趣的同学去使用体验下吧~ 《Androi…

LabVIEW专栏七、队列

目录 一、队列范例二、命令簇三、队列应用1.1、并行循环队列1.2、命名队列和匿名队列1.2.1、命名队列1.2.2、匿名队列 1.3、长度为1的队列 队列是一种特殊的线性表,就是队列里的元素都是按照顺序进出。 队列的数据元素又称为队列元素。在队列中插入一个队列元素称为…

mysql reset slave reset master

mysql reset slave reset master 1、问题背景2、问题分析3、解决方法3.1、锁定主库,手动同步主库数据到从库,使得主从数据库数据一致3.1、从机执行stop slave、reset slave3.2、从机上再次指定主机的binlog文件名和偏移量3.3、从机执行 start slave3.4、…

蓝牙低能耗安全连接 – 数值比较

除了 LE Legacy 配对之外,LE Secure Connections 是另一种配对选项。 LE 安全连接是蓝牙 v4.2 中引入的增强安全功能。它使用符合联邦信息处理标准 (FIPS) 的算法(称为椭圆曲线 Diffie Hellman (ECDH))来生成密钥。对于 LE 安全连接&#xff…

MMSeg搭建模型的坑

Input type(torch.suda.FloatTensor) and weight type (torch.FloatTensor) should be same 自己搭建模型的时候,经常会遇到二者不匹配,以这种情况为例,是因为部分模型没有加载到CUDA上面造成的。 注意搭建模型的时候,所有层都应…

汽车企业安全上网解决方案

需求背景 成立于1866年的某老牌汽车服务独立运营商,目前已经是全球最大的独立汽车服务网络之一,拥有95年的历史,在全球150多个国家拥有17,000多个维修站,始终致力于为每一位车主提供高品质,可信赖的的专业汽车保养和维…

win10加入域环境

win10加入域环境 导航 文章目录 win10加入域环境导航一、关闭防火墙二、使客户端的电脑指向于域控服务器三、检验是否加入了域 一、关闭防火墙 在进行加入域服务之前,我们需要先关闭防火墙(为了不必要的麻烦) 按 winr调出运行窗口,输入 control打开控制面板 点击系统和安全点…

42. UE5 RPG 实现火球术伤害

上一篇,我们解决了火球术于物体碰撞的问题,现在火球术能够正确的和攻击目标产生碰撞。接下来,我们要实现火球术的伤害功能,在火球术击中目标后,给目标造成伤害。 实现伤害功能的思路是给技能一个GameplayEffect&#x…

JAVA毕业设计136—基于Java+Springboot+Vue的房屋租赁管理系统(源代码+数据库)

毕设所有选题: https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootVue的房屋租赁管理系统(源代码数据库)136 一、系统介绍 本项目前后端分离,分为管理员、用户、工作人员、房东四种角色 1、用户/房东: …

正态性检验

t检验、方差分析(ANOVA)等参数检验都有一个共同的前提条件:样本数据必须服从正态分布,即样本数据必须来源于一个正态分布的总体,若样本数据不服从正态分布,就不能用以上参数检验对数据进行分析,…

OpenCV鼠标绘制线段

鼠标绘制线段 // 鼠标回调函数 void draw_circle(int event, int x, int y, int flags, void* param) {cv::Mat* img (cv::Mat*)param;if (event cv::EVENT_LBUTTONDBLCLK){cv::circle(*img, cv::Point(x, y), 100, cv::Scalar(0, 0, 255), -1);} }// 鼠标回调函数 void dra…

.NET 个人博客-添加RSS订阅功能

个人博客-添加RSS订阅功能 前言 个人博客系列已经完成了 留言板文章归档推荐文章优化推荐文章排序 博客地址 然后博客开源的原作者也是百忙之中添加了一个名为RSS订阅的功能,那么我就来简述一下这个功能是干嘛的,然后照葫芦画瓢实现一下。 RSS简述…

SpringBoot+RabbitMQ实现MQTT协议通讯

一、简介 MQTT(消息队列遥测传输)是ISO 标准(ISO/IEC PRF 20922)下基于发布/订阅范式的消息协议。它工作在 TCP/IP协议族上,是为硬件性能低下的远程设备以及网络状况糟糕的情况下而设计的发布/订阅型消息协议,为此,它需要一个消息中间件 。此…

阿斯达年代记游戏下载教程 阿斯达年代记下载教程

《阿斯达年代记:三强争霸》作为一款气势恢宏的MMORPG大作,是Netmarble与STUDIO DRAGON强强联合的巅峰创作,定于4月24日迎来全球玩家热切期待的公测。游戏剧情围绕阿斯达大陆的王权争夺战展开,三大派系——阿斯达联邦、亚高联盟及边…

浅谈菊风实时音视频 (RTC)与实时操作系统 (RTOS) 在智能硬件领域应用

近年来,菊风通过实时音视频赋能智能手表、智能门禁、智能门锁/门铃、智能眼镜等数十种智能硬件,与一众合作伙伴共同探索在IoT智能硬件领域的不同场景应用,积累了丰富的实践经验。在智能硬件中,RTOS因其轻量化的系统内核&#xff0…

使用Mybatis映射时间 DateTime ==> LocalDateTime

首先查看,数据库字段: 书写映射实体类对象VO: Data public class OrderListVO implements Serializable {private Integer orderId;private String memberName;private String orderNumber;private BigDecimal orderPrice;private String l…

element-ui upload 组件 手动多次出发 submit

element 上传组件 upload 上传成功以后,想重新 调用 submit()函数,发现是不可以进行多次触发的,。 直接上解决方法,在上传成功后的钩子函数里添加:fileList[0l.status ready fileList是文件列表,status是单文件的状态改成ready就…

全栈从0到1 3D旅游地图标记和轨迹生成

功能演示 演示视频 体验地址 Vercel App 开发技术栈: NextJs(前端框架)React(前端框架)TailwindCSS (CSS样式)echart echart gl (地图生成)shadui(UI组件…

机器视觉系统-工业光源什么是无影光

光路描述:通过结构或漫射板改变光路,最终发光角度包含了高角度 和低角度。 效果分析:兼具了高角度光和低角度光的效果,使被测物得到了多角度的照射,表面纹理、皱褶被弱化, 图像上整体均匀。 主要应用&#…

linux 上 jps 列出一堆 jar,如何快速定位 jar 文件启动位置?

例如,在 /data下有一个 xxx.jar ,如果是通过 "java -jar /data/xxx.jar" 方式启动,则 jps会列出的名字中带 xxx.jar,这时再 "ps -ef | grep xxx.jar" 就会列出 更详细的信息,例如 "java -ja…