首页 文章 精选 留言 我的

精选列表

搜索[es],共4394篇文章
优秀的个人博客,低调大师

[译]深入ES6之箭头函数

原作者:江凌 箭头一族缺少的家庭成员 在JavaScript出现以来,箭头(Arrow)就一直是其语法的一部分。一般来说,JavaScript教程的第一篇就会讲如何在HTML中插入箭头括号来作为注释,这会阻止不支持JS的浏览器错误的将你的JS代码作为文字展现出来,比如这个: <script language="javascript"> <!-- document.bgColor = "brown"; // red // --> </script> 老旧的浏览器只会看到两个不支持的tag以及一个注释,而现代浏览器则会看到JS代码。 注:其实这是一种hack写法,在如今现代浏览器都支持JS的情况下已经慢慢不会再出现这种代码了 为了支持这个这个古怪的hack写法,浏览器的

优秀的个人博客,低调大师

《Spring ES实战》第3招 Mapping决定上限

# Document Mapping 实战:Text 与 Keyword 的工程冲突与价值 ```text 旧方案:所有 String 都映射成 Text → 冲突:排序、聚合、精确过滤全部报错或结果错误 → 后果:搜索功能看似可用,运营后台一排序就崩 ``` 很多团队第一次接 Elasticsearch 时,会把 Java 实体里的 String 一股脑映射成 Text。 表面看,全文搜索能出结果,开发自测也通过了。 但一到运营后台按品牌、分类、标签做精确过滤,或者按创建时间排序,问题就集中爆发。 我是老李,过去半年在深圳一个电商中台项目里,正好经历了从“能搜”到“能正确搜、能排序、能聚合”的完整改造。这个改造的起点不是性能,而是一张错误的 Mapping。 为什么所有 String 都用 Text 会出问题? 当排序和精确过滤同时出现时,应该选择 Text、Keyword 还是多字段? 如果一开始选错字段类型,等到数据量上来后再改,选错的后果是什么? # 01、故事 ```text 周四 23:40 → 深圳南山某电商中台 → 任务:商品搜索页支持按品牌聚合与价格排序 → 冲突:运营点击“按品牌筛选”后结果为空,点击“价格从低到高”直接报错 ``` 那天晚上十一点多,运营在群里发了一张截图:商品搜索页选择品牌“华为”后,列表一片空白。紧接着又发了一张:按价格排序时,接口返回 500。 任务是当天必须让品牌筛选和价格排序可用,因为第二天早会要向业务方演示。我打开 Kibana 看了一眼索引,心里大概有了判断,但还需要用请求验证。 当时索引里的 name、brand、category、tags 全是 Text 类型。Text 字段不是不能查,而是它默认被 Analyzer 切词,存的是倒排索引里的词项,不是完整原始值。 > 夜深索引错,字段尽成词。 > 运营点筛选,空屏无人知。 > 排序触红线,五百报错时。 > 若要明因果,先看映射值。 # 02、问题 ```text 需求变化:从“只能关键词搜索”到“搜索+筛选+排序+聚合” → 冲突:Text 字段无法用于精确 term 过滤,也无法用于排序和聚合 → 后果:品牌筛选为空、价格排序报错、类目聚合结果不可信 ``` 最初的索引只是为了支持商品名称的关键词搜索,所以把所有字符串都设置成了 Text。这个决定在当时没有错,因为需求确实只有搜索。 但当需求扩展到筛选、排序、聚合时,Text 的底层结构就不适用了。Text 经过 Analyzer 后,原始值会被切分成多个词项,倒排索引里存的是词项,而不是完整字符串。 比如品牌“华为”可能被切成一个词,但“华为荣耀”可能被切成两个词。用 term 查询去匹配完整值“华为荣耀”,自然匹配不到。 可验证的验收标准是:对 brand 字段执行 term 查询,能精确返回该品牌的商品;对 price 字段执行排序,不报错且顺序正确;对 category 字段执行聚合,桶数量与类目数量一致。 > 全文搜索路,切词本无过。 > 一旦加筛选,完整值难托。 > 排序与聚合,字段类型错。 > 需求未曾变,映射先定夺。 # 03、原理 ```text 核心机制:Mapping 在索引创建时决定字段的存储与索引方式;Text 走 Analyzer 生成倒排词项,Keyword 保留完整值并启用 doc_values ``` Elasticsearch 的 Mapping 不是 Java 类型映射,而是提前声明“这个字段未来准备怎么被搜索”。同一个字符串,映射成 Text 和 Keyword,底层数据结构完全不同。 Text 类型会经过 Analyzer,通常由 Character Filter、Tokenizer、Token Filter 三部分组成。 原始文本先被字符过滤,再被分词器切分,最后经过词项过滤,生成一组词项写入倒排索引。 Keyword 类型默认不做分词,保留完整字符串作为单个词项。更重要的是,Keyword 默认启用 doc_values,这是列式存储结构,专门支撑排序、聚合和脚本读取。 一个反直觉判断是:Text 字段虽然能全文搜索,但它默认不支持排序和聚合,不是配置问题,而是数据结构问题。另一个反直觉判断是:把字段改成 Keyword 后,模糊搜索能力会下降,因为不再有分词召回。 > 映射非类型,搜索先定局。 > Text 生词项,Keyword 存全句。 > 排序靠列存,聚合亦同域。 > 若要两全法,多字段可取。 # 04、架构 ```text 输入:商品 JSON 文档 → 本模块:Mapping 定义字段类型与 Analyzer → 输出:可搜索、可排序、可聚合的索引结构 / 边界:类型一旦写入,已有字段类型不可直接变更 ``` 这一层的核心职责是把业务数据模型翻译成搜索引擎的索引模型。输入是商品文档,输出是索引中的字段定义和底层数据结构。 边界很明确:Mapping 中的字段类型在索引创建后不能直接修改。可以新增字段,但不能改变已有字段的类型,也不能直接更换已有字段的 Analyzer。 收益是查询行为可预期。Text 负责召回,Keyword 负责精确过滤、排序和聚合,多字段可以同时支持 name 搜索和 name.keyword 排序。 代价是存储和索引成本上升。多字段意味着同一份数据可能被索引两次,Text 写倒排,Keyword 写 doc_values。适用条件是字段既有全文搜索需求,又有精确匹配或排序聚合需求。 > 输入商品文,输出索引型。 > 类型定边界,改后不可行。 > 多字段虽贵,职责各分明。 > 搜索与排序,从此两不惊。 # 05、第一版实战 ```text 环境:Elasticsearch 8.x + Kibana → 配置:创建 product 索引并定义 Mapping → 代码:写入商品文档 → 启动:执行写入与查询 → 验证:观察 _mapping 与 _analyze 结果 → V1:Text 与 Keyword 多字段方案 ``` 以下命令基于 Elasticsearch 8.x 编写,版本细节需按当前官方文档核验。 先创建索引,明确 name 使用多字段,brand、category、tags 使用 Keyword,price 使用数值类型,attributes 使用 Object,createdAt 使用 Da te。 ```json PUT /product { "settings": { "number_of_shards": 1, "number_of_replicas": 0, "analysis": { "analyzer": { "product_analyzer": { "type": "custom", "char_filter": ["html_strip"], "tokenizer": "standard", "filter": ["lowercase", "asciifolding"] } } } }, "mappings": { "properties": { "name": { "type": "text", "analyzer": "product_analyzer", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "description": { "type": "text", "analyzer": "product_analyzer" }, "brand": { "type": "keyword" }, "category": { "type": "keyword" }, "price": { "type": "double" }, "tags": { "type": "keyword" }, "attributes": { "type": "object" }, "createdAt": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss||epoch_millis" } } } } ``` 写入一条商品文档,验证多字段和 Object 类型的实际行为。 ```json POST /product/_doc/1 { "name": "HUAWEI Mate 60 Pro 手机", "description": "旗舰智能手机,支持卫星通话", "brand": "华为", "category": "手机", "price": 6999.00, "tags": ["旗舰", "卫星通话", "5G"], "attributes": { "color": "黑色", "storage": "512GB" }, "createdAt": "2024-11-01 10:00:00" } ``` 验证 Mapping 是否按预期生成。 ```json GET /product/_mapping ``` 示例输出中,name 字段应包含 type 为 text 的主字段和 type 为 keyword 的子字段。 brand、category、tags 应为 keyword,price 为 double,attributes 为 object,createdAt 为 date。 再用 _analyze 观察 name 字段的切词结果。 ```json POST /product/_analyze { "field": "name", "text": "HUAWEI Mate 60 Pro 手机" } ``` 示例输出会显示词项被转换为小写,例如 huawei、mate、60、pro、手机。这说明 Text 字段确实经过了 Analyzer,原始大小写和空格信息已经丢失。 > 建索引先定,字段各司职。 > 名称双字段,品牌关键词。 > 写入一商品,映射验真实。 > 分析见词项,大小写已失。 # 06、排查 ```text 诊断树:品牌筛选为空 → 怀疑 term 查询写错 → 检查字段类型 → 发现 brand 为 Text → 根因:Text 被分词,term 匹配完整值失败 → 修复:改为 Keyword 或多字段 ``` 第一个问题是品牌筛选为空。运营选择品牌“华为”时,前端传的是 term 查询。我一开始怀疑查询语句写错,于是用 Kibana 直接执行。 ```json POST /product/_search { "query": { "term": { "brand": "华为" } } } ``` 结果确实为空。接着查看字段类型。 ```json GET /product/_mapping/field/brand ``` 示例输出显示 brand 是 Text 类型。用 _analyze 验证“华为”是否被切词。 ```json POST /product/_analyze { "field": "brand", "text": "华为" } ``` 示例输出显示词项是“华”和“为”。根因清楚了:标准分词器把“华为”切成了两个单字,term 查询匹配完整词项“华为”自然失败。 第二个问题是价格排序报错。 错误信息类似“Text fields are not optimised for operations that require per-document field data”。 这是因为 price 字段被错误映射成了 Text。 **错误尝试:** 我一开始试图在排序请求里加 `"unmapped_type": "double"`,想让 Elasticsearch 临时把字段当数值处理。 结果仍然报错,因为已有 Mapping 的类型优先级更高,unmapped_type 只对未映射字段生效。 最终修复是删除索引,用正确的 Mapping 重建。brand 改为 Keyword,price 改为 double。重建后,品牌筛选和价格排序都恢复正常。 > 筛选空无果,疑是查询错。 > 查映射方知,品牌被切破。 > 排序报错急,误试未映射。 > 根因在类型,重建方解脱。 # 07、优化 ```text V1:多字段方案已能支撑搜索、筛选、排序 → 证据:品牌 term 查询命中、价格排序正常 → V2:引入自定义 Analyzer,统一 Index Analyzer 与 Search Analyzer,并补充 Nested 与 DenseVector 字段 ``` V1 解决了类型问题,但中文切词仍然不理想。标准分词器把“华为”切成“华”和“为”,虽然 Keyword 字段解决了精确匹配,但 name 的全文搜索召回质量不高。 优化方向是引入自定义 Analyzer。对于中文,可以使用 IK 分词器插件;对于英文型号,可以保留 standard 分词器并配合 lowercase 和 asciifolding。 ```json PUT /product_v2 { "settings": { "analysis": { "analyzer": { "product_search_analyzer": { "type": "custom", "tokenizer": "ik_max_word", "filter": ["lowercase"] }, "product_index_analyzer": { "type": "custom", "tokenizer": "ik_smart", "filter": ["lowercase"] } } } }, "mappings": { "properties": { "name": { "type": "text", "analyzer": "product_index_analyzer", "search_analyzer": "product_search_analyzer", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "description": { "type": "text", "analyzer": "product_index_analyzer", "search_analyzer": "product_search_analyzer" }, "brand": { "type": "keyword" }, "category": { "type": "keyword" }, "price": { "type": "double" }, "tags": { "type": "keyword" }, "attributes": { "type": "nested", "properties": { "key": { "type": "keyword" }, "value": { "type": "keyword" } } }, "embedding": { "type": "dense_vector", "dims": 768, "index": true, "similarity": "cosine" }, "createdAt": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss||epoch_millis" } } } } ``` 为什么区分 Index Analyzer 和 Search Analyzer? 索引时用 ik_smart 做粗粒度切分,减少词项数量; 搜索时用 ik_max_word 做细粒度切分,提高召回。 两者职责不同,不能混用。 > 类型已修正,切词仍粗疏。 > 索引用粗切,搜索细粒度。 > 属性改嵌套,向量留后路。 > 优化非终点,演进看新图。 # 08、演进 ```text V2:自定义 Analyzer + Nested + DenseVector → 能力:中文召回提升、属性精确组合查询、向量相似搜索预留 → 遗留:已有索引需重建,Embedding 数据尚未接入 ``` 用 V1 同一组请求验证 V2 的效果。品牌筛选仍然精确命中,价格排序正常。新增验证:对 name 搜索“华为手机”,观察召回结果。 ```json POST /product_v2/_search { "query": { "match": { "name": "华为手机" } } } ``` 示例输出中,商品“HUAWEI Mate 60 Pro 手机”应能命中。 这是因为搜索时 ik_max_word 会把“华为手机”切成“华为”和“手机”,索引时 ik_smart 也会保留“华为”和“手机”等词项。 收益是中文召回质量明显提升,同时 Keyword 子字段继续支撑排序和精确过滤。代价是索引体积增加,IK 插件需要额外部署。 遗留问题是 attributes 从 Object 改为 Nested 后,旧索引数据无法直接迁移,必须重建。 DenseVector 字段虽然定义了,但 Embedding 生成服务还没接入,当前写入的文档没有向量数据。 > 同组请求验,筛选仍精确。 > 中文搜索路,召回已补缺。 > 向量虽定义,数据未连接。 > 演进留余地,落地看下节。 # 09、洞见 ```text 从现象到判断:Mapping 不是类型映射,而是搜索能力的提前声明 ``` ## Mapping 是搜索契约,不是数据字典 ```text Java 类型 → 搜索行为 → 底层结构 ``` Java 里的 String 可以随意拼接、比较、截取,但 Elasticsearch 里的 String 一旦映射为 Text 或 Keyword,搜索行为就固定了。 Text 负责召回,Keyword 负责精确。 把 Mapping 当数据字典看,是很多线上事故的起点。 ## 换 Analyzer 常常意味着重建索引 ```text 旧词项 → 新 Analyzer → 词项不一致 → 索引不可复用 ``` 反直觉判断:Analyzer 不是查询时的配置,而是索引时的写入逻辑。 已经写入的词项是用旧 Analyzer 生成的,换新 Analyzer 后,旧词项不会自动重新切分。 要生效,只能重建索引并重新写入数据。 ## 多字段是工程妥协,不是银弹 ```text Text 召回 + Keyword 精确 → 双份索引成本 ``` 反直觉判断:多字段看起来解决了所有问题,但它本质上是同一份数据写了两遍。字段数量多、数据量大时,存储和写入成本会成倍增加。只有真正需要全文搜索和精确操作同时存在的字段,才值得用多字段。 ## 先问搜索方式,再定字段类型 ```text 未来怎么搜 → 决定今天怎么映射 ``` 创建索引前,先列出这个字段未来可能参与的所有查询:全文搜索、精确过滤、排序、聚合、范围查询。根据查询方式反推字段类型,而不是根据 Java 类型直接映射。 > 映射非字典,搜索契约先。 > 分析器一换,旧词难再全。 > 多字段双写,成本记心间。 > 先问如何搜,再定类型篇。 # 10、系统落地 ```text 入口:商品管理后台与搜索服务 → 服务:Spring Boot 应用通过 Elasticsearch Java Client 写入与查询 → 本模块:product 索引 Mapping 与 Analyzer → 存储:Elasticsearch 集群 → 未实现:Embedding 向量生成与混合检索 ``` 原来系统只有商品基础信息存储和简单关键词搜索。 本篇增加了 product 索引的完整 Mapping,包括 Text 多字段、Keyword 精确字段、Nested 属性、DenseVector 向量字段,以及自定义中文 Analyzer。 现在系统能做什么:商品名称全文搜索、品牌和类目精确筛选、价格排序、标签聚合、属性组合查询。搜索接口的查询行为可预期,不再出现排序报错或筛选为空。 下一篇缺什么:Embedding 向量生成服务还没有接入,DenseVector 字段目前是空转。需要补上向量化流程,才能实现语义搜索和混合检索。 官方文档可参考 Elasticsearch 官网的 Mapping 和 Text analysis 章节,具体版本细节以当前官方文档为准。 > 入口管理台,服务写查来。 > 映射定索引,存储集群开。 > 向量未接入,混合检索待。 > 落地一步步,下篇补空白。 # 11、小结 ```text 三问三答:为什么全用 Text 出问题?排序聚合与精确过滤需要 Keyword 的完整值与 doc_values。怎么选?按未来搜索方式反推字段类型,多字段兼顾召回与精确。选错后果?数据量上来后必须重建索引,停机与迁移成本高。 ``` 为什么所有 String 都用 Text 会出问题? 因为 Text 经过 Analyzer 切词,倒排索引里存的是词项而不是完整值。 排序、聚合、精确过滤需要的是完整值和列式存储,Text 的底层结构根本不支持。 当排序和精确过滤同时出现时,应该选择 Text、Keyword 还是多字段?如果字段只有全文搜索需求,用 Text;只有精确过滤、排序、聚合需求,用 Keyword;两者都有,用多字段。 如果一开始选错字段类型,选错的后果是什么?数据量小的时候可以删索引重建,代价不大。数据量大了以后,重建索引意味着停机、数据迁移、双写过渡,甚至影响线上业务。 当前状态:product 索引已按 V2 方案重建,品牌筛选、价格排序、中文搜索均验证通过。DenseVector 字段已定义,等待向量生成服务接入。 > 全用 Text 错,词项非全值。 > 排序与聚合,Keyword 方支持。 > 选错类型后,重建代价知。 > 三问皆已答,作业待君试。 # 12、作业 ```text 理解 → 实战 → 排障 → 架构判断 ``` ## 理解题:为什么 Text 字段默认不能用于排序和聚合,而 Keyword 可以? Text 字段经过 Analyzer 切词后,倒排索引里存的是多个词项,不是完整原始值。 排序和聚合需要读取字段的完整值,而 Text 默认不启用 doc_values,无法高效读取。 Keyword 保留完整字符串作为单个词项,并默认启用 doc_values 列式存储,所以可以排序和聚合。 ## 实战题:创建一个 blog 索引,title 字段需要同时支持全文搜索和精确排序,content 字段只需要全文搜索,status 字段只需要精确过滤。请写出 Mapping。 ```json PUT /blog { "mappings": { "properties": { "title": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "content": { "type": "text" }, "status": { "type": "keyword" } } } } ``` ## 排障题:某索引的 name 字段是 Text 类型,现在业务要求按 name 精确排序,直接对 name 排序报错。请给出排查步骤和修复方案。 先查看 Mapping 确认 name 字段类型。 如果 name 是 Text 且没有 keyword 子字段,直接排序会报错。 修复方案有两种:一是删除索引,将 name 改为多字段,增加 name.keyword 子字段,然后重新写入数据; 二是如果数据量极大不能停机,使用 Reindex API 将数据迁移到新索引,新索引使用正确的多字段 Mapping,迁移完成后切换别名。 ## 架构判断题:一个字段未来既可能参与全文搜索,也可能参与精确过滤和排序,但当前数据量只有几千条。是否应该立即使用多字段?请说明理由。 应该立即使用多字段。 虽然当前数据量小,重建索引代价低,但 Mapping 是搜索契约,提前定义好多字段可以避免未来需求变化时再次重建。 多字段的额外存储成本在几千条数据量下可以忽略,但未来数据量增长后,重建索引的代价会远高于现在。 提前用多字段是成本最低的选择。 > 理解类型别,实战建索引。 > 排障查映射,架构判先机。 > 四题皆作答,工程见真知。 > 升华在末章,结论待君思。 # 13、升华 ```text 工程结论:Mapping 是在索引创建时提前决定“未来准备怎么搜索这份数据” ``` 这次改造的起点是一张错误的 Mapping,但根因不是技术不熟,而是把 Mapping 当成了 Java 类型映射。 Java 里的 String 可以随意操作,但 Elasticsearch 里的 String 一旦映射,搜索行为就固定了。 Text 和 Keyword 没有好坏之分,只有适用场景之分。Text 解决召回,Keyword 解决精确。多字段是两者兼得的工程手段,但代价是双份索引。 Analyzer 是索引时的写入逻辑,不是查询时的临时配置。换 Analyzer 之所以常常意味着重建索引,是因为旧词项不会自动重新切分。 有依据的结论是:在创建索引之前,先列出字段未来可能参与的所有搜索方式,再反推字段类型和 Analyzer。 这个顺序不能颠倒。 下一篇可以继续深入 Index Template 与 Aliases,解决多环境索引管理和零停机切换问题。 > 映射非类型,搜索先定局。 > Text 与 Keyword,各司其职去。 > 分析器一换,重建是常律。 > 先问如何搜,再定映射序。 **系列项目:** 电商中台搜索服务 **已完成:** 商品索引基础 Mapping、Text 与 Keyword 多字段、自定义中文 Analyzer、Nested 属性查询 **本篇:** Document Mapping 实战,讲透 Text、Keyword、Analyzer 的工程边界与重建索引判断 **下一篇:** Index Template 与 Aliases 实战,实现多环境索引管理与零停机切换

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册