OceanBase技术解析:自适应分布式下压技术

news2024/9/27 6:34:28
在《OceanBase 数据库源码解析》这本书中,关于SQL执行器的深入剖析相对较少,因此,希望增添一些实用且详尽的补充内容。
上一篇博客《 OceanBase技术解析: 执行器中的自适应技术》中,已初步介绍了执行器中几项典型的自适应技术,但其中对于hash group by 中的两阶段下压技术,我是基于大家已有一定了解的前提展开了阐述。若你在执行器的多阶段下压技术方面尚存疑惑,欢迎阅读这篇博客,来一起学习一下 OceanBase 中比较常见的几种自适应分布式下压技术。

什么是分布式下压

在分布式执行的过程中,为了更好地利用并行的能力,降低 CPU 和网络的开销,优化器生成计划的过程中,往往会将部分算子下压到更下层的各个计算节点上。目的是为了充分利用集群的计算资源,提升执行效率。这次就来介绍下 OceanBase 里面最常见的几种分布式下压技术。

LIMIT 下压

我们先介绍一下 limit 的下压。举一个简单的例子,这两条 SQL 是创建一个 orders 表,并从 orders 表中读 100 行数据。

CREATE TABLE `orders` (
  `o_orderkey` bigint(20) NOT NULL,
  `o_custkey` bigint(20) NOT NULL,
  `o_orderdate` date NOT NULL,
  PRIMARY KEY (`o_orderkey`, `o_orderdate`, `o_custkey`),
  KEY `o_orderkey` (`o_orderkey`) LOCAL  BLOCK_SIZE 16384
)  partition by range columns(o_orderdate)
   subpartition by hash(o_custkey) subpartitions 64
(partition ord1 values less than ('1992-01-01'),
partition ord2 values less than ('1992-02-01'),
partition ord3 values less than ('1992-03-01'),
partition ord77 values less than ('1998-05-01'),
partition ord78 values less than ('1998-06-01'),
partition ord79 values less than ('1998-07-01'),
partition ord80 values less than ('1998-08-01'),
partition ord81 values less than (MAXVALUE));

select * from orders limit 100;

图中的计划是分布式下压的一个很常见的场景:

explain select * from orders limit 100;
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Query Plan                                                                                                                                                      |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| =================================================================                                                                                               |
| |ID|OPERATOR                     |NAME    |EST.ROWS|EST.TIME(us)|                                                                                               |
| -----------------------------------------------------------------                                                                                               |
| |0 |LIMIT                        |        |1       |2794        |                                                                                               |
| |1 |└─PX COORDINATOR             |        |1       |2794        |                                                                                               |
| |2 |  └─EXCHANGE OUT DISTR       |:EX10000|1       |2793        |                                                                                               |
| |3 |    └─LIMIT                  |        |1       |2792        |                                                                                               |
| |4 |      └─PX PARTITION ITERATOR|        |1       |2792        |                                                                                               |
| |5 |        └─TABLE FULL SCAN    |orders  |1       |2792        |                                                                                               |
| =================================================================                                                                                               |
| Outputs & filters:                                                                                                                                              |
| -------------------------------------                                                                                                                           |
|   0 - output([orders.o_orderkey], [orders.o_custkey], [orders.o_orderdate]), filter(nil)                                                                        |
|       limit(100), offset(nil)                                                                                                                                   |
|   1 - output([orders.o_orderkey], [orders.o_custkey], [orders.o_orderdate]), filter(nil)                                                                        |
|   2 - output([orders.o_orderkey], [orders.o_custkey], [orders.o_orderdate]), filter(nil)                                                                        |
|       dop=1                                                                                                                                                     |
|   3 - output([orders.o_orderkey], [orders.o_custkey], [orders.o_orderdate]), filter(nil)                                                                        |
|       limit(100), offset(nil)                                                                                                                                   |
|   4 - output([orders.o_orderkey], [orders.o_orderdate], [orders.o_custkey]), filter(nil)                                                                        |
|       force partition granule                                                                                                                                   |
|   5 - output([orders.o_orderkey], [orders.o_orderdate], [orders.o_custkey]), filter(nil)                                                                        |
|       access([orders.o_orderkey], [orders.o_orderdate], [orders.o_custkey]), partitions(p0sp[0-63], p1sp[0-63], p2sp[0-63], p3sp[0-63], p4sp[0-63], p5sp[0-63], |
|        p6sp[0-63], p7sp[0-63])                                                                                                                                  |
|       limit(100), offset(nil), is_index_back=false, is_global_index=false,                                                                                      |
|       range_key([orders.o_orderkey], [orders.o_orderdate], [orders.o_custkey]), range(MIN,MIN,MIN ; MAX,MAX,MAX)always true                                     |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+

