ElasticSearch基本使用
说明:基于 elasticsearch/elasticsearch 扩展包的 php 版使用。
文档:https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/getting-started-php.html
一、自定义服务类
php
<?php
/**
* Created by PhpStorm
* User: Jason
* Date: 2024-06-07
* Time: 14:48
*/
require_once '../vendor/autoload.php';
use Elasticsearch\ClientBuilder;
class ES
{
/**
* 客户端
*
* @var \Elasticsearch\Client
*/
private $client;
/**
* @param array $hosts
*/
public function __construct(array $hosts = ['127.0.0.1'])
{
$this->client = ClientBuilder::create()->setHosts($hosts)->build();
}
// ================================= 索引相关操作 =================================================
/**
* 创建索引
* 说明:相当于Mysql的表,只需要创建一次;原本相当于database,8.x废除了type(相当于table)
*
* @param string $index 索引名称
* @param array $settings 索引设置
* @param array $mappings 索引映射
* @return array
*/
public function creatIndex(string $index, array $settings = [], array $mappings = []): array
{
$params = [
'index' => $index,
'body' => [
'settings' => $settings,
'mappings' => $mappings,
]
];
return $this->client->indices()->create($params);
}
/**
* 删除索引
*
* @param $indexName string 索引名称
* @return array
*/
public function deleteIndex(string $indexName): array
{
$params = [
'index' => $indexName,
];
return $this->client->indices()->delete($params);
}
/**
* 检查索引是否存在
*
* @param string $indexName
* @return bool
*/
public function existsIndex(string $indexName)
{
return $this->client->indices()->exists(['index' => $indexName]);
}
/**
* 获取索引设置
*
* @param string $indexName 索引名称
* @return array
*/
public function getIndexSettings(string $indexName): array
{
$params = [
'index' => $indexName
];
return $this->client->indices()->getSettings($params);
}
/**
* 关闭索引
*
* @param $indexName string 索引名称
* @return array
*/
public function closeIndex(string $indexName): array
{
$params = ['index' => $indexName];
return $this->client->indices()->close($params);
}
/**
* 打开索引
*
* @param $indexName string 索引名称
* @return array
*/
public function openIndex(string $indexName): array
{
$params = ['index' => $indexName];
return $this->client->indices()->open($params);
}
/**
* 更新索引设置
*
* @param string $indexName 索引名称
* @param array $settings 新的索引设置
* @return array 更新索引设置的响应
*/
public function updateIndexSettings(string $indexName, array $settings): array
{
$params = [
'index' => $indexName,
'body' => ['settings' => $settings]
];
return $this->client->indices()->putSettings($params);
}
/**
* 更新非动态索引设置
*
* @param $indexName string 索引名称
* @param array $settings 索引设置
* @return array
*/
public function updateNonDynamicIndexSettings(string $indexName, array $settings)
{
// 关闭索引
$this->closeIndex($indexName);
// 更新索引设置
$response = $this->updateIndexSettings($indexName, $settings);
// 重新打开索引
$this->openIndex($indexName);
return $response;
}
/**
* 获取索引映射
*
* @param string $indexName 索引名称
* @return array 获取索引映射的响应
*/
public function getIndexMappings($indexName)
{
$params = [
'index' => $indexName
];
return $this->client->indices()->getMapping($params);
}
/**
* 更新索引映射
*
* @param string $indexName 索引名称
* @param array $mappings 新的索引映射
* @return array 更新索引映射的响应
*/
public function updateIndexMappings(string $indexName, array $mappings): array
{
$params = [
'index' => $indexName,
'body' => ['properties' => $mappings]
];
return $this->client->indices()->putMapping($params);
}
/**
* 刷新索引
*
* @param $indexName string 索引名称
* @return array
*/
public function refreshIndex(string $indexName): array
{
$params = [
'index' => $indexName
];
return $this->client->indices()->refresh($params);
}
// ===================================== 文档相关操作 ===============================================
/**
* 新增文档
*
* @param string $indexName 索引名称
* @param string $id 文档ID
* @param array $body 文档内容
* @return array
*/
public function addDocument($indexName, string $id, array $body)
{
$params = [
'index' => $indexName,
'id' => $id, // 文档ID,可省略,默认生成随机ID
'body' => $body
];
return $this->client->index($params);
}
/**
* 批量新增文档
*
* @param array $params
* @return array|callable
*/
public function batchAddDocument(array $params)
{
return $this->client->bulk($params);
}
/**
* 获取文档
* 说明:查询单挑记录
*
* @param string $indexName 索引名称
* @param string $id 文档ID
* @return array|callable
*/
public function getDocument(string $indexName, string $id): callable|array
{
$params = [
'index' => $indexName,
'id' => $id,
];
return $this->client->get($params);
}
/**
* 更新文档
*
* @param string $indexName 索引名称
* @param string $id 文档ID
* @param array $doc 更新内容
* @return array|callable
*/
public function updateDocument(string $indexName, string $id, array $doc)
{
$params = [
'index' => $indexName,
'id' => $id,
'body' => [
'doc' => $doc, // 需要更新的内容
],
];
return $this->client->update($params);
}
/**
* 删除文档
*
* @param string $indexName
* @param $id
* @return array|callable
*/
public function deleteDocument(string $indexName, $id)
{
$params = [
'index' => $indexName,
'id' => $id,
];
return $this->client->delete($params);
}
// ======================================= 搜索相关操作 ================================================
/**
* 基础搜索方法
*
* @param string $indexName 索引名称
* @param array $query 搜索查询
* @param array|null $sort 排序参数
* @param int|null $from 起始位置
* @param int|null $size 返回结果数
* @return array
*/
public function search(string $indexName, array $query, array $sort = null, int $from = null, int $size = null)
{
$params = [
'index' => $indexName,
'body' => [
'query' => $query
]
];
if ($sort) {
$params['body']['sort'] = $sort;
}
if (!is_null($from)) {
$params['body']['from'] = $from;
}
if (!is_null($size)) {
$params['body']['size'] = $size;
}
return $this->client->search($params);
}
}
二、索引基本使用
2.1 创建索引
php
<?php
$hosts = [
'192.168.0.117:9200'
];
$es = new ES($hosts);
// 索引名
$userIndex = 'user';
// 设置
$settings = [
// 分片数量: 一个索引库将拆分成多片分别存储不同的结点,默认5个
'number_of_shards' => count($hosts),
// 每个分片分配的副本数,replica shard是primary shard的副本,负责容错,以及承担读请求负载,如果服务器只有一台,只能设置为0
'number_of_replicas' => 0
];
// 创建文档映射,就是文档存储在ES中的数据结构
$mappings = [
'properties' => [
'user_id' => [
'type' => 'integer',
'index' => 'true'
],
'nickname' => [
// 数据类型: text支持分词; keyword不支持分词,只能精确索引;数值类型(integer,...),日期类型(date),逻辑类型:boolean;IP类型(ip);地理坐标类型(geo_point);.....
// keyword 排序是按照字符串的ASCII码排序的,'3'>'20'
'type' => 'text',
// 字段可以被索引,也就是能用来当做查询条件来查询,只能填写true和false
'index' => 'true',
// 索引分词器,用于字符串类型,这里使用中文分词器ik,用默认分词器可以省略
'analyzer' => 'ik_max_word',
// 搜索分词器,用于搜索关键词的分词器
'search_analyzer' => 'ik_max_word',
],
'age' => [
'type' => 'integer',
'index' => 'true',
],
'create_time' => [
'type' => 'date',
'index' => 'true',
'format' => 'yyyy-MM-dd HH:mm:ss'
],
]
];
$res = $es->creatIndex($userIndex, $settings, $mappings);
var_dump(json_encode($res, JSON_UNESCAPED_UNICODE));
// 输出: {"acknowledged":true,"shards_acknowledged":true,"index":"user"}
2.2 判断索引是否存在
php
<?php
$res = $es->existsIndex($userIndex);
var_dump($res);
// 输出: true
2.3 删除索引
php
<?php
$res = $es->deleteIndex($userIndex);
var_dump(json_encode($res, JSON_UNESCAPED_UNICODE));
// 输出: {"acknowledged":true}
2.4 获取索引设置
php
<?php
$res = $es->getIndexSettings($userIndex);
var_dump(json_encode($res,JSON_UNESCAPED_UNICODE));
// 输出: {"user":{"settings":{"index":{"routing":{"allocation":{"include":{"_tier_preference":"data_content"}}},"refresh_interval":"1s","number_of_shards":"1","provided_name":"user","creation_date":"1719907928617","number_of_replicas":"0","uuid":"7d3Mojg6SL6q_7Q0gamMog","version":{"created":"8503000"}}}}}
2.5 更新索引设置
php
<?php
$settings = [
'index.refresh_interval' => '1s' // 刷新时间间隔
];
$res = $es->updateNonDynamicIndexSettings($userIndex, $settings);
var_dump(json_encode($res, JSON_UNESCAPED_UNICODE));
// 输出: {"acknowledged":true}
2.6 获取索引映射
php
<?php
$res = $es->getIndexMappings($userIndex);
var_dump(json_encode($res, JSON_UNESCAPED_UNICODE));
// 输出: {"user":{"mappings":{"properties":{"age":{"type":"integer"},"create_time":{"type":"date","format":"yyyy-MM-dd HH:mm:ss"},"nickname":{"type":"text","analyzer":"ik_max_word"},"user_id":{"type":"integer"}}}}}
2.7 更新索引映射
php
<?php
$mappings = [
'phone' => [
'type' => 'keyword',
'index' => 'true'
]
];
$res = $es->updateIndexMappings($userIndex, $mappings);
var_dump(json_encode($res, JSON_UNESCAPED_UNICODE));
// 输出: {"acknowledged":true}
2.8 刷新索引
php
<?php
$res = $es->refreshIndex($userIndex);
var_dump(json_encode($res, JSON_UNESCAPED_UNICODE));
// 输出: {"_shards":{"total":1,"successful":1,"failed":0}}
三、文档基本使用
3.1 新增文档
php
<?php
$res = $es->addDocument($userIndex, '1', [
'user_id' => 1,
'nickname' => '爆款煎饼(传统双蛋煎饼+肉松+优质火腿片+配菜+薄脆)',
'age' => mt_rand(10, 40),
'create_time' => \Carbon\Carbon::now()->toDateTimeString(),
'phone' => '13612348888'
]);
var_dump(json_encode($res, JSON_UNESCAPED_UNICODE));
// 输出: {"_index":"user","_id":"1","_version":1,"result":"created","_shards":{"total":1,"successful":1,"failed":0},"_seq_no":0,"_primary_term":5}
3.2 批量新增文档
php
<?php
$documents = [
[
'user_id' => 2,
'nickname' => '高颜值情侣玻璃浮雕吸管杯2个-xh',
'age' => 12,
'create_time' => '2024-06-29 12:00:05',
'phone' => '13575861234',
],
[
'user_id' => 3,
'nickname' => '网红爆款护手霜6支香味随机-K',
'age' => 23,
'create_time' => '2024-06-28 03:18:08',
'phone' => '19512345286',
],
[
'user_id' => 4,
'nickname' => '【可拆卸一拖四线】充电宝迷你自带线大容量数显快充移动电源',
'age' => 34,
'create_time' => '2024-07-02 03:18:08',
'phone' => '19538883347',
],
];
$params = [];
foreach ($documents as $document) {
$params['body'][] = [
'index' => [
'_index' => $userIndex,
'_id' => strval($document['user_id'])
]
];
$params['body'][] = [
'user_id' => $document['user_id'],
'nickname' => $document['nickname'],
'age' => $document['age'],
'create_time' => $document['create_time'],
'phone' => $document['phone'],
];
}
var_dump(json_encode($es->batchAddDocument($params), JSON_UNESCAPED_UNICODE));
// 输出: {"errors":false,"took":15,"items":[{"index":{"_index":"user","_id":"2","_version":1,"result":"created","_shards":{"total":1,"successful":1,"failed":0},"_seq_no":3,"_primary_term":5,"status":201}},{"index":{"_index":"user","_id":"3","_version":1,"result":"created","_shards":{"total":1,"successful":1,"failed":0},"_seq_no":4,"_primary_term":5,"status":201}},{"index":{"_index":"user","_id":"4","_version":1,"result":"created","_shards":{"total":1,"successful":1,"failed":0},"_seq_no":5,"_primary_term":5,"status":201}}]}
3.3 获取文档
php
<?php
var_dump(json_encode($es->getDocument($userIndex, '9'), JSON_UNESCAPED_UNICODE));
// 输出: {"_index":"user","_id":"1","_version":3,"_seq_no":2,"_primary_term":5,"found":true,"_source":{"user_id":1,"nickname":"爆款煎饼(传统双蛋煎饼+肉松+优质火腿片+配菜+薄脆)","age":29,"create_time":"2024-07-02 17: 15: 16","phone":"13612348888"}}
3.4 更新文档
php
<?php
$res = $es->updateDocument($userIndex, '1', [
'nickname' => '9999Jason的爆款煎饼(传统双蛋煎饼+肉松+优质火腿片+配菜+薄脆)',
'age' => 30
]);
var_dump(json_encode($res, JSON_UNESCAPED_UNICODE));
// 输出: {"_index":"user","_id":"1","_version":2,"result":"updated","_shards":{"total":1,"successful":1,"failed":0},"_seq_no":7,"_primary_term":5}
3.5 删除文档
php
<?php
$res = $es->deleteDocument($userIndex, '9');
var_dump(json_encode($res, JSON_UNESCAPED_UNICODE));
// 输出: {"_index":"user","_id":"9","_version":3,"result":"deleted","_shards":{"total":1,"successful":1,"failed":0},"_seq_no":8,"_primary_term":5}
四、文档搜索
4.1 词条匹配查询(精确匹配查询): 类似 where age = xx
php
<?php
$query = [
'term' => [
'age' => 34
]
];
$res = $es->search($userIndex, $query);
var_dump(json_encode($res, JSON_UNESCAPED_UNICODE));
// 输出: {"took":2,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":2,"relation":"eq"},"max_score":null,"hits":[{"_index":"user","_id":"4","_score":null,"_source":{"user_id":4,"nickname":"【可拆卸一拖四线】充电宝迷你自带线大容量数显快充移动电源","age":34,"create_time":"2024-07-02 03: 18: 08","phone":"19538883347"},"sort":[1719890288000,4]}]}}
4.2 多条件查询 : 类似 where xxx and xxx
php
<?php
$query = [
'bool' => [
'must' => [
[
'term' => [
'age' => 29
],
],
[
'term' => [
'phone' => '13612348888'
],
]
]
]
];
$res = $es->search($userIndex, $query);
var_dump(json_encode($res, JSON_UNESCAPED_UNICODE)); // 输出: {"took":1,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":1,"relation":"eq"},"max_score":2.3862944,"hits":[{"_index":"user","_id":"1","_score":2.3862944,"_source":{"user_id":1,"nickname":"爆款煎饼(传统双蛋煎饼+肉松+优质火腿片+配菜+薄脆)","age":29,"create_time":"2024-07-02 17:15:16","phone":"13612348888"}}]}}
4.3 通配符查询
说明:对于keyword等不支持分词的类型,模糊查询使用通配符(wildcard)查询;
php
<?php
$query = [
'wildcard' => [
"nickname" => '*肉松*' // 使用 * 作为通配符,* 可以匹配任意字符序列(包括空字符序列)。例如,*elastic* 可以匹配 elastic 、my elastic search 、elasticity 等
]
];
$res = $es->search($userIndex, $query);
var_dump(json_encode($res, JSON_UNESCAPED_UNICODE));
// 输出: {"took":3,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":2,"relation":"eq"},"max_score":1,"hits":[{"_index":"user","_id":"1","_score":1,"_source":{"user_id":1,"nickname":"爆款煎饼(传统双蛋煎饼+肉松+优质火腿片+配菜+薄脆)","age":29,"create_time":"2024-07-02 17: 15: 16","phone":"13612348888"}},{"_index":"user","_id":"5","_score":1,"_source":{"user_id":5,"nickname":"爆款煎饼(传统双蛋煎饼+肉松+优质火腿片+配菜+薄脆)","age":34,"create_time":"2024-07-03 09: 28: 29","phone":"19575621495"}}]}}
4.4 模糊查询
说明:对于text等支持分词的类型,推荐使用fuzzy进行模糊查询
php
<?php
$query = [
'fuzzy' => [
"nickname" => [
'value' => '肉松',
'fuzziness' => 1 // fuzziness 参数指定了允许的模糊程度。值越大,允许的差异越大。例如,fuzziness: 2 可能允许一些字符的替换、插入或删除
]
]
];
$res = $es->search($userIndex, $query);
var_dump(json_encode($res, JSON_UNESCAPED_UNICODE));
// 输出: {"took":3,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":2,"relation":"eq"},"max_score":0.8915597,"hits":[{"_index":"user","_id":"1","_score":0.8915597,"_source":{"user_id":1,"nickname":"爆款煎饼(传统双蛋煎饼+肉松+优质火腿片+配菜+薄脆)","age":29,"create_time":"2024-07-02 17: 15: 16","phone":"13612348888"}},{"_index":"user","_id":"5","_score":0.8915597,"_source":{"user_id":5,"nickname":"爆款煎饼(传统双蛋煎饼+肉松+优质火腿片+配菜+薄脆)","age":34,"create_time":"2024-07-03 09: 28: 29","phone":"19575621495"}}]}}
4.5 前缀查询
说明: 前缀查询通常适用于keyword类型的字段,这种类型的字段不会进行分词处理,会将整个值作为一个词条来建立索引
php
<?php
$query = [
'prefix' => [
"phone" => '195'
]
];
$res = $es->search($userIndex, $query);
var_dump(json_encode($res, JSON_UNESCAPED_UNICODE));
// 输出: {"took":3,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":3,"relation":"eq"},"max_score":1,"hits":[{"_index":"user","_id":"3","_score":1,"_source":{"user_id":3,"nickname":"网红爆款护手霜6支香味随机-K","age":23,"create_time":"2024-06-28 03: 18: 08","phone":"19512345286"}},{"_index":"user","_id":"4","_score":1,"_source":{"user_id":4,"nickname":"【可拆卸一拖四线】充电宝迷你自带线大容量数显快充移动电源","age":34,"create_time":"2024-07-02 03: 18: 08","phone":"19538883347"}},{"_index":"user","_id":"5","_score":1,"_source":{"user_id":5,"nickname":"爆款煎饼(传统双蛋煎饼+肉松+优质火腿片+配菜+薄脆)","age":34,"create_time":"2024-07-03 09: 28: 29","phone":"19575621495"}}]}}
4.6 联合查询 - and
SQL: where age in (23,34) and create_time between 2024-07-01 00:00:00 and 2024-07-02 23:59:59
php
<?php
$query = [
'bool' => [
// must(且): 数组里面的条件都要满足,该条数据才会被选择
'must' => [
[
'terms' => [
'age' => [23, 34] // 类似于 where in
]
],
[
'range' => [ // 类似 between
'create_time' => [
'gte' => '2024-07-01 00:00:00',
'lte' => '2024-07-02 23:59:59',
]
]
]
]
]
];
$res = $es->search($userIndex, $query);
var_dump(json_encode($res, JSON_UNESCAPED_UNICODE));
// 输出: {"took":3,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":1,"relation":"eq"},"max_score":2,"hits":[{"_index":"user","_id":"4","_score":2,"_source":{"user_id":4,"nickname":"【可拆卸一拖四线】充电宝迷你自带线大容量数显快充移动电源","age":34,"create_time":"2024-07-02 03: 18: 08","phone":"19538883347"}}]}}
4.7 联合查询 - or
SQL: where (age <=18 or age >=34) and between 2024-07-01 00:00:00 and 2024-07-02 23:59:59
php
<?php
$query = [
'bool' => [
"must" => [
[
'range' => [
'create_time' => [
'gte' => '2024-06-29 00:00:00',
'lte' => '2024-07-02 04:00:00',
]
]
]
],
// should(或): 数组里面的条件满足其中一个,该条数据被选择
"should" => [
[
'range' => [
'age' => [
'lte' => 18,
]
]
],
[
'range' => [
'age' => [
'gte' => 34,
]
]
]
]
]
];
$res = $es->search($userIndex, $query);
var_dump(json_encode($res, JSON_UNESCAPED_UNICODE));
// 输出: {"took":2,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":2,"relation":"eq"},"max_score":2,"hits":[{"_index":"user","_id":"2","_score":2,"_source":{"user_id":2,"nickname":"高颜值情侣玻璃浮雕吸管杯2个-xh","age":12,"create_time":"2024-06-29 12: 00: 05","phone":"13575861234"}},{"_index":"user","_id":"4","_score":2,"_source":{"user_id":4,"nickname":"【可拆卸一拖四线】充电宝迷你自带线大容量数显快充移动电源","age":34,"create_time":"2024-07-02 03: 18: 08","phone":"19538883347"}}]}}
4.8 联合查询 - or and or
SQL: where (age >= 34 or age <= 18) and (create_time >= '2024-07-02 00:00:00' or create_time <= '2024-06-30 00:00:00')
php
<?php
# 联合查询
$query = [
'bool' => [
'must' => [
[
'bool' => [
'should' => [
[
'range' => [
'age' => [
'lte' => 18,
]
]
],
[
'range' => [
'age' => [
'gte' => 34,
]
]
]
]
]
],
[
'bool' => [
'should' => [
[
'range' => [
'create_time' => [
'lte' => '2024-06-30 00:00:00',
]
]
],
[
'range' => [
'create_time' => [
'gte' => '2024-07-02 00:00:00',
]
]
]
]
]
]
]
]
];
$res = $es->search($userIndex, $query);
var_dump(json_encode($res, JSON_UNESCAPED_UNICODE));