首页 > 技术文章 > PHP:Laravel 使用 elasticsearch 组件包

wish-yang 2021-02-24 10:50 原文

关于MySQL数据同步到ES:

  有一些开源组件可以使用:go-mysql-elasticsearch,logstash,cancel 等...

  然后就是同步双写,和异步双写(此文使用方式)。

关于Laravel的ES组件:(注意:安装组件请注意版本和ES相同)

  composer require laravel/scout

  Scout 是 Laravel 官方出的一个让 Eloquent 模型支持全文搜索的包,这个包封装好一批方法,通过这些方法就可以将数据索引到全文搜索引擎中、

以及使用关键字从搜索引擎搜索文档。这个包适用于一些简单的搜索场景,比如博客文章搜索,但无法发挥出全文搜索引擎全部威力。

  composer require elasticsearch/elasticsearch (此文使用组件)

  Elasticsearch 是Elasticsearch官方提供的composer包,它包含更丰富的操作查询语句,例如 should 语句、

模糊查询、分片查询等,根本不是 Scout 几个简单的方法能够覆盖的

一:安装elasticsearch 组件及配置

composer require elasticsearch/elasticsearch "elasticsearch/elasticsearch":"7.6.1"

  

  配置.env:

QUEUE_CONNECTION=redis
 ES_HOSTS=192.168.244.100
REDIS_HOST=192.168.244.110
REDIS_PASSWORD=123456
REDIS_PORT=6379

  配置 config/database.php:

'elasticsearch' => [
        // Elasticsearch 支持多台服务器负载均衡,因此这里是一个数组
        'hosts' => explode(',', env('ES_HOSTS')),
    ],

  初始化elasticsearch对象,并注入到 Laravel 容器中:

  修改app/Providers/AppServiceProvider.php:

use Elasticsearch\ClientBuilder as ESClientBuilder;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
        // 注册一个名为 es 的单例
        $this->app->singleton('es', function () {
            // 从配置文件读取 Elasticsearch 服务器列表
            $builder = ESClientBuilder::create()->setHosts(config('database.elasticsearch.hosts'));
            // 如果是开发环境
            if (app()->environment() === 'local') {
                // 配置日志,Elasticsearch 的请求和返回数据将打印到日志文件中,方便我们调试
                $builder->setLogger(app('log')->driver());
            }
            return $builder->build();
        });
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

  命令行先测试一下:

  

二:使用

  1,先建立一个索引,我在kibana里面创建的,也可以直接用 curl -XPUT http://192.168.244.100/ylt_drivers?pretty

# 创建一个索引
PUT /ylt_drivers
# 设置索引规则
PUT /ylt_drivers/_mappings?pretty
{
  "properties":{
      "name":{
        "type":"keyword"
      },
      "mobile":{
        "type":"keyword"
      },
      "sex":{
        "type":"integer"
      },
      "driver_age":{
        "type":"integer"
      },
      "is_quit":{
        "type":"boolean"
      },
      "birthday":{
        "type":"date",
        "format":"yyyy-MM-dd"
      },
      "line":{
        "type": "nested",
        "properties":{
          "name":{
            "type":"text",
            "analyzer":"ik_smart"
          },
          "distance":{
            "type":"scaled_float",
            "scaling_factor":100
          }
        }
      }
    }
}

  2,创建一个laravel command命令同步MySQL现有数据

  2.1 先在Model里面创建一个方法用来转换数据为es需要的格式

use Illuminate\Support\Arr;

 //创建一个导入es的数据格式
    public function toESArray(){
        $arr = Arr::only($this->toArray(),[
            'id','name','mobile','sex','age','driver_age','brithday'
        ]);
      $arr['is_quit'] = $this->is_quit == 'Y' ? True : False
$arr['line'] = Arr::only($this->line->toArray(),['name','distance']);
        // 如果是列表可以用map()
        // $arr['lines'] = $this->line->map(function(Line $line){
        //     return Arr::only($line->toArray(),['name','distance']);
        // });
        return $arr;
    }

  2.2 先用命令行测试一下

  2.3  创建一个artisan command命令同步数据

php artisan make:command Elasticsearch/SyncDrivers

  打开 App\Console\Commands\Elasticsearch\SyncDrivers

namespace App\Console\Commands\Elasticsearch;

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

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

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = '将drivers数据同步到es';

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

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        // 获取 Elasticsearch 对象
        $es = app('es');
        Driver::query()
            // 预加载 line
            ->with(['line'])
            // 使用 chunkById 避免一次性加载过多数据
            ->chunkById(100, function ($drivers) use ($es) {
                $this->info(sprintf('正在同步 ID 范围为 %s 至 %s 的商品', $drivers->first()->id, $drivers->last()->id));
                // 初始化请求体
                $req = ['body' => []];
                // 遍历数据
                foreach ($drivers as $driver) {
                    // 将数据模型转为 Elasticsearch 所用的数组
                    $data = $driver->toESArray();
                    $req['body'][] = [
                        'index' => [
                            '_index' => 'ylt_drivers',
                            '_type'  => '_doc',
                            '_id'    => $data['id'],
                        ],
                    ];
                    $req['body'][] = $data;
                }
                try {
                    // 使用 bulk 方法批量创建
                    $es->bulk($req);
                } catch (\Exception $e) {
                    $this->error($e->getMessage());
                }
            });
        $this->info('同步完成');
    }
}

  先删除之前测试数据,再用命令执行测试。

  3:将数据提交到redis队列,通过异步任务同步数据到es

  3.1、先创建一个异步任务:

php artisan make:job SyncOneDriverToES

  3.2、打开App\Jobs\SyncOneDriverToES

namespace App\Jobs;

use App\Models\Driver;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class SyncOneDriverToES implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $driver;
    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(Driver $driver)
    {
        $this->driver = $driver;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $data = $this->driver->toESArray();
        app('es')->index([
            'index' => 'ylt_drivers',
            'type'  => '_doc',
            'id'    => $data['id'],
            'body'  => $data,
        ]);
    }
}

  3.3、在数据编辑和新增的地方加入异步提交代码,这里使用的是laravel-admin,所以在form()方法里加入:

 $form->saved(function (Form $form) {
            $driver = $form->model();
            dispatch(new SyncOneDriverToES($driver));
        });

  3.4、启动队列

php artisan queue:work

  

然后可以通过es-head等工具查看信息否同步成功。

推荐阅读