可以看到计划中有两个 limit 算子(1 号和 3 号)。通过下压生成 3 号 limit 算子,可以降低对 5 号 table scan 对 orders 每个分区的扫描行数,让每个 table scan 的线程最多只扫描 100 行数据,这样可以降低 table scan 扫描数据的开销以及发送数据到 1 号算子进行汇总的网络开销。目前 OB 的一个 exchange 算子是从下层收到 64K 数据以后发一个包,limit 如果不下压的话可能会多扫描很多的数据,并且带来很大的网络开销。

真实的场景中,limit 往往伴随着 order by。如果在前面的例子中加上 order by 关键字,order by 加 limit 会在计划中生成一个 top-n sort 算子,它的性能是比 sort 要好很多的。

explain select * from orders order by o_orderdate limit 100;
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Query Plan                                                                                                                                                      |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+
| =================================================================                                                                                               |
| |ID|OPERATOR                     |NAME    |EST.ROWS|EST.TIME(us)|                                                                                               |
| -----------------------------------------------------------------                                                                                               |
| |0 |LIMIT                        |        |1       |2794        |                                                                                               |
| |1 |└─PX COORDINATOR MERGE SORT  |        |1       |2794        |                                                                                               |
| |2 |  └─EXCHANGE OUT DISTR       |:EX10000|1       |2793        |                                                                                               |
| |3 |    └─TOP-N SORT             |        |1       |2792        |                                                                                               |
| |4 |      └─PX PARTITION ITERATOR|        |1       |2792        |                                                                                               |
| |5 |        └─TABLE FULL SCAN    |orders  |1       |2792        |                                                                                               |
| =================================================================                                                                                               |
| Outputs & filters:                                                                                                                                              |
| -------------------------------------                                                                                                                           |
|   0 - output([orders.o_orderkey], [orders.o_custkey], [orders.o_orderdate]), filter(nil)                                                                        |
|       limit(100), offset(nil)                                                                                                                                   |
|   1 - output([orders.o_orderkey], [orders.o_custkey], [orders.o_orderdate]), filter(nil)                                                                        |
|       sort_keys([orders.o_orderdate, ASC])                                                                                                                      |
|   2 - output([orders.o_orderkey], [orders.o_custkey], [orders.o_orderdate]), filter(nil)                                                                        |
|       dop=1                                                                                                                                                     |
|   3 - output([orders.o_orderkey], [orders.o_custkey], [orders.o_orderdate]), filter(nil)                                                                        |
|       sort_keys([orders.o_orderdate, ASC]), topn(100)                                                                                                           |
|   4 - output([orders.o_orderkey], [orders.o_orderdate], [orders.o_custkey]), filter(nil)                                                                        |
|       force partition granule                                                                                                                                   |
|   5 - output([orders.o_orderkey], [orders.o_orderdate], [orders.o_custkey]), filter(nil)                                                                        |
|       access([orders.o_orderkey], [orders.o_orderdate], [orders.o_custkey]), partitions(p0sp[0-63], p1sp[0-63], p2sp[0-63], p3sp[0-63], p4sp[0-63], p5sp[0-63], |
|        p6sp[0-63], p7sp[0-63])                                                                                                                                  |
|       is_index_back=false, is_global_index=false,                                                                                                               |
|       range_key([orders.o_orderkey], [orders.o_orderdate], [orders.o_custkey]), range(MIN,MIN,MIN ; MAX,MAX,MAX)always true                                     |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------+

