首页 文章 精选 留言 我的

精选列表

搜索[AI技能],共10000篇文章
优秀的个人博客,低调大师

PHP 技能精进之 PHP-FPM 多进程模型

PHP-FPM 提供了更好的 PHP 进程管理方式,可以有效控制内存和进程、可以平滑重载PHP配置。那么当我们谈论 PHP-FPM 多进程模型的时候,作为 PHPer 的你了解多少呢? 首先,让我们一起看几个问题: ①:PHP-FPM 启动进程的方式主要有哪几种,区别是什么? ②:PHP-FPM,是主进程接收请求转给子进程,还是子进程单独接收请求并处理,如何验证? ③:为何在 PHP-FPM 模式下,PHP 代码很少有人去做连接池? ④:PHP-FPM 模式性能差的体现有哪些,如何优化? ⑤:PHP-FPM 模式下的 YAC 为何无法和 CLI 模式无法共享内存? 1. 如何启动进程 PHP-FPM 是多进程模式,由 Master 进程管理 Worker 进程。进程的数量,都可以通过 php-fpm.conf 做具体配置。PHP-FPM 的进程可以分为动态模式及静态模式: ①:静态(Static) 直接开启指定数量的 PHP-FPM 进程,不再增加或者减少;启动固定数量的进程,占用内存高。但在用户请求波动大的时候,对 Linux 操作系统进程的处理上耗费的系统资源低。 ②:动态(Dynamic) 开始时开启一定数量的 PHP-FPM 进程,当请求量变大的时候,动态增加 PHP-FPM进程数到上限,当空闲的时候自动释放空闲进程数到一个下限。 动态模式会根据 max、min、idle children 配置,动态的调整进程数量。在用户请求较为波动,或者瞬间请求增高的时候,动态模式下会进行大量进程的创建、销毁等操作,而造成 Linux 负载波动升高。简单来说,请求量少,PHP-FPM 进程数少,请求量大,进程数多。优势就是,当请求量小的时候,进程数少,内存占用也小。 ③:按需 (Ondemand) 这种模式下,PHP-FPM 的 Master 不会 Fork 任何子进程,纯粹就是按需启动。 这种模式通常很少使用,因为它基本无法适应有一定量级的线上业务。由于 php-fpm 是短连接的,所以每次请求都会先建立连接,建立连接的过程必然会触发上图的执行步骤。所以,在大流量的系统上 Master 进程会变得繁忙,占用系统 CPU 资源,不适合大流量环境的部署。 借用一张网络图片来说明: 需要注意 2 个点,「连接」和「数据」到来。有连接进来再 Fork 进程,同样可以达到子进程继承父进程上下文,然后子进程处理用户请求这个目的。 (关于动态、静态进程模式的相关参数,可参考 PHP 官方文档。) 我们需要关注的是对于我们自身的业务,应该选择的 PHP-FPM 模式为动态还是静态。 通常来说,对于比较大内存的服务器,设置为静态的话会提高效率。因为频繁开关 php-fpm 进程也会有时滞,所以内存够大的情况下开静态效果会更好。数量也可以根据 内存/30M 得到。比如说 2GB 内存的服务器,可以设置为 50;4GB 内存可以设置为 100 等。高配机器选静态,低配机器(省内存)选动态,高配机器用动态不能充分利用内存资源和 CPU 资源,也无法及时应对瞬时高并发。 2.如何进行请求处理和验证 PHP-FPM 的进程管理方式和 Nginx 的进程管理方式有些类似。在处理请求时,并非由主进程接受请求后转给子进程,而是子进程「抢占式」地接受用户请求。本质上 PHP-FPM 多进程以及 Nginx 多进程,都是在主进程监听同一个端口后,Fork 子进程达到多个进程监听同一端口的目的。 Linux 系统所有的进程 IO 操作,都需要和操作系统打交道。也就是说,系统知道所有 IO 操作。这个过程就是我们常说的「系统调用」。我们可以从系统调用入手解决这个问题。系统调用的查看,可以使用 Strace。 如何验证相对简单,我们可以采取 2 种方式: 看 PHP-FPM 进程的日志。这需要配置好合适的 PHP-FPM 日志格式; 既然 IO 数据会通过内核态过度到用户态进程,我们可以通过 strace -p <pid> 命令去跟踪系统调用。分别跟踪 PHP-FPM 的主进程 ID 以及子进程 ID,然后访问 Nginx,由 Nginx 通过 fast-cgi 协议转到 PHP-FPM 进程上,看在哪个进程上发送了系统调用。 3. 为何不在 PHP-FPM 下做代码连接池 ? 首先,在 PHP-FPM 模式下,一个请求的生命周期注定只有 1 次。也就是说,从 FPM 请求到请求、解析 PHP 脚本,到 FPM 的 Zend 虚拟机分配资源执行,再到最后的处理结束,PHP-FPM 会回收这次请求的所有资源。 这种方式一是为了让开发不需要关心资源的回收处理,所以你可能没怎么关心过网络的关闭、文件描述符的关闭等等。二是为了减少内存溢出的情况。 如果在这种模式下,你实现了连接池,也意味着请求结束,连接池消失,做了一次无用功而已。 「鸡肋的」PConnect(持久化链接)。持久化链接也就是链接不释放。但问题在于,PHP-FPM 是多进程模式,而持久化的链接存在于进程中。这就意味着,如果一台机器有 300 个 FPM 进程,会一次性初始化 300 个持久化链接。如果因为面临业务活动需求冒然对机器扩容,很可能造成业务的数据库连接数直接打满。 4. 如何优化性能 首先,我们应该思考导致性能差可能的原因是什么。如果一个应用的性能差,我们往往会从 2 个方面来分析,一个是 IO 性能,一个是计算性能。 IO 方面,因为 PHP-FPM 模式下难以做连接池,所以高并发业务下的网络处理会有劣势。注意我这里一直说的都是 PHP-FPM 模式下,在 CLI 模式下还是可以自己做连接池的。只不过这个连接池仅限于 CLI 模式的单进程内,而且这个模式不能用于处理网络请求(比如 HTTP 请求)。因为 PHP 默认单进程模式,FPM、CLI 都是默认单进程,即便 CLI 可以做连接池 ,也不方便做链接保活(不能同时做心跳检测)。 计算性能上来说,虽然 PHP 是用 C 写的,如果单纯论计算性能是不错的。但问题在于 PHP 处理请求时,每次都要解析 PHP 脚本、翻译 PHP 代码为 Opcode、用 Zend 虚拟机执行 Opcode,处理结束,释放资源。经历这样的过程是导致 PHP 计算性能慢的最大原因之一。 如何优化: 对于计算性能来说,使用 Zend OPcache 扩展,缓存字节码。 对于**IO 性能**来说,使用文件 cache 或者 memcached 减轻对网络 Cache 的压力;使用 Yac 减轻对 Cache 层的压力;在同一次请求中;复用链接不要每次都用新的;合理设计日志组件类库,优化 Logger 减少对文件操作的次数来减少 IO 的压力。 关于设计一个合格的 Logger 组件,我们需要注意几个点: ① 每次请求,只做一次日志写操作,不要每次别人调用你的函数,你都去执行一次类似 file_put_contents 的操作。 ② 兼容各种类似错误。换句话说,即使 PHP fatal error 了,你也得能把知名错误之前的日志记录下来。这个实现可以借助 PHP 类的析构方法来做。也可以使用更好的 register_shutdown_function 来注册一个钩子,在 PHP 请求结束的时候,回调此钩子,完成做最后的日志操作。 5. YAC 为何无法和 CLI 模式共享内存 我们知道,PHP 扩展开发中首要执行的一个宏是PHP_MINIT_FUNCTION。YAC 扩展需要在 PHP-FPM 进程启动时起就初始化一块共享内存,供各个进程来共享使用。因此,实现共享的关键在于需要一个让各个进程都知道的相同标识。 YAC 扩展的初始化流程为: 我们查看 create_segments 的具体实现: 上面做了一些注释,最关键的是要开启共享内存需要的系统 ID,shared_segment_name,此值包含了进程ID。也就是 PHP-FPM 的主进程 ID。有相同的共享内存标识 ID,就是 PHP-FPM 模式所有进程间能够通信的奥秘所在。而如果我们是想要通过 PHP 脚本使用 yac 扩展读取这个共享内存,会这样做: 在 CLI 模式下,这样是不可能拿到 PHP-FPM 模式下设置的共享内存数据的。因为 CLI 模式下执行 PHP 脚本、进程 ID,和 PHP-FPM 模式下的进程 ID 完全不相同。 后面的文章中,我们会找机会讲一讲进程间通讯,以及基于共享内存的通讯。总结来说,多进程要共享内存通信,必须要一开始就协调好一个唯一 ID。这个 ID 多个进程间都要知道。PHP-FPM 是多进程,主进程 fork 子进程出来,子进程自然知道这个唯一 ID 是什么(因为 Linux 进程 fork 会把整个进程的堆栈内存都 fork 一遍)。但是,php a.php 这样执行,其实是一个完全独立的进程,和 PHP-FPM 没任何关系,这样的进程,也就不能知道 PHP-FPM 进程里的那个唯一 ID 是什么。 本文作者:董红帅,马蜂窝系统部研发工程师。 关注马蜂窝技术,找到更多你想要的内容

