基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试

奋斗吧
奋斗吧
擅长邻域:未填写

标签: 基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试 Python博客 51CTO博客

2023-07-05 18:24:07 190浏览

基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试,基于centos7搭建laravel+scout+elasticsearch+ik-analyzer用于中文分词全文检索服务及测试


目录

  • 基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试
  • 相关软件及版本
  • 安装或升级jdk(版本:19.0.2)
  • 安装es(版本:8.1.1)
  • 安装ik-analyzer(版本:8.1.1)
  • laravel 7框架安装laravel-scout-elastic包
  • 在laravel中使用es进行中文分词及查询
  • 代码优化
  • 方案一
  • 方案二
  • 异常问题

基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试

浏览该文章,建议先食用 异常问题 这一节

相关软件及版本

软件/框架

版本

jdk

19.0.2

elasticsearch

8.1.1

ik-analyzer

8.1.1

laravel

7.x-dev

elasticsearch/elasticsearch

7.17.1

tamayo/laravel-scout-elastic

8.0.3

安装或升级jdk(版本:19.0.2)

下载:wget https://download.oracle.com/java/19/latest/jdk-19_linux-x64_bin.rpm
安装:rpm -ivh jdk-19_linux-x64_bin.rpm

  • 查看版本

java -version

安装es(版本:8.1.1)

下载:wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.1.1-x86_64.rpm
安装:rpm -ivh elasticsearch-8.1.1-x86_64.rpm

  • 编辑配置项

vim /etc/elasticsearch/elasticsearch.yml
直接从最后一行添加如下内容:

#bootstrap.memory_lock: true
#network.host: localhost
http.port: 9200

bootstrap.memory_lock: false
network.host: 0.0.0.0
discovery.seed_hosts: ["127.0.0.1"]
cluster.initial_master_nodes: ["node-1"]

xpack.security.enabled: false
#xpack.security.http.ssl:
#  enabled: false
#  keystore.path: certs/http.p12

#解决[.geoip_databases]  index are active  问题
ingest.geoip.downloader.enabled: false
##允许跨域
http.cors.enabled: true
http.cors.allow-origin: "*"

保存并退出。

  • 启动es服务
设置开机启动:systemctl enable elasticsearch
启动服务:systemctl start elasticsearch
提示错误:

基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试_elasticsearch

问题描述:内存空间不足


解决方案:


vim /etc/elasticsearch/jvm.options


找到-Xms4g 和 -Xmx4g 打开注释并修改为:


-Xms512m


-Xmx512m


保存并退出。


重新启动es服务:


systemctl restart elasticsearch


提示错误:

基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试_laravel-scout_02

问题描述:不能使用root用户,所以需要创建es专用用户和组
解决方案:
创建用户组:
groupadd elasticsearch
创建用户及所属组:
useradd -g elasticsearch elasticsearch
设置密码:
elasticsearch
设置 /opt/software/install/elasticsearch-8.6.2 目录权限为elasticsearch用户和组拥有:
chown -R elasticsearch:elasticsearch /opt/software/install/elasticsearch-8.6.2
切换到 elasticsearch 用户:
su elasticsearch
再次启动es服务:
systemctl restart elasticsearch
提示错误:

基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试_elasticsearch_03


问题描述:exception during geoip databases updateorg.elasticsearch.ElasticsearchException: not all primary shards of [.geoip_databases] index are active
解决方案:
vim /opt/software/install/elasticsearch-8.6.2/config/elasticsearch.yml
在文件夹的最后一行添加:
#解决[.geoip_databases] index are active 问题
ingest.geoip.downloader.enabled: false
#允许跨域
http.cors.enabled: true
http.cors.allow-origin: “*”
再次启动es服务:
systemctl restart elasticsearch
不再提示错误,表示启动成功:

基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试_elasticsearch_04

执行命令:


curl localhost:9200


基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试_elasticsearch_05


说明es安装成功。


浏览器访问:ip:9200,也能得到上图结果:


基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试_laravel-scout_06

  • 如果用的是云服务器(如阿里云),需要配置安全组开放9200端口。
  • 如果服务器开启了防火墙,需要开放9200端口。

安装ik-analyzer(版本:8.1.1)

  • 下载传送门注意:为避免出现问题,版本需要和es完全一致。
  • 安装

/usr/share/elasticsearch/bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v8.1.1/elasticsearch-analysis-ik-8.1.1.zip

  • 重启es

systemctl restart elasticsearch

  • 测试分词效果
curl -H ‘Content-Type: application/json’ -XGET ‘localhost:9200/_analyze?pretty’ -d ‘{“analyzer”:“ik_max_word”,“text”:“张三丰创建了武当派”}’

基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试_laravel-scout_07


可以看到 分词效果还是不错的,但是“张三丰”这个名字却被分成了3个,还好强大的 analysis-ik 支持自定义词库,增加自定义词库:


vim /etc/elasticsearch/analysis-ik/IKAnalyzer.cfg.xml


基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试_laravel-scout_08


增加一个自定义词库,并向其中导入自定义内容,如通过名利写入:


echo ‘张三丰’ > /etc/elasticsearch/analysis-ik/custom.dic


再看下分词效果,明显好转:


基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试_laravel_09

laravel 7框架安装laravel-scout-elastic包

  • 安装
composer.json文件中require对象加入elasticsearch包和laravel-scout-elastic

基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试_中文分词全文检索_10


执行 composer update 完成相关包下载。

  • 在config/app.php 的 providers 数组中添加:

\Laravel\Scout\ScoutServiceProvider::class,
\Tamayo\LaravelScoutElastic\LaravelScoutElasticProvider::class,

  • 执行命令:

php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider
config目录会生成一个scout.php配置文件。

  • 修改scout.php配置文件
'driver' => env('SCOUT_DRIVER', 'elasticsearch'),
	
 //在最后添加
 //配置elasticsearch引擎
 'elasticsearch' => [
      'index' => env('ELASTICSEARCH_INDEX', 'laravel'),//laravel就是索引的名字,可以随便起
      'hosts' => [
          env('ELASTICSEARCH_HOST', 'http://127.0.0.1:9200'),
      ],
  ]
  • env配置如下:
#elasticsearch
SCOUT_DRIVER=elasticsearch
ELASTICSEARCH_INDEX=laravel7
ELASTICSEARCH_HOST=http://127.0.0.1:9200
ELASTIC_CLIENT_APIVERSIONING=1

在laravel中使用es进行中文分词及查询

  • 自动更新索引

执行命令:php artisan make:command ESInit

  • 在 app\Console\Kernel.php 里增加ESInit类
protected $commands = [
	\App\Console\Commands\ESInit::class
];
  • 编写ESInit类模板内容,完整代码如下:
<?php

namespace App\Console\Commands;

use GuzzleHttp\Client;
use Illuminate\Console\Command;

class ESInit extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'es:init';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'init laravel es for post';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        $client = new Client();
        // 创建模版
        $url = config('scout.elasticsearch.hosts')[0] . '/_template/tmp';
        try {
            $client->delete($url);
        } catch (\Exception $e) {
            $this->info("===delete模版出现错误===" . $e->getMessage());
        }

        /*
         * 这个模板作用于我要做用的索引
         * */
        $param = [
            'json' => [
                /*
                 * 这句是取在scout.php(scout是驱动)里我们配置好elasticsearch引擎的
                 * index项。
                 * PS:其实都是取数组项,scout本身就是return一个数组,
                 * scout.elasticsearch.index就是取
                 * scout[elasticsearch][index]
                 * */
                'template' => config('scout.elasticsearch.index'),
                'mappings' => [
                    '_default_' => [
                        'dynamic_templates' => [
                            [
                                'string' => [
                                    'match_mapping_type' => 'string',//传进来的是string
                                    'mapping' => [
                                        'type' => 'text',//把传进来的string按text(文本)处理
                                        'analyzer' => 'ik_smart',//用ik_smart进行解析(ik是专门解析中的插件)
                                        'fields' => [
                                            'keyword' => [
                                                'type' => 'keyword'
                                            ]
                                        ]
                                    ]
                                ]
                            ]
                        ]
                    ]
                ],
            ],
        ];
        try {
            $client->put($url, $param);
        } catch (\Exception $e) {
            $this->info("===put模版出现错误===" . $e->getMessage());
        }

        $this->info('============create template success============');

        //创建index
        $url = config('scout.elasticsearch.hosts')[0] . '/' . config('scout.elasticsearch.index');
        try {
            $client->delete($url);
        } catch (\Exception $e) {
            $this->info("===delete索引出现错误===" . $e->getMessage());
        }

        $param = [
            'json' => [
                'settings' => [
                    'refresh_interval' => '5s',
                    'number_of_shards' => 1,
                    'number_of_replicas' => 0,
                ],

                'mappings' => [
                    '_default_' => [
                        '_all' => [
                            'enabled' => false
                        ]
                    ]
                ]
            ]
        ];

        try {
            $client->put($url, $param);
        } catch (\Exception $e) {
            $this->info("===put索引出现错误===" . $e->getMessage());
        }
        $this->info('============create index success============');
    }
}
  • 启动 ES自动更新索引服务

php artisan es:init

  • 修改你要搜索的 model,以 Project 为例:
模型类 Project 引入 Searchable 工具类:use Searchable;

基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试_laravel_11

  • 模型类 Project 中重写 searchableAs () 方法 toSearchableArray () 方法
/**
     * @return string
     */
    public function searchableAs() {
        return $this->getTable();
    }

    /**
     * 索引的字段
     * @return array
     */
    public function toSearchableArray() {
        return [
            'project_name' => $this->project_name,
            'nick_name' => $this->nick_name,
        ];
    }

基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试_laravel-scout_12

  • 导入数据处理:
php artisan scout:import “App\Models\Project”

基于 centos7 搭建 laravel+scout+elasticsearch+ik-analyzer 用于中文分词全文检索服务及测试_laravel-scout_13

出现上图情况,表示导入数据成功。

  • 编写查询代码
  • 路由
//es测试
$route->get('es_project','ProjectController@esProject')->name('api_v1_esProject');
  • 控制器
public function esProject(Request $request)
{
    return $this->success(
        $this->homeProjectServ->esProjectServ($request->all())
    );
}
  • 服务层
public function esProjectServ(array $params)
{
    $searchWord = $params['search_word'] ?? '义厂';
    //DB::enableQueryLog();
    $list = $this->model->search($searchWord)->get()->toArray();
    //dd($searchWord, DB::getQueryLog());

    return ['list' => $list];
}
  • postman调用测试结果:
    发现没查询到想要的结果,多方查找,终于找到问题所在,这篇文章 给出了答案,在文件 vendor/tamayo/laravel-scout-elastic/src/Engines/ElasticsearchEngine.php 中,第134行的前后两个 * 导致查询结果不达预期(可能是作者未考虑中文分词)。那么,找到问题就容易解决了,直接把前后 * 删掉即可
    删除前:

    删除后:

    再次查询结果如下: 至此,es 安装、ik中文分词、在 laravel 中使用已经完成。

代码优化

上面直接修改了 composer 下载的vendor包里的代码,是不优雅的,且在 composer install 或 composer update 后会被覆盖,那么有没有更好的处理方案呢?答案是有的,下面给出两种解决方案。两种方案都是基于重写包里对应方法的思想,方案一是通过AppServiceProvider的boot方法调用实现;方案二是通过自定义 provider 实现,下面是具体实现。

方案一

  • app/Libraries 目录下新增目录:CustomScoutElastic
  • 创建类文件:app/Libraries/CustomScoutElastic/CustomElasticsearchEngine.php
    该类为了重写 vendor 包内 vendor/tamayo/laravel-scout-elastic/src/Engines/ElasticsearchEngine.php 类内的 performSearch 方法。
<?php

namespace App\Libraries\CustomScoutElastic;

use Laravel\Scout\Builder;
use Tamayo\LaravelScoutElastic\Engines\ElasticsearchEngine;

/**
 * 自定义es引擎,重写 performSearch 方法,解决134行(本文件28行)*导致跨字符查询无结果的问题
 */
class CustomElasticsearchEngine extends ElasticsearchEngine
{
    /**
     * Perform the given search on the engine.
     *
     * @param  Builder  $builder
     * @param  array  $options
     * @return mixed
     */
    protected function performSearch(Builder $builder, array $options = [])
    {
        $params = [
            'index' => $builder->model->searchableAs(),
            'type' => get_class($builder->model),
            'body' => [
                'query' => [
                    'bool' => [
                        'must' => [['query_string' => ['query' => "{$builder->query}"]]]
                    ]
                ]
            ]
        ];

        if ($sort = $this->sort($builder)) {
            $params['body']['sort'] = $sort;
        }

        if (isset($options['from'])) {
            $params['body']['from'] = $options['from'];
        }

        if (isset($options['size'])) {
            $params['body']['size'] = $options['size'];
        }

        if (isset($options['numericFilters']) && count($options['numericFilters'])) {
            $params['body']['query']['bool']['must'] = array_merge(
                $params['body']['query']['bool']['must'],
                $options['numericFilters']
            );
        }

        if ($builder->callback) {
            return call_user_func(
                $builder->callback,
                $this->elastic,
                $builder->query,
                $params
            );
        }

        return $this->elastic->search($params);
    }
}
  • 创建类文件:app/Libraries/CustomScoutElastic/CustomElasticsearch.php
    该类继承并重写 vendor 包内 vendor/tamayo/laravel-scout-elastic/src/LaravelScoutElasticProvider.php 类内的 boot 方法
<?php

namespace App\Libraries\CustomScoutElastic;

use Elasticsearch\ClientBuilder;
use Laravel\Scout\EngineManager;
use Tamayo\LaravelScoutElastic\LaravelScoutElasticProvider;

class CustomElasticsearch extends LaravelScoutElasticProvider
{
    /**
     * 重写 laravel-scout-elastic 包 LaravelScoutElasticProvider 类 的 boot 方法
     * @throws \Exception
     */
    public function customBootFromLaravelScoutElasticProvider()
    {
        try {
            $this->ensureElasticClientIsInstalled();

            resolve(EngineManager::class)->extend('elasticsearch', function () {
                return new CustomElasticsearchEngine(
                    ClientBuilder::create()
                        ->setHosts(config('scout.elasticsearch.hosts'))
                        ->build()
                );
            });
        } catch (\Exception $e) {
            throw new \Exception($e->getMessage(), $e->getCode());
        }
    }
}
  • 编辑文件:app/Providers/AppServiceProvider.php
    use相关类(new时会自动引入),此文件boot()方法中加入相关内容,如下:
use App\Libraries\CustomScoutElastic\CustomElasticsearch;

/**
  * Bootstrap any application services.
  * @throws \Exception
  */
public function boot()
{
    try {
        //重写 laravel-scout-elastic 包 LaravelScoutElasticProvider 类 的 boot 方法
        $customElasticsearch = new CustomElasticsearch($this->app);
        $customElasticsearch->customBootFromLaravelScoutElasticProvider();
    } catch (\Exception $e) {
        throw new \Exception($e->getMessage(), $e->getCode());
    }
}
  • 至此,不侵入vendor包代码的方案一就完成了,测试依然可用:

方案二

  • app/Libraries目录下新增目录:CustomScoutElastic
  • 创建类文件:app/Libraries/CustomScoutElastic/CustomElasticsearchEngine.php
    该类为了重写vendor包内 vendor/tamayo/laravel-scout-elastic/src/Engines/ElasticsearchEngine.php 类内的 performSearch 方法。
