Citus源码(1)分布式表行为测试

news2025/4/2 23:44:57
  1. 最近对citus的实现非常好奇,本篇对citus的行为做一些测试。
  2. 本篇只测行为,不分析源码。后面会继续写一系列文章分析citus源码。

环境:3节点 PG17 with citus。

SELECT citus_set_coordinator_host('127.0.0.1', 3001);
SELECT citus_add_node('127.0.0.1', 3002);
SELECT citus_add_node('127.0.0.1', 3003);
SELECT rebalance_table_shards();
SELECT * from pg_dist_node;

postgres=# SELECT * from pg_dist_node;
+--------+---------+-----------+----------+----------+-------------+----------+----------+-------------+----------------+------------------+
| nodeid | groupid | nodename  | nodeport | noderack | hasmetadata | isactive | noderole | nodecluster | metadatasynced | shouldhaveshards |
+--------+---------+-----------+----------+----------+-------------+----------+----------+-------------+----------------+------------------+
|      1 |       0 | 10.0.0.1  |     3002 | default  | t           | t        | primary  | default     | t              | f                |
|      7 |       6 | 127.0.0.1 |     3002 | default  | t           | t        | primary  | default     | t              | t                |
|      8 |       7 | 127.0.0.1 |     3003 | default  | t           | t        | primary  | default     | t              | t                |
+--------+---------+-----------+----------+----------+-------------+----------+----------+-------------+----------------+------------------+
(3 rows)

case1:分布式表

  • 注意表只能在cn建,dn建不能做create_distributed_table操作。
  • dn上表能建,但cn上create_distributed_table时会报错,dn上已经有同名包。

结论:shard在dn上是暴漏在外面的,可以直接查询,和cn上查询效果一样。

CREATE TABLE events (
  device_id bigint,
  event_id bigserial,
  event_time timestamptz default now(),
  data jsonb not null,
  PRIMARY KEY (device_id, event_id)
);

SELECT create_distributed_table('events', 'device_id');
INSERT INTO events (device_id, data) SELECT s % 100, ('{"measurement":'||random()||'}')::jsonb FROM generate_series(1,1000000) s;
  • device_id = 1在3003上,device_id = 2在3002上,说明数据还是分片存储的,分片策略现在还不清楚,后面看源码。
  • 无论在哪个节点上,只要查询分布式表都能生成分布式计划。应该是会统一路由到cn上。具体怎么路由的需要看源码。
postgres=# EXPLAIN ANALYZE SELECT * FROM events WHERE device_id = 1 ORDER BY event_time DESC, event_id DESC LIMIT 3;
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
|                                                                         QUERY PLAN                                                                         |
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Custom Scan (Citus Adaptive)  (cost=0.00..0.00 rows=0 width=0) (actual time=13.756..13.758 rows=3 loops=1)                                                 |
|   Task Count: 1                                                                                                                                            |
|   Tuple data received from nodes: 181 bytes                                                                                                                |
|   Tasks Shown: All                                                                                                                                         |
|   ->  Task                                                                                                                                                 |
|         Tuple data received from node: 181 bytes                                                                                                           |
|         Node: host=127.0.0.1 port=3003 dbname=postgres                                                                                                     |
|         ->  Limit  (cost=1071.55..1071.56 rows=3 width=63) (actual time=11.164..11.166 rows=3 loops=1)                                                     |
|               ->  Sort  (cost=1071.55..1096.36 rows=9925 width=63) (actual time=11.162..11.163 rows=3 loops=1)                                             |
|                     Sort Key: event_time DESC, event_id DESC                                                                                               |
|                     Sort Method: top-N heapsort  Memory: 26kB                                                                                              |
|                     ->  Bitmap Heap Scan on events_102204 events  (cost=237.21..943.27 rows=9925 width=63) (actual time=1.900..6.872 rows=10000 loops=1)   |
|                           Recheck Cond: (device_id = 1)                                                                                                    |
|                           Heap Blocks: exact=582                                                                                                           |
|                           ->  Bitmap Index Scan on events_pkey_102204  (cost=0.00..234.73 rows=9925 width=0) (actual time=1.729..1.730 rows=10000 loops=1) |
|                                 Index Cond: (device_id = 1)                                                                                                |
|             Planning Time: 0.121 ms                                                                                                                        |
|             Execution Time: 11.236 ms                                                                                                                      |
| Planning Time: 0.129 ms                                                                                                                                    |
| Execution Time: 13.973 ms                                                                                                                                  |
+------------------------------------------------------------------------------------------------------------------------------------------------------------+
(20 rows)


