分散式全文搜索解決方案
1.解決方案介紹
ElasticSearch是一個基於RESTful web介面的分散式全文搜索引擎。
本解決方案是基於Mysql資料庫 、 Hadoop生態(可選)、 ElasticSearch搜索引擎三大數據系統實現一個分散式全文搜索系統。
主要包括數據接入、數據索引和全文搜索3個模組。適用於各種項目的各種搜索場景。
1.1.1 關係型資料庫
用於對商品,用戶等各種數據進行結構化存儲。 關係型資料庫對於事務性非常高的OLTP1操作(比如訂單,結算等)支援良好。
主選:mysql資料庫
1.1.2 hadoop生態
Hadoop實現了一個
Hadoop的框架最核心的設計就是:HDFS和MapReduce。HDFS為海量的數據提供存儲,而MapReduce則為海量的數據提供計算。
hadoop是數據倉庫主要的載體,除了備份關係型資料庫的所有版本,還存儲用戶行為,點擊,曝光,互動等海量日誌數據,hadoop對於數據分析,數據挖掘等OLAP2操作支援比關係型資料庫更加具有擴展性和穩定性。
Hive,基於Hadoop的一個
HBase,Hadoop的一個子項目,是一個分散式的、面向列的開源資料庫。
Spark,專為大規模數據處理而設計的快速通用的計算引擎,可以在 Hadoop 文件系統中並行運行,作為對 Hadoop 的補充。
1.1.3 搜索引擎
以elasticsearch和solr為代表。搜索引擎是獲取資訊最高效的途徑,幾乎成為各類網站,應用的基礎標配設施(地位僅次於資料庫)。
ElasticSearch是一個基於Lucene的搜索伺服器。它提供了一個分散式多用戶能力的全文搜索引擎,基於RESTful web介面。Elasticsearch是用Java開發的,並作為Apache許可條款下的開放源碼發布,是當前流行的企業級搜索引擎。設計用於
2.軟體安裝
2.1 安裝JDK
ElasticSearch是用JAVA語言開發的,其運行需要安裝JDK。
JDK (Java Development Kit) ,是整個Java的核心,包括了Java運行環境(Java Runtime Envirnment),一堆Java工具和Java基礎的類庫(rt.jar)。
下載地址//www.oracle.com/technetwork/java/javase/downloads/index.html






2.1.2 配置環境變數
配置 JAVA_HOME環境變數


2.1.3 測試-查看JDK版本
打開命令行窗口,輸入java -version查看JDK版本

出現以上介面,說明安裝成功。
2.2 安裝Elasticsearch
權威指南//www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
2.2.1 下載安裝
下載地址//www.elastic.co/downloads



2.2.2 配置Path環境變數

2.2.3 啟動elasticsearch
打開命令行窗口 執行命令 elasticsearch -d 啟動elasticsearch

註:該命令行窗口 不要關閉。
瀏覽器打開 //localhost:9200

-
-
config:配置文件
-
log4j2.properties:日誌配置文件
-
jvm.options:java虛擬機的配置
-
elasticsearch.yml:es的配置文件
-
-
data:索引數據目錄
-
lib:相關類庫Jar包
-
logs:日誌目錄
-
modules:功能模組
-
2.2.5(選裝)安裝Elasticsearch-Head
elasticsearch-head是一個用於瀏覽ElasticSearch集群並與其進行交互的Web項目
GitHub託管地址://github.com/mobz/elasticsearch-head
下載並解壓:

#新添加的配置行
http.cors.enabled: true
http.cors.allow-origin: “*”
重新啟動
2.3 安裝Elasticsearch-php
//github.com/elastic/elasticsearch-php
在項目目錄下,執行以下命令
composer require elasticsearch/elasticsearch

2.4 配置php.ini
3.ElasticSearch基本使用
3.1.1 節點與集群
Elastic 本質上是一個分散式資料庫,允許多台伺服器協同工作,每台伺服器可以運行多個 Elastic 實例。

