ElasticSearch
- 官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started.html
- 非官方中文文档:https://learnku.com/docs/elasticsearch73/7.3
- 极简概括:基于Apache Lucene构建开源的分布式搜索引擎。
- 解决题目:MySQL like中文全文搜索不走索引,或大数据搜索性能低下的题目。
- 适用场景:
- 大数据检索:在大数据量的查询场景下,ES查询性能依然保持上风,常用于替代MySQL由于性能不足而做一些复杂的查询。
- 大数据开辟:大数据开辟几乎离不开Spark、Fink、Hadoop、ElasticSearch、MySQL、Redis、ZooKeeper这些组件。
- ELK结合:ES结合LK作为ELK(Elasticsearch(搜索), Logstash(采集转换), Kibana(分析))组合,可用于及时监控、分析和可视化大量日记和事件数据,如系统日记、应用程序日记、网络流量日记等。
- 优点:
- 跨平台:组件支持在Linux、Windows、MacOS上运行。
- 查询性能优异:在超大数据量的查询场景下,ES查询性能依然保持上风。
- 支持全文检索:替代MySQL中文全文检索不走索引的查询弱项。
- 生态繁荣:是面向开辟者的主流的搜索引擎,文档,解决方案,疑难杂症,非0day毛病,基本都有成熟的解决方案。
- 支持分布式:每个ES节点,都可以执行一部门搜索任务,然后将结果合并。累加的算力结果为虎傅翼。
- 支持复杂查询:支持,模糊匹配,范围查询,布尔搜索。
- 缺点:
- ES没有事务机制,对于MySQL的合作呢,也是最终一致性,以是强一致性的搜索环境下并不适用,保举Redis。
- json请求体父子格式反人类:果然技能厉害的程序员往往不会是一个好的产品司理。
- json响应体格式反人类,按照["成功或失败的code", "data数据", "msg增补说明"]这种格式返回就好了。
- PHP API经常性异常:APi接口,写操作失败返回false也行,非要返回异常,异常若没有处理,会中断程序执行。
- 查询方式受mapping限制:相比于MySQL,哪怕是个数字,都可以用like强制查询,但是ES不行。
- 同类组件:Apache Solr、Apache Lucene、Algolia、Sphinx、XunSearch。
正排索引和倒排索引
ES用的倒排索引算法。正倒两种索引都是用于快速检索数据的实现方案,我没有太官方的解释,以是举例说明:
- 正排索引:有一个文章表,有文章id、标题、详情3个字段,通过文章列表功能获取文章,通过id作为索引值获取文章内容,这是很广泛的业务逻辑。想要搜索包含指定关键词的文章,数据库就必要对文章的标题和内容逐一做对比,因为不走索引,数据量不大还好,数据量一大性能降低。
- 倒排索引:用于加快文本的检索,文章内容利用分词器拆分,将拆分好的关键词与文章id做关联,然后生存。类比MySQL表的两个列,一列是关键词,另一列是包含这个关键词的文章id,多个倒排索引数据集构成一个倒排表。再查询时,不必要针对数据源本身做查询,而是变成了,关键词为xxx的id为多少。
分词
分词就是把字符串拆分成有效的关键词,用于提供高质量搜索的数据源。
- 对英文:分词直接用空格就行,I love you,可直接利用空格分成3个词,对中文显然不适用。
- 对中文:例如“本日温度很高”,能用的词汇可以拆分成“本日”、“温度”、“很高”,可程序不知道怎么拆分,若拆分为“本日温”、“天温”、“”度很”这样的关键词就显得很怪异。
以是也就诞生了语法分析+字典的解决方案,用人工干涉+词典的方式实现分词器的逻辑。
至于利用NLP语义分析,上下文猜测,的AI模式,不属于ES的范畴,不展开。
- 若搜索关键词为语句或短语:必要利用TF-IDF和BM25算法(等更高级的算法),先对句子进行分词,然后根据这多个分词的再对结果集进行分词查询,然后评分,组合,最终返回结果。
安装ES 8.14.1
- 系统设置,用于开启防火墙,创建用户,和大数据环境下提升性能。
- Java写的组件吃内存,建议VM虚拟机内存设置大一点,系统设置为1G内存。
- 开两个端口,并重启防火墙
- firewall-cmd --add-port=9200/tcp --zone=public --permanent
- firewall-cmd --add-port=9300/tcp --zone=public --permanent
- systemctl restart firewalld
- 新建一个es用户,以非root形式运行,否则运行es会报错,java.lang.RuntimeException: can not run elasticsearch as root
- useradd -M es
- passwd es 密码为123456
- vim /etc/security/limits.conf
- 文末添加两行配置,优化文件描述符软硬限制,对提高性能非常重要,文件描述符用于标识和管理每个进程都可以打开文件的数量
- es soft nofile 65536
- es hard nofile 65536
- vim /etc/security/limits.d/20-nproc.conf
- 文末添加两行配置,优化文件描述符软硬限制,对提高性能非常重要,文件描述符用于标识和管理每个进程都可以打开文件的数量
- es soft nofile 65536
- es hard nofile 65536
- vim /etc/sysctl.conf
- 定义系统中可以同时打开的最大文件描述符数量。
- fs.file-max=655350
- 定义Linux内核中进程可以拥有的最大内存映射区域数量
- vm.max_map_count=262144
- 重启
- sysctl -p
复制代码- 下载tar包并解压,这个包地址来源于官网,并非java源码包
- wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.14.1-linux-x86_64.tar.gz
- tar zxf elasticsearch-8.14.1-linux-x86_64.tar.gz
- 更改所属的用户和用户组
- chown -R es:es elasticsearch-8.14.1
- 切换用户
- su es
- 启动ES,如果发现报错,请清空bin目录同级的data目录
- ./bin/elasticsearch
- 启动后,直到看到如下字样,说明能成功启动,但是输入它生成的用户名密码,登不进去
- 然后Ctrl + C强制停止,因为启动一次之后,config/elasticsearch.yml配置文件,会发生变化,这一步不可少
- Elasticsearch security features have been automatically configured!
- 登不进去,那就改配置
- vim config/elasticsearch.yml
- 把91~103行的true全部改为false,如下,注意配置格式,key: value之间要留出空格,否则ES不识别对应的值。
- # Enable security features
- xpack.security.enabled: false
- xpack.security.enrollment.enabled: false
- # Enable encryption for HTTP API client connections, such as Kibana, Logstash, and Agents
- xpack.security.http.ssl:
- enabled: false
- keystore.path: certs/http.p12
- # Enable encryption and mutual authentication between cluster nodes
- xpack.security.transport.ssl:
- enabled: false
- 保存退出后,清除初始化的data数据
- rm -rf elasticsearch-8.14.1/data/*
- 再次执行,并使其后台运行
- ./bin/elasticsearch -d
- 查看进程,确定ES是否成功执行
- ps aux | grep elastic
- es 49044 30.2 64.3 8291804 640416 pts/0 Sl 05:08 0:26 /test/elasticsearch-8.14.1/jdk/bin/java -Des.networkaddress.cache.ttl=60 -Des.networkaddress.cache.negative.ttl=10 -Djava.security.manager=allow -XX:+AlwaysPreTouch -Xss1m -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djna.nosys=true -XX:-OmitStackTraceInFastThrow -Dio.netty.noUnsafe=true -Dio.netty.noKeySetOptimization=true -Dio.netty.recycler.maxCapacityPerThread=0 -Dlog4j.shutdownHookEnabled=false -Dlog4j2.disable.jmx=true -Dlog4j2.formatMsgNoLookups=true -Djava.locale.providers=SPI,COMPAT --add-opens=java.base/java.io=org.elasticsearch.preallocate --add-opens=org.apache.lucene.core/org.apache.lucene.store=org.elasticsearch.vec --enable-native-access=org.elasticsearch.nativeaccess -XX:ReplayDataFile=logs/replay_pid%p.log -Djava.library.path=/test/elasticsearch-8.14.1/lib/platform/linux-x64:/usr/java/packages/lib:/usr/lib64:/lib64:/lib:/usr/lib -Djna.library.path=/test/elasticsearch-8.14.1/lib/platform/linux-x64:/usr/java/packages/lib:/usr/lib64:/lib64:/lib:/usr/lib -Des.distribution.type=tar -XX:+UnlockDiagnosticVMOptions -XX:G1NumCollectionsKeepPinned=10000000 -XX:+UseG1GC -Djava.io.tmpdir=/tmp/elasticsearch-13971958964404181235 --add-modules=jdk.incubator.vector -XX:+HeapDumpOnOutOfMemoryError -XX:+ExitOnOutOfMemoryError -XX:HeapDumpPath=data -XX:ErrorFile=logs/hs_err_pid%p.log -Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,level,pid,tags:filecount=32,filesize=64m -Xms389m -Xmx389m -XX:MaxDirectMemorySize=204472320 -XX:G1HeapRegionSize=4m -XX:InitiatingHeapOccupancyPercent=30 -XX:G1ReservePercent=15 --module-path /test/elasticsearch-8.14.1/lib --add-modules=jdk.net --add-modules=ALL-MODULE-PATH -m org.elasticsearch.server/org.elasticsearch.bootstrap.Elasticsearch
- es 49075 0.0 0.0 55180 880 pts/0 Sl 05:09 0:00 /test/elasticsearch-8.14.1/modules/x-pack-ml/platform/linux-x86_64/bin/controller
- es 49230 0.0 0.0 112828 968 pts/0 R+ 05:10 0:00 grep elastic
- 访问:
- http://IP:9200/
复制代码 设置密码(保举添加)
上文设置的是没有密码的方案,倘若服务器IP和端口对外暴露,这不是一种安全的行为。
注意,要部署集群,各个节点密码应当一致。- 注意配置格式,key: value之间要留出空格,否则ES不识别对应的值。
- vim es根目录/config/elasticsearch.yml
- 修改以下配置
- xpack.security.enabled: true
- 非root用户下启动es
- ./bin/elasticsearch -d
- 启动一个交互式命令行界面,从而设置密码,期间的几个交互,全部设置为123456
- ./bin/elasticsearch-setup-passwords interactive
- 默认用户名:elastic
- 密码:123456
复制代码 概念辅助类比
ES中有些新的概念,可通过MySQL的概念去辅助记忆。
ESMySQL备注Index(索引)库表/Type(类型)表7及以上的版本被移除,原先是对标MySQL表的理念,厥后发现这对于ES并非必须,就移除了Documents(文档)行数据/Fields(字段)字段/Mapping(映射)表结构/Shards(分片)分表顾名思义,当数据量太大单个节点都装不下的时候,就拆分到别的节点上默认页说明
- {
- "name": "lnmp",
- "cluster_name": "elasticsearch",
- "cluster_uuid": "k61PBMDqTKO31rZeV-ENGA",
- "version": {
- "number": "8.14.1",
- "build_flavor": "default",
- "build_type": "tar",
- "build_hash": "93a57a1a76f556d8aee6a90d1a95b06187501310",
- "build_date": "2024-06-10T23:35:17.114581191Z",
- "build_snapshot": false,
- "lucene_version": "9.10.0",
- "minimum_wire_compatibility_version": "7.17.0",
- "minimum_index_compatibility_version": "7.0.0"
- },
- "tagline": "You Know, for Search"
- }
- "name": "lnmp":系统标识
- "cluster_name": "elasticsearch":Elasticsearch 集群的名称为 “elasticsearch”。
- "cluster_uuid": "k61PBMDqTKO31rZeV-ENGA":Elasticsearch集群的唯一标识符。
- "version":版本信息:
- "number": 版本号
- "build_flavor": "default":构建的类型,这里是默认的。
- "build_type": "tar":构建类型为 tar 包。
- "build_hash": "93a57a1a76f556d8aee6a90d1a95b06187501310":构建的哈希值,用于唯一标识这个特定的构建。
- "build_date": "2024-06-10T23:35:17.114581191Z":构建的日期和时间。
- "build_snapshot": false:表示这个构建不是一个快照版本。
- "lucene_version": "9.10.0":基于Lucene 9.10.0的版本。
- "minimum_wire_compatibility_version": "7.17.0":最低兼容的网络传输版本。
- "minimum_index_compatibility_version": "7.0.0":最低兼容的索引版本。
- "tagline": "You Know, for Search":Elasticsearch 的标语,说明其用途是进行搜索。
复制代码 索引增删查操作
- {
- "acknowledged": true,
- "shards_acknowledged": true,
- "index": "zs_index"
- }
- "acknowledged": true:指示请求是否被成功接受和处理。
- "shards_acknowledged": true:指示所有分片是否已经确认请求。
- "index": "zs_index":这表示操作涉及的索引名称为 “zs_index”。
复制代码- {
- "error": {
- "root_cause": [
- {
- "type": "resource_already_exists_exception",
- "reason": "index [zs_index/dCMAgdlqTeaihB4JSH1gNw] already exists",
- "index_uuid": "dCMAgdlqTeaihB4JSH1gNw",
- "index": "zs_index"
- }
- ],
- "type": "resource_already_exists_exception",
- "reason": "index [zs_index/dCMAgdlqTeaihB4JSH1gNw] already exists",
- "index_uuid": "dCMAgdlqTeaihB4JSH1gNw",
- "index": "zs_index"
- },
- "status": 400
- }
- "error":这个对象包含了发生的错误信息。
- "root_cause":根本原因的数组,指示导致问题的具体原因。
- "type": "resource_already_exists_exception":错误的类型,表示尝试创建的索引已经存在。
- "reason": "index [zs_index/dCMAgdlqTeaihB4JSH1gNw] already exists":错误的详细原因,指明索引 “zs_index” 和其唯一标识符 “dCMAgdlqTeaihB4JSH1gNw” 已经存在。
- "index_uuid": "dCMAgdlqTeaihB4JSH1gNw":已存在索引的 UUID。
- "index": "zs_index":已存在索引的名称。
- "type": "resource_already_exists_exception":总体错误类型,与根本原因相同。
- "reason": "index [zs_index/dCMAgdlqTeaihB4JSH1gNw] already exists":再次指明索引已经存在的原因。
- "index_uuid": "dCMAgdlqTeaihB4JSH1gNw":重复指定已存在索引的 UUID。
- "index": "zs_index":重复指定已存在索引的名称。
- "status": 400:HTTP 状态码,表示客户端请求错误
复制代码- {
- "zs_index": {
- "aliases": {},
- "mappings": {},
- "settings": {
- "index": {
- "routing": {
- "allocation": {
- "include": {
- "_tier_preference": "data_content"
- }
- }
- },
- "number_of_shards": "1",
- "provided_name": "zs_index",
- "creation_date": "1719699272706",
- "number_of_replicas": "1",
- "uuid": "dCMAgdlqTeaihB4JSH1gNw",
- "version": {
- "created": "8505000"
- }
- }
- }
- }
- }
- "aliases": {}:索引的别名列表为空,表示该索引当前没有别名。
- "mappings": {}:索引的映射为空对象,即没有定义特定的字段映射。
- "settings":索引的设置信息:
- "index":
- "routing":
- "allocation":
- "include":
- "_tier_preference": "data_content":指定索引分配时偏好的数据内容层级。
- "number_of_shards": "1":该索引被分成了一个分片。
- "provided_name": "zs_index":索引的提供的名称为 “zs_index”。
- "creation_date": "1719699272706":索引的创建日期的时间戳形式。
- "number_of_replicas": "1":该索引有一个副本。
- "uuid": "dCMAgdlqTeaihB4JSH1gNw":索引的唯一标识符 UUID。
- "version":
- "created": "8505000":索引的版本信息,表示索引在 Elasticsearch 版本 “8505000” 中创建。
复制代码
- 检察所有索引:
GET请求 IP:9200/_cat/indices?v
- health status index uuid pri rep docs.count docs.deleted store.size pri.store.size dataset.size
- yellow open zs_index dCMAgdlqTeaihB4JSH1gNw 1 1 0 0 249b 249b 249b
- health: 索引的健康状态,此处为 “yellow”,表示所有预期的分片都可用,但副本尚未分配。
- status: Elasticsearch 的状态指示符,这里是 “open”,表示索引是打开状态,可以接收读写操作。
- index: 索引名。
- uuid: 索引的唯一标识符。
- pri: 主分片数为 1,即索引被分成了一个主分片。
- rep: 副本数为 1,表示每个主分片有一个副本。
- docs.count: 文档数量为 0,当前索引中的文档总数。
- docs.deleted: 已删除的文档数量为 0。
- store.size: 存储大小为 249b,索引占用的物理存储空间。
- pri.store.size: 主分片的存储大小,也是 249b。
- dataset.size: 数据集大小为 249b,即索引的数据集大小。
复制代码
- 删除索引 DELETE方式 IP:9200/索引名
- {
- "acknowledged": true
- }
- 返回true表示成功执行。
复制代码 文档增编削查操作
- 增文档(数据):
方式1:POST请求 IP:9200/索引名/_doc/可选参数,数据唯一标识
方式2:PUT请求 IP:9200/索引名/_create/必填唯一标识符 由于方式2的put请求是幂等,以是再次请求会报错
- 这是存入的数据
- {
- "id":1,
- "content":"C是世界上最好的编程语言"
- }
- 这是返回的数据,若用户指定id,则id处显示的是用户指定的id
- {
- "_index": "zs_index",
- "_id": "0mMsZpABZdTHCHXLZQhu",
- "_version": 1,
- "result": "created",
- "_shards": {
- "total": 2,
- "successful": 1,
- "failed": 0
- },
- "_seq_no": 0,
- "_primary_term": 1
- }
- "_index": "zs_index": 表示文档被添加到了名为zs_index的索引中。
- "_id": "0mMsZpABZdTHCHXLZQhu": 是新添加的文档的ID。在Elasticsearch中,每个文档都有一个唯一的ID,用于唯一标识和检索该文档。
- "_version": 1: 表示该文档的版本号是1。每当文档被更新时,版本号会增加,这有助于跟踪文档的更改历史。
- "result": "created": 表示操作的结果是创建了一个新的文档。
- "_shards": 这个字段提供了关于索引操作的分片信息。
- "total": 2: 表示总共有2个分片参与了这次索引操作(通常是一个主分片和其副本)。
- "successful": 1: 表示有1个分片成功完成了索引操作。在yellow健康状态的索引中,这通常意味着主分片成功了,但副本分片可能还没有数据(因为它是yellow状态,副本可能还没有分配或同步)。
- "failed": 0: 表示没有分片失败。
- "_seq_no": 0: 是文档在Lucene段中的序列号,用于在内部跟踪文档的版本和顺序。
- "_primary_term": 1: 主要术语(primary term)是与_seq_no一起使用的,用于确保文档版本的一致性,特别是在主节点更换时。
复制代码
- 改文档(数据):
方式1(用于覆盖老数据):POST请求 IP:9200/索引名/_doc/唯一标识
方式2(用于覆盖老数据):PUT请求 IP:9200/索引名/_doc/唯一标识
方式3(用于修改局部数据):POST请求 IP:9200/索引名/_update/唯一标识
- 方式1,若有id号,再次执行增文档操作,可自动将create操作编程update操作。
- 更新数据
- {
- "id":1,
- "content":"C是世界上最好的编程语言"
- }
- 方式2,请求内容同方式1
- 方式3,因为要修改局部数据,所以必须告知ES修改那块的局部数据,以下:第一层花括号和doc是固定格式。
- {
- "doc" : {
- "content": "C是最好的编程语言"
- }
- }
- 3种方式的响应格式一致:
- {
- "_index": "zs_index",
- "_id": "1",
- "_version": 17,
- "result": "updated",
- "_shards": {
- "total": 2,
- "successful": 1,
- "failed": 0
- },
- "_seq_no": 24,
- "_primary_term": 1
- }
- "_index": "zs_index": 表示被更新的文档位于名为zs_index的索引中。
- "_id": "1": 是被更新的文档的唯一ID。
- "_version": 17: 表示该文档的版本号已更新为17。版本号在每次更新时增加,用于跟踪文档的变化历史。
- "result": "updated": 表示更新操作已成功执行,文档被更新了。
- "_shards": 提供了关于更新操作涉及的分片信息。
- "total": 2: 表示总共有2个分片参与了更新操作(通常是一个主分片和其副本)。
- "successful": 1: 表示有1个分片成功完成了更新操作。在yellow健康状态的索引中,这意味着主分片成功了,但副本分片可能尚未同步数据。
- "failed": 0: 表示没有分片失败。
- "_seq_no": 24: 是文档在Lucene段中的序列号,用于内部跟踪文档版本和顺序。
- "_primary_term": 1: 主要术语(primary term)与_seq_no一起使用,确保文档版本的一致性,特别是在主节点更换时。
复制代码
- 查询单条数据:
GET请求 IP:9200/索引名/_doc/唯一标识
- {
- "_index": "zs_index",
- "_id": "1",
- "_version": 8,
- "_seq_no": 14,
- "_primary_term": 1,
- "found": true,
- "_source": {
- "id": 1,
- "content": "C是世界上最好的编程语言"
- }
- }
- "_seq_no": 14: 是文档在Lucene段中的序列号,用于内部跟踪文档版本和顺序。
- "_primary_term": 1: 主要术语(primary term)与_seq_no一起使用,确保文档版本的一致性,尤其是在主节点更换时。
- "found": true: 表示Elasticsearch成功找到了指定ID的文档,若为false,表示未找到。
- "_source": 包含了文档的实际内容。
复制代码
- 查询多条数据:
GET请求 IP:9200/索引名/_search
- {
- "took": 137,
- "timed_out": false,
- "_shards": {
- "total": 1,
- "successful": 1,
- "skipped": 0,
- "failed": 0
- },
- "hits": {
- "total": {
- "value": 8,
- "relation": "eq"
- },
- "max_score": 1,
- "hits": [
- {
- "_index": "zs_index",
- "_id": "1",
- "_score": 1,
- "_source": {
- "id": 1,
- "content": "C是世界上最好的编程语言"
- }
- },
- {
- "_index": "zs_index",
- "_id": "02M0ZpABZdTHCHXLjAgN",
- "_score": 1,
- "_source": {
- "id": 1,
- "content": "C是世界上最好的编程语言"
- }
- }
- ]
- }
- }
- "took": 137: 表示搜索操作耗费了137毫秒。
- "timed_out": false: 表示搜索操作未超时。
- "_shards": 提供了关于搜索操作涉及的分片信息。
- "total": 1: 表示总共有1个分片参与了搜索操作。
- "successful": 1: 表示所有参与的分片都成功完成了搜索。
- "skipped": 0: 表示没有分片被跳过。
- "failed": 0: 表示没有分片失败。
- "hits": 包含了搜索结果的详细信息。
- "total": {"value": 8, "relation": "eq"}: 表示符合搜索条件的文档总数为8个。
- "value": 8: 具体的文档数。
- "relation": "eq": 表示与总数值相等,即已经获取了所有匹配的文档。
- "hits"数组: 包含了每个匹配文档的详细信息。
- 每个文档对象包括了:
- "_index": "zs_index": 文档所属的索引名称。
- "_id": 文档的唯一ID。
- "_score": 1: 文档的匹配分数,此处为1(最高分)。
- "_source": 包含了文档的实际内容。
复制代码
- 删除数据
DELETE请求 IP:9200/索引名/_doc/唯一标识
- {
- "_index": "zs_index",
- "_id": "1",
- "_version": 24,
- "result": "not_found",
- "_shards": {
- "total": 2,
- "successful": 1,
- "failed": 0
- },
- "_seq_no": 31,
- "_primary_term": 1
- }
- "result": "not_found": 表示更新操作未找到指定的文档,若是deleted,表示成功删除。
- _shards": 提供了关于更新操作涉及的分片信息。
- "total": 2: 表示总共有 2 个分片参与了更新操作(通常是一个主分片和其副本)。
- "successful": 1: 表示有 1 个分片成功完成了更新操作。在索引状态为 yellow 时,这可能意味着主分片成功了,但副本分片可能尚未同步数据。
- "failed": 0: 表示没有分片失败。
- "_seq_no": 31: 是文档在 Lucene 段中的序列号,用于内部跟踪文档版本和顺序。
- "_primary_term": 1: 主要术语(primary term)与 _seq_no 一起使用,确保文档版本的一致性,特别是在主节点更换时。
复制代码 文档复杂查询操作
- 通过关键词查询:
方式1:GET请求 IP:9200/索引名/_search?q=文档字段名:要搜索的关键字
方式2:GET请求 IP:9200/索引名/_search
并添加请求body{ "query":{ "match": { "文档字段名":"要搜索的关键字" } } }
- {
- "took": 8,
- "timed_out": false,
- "_shards": {
- "total": 1,
- "successful": 1,
- "skipped": 0,
- "failed": 0
- },
- "hits": {
- "total": {
- "value": 6,
- "relation": "eq"
- },
- "max_score": 0.074107975,
- "hits": [
- {
- "_index": "zs_index",
- "_id": "0mMsZpABZdTHCHXLZQhu",
- "_score": 0.074107975,
- "_source": {
- "id": 1,
- "content": "C是世界上最好的编程语言"
- }
- }
- ]
- }
- }
- took: 查询花费的时间,单位为毫秒。在这个例子中,值为8,表示查询执行花费了8毫秒时间。
- timed_out: 表示查询是否超时。在这个例子中,值为false,表示查询未超时。
- _shards: 分片相关信息,包括:
- total: 总分片数,这里是1个分片。
- successful: 成功的分片数,这里是1个分片。
- skipped: 被跳过的分片数,这里是0个分片。
- failed: 失败的分片数,这里是0个分片。
- hits: 查询命中的结果集信息,包含:
- total: 总命中数,这里是6。
- max_score: 结果集中最高得分,这里是0.074107975。
- hits: 包含具体的命中文档数组。
- 每个文档包含以下信息:
- _index: 文档所在的索引。
- _id: 文档的唯一标识符。
- _score: 文档的得分。
- _source: 存储实际数据的字段。
复制代码
- 分页查询:
GET请求 IP:9200/索引名/_search
body体添加{ "query": { "match": { "文档字段名":"要搜索的关键字" } }, "from":0, "size":2 }
其中,from为起始位置偏移量,size为每页表现的条数。
from算法:(页码 -1)* size = form。
第1页:(1 - 1)* 2 = 0,以是from为0。
第2页:(2 - 1)* 2 = 2,以是from为2。
响应结果同上。
- 只表现数据的部门字段:
GET请求 IP:9200/索引名/_search
body体添加_source项即可{ "query": { "match": { "文档字段名":"要搜索的关键字" } }, "_source":["id"] }
响应结果同上。
- 排序:
GET请求 IP:9200/索引名/_search
body体添加sort项即可 { "query": { "match": { "文档字段名":"要搜索的关键字" } }, "sort":{ "排序的字段名":{ "order":"asc" } } }
注意,这个将要排序的字段,可以不被展示出来也能排序(_source控制项)
响应结果同上。
- 多条件and或or查询,区间查询
GET请求 IP:9200/索引名/_search
如下,需添加以下body,表示查询content字段为C语言和(&&)C++语言(C++语言会被拆分),并且content>1(随意测试)的数据。
若替换must为should,则表示或(or)之意。
- {
- "query": {
- "bool": {
- "must": [
- {
- "match": {
- "content": "C语言"
- }
- },
- {
- "match": {
- "content": "C++语言"
- }
- }
- ],
- "filter": {
- "range": {
- "content": {
- "gt": 1
- }
- }
- }
- }
- }
- }
复制代码 响应结果同上。
- 全文精准匹配
GET请求 IP:9200/索引名/_search
仍需添加如下body{ "query":{ "match_phrase" :{ "字段名":"要搜索的关键字" } } }
响应结果同上。
- 查询到的结果高亮表现
GET请求 IP:9200/索引名/_search
仍需添加如下body{ "query":{ "match_phrase" :{ "字段名":"要搜索的关键字" } }, "highlight":{ "fields":{ "字段名":{} } } }
响应结果同上。
聚合查询
- {
- "took": 35,
- "timed_out": false,
- "_shards": {
- "total": 1,
- "successful": 1,
- "skipped": 0,
- "failed": 0
- },
- "hits": {
- "total": {
- "value": 6,
- "relation": "eq"
- },
- "max_score": null,
- "hits": []
- },
- "aggregations": {
- "id_group_avg": {
- "value": 1
- }
- }
- }
- took: 查询花费的时间,单位是毫秒。这里是 35 毫秒。
- timed_out: 查询是否超时。这里显示为 false,表示查询在规定时间内完成。
- _shards: 这个对象提供关于查询在分片上的执行情况的详细信息:
- total: 总分片数。
- successful: 成功完成查询的分片数。
- skipped: 跳过的分片数。
- failed: 查询失败的分片数。
- 在这个例子中,总分片数为 1,且成功完成了查询。
- hits: 包含有关查询匹配的文档信息:
- total: 文档匹配的总数。
- value: 匹配的文档数,这里是 6。
- relation: 匹配关系,这里是 “eq” 表示精确匹配。
- max_score: 最高得分,如果不需要计算得分则为 null。
- hits: 实际匹配的文档数组。在这个例子中是空的,因为没有具体的文档数据。
- aggregations: 聚合结果信息:
- id_group_avg: 聚合名称,这里的值为 1。具体的聚合结果会根据你的查询和聚合定义而有所不同。
复制代码 分词与不分词的控制
这块由于涉及到字段的改动,以是必要重新建立索引,并且添加了映射(mapping)的概念- 重新建立一个people索引
- PUT请求 IP:9200/people
- 再次请求,添加映射
- IP:9200/people/_mapping
- {
- "properties" :{
- "name" : {
- "type":"text",
- "index":true
- },
- "sex" : {
- "type":"keyword",
- "index":true
- },
- "tel" : {
- "type":"keyword",
- "index":false
- }
- }
- }
- 上方的index指的是是否为这条数据添加索引。
- type是索引类型,text代表支持分词查询(MySQL like '%kw%'),keyword代表不可分词查询 (MySQL = 'kw')。
- 然后添加三条数据
- PUT IP:9200/people/_create/1
- {
- "name":"张三",
- "sex":"男性",
- "tel":"18888888888"
- }
- PUT IP:9200/people/_create/2
- {
- "name":"李四",
- "sex":"女性",
- "tel":"16666666666"
- }
- PUT IP:9200/people/_create/3
- {
- "name":"王五",
- "sex":"男性",
- "tel":"18866668888"
- }
- 搜索
- GET IP:9200/people/_search
- {
- "query" :{
- "match" :{
- "sex" : "男" 把性去掉,搜索不到数据
- }
- }
- }
- GET IP:9200/people/_search
- {
- "query" :{
- "match" :{
- "name" : "张" 把三去掉,可以搜索到数据
- }
- }
- }
- GET IP:9200/people/_search
- {
- "query" :{
- "match" :{
- "tel" : "188" 若输入手机号前3位,则搜不到数据,输入完整的手机号,则可以搜索到数据
- }
- }
- }
复制代码 PHP Api调用
官方文档:https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/getting-started-php.html#_installation
某些ES Api(例如创建索引)不能重复执行,重复执行会报错,以是在执行写操作的上游做判断,大概使用try catch。- composer require elasticsearch/elasticsearch
- 推荐安装symfony/var-dumper,用于dd()或dump()执行,美化输出。
- 新建PHP文件,以下代码数据为公共部分。
- include './vendor/autoload.php';
- use Elastic\Elasticsearch\ClientBuilder;
- //连接ES
- $client = ClientBuilder::create()->setHosts(['192.168.0.183:9200'])->build();
- //若es有密码,则需要添加一个setBasicAuthentication()方法。
- $client = ClientBuilder::create()->setHosts(['192.168.0.183:9200'])->setBasicAuthentication('elastic', '123456')->build();
复制代码 PHP ES Api针对Index增编削查
- 返回bool
- $response = $client->indices()->create([
- 'index' => 'php_index'
- ]);
- $response->asBool();
复制代码- 返回bool
- $response = $client->indices()->exists(['index' => 'php_index']);
- dd($response->asBool());
复制代码- 返回array
- $response = $client->indices()->get(['index' => 'php_index']);
- dd($response->asArray());
复制代码- 返回bool
- $response = $client->indices()->delete(['index' => 'php_index']);
- dd($response->asBool());
复制代码 PHP ES Api针对Mapping增编削查
- 返回bool
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'properties' => [
- 'name' => [
- 'type' => 'text',
- ],
- ]
- ]
- ];
- $response = $client->indices()->putMapping($params);
- dd($response->asBool());
复制代码- 返回bool
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'mappings' => [
- 'properties' => [
- 'title' => [
- 'type' => 'text',
- ],
- 'content' => [
- 'type' => 'text',
- ],
- ]
- ]
- ]
- ];
- $response = $client->indices()->create($params);
- dd($response->asBool());
复制代码- 返回数组
- $response = $client->indices()->getMapping();
- dd($response->asArray());
复制代码- 返回数组
- $response = $client->indices()->getMapping(['index' => 'php_index']);
- dd($response->asArray());
复制代码
- 删
请直接删除索引。
- 改
请重新建立索引,在新索引基础上做映射的修改。
PHP ES Api针对Doc增编削
- 准备四个直辖市的名称,简介,人口和面积大小。
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'mappings' => [
- 'properties' => [
- 'city' => [
- 'type' => 'keyword',
- ],
- 'description' => [
- 'type' => 'text',
- ],
- 'population' => [
- 'type' => 'integer'
- ],
- 'area' => [
- 'type' => 'integer'
- ],
- ]
- ]
- ]
- ];
- $response = $client->indices()->create($params);
- dd($response->asArray());
复制代码
- 增 单条 请记忆这4个直辖市的数据生存格式,下文基本每个演示都要用
一级数组下有个id属性,若省去,ES会默认给这条数据加一个id。不保举。保举使用MySQL的数据id作为ES的id。
- 返回bool
- $params = [
- 'index' => 'php_index',
- 'id' => 1,
- 'body' => [
- 'id' => 1,
- 'city' => '北京市',
- 'description' => '北京市(Beijing),简称“京”,古称燕京、北平,是中华人民共和国首都、直辖市、国家中心城市、超大城市, 国务院批复确定的中国政治中心、文化中心、国际交往中心、科技创新中心, 中国历史文化名城和古都之一,世界一线城市',
- 'population' => '2186',
- 'area' => '16411',
- ]
- ];
- $response = $client->index($params);
- dd($response->asBool());
- 再增加3条数据
- $params = [
- 'index' => 'php_index',
- 'id' => 2,
- 'body' => [
- 'id' => 2,
- 'city' => '上海市',
- 'description' => '上海市(Shanghai City),简称“沪” ,别称“申”,中华人民共和国直辖市、国家中心城市、超大城市、上海大都市圈核心城市、国家历史文化名城 [206],是中国gcd的诞生地。上海市入围世界Alpha+城市, 基本建成国际经济、金融、贸易、航运中心,形成具有全球影响力的科技创新中心基本框架。截至2022年12月底,上海市辖16个区,107个街道、106个镇、2个乡。',
- 'population' => '2487',
- 'area' => '6341',
- ]
- ];
- $params = [
- 'index' => 'php_index',
- 'id' => 3,
- 'body' => [
- 'id' => 3,
- 'city' => '天津市',
- 'description' => '天津市(Tianjin City),简称“津”,别称津沽、津门,是中华人民共和国省级行政区、直辖市、国家中心城市、超大城市 [222],地处中华人民共和国华北地区,海河流域下游,东临渤海,北依燕山,西靠首都北京市,其余均与河北省相邻。截至2023年10月,天津市共辖16个区。',
- 'population' => '1364',
- 'area' => '11966',
- ]
- ];
- $params = [
- 'index' => 'php_index',
- 'id' => 4,
- 'body' => [
- 'id' => 4,
- 'city' => '重庆市',
- 'description' => '重庆市,简称“渝”, 别称山城、江城,是中华人民共和国直辖市、国家中心城市、超大城市,国务院批复的国家重要中心城市之一、长江上游地区经济中心, 国际消费中心城市,全国先进制造业基地、西部金融中心、西部科技创新中心、 国际性综合交通枢纽城市和对外开放门户,辖38个区县',
- 'population' => '3191',
- 'area' => '82400',
- ]
- ];
复制代码- 返回数组
- //假设MySQL查询出来的数据如下
- $mysql_data = [
- [
- 'id' => 1024,
- 'city' => 'xx市',
- 'description' => 'xxxx',
- 'population' => '6666',
- 'area' => '6666',
- ],
- [
- 'id' => 1025,
- 'city' => 'yy市',
- 'description' => 'yyyy',
- 'population' => '8888',
- 'area' => '8888',
- ]
- ];
- //由于ES插入的要求,需要将插入数据的格式转化,为此可以封装一个方法
- function esBatchInsert($index_name, $mysql_data) {
- $params = [];
- foreach($mysql_data as $v) {
- $params['body'][] = ['index' => ['_index' => $index_name, '_id' => $v['id']],];
- $params['body'][] = $v;
- }
- return $params;
- }
- $response = $client->bulk(esBatchInsert('php_index', $mysql_data));
- dd($response->asArray());
- 可根据返回的数据再次循环,排查失败掉的漏网之鱼
复制代码- 返回bool
- $params = [
- 'index' => 'php_index',
- 'id' => '1025'
- ];
- $response = $client->delete($params);
- dd($response->asBool());
复制代码- 方式1:
- 返回mixed
- for($i = 1000; $i < 1050; $i++) { //模拟要删除这些数据
- $params = [
- 'index' => 'php_index',
- 'id' => $i
- ];
- if(! $client->exists($params)->asBool()) {
- continue;
- }
- $response = $client->delete($params)->asBool();
- if(! $response) {
- //若删除失败,请添加其它操作,记录日志或存入队列,进行重试或者人工介入
- }
- }
- 方式2:
- 返回mixed
- for($i = 1000; $i < 1050; $i++) { //模拟要删除这些数据
- $params['body'][] = [
- 'delete' => [
- '_index' => 'php_index',
- '_id' => $i,
- ]
- ];
- }
- $response = $client->bulk($params)->asArray();
- if ($response['errors']) {
- foreach ($response['items'] as $item) {
- if (isset($item['delete']['status']) && ($item['delete']['status'] != 200)) {
- //若删除失败,请添加其它操作,记录日志或存入队列,进行重试或者人工介入
- }
- }
- } else {
- echo "批量删除成功!";
- }
复制代码- 返回bool
- $params = [
- 'index' => 'php_index',
- 'id' => 1,
- 'body' => [
- 'script' => [
- 'source' => 'ctx._source.remove(params.field)',
- 'params' => [
- 'field' => '要删除的字段名'
- ]
- ]
- ]
- ];
- $response = $client->update($params);
- dd($response->asBool());
复制代码- 返回bool
- $params = [
- 'index' => 'php_index',
- 'id' => 1,
- 'body' => [
- 'doc' => [
- 'city' => '北京' //这里是要修改的字段,把北京市改为北京
- ]
- ]
- ];
- $response = $client->update($params);
- dd($response->asBool());
复制代码- 返回bool
- 官方文档演示有误,请按照以下正确写法。
- $params = [
- 'index' => 'php_index',
- 'id' => 1,
- 'body' => [
- 'script' => [
- //表达式
- 'source' => 'ctx._source.population += params.population', //给北京人口加4万,population为自定义文档字段,其余字符固定写法。
- //表达式所使用的变量
- 'params' => [
- 'population' => 4
- ],
- ],
- ]
- ];
- $response = $client->update($params);
- dd($response->asBool());
复制代码- $params = [
- 'index' => 'php_index',
- 'id' => 60, //若id对应的文档不存在,则利用upsert段的数据,重新生成一个id为60的文档。
- 'body' => [
- 'doc' => [
- 'city' => '台北市'
- ],
- 'upsert' => [
- 'append_field' => 1
- ],
- ]
- ];
- $response = $client->update($params);
- dd($response->asBool());
复制代码- //假设以下数据时数据表中查询出来的字段,要修改以下内容
- $mysql_data = [
- ['id' => 1, 'city' => '北京'],
- ['id' => 2, 'city' => '上海'],
- ];
- //可以封装一个方法,格式化数据
- function esBatchUpdate($index_name, $update_data) {
- if(! $update_data) {
- return [];
- }
- $arr = [];
- foreach($update_data as $v) {
- $arr[] = ['update' => ['_index' => $index_name, '_id' => $v['id']]];
- unset($v['id']);
- $arr[] = ['doc' => $v];
- }
- return ['body' => $arr];
- }
- $response = $client->bulk(esBatchUpdate('php_index', $mysql_data));
- $response = $response->asArray();
- //处理
- if ($response['errors']) {
- foreach ($response['items'] as $item) {
- if (isset($item['update']['status']) && ($item['update']['status'] != 200)) {
- //若删除失败,请添加其它操作,记录日志或存入队列,进行重试或者人工介入
- }
- }
- } else {
- echo "批量删除成功!";
- }
复制代码- $params = [
- 'index' => 'php_index',
- 'id' => '1',
- 'body' => [
- 'doc' => [
- 'new_field' => 'new_value'
- ],
- ]
- ];
- $response = $client->update($params);
复制代码- 返回bool
- $params = [
- 'index' => 'php_index',
- 'id' => 1,
- 'body' => [
- 'script' => [
- 'source' => 'ctx._source.remove(params.field)',
- 'params' => [
- 'field' => '要删除的字段名'
- ]
- ]
- ]
- ];
- $response = $client->update($params);
- dd($response->asBool());
复制代码 PHP ES Api针对Doc高级查询
查询关键词官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/term-level-queries.html
- 返回string
- $params = [
- 'index' => 'php_index',
- 'id' => 1,
- ];
- $response = $client->get($params);
- echo $response->asString();
- 得到以下结果
- {
- "_index": "php_index",
- "_id": "1",
- "_version": 1,
- "_seq_no": 0,
- "_primary_term": 1,
- "found": true,
- "_source": {
- "id": 1,
- "city": "北京市",
- "description": "北京市(Beijing),简称“京”,古称燕京、北平,是中华人民共和国首都、直辖市、国家中心城市、超大城市, 国务院批复确定的中国政治中心、文化中心、国际交往中心、科技创新中心, 中国历史文化名城和古都之一,世界一线城市",
- "population": "2186",
- "area": "16411"
- }
- }
复制代码- 返回array
- $response['hits']['total']['value']可获取条数
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'match_all' => new StdClass
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
复制代码- 返回数组
- $response['hits']['total']['value']可获取条数
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'ids' => [
- 'values' => [1, 2]
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
复制代码- 返回数组
- //传输的页码
- $page = 2;
- $size = 2;
- //偏移量算法
- $offset = ($page -1 ) * $size;
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'from' => $offset,
- 'size' => $size,
- // 可以添加其他查询条件
- 'query' => [
- 'match_all' => new \stdClass()
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
复制代码- 返回数组
- $params = [
- 'index' => 'php_index',
- 'body' => [
- '_source' => ['description'], //自定义字段
- 'query' => [
- 'match_all' => new \stdClass()
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
复制代码- 返回bool
- $params = [
- 'index' => 'php_index',
- 'id' => 10
- ];
- $response = $client->exists($params);
复制代码- 返回int
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'match_all' => new StdClass
- ]
- ]
- ];
- $response = $client->count($params);
- dd($response->asArray()['count'] ?? 0);
复制代码- 返回string
- echo "";
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'match' => [
- 'description' => '北京' //返回该字段含有北京或北或京的文字。
- ]
- ],
- 'highlight' => [
- 'fields' => [
- 'city' => ['pre_tags' => ['<em>'], 'post_tags' => ['</em>'],], //配置要高亮的字段
- 'description' => ['pre_tags' => ['<em>'], 'post_tags' => ['</em>'],] //配置要高亮的字段
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- print_r($response->asString());
- 返回格式如下,具体要用那个字段,看具体需求
- {
- "took":13,
- "timed_out":false,
- "_shards":{
- "total":1,
- "successful":1,
- "skipped":0,
- "failed":0
- },
- "hits":{
- "total":{
- "value":2,
- "relation":"eq"
- },
- "max_score":2.9070516,
- "hits":[
- {
- "_index":"php_index",
- "_id":"1",
- "_score":2.9070516,
- "_source":{
- "id":1,
- "city":"北京",
- "description":"北京市(Beijing),简称“京”,古称燕京、北平,是中华人民共和国首都、直辖市、国家中心城市、超大城市, 国务院批复确定的中国政治中心、文化中心、国际交往中心、科技创新中心, 中国历史文化名城和古都之一,世界一线城市",
- "population":2198,
- "area":"16411",
- "new_field":"new_value"
- },
- "highlight":{
- "description":["<em>北</em><em>京</em>市(Beijing),简称“<em>京</em>”,古称燕<em>京</em>、<em>北</em>平,是中华人民共和国首都、直辖市、国家中心城市、超大城市, 国务院批复确定的中国政治中心、文化中心、国际交往中心、科技创新中心, 中国历史文化名城和古都之一"]
- }
- },
- {
- "_index":"php_index",
- "_id":"3",
- "_score":2.5460577,
- "_source":{
- "id":3,
- "city":"天津市",
- "description":"天津市(Tianjin City),简称“津”,别称津沽、津门,是中华人民共和国省级行政区、直辖市、国家中心城市、超大城市 [222],地处中华人民共和国华北地区,海河流域下游,东临渤海,北依燕山,西靠首都北京市,其余均与河北省相邻。截至2023年10月,天津市共辖16个区。",
- "population":"1364",
- "area":"11966"
- },
- "highlight":{
- "description":["天津市(Tianjin City),简称“津”,别称津沽、津门,是中华人民共和国省级行政区、直辖市、国家中心城市、超大城市 [222],地处中华人民共和国华<em>北</em>地区,海河流域下游,东临渤海,<em>北</em>依燕山,西靠首都<em>北</em><em>京</em>市",",其余均与河<em>北</em>省相邻。"]
- }
- }
- ]
- }
- }
复制代码
- 限量 可参考分页逻辑(类比MySQL limit)
- 返回array
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'match_all' => new stdClass
- ],
- 'from' => 0,
- 'size' => 1,
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
复制代码
- 定值查找 (类比MySQL wher filed = ‘kw’)
keyword 或 integer 等非分词字段:可用 term 准确匹配。如果字段是 text 类型,那么 term 查询无法找到预期的匹配结果。
text 类型并且你想要准确匹配,可以使用 match_phrase 查询
- 方式1 针对integer字段的精准匹配
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'term' => [
- 'city' => '北京市' //北京或北或京无法查询出指定数据
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
复制代码
- 分词查找(类比MySQL where filed like '%kw%' or filed like '%k%' or filed like '%w%')
- 方式1
- 返回array
- 这种方式仅支持text类型
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'match' => [
- 'description' => '北京'
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
- 方式2
- 返回array
- 非text类型,可手动分词
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'bool' => [
- 'should' => [ //or
- [
- 'match' => ['city' => '北京']
- ],
- [
- 'match' => ['city' => '北京市']
- ]
- ],
- 'minimum_should_match' => 1
- //minimum_should_match 设置为 1,表示至少需要匹配一个 should 子句中的条件
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
复制代码
- 模糊匹配 (类比MySQL where filed like '%kw%')wildcard性能可能不如别的类型的查询,如match查询,因为wildcard查询必要对每个文档的字段值进行模式匹配
- 方式1,针对keyword mapping
- 返回array
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'wildcard' => [
- 'city' => '*北京*' //*表示任意字符,?表示任意一个字符
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
- 方式2,针对text mapping,并非严格意义上的MySQL where filed like '%kw%',而是 where filed like '%kw%' or filed like '%k%' or filed like '%w%'
- 返回array
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'match' => [
- 'description' => '北京'
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
复制代码
- 前缀查找 (类比MySQL where filed like 'kw%')针对keyword类型的字段有效
- 返回array
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'prefix' => [
- 'city' => '北'
- ]
- ]
- ]
- ];
- $response = $client->search($params);
复制代码
- 后缀查找 (类比MySQL where filed like '%kw')针对keyword字段有效
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'wildcard' => [
- 'city' => '*京市'
- ]
- ]
- ]
- ];
- $response = $client->search($params);
复制代码
- 区间查找(类比MySQL where field =、between)
- 返回array
- <是lt、<=是lte、>是gt、>=是gte
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'range' => [
- 'area' => [ //面积大于1000平方千米的城市
- 'gt' => 1000
- ]
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
- 返回array
- between
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'range' => [
- 'area' => [ //获取面积大于1000平方千米,但在10000平方千米以内的城市数据
- 'gt' => 1000,
- 'lt' => 10000,
- ]
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
复制代码
- 正则匹配(类比MySQL where field regexp 'xxx')针对keyword字段有效
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'regexp' => [
- 'city' => '.*北京.*' //搜索包含北京关键字的字段
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
- .*: 匹配任意数量的任意字符
- .: 匹配任意单个字符。
- *: 匹配前面的元素零次或多次。
- +: 匹配前面的元素一次或多次。
- ?: 匹配前面的元素零次或一次。
- ^: 匹配字符串的开头。
- $: 匹配字符串的结尾。
- [...]: 匹配方括号中的任意字符。
- {n}: 匹配前面的元素恰好 n 次。
- {n,}: 匹配前面的元素至少 n 次。
- {n,m}: 匹配前面的元素至少 n 次,但不超过 m 次。
复制代码
- 取反查找(类比MySQL where filed != 'kw')针对text类型的字段无效
- 返回bool
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'bool' => [
- 'must_not' => [
- 'term' => [
- 'city' => '北京市' //返回不是北京市的数据
- ]
- ]
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'bool' => [
- 'must_not' => [
- 'range' => [
- 'area' => [ //面积不小于1000平方千米的城市
- 'lt' => 1000
- ]
- ]
- ]
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
复制代码
- 多条件and查找(类比MySQL where expression1 and expression2)
- 返回array
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'bool' => [
- 'must' => [ //返回city字段是北京市,并且描述带有首都的数据
- ['term' => ['city' => '北京市']],
- ['match' => ['description' => '首都']],
- ]
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
复制代码
- 多条件or查找(类比MySQL where expression1 or expression2)
- 返回array
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'bool' => [
- 'should' => [ //查询城市名北京市,或者描述含有沪的描述内容
- ['term' => ['city' => '北京市']],
- ['match' => ['description' => '沪']],
- ]
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
复制代码- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'bool' => [ //查询城市名为北京市或上海市,并且描述带有京字的数据
- 'must' => [
- [
- 'bool' => [
- 'should' => [
- ['term' => ['city' => '北京市']],
- ['term' => ['city' => '上海市']]
- ]
- ]
- ],
- ['match' => ['description' => '京']]
- ]
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
复制代码- 单字段排序
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'match_all' => new StdClass,
- ],
- 'sort' => [ //四个直辖市数据按照区域大小排名
- ['area' => ['order' => 'asc']] //asc或desc
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
- 多字段排序
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'match_all' => new StdClass,
- ],
- 'sort' => [ //区域按照降序,人口按照升序排,条件不会冲突,回想MySQL order by那样,合并处理。
- ['area' => ['order' => 'asc']], //asc或desc
- ['population' => ['order' => 'desc']], //asc或desc
- ]
- ]
- ];
复制代码- 返回bool
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'size' => 0, // 设置为0表示不返回实际的文档,仅返回聚合结果
- 'aggs' => [
- 'population_data' => [ //这个key为自定义名称
- 'avg' => [ //返回4个直辖市平均人口
- 'field' => 'population'
- ]
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
- avg : 平均值
- sum :总和
- min : 最小值
- max :最大值
- 没有count。
复制代码- 返回string
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'size' => 0, // 不返回文档,只返回聚合结果
- 'aggs' => [
- 'city_group' => [ //自定义名称
- 'terms' => [
- 'field' => 'city',
- 'size' => 10 // 聚合结果的数量限制
- ]
- ]
- ]
- ]
- ];
- $response = $client->search($params)->asArray();
- $aggregations = $response['aggregations']['city_group']['buckets'];
- foreach ($aggregations as $bucket) {
- echo "城市名:" . $bucket['key'] . " - 本组组对应的数量:" . $bucket['doc_count'] . "\n";
- }
- 城市名:上海市 - 本组组对应的数量:1
- 城市名:北京市 - 本组组对应的数量:1
- 城市名:天津市 - 本组组对应的数量:1
- 城市名:重庆市 - 本组组对应的数量:1
复制代码
- 合并(类比MySQL union)
用PHP array_merge实现吧,这对于ES不适用。
- 指定数据靠前(类比竞价排名)
- 返回array
- 个人还是推荐使用自定义字段,因为['hits']['_score']字段得出来分数不可控。
- 搜索城市,原先是北京靠前,现在通过修改权重,使其上海靠前
- $params = [
- 'index' => 'php_index',
- 'body' => [
- 'query' => [
- 'function_score' => [
- 'query' => [
- 'bool' => [
- 'should' => [
- ['term' => ['city' => '北京市']],
- ['match' => ['description' => '沪']]
- ]
- ]
- ],
- 'functions' => [
- [
- 'filter' => [
- 'match' => ['description' => '沪']
- ],
- 'weight' => 2 // 增加包含“沪”的文档的权重
- ]
- ],
- 'boost_mode' => 'sum'
- ]
- ],
- 'sort' => [
- '_score' => [
- 'order' => 'desc' // 按照得分降序排序
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
- boost_mode设定了如何将查询的基础得分(由 query 部分确定)与功能得分(由 functions 部分计算)进行组合。以下是几种常用的 boost_mode 设置:
- multiply: 基础得分与功能得分相乘。
- replace: 功能得分替代基础得分。
- sum: 基础得分与功能得分相加。
- avg: 基础得分与功能得分的平均值。
- max: 取基础得分与功能得分中的最大值。
复制代码 Painless
- //counter子对岸自增
- ctx._source.counter += params.count
- //if else 判断
- if (ctx._source.someField > 10) {
- ctx._source.anotherField = ctx._source.someField * params.multiplier;
- } else {
- ctx._source.anotherField = params.defaultValue;
- }
复制代码 IK中问分词与高级索引创建
- 使用理由:ES默认的分词器对中文不友好,英文分词器会把中文每个字分开,因此必要专门的中文分词器。
- 分词器的服务对象是映射,而不是索引。
- 安装:
- 关闭ES
- 执行以下代码,注意版本号的问题
- bin/elasticsearch-plugin install https://get.infini.cloud/elasticsearch/analysis-ik/8.14.1
- 进入交互界面输入Y。
- 之后启动ES
复制代码
- ik_max_word与ik_smart分词精度控制演示:
- 演示分词:
- GET IP:9200/_analyze
- 传入以下内容
- {
- "text":"射雕英雄传",
- "analyzer":"ik_smart"
- }
- 返回
- {
- "tokens": [
- {
- "token": "射雕英雄传",
- "start_offset": 0,
- "end_offset": 5,
- "type": "CN_WORD",
- "position": 0
- }
- ]
- }
- 若使用ik_max_word
- {
- "text":"射雕英雄传",
- "analyzer":"ik_max_word"
- }
- 则返回
- {
- "tokens": [
- {
- "token": "射雕英雄传",
- "start_offset": 0,
- "end_offset": 5,
- "type": "CN_WORD",
- "position": 0
- },
- {
- "token": "射雕",
- "start_offset": 0,
- "end_offset": 2,
- "type": "CN_WORD",
- "position": 1
- },
- {
- "token": "英雄传",
- "start_offset": 2,
- "end_offset": 5,
- "type": "CN_WORD",
- "position": 2
- },
- {
- "token": "英雄",
- "start_offset": 2,
- "end_offset": 4,
- "type": "CN_WORD",
- "position": 3
- },
- {
- "token": "传",
- "start_offset": 4,
- "end_offset": 5,
- "type": "CN_CHAR",
- "position": 4
- }
- ]
- }
复制代码- 有些场景,有很多的专业用语,但是IK分词器把它拆分开,就显得不是很精准,因此可以添加自定义分词解决。
- vim ES安装目录/config/analysis-ik/IKAnalyzer.cfg.xml
- 在<entry key="ext_dict"></entry>的双标签中间写入文件名,例如
- <entry key="ext_dict">self_words.dic</entry>
- vim self_words.dic
- 逐行添加自定义词汇
- 重启ES。
复制代码- 返回bool
- $params = [
- 'index' => 'test_index',
- 'body' => [
- 'settings' => [
- 'analysis' => [
- 'analyzer' => [
- 'analyzer_ik_max_word' => [
- 'type' => 'ik_max_word' //ik分词器内置关键配置,更多的分词结果
- ],
- 'analyzer_ik_smart' => [
- 'type' => 'ik_smart' //ik分词器内置关键配置,更快的分词结果
- ]
- ]
- ]
- ],
- 'mappings' => [
- 'properties' => [
- 'content' => [
- 'type' => 'text',
- 'analyzer' => 'analyzer_ik_smart', // 设置索引时的分词器
- 'search_analyzer' => 'analyzer_ik_smart' // 设置搜索时的分词器
- ]
- ]
- ]
- ]
- ];
- $response = $client->indices()->delete(['index' => 'test_index']);
- dump($response->asBool());
复制代码- 返回bool
- 这里尝试创建了一个更复杂的索引,添加了过滤器,但是不生效,不知道是那里的问题。
- 如下,按照以下索引的配置,过滤后的结果,应当是"C世界上最好编程语言",然后再分词,可却不生效。
- GET IP:9200/test_index/_analyze
- {
- "analyzer":"self_ik_max_word",
- "text" : "PHP是世界上最好的编程语言"
- }
- $params = [
- 'index' => 'test_index', // 指定要创建的索引名称
- 'body' => [
- 'settings' => [ // 配置索引的设置
- 'analysis' => [ // 分析器设置
- 'char_filter' => [ // 字符过滤器设置
- 'self_char_filter' => [ // 自定义字符过滤器名称
- 'type' => 'mapping', // 过滤器类型为映射
- 'mappings' => ['PHP => C'] // 替换分词的字符
- ]
- ],
- 'filter' => [ // 过滤器设置
- 'self_filter' => [ // 自定义停用词过滤器名称
- 'type' => 'stop', // 过滤器类型为停用词
- 'stopwords' => ['是', '的'] // 停用词列表
- ]
- ],
- 'analyzer' => [ // 分析器设置
- 'self_ik_max_word' => [ // IK 分词器名称
- 'type' => 'ik_max_word', // 使用 IK 分词器的最大分词模式
- 'char_filter' => ['html_strip', 'self_char_filter'], // html_strip过滤器会把html标签忽略,但html转义字符仍旧生效( 仍旧是空格),且会把<br/>转化为\n
- 'filter' => ['lowercase', 'self_filter'] //lowercase过滤器是将大写字母变为小写
- ],
- 'self_ik_smart' => [ // IK 分词器名称
- 'type' => 'ik_smart', // 使用 IK 分词器的快速分词模式
- 'char_filter' => ['html_strip', 'self_char_filter'], // html_strip过滤器会把html标签忽略,但html转义字符仍旧生效( 仍旧是空格),且会把<br/>转化为\n
- 'filter' => ['lowercase', 'self_filter'] //lowercase过滤器是将大写字母变为小写
- ]
- ]
- ]
- ],
- 'mappings' => [ // 配置索引的映射
- 'properties' => [ // 文档字段的属性设置
- 'content' => [ // 文档中的字段名称
- 'type' => 'text', // 字段类型为文本
- 'analyzer' => 'self_ik_max_word', // 设置索引时的分词器
- 'search_analyzer' => 'self_ik_max_word' // 设置搜索时的分词器
- ]
- ]
- ]
- ]
- ];
- $response = $client->indices()->create($params);
- dd($response->asBool());
复制代码 ELK
- 概念:ES结合LK作为ELK(Elasticsearch(搜索), Logstash(采集转换), Kibana(分析))组合,可用于及时监控、分析和可视化大量日记和事件数据,如系统日记、应用程序日记、网络流量日记等。
- 构成
- Elasticsearch:一个分布式搜索引擎,提供强大的搜索功能和及时的数据分析能力。
- Logstash:一个数据处理管道,用于收集、解析和转发日记数据。
- Kibana:一个数据可视化工具,帮助用户通过图形化界面检察和分析 Elasticsearch 中的数据。
- 作用:
- 日记管理:集中化日记收集:通过Logstash收集来自不同系统和应用的日记,统一存储在Elasticsearch中。
- 日记分析:利用Kibana对日记数据进行及时分析和可视化,帮助发现系统题目和异常。
- 及时监控:跟踪应用程序的性能指标,及时检察应用的健康状态。
- 性能瓶颈检测:通过分析日记数据,识别息争决性能瓶颈。
- 安全事件分析:监控和分析系统中的安全事件,检测异常行为。
- 合规性审计:记录和分析系统日记,以满足合规性要求。
- 数据可视化:通过Kibana创建各种图表和仪表盘,帮助业务分析师明白数据趋势和模式。
- 用户行为分析:分析用户的操作日记,优化用户体验和产品计划。
- 服务器监控:跟踪服务器的性能指标,如CPU使用率、内存使用环境和磁盘空间。
- 应用状态监控:监控应用程序的运行状态和日记,以确保正常运行。
- 题目诊断:利用Elasticsearch存储的日记数据,快速定位息争决系统故障。
- 根因分析:分析相关日记,帮助找到题目的根本原因。
- 对于PHP而言:几乎用不到,这是Java和大数据方向的。
Kibana
- 保证ES服务已启动。
- 防火墙开启5601端口
- firewall-cmd --add-port=5601/tcp --zone=public --permanent
- systemctl restart firewalld
- 下载与解压
- curl -O https://artifacts.elastic.co/downloads/kibana/kibana-8.14.1-linux-x86_64.tar.gz
- tar zxf kibana-8.14.1-linux-x86_64.tar.gz
- 权限配置
- chown -R es:es kibana-8.14.1
- 切换用户
- su es
- kibana不支持elastic用户,所以需要创建新用户,并赋予超级管理员角色,并赋予kibana_system角色
- elasticsearch-users useradd zs
- elasticsearch-users roles -a superuser zs
- 少了这一步报错,让我搞了4个小时。
- elasticsearch-users roles -a kibana_system zs
- 修改配置
- vim kibana-8.14.1/config/kibana.yml
- elasticsearch.username: "zs"
- elasticsearch.password: "123456"
- elasticsearch.hosts: ["ES IP:9200"]
- i18n.locale: "zh-CN"
- 启动
- kibana-8.14.1/bin/kibana
- 过2分钟后,访问http://IP:5601
复制代码 4种和数据库同步方案
- 不妨先讲一讲业务层是怎么使用ES的读功能的:
以电商系统为例,用到ES的原因,一个是商品数目巨大,一个是分词有助于展示更好的结果,上架的商品因为关键词误差搜不到,这就是损失。
例如商品列表数据的展示,可将价格,名称,形貌,主图片,标签,id等其他数据存入ES,然后展示。
当用户点击某个商品时,根据id进行哈希运算,获取商品数据在谁人MySQL分表中,利用id主键索引极速查询的特性,快速获取商品数据。
- 同步双写:MySQL和ES同步更新
- 优点:实现简单,及时性高。
- 缺点:耦合度高,其中一个组件异常可能会影响另一个。
- 异步双写:先同步MySQL,再用MQ同步ES。
- 优点:优雅,由于MQ(非Redis实现的MQ)具有高可用机制,因此ES消费失败可以重试。
- 缺点:多了一个MQ,就多了一层运维本钱。有延迟。
- 主动化任务,定时遍历SQL:用时间戳做标识符,用于区分哪些数据未同步,没有同步就用脚本定时同步到ES。
- 优点:业务逻辑层不必要额外的针对ES做写操作。
- 缺点:及时性不够,对MySQL压力大。
- 使用Canal基于Binlog进行靠近及时的同步,使用Canal监听MySQL Binlog,并部署同步ES数据的脚本,从而主动化保持同步。也可直接利用Canal同步ES。相关链接:https://github.com/alibaba/canal/wiki/Sync-ES。
- 优点:及时性高,对业务层代码无侵入。
- 缺点:多了一个Canal,就多了一层运维本钱。
高并发下ES本身一致性解决方案
- 题目:与上文讲的数据库一致性,不是一个东西。这里讲的是并发下ES本身更新数据导致的一致性题目。例如并发过来的两个请求,查询到结果是10,都想要-1,等两个执行完毕后,结果不是8而是9,那么就出现了数据一致性题目。
- ES之外的解决方案:分布式锁。或非分布式环境下编程语言自带的具有排它性的锁。
- ES乐观锁解决方案1:
- 背景:先创建一个num_test索引,并添加名为num的int类型的映射。并插入一条数据。
- 流程:当进行数据更新时,先做一次查询(get方法,不是search方法),获取相关的_primary_term,_seq_no值。
- 当更新数据时,添加对应的版本号,如果ES检测到版本号不对,则会报错,如下:
- $params = [
- 'index' => 'num_test',
- 'id' => 1,
- 'body' => [
- 'doc' => [
- 'num' => 10
- ]
- ],
- 'if_seq_no' => 3, // 使用序列号
- 'if_primary_term' => 1, // 使用主分片术语
- ];
- try {
- $response = $client->update($params);
- } catch (\Exception $exception) {
- echo '出错了,这里重试查询后再更新,或者记录错误等其它操作。。。'
- }
复制代码- $params = [
- 'index' => 'num_test',
- 'id' => 1,
- 'body' => [
- 'doc' => [
- 'num' => 1800
- ]
- ],
- 'version' => 40, // 提供外部版本号
- 'version_type' => 'external' // 使用外部版本号
- ];
- try {
- $response = $client->update($params); //版本不生效的方案,不推荐使用
- } catch (\Exception $exception) {
- dump('出错了,这里进行重试,或者记录错误,等其它操作');
- }
复制代码
- ES应对高并发写的报错题目(和上文内容不是一回事):ES针对大量的并发过来的写请求,ES支持的并不好,ES底层接纳乐观锁的形式,这会导致ES内部在频繁并发写入时内部维护版本号辩论,也就是说更新前查询出来的版本号,比当前实际的版本号小(被别的并发过来的请求增加了版本号),那就会报错,这也就是所谓的ES报版本辩论的错误的题目,对于这种场景,可添加重试次数,和业务层的异常获取作为兜底策略。重试代码示例如下:
- $params = [
- 'index' => 'index',
- 'id' => '10',
- 'body' => [
- 'doc' => [
- 'field1' => 'new value1',
- 'field2' => 'new value2'
- ]
- ],
- 'retry_on_conflict' => 3 // 设置重试次数
- ];
- try {
- $response = $client->update($params);
- } catch (Exception $e) {
- // 处理异常,可以选择记录日志或执行其它操作,这个catch是用来重试3次还报错的兜底策略。
- }
复制代码 为什么不消ES替代MySQL
- ES没有MySQL的事务机制,高可用无法保证。
- ES没有MySQL的关系型侧重,MySQL有强大的关联策略,MySQL join多张表时,ES必要手动实现。
- ES的定位是快速索引快速查找,并非有过多高可用存储的机制,还是必要配合MySQL使用。
EQL
- 极简概括:Event Query Language用于在ES中进行事件数据查询的类SQL语言。
- 解决题目:为了更方便地分析时间序列数据和事件流数据,特殊适用于安全事件、日记数据和监控数据的分析。
- 弃用原因:多用于快速调试。毕竟ES不是MySQL,SQL API 并不是ES中所有功能的完整替代品,有些复杂的查询和功能可能必要使用原生的ES查询 DSL(ES范畴或题目域计划的编程语言或语法)。
- 简单示例:要查询索引下的一条数据
- POST IP:9200/_sql?format=json //类型可未txt,用制表符更直观的展示
- {
- "query": "SELECT * FROM php_index WHERE city = '北京市'"
- }
- 结果:
- {
- "columns": [
- {
- "name": "_boost",
- "type": "float"
- },
- {
- "name": "area",
- "type": "integer"
- },
- {
- "name": "city",
- "type": "keyword"
- },
- {
- "name": "description",
- "type": "text"
- },
- {
- "name": "id",
- "type": "long"
- },
- {
- "name": "population",
- "type": "integer"
- }
- ],
- "rows": [
- [
- null,
- 16411,
- "北京市",
- "北京市(Beijing),简称“京”,古称燕京、北平,是中华人民共和国首都、直辖市、国家中心城市、超大城市, 国务院批复确定的中国政治中心、文化中心、国际交往中心、科技创新中心, 中国历史文化名城和古都之一,世界一线城市",
- 1,
- 2186
- ]
- ]
- }
复制代码- POST IP:9200/_sql?format=txt
- {
- "query": "show tables"
- }
- catalog | name | type | kind
- ---------------+--------------------------------------------------+---------------+---------------
- zs_es_cluster |.alerts-default.alerts-default |VIEW |ALIAS
- zs_es_cluster |.alerts-ml.anomaly-detection-health.alerts-default|VIEW |ALIAS
- zs_es_cluster |.alerts-ml.anomaly-detection.alerts-default |VIEW |ALIAS
- zs_es_cluster |.alerts-observability.apm.alerts-default |VIEW |ALIAS
- zs_es_cluster |.alerts-observability.logs.alerts-default |VIEW |ALIAS
- zs_es_cluster |.alerts-observability.metrics.alerts-default |VIEW |ALIAS
- zs_es_cluster |.alerts-observability.slo.alerts-default |VIEW |ALIAS
- zs_es_cluster |.alerts-observability.threshold.alerts-default |VIEW |ALIAS
- zs_es_cluster |.alerts-observability.uptime.alerts-default |VIEW |ALIAS
- zs_es_cluster |.alerts-security.alerts-default |VIEW |ALIAS
- zs_es_cluster |.alerts-stack.alerts-default |VIEW |ALIAS
- zs_es_cluster |.alerts-transform.health.alerts-default |VIEW |ALIAS
- zs_es_cluster |.kibana-observability-ai-assistant-conversations |VIEW |ALIAS
- zs_es_cluster |.kibana-observability-ai-assistant-kb |VIEW |ALIAS
- zs_es_cluster |.siem-signals-default |VIEW |ALIAS
- zs_es_cluster |my_index |TABLE |INDEX
- zs_es_cluster |num_test |TABLE |INDEX
- zs_es_cluster |php_index |TABLE |INDEX
- zs_es_cluster |test_index |TABLE |INDEX
- zs_es_cluster |zs_index |TABLE |INDEX
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |