使用 PostgreSQL 创建全文搜索引擎1

news2024/12/28 18:38:27

PostgreSQL 提供了必要的模块,可以组合和创建自己的全文搜索搜索引擎。让我们尝试一下。

这是系列文章的第 1 部分,将要在其中探索 PostgreSQL 中的全文搜索功能,并研究我们可以完成多少典型的搜索引擎功能。在第 2 部分中,我们将比较 PostgreSQL 的全文搜索和 Elasticsearch。

如果您想跟着并尝试示例查询(推荐这样做,这样会更有趣),代码示例将使用来自 Kaggle 的 Wikipedia Movie Plots 数据集。要导入它,请下载 CSV 文件,然后创建此表:

CREATE TABLE movies(
	ReleaseYear int,
	Title text,
	Origin text,
	Director text,
	Casting text,
	Genre text,
	WikiPage text,
	Plot text);

并像这样导入 CSV 文件:

\COPY movies(ReleaseYear, Title, Origin, Director, Casting, Genre, WikiPage, Plot)
	FROM 'wiki_movie_plots_deduped.csv' DELIMITER ',' CSV HEADER;

该数据集包含 34,000 个电影标题,CSV 格式大小约为 81 MB。

1. PostgreSQL 全文搜索原语(full-text search primitives)

Postgres 为全文搜索方法提供了构建块,我们可以组合这些构建块来创建自己的搜索引擎。这非常灵活,但也意味着与 ElasticsearchTypesenseMellisearch 等专业搜索引擎相比,它通常感觉比较低级。