3.1.2 索引
在Elasticsearch中存儲數據的行為就叫做索引(indexing)
在Elasticsearch中,文檔歸屬於一種類型(type),而這些類型存在於索引(index)中
類比傳統關係型資料庫:
Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields
Elasticsearch集群可以包含多個索引(indices)(資料庫)
每一個索引可以包含多個類型(types)(表)
每一個類型包含多個文檔(documents)(行)
然後每個文檔包含多個欄位(Fields)(列)。

3.2 基本使用
3.2.1 創建索引
$es = \Elasticsearch\ClientBuilder::create()->setHosts([‘127.0.0.1:9200’])->build();
$params = [
‘index’ => ‘test_index’
];
$r = $es->indices()->create($params);
dump($r);die;
預期結果:
array(3) {
[“acknowledged”] => bool(true)
[“shards_acknowledged”] => bool(true)
[“index”] => string(10) “test_index”
}
$es = \Elasticsearch\ClientBuilder::create()->setHosts(['127.0.0.1:9200'])->build();
$params = [
'index' => 'test_index',
'type' => 'test_type',
'id' => 100,
'body' => ['id'=>100, 'title'=>'PHP從入門到精通', 'author' => '張三']
];
$r = $es->index($params);
dump($r);die;
預期結果:
array(8) {
[“_index”] => string(10) “test_index”
[“_type”] => string(9) “test_type”
[“_id”] => string(3) “100”
[“_version”] => int(1)
[“result”] => string(7) “created”
[“_shards”] => array(3) {
[“total”] => int(2)
[“successful”] => int(1)
[“failed”] => int(0)
}
[“_seq_no”] => int(0)
[“_primary_term”] => int(1)
}
$es = \Elasticsearch\ClientBuilder::create()->setHosts([‘127.0.0.1:9200’])->build();
$params = [
‘index’ => ‘test_index’,
‘type’ => ‘test_type’,
‘id’ => 100,
‘body’ => [
‘doc’ => [‘id’=>100, ‘title’=>’ES從入門到精通’, ‘author’ => ‘張三’]
]
];
$r = $es->update($params);
dump($r);die;
預期結果:
array(8) {
[“_index”] => string(10) “test_index”
[“_type”] => string(9) “test_type”
[“_id”] => string(3) “100”
[“_version”] => int(2)
[“result”] => string(7) “updated”
[“_shards”] => array(3) {
[“total”] => int(2)
[“successful”] => int(1)
[“failed”] => int(0)
}
[“_seq_no”] => int(1)
[“_primary_term”] => int(1)
}
$es = \Elasticsearch\ClientBuilder::create()->setHosts([‘127.0.0.1:9200’])->build();
$params = [
‘index’ => ‘test_index’,
‘type’ => ‘test_type’,
‘id’ => 100,
];
$r = $es->delete($params);
dump($r);die;
預期結果:
array(8) {
[“_index”] => string(10) “test_index”
[“_type”] => string(9) “test_type”
[“_id”] => string(3) “100”
[“_version”] => int(3)
[“result”] => string(7) “deleted”
[“_shards”] => array(3) {
[“total”] => int(2)
[“successful”] => int(1)
[“failed”] => int(0)
}
[“_seq_no”] => int(2)
[“_primary_term”] => int(1)
}
封裝操作es的工具類:項目目錄/extends/tools/es/MyElasticsearch.php
<?php
namespace tools\es;
use Elasticsearch\ClientBuilder;
class MyElasticsearch
{
//ES客戶端鏈接
private $client;
/**
* 構造函數
* MyElasticsearch constructor.
*/
public function __construct()
{
$params = array(
‘127.0.0.1:9200’
);
$this->client = ClientBuilder::create()->setHosts($params)->build();
}
/**
* 判斷索引是否存在
* @param string $index_name
* @return bool|mixed|string
*/
public function exists_index($index_name = ‘test_ik’)
{
$params = [
‘index’ => $index_name
];
try {
return $this->client->indices()->exists($params);
} catch (\Elasticsearch\Common\Exceptions\BadRequest400Exception $e) {
$msg = $e->getMessage();
$msg = json_decode($msg,true);
return $msg;
}
}
/**
* 創建索引
* @param string $index_name
* @return array|mixed|string
*/
public function create_index($index_name = ‘test_ik’) { // 只能創建一次
$params = [
‘index’ => $index_name,
‘body’ => [
‘settings’ => [
‘number_of_shards’ => 5,
‘number_of_replicas’ => 0
]
]
];
try {
return $this->client->indices()->create($params);
} catch (\Elasticsearch\Common\Exceptions\BadRequest400Exception $e) {
$msg = $e->getMessage();
$msg = json_decode($msg,true);
return $msg;
}
}
/**
* 刪除索引
* @param string $index_name
* @return array
*/
public function delete_index($index_name = ‘test_ik’) {
$params = [‘index’ => $index_name];
$response = $this->client->indices()->delete($params);
return $response;
}
/**
* 添加文檔
* @param $id
* @param $doc [‘id’=>100, ‘title’=>’phone’]
* @param string $index_name
* @param string $type_name
* @return array
*/
public function add_doc($id,$doc,$index_name = ‘test_ik’,$type_name = ‘goods’) {
$params = [
‘index’ => $index_name,
‘type’ => $type_name,
‘id’ => $id,
‘body’ => $doc
];
$response = $this->client->index($params);
return $response;
}
/**
* 判斷文檔存在
* @param int $id
* @param string $index_name
* @param string $type_name
* @return array|bool
*/
public function exists_doc($id = 1,$index_name = ‘test_ik’,$type_name = ‘goods’) {
$params = [
‘index’ => $index_name,
‘type’ => $type_name,
‘id’ => $id
];
$response = $this->client->exists($params);
return $response;
}
/**
* 獲取文檔
* @param int $id
* @param string $index_name
* @param string $type_name
* @return array
*/
public function get_doc($id = 1,$index_name = ‘test_ik’,$type_name = ‘goods’) {
$params = [
‘index’ => $index_name,
‘type’ => $type_name,
‘id’ => $id
];
$response = $this->client->get($params);
return $response;
}
/**
* 更新文檔
* @param int $id
* @param string $index_name
* @param string $type_name
* @param array $body [‘doc’ => [‘title’ => ‘蘋果手機iPhoneX’]]
* @return array
*/
public function update_doc($id = 1,$index_name = ‘test_ik’,$type_name = ‘goods’, $body=[]) {
// 可以靈活添加新欄位,最好不要亂添加
$params = [
‘index’ => $index_name,
‘type’ => $type_name,
‘id’ => $id,
‘body’ => $body
];
$response = $this->client->update($params);
return $response;
}
/**
* 刪除文檔
* @param int $id
* @param string $index_name
* @param string $type_name
* @return array
*/
public function delete_doc($id = 1,$index_name = ‘test_ik’,$type_name = ‘goods’) {
$params = [
‘index’ => $index_name,
‘type’ => $type_name,
‘id’ => $id
];
$response = $this->client->delete($params);
return $response;
}
/**
* 搜索文檔 (分頁,排序,權重,過濾)
* @param string $index_name
* @param string $type_name
* @param array $body
* $body = [
‘query’ => [
‘bool’ => [
‘should’ => [
[
‘match’ => [
‘cate_name’ => [
‘query’ => $keywords,
‘boost’ => 4, // 權重大
]
]
],
[
‘match’ => [
‘goods_name’ => [
‘query’ => $keywords,
‘boost’ => 3,
]
]
],
[
‘match’ => [
‘goods_introduce’ => [
‘query’ => $keywords,
‘boost’ => 2,
]
]
]
],
],
],
‘sort’ => [‘id’=>[‘order’=>’desc’]],
‘from’ => $from,
‘size’ => $size
];
* @return array
*/
public function search_doc($index_name = “test_ik”,$type_name = “goods”,$body=[]) {
$params = [
‘index’ => $index_name,
‘type’ => $type_name,
‘body’ => $body
];
$results = $this->client->search($params);
return $results;
}
}
4.商品搜索功能
4.1 搜索規則
可根據關鍵詞對商品名稱、商品介紹、商品分類進行全文搜索
4.2 創建商品全量索引
項目目錄/application/cli/controller/Es.php
<?php
namespace app\cli\controller;
use think\Controller;
use think\Request;
class Es extends Controller
{
/**
* 創建商品索引並導入全部商品文檔
* cd public
* php index.php /cli/Es/createAllGoodsDocs
*/
public function createAllGoodsDocs()
{
try{
//實例化ES工具類
$es = new \tools\es\MyElasticsearch();
//創建索引
if($es->exists_index(‘goods_index’)) $es->delete_index(‘goods_index’);
$es->create_index(‘goods_index’);
$i = 0;
while(true){
//查詢商品數據 每次處理1000條
$goods = \app\common\model\Goods::with(‘category’)->field(‘id,goods_name,goods_desc, goods_price,goods_logo,cate_id’)->limit($i, 1000)->select();
if(empty($goods)){
//查詢結果為空,則停止
break;
}
//添加文檔
foreach($goods as $v){
unset($v[‘cate_id’]);
$es->add_doc($v[‘id’],$v, ‘goods_index’, ‘goods_type’);
}
$i += 1000;
}
die(‘success’);
}catch (\Exception $e){
$msg = $e->getMessage();
die($msg);
}
}
}
切換到public目錄 執行命令
php index.php /cli/Es/createAllGoodsDocs
註:其中,使用了封裝的ES工具類 : 項目目錄/extends/tools/es/MyElasticsearch.php
4.3 搜索
4.3.1 頁面部分
項目目錄/application/home/view/layout.html中,修改搜索框表單如下:
<form action=”{:url(‘home/goods/index’)}” method=”get” class=”sui-form form-inline”>
<!–searchAutoComplete–>
<div class=”input-append”>
<input type=”text” id=”autocomplete” class=”input-error input-xxlarge” name=”keywords” value=”{$Request.param.keywords}” />
<button class=”sui-btn btn-xlarge btn-danger” type=”submit”>搜索</button>
</div>
</form>
4.3.2 控制器部分
public function index($id=0)
{
//接收參數
$keywords = input(‘keywords’);
if(empty($keywords)){
//獲取指定分類下商品列表
if(!preg_match(‘/^\d+$/’, $id)){
$this->error(‘參數錯誤’);
}
//查詢分類下的商品
$list = \app\common\model\Goods::where(‘cate_id’, $id)->order(‘id desc’)->paginate(10);
//查詢分類名稱
$category_info = \app\common\model\Category::find($id);
$cate_name = $category_info[‘cate_name’];
}else{
try{
//從ES中搜索
$list = \app\home\logic\GoodsLogic::search();
$cate_name = $keywords;
}catch (\Exception $e){
$this->error(‘伺服器異常’);
}
}
return view(‘index’, [‘list’ => $list, ‘cate_name’ => $cate_name]);
}
4.3.3 搜索邏輯部分
<?php
namespace app\home\logic;
use think\Controller;
class GoodsLogic extends Controller
{
public static function search(){
//實例化ES工具類
$es = new \tools\es\MyElasticsearch();
//計算分頁條件
$keywords = input(‘keywords’);
$page = input(‘page’, 1);
$page = $page < 1 ? 1 : $page;
$size = 10;
$from = ($page – 1) * $size;
//組裝搜索參數體
$body = [
‘query’ => [
‘bool’ => [
‘should’ => [
[ ‘match’ => [ ‘cate_name’ => [
‘query’ => $keywords,
‘boost’ => 4, // 權重大
]]],
[ ‘match’ => [ ‘goods_name’ => [
‘query’ => $keywords,
‘boost’ => 3,
]]],
[ ‘match’ => [ ‘goods_desc’ => [
‘query’ => $keywords,
‘boost’ => 2,
]]],
],
],
],
‘sort’ => [‘id’=>[‘order’=>’desc’]],
‘from’ => $from,
‘size’ => $size
];
//進行搜索
$results = $es->search_doc(‘goods_index’, ‘goods_type’, $body);
//獲取數據
$data = array_column($results[‘hits’][‘hits’], ‘_source’);
$total = $results[‘hits’][‘total’][‘value’];
//分頁處理
$list = \tools\es\EsPage::paginate($data, $size, $total);
return $list;
}
}
4.3.4 ES分頁類
借鑒模型的分頁查詢方法,封裝用
<?php
namespace tools\es;
use think\Config;
class EsPage
{
public static function paginate($results, $listRows = null, $simple = false, $config = [])
{
if (is_int($simple)) {
$total = $simple;
$simple = false;
}else{
$total = null;
$simple = true;
}
if (is_array($listRows)) {
$config = array_merge(Config::get(‘paginate’), $listRows);
$listRows = $config[‘list_rows’];
} else {
$config = array_merge(Config::get(‘paginate’), $config);
$listRows = $listRows ?: $config[‘list_rows’];
}
/** @var Paginator $class */
$class = false !== strpos($config[‘type’], ‘\\’) ? $config[‘type’] : ‘\\think\\paginator\\driver\\’ . ucwords($config[‘type’]);
$page = isset($config[‘page’]) ? (int) $config[‘page’] : call_user_func([
$class,
‘getCurrentPage’,
], $config[‘var_page’]);
$page = $page < 1 ? 1 : $page;
$config[‘path’] = isset($config[‘path’]) ? $config[‘path’] : call_user_func([$class, ‘getCurrentPath’]);
return $class::make($results, $listRows, $page, $total, $simple, $config);
}
}
商品列表頁 商品分類展示位置