postgres=# EXPLAIN ANALYZE SELECT * FROM events WHERE device_id = 2 ORDER BY event_time DESC, event_id DESC LIMIT 3;
+-------------------------------------------------------------------------------------------------------------------------------------------------------------+
|                                                                         QUERY PLAN                                                                          |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Custom Scan (Citus Adaptive)  (cost=0.00..0.00 rows=0 width=0) (actual time=12.512..12.514 rows=3 loops=1)                                                  |
|   Task Count: 1                                                                                                                                             |
|   Tuple data received from nodes: 181 bytes                                                                                                                 |
|   Tasks Shown: All                                                                                                                                          |
|   ->  Task                                                                                                                                                  |
|         Tuple data received from node: 181 bytes                                                                                                            |
|         Node: host=127.0.0.1 port=3002 dbname=postgres                                                                                                      |
|         ->  Limit  (cost=842.04..842.04 rows=3 width=63) (actual time=9.843..9.846 rows=3 loops=1)                                                          |
|               ->  Sort  (cost=842.04..867.04 rows=10000 width=63) (actual time=9.841..9.843 rows=3 loops=1)                                                 |
|                     Sort Key: event_time DESC, event_id DESC                                                                                                |
|                     Sort Method: top-N heapsort  Memory: 26kB                                                                                               |
|                     ->  Bitmap Heap Scan on events_102227 events  (cost=237.79..712.79 rows=10000 width=63) (actual time=1.744..5.660 rows=10000 loops=1)   |
|                           Recheck Cond: (device_id = 2)                                                                                                     |
|                           Heap Blocks: exact=350                                                                                                            |
|                           ->  Bitmap Index Scan on events_pkey_102227  (cost=0.00..235.29 rows=10000 width=0) (actual time=1.647..1.647 rows=10000 loops=1) |
|                                 Index Cond: (device_id = 2)                                                                                                 |
|             Planning Time: 0.113 ms                                                                                                                         |
|             Execution Time: 9.908 ms                                                                                                                        |
| Planning Time: 0.126 ms                                                                                                                                     |
| Execution Time: 12.710 ms                                                                                                                                   |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------+
(20 rows)

case2:分布式表 join 分布式表

CREATE TABLE events1 (
  device_id bigint,
  event_id bigserial,
  event_time timestamptz default now(),
  data jsonb not null,
  PRIMARY KEY (device_id, event_id)
);

SELECT create_distributed_table('events1', 'device_id');
INSERT INTO events1 (device_id, data) SELECT s % 100, ('{"measurement":'||random()||'}')::jsonb FROM generate_series(1,1000000) s;

分布键join

  • 并发开的很积极,in的条件全部被并行了,in几个值就能并行几个。
  • 从计划上看是OK的,但执行时间上看明显不是每个worker在本地join的,要大量shuffle。说明citus默认分布式表的分布策略,对于不同表是不一样的。例如两个表的shard key都是device_id,但同一个device_id不一定在容一个节点上。
  • Co-location表负责解决这类问题。Co-location表 = PGXC的distribute by hash表
postgres=# explain select * from events e,events1 e1 where e.device_id=e1.device_id and e.device_id in (1,2,3,4,5);
+-------------------------------------------------------------------------------------------------------------------+
|                                                    QUERY PLAN                                                     |
+-------------------------------------------------------------------------------------------------------------------+
| Custom Scan (Citus Adaptive)  (cost=0.00..0.00 rows=100000 width=112)                                             |
|   Task Count: 5                                                                                                   |
|   Tasks Shown: One of 5                                                                                           |
|   ->  Task                                                                                                        |
|         Node: host=127.0.0.1 port=3003 dbname=postgres                                                            |
|         ->  Hash Join  (cost=1707.29..1126727.63 rows=99645950 width=126)                                         |
|               Hash Cond: (e.device_id = e1.device_id)                                                             |
|               ->  Index Scan using events_pkey_102332 on events_102332 e  (cost=0.29..2603.53 rows=9965 width=63) |
|                     Index Cond: (device_id = ANY ('{1,2,3,4,5}'::bigint[]))                                       |
|               ->  Hash  (cost=1082.00..1082.00 rows=50000 width=63)                                               |
|                     ->  Seq Scan on events1_102364 e1  (cost=0.00..1082.00 rows=50000 width=63)                   |
+-------------------------------------------------------------------------------------------------------------------+

