ThinkPHP6+Elasticsearch构建千万级商品搜索系统实战 | 高性能搜索解决方案

2025-08-10 0 917

从架构设计到性能优化的企业级搜索解决方案

一、搜索技术选型对比

主流PHP搜索方案性能测试对比(百万数据量):

方案 查询速度 相关性 扩展性
MySQL LIKE 1200ms+
MySQL全文索引 300-500ms
Sphinx 100-200ms
Elasticsearch 50-100ms 极高

二、系统架构设计

1. 整体架构

数据层 → 索引服务层 → 搜索服务层 → API接口层 → 前端展示
    ↑           ↑              ↑             ↑           ↑
MySQL     数据同步机制    复杂查询处理    RESTful接口    Vue.js
            

2. 数据同步方案

数据库变更 → 消息队列 → 索引处理器 → Elasticsearch
    ↑              ↑            ↑
binlog监听    RabbitMQ      批量写入优化

三、核心模块实现

1. Elasticsearch服务封装

<?php
namespace appcommonservice;

use ElasticElasticsearchClientBuilder;

class ElasticsearchService
{
    private $client;
    
    public function __construct()
    {
        $this->client = ClientBuilder::create()
            ->setHosts(config('es.hosts'))
            ->setRetries(3)
            ->build();
    }
    
    /**
     * 创建商品索引
     */
    public function createProductIndex()
    {
        $params = [
            'index' => 'products',
            'body' => [
                'settings' => [
                    'number_of_shards' => 3,
                    'number_of_replicas' => 1,
                    'analysis' => [
                        'analyzer' => [
                            'ik_smart_pinyin' => [
                                'type' => 'custom',
                                'tokenizer' => 'ik_smart',
                                'filter' => ['pinyin_filter']
                            ]
                        ],
                        'filter' => [
                            'pinyin_filter' => [
                                'type' => 'pinyin',
                                'keep_first_letter' => true,
                                'keep_separate_first_letter' => false
                            ]
                        ]
                    ]
                ],
                'mappings' => [
                    'properties' => [
                        'id' => ['type' => 'integer'],
                        'title' => [
                            'type' => 'text',
                            'analyzer' => 'ik_smart_pinyin',
                            'fields' => [
                                'keyword' => ['type' => 'keyword']
                            ]
                        ],
                        'category_id' => ['type' => 'integer'],
                        'price' => ['type' => 'scaled_float', 'scaling_factor' => 100],
                        'sales' => ['type' => 'integer'],
                        'create_time' => ['type' => 'date']
                    ]
                ]
            ]
        ];
        
        return $this->client->indices()->create($params);
    }
    
    /**
     * 批量索引商品数据
     */
    public function bulkIndexProducts($products)
    {
        $params = ['body' => []];
        
        foreach ($products as $product) {
            $params['body'][] = [
                'index' => [
                    '_index' => 'products',
                    '_id' => $product['id']
                ]
            ];
            
            $params['body'][] = [
                'id' => $product['id'],
                'title' => $product['title'],
                'category_id' => $product['category_id'],
                'price' => $product['price'],
                'sales' => $product['sales'],
                'create_time' => $product['create_time']
            ];
        }
        
        return $this->client->bulk($params);
    }
}

2. 数据同步服务

<?php
namespace appcommand;

use thinkconsoleCommand;
use thinkconsoleInput;
use thinkconsoleOutput;
use appcommonserviceElasticsearchService;

class SyncProducts extends Command
{
    protected function configure()
    {
        $this->setName('sync:products')
            ->setDescription('同步商品数据到Elasticsearch');
    }
    
    protected function execute(Input $input, Output $output)
    {
        $lastId = cache('last_sync_product_id') ?: 0;
        
        $products = appmodelProduct::where('id', '>', $lastId)
            ->limit(1000)
            ->select()
            ->toArray();
            
        if (empty($products)) {
            $output->writeln('没有需要同步的商品数据');
            return;
        }
        
        $esService = new ElasticsearchService();
        $result = $esService->bulkIndexProducts($products);
        
        if ($result['errors']) {
            $output->writeln('同步失败: ' . json_encode($result['items']));
        } else {
            cache('last_sync_product_id', end($products)['id']);
            $output->writeln(sprintf(
                '成功同步 %d 条商品数据,最后ID: %d',
                count($products),
                end($products)['id']
            ));
        }
    }
}

四、高级搜索功能

1. 多条件复合搜索

<?php
class ProductSearchService
{
    public function search($params)
    {
        $must = [];
        $filter = [];
        $should = [];
        
        // 关键词搜索
        if (!empty($params['keyword'])) {
            $must[] = [
                'multi_match' => [
                    'query' => $params['keyword'],
                    'fields' => ['title^3', 'category_name'],
                    'type' => 'best_fields'
                ]
            ];
        }
        
        // 分类筛选
        if (!empty($params['category_id'])) {
            $filter[] = ['term' => ['category_id' => $params['category_id']]];
        }
        
        // 价格区间
        if (!empty($params['min_price']) || !empty($params['max_price'])) {
            $priceRange = [];
            if ($params['min_price']) $priceRange['gte'] = $params['min_price'];
            if ($params['max_price']) $priceRange['lte'] = $params['max_price'];
            $filter[] = ['range' => ['price' => $priceRange]];
        }
        
        // 构建查询DSL
        $query = [
            'bool' => [
                'must' => $must,
                'filter' => $filter,
                'should' => $should,
                'minimum_should_match' => 0
            ]
        ];
        
        // 排序
        $sort = [];
        switch ($params['sort'] ?? 'default') {
            case 'price_asc':
                $sort[] = ['price' => 'asc'];
                break;
            case 'price_desc':
                $sort[] = ['price' => 'desc'];
                break;
            case 'sales':
                $sort[] = ['sales' => 'desc'];
                break;
            default:
                $sort[] = ['_score' => 'desc'];
        }
        
        $searchParams = [
            'index' => 'products',
            'body' => [
                'query' => $query,
                'sort' => $sort,
                'from' => ($params['page'] - 1) * $params['size'],
                'size' => $params['size'],
                'highlight' => [
                    'fields' => [
                        'title' => new stdClass()
                    ]
                ]
            ]
        ];
        
        return $this->client->search($searchParams);
    }
}