4.4 商品文檔維護
新增商品後,在ES中添加商品文檔
更新商品後,在ES中修改商品文檔
刪除商品後,在ES中刪除商品文檔
使用MVC的後台測試,則在admin/model/Goods.php中
使用前後端分離介面api測試,則寫在common/model/Goods.php中
項目目錄/application/admin/model/Goods.php中,init方法程式碼如下:
protected static function init()
{
//實例化ES工具類
$es = new \tools\es\MyElasticsearch();
//設置新增回調
self::afterInsert(function($goods)use($es){
//添加文檔
$doc = $goods->visible([‘id’, ‘goods_name’, ‘goods_desc’, ‘goods_price’])->toArray();
$doc[‘cate_name’] = $goods->category->cate_name;
$es->add_doc($goods->id, $doc, ‘goods_index’, ‘goods_type’);
});
//設置更新回調
self::afterUpdate(function($goods)use($es){
//修改文檔
$doc = $goods->visible([‘id’, ‘goods_name’, ‘goods_desc’, ‘goods_price’, ‘cate_name’])->toArray();
$doc[‘cate_name’] = $goods->category->cate_name;
$body = [‘doc’ => $doc];
$es->update_doc($goods->id, ‘goods_index’, ‘goods_type’, $body);
});
//設置刪除回調
self::afterDelete(function($goods)use($es){
//刪除文檔
$es->delete_doc($goods->id, ‘goods_index’, ‘goods_type’);
});
}
分散式全文搜索解決方案:是基於Mysql資料庫 、 Hadoop生態(可選)、 ElasticSearch搜索引擎三大數據系統實現一個分散式全文搜索系統。
Mysql資料庫用於結構化存儲項目數據。
Hadoop生態用於備份關係型資料庫的所有版本,還存儲用戶行為,點擊,曝光,互動等海量日誌數據,用於數據分析處理。
ElasticSearch搜索引擎用於對Mysql或者Hadoop提供的數據進行索引和全文搜索。
其中核心功能,包括全量創建索引、增量創建索引、實時同步數據(文檔的curd)、全文搜索等。