非分布键join,默认不支持shuffle

postgres=# select * from events e,events1 e1 where e.device_id=e1.event_id and e.device_id in (1,2,3,4,5);
ERROR:  the query contains a join that requires repartitioning
HINT:  Set citus.enable_repartition_joins to on to enable repartitioning

case3:分布式表 join 本地表

  • 计划能做出来一半本地计划,一半分布式计划。
  • 分布式结果会做成Function Scan on read_intermediate_result intermediate_result节点,提供给本地计划。
  • CN上或DN上都能执行,分布式计划的部分都会路由到CN上执行,无论在哪个节点上,分布式计划结果都会做成Function Scan在进行下一步处理。

在CN上执行:

postgres=# drop table localtbl;
DROP TABLE
postgres=# create table localtbl(a int, b int);
CREATE TABLE
postgres=# insert into localtbl select t.i,t.i%10 from generate_series(0, 99) t(i);
INSERT 0 100
postgres=# select * from events e, localtbl l where e.device_id = l.a and e.device_id = 1 and e.event_id=86201;
+-----------+----------+-------------------------------+--------------------------------------+---+---+
| device_id | event_id |          event_time           |                 data                 | a | b |
+-----------+----------+-------------------------------+--------------------------------------+---+---+
|         1 |    86201 | 2025-03-25 17:02:52.776599+08 | {"measurement": 0.39845320795492434} | 1 | 1 |
+-----------+----------+-------------------------------+--------------------------------------+---+---+
(1 row)

postgres=# explain select * from events e, localtbl l where e.device_id = l.a and e.device_id = 1 and e.event_id=86201;
+---------------------------------------------------------------------------------------------------------------------+
|                                                     QUERY PLAN                                                      |
+---------------------------------------------------------------------------------------------------------------------+
| Custom Scan (Citus Adaptive)  (cost=0.00..0.00 rows=0 width=0)                                                      |
|   ->  Distributed Subplan 89_1                                                                                      |
|         ->  Custom Scan (Citus Adaptive)  (cost=0.00..0.00 rows=0 width=0)                                          |
|               Task Count: 1                                                                                         |
|               Tasks Shown: All                                                                                      |
|               ->  Task                                                                                              |
|                     Node: host=127.0.0.1 port=3003 dbname=postgres                                                  |
|                     ->  Index Scan using events_pkey_102332 on events_102332 e  (cost=0.29..8.31 rows=1 width=63)   |
|                           Index Cond: ((device_id = 1) AND (event_id = 86201))                                      |
|   Task Count: 1                                                                                                     |
|   Tasks Shown: All                                                                                                  |
|   ->  Task                                                                                                          |
|         Node: host=localhost port=3001 dbname=postgres                                                              |
|         ->  Nested Loop  (cost=0.00..53.36 rows=11 width=64)                                                        |
|               ->  Function Scan on read_intermediate_result intermediate_result  (cost=0.00..15.00 rows=1 width=56) |
|                     Filter: ((device_id = 1) AND (event_id = 86201))                                                |
|               ->  Seq Scan on localtbl l  (cost=0.00..38.25 rows=11 width=8)                                        |
|                     Filter: (a = 1)                                                                                 |
+---------------------------------------------------------------------------------------------------------------------+
(18 rows)

在DN上执行

postgres=# create table localtbl123(a int, b int);
CREATE TABLE
postgres=# insert into localtbl123 select t.i,t.i%10 from generate_series(0, 99) t(i);
INSERT 0 100
postgres=# select * from events e, localtbl123 l where e.device_id = l.a and e.device_id = 1 and e.event_id=86201;
+-----------+----------+-------------------------------+--------------------------------------+---+---+
| device_id | event_id |          event_time           |                 data                 | a | b |
+-----------+----------+-------------------------------+--------------------------------------+---+---+
|         1 |    86201 | 2025-03-25 17:02:52.776599+08 | {"measurement": 0.39845320795492434} | 1 | 1 |
+-----------+----------+-------------------------------+--------------------------------------+---+---+
(1 row)