2. 搜索建议与纠错

<?php
class SearchSuggestionService
{
    public function getSuggestions($keyword)
    {
        $params = [
            'index' => 'products',
            'body' => [
                'suggest' => [
                    'text' => $keyword,
                    'simple_phrase' => [
                        'phrase' => [
                            'field' => 'title',
                            'size' => 1,
                            'gram_size' => 2,
                            'direct_generator' => [[
                                'field' => 'title',
                                'suggest_mode' => 'always'
                            ]],
                            'highlight' => [
                                'pre_tag' => '<em>',
                                'post_tag' => '</em>'
                            ]
                        ]
                    ],
                    'word_suggest' => [
                        'text' => $keyword,
                        'term' => [
                            'field' => 'title'
                        ]
                    ]
                ]
            ]
        ];
        
        $result = $this->client->search($params);
        
        $suggestions = [];
        if (!empty($result['suggest']['simple_phrase'][0]['options'])) {
            foreach ($result['suggest']['simple_phrase'][0]['options'] as $option) {
                $suggestions[] = $option['highlighted'];
            }
        }
        
        return array_unique($suggestions);
    }
}

五、性能优化策略

1. 索引分片优化

# 商品索引分片策略
PUT /products
{
  "settings": {
    "number_of_shards": 5,  # 根据数据量调整,建议每分片不超过30GB
    "number_of_replicas": 1,
    "refresh_interval": "30s",  # 降低刷新频率提高写入性能
    "index": {
      "max_result_window": 100000  # 允许深度分页
    }
  }
}

# 热数据分离
PUT /products/_settings
{
  "index.routing.allocation.require.box_type": "hot"
}

2. 缓存与预加载

<?php
class CachedSearchService
{
    protected $cachePrefix = 'search_result:';
    protected $cacheTtl = 300; // 5分钟
    
    public function search($params)
    {
        $cacheKey = $this->buildCacheKey($params);
        
        if ($result = cache($cacheKey)) {
            return $result;
        }
        
        $result = $this->elasticsearch->search($params);
        cache($cacheKey, $result, $this->cacheTtl);
        
        // 预加载下一页
        if ($params['page'] == 1) {
            $nextParams = $params;
            $nextParams['page'] = 2;
            $nextKey = $this->buildCacheKey($nextParams);
            $this->preloadNextPage($nextParams, $nextKey);
        }
        
        return $result;
    }
    
    protected function preloadNextPage($params, $key)
    {
        // 异步预加载
        thinkfacadeQueue::push(new appjobPreloadSearchJob([
            'params' => $params,
            'cache_key' => $key,
            'ttl' => $this->cacheTtl
        ]));
    }
}

六、实战案例:电商搜索系统

1. 搜索API接口

<?php
namespace appcontroller;

use thinkRequest;
use appcommonserviceProductSearchService;

class Search extends BaseController
{
    public function index(Request $request)
    {
        $params = $request->only([
            'keyword', 'category_id', 
            'min_price', 'max_price',
            'sort', 'page', 'size'
        ], 'get');
        
        $params['page'] = $params['page'] ?? 1;
        $params['size'] = $params['size'] ?? 10;
        
        try {
            $service = new ProductSearchService();
            $result = $service->search($params);
            
            return json([
                'code' => 200,
                'data' => [
                    'list' => $this->formatHits($result['hits']['hits']),
                    'total' => $result['hits']['total']['value'],
                    'suggestions' => $this->getSuggestions($params['keyword'] ?? '')
                ]
            ]);
        } catch (Exception $e) {
            return json([
                'code' => 500,
                'message' => '搜索服务异常'
            ]);
        }
    }
}

2. 数据看板实现

<?php
class SearchAnalyticsService
{
    public function getHotKeywords($days = 7, $size = 10)
    {
        $params = [
            'index' => 'search_logs',
            'body' => [
                'size' => 0,
                'query' => [
                    'range' => [
                        'create_time' => [
                            'gte' => 'now-' . $days . 'd/d'
                        ]
                    ]
                ],
                'aggs' => [
                    'hot_keywords' => [
                        'terms' => [
                            'field' => 'keyword',
                            'size' => $size,
                            'order' => ['_count' => 'desc']
                        ]
                    ]
                ]
            ]
        ];
        
        $result = $this->client->search($params);
        return array_column($result['aggregations']['hot_keywords']['buckets'], 'key');
    }
    
    public function logSearch($keyword, $userId = null)
    {
        $log = [
            'keyword' => $keyword,
            'user_id' => $userId,
            'create_time' => date('Y-m-d H:i:s')
        ];
        
        $this->client->index([
            'index' => 'search_logs',
            'body' => $log
        ]);
    }
}
ThinkPHP6+Elasticsearch构建千万级商品搜索系统实战 | 高性能搜索解决方案
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

淘吗网 thinkphp ThinkPHP6+Elasticsearch构建千万级商品搜索系统实战 | 高性能搜索解决方案 https://www.taomawang.com/server/thinkphp/789.html

常见问题

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务