avatar

mdo

Hello

  • 首页
  • 知识库
  • 归档
  • 标签
  • 关于
主页 Manticore Search
文章

Manticore Search

发表于 2026-08-19 更新于 2026-08- 19
作者 mdo
39~51 分钟 阅读

在 PHP ThinkPHP 6 (简称 TP6) 框架中使用 Manticore Search,最推荐、最现代化的方式是使用官方提供的 Manticore PHP 客户端(基于 PSR-18 规范,支持 JSON Over HTTP 接口),或者利用 TP6 的数据库连接池通过 MySQL 协议直接查询。

以下为您梳理两种在 TP6 中集成和使用 Manticore Search 的主流方案。


方案一:使用官方扩展包(推荐,支持向量搜索与新特性)

官方的 manticoresearch/manticoresearch-php 扩展包功能最全,能够完美适配 Manticore 的全文检索、向量搜索(KNN)以及最新的各种复杂 JSON 查询。

1. 安装官方扩展包

在你的 TP6 项目根目录下,通过 Composer 安装: [1]

bash

composer require manticoresearch/manticoresearch-php

请谨慎使用此类代码。

2. 在 TP6 中封装单例服务(Service)

为了方便在控制器(Controller)或模型(Model)中复用,建议将其注册为 TP6 的服务或创建公共类。

创建文件 app/common/service/Manticore.php:

php

<?php
namespace app\common\service;

use ManticoreSearch\Client;

class Manticore
{
    protected static $client = null;

    public static function getClient()
    {
        if (self::$client === null) {
            // 读取 TP6 config 目录下的自定义配置文件,或直接写死
            $config = [
                'host' => '127.0.0.1',
                'port' => 9308 // Manticore 默认 HTTP 端口
            ];
            self::$client = new Client($config);
        }
        return self::$client;
    }
}

请谨慎使用此类代码。

3. 在控制器中进行增删改查

在控制器中,你可以直接调用官方客户端的方法:

php

<?php
namespace app\controller;

use app\BaseController;
use app\common\service\Manticore;

class Search extends BaseController
{
    public function index()
    {
        $client = Manticore::getClient();
        $index = $client->index('products'); // 假设你有一个名为 products 的索引

        // 1. 添加文档
        $index->addDocument([
            'title' => '搭载 M4 芯片的全新 MacBook Pro',
            'price' => 12999,
            'tags'  => ['apple', 'laptop']
        ], 101); // 101 为文档 ID

        // 2. 全文检索
        $results = $index->search('MacBook')->get();

        // 3. 循环输出结果
        $data = [];
        foreach ($results as $hit) {
            $data[] = [
                'id'    => $hit->getId(),
                'title' => $hit->title,
                'price' => $hit->price,
            ];
        }

        return json(['code' => 200, 'data' => $data]);
    }
}

请谨慎使用此类代码。


方案二:通过 MySQL 协议接入(无需安装额外包,零门槛)

Manticore Search 原生支持 MySQL 协议(默认端口 9306)。你可以直接把 Manticore 当作一个普通的 MySQL 数据库,在 TP6 的配置文件中进行配置,然后使用 TP6 原生的 Db 门面(Facade)执行原生 SQL。

1. 配置数据库连接

打开 TP6 的数据库配置文件 config/database.php,在 connections 数组中追加 Manticore 的连接配置:

php

return [
    'default' => 'mysql',
    'connections' => [
        'mysql' => [ /* 你的主数据库配置 */ ],
        
        // 追加 Manticore 配置
        'manticore' => [
            'type'            => 'mysql',
            'hostname'        => '127.0.0.1',
            'database'        => '', // Manticore 采用扁平索引,留空即可
            'username'        => 'root', // 默认不需要账号密码,若设置了请填写
            'password'        => '',
            'hostport'        => '9306', // 注意:这里是 Manticore 的 MySQL 协议端口
            'charset'         => 'utf8mb4',
            'prefix'          => '',
        ],
    ],
];

请谨慎使用此类代码。

2. 在控制器中通过原生 SQL 查询

由于 Manticore 使用的是 SphinxQL(与标准 SQL 高度相似),你可以直接在 TP6 中使用 Db::connect('manticore') 执行查询:

php

<?php
namespace app\controller;

use app\BaseController;
use think\facade\Db;