postgres=# explain select * from events e, localtbl123 l where e.device_id = l.a and e.device_id = 1 and e.event_id=86201;
+---------------------------------------------------------------------------------------------------------------------+
|                                                     QUERY PLAN                                                      |
+---------------------------------------------------------------------------------------------------------------------+
| Custom Scan (Citus Adaptive)  (cost=0.00..0.00 rows=0 width=0)                                                      |
|   ->  Distributed Subplan 45_1                                                                                      |
|         ->  Custom Scan (Citus Adaptive)  (cost=0.00..0.00 rows=0 width=0)                                          |
|               Task Count: 1                                                                                         |
|               Tasks Shown: All                                                                                      |
|               ->  Task                                                                                              |
|                     Node: host=127.0.0.1 port=3003 dbname=postgres                                                  |
|                     ->  Index Scan using events_pkey_102332 on events_102332 e  (cost=0.29..8.31 rows=1 width=63)   |
|                           Index Cond: ((device_id = 1) AND (event_id = 86201))                                      |
|   Task Count: 1                                                                                                     |
|   Tasks Shown: All                                                                                                  |
|   ->  Task                                                                                                          |
|         Node: host=localhost port=3002 dbname=postgres                                                              |
|         ->  Nested Loop  (cost=0.00..53.36 rows=11 width=64)                                                        |
|               ->  Function Scan on read_intermediate_result intermediate_result  (cost=0.00..15.00 rows=1 width=56) |
|                     Filter: ((device_id = 1) AND (event_id = 86201))                                                |
|               ->  Seq Scan on localtbl123 l  (cost=0.00..38.25 rows=11 width=8)                                     |
|                     Filter: (a = 1)                                                                                 |
+---------------------------------------------------------------------------------------------------------------------+
(18 rows)

case4:分布式colocate_with表

CREATE TABLE devices (
  device_id bigint primary key,
  device_name text,
  device_type_id int
);

SELECT create_distributed_table('devices', 'device_id', colocate_with := 'events');

INSERT INTO devices (device_id, device_name, device_type_id)
SELECT s, 'device-'||s, 55 FROM generate_series(0, 99) s;
CREATE INDEX ON devices (device_type_id);

ALTER TABLE events ADD CONSTRAINT device_id_fk
FOREIGN KEY (device_id) REFERENCES devices (device_id);

SELECT avg((data->>'measurement')::double precision)
FROM events JOIN devices USING (device_id)
WHERE device_type_id = 55;

结果:分布键join,执行飞快,本地join,无shuffle。

postgres=# explain SELECT avg((data->>'measurement')::double precision)
FROM events e, devices d
WHERE e.device_id=d.device_id and d.device_type_id = 55;
+---------------------------------------------------------------------------------------------------------------------+
|                                                     QUERY PLAN                                                      |
+---------------------------------------------------------------------------------------------------------------------+
| Aggregate  (cost=500.00..500.02 rows=1 width=8)                                                                     |
|   ->  Custom Scan (Citus Adaptive)  (cost=0.00..0.00 rows=100000 width=16)                                          |
|         Task Count: 32                                                                                              |
|         Tasks Shown: One of 32                                                                                      |
|         ->  Task                                                                                                    |
|               Node: host=127.0.0.1 port=3002 dbname=postgres                                                        |
|               ->  Aggregate  (cost=1013.83..1013.84 rows=1 width=16)                                                |
|                     ->  Nested Loop  (cost=237.79..813.83 rows=10000 width=39)                                      |
|                           ->  Seq Scan on devices_102395 d  (cost=0.00..1.04 rows=1 width=8)                        |
|                                 Filter: (device_type_id = 55)                                                       |
|                           ->  Bitmap Heap Scan on events_102331 e  (cost=237.79..712.79 rows=10000 width=47)        |
|                                 Recheck Cond: (device_id = d.device_id)                                             |
|                                 ->  Bitmap Index Scan on events_pkey_102331  (cost=0.00..235.29 rows=10000 width=0) |
|                                       Index Cond: (device_id = d.device_id)                                         |
+---------------------------------------------------------------------------------------------------------------------+
(14 rows)

