ElasticSearch
es的场景
Elasticsearch(简称ES)是一个开源的分布式搜索和分析引擎,它提供了强大的<b>全文搜索功能</b>,可以快速地对大规模数据进行搜索、分析和可视化。<br>
Elasticsearch中,查询是非常重要的操作,通过ES基于倒排索引可以从索引中检索出符合条件的文档数据。
检索核心逻辑<br>
1.1 数据存储和索引构建<br>Elasticsearch中的每个文档都有一个ID以及与其相关的字段,字段值会通过索引进行存储。<br>在索引过程中,ES会将每个字段拆分成多个“词条”(terms),并<b>存储这些词条与文档ID之间的映射</b>。<br>倒排索引就是根据每个词条(term)<b>反向查找包含该词条的文档ID集合</b>。<br>
1.2 查询解析与执行<br>用户提交查询时,ES会解析查询并通过倒排索引查找相关的文档。<br>查询的执行通常分为两个阶段:<br>评分阶段:根据评分算法(如TF-IDF或BM25)计算每个文档的相关性得分,返回得分最高的文档。<br>
1.3 查询类型<br>标准查询:如match、term查询等,直接针对某些字段进行匹配。<br>复合查询:如bool查询,允许用户组合多个查询条件。<br>聚合查询:用于分析数据,如terms聚合、range聚合等。<br>
1.4 查询优化<br>查询缓存:ES会缓存常见查询的结果,以提高查询性能。<br>分片和副本:数据被分为多个分片,查询时会并行在各个分片上执行,以加速查询过程。
倒排索引
ES中 存储数据的基本单位是 <b>index 索引</b>,相当于mysql里的一张表。<br>
<br>
ES中 数据以 <b>docs 文档</b> 的形式存储在index中,相当于一行数据。
每个文档docs包含多个字段,字段是文档的基本单位。
传统的我们的检索是通过文章,逐个遍历找到对应关键词的位置。<br><b>倒排索引</b> 是通过<b>分词策略</b>,形成了<b>词-文章的映射</b>关系表,这种 <b>词典+映射表</b> 即为倒排索引。<br>
有了倒排索引,就能实现 O(1) 时间复杂度的效率检索文章了,极大的提高了检索效率。
es接收到一个文档后,进行字符 <b>过滤</b>-><b>分词</b>-><b>归一化</b>(停用词,大小写,单数和复数,相近词(相似词))
BKD树(K-D数和B+树)
es的分布式架构原理<br>
1.核心思想就是在多个机器上启动多个es进程实例,组成一个集群。<br>2.创建一个索引,这个索引可以被分成多个shard(分片),每个shard存储一部分数据<br>3.shared分片也会有主分片primary副分片replica<br>4.shared主副分片均匀分布在各个机器上<br>5.数据都是写入到主分片,然后主分片同步写入到副分片上。读数据则可读取主分片或者副分片的数据。<br>6.如果某台机器进程宕机,master进程宕机,选举其他进程作为master,并且将宕机的进程里的主分片的副分片转为主分片。<br>7.宕机的进程好了以后,便不再是master 节点,里面的主分片parimary shard转为副分片 replica shard。
es如何实现master选举
ZenDiscover模块负责
对所有可以成为master的节点(node.master: true)根据nodeId字典排序,每次选举每个节点都把自己所知道的节点排一次序,然后选出第一个节点,暂且认为它是master节点。<br>
如果对某个节点的投票数达到一定值并且该节点自己也选举自己,那这个节点就是master
写数据过程
基本写入流程<br>1.首先客户端随便选择一个节点node去写,此时这个节点称为协调节点<br>2.协调节点对写的数据进行hash,确定这个数据属于哪个shard(分片)<br>3.协调节点对数据进行路由,把请求发到所属主分片 pimary shard 的node上去<br>4.主节点同步数据到从节点,primary, replicate sharding 都写完了,那么协调节点会返回写成功的响应给客户端。<br>
primary shard存储底层原理<br>(refresh,flush,translog,merge)
1.数据写入shard的时候,先写入内存buffer里,同时它会写入到translog日志文件里。<br>(此时如果客户端要查询数据是查不到的)<br>
2.如果buffer快满了或者每隔一段(默认1s)时间,es会把内存buffer中的数据 refresh刷到到一个新的segment file,每隔1秒产生一个新的segment文件<br> 但是如果buffer里面此时没有数据,就不会执行refresh。<br><font color="#c41230">数据在写入segment file之后,便存储好了这1s的数据,同时就建立好倒排索引了。</font>
3.操作系统中,磁盘文件其实都有一个东西,叫os cache,操作系统缓存。<br>就是说数据写入磁盘文件之前,会先进入os cache。<br><font color="#c41230"> 只要buffer里的数据写入到了os cache里面,客户端就能搜索到这部分数据了。</font><br>
为什么es是准实时的?<br>
因为写入1s后才会刷到os cache里。写入到os cache里之后,buffer里的数据就会清空,translog会保留。
translog也是磁盘文件,所以也是先写入os cache里的,默认5秒刷新数据到磁盘中
4.当translog不断变大,大到一定阈值,或者30分钟 就会触发commit(flush)操作。<br>(默认30分钟会自动执行)整个commit过程叫flush,手动根据es api也可以执行flush。<br><br> commit操作: 1.写commit point 2.将os cache fsync强刷到磁盘上去 3.清空translog日志文件<br><ul><li> 1.将buffer里的数据都写入os cache里面去,然后清空buffer。</li><li> 2.将一个commit point文件写入到磁盘,里面标示着之前写入的所有segment file,但是数据还是在os cache中。</li><li> 3.把os cache缓冲的所有的数据都fsync到磁盘上面的每个segment file中去。</li><li> 4.刷完以后会删除并新建translog</li></ul><br>
translog日志作用:<br>数据一般都是存储在buffer或者os cache内存里,一旦服务器宕机重启,内存中的数据就会丢失。<br>所以将es操作日志存储在translog里,es重启时通过translog将数据恢复到buffer及os cache中。
删除数据写入.del文件中标识一下这个数据被删除了,里面某个doc标识为deleted状态<br>客户端搜索某条数据,一旦发现这条数据在.del文件中找到这条数据被标识成删除状态了,就不会搜索出来。<br>
在新的文档被创建时,Elasticsearch会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。
由于每隔1s生成一个segment file,当文件多到一定程度的时候,es会merge成一个大的segment file,然后删除旧的文件<br><font color="#c41230">在merge的时候,会看一下如果某条数据在.del文件中标识为删除,那么merge后的新文件里这条数据就没了(物理删除)</font>
丢失数据情况
默认5s才会将 translog 从os cache写入到磁盘文件中,所以会有5s数据丢失的可能
解决:可以设置个参数,官方文档。每次写入一条数据,都是写入buffer,同时写入磁盘上的translog。<br> 但是会导致写性能,写入吞吐量下降一个数量级。本来1s可以写入2000条,现在1s钟可能只能写200条。
读数据过程<br>
查询流程原理:<br>客户端发送一个请求到任意一个node,node成为协调节点。<br>请求转发到对应相关的所有shards,此时会使用随机轮询算法,在primary shard及所有replica shard中实现请求负载均衡。<br>请求节点 query phase 每个 shard 将自己的搜索结果(其实就是一些 doc id)返回给协调节点(coordinate node)<br>协调节点 fetch phase 进行数据的合并,排序,分页等操作。根据doc id去各个节点上拉取实际的document数据,返回 document 给客户端。<br>
一条数据精准查询
数据写入了某个document,这个document会自动给你分配一个全局唯一的id (doc id)<br>同时也是根据doc id进行hash路由到对应的primary shard上去的。也可以手动指定doc id,比如用户id,订单id。<br>
Match Query:匹配查询<br>
用于在指定字段中搜索包含指定关键词的文档。
{<br> "query": {<br> "match": {<br> "content": "Elasticsearch"<br> }<br> }<br>}
Match Phrase Query:短语匹配查询
用于匹配包含指定短语的文档。<br>
{<br> "query": {<br> "match_phrase": {<br> "content": "Elasticsearch tutorial"<br> }<br> }<br>}<br>
Term Query:精确匹配
用于精确匹配指定字段中的值。<br>
{<br> "query": {<br> "term": {<br> "category": "Technology"<br> }<br> }<br>}
Bool Query:布尔查询
用于组合多个查询条件,支持must、should、must_not等逻辑操作符。<br>
{<br> "query": {<br> "bool": {<br> "must": [<br> { "match": { "title": "Elasticsearch" } },<br> { "term": { "category": "Technology" } }<br> ]<br> }<br> }<br>}
案例
Range Query:范围查询
用于匹配指定字段中符合范围条件的值。<br>
{<br> "query": {<br> "range": {<br> "price": {<br> "gte": 100,<br> "lte": 500<br> }<br> }<br> }<br>}
案例<br>
es查询(url方式)
目标 Index
/_search 在所有索引上检索
/search1/_search 在search1当前索引上检索
/search1,search2/_search 在search1,search2索引上检索
/search*/_search 在search开头的索引上检索
/g*,user*/_search 在g和user开头的索引上检索
查询所有文档:<br>
GET http://localhost:9200/_search
查询 <b>指定索引</b> <b>指定类型 </b>的文档:<br>
<b>GET http://localhost:9200/index/type/_search</b>
http://es_ip_address:9200/doc-202403/docs/_search<br>
这个URL将返回doc-202403索引中所有docs文档类型的搜索结果。
泛查询 <b>所有字段 </b>值为"SZ000001"的文档:
http://es_ip_address:9200/doc-202403/docs/_search?q=SZ000001
这个URL将返回 <b>doc-202403 索引</b>中特定字段的值为"SZ000001"的文档的搜索结果。
查询多字段字段的值为"XXX"的文档:
http://es_ip_address:9200/doc-202403/docs/_search?q=field1:(SZ000001 and 2024-03-01)
<br>这个URL将返回doc-202403索引中特定字段的值为"SZ000001" | "2022-01-01"的文档的搜索结果。
查询特定字段的值包含"SZ"的文档: <br>
http://es_ip_address:9200/doc-202403/docs/_search?q=field1:*SZ
{ "query": { "bool": { "must": { "match": { "field1": "SZ" } } } } }<br>
<br>这个URL将返回doc-202403索引中特定字段的值包含"SZ"的文档的搜索结果。通配符"*"表示匹配任意字符。
查询后排序
&sort=pubdate:desc
召回最大数量
http://10.15.108.88:9200/doc-202404/docs/_search?q=category:(%E6%9C%8B%E5%8F%8B%E5%9C%88%20and%20%E5%85%AC%E5%8F%B8%E5%85%AC%E5%91%8A)&sort=pubdate:desc&size=100
es调优
es生产集群的部署架构是什么?<br>
es生产集群我们部署了5台机器,每台机器是6核64G的,集群总内存是320G
es集群的日增量数据大概3000万条,每天日增量数据大概是1G
每个索引的数据量大概有多少?分片情况?<br>
目前线上有5个索引(结合业务来),每个索引的数据量大概是20G。我们每个索引分配的是8个shard,比默认的5个shard多了3个shard。
每个月份有1个索引对应大智慧APP。每个索引在测试中3个shard,在生成中8给shard。
十亿数据,第一次5~10s,第二次就快了
es性能优化是没有什么银弹的。不要期待随手调一个参数,就可以万能的应对所有性能慢的场景。<br>有些场景换个参数,或者调整个语法就能搞定,但是绝对不是所有场景都是这样的。
1.性能优化杀手锏 filesystem cache
第一次从磁盘查出数据会存到内存的fileSystem Cache,es搜索引擎严重依赖底层的os cache。
如果走磁盘一般肯定上秒, 但是如果走filesystem cache,走纯内存,那么基本上就是毫秒级的。从几毫秒到几百毫秒不等。
<font color="#c41230">1.如果要es性能好,最佳情况下,机器的内存要容纳你总数据量的一半。</font>
比如es中要存储1T数据,那么你多台机器留给filesystem cache的内存要加起来综合到512g。
<font color="#c41230">2.往es里存少量的数据,比如30个字段只用到了三个就存三个。让内存留给filesystem cache的大小跟数据量一致。</font>性能就会非常高,一般可以在1s以内
<font color="#c41230">3.其他字段的数据可以存在mysql里面,建议采用es+hbase</font><br>hbase的特点就是适用于海量数据的在线存储,就是可以对hbase写入海量数据,不要做复杂的搜索,就是做很简单的一些根据id或者范围查询的操作
总结:最好写入es数据小于 fileSystem cache内存大小
2.缓存预热
假如说,按照上面的方案去做了,es集群中每个机器写入的数据量还是超过了filesystem cache的一倍,60g数据,filesystem cache就30g,还有30g在磁盘中
可以自己后台搞个系统,每隔一会就去搜索一下热数据,刷到filesystem cache中。后面用户搜索热数据就是直接去内存里查了
3.冷热分离
1.将大量不搜索的字段,拆分到别的存储引擎里去,这个类似于mysql分库分表的垂直拆分。
2.可以做类似mysql水平拆分,就是说将大量的访问很少,频率很低的数据,单独写一个索引,然后将访问很频繁的热数据单独写一个索引。
比如:6台机器,2个索引,一个放冷数据,一个放热数据,每个索引3个shard<br> 3台放热数据index;3台放冷数据index;<br> 这样的话,大量的时候是在访问热数据,热数据可能就占总数据的10%,此时数据量很少,几乎能确保数据全部保留在filesystem cache<br> 对于冷数据而言,是在别的index里面,跟热数据都不在同一个index机器上,如果有人访问冷数据,在磁盘上,此时性能差点就差点了。
子主题
4.document模型设计
<font color="#c41230">es里的复杂的关联查询,复杂的查询语法,尽量别用,一旦用了性能一般都不太好。<br></font>所以要好好设计es里的数据模型。
写入es的java系统里,就完成关联,将关联好的数据直接写入es中,搜索的时候就不需要利用es的搜索语法
比如 mysql两个表需要join<br>在写入es的时候java直接将join好的数据写入es,不用es的join语法查询
5.分页性能优化
es分页性能比较坑<br>假设每页10条数据,现在要查询第100页,<font color="#c41230">实际上是会把每个shard上存储前1000条数据都查到一个协调节点上,如果你有5个shard,那么就有5000条数据,接着协调节点对这5000条数据进行一些合并,处理。再获取到最终第100页的10条数据。</font><br>翻页的时候,翻的越深,每个shard返回的数据就越多,协调节点处理数据时间越长,非常坑爹。<br>
1.不允许深度分页/默认深度分页性能很差。
系统不允许翻那么深的页,或者告诉产品默认翻的越深性能越差
2.类似于app里的推荐商品或者微博,不断下拉出现一页一页的。<br>可以用scroll api来进行处理<br>scroll会一次性给你生成所有数据的快照,每次翻页通过游标移动,获取下一页这样子,性能会比上面说的那种分页性能高很多。<br>无论分多少页,性能基本上都是毫秒级的。<br>因为scroll api 只能一页一页往后翻,不允许先第十页再120页。
es高维数据检索
高维信息(如文本嵌入、图像或其他结构化高维数据),Elasticsearch 通过结合: 向量相似度搜索, 分布式索引, 缓存<br>
向量相似度搜索:利用 L2 距离、余弦相似性等算法计算对象在向量空间中的距离,从而评估数据点的相似度。
分布式索引:数据和查询任务可以通过自动路由分配到多个节点,这不仅可以并行执行操作,还提高了容错能力。
二级存储索引:为优化高维向量的搜索速度和内存消耗,Esper 提供了使用预计算或近似数据结构(<b>如树状聚类</b>)的二级索引来加速查询的过程。
es的Scoll设计
Scroll API 是一个用于高效分页遍历大量数据的机制,,它的核心设计理念是通过保持一个查询上下文<br>
查询上下文保持: Scroll API 的核心设计是通过持久化一个“scroll上下文”,这个上下文在进行滚动查询时被不断复用。查询结果会被保留在该上下文中,直到你显式地终止滚动,或者滚动超时。<br>
时间限定: 每次滚动查询都会给定一个滚动的时间(scroll)。在指定的时间内,查询会保持有效,查询上下文不会被清除。比如,scroll=1m 表示上下文会在1分钟内有效。<br>
滚动查询返回的ID: 每次查询结果中,ES会返回一个“scroll ID”,这个ID用于下一次滚动查询,以便保持查询状态。每次执行滚动查询时,都会返回新的结果和新的scroll ID,继续查询直到获取完所有数据。<br>
非分页处理: 滚动查询并不通过传统的from和size进行分页,而是通过scroll ID来维持游标状态并批量返回<b>固定数量的文档(由size指定)</b>,不需要进行复杂的分页计算,因此能避免分页时的性能瓶颈。
helpers.scan python的使用<br>
# 使用helpers.scan进行扫描<br>for doc in helpers.scan(es, query=query, index="your_index"):<br> print(doc["_source"])<br><b>query: 查询条件,通常是一个简单的查询,如match_all。<br>index: 要查询的索引。<br>scroll: 设置滚动的时间长度,默认为10s。<br>size: 每次返回文档的数量。</b>
helpers.scan与传统查询的对比<br>
传统分页:常见的分页方式是通过from和size来分页。当数据量大时,因为随着from的增大,ES需要跳过更多的文档,导致查询效率下降。<br>
helpers.scan(滚动查询):滚动查询不依赖from和size,而是返回一个游标(scroll id)来维持查询的上下文。遍历大量文档时非常高效。
es中scoll查询问题
Scroll API 主要用于需要遍历整个数据集的场景,例如批量导出数据、ETL处理等。它并不像普通的查询那样每次都重新计算结果。
Scroll API 在查询过程中,数据集保持一致,不会因为其他操作(如写入、删除等)导致查询结果变化。
Scroll API是有超时限制的。每次滚动查询都需要指定一个scroll参数来定义滚动的持续时间(如scroll=1m,表示1分钟内滚动查询有效)。如果在指定的时间窗口内没有进行滚动请求,查询上下文将会被自动清除,导致scroll ID失效,从而发生超时。
Milvus
基本介绍
Milvus 是一款高性能的向量数据库系统,旨在为 AI 应用提供大规模数据存储、实时查询和近似查找服务。<br>
<br>Milvus 是一个开源的向量数据库,针对机器学习和人工智能应用场景中的大规模向量数据(如图像、文本和视频特征向量)的高效存储和检索。
整体架构设计
系统层次结构
计算层设计
向量索引构建
查询优化算法
实时计算能力
存储层设计
元数据管理
数据分片策略
分布式存储机制
数据流动路径
数据写入流程
客户端请求接收
数据预处理
存储与索引构建
数据查询流程
查询请求解析
索引匹配与筛选
结果聚合与返回
数据删除流程
删除标记设置
垃圾数据清理
索引更新
Milvus架构
1. 客户端接口层(API 层)<br>
提供多种编程语言的 SDK,例如 Python、Java、C++ 等,用于应用程序与 Milvus 交互。<br>支持常见的操作,如插入向量、删除向量、向量搜索和集合管理。<br>支持 SQL 和 Milvus 自定义的查询语言。
2. 计算引擎(Query Node 和 Index Node)<br>
Query Node:<br>
负责处理实时搜索请求,如近邻搜索(ANN,Approximate Nearest Neighbor Search)和过滤操作。<br>
支持多种检索算法,例如 IVF(Inverted File Index)、HNSW(Hierarchical Navigable Small World)、Flat 等。<br>
动态分配计算资源,优化查询性能。
Index Node:<br>
负责构建索引,支持多种索引类型以适应不同场景需求。<br>
3. 存储层(Data Node 和 Storage Layer)<br>
数据持久化存储在支持对象存储的系统中,例如 S3、Ceph、HDFS 等。<br>支持冷热数据分离策略,优化存储成本。<br>
元数据存储(Meta Store):<br>通过 etcd 或其他分布式系统管理元数据。<br>记录集合、分片、副本、向量索引等信息。<br>
4. 集群管理层<br>
协调器:<br>管理集群中的节点资源分配。<br>负责任务调度和负载均衡。<br>
监控和故障恢复:<br>内置 Prometheus 和 Grafana 支持,用于实时监控。<br>提供高可用性和故障自动恢复机制。
Milvus 的工作原理<br>
1. 数据插入
用户通过 API 将高维向量插入到 Milvus 集合中。<br>向量数据先存入内存中的缓冲区(Buffer),随后异步写入持久化存储。<br>数据会被分区、分片,并可以根据用户定义的策略构建索引。<br>
2. 索引构建<br>
Milvus 提供多种索引算法,支持用户根据数据特点选择合适的索引<br>
Milvus 索引在后台异步构建,优化了数据插入和索引构建的效率。
IVF:适用于海量数据的近似搜索。<br>HNSW:适用于小规模数据的高精度搜索。<br>Flat:适用于需要精确搜索的场景,但速度较慢。<br>
3. 向量搜索<br>
支持多种检索模式:<br>
近邻搜索(ANN):快速找到与查询向量最接近的向量。<br>
通过索引结构快速找到查询向量的近似邻居。<br>
提供 Top-K 搜索结果,支持按距离排序。
Hybrid Search(混合搜索):结合属性过滤条件执行高效的向量检索。<br>
结合过滤条件的搜索,例如“查找与向量A相似且满足某些属性条件的数据”。
查询流程:<br>查询请求由 API 接口接收,传递到 Query Node。<br>Query Node 根据元数据找到相应的分区和索引。<br>使用索引加速检索,并结合过滤条件返回结果。<br>
4. 高可用和扩展性
高可用性:<br>通过 Raft 协议或其他分布式一致性算法确保元数据的一致性。<br>数据分片和副本机制保障了容灾能力。<br>
水平扩展:<br>节点可动态增加或减少,支持弹性扩展。<br>基于计算和存储分离设计,轻松处理 PB 级数据规模。
Milvus 检索召回策略<br>
多种距离算法:支持余弦相似性、欧式距离等多种向量间相似度计算,以满足各种应用需求。
内存优化索引:Milvus 支持如 IVF(Induction of Vector)和 Faiss 索引,这些索引能够在保证搜索精度的同时尽量节省内存资源。<br>
Milvus 效率
高效存储:通过冷热分离、分片存储和异步索引降低存储开销,提高效率。
并行计算能力:利用本地内存和计算资源进行高效的向量化查询和聚合计算,通过并行处理加速搜索过程。<br>
检索加速: Milvus利用了分块、近似近邻(ANNS)、空间索引等技术以减少查询响应时间,对数百万级/千万级的数据规模
利用 GPU 资源
对比ES
Elasticsearch 和 Milvus 在设计上有不同侧重点。Elasticsearch 更侧重于文本搜索和<b>多样化的数据</b>索引
Milvus 是针对大型向量数据设计的,特别强调<b>高维数据</b>的存储和查询。
实践操作中考虑: 性能要求、数据复杂性、集成的现有系统等,确保最佳的检索召回效果