如果上面的 limit 不下压的话,3 号算子就会变成 sort 算子,每个线程需要将自己扫描的所有数据排序以后发送给上层的 DFO(DFO 就是一个子计划,相邻的 DFO 之间以 exchange 算子作为分割,详见:OceanBase分布式数据库-海量数据 笔笔算数)。

limit 下压的作用,就是能够提前结束执行,减少计算和网络的开销。

AGGREGATION 下压

下面介绍一下聚合中的分布式下压,以这条 group by 语句为例:

select count(o_totalprice), sum(o_totalprice) from orders group by o_orderdate;

这条 SQL 查询了每一天的订单数和销售额,如果希望并行地执行这条 SQL 的话,最直接的想法肯定是让表中数据根据 group by 列(o_orderdate)的 hash 值进行数据的分发,因为这样可以确保 o_orderdate 值相同的行都被发送到了同一个线程,各个线程可以并行地对收到的数据去进行聚合。

但是这个计划的一个弊端是要对表中所有的数据都要做一次 shuffle 网络的开销可能很大;还有一个问题是如果表中存在数据倾斜比如某一天的订单特别多,那么处理负责处理这一天订单的线程的工作量就会比其他线程多很多,这个长尾的任务可能直接导致这个查询的执行时间特别长。

为了解决上述这些问题,我们会对 group by 算子进行下压,生成这样一个计划:

explain select count(o_totalprice), sum(o_totalprice) from orders group by o_orderdate;
+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Query Plan                                                                                                                                                |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| =====================================================================                                                                                     |
| |ID|OPERATOR                         |NAME    |EST.ROWS|EST.TIME(us)|                                                                                     |
| ---------------------------------------------------------------------                                                                                     |
| |0 |PX COORDINATOR                   |        |1       |2796        |                                                                                     |
| |1 |└─EXCHANGE OUT DISTR             |:EX10001|1       |2795        |                                                                                     |
| |2 |  └─HASH GROUP BY                |        |1       |2795        |                                                                                     |
| |3 |    └─EXCHANGE IN DISTR          |        |1       |2794        |                                                                                     |
| |4 |      └─EXCHANGE OUT DISTR (HASH)|:EX10000|1       |2794        |                                                                                     |
| |5 |        └─HASH GROUP BY          |        |1       |2793        |                                                                                     |
| |6 |          └─PX PARTITION ITERATOR|        |1       |2792        |                                                                                     |
| |7 |            └─TABLE FULL SCAN    |orders  |1       |2792        |                                                                                     |
| =====================================================================                                                                                     |
| Outputs & filters:                                                                                                                                        |
| -------------------------------------                                                                                                                     |
|   0 - output([INTERNAL_FUNCTION(T_FUN_COUNT_SUM(T_FUN_COUNT(orders.o_totalprice)), T_FUN_SUM(T_FUN_SUM(orders.o_totalprice)))]), filter(nil)              |
|   1 - output([INTERNAL_FUNCTION(T_FUN_COUNT_SUM(T_FUN_COUNT(orders.o_totalprice)), T_FUN_SUM(T_FUN_SUM(orders.o_totalprice)))]), filter(nil)              |
|       dop=1                                                                                                                                               |
|   2 - output([T_FUN_COUNT_SUM(T_FUN_COUNT(orders.o_totalprice))], [T_FUN_SUM(T_FUN_SUM(orders.o_totalprice))]), filter(nil)                               |
|       group([orders.o_orderdate]), agg_func([T_FUN_COUNT_SUM(T_FUN_COUNT(orders.o_totalprice))], [T_FUN_SUM(T_FUN_SUM(orders.o_totalprice))])             |
|   3 - output([orders.o_orderdate], [T_FUN_COUNT(orders.o_totalprice)], [T_FUN_SUM(orders.o_totalprice)]), filter(nil)                                     |
|   4 - output([orders.o_orderdate], [T_FUN_COUNT(orders.o_totalprice)], [T_FUN_SUM(orders.o_totalprice)]), filter(nil)                                     |
|       (#keys=1, [orders.o_orderdate]), dop=1                                                                                                              |
|   5 - output([orders.o_orderdate], [T_FUN_COUNT(orders.o_totalprice)], [T_FUN_SUM(orders.o_totalprice)]), filter(nil)                                     |
|       group([orders.o_orderdate]), agg_func([T_FUN_COUNT(orders.o_totalprice)], [T_FUN_SUM(orders.o_totalprice)])                                         |
|   6 - output([orders.o_orderdate], [orders.o_totalprice]), filter(nil)                                                                                    |
|       force partition granule                                                                                                                             |
|   7 - output([orders.o_orderdate], [orders.o_totalprice]), filter(nil)                                                                                    |
|       access([orders.o_orderdate], [orders.o_totalprice]), partitions(p0sp[0-63], p1sp[0-63], p2sp[0-63], p3sp[0-63], p4sp[0-63], p5sp[0-63], p6sp[0-63], |
|        p7sp[0-63])                                                                                                                                        |
|       is_index_back=false, is_global_index=false,                                                                                                         |
|       range_key([orders.o_orderkey], [orders.o_orderdate], [orders.o_custkey]), range(MIN,MIN,MIN ; MAX,MAX,MAX)always true                               |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------+

在这个计划里面,每个线程在进行数据的分发之前会先对自己读取的这部分数据进行预聚合,也就是计划里面的 5 号 group by 算子做的工作。然后 5 号算子将聚合的结果发送给上层的算子,之后上层的 2号 group by 算子会对收到的数据再进一次聚合。因为经过这个 5 号 group by 算子的提前聚合之后,数据量一般都会大幅降低,这样即可以降低数据 shuffle 带来的网络开销,也能降低数据倾斜对执行时间的影响。

接下来展示一下具体的执行过程来进行说明,还是刚才那条熟悉的 SQL,求每一天的订单数和销售额。

select count(o_totalprice), sum(o_totalprice) from orders group by o_orderdate;

原始的数据共有 7 行,每笔订单的销售额都是 10 元,它分布在 1、2、3 号这三天里面。

下图中展示了执行的过程,这里我们把并行度设置为 2:

我们从左往右看,可以看到左上方的第一个线程扫描了 3 行的数据,左下方的第二个线程扫描了 4 行数据。日期相同的数据,也就是同一组的数据,都被标成了相同的颜色。

先看左侧,第一个线程对其扫描的 3 行数据去进行聚合,这 3 行数据分布在两个 group 里面,6 月 1 号有 2 行,6 月 3 号有 1 行。因为 6 月 1 号有 2 行,所以它的 count 是 2,销售额是 20。6 月 3 号有 1 行,它的 count 是 1,销售额是10。第二个线程扫描的 4 行数据也分布在两个 group 里面,聚合后也生成了两行数据,这里不再赘述。这部分工作对应计划里的 5 号算子。

然后这两个线程利用 o_orderdate 列的 hash 值进行数据的分发,让同一天的数据都发送到同一个线程。这部分工作对应计划里的 3 号和 4 号算子。

右侧的每个线程对收到的数据会再进行一次聚合。可以看到左边两个线程中 6 月 3 号的数据(红色)都被发送到了右下方这个线程里,这两行从左侧不同线程发过来的 6 月 3 号的数据被右侧的算子再次进行聚合,count 和 sum 都再次被相加,count 变成了2,sum 变成了 20,最终被聚合成了一行。这部分工作对应计划里的 2 号算子。

然后所有的数据都会发送给协调者,协调者对数据进行汇总之后,将最终的计算结果发送给客户端。

JOIN FILTER 下压

join 算子中也会把左表的过滤条件 join filter 下压到右表,对右表的数据进行提前过滤和分区裁剪。

提前过滤

hash join 在执行的时候,总是先读左表的数据,建立一个哈希表。然后用右侧的数据去探测这个哈希表,如果探测成功的话就会把这个数据发送给上层算子。这里存在的一个问题就是如果 hash join 的右侧存在一个数据 reshuffle(重分布)的话,网络的开销可能比较大,这个开销标取决于右表的数据量大小。在这种情况下,我们可以通过 join filter 来降低数据 shuffle 的网络开销。

以这个计划为例:

在上面这个计划中,2 号的 hash join 算子从左侧读数据,读的时候会使用 t1.c1 这个连接键创建一个 join filter,就是计划中的这个 3 号 join filter create 算子。join filter 最常见的一个形式是 bloom filter,join filter 创建完成以后会被发送到 hash join 右侧这个 DFO(6 号算子以及更下层的算子)。

可以看到,10 号的这个 table scan 上面有一个过滤条件 sys_op_bloom_filter(t2.c1),表示会用 bloom_filter 对 hash join 右表 t2.c1 的值去进行一个快速的探测。如果探测失败的话说明不存在 t2.c1 跟这个 t1.c1 相等,那么这行数据可以直接被提前过滤掉,不需要向上发送给 hash join。

分区裁剪

join filter 不仅可以对行进行过滤,还可以用于分区裁剪,即对分区进行过滤。如果 t1 是一个分区表,并且连接键是它的分区键的话,那么可以生成这样的计划:

可以看到这个计划里,3 号是一个 partition join filter create 算子,它会感知 hash join 右边的 t1 表的分区方式,它每从下层获取一行左表的数据,就会用 c1 的值去计算这行数据在右表 t1 表里的哪个分区里面,并将这个 partition id 记录到 join filter 里。最终这个 partition id 的 join filter 会在 8 号算子上用于 hash join 右表的分区裁剪。右表扫描每一个分区之前都会检查这个 partition id 是否存在于 join filter 中,如果不存在的话,可以直接跳过整分区的扫描。

join filter 可以提前对数据进行过滤、提前对分区进行裁剪,降低了扫描数据、网络传输和探测 hash 表的开销。目前 4.2 之前只支持 bloom filter 这一种类型的 join filter。4.2上新支持了 in filter 和 range filter 这两种类型的 join filter,这两种新的 join filter 在一些场景中对性能有很好的提升,特别是在左表不同值的个数较少或者是左表值连续的场景。

其他的分布式下压

除了上述介绍的几个比较常见也比较易于理解的分布式下压技术,OceanBase 还支持更多的自适应分布式下压,例如: window function 的自适应两阶段下压、三阶段的聚合下压等等。

OceanBase 中这些更为复杂的分布式下压技术,由于精力所限,就不再一一详细介绍。下面会贴一下刚才提到的两种分布式下压的执行计划,供有兴趣的同学去进行更深入的研究。

window function 的自适应两阶段下压:

select /*+parallel(3) */
		c1, sum(c2) over (partition by c1) from t1 order by c1;
Query Plan
===================================================
|ID|OPERATOR                             |NAME    |
---------------------------------------------------
|0 |PX COORDINATOR MERGE SORT            |        |
|1 | EXCHANGE OUT DISTR                  |:EX10001|
|2 |  MATERIAL                           |        |
|3 |   WINDOW FUNCTION CONSOLIDATOR      |        |
|4 |    EXCHANGE IN MERGE SORT DISTR     |        |
|5 |     EXCHANGE OUT DISTR (HASH HYBRID)|:EX10000|
|6 |      WINDOW FUNCTION                |        |
|7 |       SORT                          |        |
|8 |        PX BLOCK ITERATOR            |        |
|9 |         TABLE SCAN                  |t1      |
===================================================

三阶段的聚合下压:

select /*+ parallel(2) */
    c1, sum(distinct c2),count(distinct c3), sum(c4) from t group by c1;
Query Plan
===========================================================================
|ID|OPERATOR                               |NAME    |EST.ROWS|EST.TIME(us)|
---------------------------------------------------------------------------
|0 |PX COORDINATOR                         |        |1       |8           |
|1 |└─EXCHANGE OUT DISTR                   |:EX10002|1       |7           |
|2 |  └─HASH GROUP BY                      |        |1       |6           |
|3 |    └─EXCHANGE IN DISTR                |        |2       |6           |
|4 |      └─EXCHANGE OUT DISTR (HASH)      |:EX10001|2       |6           |
|5 |        └─HASH GROUP BY                |        |2       |4           |
|6 |          └─EXCHANGE IN DISTR          |        |2       |4           |
|7 |            └─EXCHANGE OUT DISTR (HASH)|:EX10000|2       |3           |
|8 |              └─HASH GROUP BY          |        |2       |2           |
|9 |                └─PX BLOCK ITERATOR    |        |1       |1           |
|10|                  └─TABLE FULL SCAN    |t       |1       |1           |
===========================================================================  

下回预告

这篇博客给大家介绍了 OceanBase 执行器中几个比较具有代表性的分布式下压技术,但是已经假设大家对数据库的分布式执行技术有所了解。如果大家对执行器的并行执行技术还不是特别了解,请期待下一篇博客《OceanBase 并行执行技术》。

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

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

相关文章

HarmonyOS异常处理实践

一、HarmonyOS应用异常处理框架 全面检测、精准记录异常传播路径、日志精简 二、FaultLog FaultLog是应用异常日志查询接口,提供QuerySelfFaultLog接口以查询自身故障。 JS_CRASH:ArkTS程序故障类型 CPP_CRASH:C程序故障类型 APP_FREEZE&…

csv导入导出

一、csv 1、介绍 CSV(Comma-Separated Values,逗号分隔的值)是一种简单、实用的文件格式,用于存储和表示包括文本、数值等各种类型的数据。CSV 文件通常以 .csv 作为文件扩展名。这种文件格式的一个显著特点是:文件内…

JavaSE——Arrays类、System类

目录 一、Arrays类 1.Arrays.toString() 2.Arrays.sort() 3.Arrays实现冒泡排序的定制排序 4.Arrays.binarySearch()——二叉查找 5.Arrays.copyOf()——数组元素的复制 6.Arrays.fill()——数组的填充 7.Arrays.equals(arr1,arr2)——比较2个数组元素内容是否完全一致…

java中的ArrayList和LinkedList的底层剖析

引入: 数据结构的分类,数据结构可以分成:线性表,树形结构,图形结构。 线性结构(线性表)包括:数组、链表、栈队列 树形结构:二叉树、AVL树、红黑树、B树、堆、Trie、哈夫曼树、并查集 图形结构:邻接矩阵、邻接表 线性表是具有存…

通信工程学习:什么是TDD时分双工

TDD:时分双工 TDD(时分双工,Time Division Duplexing)是一种在移动通信系统中广泛使用的全双工通信技术。以下是TDD的详细解释: 一、定义与原理 TDD是一种通过时间划分来实现双向通信的技术。在TDD模式中,接收和传送在同一频率信道(即载波)的不同时隙…

新品上市!智能无线接入型路由器ZX7981EP,WIFI6技术双频频段

在这个快节奏的时代 每一次点击都渴望即刻响应,每一份数据都期待安全传输 我们希望大家都能享有顶尖的网络体验,由此 启明智显ZX7891EP智能无线接入型路由器新品上市! 2.4G/5G双频段,WAN口/LAN口皆齐全 最新802.1ax WiFi6技术…

【Linux】Linux工具——CMake入门

目录 1.什么是CMake 2.CMakeflie的安装和版本的查看 3.几个简单示例 3.1.编译一个.cc文件 3.2.编译一个.hpp文件和一个.cc文件 3.3.编译一个.hpp文件和两个.cc文件 3.4.编译两个.hpp文件和一个.cc文件 4.CMakeLists.txt 4.1.CMakeLists.txt常用的几条指令 4.2.变量和…

软件测试之单元测试/系统测试/集成测试详解

🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 一、单元测试的概念 单元测试是对软件基本组成单元进行的测试,如函数或一个类的方法。当然这里的基本单元不仅仅指的是一个函数或者方法&#xff0…

基于densenet模型在RML201610a数据集上的调制识别【代码+数据集+python环境+GUI系统】

基于densenet模型在RML201610a数据集上的调制识别【代码数据集python环境GUI系统】 Loss曲线 背景意义 随着社会的快速发展,人们在通信方面的需求逐渐增加,特别是在无线通信领域。通信环境的复杂化催生了多种通信形式和相关应用,这使得调制…

最新版无忧二级域名分发源码,支持包月续费

目前版本支持,开通会员,会员组可以解析哪些域名 比如 用普通域名引流,免费使用,就可以注册就是普通用户组 会员组可以设置价格比如10块钱买永久会员,没有别的特权,只是会员才可以租备案域名, 设…

有源蜂鸣器(5V STM32)

目录 一、介绍 二、模块原理 1.有/无源蜂鸣器介绍 2.原理图 3.引脚描述 三、程序设计 main.c文件 beep.h文件 beep.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 蜂鸣器是一种能将音频信号转化声音信号的发音器件,在家电器上,在银行…

直播 SDK

直播 SDK 是音视频终端 SDK(腾讯云视立方)针对移动直播场景专属打造的一体化产品,支持直播推拉流、主播观众互动连麦、主播跨房 PK 等能力,为用户提供高质量直播服务,快速满足手机直播的需求。更多关于直播 SDK 的文档…

Ubuntu 22.04无法连接网络(网络图标丢失)解决方案

对于Ubuntu 22.04而言: sudo service NetworkManager stop sudo rm /var/lib/NetworkManager/NetworkManager.state sudo service NetworkManager start

嵌入式项目:STM32平衡车详解 (基础知识篇) (基于STM32F103C8T6)

前言: 本文是基于B站草履虫编写的平衡车相关内容,包括模块和基础知识,结合代码进行讲解,将知识进行汇总 (由于本篇内容较长,请结合目录使用) 注:基于开源精神,本文仅供学习参考 目…

基于Node.js+Express+MySQL+VUE实现的计算机毕业设计旅游推荐网站

猜你喜欢评论 登录注册搜索 推荐定制景点/springboot/javaWEB/J2EE/MYSQL数据库/vue前后分离小程序 功能图如下所示: 一、设计目标 本次计算机毕业设计项目的主要目标是设计和开发一款功能完善、用户友好的旅游推荐网站。该网站旨在为广大旅游爱好者提供一个便捷、…

蓝桥杯--STM32G431RBT6(TIM定时器的输出频率和占空比,含详细原理介绍和使用方法)

目录 一、前言 二、代码 实现功能:​编辑 按如图配置 定义变量 编写执行代码 显示在LCD上 加入按键效果 三、效果展示 四、代码开源 一、前言 ARR 即自动重装载值(Auto Reload Register)。相当于一个水杯,水杯容量&am…

sqlserver迁移数据库文件存储位置

业务背景:由于C盘爆满,需要将数据库文件迁移到别处比如D盘 下面以某一个数据库转移为示例:(可以用SSMS工具,新建查询配合使用) 1.查询数据库文件存储路径 sql语句: -- 查询路径 USE QiangTes…

[Redis][哨兵][上]详细讲解

目录 0.前言1.基本概念1.相关名词解释2.主从复制的问题3.人工恢复主节点故障4.哨兵自动恢复主节点故障 0.前言 说明:该章节相关操作不需要记忆,理解流程和原理即可,用的时候能自主查到即可Redis的主从复制模式下,⼀旦主节点由于故…

使用豆包MarsCode 实现高可用扫描工具

以下是「 豆包MarsCode 体验官」优秀文章,作者郝同学测开笔记。 前言 最近接触K8s,了解到K8s提供了非常方便的实现高可用的能力,再加上掘金推出「豆包MarsCode初体验」征文活动,所以打算使用豆包 MarsCode IDE来实现…

UniApp基于xe-upload实现文件上传组件

xe-upload地址:文件选择、文件上传组件(图片,视频,文件等) - DCloud 插件市场 致敬开发者!!! 感觉好用的话,给xe-upload的作者一个好评 背景:开发中经常会有…