Time: 8.212 ms
postgres=# SELECT avg((data->>'measurement')::double precision)
FROM events e, devices d
WHERE e.device_id=d.device_id and d.device_type_id = 55;
+-------------------+
|        avg        |
+-------------------+
| 0.500442721594347 |
+-------------------+
(1 row)

Time: 354.369 ms

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

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

相关文章

【PCIE711-214】基于PCIe总线架构的4路HD-SDI/3G-SDI视频图像模拟源

产品概述 PCIE711-214是一款基于PCIE总线架构的4路SDI视频模拟源。该板卡为标准的PCIE插卡,全高尺寸,适合与PCIE总线的工控机或者服务器,板载协议处理器,可以通过PCIE总线将上位机的YUV 422格式视频数据下发通过SDI接口播放出去&…

突破反爬困境:SDK开发,浏览器模块(七)

声明 本文所讨论的内容及技术均纯属学术交流与技术研究目的,旨在探讨和总结互联网数据流动、前后端技术架构及安全防御中的技术演进。文中提及的各类技术手段和策略均仅供技术人员在合法与合规的前提下进行研究、学习与防御测试之用。 作者不支持亦不鼓励任何未经授…

rce操作

Linux命令长度突破限制 源码 <?php $param $_REQUEST[param];if ( strlen($param) < 8 ) {echo shell_exec($param); } echo执行函数&#xff0c;$_REQUEST可以接post、get、cookie传参 源码中对参数长度做了限制&#xff0c;小于8位&#xff0c;可以利用临时函数&…

LabVIEW高效溢流阀测试系统

开发了一种基于LabVIEW软件和PLC硬件的溢流阀测试系统。通过集成神经网络优化的自适应PID控制器&#xff0c;该系统能自动进行压力稳定性、寿命以及动静态性能测试。该设计不仅提升了测试效率&#xff0c;还通过智能化控制提高了数据的精确性和操作的便捷性。 ​ 项目背景&…

DataGear 5.3.0 制作支持导出表格数据的数据可视化看板

DataGear 内置表格图表底层采用的是DataTable表格组件&#xff0c;默认并未引入导出数据的JS支持库&#xff0c;如果有导出表格数据需求&#xff0c;则可以在看板中引入导出相关JS支持库&#xff0c;制作具有导出CSV、Excel、PDF功能的表格数据看板。 在新发布的5.3.0版本中&a…

Web网页内嵌 Adobe Pdf Reader 谷歌Chrome在线预览编辑PDF文档

随着数字化办公的普及&#xff0c;PDF文档已成为信息处理的核心载体&#xff0c;虽然桌面端有很多软件可以实现预览编辑PDF文档&#xff0c;而在线在线预览编辑PDF也日益成为一个难题。 作为网页内嵌本地程序的佼佼者——猿大师中间件&#xff0c;之前发布的猿大师办公助手&am…

Sentinel[超详细讲解]-1

定义一系列 规则 &#x1f47a;&#xff0c;对资源进行 保护 &#x1f47a;&#xff0c; 如果违反的了规则&#xff0c;则抛出异常&#xff0c;看是否有fallback兜底处理&#xff0c;如果没有则直接返回异常信息&#x1f60e; 1. 快速入门 1.1 引入 Sentinel 依赖 <depend…

如何让 SQL2API 进化为 Text2API:自然语言生成 API 的深度解析?

在过去的十年里&#xff0c;技术的进步日新月异&#xff0c;尤其是在自动化、人工智能与自然语言处理&#xff08;NLP&#xff09;方面。 随着“低代码”平台的崛起&#xff0c;开发者和非技术人员能够更轻松地构建强大而复杂的应用程序。然而&#xff0c;尽管技术门槛降低了&…

OCCT(2)Windows平台编译OCCT

文章目录 一、Windows平台编译OCCT1、准备环境2、下载源码3、下载第三方库4、使用 CMake 配置5、编译OCCT源码6、运行示例 一、Windows平台编译OCCT 1、准备环境 安装工具&#xff1a; Visual Studio&#xff08;推荐 VS2019/2022&#xff0c;选择 C 桌面开发 组件&#xff0…

【蓝桥杯—单片机】通信总线专项 | 真题整理、解析与拓展 (更新ing...)