class Search extends BaseController
{
    public function searchBySql()
    {
        $keyword = input('keyword', 'MacBook');

        // 使用 MATCH 进行高性能全文检索
        // 注意:Manticore 的全文检索必须使用 MATCH('...') 语法
        $sql = "SELECT id, title, price FROM products WHERE MATCH(:keyword) ORDER BY price DESC LIMIT 0, 20";
        
        $results = Db::connect('manticore')->query($sql, [
            'keyword' => $keyword
        ]);

        return json(['code' => 200, 'data' => $results]);
    }

    public function insertBySql()
    {
        // 插入数据示例
        $sql = "INSERT INTO products(id, title, price) VALUES(102, 'iPhone 17 Pro Max', 9999)";
        Db::connect('manticore')->execute($sql);

        return json(['code' => 200, 'msg' => '插入成功']);
    }
}

请谨慎使用此类代码。


💡 核心开发建议

  1. 写操作路由:如果是海量日志或高频写入,推荐使用方案一。因为官方 PHP SDK 的 HTTP 协议在高并发批量写入(Bulk Insert)时,比频繁创建和断开 MySQL 链接更稳定且效率更高。

  2. 读操作与高亮显示:如果你需要对搜索关键词进行标签高亮(Highlighting),Manticore 支持在 SQL 中使用 HIGHLIGHT() 函数。例如:

    sql

    SELECT id, HIGHLIGHT() FROM products WHERE MATCH('MacBook')
    

    请谨慎使用此类代码。

    你可以很方便地在方案二的原生查询中直接使用。

  3. 中文分词:请确保你的 Manticore 实例在创建 products 索引(Table)时配置了中文字符集和分词器(如内置的 jieba 分词),否则中文匹配精度会大幅下降。

您目前的项目里,搜索场景主要是海量商品/文章的检索,还是需要做诸如 Log 日志分析或 AI 向量检索?可以告诉我具体的业务需求,我为您提供更针对性的代码设计建议。

Log 日志分析

针对 Log 日志分析 场景,Manticore Search 是一个极佳的轻量级、高性能方案,特别适合替代笨重的 ELK(Elasticsearch)架构。在 ThinkPHP 6(TP6)中实现日志分析,核心逻辑是:“异步/批量写入 + 列式存储聚合查询”。

为了确保在高并发日志写入下 TP6 不会卡死,同时能在前端实现毫秒级的日志检索与聚合分析,下面为您提供一套完整的架构设计与核心代码实现。


🏗️ 1. Manticore 表结构设计(推荐使用列式存储)

在 Manticore 中,日志分析场景强烈推荐使用 列式存储(Columnar Storage)。它可以让类似 GROUP BY、AVG、COUNT 的聚合统计速度提升数十倍,且极大地节省内存。

请先在 Manticore 中创建日志表(可以通过 MySQL 客户端执行):

sql

CREATE TABLE app_logs (
    id bigint,
    timestamp timestamp,
    level string attribute,
    env string attribute,
    module string attribute,
    message text,
    ip string attribute,
    execution_time float attribute
) engine='columnar';  -- 声明使用列式存储引擎

请谨慎使用此类代码。

注:message 作为 text 类型用于全文检索(查关键词、堆栈信息),其他字段作为 attribute(属性)用于过滤和聚合统计。


📥 2. 日志写入:TP6 自定义日志驱动(异步/批量)

高并发下,如果每产生一条日志就同步写入 Manticore,会严重拖慢 PHP 的响应速度。建议使用 TP6 的 自定义日志驱动,在请求结束时批量(Bulk)写入 Manticore。

步骤一:创建自定义日志驱动类

新建 app/common/log/driver/Manticore.php:

php

<?php
namespace app\common\log\driver;

use ManticoreSearch\Client;
use think\contract\LogHandlerInterface;

class Manticore implements LogHandlerInterface
{
    protected $client;
    protected $index = 'app_logs';

    public function __construct()
    {
        // 初始化 Manticore 官方客户端
        $this->client = new Client([
            'host' => '127.0.0.1',
            'port' => 9308 // HTTP 端口,批量写入性能更好
        ]);
    }