<?php

namespace App\Libraries\CustomScoutElastic;

use Laravel\Scout\Builder;
use Tamayo\LaravelScoutElastic\Engines\ElasticsearchEngine;

/**
 * 自定义es引擎,重写 performSearch 方法,解决134行(本文件28行)*导致跨字符查询无结果的问题
 */
class CustomElasticsearchEngine extends ElasticsearchEngine
{
    /**
     * Perform the given search on the engine.
     *
     * @param  Builder  $builder
     * @param  array  $options
     * @return mixed
     */
    protected function performSearch(Builder $builder, array $options = [])
    {
        $params = [
            'index' => $builder->model->searchableAs(),
            'type' => get_class($builder->model),
            'body' => [
                'query' => [
                    'bool' => [
                        'must' => [['query_string' => ['query' => "{$builder->query}"]]]
                    ]
                ]
            ]
        ];

        if ($sort = $this->sort($builder)) {
            $params['body']['sort'] = $sort;
        }

        if (isset($options['from'])) {
            $params['body']['from'] = $options['from'];
        }

        if (isset($options['size'])) {
            $params['body']['size'] = $options['size'];
        }

        if (isset($options['numericFilters']) && count($options['numericFilters'])) {
            $params['body']['query']['bool']['must'] = array_merge(
                $params['body']['query']['bool']['must'],
                $options['numericFilters']
            );
        }

        if ($builder->callback) {
            return call_user_func(
                $builder->callback,
                $this->elastic,
                $builder->query,
                $params
            );
        }

        return $this->elastic->search($params);
    }
}
  • App\Providers 目录下创建provider类文件:CustomLaravelScoutElasticProvider.php
    具体路径为:app/Providers/CustomLaravelScoutElasticProvider.php
    该类继承并重写 vendor 包内 vendor/tamayo/laravel-scout-elastic/src/LaravelScoutElasticProvider.php 类内的 boot 方法:
<?php

namespace App\Providers;


use App\Libraries\CustomScoutElastic\CustomElasticsearchEngine;
use Elasticsearch\ClientBuilder;
use Laravel\Scout\EngineManager;
use Tamayo\LaravelScoutElastic\LaravelScoutElasticProvider;

/**
 * 自定义 LaravelScoutElastic 服务提供者,用于重写 LaravelScoutElasticProvider 类的boot方法
 * 解决134行(本文件28行)*导致跨字符查询无结果的问题
 */
//class CustomLaravelScoutElasticProvider extends LaravelScoutElasticProvider
class CustomLaravelScoutElasticProvider extends LaravelScoutElasticProvider
{
    /**
     * Bootstrap the application services.
     * @throws \Exception
     */
    public function boot()
    {
        try {
            $this->ensureElasticClientIsInstalled();

            resolve(EngineManager::class)->extend('elasticsearch', function () {
                return new CustomElasticsearchEngine(
                    ClientBuilder::create()
                        ->setHosts(config('scout.elasticsearch.hosts'))
                        ->build()
                );
            });
        } catch (\Exception $e) {
            throw new \Exception($e->getMessage(), $e->getCode());
        }
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}
  • config/app.php 文件 providers 加载创建的 CustomLaravelScoutElasticProvider 类
\App\Providers\CustomLaravelScoutElasticProvider::class,
  • 至此,不侵入 vendor 包代码的方案二就完成了,测试依然可用:

异常问题

  • 软件安装过程中遇到了各种各样的问题,最终确定了 es8.1.1 版本,注意 ik 版本需要和 es 版本绝对一致。
  • 使用方案二过程中,如果代码都写好了,还是无效,建议清除 bootstrap/cache 内文件后重新请求,可能是 provider 缓存导致的。

*******************************只要思想不滑坡,办法总比困难多*******************************


好博客就要一起分享哦!分享海报

此处可发布评论

评论(0展开评论

暂无评论,快来写一下吧

展开评论

您可能感兴趣的博客

客服QQ 1913284695