我们将通过示例介绍的主要构建块是:

  • 数据类型 tsvectortsquery
  • 用匹配运算符 @@ 检查 tsquery 是否与 tsvector 匹配
  • 对每个匹配进行排名的函数( ts_rankts_rank_cd
  • GIN索引类型,一种用于提高查询 tsvector效率 的 倒排索引

我们将从查看这些构建块开始,然后将进入更高级的主题,包括相关性增强器(relevancy boosters)、拼写错误容忍(typo-tolerance)和多面搜索(faceted search)。

1.1 tsvector

tsvector 数据类型存储词素(lexemes)的排序列表。词素(lexemes)是一个字符串,就像一个标记(token)一样,但它已经被标准化,以便可以将同一单词的不同形式标准化为同一个形式。例如,规范化(normalization )几乎总是包括将大写字母转为小写,并且通常涉及删除后缀(例如英语中的 sing )。下面是一个示例,使用 to_tsvector 函数 将英语短语解析为 tsvector

SELECT * FROM unnest(to_tsvector('english',
	'I''m going to make him an offer he can''t refuse. Refusing is not an option.'));
 
 lexeme | positions | weights
--------+-----------+---------
 go     | {3}       | {D}
 m      | {2}       | {D}
 make   | {5}       | {D}
 offer  | {8}       | {D}
 option | {17}      | {D}
 refus  | {12,13}   | {D,D}
(6 rows)

如上,“I”、“to”或“an”等停用词被删除,因为它们太常见而无法用于搜索。(例如“refuse”和“Refusing”都转换为“refus”)这些单词被规范化并简化为其词根。标点符号将被忽略。对于每个单词,记录其在原始短语中的位置(positions)(例如“refus”是文本中的第 12 个和第 13 个单词)和权重(weights)(这对于排名很有用,我们将在稍后讨论)。

在上面的示例中,从单词到词素(lexemes)的转换规则基于 english 搜索配置。使用 simple 搜索配置运行相同的查询会生成 tsvector ,将包含在文本中找到的所有单词:

SELECT * FROM unnest(to_tsvector('simple',
	'I''m going to make him an offer he can''t refuse. Refusing is not an option.'));
 
  lexeme  | positions | weights
----------+-----------+---------
 an       | {7,16}    | {D,D}
 can      | {10}      | {D}
 going    | {3}       | {D}
 he       | {9}       | {D}
 him      | {6}       | {D}
 i        | {1}       | {D}
 is       | {14}      | {D}
 m        | {2}       | {D}
 make     | {5}       | {D}
 not      | {15}      | {D}
 offer    | {8}       | {D}
 option   | {17}      | {D}
 refuse   | {12}      | {D}
 refusing | {13}      | {D}
 t        | {11}      | {D}
 to       | {4}       | {D}
(16 rows)

正如您所看到的,“refuse”和“refusing”现在会生成不同的词素(lexemes)。当您的列包含标签(labels)或标记(tags)时, simple 配置特别有用。

PostgreSQL 具有针对一组相当不错的语言的内置配置。您可以通过运行以下命令查看该列表:

SELECT cfgname FROM pg_ts_config;

但值得注意的是,没有 CJK(中文-日语-韩语)的配置,如果您需要使用这些语言创建搜索查询,则值得记住这一点。虽然 simple 配置在实践中对于不受支持的语言应该可以很好地工作,但我不确定这对于 CJK 是否足够。

1.2 tsquery

tsquery 数据类型用于表示规范化查询。 tsquery 包含搜索词,它必须是已经规范化的词素(lexemes),并且可以使用 AND、OR、NOT 和 FOLLOWED BY 运算符组合多个词。像 to_tsqueryplainto_tsquerywebsearch_to_tsquery 这样的函数有助于将用户编写的文本转换为正确的 tsquery ,主要是通过规范文本中出现的单词。

为了了解 tsquery ,现在看一些使用 websearch_to_tsquery 的示例:

SELECT websearch_to_tsquery('english', 'the darth vader');
 websearch_to_tsquery
----------------------
'darth' & 'vader'

这是一个逻辑 AND,意味着文档需要同时包含“darth”和“vader”才能匹配。您也可以进行逻辑或:

SELECT websearch_to_tsquery('english', 'darth OR vader');
 websearch_to_tsquery
----------------------
 'darth' | 'vader'

并且您可以排除单词:

SELECT websearch_to_tsquery('english', 'darth vader -wars');
   websearch_to_tsquery
---------------------------
 'darth' & 'vader' & !'war'

此外,您还可以表示短语搜索:

SELECT websearch_to_tsquery('english', '"the darth vader son"');
     websearch_to_tsquery
------------------------------
 'darth' <-> 'vader' <-> 'son'

这意味着:“darth” 后边跟着的是“vader”,然后跟着“son”。

但请注意,“the”一词将被忽略,因为根据 english 搜索配置,它是一个停止词。对于这样的短语,这可能是一个问题:

SELECT websearch_to_tsquery('english', '"do or do not, there is no try"');
 websearch_to_tsquery
----------------------
 'tri'
(1 row)

哎,几乎整个短语都丢失了。使用 simple 配置给出了预期的结果:

SELECT websearch_to_tsquery('simple', '"do or do not, there is no try"');
                           websearch_to_tsquery
--------------------------------------------------------------------------
 'do' <-> 'or' <-> 'do' <-> 'not' <-> 'there' <-> 'is' <-> 'no' <-> 'try'

您可以使用匹配运算符 @@ 检查 tsquery 是否与 tsvector 匹配。

SELECT websearch_to_tsquery('english', 'darth vader') @@
	to_tsvector('english',
		'Darth Vader is my father.');
 
?column?
----------
 t

While the following example doesn’t match:
虽然以下示例不匹配:

SELECT websearch_to_tsquery('english', 'darth vader -father') @@
	to_tsvector('english',
		'Darth Vader is my father.');
 
?column?
----------
 f

1.3 GIN

现在我们已经了解了 tsvectortsquery 的工作原理,让我们看看另一个关键构建块:加快查询的GIN 索引。 GIN 代表广义倒排索引(Generalized Inverted Index)。 GIN 设计用于处理要索引的项是复合值的情况,并且索引要处理的查询需要搜索复合项中出现的元素值。这意味着 GIN 不仅仅可以用于文本搜索,还可以用于索引 JSON 查询。

您可以在一组列上创建 GIN 索引,也可以首先创建 tsvector 类型的列,以包含所有可搜索的列。像这样:

ALTER TABLE movies ADD search tsvector GENERATED ALWAYS AS
	(to_tsvector('english', Title) || ' ' ||
   to_tsvector('english', Plot) || ' ' ||
   to_tsvector('simple', Director) || ' ' ||
	 to_tsvector('simple', Genre) || ' ' ||
   to_tsvector('simple', Origin) || ' ' ||
   to_tsvector('simple', Casting)
) STORED;

然后创建实际GIN索引:

CREATE INDEX idx_search ON movies USING GIN(search);

您现在可以执行简单的测试搜索,如下所示:

SELECT title FROM movies WHERE search @@ websearch_to_tsquery('english','darth vader');
 
                        title
--------------------------------------------------
 Star Wars Episode IV: A New Hope (aka Star Wars)
 Return of the Jedi
 Star Wars: Episode III – Revenge of the Sith
(3 rows)

要查看索引的效果,您可以比较使用和不使用索引的上述查询的时间。在我的计算机上,GIN 索引将其时间从 200 多毫秒缩短到了大约 4 毫秒。

1.4 ts_rank

到目前为止,我们已经了解了 ts_vectorts_query 如何匹配搜索查询。然而,为了获得良好的搜索体验,首先显示最佳结果非常重要 - 这意味着结果需要按相关性(relevancy)排序。

在官方文档有如下内容:

PostgreSQL provides two predefined ranking functions, which take into account lexical, proximity, and structural information; that is, they consider how often the query terms appear in the document, how close together the terms are in the document, and how important is the part of the document where they occur. However, the concept of relevancy is vague and very application-specific. Different applications might require additional information for ranking, e.g., document modification time. The built-in ranking functions are only examples. You can write your own ranking functions and/or combine their results with additional factors to fit your specific needs.

PostgreSQL 提供了两个预定义的排名函数,它们考虑了词汇(lexical)、邻近度(proximity)和结构信息;也就是说,他们考虑查询术语在文档中出现的频率、这些术语在文档中的接近程度以及它们出现的文档部分的重要性。然而,相关性的概念是模糊的并且非常特定于应用程序。不同的应用程序可能需要额外的信息进行排名,例如文档修改时间。内置排名功能仅是示例。您可以编写自己的排名函数和/或将其结果与其他因素结合起来以满足您的特定需求。

提到的两个排名函数是 ts_rankts_rank_cd 。它们之间的区别在于,虽然它们都考虑了术语的频率,但 ts_rank_cd 还考虑了匹配词素(lexemes )彼此的接近度。

要在查询中使用它们,您可以执行以下操作:

SELECT title,
       ts_rank(search, websearch_to_tsquery('english', 'darth vader')) rank
  FROM movies
  WHERE search @@ websearch_to_tsquery('english','darth vader')
  ORDER BY rank DESC
  LIMIT 10;
 
                      title                       |    rank
--------------------------------------------------+-------------
 The Empire Strikes Back                          |  0.26263964
 Star Wars Episode IV: A New Hope (aka Star Wars) |  0.18902963
 Star Wars: Episode III – Revenge of the Sith     |  0.10292397
 Rogue One: A Star Wars Story (film)              |  0.10049681
 Return of the Jedi                               |  0.09910346
 American Honey                                   |  0.09910322

关于 ts_rank 需要注意的一件事是它需要访问每个结果的 search 列。这意味着如果 WHERE 条件匹配很多行,PostgreSQL 需要访问所有行才能进行排名,这可能会很慢。举例来说,上述查询在我的计算机上会在 5-7 毫秒内返回。如果我修改查询来搜索 darth OR vader ,它会在大约 80 毫秒内返回,因为现在有超过 1000 个匹配结果需要排名和排序。

2. 相关性调整(Relevancy tuning)

虽然基于词频的相关性是搜索排序的一个很好的默认值,但数据通常包含比简单的频率更相关的重要指标。

以下是电影数据集的一些示例:

  • 标题中的匹配应比描述或情节中的匹配具有更高的重要性。
  • 可以根据收视率和/或收到的票数来推广更受欢迎的电影。
  • 考虑到用户的偏好,某些类别可以得到更多提升。例如,如果特定用户喜欢喜剧,则可以给予这些电影更高的优先级。
  • 对搜索结果进行排名时,较新的标题可以被认为比非常旧的标题更相关。

这就是为什么专用搜索引擎通常提供使用不同列或字段来影响排名的方法。以下是来自 ElasticTypesenseMeil​​isearch 的示例调整指南。

如果您想要直观地演示相关性调整的影响,这里有一个 4 分钟的快速视频:

视频连接

2.1 数字、日期和精确值的提升 (Numeric, date, and exact value boosters)

虽然 Postgres 不直接支持基于其他列的提升,但排名最终只是一个排序表达式,因此您可以向其中添加自己的排名因子。

例如,如果你想增加投票数,你可以这样做:

SELECT title,
  ts_rank(search, websearch_to_tsquery('english', 'jedi'))
    -- numeric booster example
    + log(NumberOfVotes)*0.01
 FROM movies
 WHERE search @@ websearch_to_tsquery('english','jedi')
 ORDER BY rank DESC LIMIT 10;

对数的作用是平滑影响,0.01 因子使提升(booster )达到与排名分数相当的规模。

您还可以设计更复杂的提升器,例如通过评级进行提升,但前提是排名具有一定的票数。为此,您可以创建一个如下函数:

create function numericBooster(rating numeric, votes numeric, voteThreshold numeric)
	returns numeric as $$
		select case when votes < voteThreshold then 0 else rating end;
$$ language sql;

并像这样使用它:

SELECT title,
  ts_rank(search, websearch_to_tsquery('english', 'jedi'))
    -- numeric booster example
    + numericBooster(Rating, NumberOfVotes, 100)*0.005
 FROM movies
 WHERE search @@ websearch_to_tsquery('english','jedi')
 ORDER BY rank DESC LIMIT 10;

再举一个例子。假设我们想要提高喜剧的排名。您可以创建一个如下所示的 valueBooster 函数:

create function valueBooster (col text, val text, factor integer)
	returns integer as $$
		select case when col = val then factor else 0 end;
$$ language sql;

如果列与特定值匹配,则该函数返回一个因子,不匹配则返回 0。在如下查询中使用它:

SELECT title, genre,
   ts_rank(search, websearch_to_tsquery('english', 'jedi'))
   -- value booster example
   + valueBooster(Genre, 'comedy', 0.05) rank
FROM movies
   WHERE search @@ websearch_to_tsquery('english','jedi')                                                                                                 ORDER BY rank DESC LIMIT 10;
                      title                       |               genre                |        rank
--------------------------------------------------+------------------------------------+---------------------
 The Men Who Stare at Goats                       | comedy                             |  0.1107927106320858
 Clerks                                           | comedy                             |  0.1107927106320858
 Star Wars: The Clone Wars                        | animation                          | 0.09513916820287704
 Star Wars: Episode I – The Phantom Menace 3D     | sci-fi                             | 0.09471701085567474
 Star Wars: Episode I – The Phantom Menace        | space opera                        | 0.09471701085567474
 Star Wars: Episode II – Attack of the Clones     | science fiction                    | 0.09285612404346466
 Star Wars: Episode III – Revenge of the Sith     | science fiction, action            | 0.09285612404346466
 Star Wars: The Last Jedi                         | action, adventure, fantasy, sci-fi |  0.0889768898487091
 Return of the Jedi                               | science fiction                    | 0.07599088549613953
 Star Wars Episode IV: A New Hope (aka Star Wars) | science fiction                    | 0.07599088549613953
(10 rows)

2.2 列权重(Column weights)

还记得我们讨论过 tsvector 词素以及它们可以附加权重吗? Postgres 支持 4 个权重,分别为 A、B、C 和 D。A 是最大的权重,D 是最小的默认权重。您可以通过 setweight 函数控制权重,在构建 tsvector 列时通常会调用该函数:

ALTER TABLE movies ADD search tsvector GENERATED ALWAYS AS
   (setweight(to_tsvector('english', Title), 'A') || ' ' ||
   to_tsvector('english', Plot) || ' ' ||
   to_tsvector('simple', Director) || ' ' ||
   to_tsvector('simple', Genre) || ' ' ||
   to_tsvector('simple', Origin) || ' ' ||
   to_tsvector('simple', Casting)
) STORED;

让我们看看这样做的效果。如果没有 setweight ,搜索 jedi 将返回:

SELECT title, ts_rank(search, websearch_to_tsquery('english', 'jedi')) rank
   FROM movies
   WHERE search @@ websearch_to_tsquery('english','jedi')
   ORDER BY rank DESC;
                      title                       |    rank
--------------------------------------------------+-------------
 Star Wars: The Clone Wars                        |  0.09513917
 Star Wars: Episode I – The Phantom Menace        |  0.09471701
 Star Wars: Episode I – The Phantom Menace 3D     |  0.09471701
 Star Wars: Episode III – Revenge of the Sith     | 0.092856124
 Star Wars: Episode II – Attack of the Clones     | 0.092856124
 Star Wars: The Last Jedi                         |  0.08897689
 Return of the Jedi                               | 0.075990885
 Star Wars Episode IV: A New Hope (aka Star Wars) | 0.075990885
 Clerks                                           |  0.06079271
 The Empire Strikes Back                          |  0.06079271
 The Men Who Stare at Goats                       |  0.06079271
 How to Deal                                      |  0.06079271
(12 rows)

如果标题列上有 setweight

SELECT title, ts_rank(search, websearch_to_tsquery('english', 'jedi')) rank
   FROM movies
   WHERE search @@ websearch_to_tsquery('english','jedi')
   ORDER BY rank DESC;
                      title                       |    rank
--------------------------------------------------+-------------
 Star Wars: The Last Jedi                         |   0.6361112
 Return of the Jedi                               |   0.6231253
 Star Wars: The Clone Wars                        |  0.09513917
 Star Wars: Episode I – The Phantom Menace        |  0.09471701
 Star Wars: Episode I – The Phantom Menace 3D     |  0.09471701
 Star Wars: Episode III – Revenge of the Sith     | 0.092856124
 Star Wars: Episode II – Attack of the Clones     | 0.092856124
 Star Wars Episode IV: A New Hope (aka Star Wars) | 0.075990885
 The Empire Strikes Back                          |  0.06079271
 Clerks                                           |  0.06079271
 The Men Who Stare at Goats                       |  0.06079271
 How to Deal                                      |  0.06079271
(12 rows)

请注意,名称中带有“jedi”的电影如何跃居列表顶部,并且排名也有所上升。

值得指出的是,只有四个权重“类”有一定的限制,并且在计算 tsvector 时需要应用它们。

3. 拼写错误/模糊搜索(Typo-tolerance / fuzzy search)

使用 tsvectortsquery 时,PostgreSQL 不直接支持模糊搜索或拼写错误。然而,假设拼写错误出现在查询部分,我们可以实现以下想法:

  • 将内容中的所有词素(lexemes)索引到单独的表中
  • 对于查询中的每个单词,使用相似度或编辑距离在此表中进行搜索
  • 修改查询以包含找到的任何单词
  • 执行搜索

下面是它的工作原理。首先,使用 ts_stats 获取物化视图中的所有单词:

CREATE MATERIALIZED VIEW unique_lexeme AS
   SELECT word FROM ts_stat('SELECT search FROM movies');

现在,对于查询中的每个单词,检查它是否在 unique_lexeme 视图中。如果不是,请在该视图中进行模糊搜索以查找可能的拼写错误:

SELECT * FROM unique_lexeme
   WHERE levenshtein_less_equal(word, 'pregant', 2) < 2;
 
   word
----------
 premant
 pregrant
 pregnant
 paegant

在上面我们使用 Levenshtein 距离,因为这是 Elasticsearch 等搜索引擎用于模糊搜索的距离。

获得候选单词列表后,您需要调整查询以将它们全部包含在内。

4. 多面搜索(Faceted search)

分面搜索尤其在电子商务网站上很受欢迎,因为它可以帮助客户迭代地缩小搜索范围。以下是来自 amazon.com 的示例:

Faceted search on Amazon
亚马逊上的多面搜索

上述可以通过手动定义类别,然后将其作为 WHERE 条件添加到搜索中来实现。另一种方法是根据现有数据通过算法创建类别。例如,您可以使用以下内容创建“Decade”构面:

SELECT ReleaseYear/10*10 decade, count(Title) cnt FROM movies
  WHERE search @@ websearch_to_tsquery('english','star wars')
  GROUP BY decade ORDER BY cnt DESC;
 
decade | cnt
--------+-----
   2000 |  39
   2010 |  31
   1990 |  29
   1950 |  28
   1940 |  26
   1980 |  22
   1930 |  13
   1960 |  11
   1970 |   7
   1910 |   3
   1920 |   3
(11 rows)

这还提供了每个十年的匹配数,您可以将其显示在括号中。

如果您想在单个查询中获取多个方面,可以将它们组合起来,例如使用 CTE:

WITH releaseYearFacets AS (
  SELECT 'Decade' facet, (ReleaseYear/10*10)::text val, count(Title) cnt
  FROM movies
  WHERE search @@ websearch_to_tsquery('english','star wars')
  GROUP BY val ORDER BY cnt DESC),
genreFacets AS (
  SELECT 'Genre' facet, Genre val, count(Title) cnt FROM movies
  WHERE search @@ websearch_to_tsquery('english','star wars')
  GROUP BY val ORDER BY cnt DESC LIMIT 5)
SELECT * FROM releaseYearFacets UNION SELECT * FROM genreFacets;
 
 facet  |   val   | cnt
--------+---------+-----
 Decade | 1910    |   3
 Decade | 1920    |   3
 Decade | 1930    |  13
 Decade | 1940    |  26
 Decade | 1950    |  28
 Decade | 1960    |  11
 Decade | 1970    |   7
 Decade | 1980    |  22
 Decade | 1990    |  29
 Decade | 2000    |  39
 Decade | 2010    |  31
 Genre  | comedy  |  21
 Genre  | drama   |  35
 Genre  | musical |   9
 Genre  | unknown |  13
 Genre  | war     |  15
(16 rows)

上面的方法在中小型数据集上应该可以很好地工作,但是在非常大的数据集上它可能会变得很慢。

5. 结论

我们已经了解了 PostgreSQL 全文搜索原语,以及如何将它们组合起来创建一个非常先进的全文搜索引擎,该引擎也恰好支持连接和 ACID 事务等功能。换句话说,它具有其他搜索引擎通常不具备的功能。

还有更高级的搜索主题值得详细介绍:

  • 建议/自动完成(suggesters / auto-complete )
  • 精确短语匹配 (exact phrase matching )
  • 与 pg-vector 结合的混合搜索(语义 + 关键字)

这些都值得写一篇博文(即将到来!),但现在您应该对它们有一个直观的感觉:它们很可能使用 PostgreSQL,但它们要求您进行组合原语的工作,并且在某些情况下在非常大的数据集上,性能可能会受到影响。

在第 2 部分中,我们将与 Elasticsearch 进行详细比较,以回答何时值得在 PostgreSQL 中实施搜索以及将 Elasticsearch 添加到基础设施并同步数据的问题。如果您想在本文发布时收到通知,您可以在 Twitter 上关注我们或加入我们的 Discord。


Written by Tudor Golubenco

Published on July 12, 2023

原文地址

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

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

相关文章

【Redis从头学-0】一张思维导图对Redis做出基本介绍

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;啥技术都喜欢捣鼓捣鼓&#xff0c;喜欢分享技术、经验、生活。 &#x1f60e;人生感悟&#xff1a;尝尽人生百味&#xff0c;方知世间冷暖。 &#x1f4d6;所属专栏&#xff1a;Re…

[centos]设置主机名

1、设置 hostnamectl set-hostname 名字 2、查看是否生效 hostnamectl status 3、打开一个新链接就可以了

零代码编程:用ChatGPT批量删除Excel文件中的行

文件夹中有上百个Excel文件&#xff0c;每个文件中都有如下所示的两行&#xff0c;要进行批量删除。 在ChatGPT中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;要完成一个处理Excel文件内容的任务&#xff0c;具体步骤如下&#xff1a; 打开F盘的文件夹&#x…

16.1.2 Linux 的多用户多任务环境

在 Linux 下面执行一个指令时&#xff0c;系统会将相关的权限、属性、程序码与数据等均载入内存&#xff0c; 并给予这个单元一个程序识别码 &#xff08;PID&#xff09;&#xff0c;最终该指令可以进行的任务则与这个 PID 的权限有关。根据这个说明&#xff0c;我们就可以简单…

ESD培训和咨询的相关服务

ESD&#xff08;Electrostatic Discharge&#xff0c;静电放电&#xff09;是指在两个物体之间发生的电荷平衡或不平衡&#xff0c;导致静电能量的释放。静电放电可能会对敏感的电子设备、芯片和电子元件产生损坏&#xff0c;因此对于需要处理电子设备的行业来说&#xff0c;ES…

MSP432自主开发笔记6:定时器多通道捕获多条编码器线脉冲数

所用开发板&#xff1a;MSP432P401R 今日在此更新一下编码器测速的定时器捕获写法&#xff0c;之前学习时竟然忘记更新了~~ 本文讲如何用定时器的通道来 捕获编码器的脉冲信号数量&#xff0c;不提供速度路程的计算方式&#xff0c; 文章提供源码&#xff0c;测试工程下载&a…

手动实现 Spring 底层机制【初始化 IOC容器+依赖注入+BeanPostProcessor 机制+AOP】之实现任务阶段 5- bean 后置处理器

&#x1f600;前言 手动实现 Spring 底层机制【初始化 IOC容器依赖注入BeanPostProcessor 机制AOP】的第五篇具体实现了任务阶段 5- bean 后置处理器 &#x1f3e0;个人主页&#xff1a;尘觉主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是尘觉&#xff0c;希…

ModaHub魔搭社区:从OpenAI实践看分工必要性,核心关注工作流相关的基础软件工具栈

从OpenAI实践看分工必要性,核心关注工作流相关的基础软件工具栈 参考海外OpenAI的率先尝试,工作流分工、点工具加持助力成功。一方面,OpenAI在《GPT-4 Technical Report》论文中[1]中披露了参与GPT 4开发的人员分工,共249人,角色分工明确,预训练、强化学习和对齐、部署等…

时间序列去趋势化和傅里叶变换

在计算傅里叶变换之前对信号去趋势是一种常见的做法&#xff0c;特别是在处理时间序列时。在这篇文章中&#xff0c;我将从数学和视觉上展示信号去趋势是如何影响傅里叶变换的。 这篇文章的目的是让介绍理解什么是常数和线性去趋势&#xff0c;为什么我们使用它们&#xff0c;…

CRM系统如何搭建?流程是什么样的?

CRM系统可以提高企业的销售效率和客户满意度&#xff0c;从而增加企业的收入和利润。但是&#xff0c;要想成功地上线CRM系统&#xff0c;需要经过一系列的步骤和流程&#xff0c;下面说说&#xff0c;企业如何上线CRM系统&#xff1f;CRM系统搭建流程。 1、需求分析 需求分析…

记一次物理机安装centos7遇到的问题

首先制作U盘镜像&#xff08;之前装windows的大白菜之类的就没用了&#xff09; 用的这个UltraISO制作U盘镜像 然后从U盘启动开始安装&#xff0c; 问题一 安装时报错 dracut-pre-udev[351]:modprobe :ERROR:could not insert ‘floppy’ dracut-pre-udev[351]:modprobe…

Nacos源码 (3) 注册中心

本文将从一个服务注册示例入手&#xff0c;通过阅读客户端、服务端源码&#xff0c;分析服务注册、服务发现原理。 使用的2.0.2的版本。 返回目录 客户端 创建NacosNamingService对象 NacosNamingService nacosNamingService new NacosNamingService(NACOS_HOST);NacosNami…

华为OD机试 - 最长的连续子序列 (Java 2022Q4 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#xff09;》…

ISIS技术(第三十七课)

1 分享一下华为官网上的一张地图 官网地址:https://support.huawei.com/hedex/hdx.do?docid=EDOC1000105967&id=ZH-CN_CONCEPT_0000001501534705 2 路由的分类 -直连路由 直接连接的路由,且配置了IP地址之后(在同一网段内),就是直连路由。 -非直连路由 -静态路由…

如何在金属制品业运用IPD?

金属制品行业是指以金属材料为原料&#xff0c;通过加工、制造、加工等工艺制造出各种金属制品的企业和产业。这些金属制品包括但不限于机械设备、工具、建筑材料、家具、电子产品、交通运输设备等。金属制品加工业是机械装备行业的一个子行业&#xff0c;包括结构性金属制品制…

SpringBoot对一个URL通过method(GET、POST、PUT、DELETE)实现增删改查操作

目录 1. rest风格基础2. 开启方法3. 实战练习 1. rest风格基础 我们都知道GET、POST、PUT、DELETE分别对应查、增、改、删除 虽然Postman这些工具可以直接发送GET、POST、PUT、DELETE请求。但是RequestMapping并不支持PUT和DELETE请求操作。需要我们手动开启 2. 开启方法 P…

ModaHub魔搭社区:Milvus Cloud素材集合帖,等你查收

Hi~Milvus Cloud 的各位朋友,这是一期 Milvus Cloud 素材弹药库的集中汇总帖。随着向量数据库的火爆,越来越多的伙伴开始关注到向量数据库并开始使用 Milvus Cloud 。 考虑到目前信息获取的渠道多且分散,我们专门为大家整理了一期 Milvus Cloud 信息集合帖,让大家可以在快…

iPhone苹果手机触屏失灵无法关机,如何强制重启

参考:https://zhuanlan.zhihu.com/p/615223121 1&#xff0c;只轻按一下音量上键后快速松开 2&#xff0c;只轻按一下音量下键后快速松开 3&#xff0c;只按住右侧电源键长按不松手&#xff0c;直到手机关机。

Tomcat的多实例和动静分离

目录 一、多实例 二、 nginxtomcat的负载均衡和动静分离 三、Tomcat 客户端->四层代理->七层代理->tomcat服务器 实验&#xff1a; 问题总结&#xff1a; tomcat日志文件&#xff1a;/usr/local/tomcat/logs/catalina.out 一、多实例 在一台服务器上有多个tomc…

python——案例19:九九乘法表

案例19&#xff1a;九九乘法表for i in range(1,10): #i是行&#xff0c;j是列for j in range(1,i1): #确保内循环中的列小于等于列print(%d*%d%2ld%(i,j,i*j),end ) #计算方法&#xff0c;并且确保内容连续print()