当前位置: 首页 > news >正文

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

在《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 并行执行技术》。

相关文章:

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

在《OceanBase 数据库源码解析》这本书中,关于SQL执行器的深入剖析相对较少,因此,希望增添一些实用且详尽的补充内容。 上一篇博客《 OceanBase技术解析: 执行器中的自适应技术》中,已初步介绍了执行器中几项典型的自适…...

Firebase和JavaScript创建Postback Link逻辑

Firebase是一个提供后端即服务(BaaS)的平台,它允许开发者快速构建应用程序而无需管理服务器。Firebase不直接提供生成Postback Link的功能,但您可以使用Firebase的功能来构建和管理URL,然后在客户端使用这些URL来实现Postback。 以下是如何使用Firebase和JavaScript来创建…...

docker配置daemon.json文件

报错 :Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) 解决方法 配置加速地址 vim /etc/docker/daemon.json添加以下内容 {"registry-mirro…...

【08】纯血鸿蒙HarmonyOS NEXT星河版开发0基础学习笔记-Scroll容器与Tabs组件

序言: 本文详细讲解了关于我们在页面上经常看到的可滚动页面和导航栏在鸿蒙开发中如何用Scroll和Tabs组件实现,介绍了Scroll和Tabs的基本用法与属性。 笔者也是跟着B站黑马的课程一步步学习,学习的过程中添加部分自己的想法整理为笔记分享出…...

苏州 数字化科技展厅展馆-「世岩科技」一站式服务商

数字化科技展厅展馆设计施工是一个综合性强、技术要求高的项目,涉及到众多方面的要点。以下是对数字化科技展厅展馆设计施工要点的详细分析: 一、明确目标与定位 在设计之初,必须明确展厅的目标和定位。这包括确定展厅的主题、目标受众、展…...

音频搜索公司 DeepGram,定位语音搜索AI大脑,DeepGram想做“音频版”

1. 亦仁分享 DeepGram 成立于 2015 年,位于美国山景城,是一家基于 AI 技术的音频搜索引擎公司。运用机器学习进行语音识别、搜寻重要时刻并对音频和视频进行分类,帮助用户快速索引和浏览音频和视频文件,包括电话语音、会议语音、…...

基于php的在线租房管理系统

作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏:Java精选实战项目…...

如何评价 Python 语言的运行速度

Python 作为一门编程语言,其运行速度一直是业界讨论的焦点。它的简洁语法和广泛的应用使得它在开发过程中非常高效,然而,运行速度与一些更底层的编程语言相比存在一定的劣势。这是否是由于 Python 语法的简洁性所带来的代价?我们可…...

Tomcat系列漏洞复现

CVE-2017-12615——Tomcat put⽅法任意⽂件写⼊漏洞 漏洞描述 当 Tomcat运⾏在Windows操作系统时,且启⽤了HTTP PUT请求⽅法(例如,将 readonly初始化参数由默认值设置为false),攻击者将有可能可通过精⼼构造的攻击请求…...

K8S拉取本地docker中registry的镜像报错:http: server gave HTTP response to HTTPS client

本地部署了一个K8S集群,但是worker1和worker2的docker无法拉取外面的镜像,docker的daemon.json也配置了,无法下载,于是在master部署了一个docker registry。 但是pod还是无法拉取registry的镜像并报错。 我这里使用的是container…...

Leetcode 1235. 规划兼职工作

1.题目基本信息 1.1.题目描述 你打算利用空闲时间来做兼职工作赚些零花钱。 这里有 n 份兼职工作,每份工作预计从 startTime[i] 开始到 endTime[i] 结束,报酬为 profit[i]。 给你一份兼职工作表,包含开始时间 startTime,结束时…...

LeetCode 2535.数组元素和与数字和的绝对差:模拟

【LetMeFly】2535.数组元素和与数字和的绝对差:模拟 力扣题目链接:https://leetcode.cn/problems/difference-between-element-sum-and-digit-sum-of-an-array/ 给你一个正整数数组 nums 。 元素和 是 nums 中的所有元素相加求和。数字和 是 nums 中每…...

SpringCloud-pom创建Eureka

<?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 https://…...

动态规划算法专题(一):斐波那契数列模型

目录 1、动态规划简介 2、算法实战应用【leetcode】 2.1 题一&#xff1a;第N个泰波那契数 2.1.1 算法原理 2.1.2 算法代码 2.1.3 空间优化原理——滚动数组 2.1.4 算法代码——空间优化版本 2.2 题二&#xff1a;三步问题 2.2.1 算法原理 2.2.2 算法代码 2.3 题二&a…...

H.264编解码工具 - x264

一、简介 x264是一个开源的H.264/AVC视频编码库,它可以将视频数据压缩成H.264格式,并且可以从H.264格式解码出原始视频数据。 x264是以C语言编写的,并且可以在多个平台上使用,包括Windows、Linux和Mac OS等操作系统。 x264具有很高的编码效率和视频质量,它支持多种编码…...

外卖点餐小程序源码系统 单店多门店自助切换 带完整的安装代码包以及搭建部署教程

系统概述 本外卖点餐小程序源码系统旨在帮助餐饮企业和商家快速搭建一个功能完善的在线外卖平台。系统支持单店与多门店的灵活切换&#xff0c;方便商家根据自身业务需求进行管理和运营。同时&#xff0c;系统还提供了丰富的营销工具和数据分析功能&#xff0c;助力商家实现精…...

通过Ideal和gitbash共同实现分支合并

文章目录 背景描述&#xff1a;演示jy_20240704_develop分支同步到jy_dev分支方式一方式二 背景描述&#xff1a; 目前项目里有四个分支&#xff0c;分别是master、jy_20240704_develop、jy_dev、jy_qas。 其中master是主分支&#xff0c;其他三个分支都是根据master来创建的…...

Vue.js 组件开发

Vue.js 是一个渐进式的JavaScript框架&#xff0c;主要用于构建用户界面。它采用了组件化的开发方式&#xff0c;使得前端开发更加高效、灵活且易于维护。组件是Vue.js的核心概念之一&#xff0c;理解和掌握组件的开发&#xff0c;有助于我们高效地构建现代Web应用。 本文将涵…...

【Lcode 随笔】C语言版看了不后悔系列持续更新中。。。

文章目录 题目一&#xff1a;最长回文子串题目描述&#xff1a;示例输入与输出&#xff1a;题目分析&#xff1a;解题思路&#xff1a;示例代码&#xff1a;深入剖析&#xff1a; 题目二&#xff1a;合并K个有序链表题目描述&#xff1a;示例输入与输出&#xff1a;题目分析&am…...

排序--希尔排序

希尔排序介绍 希尔排序核心思想就是:1,分组;2,直接插入排序:越有序越快 希尔排序就是多次利用直接插入排序的一个排序算法. 希尔排序的算法思想:间隔式分组,利用直接插入排序让组内有序,然后缩小分组再次排序,直到组数为1希尔排序的理论基础就是直接插入排序越有序越快; 希尔排…...

【教程】57帧! Mac电脑流畅运行黑神话悟空

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 1、先安装CrossOver。网上有许多和谐版&#xff0c;可自行搜索。&#xff08;pd虚拟机里运行黑神话估计够呛的&#xff09; 2、运行CrossOver&#xf…...

『大模型笔记』Docker如何清理Build Cache!

Docker如何清理Build Cache! 文章目录 一. docker system df1. 镜像(Images)2. 容器(Containers)3. 本地卷(Local Volumes)4. 构建缓存(Build Cache)5. 总结二. 构建缓存(Build Cache)删除有什么影响1. 镜像构建速度变慢2. 磁盘空间被释放3. 不会影响已构建和运行的…...

如何使用 Python 读取数据量庞大的 excel 文件

使用 pandas.read_excel 读取大文件时&#xff0c;的确会遇到性能瓶颈&#xff0c;特别是对于10万行20列这种规模的 .xlsx 文件&#xff0c;常规的 pandas 方法可能会比较慢。 要提高读取速度&#xff0c;关键是找到更高效的方式处理 Excel 文件&#xff0c;特别是在 Python 的…...

c语言200例 067

大家好&#xff0c;欢迎来到无限大的频道 今天给大家带来的是c语言200例 题目要求&#xff1a; 设计一个共用体类型&#xff0c;使其成员包含多种数据类型&#xff0c;根据不同的数据类型&#xff0c;输出不同的结果 要设计一个共用体&#xff08;union&#xff09;类型&…...

RabbitMQ的高级特性-死信队列

死信(dead message) 简单理解就是因为种种原因, ⽆法被消费的信息, 就是死信. 有死信, ⾃然就有死信队列. 当消息在⼀个队列中变成死信之后&#xff0c;它能被重新被发送到另⼀个交换器 中&#xff0c;这个交换器就是DLX( Dead Letter Exchange ), 绑定DLX的队列, 就称为死信队…...

Python 复制PDF中的页面

操作PDF文档时&#xff0c;复制其中的指定页面可以帮助我们从PDF文件中提取特定信息&#xff0c;如文本、图表或数据等&#xff0c;以便在其他文档中使用。复制PDF页面也可以实现在不同文件中提取页面&#xff0c;以创建一个新的综合文档。 本文将介绍如何使用Python 在同一文档…...

Sql Developer日期显示格式设置

默认时间格式显示 设置时间格式&#xff1a;工具->首选项->数据库->NLS->日期格式: DD-MON-RR 修改为: YYYY-MM-DD HH24:MI:SS 设置完格式显示&#xff1a;...

IP地址与智能家居能够碰撞出什么样的火花呢?

感应灯、远程遥控空调&#xff0c;自动感应窗帘——智能家居已经在正逐步走入我们的生活&#xff0c;为我们带来前所未有的便捷与舒适体验。而在这一进程中&#xff0c;IP地址又能够与智能家居碰撞出什么样的火花呢&#xff1f; 一、IP地址&#xff1a;智能家居的连接基石 智…...

人工智能技术在电磁场与微波技术专业的应用

在人工智能与计算电磁学的融合背景下&#xff0c;电磁学的研究和应用正在经历一场革命。计算电磁 学是研究电磁场和电磁波在不同介质中的传播、散射和辐射等问题的学科&#xff0c;它在通信、雷达、无 线能量传输等领域具有广泛的应用。随着人工智能技术的发展&#xff0c;这一…...

The First项目报告:探索Yield Guild Games运行机制与发展潜力

在探索数字娱乐与金融融合的全新疆域中&#xff0c;GameFi&#xff08;游戏化金融&#xff09;以其独特的魅力引领了一场前所未有的变革。这一创新概念&#xff0c;最初由MixMarvel的CSO Mary Ma在2019年底乌镇大会的远见卓识中首次提出&#xff0c;它将去中心化金融&#xff0…...

iis7安装wordpress/视频号怎么付费推广

软件工程-软件维护/系统维护 系统可维护性 评价指标 可理解性可测试性可修改性 系统维护类型 改正性维护适应性维护预防性维护完善性维护 tip&#xff1a;完善性维护占半壁江山 系统文档 开发文档&#xff1a;技术员编写&#xff0c;与开发相关产品文档&#xff1a;与用…...

哈尔滨建设局网站/怎么做网页设计的页面

Linux 有问必答&#xff1a;如何在Perl中捕捉并处理信号 提问: 我需要通过使用Perl的自定义信号处理程序来处理一个中断信号。在一般情况下&#xff0c;我怎么在Perl程序中捕获并处理各种信号&#xff08;如INT&#xff0c;TERM&#xff09;&#xff1f; 作为POSIX标准的异步通…...

自己可以建设网站吗/亚马逊开店流程及费用

以下是各个插件简介及下载地址&#xff1a; http://cwiki.apache.org/S2PLUGINS/home.html...

wordpress 换空间 换域名/欧美seo查询

最近一些项目开始使用机器学习和神经网络&#xff0c;有一个项目是对从搜索引擎抓回来的文字判断倾向性&#xff0c;看文章是正面的还是负面的(抱怨、怀疑、责备)&#xff0c;没办法&#xff0c;我们所服务的互联网金融最近就是一个被高度关注的领域。之前开发小组使用 R 语言基…...

如何做pc网站适配/静态网页制作

文章目录一、清除浮动需求 ( 没有设置高度的盒子且内部设置了浮动 )二、清除浮动代码示例一、清除浮动需求 ( 没有设置高度的盒子且内部设置了浮动 ) 如果盒子没有设置高度 , 并且盒子中还设置了浮动 , 如上一篇博客 【CSS】课程网站 网格商品展示 模块制作 ② ( 网格商品展示盒…...

上海网网站建设/在线子域名二级域名查询工具

一、分类的目的和分类的方法 目标 能够说出项目中进行文本的目的能够说出意图识别的方法能够说出常见的分类的方法 1.1 文本分类的目的 回顾之前的流程&#xff0c;我们可以发现文本分类的目的就是为了进行意图识别 在当前我们的项目的下&#xff0c;我们只有两种意图需要被…...