一分一毛也是爱

微信

微信

支付宝

支付宝

观海听潮

观海听潮博客

登录
还没有账号?去注册
观海听潮

观海听潮博客

注册
×

我的名片

网名:观海听潮

职业:PHP开发工程师

现居:山东省-青岛市

Email:1256699215@qq.com

网站统计

  • 观海听潮•博客
  • 77篇
  • 149条
  • 119537次
  • 274次
  • 美国弗吉尼亚州

您现在的位置是:首页  > 技术杂谈  > Laravel  > php  > elasticsearch elasticsearch

观海听潮

laravel 引入elasticsearch7.8.1搜索引擎(六)

摘要
本文主要讲针对elasticsearch迁移和更改过程中出现的问题,提供解决方案别名和完整的代码,同时整合了上篇文章的多个命令。

1、索引结果迁移

在涉及到订单索引变更上会出现的问题:

1、在做某些变更时需要先关闭索引,完成变更之后再打开,需要执行很多次命令,操作起来比较麻烦;

2、索引的字段只能添加而不能修改,比如可以给 nested 类型的字段添加新的子字段,而 integer 类型的字段则无法被修改为 string 类型。

在变更迁移的时候关闭索引会使得有很长的一段时间搜索是不能用的,针对这一问题引入别名概念。

2、别名

在 Elasticsearch 中可以给索引指定一个『别名』,对别名的所有操作都会映射到该别名所对应的索引。同时 Elasticsearch 也允许我们修改一个已存在的『别名』,将其指向另外一个索引。

我们可以将订单索引命名为 order_0,然后创建一个名为 order 的别名并指向 order_0,当我们需要修改订单索引的字段时,先尝试直接在 order_0 上修改,如果修改成功则不做任何操作,否则我们用新的结构创建一个新的索引 order_1,然后将订单数据同步到 order_1 中,同步完成后我们将 order 别名修改为指向 order_1,然后删除掉原有的 order_0 索引。

由于我们在代码中的所有操作都是对 order 这个别名进行的,而我们在变更索引的整个过程中 order 始终指向一个可用的索引,这样就实现了 Elasticsearch 索引结构的无缝迁移。

流程图:https://cdn.learnku.com/uploads/images/201808/24/5320/Rfz1I0YPON.png?imageView2/2/w/1240/h/0

3、实现流程

创建索引迁移migrate命令

php artisan make:command Elasticsearch/Migrate

Migrate.php文件内容

<?php
namespace App\Console\Commands\Elasticsearch;

use Illuminate\Console\Command;

class Migrate extends Command
{
    protected $signature = 'es:migrate';
    protected $description = 'Elasticsearch 索引结构迁移';
    protected $es;

    public function __construct()
    {
        parent::__construct();
    }

    public function handle()
    {
        $this->es = app('es');
        // 索引类数组,先留空
        $indices  = [Indices\OrderIndex::class];
        // 遍历索引类数组
        foreach ($indices as $indexClass) {
            // 调用类数组的 getAliasName() 方法来获取索引别名
            $aliasName = $indexClass::getAliasName();
            $this->info('正在处理索引 '.$aliasName);
            // 通过 exists 方法判断这个别名是否存在
            if (!$this->es->indices()->exists(['index' => $aliasName])) {
                $this->info('索引不存在,准备创建');
                $this->createIndex($aliasName, $indexClass);
                $this->info('创建成功,准备初始化数据');
                $indexClass::rebuild($aliasName);
                $this->info('操作成功');
                continue;
            }
            // 如果索引已经存在,那么尝试更新索引,如果更新失败会抛出异常
            try {
                $this->info('索引存在,准备更新');
                $this->updateIndex($aliasName, $indexClass);
            } catch (\Exception $e) {
                $this->warn('更新失败,准备重建');
                $this->reCreateIndex($aliasName, $indexClass);
            }
            $this->info($aliasName.' 操作成功');
        }
    }

    // 创建新索引
    protected function createIndex($aliasName, $indexClass)
    {
        // 调用 create() 方法创建索引
        $this->es->indices()->create([
            // 第一个版本的索引名后缀为 _0
            'index' => $aliasName.'_0',
            'body'  => [
                // 调用索引类的 getSettings() 方法获取索引设置
                'settings' => $indexClass::getSettings(),
                'mappings' => [
                    // 调用索引类的 getProperties() 方法获取索引字段
                    'properties' => $indexClass::getProperties(),
                ],
                'aliases'  => [
                    // 同时创建别名
                    $aliasName => new \stdClass(),
                ],
            ],
        ]);
    }

    // 更新已有索引
    protected function updateIndex($aliasName, $indexClass)
    {
        $indexInfo     = $this->es->indices()->getAlias(['name'=>$aliasName]);
        $indexName = array_keys($indexInfo)[0];
        // 暂时关闭索引
        $this->es->indices()->close(['index' => $indexName]);
        // 更新索引设置
        $this->es->indices()->putSettings([
            'index' => $indexName,
            'body'  => [
                'settings' =>$indexClass::getSettings()
            ]
        ]);
        // 更新索引字段
        $this->es->indices()->putMapping([
            'index' => $indexName,
            'type'=>'_doc',
            'custom'=>['include_type_name'=>true],
            'body'  => [
                '_doc'=>[
                    'properties'=>$indexClass::getProperties()
                ]
            ],
        ]);
        // 重新打开索引
        $this->es->indices()->open(['index' => $indexName]);
    }

    // 重建索引
    protected function reCreateIndex($aliasName, $indexClass)
    {
        // 获取索引信息,返回结构的 key 为索引名称,value 为别名
        $indexInfo     = $this->es->indices()->getAlias(['name'=>$aliasName]);
        // 取出第一个 key 即为索引名称
        $indexName = array_keys($indexInfo)[0];
        // 用正则判断索引名称是否以 _数字 结尾
        if (!preg_match('~_(\d+)$~', $indexName, $m)) {
            $msg = '索引名称不正确:'.$indexName;
            $this->error($msg);
            throw new \Exception($msg);
        }
        // 新的索引名称
        $newIndexName = $aliasName.'_'.($m[1] + 1);
        $this->info('正在创建索引'.$newIndexName);
        $this->es->indices()->create([
            'index' => $newIndexName,
            'body'  => [
                'settings' => $indexClass::getSettings(),
                'mappings' => [
                    'properties' => $indexClass::getProperties(),
                ],
            ],
        ]);
        $this->info('重建成功,准备修改别名');
        $this->es->indices()->putAlias(['index' => $newIndexName, 'name' => $aliasName]);
        $this->info('修改成功,准备删除旧索引');
        $this->es->indices()->delete(['index' => $indexName]);
        $this->info('创建成功,准备重建数据');
        $this->info('创建成功,'.$aliasName);
        $indexClass::rebuild($aliasName);
    }
}

创建索引类,执行命令

mkdir -p app/Console/Commands/Elasticsearch/Indices/
touch app/Console/Commands/Elasticsearch/Indices/OrderIndex.php

OrderIndex.php文件内容

<?php
namespace App\Console\Commands\Elasticsearch\Indices;
use Illuminate\Support\Facades\Artisan;
class OrderIndex
{
    public static function getAliasName()
    {
        return 'orders';
    }