通信总线专项 前言SPI第十五届省赛题 UART/RS485/RS232UARTRS485RS232第十三届省赛题小结和拓展&#xff1a;传输方式的分类第十三届省赛 其他相关考点网络传输速率第十五届省赛题第十二届省赛题 前言 在本文中我会把 蓝桥杯单片机赛道 历年真题 中涉及到通信总线的题目整理出…

Uni-app页面信息与元素影响解析

获取窗口信息uni.getWindowInfo {pixelRatio: 3safeArea:{bottom: 778height: 731left: 0right: 375top: 47width: 375}safeAreaInsets: {top: 47, left: 0, right: 0, bottom: 34},screenHeight: 812,screenTop: 0,screenWidth: 375,statusBarHeight: 47,windowBottom: 0,win…

CentOS(最小化)安装之后,快速搭建Docker环境

本文以VMware虚拟机中安装最小化centos完成后开始。 1. 检查网络 打开网卡/启用网卡 执行命令ip a查看当前的网络连接是否正常&#xff1a; 如果得到的结果和我一样&#xff0c;有ens网卡但是没有ip地址&#xff0c;说明网卡未打开 手动启用&#xff1a; nmcli device sta…

【身份证证件OCR识别】批量OCR识别身份证照片复印件图片里的文字信息保存表格或改名字,基于QT和腾讯云api_ocr的实现方式

项目背景 在许多业务场景中,需要处理大量身份证照片复印件,手动输入其中的文字信息效率低下且容易出错。利用 OCR(光学字符识别)技术可以自动识别身份证图片中的文字信息,结合 QT 构建图形用户界面,方便用户操作,同时使用腾讯 OCR API 能够保证较高的识别准确率。 界面…

IP属地和发作品的地址不一样吗

在当今这个数字化时代&#xff0c;互联网已经成为人们日常生活不可或缺的一部分。随着各大社交平台功能的不断完善&#xff0c;一个新功能——IP属地显示&#xff0c;逐渐走进大众视野。这一功能在微博、抖音、快手等各大平台上得到广泛应用&#xff0c;旨在帮助公众识别虚假信…

Redis - 概述

目录 ​编辑 一、什么是redis 二、redis能做什么&#xff08;有什么特点&#xff09;&#xff1f; 三、redis有什么优势 四、Redis与其他key-value存储有什么不同 五、Redis命令 六、Redis数据结构 1、基础数据结构 2、高级数据结构 一、什么是redis 1、redis&#x…

vue3 根据城市名称计算城市之间的距离

<template><div class"distance-calculator"><h1>城市距离计算器</h1><!-- 城市输入框 --><div class"input-group"><inputv-model"city1"placeholder"请输入第一个城市"keyup.enter"cal…

html 列表循环滚动,动态初始化字段数据

html <div class"layui-row"><div class"layui-col-md4"><div class"boxall"><div class"alltitle">超时菜品排行</div><div class"marquee-container"><div class"scroll-…

QT基础:安装与简介

QT初级 1、简介1.1 安装1.2 设置1.3 在VS中配置Qt1.3 帮助文档 2、Qt项目2.1 创建项目2.1 项目文件2.2 Qt中的窗口类窗口显示 2.3 坐标体系2.4 内存回收 1、简介 QT是一个跨平台的C应用程序开发框架。几乎支持所有的平台, 可用于桌面程序开发以及嵌入式开发。 Qt是标准 C 的扩…

WEB安全-HTTPS

1 需求 结合Wireshark抓包实战&#xff0c;图文详解TCP三次握手及四次挥手原理&#xff08;附下载&#xff09; 结合Wireshark抓包分析&#xff0c;沉浸式体验HTTP请求的一次完整交互过程 https://mp.weixin.qq.com/s/f3LmUEtjIuLjkyjxJj7ebA 一文彻底了解DNS协议工作原理&…

【宇宙回响】从Canvas到MySQL:飞机大战的全栈交响曲【附演示视频与源码】

🌟 这是星际大战系列的第三篇,感谢一路以来支持和关注这个项目的每一位朋友! 💡 文章力求严谨,但难免有疏漏之处,欢迎各位朋友指出,让我们一起在交流中进步。 🎁 项目代码、文档和相关资源都可以免费获取,希望能帮助到更多对游戏开发感兴趣的朋友。 💌 如果您有任…