    public function save(array $log): bool
    {
        $docs = [];
        // 雪花算法生成唯一ID(Manticore 的 ID 必须是正整数)
        $snowflake = new \think\Id(); 

        foreach ($log as $type => $messages) {
            foreach ($messages as $msg) {
                // 格式化或解析你的日志内容,这里假设日志是规范的字符串或数组
                // 实际生产中可以解析出当前请求的 IP、执行时间等
                $docs[] = [
                    'insert' => [
                        'index' => $this->index,
                        'id'    => (int)$snowflake->id(),
                        'doc'   => [
                            'timestamp'      => time(),
                            'level'          => $type,
                            'env'            => app()->env('app_env', 'prod'),
                            'module'         => app('http')->getName(), // 获取当前多应用/模块名
                            'message'        => is_string($msg) ? $msg : json_encode($msg, JSON_UNESCAPED_UNICODE),
                            'ip'             => request()->ip(),
                            'execution_time' => (float)number_format(microtime(true) - app()->getStartTime(), 4)
                        ]
                    ]
                ];
            }
        }

        if (!empty($docs)) {
            try {
                // 使用 Manticore 的 Bulk 接口进行批量高效写入
                $this->client->bulk(['body' => $docs]);
            } catch (\Exception $e) {
                // 降级处理:若 Manticore 异常,写入本地文件防止日志丢失
                error_log($e->getMessage(), 3, runtime_path() . 'manticore_error.log');
                return false;
            }
        }

        return true;
    }
}

请谨慎使用此类代码。

步骤二:修改 TP6 日志配置文件

打开 config/log.php,将通道切换或追加为 manticore:

php

return [
    'default'      => 'manticore',
    'channels'     => [
        'file' => [ /* 原有的文件日志配置 */ ],
        
        // 追加 Manticore 通道
        'manticore' => [
            'type' => \app\common\log\driver\Manticore::class,
        ],
    ],
];

请谨慎使用此类代码。

配置完成后,你在 TP6 中使用的 Log::error('用户登录失败') 或 trace('SQL执行超时', 'sql') 都会自动批量流入 Manticore。


📊 3. 日志分析与检索:控制器实现

日志收集进来后,我们需要在后台进行两类操作:关键词全文检索(排查 Bug) 和 多维聚合统计(监控大盘)。由于查询不涉及高频并发,直接使用 SQL 协议(方案二) 编写代码最直观、最易读。

新建日志分析控制器 app/controller/LogAnalyzer.php:

php

<?php
namespace app\controller;

use app\BaseController;
use think\facade\Db;

class LogAnalyzer extends BaseController
{
    /**
     * 1. 日志高级检索(支持关键词全文检索、级别过滤、时间范围)
     */
    public function search()
    {
        $keyword = input('keyword', '');   // 搜索关键词,如 "NullPointerException"
        $level   = input('level', '');     // 日志级别,如 "error"
        $startTime = input('start_time', time() - 86400); // 默认查24小时内
        $endTime   = input('end_time', time());
        $page      = input('page/d', 1);
        $limit     = input('limit/d', 20);
        $offset    = ($page - 1) * $limit;

        // 基础 SQL
        $where = ["timestamp BETWEEN :start AND :end"];
        $params = ['start' => $startTime, 'end' => $endTime];

        // 全文检索必须使用 MATCH
        if (!empty($keyword)) {
            $where[] = "MATCH(:keyword)";
            $params['keyword'] = $keyword;
        }

        // 属性过滤直接用 =
        if (!empty($level)) {
            $where[] = "level = :level";
            $params['level'] = $level;
        }

        $whereSql = implode(' AND ', $where);

        // 执行查询(带上高亮函数 HIGHLIGHT(),方便前端查看关键词)
        $sql = "SELECT id, timestamp, level, module, ip, execution_time, HIGHLIGHT() as message 
                FROM app_logs 
                WHERE {$whereSql} 
                ORDER BY timestamp DESC 
                LIMIT {$offset}, {$limit}";

        $list = Db::connect('manticore')->query($sql, $params);

        // 获取总数(Manticore 推荐使用 SHOW META 查看上一次查询的命中总数,比 COUNT(*) 快得多)
        $meta = Db::connect('manticore')->query("SHOW META");
        $total = 0;
        foreach ($meta as $row) {
            if ($row['Variable_name'] === 'total_found') {
                $total = (int)$row['Value'];
                break;
            }
        }

        return json([
            'code'  => 200,
            'data'  => $list,
            'total' => $total,
            'page'  => $page
        ]);
    }