    public static function getProperties()
    {
        return [
            'id'=>['type'=>'integer'],
            'order_no'=>['type'=>'keyword'],
            'order_amount'=>['type'=>'scaled_float','scaling_factor'=>100],
            'order_status'=>['type'=>'byte'],
            'created_at'=>['type'=>'keyword'],
            'user_name'=>['type'=>'text',"analyzer"=>"ik_max_word", "search_analyzer"=>"ik_smart_synonym"],
            'consignee'=>['type'=>'text',"analyzer"=>"ik_max_word", "search_analyzer"=>"ik_smart_synonym"],
            'store_name'=>['type'=>'text',"analyzer"=>"ik_max_word", "search_analyzer"=>"ik_smart_synonym"],
            'addresses'=>['type'=>'text',"analyzer"=>"ik_max_word", "search_analyzer"=>"ik_smart_synonym"],
            'goods'=>[
                'type'=>'nested',
                'properties'=>[
                    'goods_name'=>['type'=>'text',"analyzer"=>"ik_max_word", "search_analyzer"=>"ik_smart_synonym",'copy_to'=>'goodsName'],
                    'goods_spec'=>['type'=>'text',"analyzer"=>"ik_max_word", "search_analyzer"=>"ik_smart_synonym",'copy_to'=>'goodsSpec'],
                    'goods_price'=>['type'=>'scaled_float','scaling_factor'=>100]
                ]
            ],
            'properties'=>[
                'type'=>'nested',
                'properties'=>[
                    'name'=>['type'=>'keyword','copy_to'=>'properties_name'],
                    'value'=>['type'=>'keyword','copy_to'=>'properties_value'],
                    'search_value'=>['type'=>'keyword']
                ]
            ]
        ];
    }

    public static function getSettings()
    {
        return [
            'number_of_replicas' => 0,
          //  'refresh_interval' => - 1,//-1为不刷新索引
            'analysis' => [
                'analyzer' => [
                    'ik_smart_synonym' => [
                        'type'      => 'custom',
                        'tokenizer' => 'ik_max_word',
                        'filter'    => ['synonym_filter'],
                    ],
                ],
                'filter'   => [
                    'synonym_filter' => [
                        'type'          => 'synonym',
                        'synonyms_path' => 'analysis/synonyms.txt',
                    ],
                ],
            ],
        ];
    }

    public static function rebuild($indexName)
    {
        Artisan::call('es:sync-data', ['index' => $indexName]);
    }
}

创建同步数据的命令,执行命令

php artisan make:command Elasticsearch/SyncData

SyncData.php文件内容

<?php
namespace App\Console\Commands\Elasticsearch;

use App\Models\Order;
use Illuminate\Console\Command;

class SyncOrders extends Command
{
    protected $signature = 'es:sync-data {index}';

    protected $description = '将数据同步到 Elasticsearch';
    protected $es;
    public function __construct()
    {
        parent::__construct();
    }

    public function handle()
    {
        // 获取 Elasticsearch 对象
        $this->es = app('es');
        $index = $this->argument('index');
        switch ($index){
            case 'orders':
                $this->syncOrders($this->es);
                break;
            case 'products':
                $this->syncProducts($this->es);
                break;
            default :
                $this->info('未知的索引类型');
                break;
        }
        $this->info('同步完成');
    }

    public function syncOrders($es){
        Order::with(['goods'])
        // 使用 chunkById 避免一次性加载过多数据
        ->chunkById(100, function ($orders) use ($es) {
            $this->info(sprintf('正在同步 ID 范围为 %s 至 %s 的订单', $orders->first()->id, $orders->last()->id));
            // 初始化请求体
            $req = ['body' => []];
            // 遍历订单
            foreach ($orders as $order) {
                $data = $order->toESArray();
                $req['body'][] = [
                    'index' => [
                        '_index' => 'orders',
                        '_id'    => $data['id'],
                    ],
                ];
                $req['body'][] = $data;
            }
            try {
                // 使用 bulk 方法批量创建
                $es->bulk($req);
            } catch (\Exception $e) {
                $this->error($e->getMessage());
            }
        });
    }
}

4、测试命令

php artisan es:migrate

截图.png

查看kibanba,已经创建好了索引order_0,并且别名执行了orders

截图 (1).png

修改索引字段,重新运行命令

将mapping中id的类型从integer改完keyword,重新执行命令

php artisan es:migrate

截图 (2).png

查看kibanba,已经创建了orders_1索引,之前的orders_0已经被删除

截图 (3).png

讨厌 (0)
微博logo QQ空间logo QQlogo 豆瓣logo 人人logo 百度贴吧logo 有道云笔记logo

文章评论

表情表情
×
图片图片

评论列表