多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Hyperf Scout 模型全文检索:基于 Elasticsearch 驱动的高性能协程化搜索方案

Hyperf Scout 模型全文检索:基于 Elasticsearch 驱动的高性能协程化搜索方案 后端Web框架微服务RPC框架异步编程【免费下载链接】hyperf A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.项目地址https://gitcode.com/hyperf/hyperf点击查看免费下载导读本文深入讲解 Hyperf 官方全文检索组件hyperf/scout的完整使用方式。该组件衍生于 laravel/scout在保持相同 API 的前提下完成了协程化改造让 Hyperf 模型可以借助模型观察者机制自动与搜索索引保持同步。读完本文你将掌握 Scout 的安装配置、索引的增删改查、搜索查询与分页、软删除适配以及如何编写并注册自定义搜索引擎从而在 Hyperf 项目中快速落地基于 Elasticsearch 的全文检索能力。一、组件概览什么是 Hyperf/ScoutHyperf/Scout 为模型的全文搜索提供了一个简单、基于驱动程序的解决方案。与传统的「先查数据库、再在内存中 LIKE 匹配」不同Scout 使用模型观察者Model Observer机制在模型记录发生变化时自动同步搜索索引保证索引与数据库的一致性。从当前仓库源码结构看组件包含以下几大核心模块均在 src/scout/src 目录下Searchabletrait挂载到模型后启用索引同步能力是使用 Scout 的入口ModelObserver监听模型的Saved/Deleted/ForceDeleted/Restored事件自动驱动索引同步Engine抽象类及其实现ElasticsearchEngine、NullEngine封装对搜索引擎的读写Builder搜索查询构造器提供get/raw/paginate/where/within等查询能力Console命令scout:import与scout:flush用于批量导入与清空索引EngineFactory与Provider负责按配置装配具体引擎实例。目前 Scout 自带一个Elasticsearch驱动编写自定义驱动程序也很简单你可以自由使用自己的搜索实现来扩展 Scout。二、安装与初始化2.1 引入组件包与 Elasticsearch 驱动使用 Composer 引入 Scout 组件及其默认依赖的 Elasticsearch 客户端composer require hyperf/scout composer require hyperf/elasticsearch2.2 发布配置文件Scout 安装完成后使用vendor:publish命令生成 Scout 配置文件命令会在项目的config目录下生成scout.phpphp bin/hyperf.php vendor:publish hyperf/scout仓库中该配置文件的原始版本位于 src/scout/publish/scout.php可作为对照参考。2.3 在模型中引入 Searchable Trait最后在需要搜索的模型中添加Hyperf\Scout\Searchabletrait。该 trait 会注册一个模型观察者保持模型与所有驱动的同步?php namespace App; use Hyperf\Database\Model\Model; use Hyperf\Scout\Searchable; class Post extends Model { use Searchable; }从 Searchable.php 的bootSearchable方法可以看到引入 trait 后会自动完成三件事为模型注册一个全局作用域SearchableScope扩展查询构造器的searchable/unsearchable宏、通过ListenerCollector注册ModelObserver监听器、注册集合层面的searchable/unsearchable宏。三、配置详解3.1 配置文件结构发布后的config/scout.php完整内容如下?php declare(strict_types1); return [ default env(SCOUT_ENGINE, elasticsearch), chunk [ searchable 500, unsearchable 500, ], prefix env(SCOUT_PREFIX, ), soft_delete false, concurrency 100, engine [ elasticsearch [ driver Hyperf\Scout\Provider\ElasticsearchProvider::class, // 如果 index 设置为 null则每个模型会对应一个索引反之每个模型对应一个类型 index null, hosts [ env(ELASTICSEARCH_HOST, http://127.0.0.1:9200), ], ], ], ];各配置项含义如下配置项默认值说明defaultelasticsearch默认使用的引擎名称可通过环境变量SCOUT_ENGINE覆盖对应下方engine数组中的键名chunk.searchable500批量导入searchable时分块处理的数据条数chunk.unsearchable500批量删除索引unsearchable时分块处理的数据条数prefix索引名前缀可通过环境变量SCOUT_PREFIX覆盖最终索引名由「前缀 模型表名」拼接而成soft_deletefalse是否在搜索中处理软删除模型开启后配合模型的SoftDeletestrait 使用concurrency100命令行批量导入时的协程并发数量engine.name.driver—引擎的 Provider 类名必须实现Hyperf\Scout\Provider\ProviderInterfaceengine.name.indexnull若为null每个模型对应一个独立索引否则多个模型共用一个索引、每个模型对应一个 typeengine.name.hostshttp://127.0.0.1:9200Elasticsearch 服务地址列表可通过ELASTICSEARCH_HOST环境变量覆盖关于index配置项需要结合 ElasticsearchEngine.php 的构造函数理解当index为null时每个模型使用自己的索引索引名来自模型的searchableAs()即prefix 表名当index设置了具体值如hyperf时所有模型共享该索引每个模型对应一个 type。需要注意的是由于 Elasticsearch 7.0 起移除了多 type 支持引擎在initIndex方法中会通过elastic-info()探测服务端版本若版本大于等于 7.0.0则强制将$index置为null自动退回「一模型一索引」模式见 ElasticsearchEngine.php。3.2 配置模型索引每个模型与给定的搜索「索引」同步这个「索引」包含该模型的所有可搜索记录。你可以把每一个「索引」设想为一张 MySQL 数据表。默认情况下每个模型都会被持久化到与模型「表」名相匹配的索引通常是模型名称的复数形式。你也可以通过覆盖模型上的searchableAs方法来自定义索引名?php namespace App; use Hyperf\Scout\Searchable; use Hyperf\Database\Model\Model; class Post extends Model { use Searchable; /** * Get the index name for the model. * * return string */ public function searchableAs() { return posts_index; } }searchableAs的默认实现位于 Searchable.php返回config(scout.prefix) . $this-getTable()即「配置前缀 模型表名」与文档描述一致。3.3 配置可搜索的数据默认情况下「索引」会从模型的toArray方法中读取数据做持久化。如果要自定义同步到搜索索引的数据可以覆盖模型上的toSearchableArray方法?php namespace App; use Hyperf\Scout\Searchable; use Hyperf\Database\Model\Model; class Post extends Model { use Searchable; /** * Get the indexable data array for the model. * * return array */ public function toSearchableArray() { $array $this-toArray(); // Customize array... return $array; } }该方法默认返回$this-toArray()见 Searchable.php。在返回数组中移除或新增字段即可精确控制写入 Elasticsearch 的文档内容例如过滤掉不适合全文检索的字段、追加关联数据或格式化后的字段。四、索引操作4.1 批量导入Import如果你要将 Scout 安装到现有项目可能已有大量数据库记录需要导入搜索驱动。使用 Scout 提供的import命令将所有现有记录导入搜索索引php bin/hyperf.php scout:import App\Post该命令定义在 ImportCommand.php支持两个可选参数--column或-c分块使用的排序列默认使用主键--chunk每次导入的记录数默认使用配置值scout.chunk.searchable。命令内部会define(SCOUT_COMMAND, true)并调用$model::makeAllSearchable($chunk, $column)。导入过程按配置的分块大小遍历全部记录并通过ModelsImported事件通知导入进度见 ImportCommand.php。4.2 添加记录当模型引入Hyperf\Scout\Searchabletrait 后只需save一个模型实例它就会自动添加到搜索索引。更新索引操作会在协程结束时进行不会阻塞请求$order new App\Order; // ... $order-save();从源码看这一行为由 ModelObserver.php 的saved方法触发模型保存后调用$model-searchable()进而通过queueMakeSearchable投递任务dispatchSearchableJob见 Searchable.php在协程环境中使用Coroutine::defer()延迟执行确保同步动作不阻塞当前请求。批量添加如果你希望把一批模型一次性加入搜索索引可以在模型查询构造器上链式调用searchable方法。searchable会把构造器的查询结果分块并将记录添加到搜索索引// 使用模型查询构造器增加... App\Order::where(price, , 100)-searchable(); // 使用模型关系增加记录... $user-orders()-searchable(); // 使用集合增加记录... $orders-searchable();searchable方法可以被看作是「更新插入」upsert操作如果模型记录已经在索引里它就会被更新如果不存在则被添加到索引。这一语义在 ElasticsearchEngine.php 中体现为批量请求体里的doc_as_upsert true。searchable宏由 SearchableScope.php 注册到查询构造器默认按scout.chunk.searchable500 条分块每块内先过滤出shouldBeSearchable()为真的模型再写入索引并派发ModelsImported事件。4.3 更新记录更新可搜索模型只需更新模型实例的属性并save到数据库Scout 会自动将更新同步到搜索索引$order App\Order::find(1); // 更新 order... $order-save();也可以在模型查询语句、模型关系或集合上使用searchable方法来更新一批模型。如果模型不存在于检索的索引中则会被创建// 使用模型查询语句更新... App\Order::where(price, , 100)-searchable(); // 你也可以使用模型关系更新... $user-orders()-searchable(); // 你也可以使用集合更新... $orders-searchable();4.4 删除记录直接使用delete从数据库中删除模型即可移除索引里的记录。这种删除形式甚至与软删除的模型兼容$order App\Order::find(1); $order-delete();如果你不想在删除记录之前检索模型可以在模型查询实例或集合上使用unsearchable方法// 通过模型查询删除... App\Order::where(price, , 100)-unsearchable(); // 通过模型关系删除... $user-orders()-unsearchable(); // 通过集合删除... $orders-unsearchable();从 ModelObserver.php 可以看到删除同步的逻辑分支若模型使用了软删除且scout.soft_delete配置开启则删除事件会被当作Saved处理——索引中记录仍保留但会写入软删除元数据__soft_deleted否则或执行forceDeleted时直接调用unsearchable()从索引中移除记录。此外清空某个模型全量索引可以使用scout:flush命令定义在 FlushCommand.phpphp bin/hyperf.php scout:flush App\Post4.5 暂停索引同步当需要执行一批模型操作而暂时不同步数据到搜索索引时可以使用协程安全的withoutSyncingToSearch方法。该方法接受一个立即执行的回调回调中的所有操作都不会同步到模型索引App\Order::withoutSyncingToSearch(function () { // 执行模型动作... });其实现位于 Searchable.php先调用disableSearchSyncing()在回调结束后通过finally保证重新enableSearchSyncing()。同步开关状态存储在协程上下文Context中见 ModelObserver.php因此是协程安全的不会影响其他协程的同步行为。五、执行搜索5.1 基础搜索使用search方法搜索模型它接受一个用于搜索的字符串。还需要在搜索查询上链式调用get方法才能返回匹配的模型集合$orders App\Order::search(Star Trek)-get();Scout 搜索返回模型的集合因此可以直接从路由或控制器返回结果它们会被自动转换成 JSON 格式Route::get(/search, function () { return App\Order::search([])-get(); });如果想在返回模型集合之前得到原始结果应该使用raw方法$orders App\Order::search(Star Trek)-raw();raw返回的是搜索引擎的原生响应见 Builder.php。在 Elasticsearch 引擎中默认查询语句构造为query_string的模糊匹配*关键词*见 ElasticsearchEngine.php并会把Builder上的 where 条件、排序、from/size等一并合并进bool查询。搜索查询默认在模型searchableAs方法指定的索引上执行。当然你也可以使用within方法指定要搜索的自定义索引$orders App\Order::search(Star Trek) -within(tv_shows_popularity_desc) -get();within的实现见 Builder.php它设置Builder的$index属性最终传递给引擎的search请求作为目标索引。5.2 Where 语句Scout 允许在搜索查询中增加简单的「where」语句。目前这些语句只支持基本的数值等式检查主要用于根据拥有者 ID 进行范围搜索。由于搜索索引不是关系型数据库因此当前不支持更高级的「where」语句$orders App\Order::search(Star Trek)-where(user_id, 1)-get();引擎在 ElasticsearchEngine.php 的filters方法中处理 where 条件标量值转换为match_phrase查询数组值转换为terms查询并合并进bool查询的must子句。此外Builder还提供一些与查询相关的扩展能力take($limit)限制返回数量orderBy($column, $direction)指定排序字段与方向见 Builder.phpquery(Closure)在将搜索结果映射为模型前有机会修改数据库查询配合withTrashed/onlyTrashed使用。5.3 分页除了检索模型集合你也可以使用paginate方法对搜索结果进行分页。该方法返回一个类似传统模型查询分页的Paginator实例$orders App\Order::search(Star Trek)-paginate();通过将数量作为第一个参数传入paginate可以指定每页检索多少个模型$orders App\Order::search(Star Trek)-paginate(15);获取到检索结果后就可以使用喜欢的模板引擎渲染分页链接显示结果就像传统模型查询分页一样div classcontainer foreach ($orders as $order) {{ $order-price }} endforeach /div {{ $orders-links() }}paginate的实现见 Builder.php它调用引擎的paginate方法Elasticsearch 中通过fromsize实现见 ElasticsearchEngine.php将结果映射为模型集合后包装成LengthAwarePaginator并自动把当前查询词附加为分页链接的query参数保证翻页时搜索关键词不丢失。若需要分页原始数据可使用paginateRaw方法。六、自定义引擎6.1 编写引擎如果内置的 Scout 搜索引擎不能满足需求你可以编写自定义引擎并注册到 Scout。你的引擎需要继承Hyperf\Scout\Engine\Engine抽象类。从仓库源码看见 Engine.php该抽象类包含自定义引擎必须实现的七个抽象方法文档写作时列出的五方法之外还包含mapIds与getTotalCount两个支撑方法use Hyperf\Scout\Builder; use Hyperf\Scout\Engine\Engine; abstract public function update(Collection $models): void; abstract public function delete(Collection $models): void; abstract public function search(Builder $builder); abstract public function paginate(Builder $builder, int $perPage, int $page); abstract public function mapIds($results): BaseCollection; abstract public function map(Builder $builder, $results, Model $model): Collection; abstract public function getTotalCount($results): int; abstract public function flush(Model $model): void;各方法职责如下update将模型集合写入/更新索引delete将模型集合从索引删除search执行搜索并返回原生结果paginate执行分页搜索并返回原生结果mapIds从原生结果中提取主键集合map把原生结果映射为模型集合返回顺序与命中顺序一致getTotalCount从原生结果中取命中总数flush清空该模型对应索引中的全部记录。在Hyperf\Scout\Engine\ElasticsearchEngine类src/scout/src/Engine/ElasticsearchEngine.php里查看这些方法的实现会对你有较大帮助这个类为学习如何在自定义引擎中实现这些方法提供了一个很好的起点。例如其update使用 Elasticsearch 的bulk批量接口并携带doc_as_upsert实现「更新插入」map则按命中顺序重新排序模型集合这些都是值得借鉴的实现细节。6.2 注册引擎编写好自定义引擎后在配置文件中指定即可。举个例子如果你写好了一个MySqlSearchEngine配置文件可以这样写?php return [ default mysql, engine [ mysql [ driver MySqlSearchEngine::class, ], elasticsearch [ driver \Hyperf\Scout\Provider\ElasticsearchProvider::class, ], ], ];引擎装配逻辑位于 EngineFactory.php读取scout.default得到引擎名再取对应engine.name.driver类进行实例化。若该实例实现了Hyperf\Scout\Provider\ProviderInterface见 ProviderInterface.php要求实现make(string $name): Engine则调用其make方法产出真正的引擎实例否则直接返回实例本身。以默认的 Elasticsearch 引擎为例ElasticsearchProvider.php 会借助Hyperf\Elasticsearch组件的ClientBuilderFactory创建客户端使用配置中的hosts并读取index配置后实例化ElasticsearchEngine。这意味着自定义引擎既可以像MySqlSearchEngine那样直接作为驱动类也可以通过实现ProviderInterface提供更复杂的装配逻辑。七、与 laravel/scout 的不同之处协程同步Hyperf/Scout 使用协程来高效同步搜索索引和模型记录无需依赖队列机制。写入请求通过Coroutine::defer在协程结束时异步执行命令行批量导入场景下则使用Concurrent限流并发执行见 Searchable.php配合scout.concurrency配置控制并发度默认引擎Hyperf/Scout 默认提供的是开源的 Elasticsearch 引擎而不是闭源的 Algolia。除上述差异外Scout 保持了与 laravel/scout 一致的 API 设计官方文档中的大部分用法如search、within、paginate、where、withoutSyncingToSearch、searchableAs、toSearchableArray等均可直接迁移到 Hyperf 项目中使用。八、小结Hyperf/Scout 通过「trait 模型观察者 引擎抽象」三层结构把全文检索的复杂度封装在组件内部业务代码只需要在模型上引入Searchabletrait即可获得保存即索引、删除即移除的自动化体验而引擎层提供了清晰的抽象边界让团队可以按需接入 Elasticsearch 之外的其他搜索实现。对于希望以最小成本为 Hyperf 项目引入全文检索能力的开发者而言Scout 是一条值得优先考虑的路径。进一步阅读建议组件源码src/scout/src含引擎、Builder、观察者与命令实现默认配置模板src/scout/publish/scout.php测试用例src/scout/tests/Cases覆盖 Builder、ElasticsearchEngine、ModelObserver 与 Searchable 的核心行为赞分享后端Web框架微服务RPC框架异步编程【免费下载链接】hyperf A coroutine framework that focuses on hyperspeed and flexibility. Building microservice or middleware with ease.项目地址https://gitcode.com/hyperf/hyperf点击查看免费下载相关推荐Hyperf Scout 模型全文搜索实战基于 Elasticsearch 的驱动化搜索方案Hyperf Scout 模型全文搜索实战基于 Elasticsearch 的驱动化搜索方案 本文围绕 Hyperf 框架的官方全文搜索组件 hyperf/s后端微服务Hyperf Scout 模型全文搜索指南基于 Elasticsearch 驱动实现模型索引与检索Hyperf Scout 模型全文搜索指南基于 Elasticsearch 驱动实现模型索引与检索 Hyperf Scout 为 Hyperf 框架提供了一套后端Web框架微服务RPC框架异步编程Hyperf Scout 全文搜索指南为 Hyperf 模型接入 Elasticsearch 驱动的搜索能力Hyperf Scout 全文搜索指南为 Hyperf 模型接入 Elasticsearch 驱动的搜索能力 本指南围绕 Hyperf 框架的 hyperf/后端微服务上一篇从零搭建 BepInEx 6.0 插件框架IL2CPP 构建部署与常见故障完整指南下一篇一站式游戏模组管理XXMI启动器让二次元游戏体验升级10倍创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表