    /**
     * 2. 聚合统计:获取各错误级别的日志数量统计(类似 Kibana 饼图数据)
     */
    public function getLevelStats()
    {
        // 得益于 engine='columnar',以下聚合操作在千万级数据下也是毫秒级响应
        $sql = "SELECT level, COUNT(*) as count FROM app_logs GROUP BY level ORDER BY count DESC";
        $stats = Db::connect('manticore')->query($sql);

        return json(['code' => 200, 'data' => $stats]);
    }

    /**
     * 3. 性能监控:统计各模块的平均接口响应时间(类似 APM 线图数据)
     */
    public function getModulePerformance()
    {
        $sql = "SELECT module, AVG(execution_time) as avg_time, MAX(execution_time) as max_time 
                FROM app_logs 
                GROUP BY module 
                ORDER BY avg_time DESC";
        $performance = Db::connect('manticore')->query($sql);

        return json(['code' => 200, 'data' => $performance]);
    }
}

请谨慎使用此类代码。


💡 生产环境避坑与优化指南

  1. ID 唯一性约束:Manticore 的文档 id 必须是唯一且大于 0 的整数(32位或64位)。TP6 默认的自增 ID 如果跨服务器会冲突,因此代码中推荐引入 think-id 扩展包使用雪花算法(Snowflake)或者通过 Redis 生成全局唯一递增 ID。

  2. 最大结果集限制(max_matches):Manticore 为了保护内存,默认单次查询最多只能翻页到第 1000 条(max_matches = 1000)。如果你的日志运营后台需要翻到很深的页码,需要在 Manticore 的配置文件 manticore.conf 中调大该参数,或者在 SQL 中动态设置:

    sql

    SELECT * FROM app_logs WHERE ... OPTION max_matches=50000;
    

    请谨慎使用此类代码。

  3. 日志定期清理(TTL):日志数据具有时效性。你可以写一个 TP6 的定时任务(Command),每天凌晨执行原生 SQL 清理 30 天前的旧日志,保持 Manticore 轻量运行:

    php

    $timeLimit = time() - (86400 * 30);
    Db::connect('manticore')->execute("DELETE FROM app_logs WHERE timestamp < {$timeLimit}");
    

    请谨慎使用此类代码。

您目前每天的日志产出量大概在什么级别(例如 十万级、千万级、还是亿级)?如果数据量极大,我们可以进一步探讨 Manticore 的实时分布式分片(Distributed Table)配置。

知识库
许可协议:  CC BY 4.0
分享

相关文章

10月 7, 2026

比尔盖茨的智慧:想多赚钱,就每天循环做这3件事

我们总有一种非常固执的错觉,认为那些站在财富金字塔顶端的人,必然拥有某种超越常人的特异功能。我们在脑海中给比尔盖茨这样的大佬描绘了一幅极其悲壮的奋斗画像。大家总觉得,他之所以能富可敌国,肯定是因为他每天只睡三个小时,同时对着八个电脑屏幕疯狂敲击键盘,每一秒钟都在做出价值几亿美金的生死抉择。 为了模仿

9月 28, 2026

程序员越想创业,越不要急着动手

一个能源方面的前辈找到我,希望通过我把一些人工的工作 AI 自动化。 能源方面我不懂,找 Gemini 聊完发现这个是可以复制的,非常兴奋。我跟老婆说,这个项目做好以后可以做成平台,推广到其他公司,你就等着做总裁夫人吧! 她听完以后跟我说,这个项目还是太定制化,和我之前做的一个项目很像。 那个项目一

9月 25, 2026

忍了一年多,我终于对i18n下手了过去一年,我主要参与国际机票业务的开发工作,因此每天都要和多语言(i18n)打交道

前言 大家好,我是奈德丽。 过去一年,我主要参与国际机票业务的开发工作,因此每天都要和多语言(i18n)打交道。熟悉我的朋友都知道,我这个人比较“惜力”(并不是,实际上只是忍不下去了),对于重复笨拙的工作非常抵触,于是,我开始思考如何优化团队的多语言管理模式。 痛点背景 先说说我们在机票项目中遇到的

下一篇

k6 PHP API 并发测试

上一篇

接收 astrbot_plugin_push_lite 状态回执

最近更新

  • 比尔盖茨的智慧:想多赚钱,就每天循环做这3件事
  • iOS 侧载(Sideloading)工具
  • 程序员越想创业,越不要急着动手
  • 注册 Chat Participant
  • 中秋给在外游子的一封信

热门标签

API CodeGeex Coding Cursor DeepSeek Docker Gitkraken Harness Laravel Management

目录

©2026 mdo. 保留部分权利。