推薦18-Laravel scout 與 elasticsearch 案例
- 2019 年 10 月 7 日
- 筆記
$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.5.1.zip $ unzip elasticsearch-5.5.1.zip $ cd elasticsearch-5.5.1/
接著,進入解壓後的目錄,運行以下的命令,嘗試啟動 elastic
$ ./bin/elasticsearch
注意:
初次安裝,非常有可能會出現以下報錯:
max virtual memory areas vm.maxmapcount [65530] is too low
運行以下命令即可解決:
$ sudo sysctl -w vm.max_map_count=262144
如果一切正常,elastic 默認會在本機的 9200
埠運行,請求該埠,會獲得以下
$ curl localhost:9200 { "name" : "atntrTf", "cluster_name" : "elasticsearch", "cluster_uuid" : "tf9250XhQ6ee4h7YI11anA", "version" : { "number" : "5.5.1", "build_hash" : "19c13d0", "build_date" : "2017-07-18T20:44:24.823Z", "build_snapshot" : false, "lucene_version" : "6.6.0" }, "tagline" : "You Know, for Search" }
默認情況下,elastic 只允許本機訪問,如果需要遠程訪問許可權,需要修改 elastic 安裝目錄的 config/elasticserach.yml
文件,去掉 netword.host
的注釋,將它的值 改為 0.0.0.0
,然後重啟 elastic
network.host: 0.0.0.0
上面程式碼中,設成 0.0.0.0
讓任何人都可以訪問。線上服務不要這樣設置,要設成具體的 IP
基本概念
elastic 本質上是一個分散式資料庫,允許多台伺服器協同工作,每台伺服器可以允許多個 elastic 實例,單個 elastic 實例稱為一個節點,一組節點構成一個集群
它有一些重要的概念
- Index(索引)
- Type(類型)
- Document(文檔)
- Shards(分片)
- Replicasedit(副本)
分片和副本暫時用不到,就簡單的說明一下
- 副本是乘法,越多越浪費,但也越保險。 - 分片是除法,分片越多,單分片數據就越少也越分散。
由於裡面的概念內容比較多,貼出兩個講解的非常好的部落格:
- 阮一峰的講解
- ElastSearch 的技術分析
看完了之後,我們可以用一個對比來了解一下其中重要的概念
- 關係型資料庫 -> Databases(庫) -> Tables(表) -> Rows(行) -> Columns(列)。 - Elasticsearch -> Indeces(索引) -> Types(類型) -> Documents(文檔) -> Fields(屬性)。
Elasticsearch 集群可以包含多個索引(indices)(資料庫),每一個索引可以包含多個類型 (Types)(表),每一個類型包含多個文檔(documents)(行),然後每個文檔包含多個欄位(Fields)(列)。
雖然這麼類比,但是畢竟是兩個差異化的產品,而且上面也說過在以後的版本中類型 (Types) 可能會被刪除,所以一般我們創建索引都是一個種類對應一個索引。生鮮就創建商品的索引,生活用品就創建生活用品的索引,而不會說創建一個商品的索引,裡面既包含生鮮的類型,又包含生活用品的類型。
Laravel scout 與 es
先安裝 scout 包
composer require laravel/scout
再生成配置文件
php artisan vendor:publish --provider="LaravelScoutScoutServiceProvider"
在 config/app.php
的 provider 中,添加
LaravelScoutScoutServiceProvider::class, ScoutEnginesElasticsearchElasticsearchProvider::class,
然後我們還需要在 scout.php 中,添加 es 的配置資訊,在 algolia 後添加
'elasticsearch' => [ 'index' => env('ELASTICSEARCH_INDEX', 'dongdianyi'), 'hosts' => [ env('ELASTICSEARCH_HOST', 'http://127.0.0.1:9200'), ], ],
我們還需要使用到 GuzzleHttp
安裝一下
composer require guzzlehttp/guzzle
開始寫程式碼,需要先使用 command ,讓 ES 初始化一些數據
php artisan make:command InitEs
貼出我的程式碼
<?php namespace AppConsoleCommands; use GuzzleHttpClient; use IlluminateConsoleCommand; class ESInit extends Command { protected $signature = 'es:init'; protected $description = '初始化 es庫'; public function __construct() { parent::__construct(); } public function handle() { $client = new Client(); try { $this->createTemplate($client); $this->createIndex($client); } catch (Exception $ex) { $ex->message(); } } public function createTemplate(Client $client) { $url = config('scout.elasticsearch.hosts')[0] . '/_template/tmp'; // 保證 template 不存在 // $client->delete($url); $param = [ 'json' => [ 'template' => '*', 'settings' => [ 'number_of_shards' => 1, ], 'mappings' => [ '_default_' => [ '_all' => [ 'enabled' => true, ], 'dynamic_templates' => [ [ 'strings' => [ 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'text', 'analyzer' => 'ik_smart', 'ignore_above' => 256, 'fields' => [ 'keyword' => [ 'type' => 'keyword', ], ], ], ], ], ], ], ], ], ]; $client->put($url, $param); $this->info('elasticsearch template created done'); } public function createIndex(Client $client) { // 創建 index $url = config('scout.elasticsearch.hosts')[0] . '/' . config('scout.elasticsearch.index'); // 保證 index 不存在 // $client->delete($url); $param = [ 'json' => [ 'settings' => [ 'refresh_interval' => '5s', 'number_of_shards' => 1, 'number_of_replicas' => 0, ], 'mappings' => [ '_default_' => [ '_all' => [ 'enabled' => false, ], ], ], ], ]; $client->put($url, $param); $this->info('elasticsearch index created done'); } }
然後,我們要規定,是那個模型需要被搜索
<?php namespace App; use IlluminateDatabaseEloquentModel; use LaravelScoutSearchable; class Article extends Model { use Searchable; protected $table = 'posts'; protected $fillable = [ 'url', 'author', 'title', 'content', 'post_date' ]; public function toSearchableArray() { return [ 'title' => $this->title, 'content' => $this->content ]; } }
在模型裡面,使用 Searchable 和重載 toSearchableArray 函數就可以了
然後使用命令
php artisan scout:import "AppArticle"
將目前資料庫中的數據,按照 toSearchableArray 的規則導入,導入完成就可以了
驗證結果
es 和 scout 的步驟已經走完了,接下來就可以使用了
先定義 graphql 介面
searchArticles(keyWord: String!): [Article!]! @paginate(defaultCount: 10, builder: "App\Article@searchArticles")
然後再解析
public function searchArticles($rootValue, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) { $query = $args['keyWord']; return Article::search($query); }
完成!,這裡例子中我使用的是 ik_smart ,會自動按照中文分詞的最大粒度去匹配