优秀的个人博客,低调大师

一文掌握GaussDB(DWS) SQL进阶技能:全文检索

摘要:本文简要介绍了GaussDB(DWS)全文检索的原理和使用方法。 本文分享自华为云社区《GaussDB(DWS) SQL进阶之全文检索》,原文作者:Zhang Jingyao 。 全文检索(Text search)顾名思义,就是在给定的文档中查找指定模式(pattern)的过程。GaussDB(DWS)支持对表格中文本类型的字段及字段的组合做全文检索,找出能匹配给定模式的文本,并以用户期望的方式将匹配结果呈现出来。 本文结合笔者的经验和思考,对GaussDB(DWS)的全文检索功能作简要介绍,希望能对读者有所帮助。 1. 预处理 在指定的文档中查找一个模式有很多种办法,例如可以用grep命令搜索一个正则表达式。理论上,对数据库中的文本字段也可以用类似grep的方式来检索模式,GaussDB(DWS)中就可以通过关键字“LIKE”或操作符“~”来匹配字符串。但这样做有很多问题。首先对每段文本都要扫描,效率比较低,难以衡量“匹配度”或“相关度”。而且只能机械地匹配字符串,缺少对语法语义的分析能力,例如对英语中的名词复数,动词的时态变换等难以自动地识别和匹配,对于由自然语言构成的文本无法获得令人满意的检索结果。 GaussDB(DWS)采用类似搜索引擎的方式来进行全文检索。首先对给定的文本和模式做预处理,包括从一段文本中提取出单词或词组,去掉对检索无用的停用词(stop word),对变形后的单词做标准化等等,使之变为适合检索的形式再作匹配。 GaussDB(DWS)中,原始的文档和搜索条件都用文本(text)表示,或者说,用字符串表示。经过预处理后的文档变为tsvector类型,通过函数to_tsvector来实现这一转换。例如, postgres=# select to_tsvector('a fat cat ate fat rats'); to_tsvector ----------------------------------- 'ate':4 'cat':3 'fat':2,5 'rat':6 (1 row) 观察上面输出的tsvector类型,可以看到to_tsvector的效果: 首先各个单词被摘取出来,其位置用整数标识出来,例如“fat”位于原始句子中的第2和第5个词的位置。 此外,“a”这个词太常见了,几乎每个文档里都会出现,对于检索到有用的信息几乎没有帮助。套用香农理论,一个词出现的概率越大,其包含的信息量越小。像“a”,“the”这种单词几乎不携带任何信息,所以被当做停用词(stop word)去掉了。注意这并没有影响其他词的位置编号,“fat”的位置仍然是2和5,而不是1和4。 另外,复数形式的“rats”被换成了单数形式“rat”。这个操作被称为标准化(Normalize),主要是针对西文中单词在不同语境中会发生的变形,去掉后缀保留词根的一种操作。其意义在于简化自然语言的检索,例如检索“rat”时可以将包含“rat”和“rats”的文档都检索出来。被标准化后得到的单词称为词位(lexeme),比如“rat”。而原始的单词被称为语言符号(token)。 将一个文档转换成tsvector形式有很多好处。例如,可以方便地创建索引,提高检索的速度和效率,当文档数量巨大时,通过索引来检索关键字比grep这种全文扫描匹配要快得多。再比如,可以对不同关键字按重要程度分配不同的权重,方便对检索结果进行排序,找出相关度最高的文档等等。 经过预处理后的检索条件被转换成tsquery类型,可通过to_tsquery函数实现。例如, postgres=# select to_tsquery('a & cats & rat'); to_tsquery --------------- 'cat' & 'rat' (1 row) 从上面的例子可以看到: 跟to_tsvector类似,to_tsquery也会对输入文本做去掉停用词、标准化等操作,例如去掉了“a”,把“cats”变成“cat”等。 输入的检索条件本身必须用与(&)、或(|)、非(!)操作符连接,例如下面的语句会报错 postgres=# select to_tsquery('cats rat'); ERROR: syntax error in tsquery: "cats rat" CONTEXT: referenced column: to_tsquery 但plainto_tsquery没有这个限制。plainto_tsquery会把输入的单词变成“与”条件: postgres=# select plainto_tsquery('cats rat'); plainto_tsquery ----------------- 'cat' & 'rat' (1 row) postgres=# select plainto_tsquery('cats,rat'); plainto_tsquery ----------------- 'cat' & 'rat' (1 row) 除了用函数之外,还可以用强制类型转换的方式将一个字符串转换成tsvector或tsquery类型,例如 postgres=# select 'fat cats sat on a mat and ate a fat rat'::tsvector; tsvector ----------------------------------------------------- 'a' 'and' 'ate' 'cats' 'fat' 'mat' 'on' 'rat' 'sat' (1 row) postgres=# select 'a & fat & rats'::tsquery; tsquery ---------------------- 'a' & 'fat' & 'rats' (1 row) 跟函数的区别是强制类型转换不会去掉停用词,也不会作标准化,且对于tsvector类型不会记录词的位置。 2. 模式匹配 把输入文档和检索条件转换成tsvector和tsquery之后,就可以进行模式匹配了。GaussDB(DWS)中使用“@@”操作符来进行模式匹配,成功返回True,失败返回false。 例如创建如下表格, postgres=# create table post( postgres(# id bigint, postgres(# author name, postgres(# title text, postgres(# body text); CREATE TABLE -- insert some tuples 然后想检索body中含有“physics”或“math”的帖子标题,可以用如下的语句来查询: postgres=# select title from post where to_tsvector(body) @@ to_tsquery('physics | math'); title ----------------------------- The most popular math books 也可以将多个字段组合起来查询: postgres=# select title from post where to_tsvector(title || ' ' || body) @@ to_tsquery('physics | math'); title ----------------------------- The most popular math books (1 row) 注意不同的查询方式可能产生不同的结果。例如下面的匹配不成功,因为::tsquery没对检索条件做标准化,前面的tsvector里找不到“cats”这个词: postgres=# select to_tsvector('a fat cat ate fat rats') @@ 'cats & rat'::tsquery; ?column? ---------- f (1 row) 而同样的文档和检索条件,下面的匹配能成功,因为to_tsquery会把“cats”变成“cat”: postgres=# select to_tsvector('a fat cat ate fat rats') @@ to_tsquery('cats & rat'); ?column? ---------- t (1 row) 类似地,下面的匹配不成功,因为to_tsvector会把停用词a去掉: postgres=# select to_tsvector('a fat cat ate fat rats') @@ 'cat & rat & a'::tsquery; ?column? ---------- f (1 row) 而下面的能成功,因为::tsvector保留了所有词: postgres=# select 'a fat cat ate fat rats'::tsvector @@ 'cat & rat & a'::tsquery; ?column? ---------- f (1 row) 所以应根据需要选择合适的检索方式。 此外,@@操作符可以对输入的text做隐式类型转换,例如, postgres=# select title from post where body @@ 'physics | math'; title ------- (0 rows) 准确来讲,text@@text相当于to_tsvector(text) @@ plainto_tsquery(text),因此上面的匹配不成功,因为plainto_tsquery会把或条件'physics | math'变成与条件'physic' & 'math'。使用时要格外小心。 3. 创建和使用索引 前文提到,逐个扫描表中的文本字段缓慢低效,而索引查找能够提高检索的速度和效率。GaussDB(DWS)支持用通用倒排索引GIN(Generalized Inverted Index)进行全文检索。GIN是搜索引擎中常用的一种索引,其主要原理是通过关键字反过来查找所在的文档,从而提高查询效率。可通过以下语句在text类型的字段上创建GIN索引: postgres=# create index post_body_idx_1 on post using gin(to_tsvector('english', body)); CREATE INDEX 注意这里必须使用to_tsvector函数生成tsvector,不能使用强制或隐式类型转换。而且这里用到的to_tsvector函数比前一节多了一个参数’english’,这个参数是用来指定文本搜索配置(Text search Configuration)的。关于文本搜索配置将在下一节介绍。不同的配置计算出来的tsvector不同,生成的索引自然也不同,所以这里必须明确指定,而且在查询的时候只有配置和字段都与索引定义一致才能通过索引查找。例如下面的查询中,前一个可以通过post_body_idx_1来检索,后一个找不到对应的索引,只能通过全表扫描检索。 postgres=# explain select title from post where to_tsvector('english', body) @@ to_tsquery('physics | math'); QUERY PLAN ----------------------------------------------------------------------------------------------------- id | operation | E-rows | E-width | E-costs ----+---------------------------------+--------+---------+--------- 1 | -> Streaming (type: GATHER) | 1 | 32 | 42.02 2 | -> Bitmap Heap Scan on post | 1 | 32 | 16.02 3 | -> Bitmap Index Scan | 1 | 0 | 12.00 postgres=# explain select title from post where to_tsvector('french', body) @@ to_tsquery('physics | math'); QUERY PLAN ---------------------------------------------------------------------------------------------- id | operation | E-rows | E-width | E-costs ----+------------------------------+--------+---------+------------------ 1 | -> Streaming (type: GATHER) | 1 | 32 | 1000000002360.50 2 | -> Seq Scan on post | 1 | 32 | 1000000002334.50 4. 全文检索配置(Text search Configuration) 这一节谈谈GaussDB(DWS)如何对文档做预处理,或者说,to_tsvector是如何工作的。 文档预处理大体上分如下三步进行: 第一步,将文本中的单词或词组一个一个提取出来。这项工作由解析器(Parser)或称分词(Segmentation)器来进行。完成后文档变成一系列token。 第二步,对上一步得到的token做标准化,包括依据指定的规则去掉前后缀,转换同义词,去掉停用词等等,从而得到一个个词位(lexeme)。这一步操作依据词典(Dictionary)来进行,也就是说,词典定义了标准化的规则。 最后,记录各个词位的位置(和权重),从而得到tsvector。 从上面的描述可以看出,如果给定了解析器和词典,那么文档预处理的规则也就确定了。在GaussDB(DWS)中,这一整套文档预处理的规则称为全文检索配置(Text search Configuration)。全文检索配置决定了匹配的结果和质量。 如下图所示,一个全文检索配置由一个解析器和一组词典组成。输入文档首先被解析器分解成token,然后对每个token逐个词典查找,如果在某个词典中找到这个token,就按照该词典的规则对其做Normalize。有的词典做完Normalize后会将该token标记为“已处理”,这样后面的字典就不会再处理了。有的词典做完Normalize后将其输出为新的token交给后面的词典处理,这样的词典称为“过滤型”词典。 图1 文档预处理过程 配置使用的解析器在创建配置的时候指定,且不可修改,例如, postgres=# create text search configuration mytsconf (parser = default); CREATE TEXT SEARCH CONFIGURATION GaussDB(DWS)内置了4种解析器,目前不支持自定义解析器。 postgres=# select prsname from pg_ts_parser; prsname ---------- default ngram pound zhparser (4 rows) 词典则通过ALTER TEXT SEARCH CONFIGURATION命令来指定,例如 postgres=# alter text search configuration mytsconf add mapping for asciiword with english_stem,simple;ALTER TEXT SEARCH CONFIGURATION postgres=# alter text search configuration mytsconf add mapping for asciiword with english_stem,simple;ALTER TEXT SEARCH CONFIGURATION 指定了mytsconf使用english_stem和simple这两种词典来对“asciiword”类型的token做标准化。 上面语句中的“asciiword”是一种token类型。解析器会对分解出的token做分类,不同的解析器分类方式不同,可通过ts_token_type函数查看。例如,‘default’解析器将token分为如下23种类型: postgres=# select * from ts_token_type('default'); tokid | alias | description -------+-----------------+------------------------------------------ 1 | asciiword | Word, all ASCII 2 | word | Word, all letters 3 | numword | Word, letters and digits 4 | email | Email address 5 | url | URL 6 | host | Host 7 | sfloat | Scientific notation 8 | version | Version number 9 | hword_numpart | Hyphenated word part, letters and digits 10 | hword_part | Hyphenated word part, all letters 11 | hword_asciipart | Hyphenated word part, all ASCII 12 | blank | Space symbols 13 | tag | XML tag 14 | protocol | Protocol head 15 | numhword | Hyphenated word, letters and digits 16 | asciihword | Hyphenated word, all ASCII 17 | hword | Hyphenated word, all letters 18 | url_path | URL path 19 | file | File or path name 20 | float | Decimal notation 21 | int | Signed integer 22 | uint | Unsigned integer 23 | entity | XML entity (23 rows) 当前数据库中已有的词典可以通过系统表pg_ts_dict查询。 如果指定了配置,系统会按照指定的配置对文档作预处理,如上一节创建GIN索引的命令。如果没指定配置,to_tsvector使用default_text_search_config变量指定的默认配置。 postgres=# show default_text_search_config; -- 查看当前默认配置 default_text_search_config ---------------------------- pg_catalog.english (1 row) postgres=# set default_text_search_config = mytsconf; -- 设置默认配置 SET postgres=# show default_text_search_config; default_text_search_config ---------------------------- public.mytsconf (1 row) postgres=# reset default_text_search_config; -- 恢复默认配置 RESET postgres=# show default_text_search_config; default_text_search_config ---------------------------- pg_catalog.english (1 row) 注意default_text_search_config是一个session级的变量,只在当前会话中有效。如果想让默认配置持久生效,可以修改postgresql.conf配置文件中的同名变量,如下图所示。 修改后需要重启进程。 总结 GaussDB(DWS)的全文检索模块提供了强大的文档搜索功能。相比于用“LIKE”关键字,或 “~”操作符的模式匹配,全文检索提供了较丰富的语义语法支持,能对自然语言文本做更加智能化的处理。配合恰当的索引,能够实现对文档的高效检索。 点击关注,第一时间了解华为云新鲜技术~

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册