<?xml version="1.0" encoding="utf-8"?>
<search> 
  
  
    
    <entry>
      <title>分析系统平台化探索与实践</title>
      <link href="/posts/t2f1/"/>
      <url>/posts/t2f1/</url>
      
        <content type="html"><![CDATA[<p>在数据驱动决策的实践中，企业通常需要多种分析工具的组合支持。本文描述了如何应对多元分析场景的技术复杂性，构建统一、灵活、可复用的平台，在标准化与定制化间寻找平衡，实现开发效率、系统质量与运维成本的最优解。</p><a id="more"></a><h1 id="一-背景"><a class="markdownIt-Anchor" href="#一-背景"></a> 一、背景​​</h1><p>在数据驱动决策的实践中，企业通常需要多种分析工具的组合支持。例如，用户行为分析、地理位置潜力评估、供给动态追踪、人口流动研究以及业务交叉分析等场景，往往需要差异化的解决方案。<br>这些工具在技术实现上呈现显著差异：</p><ul><li>​<strong>​功能形态​</strong>​：从标准化分析模型到高度定制化报表，从支持秒级响应的即席查询到需数小时计算的离线任务</li><li>​<strong>​数据架构​</strong>​：既有基于多表关联的动态计算模型，也有依赖预聚合的快速查询方案</li><li>​<strong>​技术栈​</strong>​：涉及OLAP引擎、批处理框架、NoSQL数据库等多种技术选型</li><li>​<strong>​交互需求​</strong>​：包含只读/读写模式、数据上传/下载等不同功能特性<br>​<strong>​平台化建设的核心挑战​</strong>​<br>在构建模块化数据分析平台时，我们面临多维度的复杂度：如何通过统一的底层架构，兼容多样的分析主题（用户/地理/供给等）、计算模式（实时/离线）、技术引擎（内存计算/批量处理）以及功能需求，同时实现三个核心目标——</li></ul><ol><li>​<strong>​开发提效​</strong>​：通过标准化组件降低重复建设</li><li>​<strong>​质量保障​</strong>​：建立统一的质量监控体系</li><li>​<strong>​成本优化​</strong>​：通过资源调度与弹性扩缩容降低运维成本<br>这要求平台设计既要有足够的扩展性适配当前技术生态，又要具备前瞻性以应对未来3-5年的业务演进。我们在分层架构设计、计算引擎抽象、服务治理等领域持续探索，力求在灵活性与标准化之间找到最佳平衡点。</li></ol><h1 id="二-分析类应用构建方式演进"><a class="markdownIt-Anchor" href="#二-分析类应用构建方式演进"></a> 二、分析类应用构建方式演进</h1><p><strong>阶段一、需求驱动（使用平台之前采用的建设方式）</strong></p><p>平台建设之前，所有系统，传统的业务开发方式，根据产品模块（业务）的划分将系统拆分为对应的一个个微服务（或者单体），case by case 实现功能。例如 用户分析 有用户细查、用户报告、路径分析等模块，系统层就对应了同等数量的微服务。仅用户分析一个产品就十几个服务，再加上其他产品，导致<strong>服务数量膨胀过多，带来较高的复杂度</strong>。每个服务都有完整的 Controller、Service、DAO层，有完整的存储引擎和中间件和发布项、流水线，需要单独部署运维。由于人力不能随之增加，容易<strong>超出承载能力</strong>。</p><p><strong>此种开发方式存在的问题：</strong><br>由于支持的产品数量变多，缺乏统一规划，容易导致服务拆分不合理、缺少复用性、难以统一维护，造成服务和代码数量膨胀（仅用户分析一项服务数20多个）、系统质量问题频发（用户分析 2022年线上问题数45个）、日常运维耗时耗力（2022年每周耗时 2～3pd 排查处理问题）、研发成本增高（2022年用户分析上线一个模型要40天）等问题。</p><p><strong>阶段二、分而治之、中心化开发方式（业界采用的方式）</strong></p><p>业界采用的构建方式一般根据产品的类型的不同，分为通用分析和报表分析不同的分析方式。不同的分析方式使用完全不同的构建方式。</p><p><strong>通用分析类</strong>（例如神策、字节TEA）：常见的建设方式是使用统一的数据模型（明细模型），存储引擎（ClickHouse）构建。系统层基于统一的数据层开发，往往比较薄，架构较为简单。​​</p><p><strong>报表分析类：</strong></p><ul><li>报表托拉拽式生成（公司魔数、字节 DataWind），通过组合各种组件配置成看板。组件通过页面选择、配置方式完成，整体无需写代码即可完成开发。该方式的优势在于开发门槛低、上线速度快，缺点在于灵活度不够，无法满足复杂的交互和计算需求。</li><li>框架开发（BIQuery、筑底），在微服务中嵌入框架，部分逻辑使用框架提供的功能开发，例如数据库连接。这种开发方式的问题在于：1）需要一定的学习成本和代码门槛 2）且框架不支持自定义扩展，增加新组件需要修改框架本身 3）框架非平台，无法统一管理各个应用。</li></ul><p>业界的构建方式不完全适合公司，原因是：<br><strong>1) 公司分析产品的建设具有特殊性</strong>。例如与字节对比，由于组织架构不同、分析系统业务范围不同，因此系统复杂度不同。其他公司负责通用分析与定制化分析在不同的团队，分开建设，往往是同类产品（指的是定制分析类和通用分析类）内部架构统一，不同类系统建设统一化平台架构的需求不高。<br>**2) 探索分析产品建设中有快速迭代试错的需求。**产品需要快速迭代上线产品，以验证不同的分析模式，下线效果不佳的分析模式。这些分析模式短时间内难以抽象为通用的分析模型，通过产品迭代进行探索。</p><p><strong>3) 分析产品建设与通用化报表建设思路不同</strong>。相比于通用分析报表，具有产品化（独立产品而非报表集合）、重体验（针对用户使用习惯优化）、强交互（例如地图缩放、联动）、高性能（即席分析的性能要求）的业务特点，通用化的平台（例如公司魔数、字节风神）难以满足。</p><p>因此，业务特点导致业界没有完全对标的产品。需要根据业务特点，吃透竞品建设内在逻辑，设计出符合公司业务需要、可以解决实际问题的，具有创新性的系统性甚至先进性的方案。</p><p><strong>阶段三、统一使用平台平台化开发（目前的开发方式）</strong><br>平台平台形成了“平台搭台，业务唱戏的合作模式”。平台提供了常用的功能，业务方通过插件扩展业务逻辑，通过配置编排流程。增加新功能模块从增加服务，变成了增加配置和插件包，更加轻量和简单。同时平台和业务也能配合，业务可以扩展平台功能，业务插件也可以更好的下沉到平台层复用。<br>相对于阶段一，新增模块不再需要新增微服务，只需新增配置+插件包，服务数量从十几个减少到个位数，变得更为轻量和精简。<br>相对于阶段二，1）平台不再区分通用分析和报表分析开发方式，两者使用统一的应用构建方式，便于统一设计、统一开发、统一运维。2）解决了平台无法扩展，无法灵活满足业务需求的痛点。</p><h1 id="三-平台平台应用建设"><a class="markdownIt-Anchor" href="#三-平台平台应用建设"></a> 三、平台平台应用建设</h1><h2 id="31-平台应用与技术模型"><a class="markdownIt-Anchor" href="#31-平台应用与技术模型"></a> 3.1 平台应用与技术模型</h2><p>为了应对分析类产品的多样性与复杂度，需要对产品进行建模。以下是平台分析类产品应用建模与对技术实践的建模。</p><p><img src="/posts/t2f1/file-20250406192456998.png" alt></p><p><strong>平台分析类产品应用模型：</strong></p><ul><li>产品应用层指整个的<strong>对外产品</strong>，例如用户分析、地理位置分析。</li><li>产品应用下包含不同的<strong>分析模型（模块）</strong>，例如 用户分析 包含用户报告分析模块，留存分析模型，地理位置分析包含潜客洞察模块、人口迁徙模块等。<br>以上两个层次是用户看得见的，下面的层次是在用户心智之外的概念层。概念层的建模用于更加容易推导出技术模型。</li><li>分析模型（模块）按照<strong>查询的即时性分类</strong>，可以分为在线分析和离线分析。</li><li>在线分析和离线分析有自己不同的<strong>业务过程</strong>，在线分析分为前置查询（查询菜单配置，例如可选的业务线、城市等）和结果查询（分析结果）两步，离线分析包含前置查询、离线任务运行与状态管理、结果查询三步。其中前置查询和结果查询都可以有同步查询</li><li>同步查询和异步查询两者都可以由<strong>功能点</strong>拼接出来。例如城市列表下载功能可以由数据源数据取回功能+数据格式化功能+下载功能组成。</li></ul><p><strong>平台技术模型：</strong><br>目前仅使用到编排层抽象，看板层抽象按需建设，看板模板层暂无需求。</p><ul><li><strong>看板模板</strong>：技术模型里产品是由多个看板模板（不是具体的看板）组合而成的。例如用户分析是由用户报告类看板、用户路径类看板等构成。用户报告类看板下有用户配置的自己的报告看板。当前没有动态构建产品的需求，无需这一层能力。</li><li><strong>编排/看板</strong>：每个分析模块，可以使用同一个看板模板配置出多个看板。看板也可以单拿出来作为一个功能，用户可以配置自己的看板。对于不是很复杂的功能或者原子性的分析组件，使用编排。编排指的是将多个功能接口组合成一个逻辑功能接口提供服务。</li><li><strong>平台服务</strong>：产品中在线分析和离线分析分别对应在线服务和离线服务。</li><li><strong>平台框架</strong>：业务过程固化成框架，确定每一个业务过程对应技术层所干的事情。</li><li><strong>平台/业务插件</strong>：产品功能点可以封装为插件，以插件粒度开发和复用，后面有相同功能时，可以复用该插件实现功能，无需再次开发。</li></ul><h2 id="32-平台标准建设"><a class="markdownIt-Anchor" href="#32-平台标准建设"></a> 3.2 平台标准建设</h2><p>基于上述模型，平台定义了一些标准与协议，用于规范认知与实践落地，包含分析业务流程、网关接口标准结构、插件数据流转协议、插件编写标准、配置文件标准结构等。</p><ul><li>分析业务流程：<ul><li>在线分析分为前置处理、引擎执行前、引擎执行、引擎执行后、结果后处理五步。所有使用平台在线分析框架（FastBI）构建的应用，均遵循以上业务流程。<br><img src="/posts/t2f1/file-20250406192912245.png" alt></li><li>离线分析业务流程：模型渲染、模型调度、模型执行、数据导入、模型后处理<br><img src="/posts/t2f1/file-20250406192935779.png" alt></li></ul></li><li>插件编写标准：前处理插件，需要继承 PreHandle、分析处理插件需要继承 BuIldInAnalysis、后处理插件需要继承 PostHandle。​​</li><li>网关接口标准结构：<ul><li>接口路径：/api/bi/{appKey}/{queryId}</li><li>请求方法：POST</li><li>接口参数：JSON类型，要求可以反序列化成 Map&lt;String,Object&gt;</li><li>返回结构：code、message、data、pageInfo</li></ul></li></ul><h2 id="33-平台操作流程-sop"><a class="markdownIt-Anchor" href="#33-平台操作流程-sop"></a> 3.3 平台操作流程、SOP</h2><p>基于上述标准，平台总结出常见场景下的操作流程，用于快速、标准化地支持应用建设，例如插件部署流程、问题定位处理流程、项目搭建操作流程等。</p><h1 id="四-回顾与展望"><a class="markdownIt-Anchor" href="#四-回顾与展望"></a> 四、回顾与展望</h1><h2 id="41-2023-应用建设过程回顾"><a class="markdownIt-Anchor" href="#41-2023-应用建设过程回顾"></a> 4.1 2023 应用建设过程回顾</h2><p>基于上述模型与平台实现，2023年平台应用于用户留存分析专项一期二期、地理位置分析节假日迁徙报告专项、地理位置分析场景专项开发中，约占整体需求专项的50% 。</p><ul><li>效率方面：2023年使用平台开发的专项需求中，无需求delay，研发效率提升50%以上，地理位置分析即席查询场景下实现了每个多维分析报表开发时间从3.5PD提升至0.55PD；2023年产品需求交付未影响产品侧的正常规划 （2022年产品侧反馈需求迭代困难）。</li><li>成本方面：节约机器数 12 台，节省人力成本1<sub>2PD/周（之前每周需要2</sub>3PD排查问题，现在需要0.5PD ），仅需 L6同学（L7指导） 即可完成日常需求开发（之前需要L7主力，L8指导）</li><li>质量方面：截止2023年，有6个产品模块同时使用平台，支持的MAU 1000+，有关平台平台线上问题数为0<br>随着支持的需求变多，效果会更加明显。</li></ul><p><strong>模型应用（建设过程）：</strong><br>以用户分析留存分析专项为例<br>应用模型建设：</p><table><thead><tr><th>产品</th><th>产品功能</th><th>涉及到的分类</th><th>业务过程</th><th>功能点</th></tr></thead><tbody><tr><td>用户分析</td><td>留存分析</td><td>在线分析、离线分析</td><td>在线分析：<br><br>- 前置查询：查询ID体系枚举、查询客户端枚举、查询应用枚举<br>    <br>- 结果查询：查询配置的留存日期范围内、第某日留存，可分人群查询。如果在线查询超过10s，降级为离线分析<br>    <br>离线分析：<br><br>- 离线查询SQL渲染<br>    <br>- 离线任务提交<br>    <br>- 离线状态流转<br>    <br>- 离线结果导入<br><br>​​</td><td>- 数据源连接<br>    <br>- 数据处理<br>    <br>- 查询降级<br>    <br>- 离线任务提交<br>    <br>- 离线数据导入<br>    <br>- 离线任务结果查询</td></tr><tr><td></td><td></td><td></td><td></td><td></td></tr></tbody></table><p>技术模型建设：</p><table><thead><tr><th><strong>看板模板</strong></th><th><strong>编排/看板</strong></th><th><strong>平台服务</strong></th><th><strong>平台框架</strong></th><th><strong>平台/业务插件</strong></th></tr></thead><tbody><tr><td>未涉及该层次</td><td>编排：<br><br>将留存指标和支付下单指标、按日周月查询编排到一起</td><td>调用在线分析服务、离线分析服务</td><td>- 使用在<strong>线分析框架</strong>（FastBI），处理流程：<br>    <br>    网关接收参数→参数校验前处理→Doris 引擎查询 →结果处理→ 平台服务返回<br>    <br>- 离线分析管理框架，处理流程：<br>    <br>    网关接收参数→渲染留存分析SQL→任务提交Tolas→任务状态流转到成功状态→结果导入到Doris查询</td><td>数据处理插件（留存业务开发）、下载插件（平台提供）、参数处理插件……</td></tr></tbody></table><h2 id="42-2024-规划"><a class="markdownIt-Anchor" href="#42-2024-规划"></a> 4.2 2024 规划</h2><p>总体从家庭作坊式开发 → 自动化阶段(人工处理少部分特殊业务逻辑，剩下的平台帮助解决)→ 智能化阶段（AI 员工辅助开发）三步走。<br>2024年主要的产品专项有：可视化应用升级（分析看板）、转化分析组件、路径分析组件、事件分析组件、用户分析（用户分层）组件。</p><ul><li>需求支持度方面：1）转化分析组件、路径分析组件、用户分析组件能力基本具备，与留存分析类似。2）分析看板能力对应技术模型的第二层，待建设。3） 事件分析可能用到元数据管理服务，新建能力还是复用已有能力待定。</li><li>开发效率提升方面：配置的托拉拽开发（对标业界开发方式）、AI驱动开发(下一代开发方式)可以按需建设。</li></ul>]]></content>
      
      
      <categories>
          
          <category> 大数据 </category>
          
          <category> 架构 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 原创 </tag>
            
            <tag> 架构 </tag>
            
            <tag> 大数据 </tag>
            
            <tag> 分析 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>地理位置信息分析技术与应用</title>
      <link href="/posts/1edt/"/>
      <url>/posts/1edt/</url>
      
        <content type="html"><![CDATA[<p>在现代商业智能和数据分析领域，地理位置信息已成为不可或缺的维度。通过分析用户的地理位置数据，企业能够获得深度的市场洞察，优化业务决策。本文将探讨大数据地理位置分析的技术实现和应用场景。</p><a id="more"></a><h2 id="一-技术篇"><a class="markdownIt-Anchor" href="#一-技术篇"></a> 一、技术篇</h2><h2 id="11-空间数据表示"><a class="markdownIt-Anchor" href="#11-空间数据表示"></a> 1.1 空间数据表示</h2><p>介绍了空间数据如何表示、如何索引、常见的运算。数仓开发中实用的概念、技术。</p><h3 id="111-大地测量系统"><a class="markdownIt-Anchor" href="#111-大地测量系统"></a> 1.1.1 大地测量系统</h3><ul><li><strong>坐标表示</strong><br>使用经纬度表示，比如天安门广场一点的经纬度：116.397303,39.909269</li><li><strong>WGS84</strong><br>大地坐标系，世界大地测量系统（World Geodetic System, WGS）是一种用于地图学、大地测量学和导航（包括全球定位系统）的大地测量系统标准。<br>WGS包含一套地球的标准经纬坐标系、一个用于计算原始海拔数据的参考椭球体，和一套用以定义海平面高度的引力等势面数据。<br>GPS全球定位系统使用的就是WGS 84参考系。</li><li><strong>GCJ02</strong><br>火星坐标系，地形图非线性保密处理算法，国测局坐标,是一种基于WGS-84制定的大地测量系统。<br>此坐标系所采用的混淆算法会在经纬度中加入看似随机的偏移。<br>GCJ02 座标串换成WGS-84的地图就可能造成100－700米不等的偏移。<br>GCJ-02大量使用正弦函数制造高频噪音，形成了一个超越方程，导致基本不可能获得解析解。<br>GCJ-02坐标系统本身保密，但是目前已有等多种语言的开源转换实现。</li></ul><h3 id="112-空间对象类型"><a class="markdownIt-Anchor" href="#112-空间对象类型"></a> 1.1.2 空间对象类型</h3><p>空间对象，是指定义空间数据的几种基础数据对象，主要是由常见的点、线、面组成。</p><ul><li>点(Point)</li><li>线(LineString)</li><li>面(Polygon)<br>其他：</li><li>多点(MultiPoint)</li><li>多线(MultiLineString)</li><li>多面(MultiPolygon)</li></ul><h3 id="113-空间数据格式"><a class="markdownIt-Anchor" href="#113-空间数据格式"></a> 1.1.3 空间数据格式</h3><ul><li>WKT/WKB<br>WKT(Well-Known Text)，是一种兼容了可读性和存储性的数据格式。WKB，是它的二进制格式，更适合数据库存储。</li><li>GeoJSON<br>Json，定义了geometry对象。在线检验GeoJSON的规范性： <a href="http://geojson.io/" rel="external nofollow noopener noreferrer" target="_blank">http://geojson.io/</a></li></ul><p>示例：</p><table>    <tr>        <td>空间对象类型</td>        <td>形状</td>        <td>WKT格式</td>        <td>GeoJSON格式</td>    </tr>    <tr>        <td>Point</td>        <td><img src="/posts/1edt/src+XaGM8NtBgYMGDCzMJl+Am4wOmV5LWx2AQAAAABJRU5ErkJggg==.png"></td>        <td>POINT (30 10)</td>        <td>{ "type": "Point", "coordinates": [30, 10] }</td>    </tr>    <tr>        <td>LineString</td>        <td><img src="/posts/1edt/t41.png"></td>        <td>LINESTRING (30 10, 10 30, 40 40)</td>        <td>{ "type": "LineString", "coordinates": [ [30, 10], [10, 30], [40, 40] ] }</td>    </tr>    <tr>        <td>Polygon</td>        <td><img src="/posts/1edt/BPwHtSsbiI43yqcAAAAASUVORK5CYII=.png"></td>        <td>POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))</td>        <td>{ "type": "Polygon", "coordinates": [ [[30, 10], [40, 40], [20, 40], [10, 20], [30, 10]]            ] }        </td>    </tr>    <tr>        <td>Polygon</td>        <td><img src="/posts/1edt/wT8B9JtwIhIB+z7AAAAAElFTkSuQmCC.png"></td>        <td>POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))</td>        <td>{ "type": "Polygon", "coordinates": [ [[35, 10], [45, 45], [15, 40], [10, 20], [35,            10]], [[20, 30], [35, 35], [30, 20], [20, 30]] ] }        </td>    </tr>    <tr>        <td>MultiPoint</td>        <td><img src="/posts/1edt/GWGmvHczcCAAaPzcM2uNwSjtR+9fgagOSn4yww1QzcDAwYMGC2Mpz8BVwo0dHnSxhGFAAAAAElFTkSuQmCC.png">        </td>        <td>MULTIPOINT ((10 40), (40 30), (20 20), (30 10))</td>        <td>{ "type": "MultiPoint", "coordinates": [ [10, 40], [40, 30], [20, 20], [30, 10] ] }</td>    </tr>    <tr>        <td>MultiPoint</td>        <td></td>        <td>MULTIPOINT (10 40, 40 30, 20 20, 30 10)</td>    </tr>    <tr>        <td>MultiLineString</td>        <td><img src="/posts/1edt/rB6nlF9PWv1DkDmNr6rnrU7AO+UMQ2jst6obuZyXoAJMAEmwEwEZkpfAv4Dgav5eaAJOX4AAAAASUVORK5CYII=.png">        </td>        <td>MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))</td>        <td>{ "type": "MultiLineString", "coordinates": [ [[10, 10], [20, 20], [10, 40]], [[40, 40],            [30, 30], [40, 20], [30, 10]] ] }        </td>    </tr>    <tr>        <td>MultiPolygon</td>        <td><img src="/posts/1edt/wT8A5vKd4jLnk3SAAAAAElFTkSuQmCC.png"></td>        <td>MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))</td>        <td>{ "type": "MultiPolygon", "coordinates": [ [ [[30, 20], [45, 40], [10, 40], [30, 20]] ],            [ [[15, 5], [40, 10], [10, 20], [5, 10], [15, 5]] ] ] }        </td>    </tr>    <tr>        <td>MultiPolygon</td>        <td><img src="/posts/1edt/jxl4iFH9c98AAAAASUVORK5CYII=.png"></td>        <td>MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20            35), (30 20, 20 15, 20 25, 30 20)))        </td>        <td>{ "type": "MultiPolygon", "coordinates": [ [ [[40, 40], [20, 45], [45, 30], [40, 40]] ],            [ [[20, 35], [10, 30], [10, 10], [30, 5], [45, 20], [20, 35]], [[30, 20], [20, 15], [20,            25], [30, 20]] ] ] }        </td>    </tr></table><h2 id="12-空间索引"><a class="markdownIt-Anchor" href="#12-空间索引"></a> 1.2 空间索引</h2><p>空间索引，是空间数据库用来优化空间查询的数据库索引，它是能够使得用户高效查询空间对象的一种数据结构。</p><p>传统的数据库索引(B树)，并不适合处理空间查询，比如点是否落入多边形内、点之间距离等等。如果不建立索引，则任何搜索都将需要对空间数据库中的每个记录进行“顺序扫描”，从而导致处理时间更长。</p><h3 id="121-需要索引的查询"><a class="markdownIt-Anchor" href="#121-需要索引的查询"></a> 1.2.1 需要索引的查询</h3><ul><li>窗口查询：给定一个查询窗口（通常是一个矩形），返回与查询窗口相重叠的物体。<br><img src="/posts/1edt/2Q==.jpeg" alt></li><li>点查询：给定一个点，返回包含这个点的所有几何图形</li></ul><h3 id="122-索引查询过程"><a class="markdownIt-Anchor" href="#122-索引查询过程"></a> 1.2.2 索引查询过程</h3><p>通过索引操作对象的MBB来进行查询一共分为两步</p><ul><li>Filtering: 过滤掉MBB(minimum bounding box，最小限定箱)不相交的数据集，剩下的MBB被索引到的称为一个数据的超集。<br><img src="/posts/1edt/9k=.jpeg" alt></li><li>Refinement: 测试实际的几何形状会不会满足查询条件，精确化。<br><img src="/posts/1edt/2Q==.1.jpeg" alt><ul><li>如何判断两个MBR是否相交？ 只要任一矩形的最右端都大于另一矩形的最左端且任一矩形最高端大于另一矩形的最低端，则两矩形相交；反之，若其中任一条件不满足，两矩形不相交。<br><img src="/posts/1edt/2Q==.2.jpeg" alt></li></ul></li></ul><h3 id="123-空间索引分类"><a class="markdownIt-Anchor" href="#123-空间索引分类"></a> 1.2.3 空间索引分类</h3><ul><li><strong>空间驱动的结构</strong><br>这些数据结构是基于将二维空间划分为单元(或网格)，并根据空间关系(重叠或相交)把空间对象映射到单元。IBM DB2和Microsoft SQL Server等商业数据库使用这些方法。</li><li><strong>数据驱动的结构</strong><br>这些数据结构直接由空间对象集合的分区来组织。使用mbr对数据对象进行分组，以适应其在嵌入空间中的分布。商业数据库，如开源数据库 PostGIS和MySQL，都使用这些数据结构。</li></ul><h3 id="124-空间索引举例"><a class="markdownIt-Anchor" href="#124-空间索引举例"></a> 1.2.4 空间索引举例</h3><p><strong>空间驱动结构 GeoHash：</strong><br>GeoHash 是一种将经纬度坐标（ lat/lon ）编码成字符串的方式。这么做的初衷只是为了让地理位置在 url 上呈现的形式更加友好，但现在 GeoHash 已经变成一种在数据库中有效索引地理坐标点和地理形状的方式。<br>GeoHash是一个二进制字符串，其中每个字符表示全局经纬度矩形的交替划分。从图8可以看出空间平面划分的过程。第一次划分将矩形分成两个GeoHash代码为“0”和“1”的正方形。位于垂直分区左侧的空间对象具有以“0”开头的 GeoHash，而位于右半部分的空间对象具有以“1”开头的GeoHash作为其第一个前缀。然后，每一边的数据进一步水平分割:在这一行以上的对象接收“0”，而在这一行以下的对象接收“1”作为它们的第二个前缀。模式继续分裂，直到达到所需的解决方案。每个Geohash矩形可以被分解成4个子哈希，划分5次,把整个世界分为 32 个单元的格子。单元又可以继续被分解成 32 个更小的单元，不断重复下去，以此类推。</p><p><img src="/posts/1edt/RvdbdM9+hVNawAAAABJRU5ErkJggg==.png" alt><img src="/posts/1edt/2Q==.3.jpeg" alt><br>GeoHash 使用 Base32 编码成字符串，比如下图展示了北京9个区域的GeoHash字符串，分别是WX4ER，WX4G2、WX4G3等等，每一个字符串代表了某一矩形区域。也就是说，这个矩形区域内所有的点（经纬度坐标）都共享相同的GeoHash字符串。<br>字符串越长，表示的范围越精确。</p><p><img src="/posts/1edt/lmPgRLBsUbI919CTGknGIJMFnaO3kB9y7XegCXcY25CF0JQMSr8L9ujCHAvVLJaAAAAAElFTkSuQmCC.png" alt><img src="/posts/1edt/Sr12idZCrOAdroVbUr15J+LR7fHATDeY4Y0lT8Dtvv2EVYCFm3YZStt+5kfPMwlWoFnRb3Ke67vzWxfjBMkybz2+fxfShtyK9uOWGdLcLBzOMeyMskc3j5ZE1g2L0WxupsNNOtKQR4GbgN2qeLVZ5ltcrzfvBCDQsb2UIDrqtjYkxXUk6sPJnVB4Yyk0A3qUabiP8P5gbDEiHufIUAAAAASUVORK5CYII=.png" alt></p><table><thead><tr><th>GeoHash <br>字符串长度</th><th>宽</th><th>高</th></tr></thead><tbody><tr><td>1</td><td>5,000km</td><td>5,000km</td></tr><tr><td>2</td><td>1,250km</td><td>625km</td></tr><tr><td>3</td><td>156km</td><td>156km</td></tr><tr><td>4</td><td>39.1km</td><td>19.5km</td></tr><tr><td>5</td><td>4.89km</td><td>4.89km</td></tr><tr><td>6</td><td>1.22km</td><td>0.61km</td></tr><tr><td>7</td><td>153m</td><td>153m</td></tr><tr><td>8</td><td>38.2m</td><td>19.1m</td></tr><tr><td>9</td><td>4.77m</td><td>4.77m</td></tr><tr><td>10</td><td>1.19m</td><td>0.596m</td></tr><tr><td>11</td><td>149mm</td><td>149mm</td></tr><tr><td>12</td><td>37.2mm</td><td>18.6mm</td></tr></tbody></table><p><strong>数据驱动的结构 R 树：</strong><br>R树是B树在高维空间的扩展，是一棵平衡树。每个R树的叶子结点包含了多个指向不同数据的指针，这些数据可以是存放在硬盘中的，也可以是存在内存中。<br>根据R树的这种数据结构，当我们需要进行一个高维空间查询时，我们只需要遍历少数几个叶子结点所包含的指针**（即缩小到某个区域下去进行查询，还是采用缩小范围的思想）**，查看这些指针指向的数据是否满足要求即可。这种方式使我们不必遍历所有数据即可获得答案，效率显著提高。<br><img src="/posts/1edt/JOBNSOhOMfkAAAAASUVORK5CYII=.png" alt></p><h2 id="13-空间数据计算"><a class="markdownIt-Anchor" href="#13-空间数据计算"></a> 1.3 空间数据计算</h2><p>空间数据计算包含坐标转换、索引计算、关系计算。例如：</p><ul><li>转换<ul><li>坐标系转换：gps转换为火星坐标系,火星转换为gps坐标系</li><li>WKT 格式与 GeoGSON 转换</li></ul></li><li>度量<ul><li>两点之间的球面距离</li><li>计算多边形面积</li></ul></li><li>索引<ul><li>点所在的 GeoHash</li><li>点到 GeoHash 中点的距离</li><li>面所在的 GeoHash</li><li>GeoHash 合并</li><li>把传入的GeoHash列表（以字符串形式传入，逗号分隔）进位至指定targetLevel</li></ul></li><li>关系<ul><li>几何 A 经过几何 B。</li><li>几何 A 完全包含在(Within)几何 B 中。</li><li>几何 A 完全包含几何 B。</li><li>几何之间彼此没有相交或接触（不相交）。</li><li>几何完全重合。</li><li>几何相互叠加。</li><li>几何接触于一点。</li></ul></li></ul><details> <summary> 关系的图形表示 </summary> <ul> <li>包含(Contains)/ Within <img src="/posts/1edt/OHFwgADs=.gif" alt="Contains Indicator"> <img src="/posts/1edt/wBylg0kkLA3lAwkSd0Tz0AnScB3CNxPZABiSQB3KQwA47CRwN13RLspOwAWab1zGwtiJgLLHL1gXVSRq62AQ1Dw3AAArCAE8tCQWw2BtKCUx7ASRc04o8uHmN2KAd2qI92qRd2qZ92qid2qq92qzd2q792rAd27I927Rd27Z927id27q927zd277928Ad3MI93MRd3HESCAA7.gif" alt="Contains Details"> </li> <li>不相交(Disjoint) <img src="/posts/1edt/6R2A14aSlwlnqHYZQAI6yJ8YmqEauqEc2qEe+qEgGqIiOqIkWqImeqIomqIquqIs2qIu+qIwGqMyOqM0WqM2eqM4mqM6uqM82qM++qNAGqRCOqREWqRGeqRImqRKuqRM2qRO+qRQGqVSOqVUWqVWeqVYmqVauqVc2qVe+qVgGqZiOqZkyk6BAAA7.gif" alt="Disjoint Representation"> </li> <li>经过(Crosses)/部分重合(Overlaps) <img src="/posts/1edt/A8If4KDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKzgoEAOw==.gif" alt="Crosses/Overlaps Indicator"> <img src="/posts/1edt/aIMBAA7.gif" alt="Crosses Detail"> </li> <li>接触(Touches) <img src="/posts/1edt/IlglPyUv8nJ8VFIDJihRL801C2mkcN4EUmM2gjRYgzZ4gziYgzq4gzzYgz74g0AYhEI4hERYhEZ4hEiYhEq4hEzYhE74hFAYhVI4hVRYhVZ4hViYhVq4hVzYhV54JIEAADs=.gif" alt="Touches Representation"> </li> </ul> </details><h2 id="14-空间数据处理在数仓计算中的应用举例"><a class="markdownIt-Anchor" href="#14-空间数据处理在数仓计算中的应用举例"></a> 1.4  空间数据处理在数仓计算中的应用举例</h2><p>已有用户流量表（包含经纬度字段）和 POI 维度数据表（包含POI 经纬度字段），计算某天内以 POI 为圆心，半径 1 km 的客流。</p><p><img src="/posts/1edt/v8B6kRmyhAIkP8AAAAASUVORK5CYII=.png" alt><br>解法：</p><table><thead><tr><th>方案</th><th>优点</th><th>缺点</th><th>采纳</th></tr></thead><tbody><tr><td>方案一：使用关系计算</td><td>实现简单，直观</td><td>性能差</td><td>否</td></tr><tr><td>方案二：使用索引计算</td><td>性能高</td><td>编码复杂</td><td>是</td></tr></tbody></table><h2 id="15-大数据地理信息系统gis"><a class="markdownIt-Anchor" href="#15-大数据地理信息系统gis"></a> 1.5 大数据地理信息系统（GIS）</h2><h3 id="151-什么是gis"><a class="markdownIt-Anchor" href="#151-什么是gis"></a> 1.5.1 什么是GIS</h3><p>GIS(Geographic Information System)，它是在计算机硬、软件系统支持下，对整个或部分地球表层（包括大气层）空间中的有<strong>关地理分布数据</strong>进行输入、存储、查询、分析和显示的计算机系统。<br>GIS将数据连接到地图上，将位置数据(事物所在的位置)与所有类型的描述信息(事物所在的位置)整合在一起。<br>地理信息系统通过使用位置作为“关键指标变量”，提供了将以前不相关的信息关联起来的能力。</p><h3 id="152-技术体系"><a class="markdownIt-Anchor" href="#152-技术体系"></a> 1.5.2 技术体系</h3><p>GIS 技术发展<br><img src="/posts/1edt/tlZCgazZ1k9PYVt2wiDNL9I0zVhYwQVhX9SUlJ3T38B3CC9U4AiWIIAAAAASUVORK5CYII=.png" alt><br>GIS基础软件五大技术体系，即：大数据GIS技术（Big Data GIS）、人工智能 GIS 技 术（AI- GIS）、新 一 代 三维 GIS 技 术 （New Three-D GIS）、分布式 GIS 技术（Distributed GIS）、跨平台 GIS 技术（Cross-platform GIS），合称 “BitDC”<br><img src="/posts/1edt/1iQP4AAAAASUVORK5CYII=.png" alt></p><h3 id="153-技术应用"><a class="markdownIt-Anchor" href="#153-技术应用"></a> 1.5.3 技术应用</h3><p>交通、国防、灾害预防、教育、能源、工程等<br>例如：</p><ul><li>增强现实——商业用途的增强现实：如广告和餐馆评论</li><li>地理围栏——用地理围栏限制汽车停放，是汽车共享和租赁的基础</li><li>自动驾驶汽车——自动驾驶汽车配备激光雷达、GPS等</li><li>实时交通——共享实时交通信息和道路警报</li></ul><h2 id="二-应用篇"><a class="markdownIt-Anchor" href="#二-应用篇"></a> 二 、应用篇</h2><h3 id="21-业务场景分类"><a class="markdownIt-Anchor" href="#21-业务场景分类"></a> 2.1 业务场景分类</h3><ul><li><strong>开城开站/选址：</strong> 业务在某个位置分析其线下客流（业务上限）、用户特征（通勤群体、白领群体等）、位置本身的特征（旅游城市、小区聚集地等），以及用户线上化渗透情况，再结合业务线自身采集的周边环境、业务供给等评估，来选定业务适合在哪些地方，什么范围开城/开站，或进行线下推广活动。</li><li><strong>业务策略调整：</strong> 根据业务当前渗透情况、平台渗透情况、需求量情况等及时调整业务已有运营策略或对制定新策略，如平台流量倾斜、省内补贴资源调配、活动上线策略等</li><li><strong>业务规模&amp;潜客空间评估：</strong> 对于业务线的一些客观特性，评估在地域范围内需求上限、业务已达成情况等，从而制定业务目标、评估可增长空间、分析当前不同区域业务特征及对应策略等</li></ul><h3 id="22-技术分类"><a class="markdownIt-Anchor" href="#22-技术分类"></a> 2.2 技术分类</h3><h4 id="221-点-面汇总类分析"><a class="markdownIt-Anchor" href="#221-点-面汇总类分析"></a> 2.2.1 点、面汇总类分析</h4><p>定义：分析区域内点（地图里的POI）、面（地图里的AOI） 的分布、数量、面积、种类、属性等信息，支持上卷下钻。</p><p>例如：计算xx城市餐饮外卖业务渗透率，即计算开通外卖的POI 的数量与全部餐饮POI的数量的比值。</p><h4 id="222-多点-线性分析"><a class="markdownIt-Anchor" href="#222-多点-线性分析"></a> 2.2.2 多点、线性分析</h4><p><strong>OD 分析：</strong></p><p>通过设置统计口径及空间粒度等相关参数，支持用户获取不同区域之间的<strong>居住和工作人口</strong>流动的数量、画像。辅助掌握不同区域之间联系紧密程度及诸如城镇化建设等相关政策的研究及制定。</p><p>可查询分析不同市、区之间的通勤信息和不同时间维度下的OD数量，以此判断职住平衡情况与城市间的出行、往来强度。</p><p>通过设置O点和D点范围、空间粒度等相关参数，支持用户获取不同区域之间的<strong>出发点-到达点</strong>数量、画像。辅助掌握城市出行强度及邻近城市之间的往来强度</p><p><strong>职住分析：</strong></p><p>通过设置统计口径、空间粒度等相关参数，支持用户获取不同市、区之间的<strong>通勤信息</strong>：通勤人口画像、通勤距离、通勤方式和通勤时间等</p><p><img src="app://781b4d20be03b26ca62464b6d978b8e9aabe/Volumes/docker/hexo-domain/source/_posts/%E5%9C%B0%E7%90%86%E4%BD%8D%E7%BD%AE%E4%BF%A1%E6%81%AF%E5%88%86%E6%9E%90%E6%8A%80%E6%9C%AF%E3%80%81%E5%BA%94%E7%94%A8%E5%8F%8A%E5%B1%95%E6%9C%9B/kwAAAAAElFTkSuQmCC.png?1743915653000" alt></p><p><strong>迁徙分析：</strong></p><p>通过设置出发区域、到达区域、时间范围等相关参数，支持用户获取<strong>不同城市</strong>之间的<strong>迁徙</strong>数量和迁徙人群画像。方便掌握城市之间人群流动特征。百度迁徙： <a href="https://qianxi.baidu.com/" rel="external nofollow noopener noreferrer" target="_blank">https://qianxi.baidu.com/</a></p><p><img src="app://781b4d20be03b26ca62464b6d978b8e9aabe/Volumes/docker/hexo-domain/source/_posts/%E5%9C%B0%E7%90%86%E4%BD%8D%E7%BD%AE%E4%BF%A1%E6%81%AF%E5%88%86%E6%9E%90%E6%8A%80%E6%9C%AF%E3%80%81%E5%BA%94%E7%94%A8%E5%8F%8A%E5%B1%95%E6%9C%9B/9jJ8wIAAAAASUVORK5CYII=.png?1743915653000" alt></p><p><strong>驻留分析：</strong></p><p>随着定位技术的不断发展，嵌入在手机的GPS、北斗等位置传感器可以周期性地记录移动对象的位置，产生了海量的轨迹数据，如行人轨迹、出租车轨迹、共享单车轨迹等。来自于这些轨迹数据中的驻留点（即移动对象在一段时间内停留在某个区域范围内产生的轨迹点）蕴含了丰富的移动对象停留信息，如出租车候车、行人游玩、货车装卸货等，这些可以帮助我们获取某一地点的动态访问信息，继而挖掘地点的动态特征，全面理解地点的时空信息</p><p><img src="app://781b4d20be03b26ca62464b6d978b8e9aabe/Volumes/docker/hexo-domain/source/_posts/%E5%9C%B0%E7%90%86%E4%BD%8D%E7%BD%AE%E4%BF%A1%E6%81%AF%E5%88%86%E6%9E%90%E6%8A%80%E6%9C%AF%E3%80%81%E5%BA%94%E7%94%A8%E5%8F%8A%E5%B1%95%E6%9C%9B/lxQAAAABJRU5ErkJggg==.png?1743915653000" alt></p><h2 id="三-解决方案案例商业选址"><a class="markdownIt-Anchor" href="#三-解决方案案例商业选址"></a> 三、解决方案案例：商业选址</h2><h3 id="31-解决哪些问题"><a class="markdownIt-Anchor" href="#31-解决哪些问题"></a> 3.1 解决哪些问题</h3><ol><li>已有门店布局是否合理？</li><li>哪些区域适合新开店？</li><li>某点位是否适合开店？</li></ol><h3 id="32-解决方案"><a class="markdownIt-Anchor" href="#32-解决方案"></a> 3.2 解决方案</h3><p><strong>（1）看概况，选片区——网点推荐</strong></p><p><img src="app://781b4d20be03b26ca62464b6d978b8e9aabe/Volumes/docker/hexo-domain/source/_posts/%E5%9C%B0%E7%90%86%E4%BD%8D%E7%BD%AE%E4%BF%A1%E6%81%AF%E5%88%86%E6%9E%90%E6%8A%80%E6%9C%AF%E3%80%81%E5%BA%94%E7%94%A8%E5%8F%8A%E5%B1%95%E6%9C%9B/pYhTCyUNr2dXSufq1mHr70z025E6qly8fPo+MiVpCtAQ5j47MQl4N6tZXIToc3inoBnC1DaILi9cAAAAAAA=.webp?1743915653000" alt></p><ol><li><p>根据不同行业的需求和选址城市的基本属性提供科学的模型评分规则，从目标客户属性、共生及竞对商家、商业氛围、周边配套、客群净需求等多维度对全城进行大数据扫描。</p></li><li><p>用户在平台可选择意向选址城市，配置个性化模型参数及其权重，输出模型的综合量化结果和可视化评估结果，查看推荐网格和叠加现有网点的网格的情况，</p></li><li><p>导出网格的详细数据，一目了然地锁定最佳价值区位。</p><p><img src="app://781b4d20be03b26ca62464b6d978b8e9aabe/Volumes/docker/hexo-domain/source/_posts/%E5%9C%B0%E7%90%86%E4%BD%8D%E7%BD%AE%E4%BF%A1%E6%81%AF%E5%88%86%E6%9E%90%E6%8A%80%E6%9C%AF%E3%80%81%E5%BA%94%E7%94%A8%E5%8F%8A%E5%B1%95%E6%9C%9B/ABdYcbdGNB1TAAAAAElFTkSuQmCC.png?1743915653000" alt></p></li></ol><p><strong>(2) 评点位——定点评估</strong></p><p>打破时空信息不对称，全方位评估备选点位特征，高效推动选址决策</p><p><img src="app://781b4d20be03b26ca62464b6d978b8e9aabe/Volumes/docker/hexo-domain/source/_posts/%E5%9C%B0%E7%90%86%E4%BD%8D%E7%BD%AE%E4%BF%A1%E6%81%AF%E5%88%86%E6%9E%90%E6%8A%80%E6%9C%AF%E3%80%81%E5%BA%94%E7%94%A8%E5%8F%8A%E5%B1%95%E6%9C%9B/AndNbODgLkIAAAAASUVORK5CYII=.png?1743915653000" alt></p><p>支持以多种方式快速锁定评估点位及其范围，包括关键词检索、上传点位坐标、自定义形状圈选、商圈选择，一键获取被评估区域的人口分析、职住分析、客流分析、品牌分析、设施分析、OD分析、交通分析和活力趋势分析的全维度可视化报告，支持保存和下载分析报告和相关数据。同时，平台打破时空信息的不对称性，支持多个点位的横向空间对比分析和单一点位的纵向时间对比分析，全方位评估备选点位情况，高效推动选址决策。</p>]]></content>
      
      
      <categories>
          
          <category> 大数据 </category>
          
          <category> 分析 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 原创 </tag>
            
            <tag> GIS </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>深入理解 ES 查询机制</title>
      <link href="/posts/9e+70/"/>
      <url>/posts/9e+70/</url>
      
        <content type="html"><![CDATA[<p>这边文章由笔者在快手一个内部分享 《深入理解 ES 查询机制》 整理而来，主要介绍了 ElasticSearch在搜索时，如何快速定位到相关文档，并揭示了文档得分的细节。包括:<br><strong>评分机制：</strong> ES 简介、TF/IDF 模型、空间向量模型、BM25 模型、模型在 ES 的实际体现；<br><strong>索引机制：</strong> 倒排索引、如何快速定位 Term、Term Index FST(有限状态机索引)、Posting List:Frame Of Reference.<br>文末提供原始分享幻灯片下载链接。</p><a id="more"></a><h1 id="elasticsearch-简介"><a class="markdownIt-Anchor" href="#elasticsearch-简介"></a> ElasticSearch 简介</h1><h2 id="为什么需要使用-es-进行搜索"><a class="markdownIt-Anchor" href="#为什么需要使用-es-进行搜索"></a> 为什么需要使用 ES 进行搜索</h2><h3 id="结构化数据-vs-非结构化数据"><a class="markdownIt-Anchor" href="#结构化数据-vs-非结构化数据"></a> 结构化数据 VS 非结构化数据</h3><p>想了解为什么需要ES进行搜索，需要先对比一下<strong>结构化数据和非结构化数据</strong>。</p><ul><li>结构化数据：<br>也称作行数据，关系型数据库进行存储和管理,是由二维表结构来逻辑表达和实现(可以使用行、列来表现)的数据，严格地遵循数据格式与长度规范。</li><li>非结构化数据：<br>又可称为全文数据，不定长或无固定格式，不适于由数据库二维表来表现，包括所有格式的办公文档、XML、HTML、word文档，邮件，各类报表、图片和音频、视频信息等。</li></ul><p>其他的不同之处还有：<br>结构化数据往往占用的空间较小，占企业数据的 20% 左右，容易管理。<br>非结构化数据通常占用更多的存储空间，约占企业数据的 80% 左右，比较难以管理</p><p><img src="/images/15720802244386.jpg" alt="结构化数据 vs 非结构化数据"></p><h3 id="结构化搜索-vs-全文搜索"><a class="markdownIt-Anchor" href="#结构化搜索-vs-全文搜索"></a> 结构化搜索 vs 全文搜索</h3><p><strong>结构化搜索：</strong><br>通常查询具有固有结构的数据<br>答案要么是肯定的，要么是否定的（即便是类似正则匹配这样的结构化搜索，正则表达式匹配数据也是确定的）<br>数据要么属于查询结果集合，要么不属于。</p><p><strong>全文搜索：</strong><br>通常查询全文字段/文档的所有内容<br>答案返回的是一系列可能的数据<br>数据有一定概率属于结果集合</p><p>到这里，为什么需要使用 ES 进行搜索的答案就很明确了：对于非结构化文本（比如评论内容），传统的结构化搜索难以满足需求，于是就会使用 ES 进行全文搜索。当然 ES 不仅可以进行全文搜索，也可以进行一部分的结构化搜索，更加扩大了他的应用范围。对于数据量巨大的情景，有公司会使用 ES 代替传统的 MySQL 管理数据。</p><h2 id="es-基本概念介绍"><a class="markdownIt-Anchor" href="#es-基本概念介绍"></a> ES 基本概念介绍</h2><p>本小结主要是介绍 ES 的一些基本概念，目的是方便之前没有了解过 ES 的同学可以理解这次分享所介绍的内容。</p><h3 id="es-存储模型"><a class="markdownIt-Anchor" href="#es-存储模型"></a> ES 存储模型</h3><p>ES 在设计存储模型时，考虑了大家从关系型数据库转换肯能带来的困难，于是设计了 Index、Type、Document、Field 分别于对应传统关系型数据库(比如 MySQL) 的 Database、Table、Row、Column。<br><strong>注意</strong>： ES 存储时，并没有 Type 的概念，同一个Index 里的 Type 会拍平存储，只是方便理解才会对使用者提供这样一个抽象。由于Type 的存在会带来一些问题，在后续的版本里会逐步移除，详情参见：<a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.7/removal-of-types.html" rel="external nofollow noopener noreferrer" target="_blank">https://www.elastic.co/guide/en/elasticsearch/reference/6.7/removal-of-types.html</a><br><img src="/images/15720830998289.jpg" alt></p><h3 id="es-与-lucene"><a class="markdownIt-Anchor" href="#es-与-lucene"></a> ES 与 Lucene</h3><p>ES 底层基于 <a href="https://lucene.apache.org/" rel="external nofollow noopener noreferrer" target="_blank">Lucene</a> 开发，Lucene作为其核心来实现<strong>索引</strong>和<strong>搜索</strong>的功能。我们虽然讲的是 ES，但很大一部分内容是 Lucene 的实现。</p><h1 id="评分机制"><a class="markdownIt-Anchor" href="#评分机制"></a> 评分机制</h1><h2 id="es-相似度similarity机制"><a class="markdownIt-Anchor" href="#es-相似度similarity机制"></a> ES 相似度(Similarity)机制</h2><h3 id="什么是相似度"><a class="markdownIt-Anchor" href="#什么是相似度"></a> 什么是相似度？</h3><p>前文中讲到，全文搜索返回的数据是有一定概率属于结果集。相似度就是反应这个概率大小的指标。<br>ES 把每一篇文档与查询词的相似度计算出来，使用一个数值来表示（<strong>评分</strong>），然后按照评分又高到底的顺序返回前 n 条数据。这就是一次查询的大致过程。</p><h3 id="如何计算相似度"><a class="markdownIt-Anchor" href="#如何计算相似度"></a> 如何计算相似度？</h3><p>相似度是使用相关算法计算的，ES 内部(也就是 Lucene)使用的称为 Lucene实用评分函数,大致长这样：</p><p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>s</mi><mi>c</mi><mi>o</mi><mi>r</mi><mi>e</mi><mo stretchy="false">(</mo><mi>q</mi><mo separator="true">,</mo><mi>d</mi><mo stretchy="false">)</mo><mo>=</mo><mstyle scriptlevel="0" displaystyle="true"><munderover><mo>∑</mo><mrow><mi>t</mi><mtext> </mtext><mi>i</mi><mi>n</mi><mtext> </mtext><mi>d</mi></mrow><mrow></mrow></munderover><mrow><mo stretchy="false">(</mo><mi>t</mi><mi>f</mi><mo stretchy="false">(</mo><mi>t</mi><mtext> </mtext><mi>i</mi><mi>n</mi><mtext> </mtext><mi>d</mi><mo stretchy="false">)</mo><mo>⋅</mo><mi>i</mi><mi>d</mi><mi>f</mi><mo stretchy="false">(</mo><mi>t</mi><msup><mo stretchy="false">)</mo><mn>2</mn></msup><mo>⋅</mo><mi>n</mi><mi>o</mi><mi>r</mi><mi>m</mi><mo stretchy="false">(</mo><mi>t</mi><mo separator="true">,</mo><mi>d</mi><mo stretchy="false">)</mo><mo>⋅</mo><mi>b</mi><mi>o</mi><mi>o</mi><mi>s</mi><mi>t</mi><mo stretchy="false">(</mo><mi>t</mi><mo stretchy="false">)</mo><mo stretchy="false">)</mo></mrow></mstyle></mrow><annotation encoding="application/x-tex">score(q,d)= \displaystyle \sum^{}_{t\, in \, d} {(tf(t \, in \, d) \cdot idf(t)^2 \cdot norm(t , d) \cdot boost(t) )}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">s</span><span class="mord mathnormal">c</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.03588em;">q</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal">d</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.652118em;vertical-align:-1.302113em;"></span><span class="mop op-limits"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3500050000000001em;"><span style="top:-1.8478869999999998em;margin-left:0em;"><span class="pstrut" style="height:3.05em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">t</span><span class="mspace mtight" style="margin-right:0.19516666666666668em;"></span><span class="mord mathnormal mtight">i</span><span class="mord mathnormal mtight">n</span><span class="mspace mtight" style="margin-right:0.19516666666666668em;"></span><span class="mord mathnormal mtight">d</span></span></span></span><span style="top:-3.0500049999999996em;"><span class="pstrut" style="height:3.05em;"></span><span><span class="mop op-symbol large-op">∑</span></span></span><span style="top:-4.300005em;margin-left:0em;"><span class="pstrut" style="height:3.05em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.302113em;"><span></span></span></span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal">i</span><span class="mord mathnormal">n</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal">d</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathnormal">i</span><span class="mord mathnormal">d</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathnormal">n</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">m</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal">d</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord mathnormal">b</span><span class="mord mathnormal">o</span><span class="mord mathnormal">o</span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mclose">)</span><span class="mclose">)</span></span></span></span></span></span></p><p>下面会详细介绍每一部分的意义。</p><h3 id="lucene-评分函数的产生"><a class="markdownIt-Anchor" href="#lucene-评分函数的产生"></a> Lucene 评分函数的产生</h3><p>Lucene 评分函数借鉴了<strong>词频/逆向文档频率</strong>（TF/IDF:term frequency/inverse document frequency）算法 和 <strong>向量空间模型</strong>（VSM:vector space model）<br>并于 ES 版本 5.0 将 TF/IDF 模型更换为 <strong>BM25 模型</strong><br>下面分别介绍提到的这几个概念</p><h2 id="词频逆向文档频率tfidf"><a class="markdownIt-Anchor" href="#词频逆向文档频率tfidf"></a> 词频/逆向文档频率（TF/IDF）</h2><h3 id="公式详解"><a class="markdownIt-Anchor" href="#公式详解"></a> 公式详解：</h3><p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>T</mi><mi>F</mi><mi>s</mi><mi>c</mi><mi>o</mi><mi>r</mi><mi>e</mi><mo>∗</mo><mi>I</mi><mi>D</mi><mi>F</mi><mi>s</mi><mi>c</mi><mi>o</mi><mi>r</mi><mi>e</mi><mo>∗</mo><mi>f</mi><mi>i</mi><mi>e</mi><mi>l</mi><mi>d</mi><mi>N</mi><mi>o</mi><mi>r</mi><mi>m</mi><mi>s</mi></mrow><annotation encoding="application/x-tex">TF score * IDF score * fieldNorms</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mord mathnormal" style="margin-right:0.13889em;">F</span><span class="mord mathnormal">s</span><span class="mord mathnormal">c</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal" style="margin-right:0.13889em;">F</span><span class="mord mathnormal">s</span><span class="mord mathnormal">c</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;"></span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mord mathnormal">i</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">d</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">m</span><span class="mord mathnormal">s</span></span></span></span></span></p><p>其中：</p><p><em><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>T</mi><mi>F</mi><mi>s</mi><mi>c</mi><mi>o</mi><mi>r</mi><mi>e</mi></mrow><annotation encoding="application/x-tex">TF score</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mord mathnormal" style="margin-right:0.13889em;">F</span><span class="mord mathnormal">s</span><span class="mord mathnormal">c</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span></span></span></span></em> 是词频得分，它的公式是:</p><p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msqrt><mrow><mi>t</mi><mi>f</mi></mrow></msqrt></mrow><annotation encoding="application/x-tex">\sqrt{tf}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.24em;vertical-align:-0.25612499999999994em;"></span><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.983875em;"><span class="svg-align" style="top:-3.2em;"><span class="pstrut" style="height:3.2em;"></span><span class="mord" style="padding-left:1em;"><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span></span></span><span style="top:-2.9438750000000002em;"><span class="pstrut" style="height:3.2em;"></span><span class="hide-tail" style="min-width:1.02em;height:1.28em;"><svg width="400em" height="1.28em" viewbox="0 0 400000 1296" preserveaspectratio="xMinYMin slice"><path d="M263,681c0.7,0,18,39.7,52,119c34,79.3,68.167,158.7,102.5,238c34.3,79.3,51.8,119.3,52.5,120c340,-704.7,510.7,-1060.3,512,-1067l0 -0c4.7,-7.3,11,-11,19,-11H40000v40H1012.3s-271.3,567,-271.3,567c-38.7,80.7,-84,175,-136,283c-52,108,-89.167,185.3,-111.5,232c-22.3,46.7,-33.8,70.3,-34.5,71c-4.7,4.7,-12.3,7,-23,7s-12,-1,-12,-1s-109,-253,-109,-253c-72.7,-168,-109.3,-252,-110,-252c-10.7,8,-22,16.7,-34,26c-22,17.3,-33.3,26,-34,26s-26,-26,-26,-26s76,-59,76,-59s76,-60,76,-60zM1001 80h400000v40h-400000z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.25612499999999994em;"><span></span></span></span></span></span></span></span></span></span></p><p><em>tf</em> 是要查询的词在一个文档中出现的次数。</p><p><em><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>I</mi><mi>D</mi><mi>F</mi><mi>s</mi><mi>c</mi><mi>o</mi><mi>r</mi><mi>e</mi></mrow><annotation encoding="application/x-tex">IDF score</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal" style="margin-right:0.13889em;">F</span><span class="mord mathnormal">s</span><span class="mord mathnormal">c</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span></span></span></span></em> 是逆向文档频率的得分，等于<em>文档频率</em> 的倒数取log。<br><em>文档频率</em> 就是要出现查询的词文档数与文档总数加一的比，代表了查询的词在全部文档中占的大小。</p><p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>i</mi><mi>d</mi><mi>f</mi><mo stretchy="false">(</mo><mi>t</mi><mtext> </mtext><mi>i</mi><mi>n</mi><mtext> </mtext><mi>d</mi><mo stretchy="false">)</mo><mo>=</mo><mn>1</mn><mo>+</mo><mi>log</mi><mo>⁡</mo><mrow><msub><mrow></mrow><mn>2</mn></msub><mn>2</mn></mrow><mfrac><mrow><mi>n</mi><mi>u</mi><mi>m</mi><mi>D</mi><mi>o</mi><mi>c</mi><mi>s</mi></mrow><mrow><mi>d</mi><mi>o</mi><mi>c</mi><mi>F</mi><mi>r</mi><mi>e</mi><mi>q</mi><mo>+</mo><mn>1</mn></mrow></mfrac></mrow><annotation encoding="application/x-tex">idf(t\,in\,d)=1+\log{_2 2} \frac{numDocs}{docFreq+1}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">i</span><span class="mord mathnormal">d</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal">i</span><span class="mord mathnormal">n</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord mathnormal">d</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.72777em;vertical-align:-0.08333em;"></span><span class="mord">1</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:2.2407700000000004em;vertical-align:-0.8804400000000001em;"></span><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord"><span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.30110799999999993em;"><span style="top:-2.5500000000000003em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord">2</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.36033em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">d</span><span class="mord mathnormal">o</span><span class="mord mathnormal">c</span><span class="mord mathnormal" style="margin-right:0.13889em;">F</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.03588em;">q</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord">1</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">n</span><span class="mord mathnormal">u</span><span class="mord mathnormal">m</span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal">o</span><span class="mord mathnormal">c</span><span class="mord mathnormal">s</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804400000000001em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><p><em>numDocs</em> 即为文档总数； <em>docFreq</em> 即为出现要查询词出现在文档(这个概念对应关系型数据库的“记录”,见上文)中出现的次数；<br><em>IDF score</em> 的意义在于考虑查询的词在文档总数出现的频率，对总分产生影响。查询的词在文档中出现的频率越高，对结果产生的影响越小。比如想 “的”，“你” 等常见的词几乎出现在每一个文档中，对结果会有很小的影响。</p><p><strong><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mi>i</mi><mi>e</mi><mi>l</mi><mi>d</mi><mi>N</mi><mi>o</mi><mi>r</mi><mi>m</mi><mi>s</mi></mrow><annotation encoding="application/x-tex">fieldNorms</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;"></span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mord mathnormal">i</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">d</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">m</span><span class="mord mathnormal">s</span></span></span></span></strong> 是字段长度归一值。它的计算方式为：</p><p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mn>1</mn><msqrt><mrow><mi>l</mi><mi>e</mi><mi>n</mi><mi>g</mi><mi>t</mi><mi>h</mi></mrow></msqrt></mfrac></mrow><annotation encoding="application/x-tex">\frac{1}{\sqrt{length}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:2.25144em;vertical-align:-0.93em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.32144em;"><span style="top:-2.275em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.835em;"><span class="svg-align" style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord" style="padding-left:0.833em;"><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">e</span><span class="mord mathnormal">n</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mord mathnormal">t</span><span class="mord mathnormal">h</span></span></span><span style="top:-2.795em;"><span class="pstrut" style="height:3em;"></span><span class="hide-tail" style="min-width:0.853em;height:1.08em;"><svg width="400em" height="1.08em" viewbox="0 0 400000 1080" preserveaspectratio="xMinYMin slice"><path d="M95,702c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429c69,-144,104.5,-217.7,106.5,-221l0 -0c5.3,-9.3,12,-14,20,-14H400000v40H845.2724s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47zM834 80h400000v40h-400000z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.20500000000000007em;"><span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.93em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><p><em>length</em> 表示文档的长度。<br><em>fieldNorms</em> 的意义在于考虑文档长度对于查询得分的影响。例如在一条短信中匹配到搜索的词与在一本书中匹配到，肯定是这个短信更有可能属于结果集。</p><p>将所有的分项带入，得到 TF/IDF 的公式：</p><p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>T</mi><mi>F</mi><mi>s</mi><mi>c</mi><mi>o</mi><mi>r</mi><mi>e</mi><mo>∗</mo><mi>I</mi><mi>D</mi><mi>F</mi><mi>s</mi><mi>c</mi><mi>o</mi><mi>r</mi><mi>e</mi><mo>∗</mo><mi>f</mi><mi>i</mi><mi>e</mi><mi>l</mi><mi>d</mi><mi>N</mi><mi>o</mi><mi>r</mi><mi>m</mi><mi>s</mi></mrow><annotation encoding="application/x-tex">TF score * IDF score * fieldNorms </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mord mathnormal" style="margin-right:0.13889em;">F</span><span class="mord mathnormal">s</span><span class="mord mathnormal">c</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal" style="margin-right:0.13889em;">F</span><span class="mord mathnormal">s</span><span class="mord mathnormal">c</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;"></span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mord mathnormal">i</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">d</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">m</span><span class="mord mathnormal">s</span></span></span></span></span></p><p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mo>=</mo><msqrt><mrow><mi>t</mi><mi>f</mi></mrow></msqrt><mo>∗</mo><mtext>（</mtext><mn>1</mn><mo>+</mo><mi>log</mi><mo>⁡</mo><mrow><msub><mrow></mrow><mn>2</mn></msub><mn>2</mn></mrow><mfrac><mrow><mi>n</mi><mi>u</mi><mi>m</mi><mi>D</mi><mi>o</mi><mi>c</mi><mi>s</mi></mrow><mrow><mi>d</mi><mi>o</mi><mi>c</mi><mi>F</mi><mi>r</mi><mi>e</mi><mi>q</mi><mo>+</mo><mn>1</mn></mrow></mfrac><mtext>）</mtext><mo>∗</mo><mfrac><mn>1</mn><msqrt><mrow><mi>l</mi><mi>e</mi><mi>n</mi><mi>g</mi><mi>t</mi><mi>h</mi></mrow></msqrt></mfrac></mrow><annotation encoding="application/x-tex">= \sqrt{tf} * （1+\log{_2 2} \frac{numDocs}{docFreq+1}） * \frac{1}{\sqrt{length}} </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.36687em;vertical-align:0em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1.24em;vertical-align:-0.25612499999999994em;"></span><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.983875em;"><span class="svg-align" style="top:-3.2em;"><span class="pstrut" style="height:3.2em;"></span><span class="mord" style="padding-left:1em;"><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span></span></span><span style="top:-2.9438750000000002em;"><span class="pstrut" style="height:3.2em;"></span><span class="hide-tail" style="min-width:1.02em;height:1.28em;"><svg width="400em" height="1.28em" viewbox="0 0 400000 1296" preserveaspectratio="xMinYMin slice"><path d="M263,681c0.7,0,18,39.7,52,119c34,79.3,68.167,158.7,102.5,238c34.3,79.3,51.8,119.3,52.5,120c340,-704.7,510.7,-1060.3,512,-1067l0 -0c4.7,-7.3,11,-11,19,-11H40000v40H1012.3s-271.3,567,-271.3,567c-38.7,80.7,-84,175,-136,283c-52,108,-89.167,185.3,-111.5,232c-22.3,46.7,-33.8,70.3,-34.5,71c-4.7,4.7,-12.3,7,-23,7s-12,-1,-12,-1s-109,-253,-109,-253c-72.7,-168,-109.3,-252,-110,-252c-10.7,8,-22,16.7,-34,26c-22,17.3,-33.3,26,-34,26s-26,-26,-26,-26s76,-59,76,-59s76,-60,76,-60zM1001 80h400000v40h-400000z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.25612499999999994em;"><span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.76666em;vertical-align:-0.08333em;"></span><span class="mord cjk_fallback">（</span><span class="mord">1</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:2.2407700000000004em;vertical-align:-0.8804400000000001em;"></span><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord"><span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.30110799999999993em;"><span style="top:-2.5500000000000003em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord">2</span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.36033em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">d</span><span class="mord mathnormal">o</span><span class="mord mathnormal">c</span><span class="mord mathnormal" style="margin-right:0.13889em;">F</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.03588em;">q</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord">1</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">n</span><span class="mord mathnormal">u</span><span class="mord mathnormal">m</span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal">o</span><span class="mord mathnormal">c</span><span class="mord mathnormal">s</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804400000000001em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord cjk_fallback">）</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:2.25144em;vertical-align:-0.93em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.32144em;"><span style="top:-2.275em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.835em;"><span class="svg-align" style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord" style="padding-left:0.833em;"><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">e</span><span class="mord mathnormal">n</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mord mathnormal">t</span><span class="mord mathnormal">h</span></span></span><span style="top:-2.795em;"><span class="pstrut" style="height:3em;"></span><span class="hide-tail" style="min-width:0.853em;height:1.08em;"><svg width="400em" height="1.08em" viewbox="0 0 400000 1080" preserveaspectratio="xMinYMin slice"><path d="M95,702c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429c69,-144,104.5,-217.7,106.5,-221l0 -0c5.3,-9.3,12,-14,20,-14H400000v40H845.2724s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47zM834 80h400000v40h-400000z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.20500000000000007em;"><span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.93em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><h3 id="tfidf-在-es-中的体现"><a class="markdownIt-Anchor" href="#tfidf-在-es-中的体现"></a> TF/IDF 在 ES 中的体现</h3><p><strong>ES 中的数据：</strong><br><img src="/images/15720913679785.jpg" alt></p><p>搜索”zhang“ 后，得到的前一条结果及其得分的详情：<br><img src="/images/15720916071375.jpg" alt></p><p>我们可以看到框选的部分： tf、idf、fieldNorm 分别对应前文公式的三个部分。感兴趣的同学可以自己具体计算一下得分。</p><h2 id="空间向量模型-vsm"><a class="markdownIt-Anchor" href="#空间向量模型-vsm"></a> 空间向量模型 (VSM)</h2><p>上面介绍的TF/IDF 模型是针对一个词来进行查询的，如果我们搜索多个词，改怎么处理呢？<br>向量空间模型（Vector Space Model） 提供一种比较多词查询的方式，模型将文档和查询都以 向量（vectors）的形式表示。</p><p>举例来说：<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p><p>假设我们有三个文档：</p><blockquote><p>I am happy in summer 。<br>After Christmas I’m a hippopotamus 。<br>The happy hippopotamus helped Harry 。</p></blockquote><p>我们要在这三个文档中搜索 “happy hippopotamus” (快乐的河马)<br>可以为每个文档都创建包括每个查询词 （ happy 和 hippopotamus ）权重的向量(事实上使用的是得分，这里为了便于理解，人工指定了权重)，然后将这些向量置入同一个坐标系中：<br><img src="/images/15720919871220.jpg" alt></p><p>通过比较各个文档的向量与查询词的向量的夹角(计算时使用夹角的余弦值)，来判断哪个文档与查询更为接近。</p><p>具体计算时，使用**<a href="http://en.wikipedia.org/wiki/Cosine_similarity" rel="external nofollow noopener noreferrer" target="_blank">余弦相似度</a>**公式计算：</p><p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>c</mi><mi>o</mi><mi>s</mi><mi>θ</mi><mo>=</mo><mfrac><mrow><mover accent="true"><mi>a</mi><mo>⃗</mo></mover><mo>⋅</mo><mover accent="true"><mi>b</mi><mo>⃗</mo></mover></mrow><mrow><mi mathvariant="normal">∣</mi><mi>a</mi><mi mathvariant="normal">∣</mi><mo>⋅</mo><mi mathvariant="normal">∣</mi><mi>b</mi><mi mathvariant="normal">∣</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">cos{\theta}= \frac{\vec{a} \cdot \vec{b}}{|a|\cdot |b|}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathnormal">c</span><span class="mord mathnormal">o</span><span class="mord mathnormal">s</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.02778em;">θ</span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:2.59044em;vertical-align:-0.936em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.65444em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">∣</span><span class="mord mathnormal">a</span><span class="mord">∣</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord">∣</span><span class="mord mathnormal">b</span><span class="mord">∣</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.714em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">a</span></span></span><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2355em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 53.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 1110.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">⋅</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.9774399999999999em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">b</span></span></span><span style="top:-3.26344em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2355em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 53.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 1110.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.936em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><h2 id="lucene-实用评分函数"><a class="markdownIt-Anchor" href="#lucene-实用评分函数"></a> Lucene 实用评分函数</h2><p>Lucene 实用评分函数是有余弦相似度公式演变而来。图中相同颜色的框代表的含义是一样的。<br><img src="/images/15720930144253.jpg" alt></p><p>我们可以看到，Lucene 实用评分函数改变的是 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi><mi>o</mi><mi>c</mi><mi>L</mi><mi>e</mi><mi>n</mi><mi>N</mi><mi>o</mi><mi>r</mi><mi>m</mi><mo stretchy="false">(</mo><mi>d</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">docLenNorm(d)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">d</span><span class="mord mathnormal">o</span><span class="mord mathnormal">c</span><span class="mord mathnormal">L</span><span class="mord mathnormal">e</span><span class="mord mathnormal">n</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">m</span><span class="mopen">(</span><span class="mord mathnormal">d</span><span class="mclose">)</span></span></span></span> 增加了 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>q</mi><mi>u</mi><mi>e</mi><mi>r</mi><mi>y</mi><mi>B</mi><mi>o</mi><mi>o</mi><mi>s</mi><mi>t</mi><mo stretchy="false">(</mo><mi>q</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">queryBoost(q)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.03588em;">q</span><span class="mord mathnormal">u</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="mord mathnormal">o</span><span class="mord mathnormal">o</span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.03588em;">q</span><span class="mclose">)</span></span></span></span> 和 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi><mi>o</mi><mi>c</mi><mi>B</mi><mi>o</mi><mi>o</mi><mi>s</mi><mi>t</mi><mo stretchy="false">(</mo><mi>d</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">docBoost(d)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">d</span><span class="mord mathnormal">o</span><span class="mord mathnormal">c</span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="mord mathnormal">o</span><span class="mord mathnormal">o</span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mopen">(</span><span class="mord mathnormal">d</span><span class="mclose">)</span></span></span></span>。<br>上文提到过 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi><mi>o</mi><mi>c</mi><mi>L</mi><mi>e</mi><mi>n</mi><mi>N</mi><mi>o</mi><mi>r</mi><mi>m</mi><mo stretchy="false">(</mo><mi>d</mi><mo stretchy="false">)</mo><mo>=</mo><mfrac><mn>1</mn><msqrt><mrow><mi>l</mi><mi>e</mi><mi>n</mi><mi>g</mi><mi>t</mi><mi>h</mi></mrow></msqrt></mfrac></mrow><annotation encoding="application/x-tex">docLenNorm(d)= \frac{1}{\sqrt{length}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">d</span><span class="mord mathnormal">o</span><span class="mord mathnormal">c</span><span class="mord mathnormal">L</span><span class="mord mathnormal">e</span><span class="mord mathnormal">n</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">m</span><span class="mopen">(</span><span class="mord mathnormal">d</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1.383108em;vertical-align:-0.538em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.845108em;"><span style="top:-2.6015625em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord sqrt mtight"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.840625em;"><span class="svg-align" style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mtight" style="padding-left:0.833em;"><span class="mord mathnormal mtight" style="margin-right:0.01968em;">l</span><span class="mord mathnormal mtight">e</span><span class="mord mathnormal mtight">n</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span><span class="mord mathnormal mtight">t</span><span class="mord mathnormal mtight">h</span></span></span><span style="top:-2.800625em;"><span class="pstrut" style="height:3em;"></span><span class="hide-tail mtight" style="min-width:0.853em;height:1.08em;"><svg width="400em" height="1.08em" viewbox="0 0 400000 1080" preserveaspectratio="xMinYMin slice"><path d="M95,702c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429c69,-144,104.5,-217.7,106.5,-221l0 -0c5.3,-9.3,12,-14,20,-14H400000v40H845.2724s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47zM834 80h400000v40h-400000z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.19937500000000008em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.538em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span> ，考虑了文档的长度，而余弦相似度公式中对应的部分为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mn>1</mn><mrow><mi mathvariant="normal">∣</mi><mi>a</mi><mi mathvariant="normal">∣</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">\frac{1}{|a|}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.365108em;vertical-align:-0.52em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.845108em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">∣</span><span class="mord mathnormal mtight">a</span><span class="mord mtight">∣</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.52em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span>，为 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mover accent="true"><mi>b</mi><mo>⃗</mo></mover></mrow><annotation encoding="application/x-tex">\vec{b}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9774399999999999em;vertical-align:0em;"></span><span class="mord accent"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.9774399999999999em;"><span style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">b</span></span></span><span style="top:-3.26344em;"><span class="pstrut" style="height:3em;"></span><span class="accent-body" style="left:-0.2355em;"><span class="overlay" style="height:0.714em;width:0.471em;"><svg width="0.471em" height="0.714em" style="width:0.471em" viewbox="0 0 471 714" preserveaspectratio="xMinYMin"><path d="M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 53.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 1110.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359c-16-25.333-24-45-24-59z"/></svg></span></span></span></span></span></span></span></span></span></span> 的单位长度，未考虑文档长度。<br><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>q</mi><mi>u</mi><mi>e</mi><mi>r</mi><mi>y</mi><mi>B</mi><mi>o</mi><mi>o</mi><mi>s</mi><mi>t</mi><mo stretchy="false">(</mo><mi>q</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">queryBoost(q)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.03588em;">q</span><span class="mord mathnormal">u</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal" style="margin-right:0.03588em;">y</span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="mord mathnormal">o</span><span class="mord mathnormal">o</span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.03588em;">q</span><span class="mclose">)</span></span></span></span> 和 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi><mi>o</mi><mi>c</mi><mi>B</mi><mi>o</mi><mi>o</mi><mi>s</mi><mi>t</mi><mo stretchy="false">(</mo><mi>d</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">docBoost(d)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">d</span><span class="mord mathnormal">o</span><span class="mord mathnormal">c</span><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="mord mathnormal">o</span><span class="mord mathnormal">o</span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mopen">(</span><span class="mord mathnormal">d</span><span class="mclose">)</span></span></span></span> 分别代表查询的权重提升和文档的权重提升，可以更为灵活的手工指定，影响搜索结果。</p><h2 id="bm25-相似度"><a class="markdownIt-Anchor" href="#bm25-相似度"></a> BM25 相似度</h2><p>BM25 全称 Okapi BM25 是 Okapi Best Match 25 的缩写。<br>ES 版本 5.0 将 TF/IDF 模型更换为了 BM25 模型。BM25可以看做是 TF/IDF 的改良。<br>BM25 与 TF/IDF  相比，主要体现在 TF 和 fieldNorms 的计算上。</p><h3 id="idf"><a class="markdownIt-Anchor" href="#idf"></a> IDF</h3><p>对于IDF，我们看到两者的趋势基本是一致的<br><img src="/images/15720955946183.jpg" alt></p><h3 id="tf"><a class="markdownIt-Anchor" href="#tf"></a> TF</h3><p>对于 TF 我们看到，传统的 TF 曲线随着 tf(词频) 的升高，其值无限的增加，而 BM 25的<br>TF 值收敛到一个值(<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span></span></span></span>)。在一定程度上，词频越高，固然得分也应该越高。但是不能没有限度。否则像是网页搜索，站长可以通过作弊，使得词频达到极高的值，就达到了垄断这个词的效果 了。<br><img src="/images/15720956551427.jpg" alt></p><h3 id="fieldnorms"><a class="markdownIt-Anchor" href="#fieldnorms"></a> fieldNorms</h3><p>BM25 将 fieldNorms 与词频进行合并，一起对得分产生影响,称为 tfNorm</p><h3 id="tfnorm"><a class="markdownIt-Anchor" href="#tfnorm"></a> tfNorm</h3><p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>t</mi><mi>f</mi><mi>N</mi><mi>o</mi><mi>r</mi><mi>m</mi><mo>=</mo><mo stretchy="false">(</mo><mo stretchy="false">(</mo><mi>k</mi><mo>+</mo><mn>1</mn><mo stretchy="false">)</mo><mo>∗</mo><mi>t</mi><mi>f</mi><mo stretchy="false">)</mo><mi mathvariant="normal">/</mi><mo stretchy="false">(</mo><mi>k</mi><mo>∗</mo><mo stretchy="false">(</mo><mn>1.0</mn><mo>−</mo><mi>b</mi><mo>+</mo><mi>b</mi><mo>∗</mo><mi>L</mi><mo stretchy="false">)</mo><mo>+</mo><mi>t</mi><mi>f</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">tfNorm = ((k + 1) * tf) / (k * (1.0 - b + b * L) + tf)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8888799999999999em;vertical-align:-0.19444em;"></span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.02778em;">r</span><span class="mord mathnormal">m</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mclose">)</span><span class="mord">/</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord">1</span><span class="mord">.</span><span class="mord">0</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord mathnormal">b</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathnormal">b</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">L</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mclose">)</span></span></span></span></span></p><p><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>L</mi></mrow><annotation encoding="application/x-tex">L</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal">L</span></span></span></span>：表示文档的长度是平均文档长度的多少倍。计算方式：<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mrow><mi mathvariant="normal">∣</mi><mi>d</mi><mi mathvariant="normal">∣</mi></mrow><mrow><mi>a</mi><mi>v</mi><mi>g</mi><mi>D</mi><mi>l</mi></mrow></mfrac><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\frac{|d|}{avgDl})</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.491108em;vertical-align:-0.481108em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.01em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">a</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">v</span><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span><span class="mord mathnormal mtight" style="margin-right:0.02778em;">D</span><span class="mord mathnormal mtight" style="margin-right:0.01968em;">l</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.485em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">∣</span><span class="mord mathnormal mtight">d</span><span class="mord mtight">∣</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.481108em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose">)</span></span></span></span><br><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>b</mi></mrow><annotation encoding="application/x-tex">b</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathnormal">b</span></span></span></span> 参数的作用：调节 L 的影响程度,当b=0时，文档的长度对于结果没有影响</p><p>下图展示了文档长度对于得分的影响：<br><img src="/images/15720966895570.jpg" alt></p><h3 id="bm25-公式"><a class="markdownIt-Anchor" href="#bm25-公式"></a> BM25 公式</h3><p>将以上部分组合起来，我们得到了 BM25 的公式：</p><p class="katex-block"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>I</mi><mi>D</mi><mi>F</mi><mo>∗</mo><mo stretchy="false">(</mo><mo stretchy="false">(</mo><mi>k</mi><mo>+</mo><mn>1</mn><mo stretchy="false">)</mo><mo>∗</mo><mi>t</mi><mi>f</mi><mo stretchy="false">)</mo><mi mathvariant="normal">/</mi><mo stretchy="false">(</mo><mi>k</mi><mo>∗</mo><mo stretchy="false">(</mo><mn>1.0</mn><mo>−</mo><mi>b</mi><mo>+</mo><mi>b</mi><mo>∗</mo><mo stretchy="false">(</mo><mfrac><mrow><mi mathvariant="normal">∣</mi><mi>d</mi><mi mathvariant="normal">∣</mi></mrow><mrow><mi>a</mi><mi>v</mi><mi>g</mi><mi>D</mi><mi>l</mi></mrow></mfrac><mo stretchy="false">)</mo><mo stretchy="false">)</mo><mo>+</mo><mi>t</mi><mi>f</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">IDF * ((k + 1) * tf) / (k * (1.0 - b + b * (\frac{|d|}{avgDl})) + tf)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal" style="margin-right:0.13889em;">F</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mclose">)</span><span class="mord">/</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord">1</span><span class="mord">.</span><span class="mord">0</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.77777em;vertical-align:-0.08333em;"></span><span class="mord mathnormal">b</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.69444em;vertical-align:0em;"></span><span class="mord mathnormal">b</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:2.30744em;vertical-align:-0.8804400000000001em;"></span><span class="mopen">(</span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.427em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">a</span><span class="mord mathnormal" style="margin-right:0.03588em;">v</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">∣</span><span class="mord mathnormal">d</span><span class="mord">∣</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804400000000001em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mclose">)</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mclose">)</span></span></span></span></span></p><h1 id="更多"><a class="markdownIt-Anchor" href="#更多"></a> 更多</h1><p>分享的第二部分，请查看 深入理解 ES 查询机制[二]（即将推出）</p><h1 id="扩展阅读"><a class="markdownIt-Anchor" href="#扩展阅读"></a> 扩展阅读</h1><hr class="footnotes-sep"><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>来自 ElasticSearch 权威指南：<a href="https://www.elastic.co/guide/cn/elasticsearch/guide/current/scoring-theory.html" rel="external nofollow noopener noreferrer" target="_blank">https://www.elastic.co/guide/cn/elasticsearch/guide/current/scoring-theory.html</a> <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content>
      
      
      <categories>
          
          <category> 大数据 </category>
          
          <category> ElasticSearch </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 原创 </tag>
            
            <tag> ES </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Delta Lake 全面解析</title>
      <link href="/posts/36c8/"/>
      <url>/posts/36c8/</url>
      
        <content type="html"><![CDATA[<p>Delta Lake 在 Spark + AI Summit 2019 宣布开源，引起了不小的震动，这到底是何方神圣？本文将从什么是 Delta Lake、它有那些特点、它是如何实现的，以及它的出现对未来大数据领域和大数据从业者可能有什么影响这些角度，全面解析这一新一代的文件存储层。</p><a id="more"></a><h1 id="delta-lake"><a class="markdownIt-Anchor" href="#delta-lake"></a> Delta Lake</h1><p><a href="https://delta.io/" rel="external nofollow noopener noreferrer" target="_blank">Delta Lake</a> 是 Databricks (俗称&quot;砖厂&quot;) 开源的一个文件存储层,目前运行在 Spark 上。它主要提供了以下功能（摘自官网）：</p><ol><li>ACID 事务(ACID transactions)</li><li>Schema 相关特性：Schema 本地存储、支持 Schema 约束、Schema 演变 (Scalable Metadata Handling、Schema Enforcement、Schema Evolution)</li><li>数据版本控制 (Time Travel (data versioning))</li><li>支持数据更新删除 （Updates and Deletes）</li><li>统一了流数据和批处理数据落地 (Unified batch and streaming sink)</li></ol><h1 id="特性详细介绍"><a class="markdownIt-Anchor" href="#特性详细介绍"></a> 特性详细介绍</h1><p>下面将会对Delta Lake 的特性详细介绍。</p><h2 id="acid-事务"><a class="markdownIt-Anchor" href="#acid-事务"></a> ACID 事务</h2><p>这里的 ACID 就是指原子性、一致性、隔离性和持久性。一致性、持久性之前就已经实现，这里主要是解释一下原子性和隔离性。<br><strong>原子性：</strong> 一个事务要么成功要么失败，不存在中间状态。这一个特性对于数据的准确性，特别是出现失败时候仍然保持准确性至关重要。当 job 失败时已经写入的数据会自动回滚到未写入的状态，不需要手工处理。<br><strong>隔离性：</strong> 基于<a href="https://en.wikipedia.org/wiki/Optimistic_concurrency_control" rel="external nofollow noopener noreferrer" target="_blank">乐观的并发控制</a>实现可序列花的隔离级别。乐观的并发控制在竞争不很激烈的情况下，会提高性能。可序列花的隔离级别保证了即使并发执行读或写操作，仍然保证像在串行读写一样。<br>具体的，Delta Lake 支持并发读取、并发追加(追加的内容不依赖于任何的读取已经存在的数据)，但不支持并发修改，出现并发修改会抛出 concurrent modification exception。<br>隔离性保证无论是并发批处理操作、流操作或者是批流并发操作，数据都是准确的</p><h2 id="schema-相关特性"><a class="markdownIt-Anchor" href="#schema-相关特性"></a> Schema 相关特性</h2><p>Hive Metastore 一般用中心化的存储（如 MySQL） 对 Schema 进行管理。在 Schema 数量特别巨大时(比如分区数特别多)，由于中心化存储伸缩性是非线性的，容易形成瓶颈。<br>Delta Lake 将元数据同样视为数据，保存在文件中，使得对大数据的处理能力可以运用在处理元数据上。<br>由于元数据由文件进行管理，所以有了更大的灵活性和可能性，Schema Enforcement （强制检验数据的 Schema，不通过则拒绝），Schema Evolution （根据数据自动更改 Schema，无需手动指定）</p><h2 id="数据版本控制"><a class="markdownIt-Anchor" href="#数据版本控制"></a> 数据版本控制</h2><p>事务都有了，实现版本控制也是顺带的事。Delta Lake 可以读取某个版本的数据或者恢复数据到某一个历史版本。</p><h2 id="支持数据更新删除"><a class="markdownIt-Anchor" href="#支持数据更新删除"></a> 支持数据更新删除</h2><p>支持包括单条数据、批量数据的更新和删除</p><h2 id="统一了流数据和批处理数据落地"><a class="markdownIt-Anchor" href="#统一了流数据和批处理数据落地"></a> 统一了流数据和批处理数据落地</h2><p>Delta Lake 还统一了流数据和批处理数据落地，而不需要最开始提到的 Lambda 架构。极大的简化了系统的复杂性</p><h1 id="delta-lake-快速体验"><a class="markdownIt-Anchor" href="#delta-lake-快速体验"></a> Delta  Lake 快速体验</h1><p>使用 Delta Lake 需要 Spark  2.4.2 及以上版本，如果仅仅想体验一下，可以使用 Docker 版 Spark</p><ul><li>安装 Spark</li></ul><figure class="hljs highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker run -d --name spark-master -h spark-master \<br>-p 8080:8080 \<br>-p 7077:7077 \<br>-p 4040:4040  \<br>-e ENABLE_INIT_DAEMON=<span class="hljs-literal">false</span>  \<br>bde2020/spark-master:2.4.3-hadoop2.7<br><br><br>docker run -d --name spark-worker-1 \<br>-p 8081:8081 \<br>--link spark-master:spark-master \<br>-e ENABLE_INIT_DAEMON=<span class="hljs-literal">false</span> \<br>bde2020/spark-worker:2.4.3-hadoop2.7<br></code></pre></td></tr></table></figure><ul><li>进入 Spark Shell</li></ul><figure class="hljs highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker <span class="hljs-built_in">exec</span> -it spark-master  /spark/bin/spark-shell --packages io.delta:delta-core_2.11:0.3.0<br></code></pre></td></tr></table></figure><ul><li>创建 Delta Table</li></ul><figure class="hljs highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs scala"><span class="hljs-keyword">val</span> data = spark.range(<span class="hljs-number">0</span>, <span class="hljs-number">5</span>)<br>data.write.format(<span class="hljs-string">"delta"</span>).save(<span class="hljs-string">"/tmp/delta-table"</span>)<br></code></pre></td></tr></table></figure><ul><li>其他更多实践请参阅 <a href="https://docs.delta.io/latest/quick-start.html" rel="external nofollow noopener noreferrer" target="_blank">官方文档</a></li></ul><h1 id="实现原理"><a class="markdownIt-Anchor" href="#实现原理"></a> 实现原理</h1><h2 id="delta-lake-的文件结构"><a class="markdownIt-Anchor" href="#delta-lake-的文件结构"></a> Delta Lake 的文件结构</h2><p>Delta Lake 在原有的 Parquet 文件的基础上，增加了 _delta_log 文件夹。_delta_log 文件夹内包含 json 文件和 checkpoint.parquet 文件。<br><img src="/images/delta-lake-folder.jpg" alt="_delta_log 结构"></p><ul><li>这些 json 文件称为 transaction log。其文件名是递增的，数字代表版本。每一个文件代表一个事务，存储了 Schema 信息，对文件的操作等。</li></ul><figure class="hljs highlight"><figcaption><span>transaction log 内容</span></figcaption><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs json">&#123;"commitInfo":&#123;"timestamp":1563414817197,"operation":"WRITE","operationParameters":&#123;"mode":"ErrorIfExists","partitionBy":"[]"&#125;,"isBlindAppend":true&#125;&#125;<br>&#123;"protocol":&#123;"minReaderVersion":1,"minWriterVersion":2&#125;&#125;<br>&#123;"metaData":&#123;"id":"72e1fda6-6860-477a-94bf-924c5935818b","format":&#123;"provider":"parquet","options":&#123;&#125;&#125;,"schemaString":"&#123;\"type\":\"struct\",\"fields\":[&#123;\"name\":\"id\",\"type\":\"long\",\"nullable\":true,<br>\"metadata\":&#123;&#125;&#125;]&#125;","partitionColumns":[],"configuration":&#123;&#125;,"createdTime":1563414816435&#125;&#125;<br>&#123;"add":&#123;"path":"part-00000-90a5fe90-b039-4cce-92be-ff70abc6aeac-c000.snappy.parquet","partitionValues":&#123;&#125;,"size":263,"modificationTime":1563414816000,"dataChange":true&#125;&#125;<br></code></pre></td></tr></table></figure><ul><li>checkpoint.parquet 是检查点，可以加速数据的读取</li></ul><p>在<strong>没有检查点的情况下</strong> ，需要从头开始读取 transaction log，重放每一个 transaction log 的文件操作，才会得到所需要的结果，有了检查点，获取数据的某一个版本时，只需要从距离版本最近的检查点，重放版本和检查点的 transaction log 即可得到指定版本的数据</p><h2 id="实现原子操作"><a class="markdownIt-Anchor" href="#实现原子操作"></a> 实现原子操作</h2><p><strong>写入失败时：</strong> 事务不提交，不形成 transaction log 文件，本次事务写入的文件就不会纳入到当前表中。<br><strong>写入成功时：</strong> 事务提交，transaction log 原子的生成，于是数据变持久性存在于当前 delta table 中</p><h2 id="实现可序列化隔离级别"><a class="markdownIt-Anchor" href="#实现可序列化隔离级别"></a> 实现可序列化隔离级别</h2><p>可序列化隔离的实现基于<a href="https://zh.wikipedia.org/zh-hans/%E4%B9%90%E8%A7%82%E5%B9%B6%E5%8F%91%E6%8E%A7%E5%88%B6" rel="external nofollow noopener noreferrer" target="_blank">乐观并发控制</a><br><strong>并发修改的情况：</strong> 事务开始是读取最新版本的数据，输出数据产生一个新的版本，在事务提交之前，检查是否还有其他提交的 transaction log 与本次提交有冲突，如果有冲突，抛出 ConcurrentModificationException。写入的数据不会提交，因此不会生效<br><strong>并发追加的情况：</strong> 与之类似，不同点在于发现冲突时会检查 Schema 是否变化，如果没有变化，会自动重试，不会抛出异常</p><h1 id="关于-delta-lake-的思考"><a class="markdownIt-Anchor" href="#关于-delta-lake-的思考"></a> 关于 Delta Lake 的思考</h1><p>像 Delta Lake、 Netflix 的 iceberg 这种新一代的文件格式的出现，解决了大数据发展中批流存储不统一、不支持事务等等痛点。</p><p>Spark 诞生之初，就在计算模型上实现了批处理和流处理的统一，现在 Delta Lake 的出现，在存储层也将实现统一。<br>批流处理的大一统后不但意味着可以消除像 Lambda 架构这种变通的解决方案，而且目前常用的基于批处理的数据加工方式也可也会被流式的数据处理方式所取代。再加上可以支持事务。</p><p>届时，开发者可以把更多精力放在&quot;如何从数据中提取有用的信息&quot;这样一件事情上，更多的关注数据流应该如何变化，而不是关注任务说明时候执行，任务失败了怎么重试等等问题。</p><p>所以像 Lambda 架构、各种数据准确性检验任务、不同系统的数据导入工具、调度执行批处理任务等等，这些在大数据领域习以为常的解决方案会成为历史。</p><p>一方面这当然是好事，开发者可以各司其职，大数据系统也会更加统一<br>但另一方面，技术的升级往往会替代一部分人，而且会让我们之前的经验一文不值。所以需要我们更需要思考如何提升自己，让自己驾驭技术，而不是让技术取代自己，就像很多职业都需要自思考如何被人工智能取代一样。</p>]]></content>
      
      
      <categories>
          
          <category> 大数据 </category>
          
          <category> Spark </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 原创 </tag>
            
            <tag> Spark </tag>
            
            <tag> Delta Lake </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>大数据系统下的 Lambda 架构</title>
      <link href="/posts/1a8c/"/>
      <url>/posts/1a8c/</url>
      
        <content type="html"><![CDATA[<p><em>Lambda 架构</em> 是大数据量下的一种数据处理的架构，它同时使用批处理和流处理的方法处理大量数据。</p><a id="more"></a><h1 id="什么是-lambda-架构"><a class="markdownIt-Anchor" href="#什么是-lambda-架构"></a> 什么是 Lambda 架构</h1><p><em>Lambda 架构</em> 是大数据量下的一种数据处理的架构，它同时使用批处理和流处理的方法处理大量数据。<br><img src="/images/15649951835087.jpg" alt="Lambda 架构"></p><h2 id="lambda-架构分层"><a class="markdownIt-Anchor" href="#lambda-架构分层"></a> Lambda 架构分层</h2><p>标准的 Lambda 架构包含 batch layer(批处理层)、serving layer（服务层）、speed layer(实时层)</p><ul><li>批处理层：包含 master dataset(存储全量数据) 和 batch view (批处理视图)。batch view 是由 master dataset 计算得来</li><li>实时层：由于批处理层数据处理存在延时，如果想获得实时数据，需要实时层的支撑。speed layer 与 batch layer非常相似，它们之间最大的区别是 speed layer 只处理最近的数据，batch layer 则要处理所有的数据。改层一般使用实时计算引擎（如 Spark Streaming、Flink）完成计算</li><li>服务层：对最终结果的查询提供支撑，合并批处理层、实时层结果。 一般使用 NoSQL 数据库存储，如 HBase</li></ul><h1 id="lambda-架构解决的问题"><a class="markdownIt-Anchor" href="#lambda-架构解决的问题"></a> Lambda 架构解决的问题</h1><p>Lambda 架构将两种异构的系统整合，实现了既能分又析历史数据，又能计算实时数据。历史数据保存了所有的明细，可以使用多变的方式分析；实时数据包含最新的信息，可以提供报警、及时分析等能力</p><h1 id="lambda-架构不足"><a class="markdownIt-Anchor" href="#lambda-架构不足"></a> Lambda 架构不足</h1><p>由于实时层和批处理层使用的是不同架构的系统，因此需要对应开发不同的代码，而且需要对同样的数据处理两次：开发者需要熟悉不同的组件、需要维护数据的一致性，都是比较复杂的。<br><a href="/posts/36c8">Delta Lake</a> 的出现，解决历史数据和实时数据需要不同系统处理的问题。</p><h1 id="lambda-架构在-twitter-的实践"><a class="markdownIt-Anchor" href="#lambda-架构在-twitter-的实践"></a> Lambda 架构在 Twitter 的实践 <sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></h1><h2 id="架构图"><a class="markdownIt-Anchor" href="#架构图"></a> 架构图</h2><p><img src="/images/lambda-architecture-2-800.jpg" alt="Lambda 架构图"></p><h2 id="批处理层"><a class="markdownIt-Anchor" href="#批处理层"></a> 批处理层</h2><p>使用 Spark 将全量数据 (master dataset) 批处理为 批处理视图 （batch view），并保存在 NoSQL 数据库 Cassandra 中。使用 Akka 的调度器按一定的时间间隔调度执行</p><figure class="hljs highlight Scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs Scala"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BatchProcessingUnit</span> </span>&#123;<br><br>  <span class="hljs-keyword">val</span> sparkConf = <span class="hljs-keyword">new</span> <span class="hljs-type">SparkConf</span>()<br>    .setAppName(<span class="hljs-string">"Lambda_Batch_Processor"</span>).setMaster(<span class="hljs-string">"local[2]"</span>)<br>    .set(<span class="hljs-string">"spark.cassandra.connection.host"</span>, <span class="hljs-string">"127.0.0.1"</span>)<br>    .set(<span class="hljs-string">"spark.cassandra.auth.username"</span>, <span class="hljs-string">"cassandra"</span>)<br><br>  <span class="hljs-keyword">val</span> sc = <span class="hljs-keyword">new</span> <span class="hljs-type">SparkContext</span>(sparkConf)<br><br>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">start</span></span>: <span class="hljs-type">Unit</span> =&#123;<br>    <span class="hljs-keyword">val</span> rdd = sc.cassandraTable(<span class="hljs-string">"master_dataset"</span>, <span class="hljs-string">"tweets"</span>)<br>    <span class="hljs-keyword">val</span> result = rdd.select(<span class="hljs-string">"userid"</span>,<span class="hljs-string">"createdat"</span>,<span class="hljs-string">"friendscount"</span>).where(<span class="hljs-string">"friendsCount &gt; ?"</span>, <span class="hljs-number">500</span>)<br>    result.saveToCassandra(<span class="hljs-string">"batch_view"</span>,<span class="hljs-string">"friendcountview"</span>,<span class="hljs-type">SomeColumns</span>(<span class="hljs-string">"userid"</span>,<span class="hljs-string">"createdat"</span>,<span class="hljs-string">"friendscount"</span>))<br>    result.foreach(println)<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="实时层"><a class="markdownIt-Anchor" href="#实时层"></a> 实时层</h2><p>实时层使用 Spark Streaming 实时处理 Kafka 消息，同样将计算结果存储在 Cassandra 中。</p><figure class="hljs highlight Scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs Scala"><span class="hljs-class"><span class="hljs-keyword">object</span> <span class="hljs-title">SparkStreamingKafkaConsumer</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">App</span> </span>&#123;<br>  <span class="hljs-keyword">val</span> brokers = <span class="hljs-string">"localhost:9092"</span><br>  <span class="hljs-keyword">val</span> sparkConf = <span class="hljs-keyword">new</span> <span class="hljs-type">SparkConf</span>().setAppName(<span class="hljs-string">"KafkaDirectStreaming"</span>).setMaster(<span class="hljs-string">"local[2]"</span>)<br>    .set(<span class="hljs-string">"spark.cassandra.connection.host"</span>, <span class="hljs-string">"127.0.0.1"</span>)<br>    .set(<span class="hljs-string">"spark.cassandra.auth.username"</span>, <span class="hljs-string">"cassandra"</span>)<br>  <span class="hljs-keyword">val</span> ssc = <span class="hljs-keyword">new</span> <span class="hljs-type">StreamingContext</span>(sparkConf, <span class="hljs-type">Seconds</span>(<span class="hljs-number">10</span>))<br>  ssc.checkpoint(<span class="hljs-string">"checkpointDir"</span>)<br>  <span class="hljs-keyword">val</span> topicsSet = <span class="hljs-type">Set</span>(<span class="hljs-string">"tweets"</span>)<br>  <span class="hljs-keyword">val</span> kafkaParams = <span class="hljs-type">Map</span>[<span class="hljs-type">String</span>, <span class="hljs-type">String</span>](<span class="hljs-string">"metadata.broker.list"</span> -&gt; brokers, <span class="hljs-string">"group.id"</span> -&gt; <span class="hljs-string">"spark_streaming"</span>)<br>  <span class="hljs-keyword">val</span> messages: <span class="hljs-type">InputDStream</span>[(<span class="hljs-type">String</span>, <span class="hljs-type">String</span>)] = <span class="hljs-type">KafkaUtils</span>.createDirectStream[<span class="hljs-type">String</span>, <span class="hljs-type">String</span>, <span class="hljs-type">StringDecoder</span>, <span class="hljs-type">StringDecoder</span>](ssc, kafkaParams, topicsSet)<br>  <span class="hljs-keyword">val</span> tweets: <span class="hljs-type">DStream</span>[<span class="hljs-type">String</span>] = messages.map &#123; <span class="hljs-keyword">case</span> (key, message) =&gt; message &#125;<br>  <span class="hljs-type">ViewHandler</span>.createAllView(ssc.sparkContext, tweets)<br>  ssc.start()<br>  ssc.awaitTermination()<br>&#125;<br></code></pre></td></tr></table></figure><p>下面是 ViewHandler 的处理逻辑，主要是选择 follow 者数量大于 500 的记录</p><figure class="hljs highlight Scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs Scala"><br><span class="hljs-class"><span class="hljs-keyword">object</span> <span class="hljs-title">ViewHandler</span> </span>&#123;<br><br><br>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">createAllView</span></span>(sparkContext: <span class="hljs-type">SparkContext</span>, tweets: <span class="hljs-type">DStream</span>[<span class="hljs-type">String</span>]) = &#123;<br>    createViewForFriendCount(sparkContext, tweets)<br>  &#125;<br><br>  <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">createViewForFriendCount</span></span>(sparkContext: <span class="hljs-type">SparkContext</span>, tweets: <span class="hljs-type">DStream</span>[<span class="hljs-type">String</span>]) = &#123;<br><br>    tweets.foreachRDD &#123; (rdd: <span class="hljs-type">RDD</span>[<span class="hljs-type">String</span>], time: <span class="hljs-type">Time</span>) =&gt;<br>      <span class="hljs-keyword">val</span> spark = <span class="hljs-type">SparkSession</span>.builder.config(rdd.sparkContext.getConf).getOrCreate()<br>      <span class="hljs-keyword">val</span> tweets: <span class="hljs-type">DataFrame</span> = spark.sqlContext.read.json(rdd)<br>      tweets.createOrReplaceTempView(<span class="hljs-string">"tweets"</span>)<br>      <span class="hljs-keyword">val</span> wordCountsDataFrame: <span class="hljs-type">DataFrame</span> = spark.sql(<span class="hljs-string">"SELECT userId,createdAt, friendsCount from tweets Where friendsCount &gt; 500 "</span>)<br>      <span class="hljs-keyword">val</span> res: <span class="hljs-type">DataFrame</span> = wordCountsDataFrame.withColumnRenamed(<span class="hljs-string">"userId"</span>,<span class="hljs-string">"userid"</span>).withColumnRenamed(<span class="hljs-string">"createdAt"</span>,<span class="hljs-string">"createdat"</span>).withColumnRenamed(<span class="hljs-string">"friendsCount"</span>,<span class="hljs-string">"friendscount"</span>)<br>      res.write.mode(<span class="hljs-type">SaveMode</span>.<span class="hljs-type">Append</span>)<br>        .format(<span class="hljs-string">"org.apache.spark.sql.cassandra"</span>)<br>        .options(<span class="hljs-type">Map</span>( <span class="hljs-string">"table"</span> -&gt; <span class="hljs-string">"friendcountview"</span>, <span class="hljs-string">"keyspace"</span> -&gt; <span class="hljs-string">"realtime_view"</span>))<br>        .save()<br>      wordCountsDataFrame.show(<span class="hljs-literal">false</span>)<br>      wordCountsDataFrame.printSchema()<br><br>    &#125;<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure><h2 id="服务层"><a class="markdownIt-Anchor" href="#服务层"></a> 服务层</h2><p>服务层聚合了批处理层和实时层的数据，以满足 Ad hoc 的需要</p><figure class="hljs highlight Scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs Scala"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">findTwitterUsers</span></span>(minute: <span class="hljs-type">Long</span>, second: <span class="hljs-type">Long</span>, tableName: <span class="hljs-type">String</span> = <span class="hljs-string">"tweets"</span>): <span class="hljs-type">Response</span> = &#123;<br>  <span class="hljs-keyword">val</span> batchInterval = <span class="hljs-type">System</span>.currentTimeMillis() - minute * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span><br>  <span class="hljs-keyword">val</span> realTimeInterval = <span class="hljs-type">System</span>.currentTimeMillis() - second * <span class="hljs-number">1000</span><br>  <span class="hljs-keyword">val</span> batchViewResult = cassandraConn.execute(<span class="hljs-string">s"select * from batch_view.friendcountview where createdat &gt;= <span class="hljs-subst">$&#123;batchInterval&#125;</span> allow filtering;"</span>).all().toList<br>  <span class="hljs-keyword">val</span> realTimeViewResult = cassandraConn.execute(<span class="hljs-string">s"select * from realtime_view.friendcountview where createdat &gt;= <span class="hljs-subst">$&#123;realTimeInterval&#125;</span> allow filtering;"</span>).all().toList<br>  <span class="hljs-keyword">val</span> twitterUsers: <span class="hljs-type">ListBuffer</span>[<span class="hljs-type">TwitterUser</span>] = <span class="hljs-type">ListBuffer</span>()<br>  batchViewResult.map &#123; row =&gt;<br>    twitterUsers += <span class="hljs-type">TwitterUser</span>(row.getLong(<span class="hljs-string">"userid"</span>), <span class="hljs-keyword">new</span> <span class="hljs-type">Date</span>(row.getLong(<span class="hljs-string">"createdat"</span>)), row.getLong(<span class="hljs-string">"friendscount"</span>))<br>  &#125;<br>  realTimeViewResult.map &#123; row =&gt;<br>    twitterUsers += <span class="hljs-type">TwitterUser</span>(row.getLong(<span class="hljs-string">"userid"</span>), <span class="hljs-keyword">new</span> <span class="hljs-type">Date</span>(row.getLong(<span class="hljs-string">"createdat"</span>)), row.getLong(<span class="hljs-string">"friendscount"</span>))<br>  &#125;<br>  <span class="hljs-type">Response</span>(twitterUsers.length, twitterUsers.toList)<br>&#125;<br></code></pre></td></tr></table></figure><hr class="footnotes-sep"><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p><a href="https://blog.knoldus.com/twitters-tweets-analysis-using-lambda-architecture/" rel="external nofollow noopener noreferrer" target="_blank">https://blog.knoldus.com/twitters-tweets-analysis-using-lambda-architecture/</a> <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content>
      
      
      <categories>
          
          <category> 大数据 </category>
          
          <category> 架构 </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 原创 </tag>
            
            <tag> 架构 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>基准测试(Benchmark) JMH 实战篇</title>
      <link href="/posts/9cee/"/>
      <url>/posts/9cee/</url>
      
        <content type="html"><![CDATA[<p>上一篇文章介绍了基准测试的基本概念以及 Java性能测试工具 JMH 的简单使用。这一篇文章将通过实例详细介绍 JMH 的使用方法，并验证几个提高程序性能的方式是否正确并给出作者的结论。</p><a id="more"></a><h1 id="字符串拼接基准测试"><a class="markdownIt-Anchor" href="#字符串拼接基准测试"></a> 字符串拼接基准测试</h1><p>Java 中有这样一条优化建议，在循环中使用”+“号拼接字符串会带来很大的性能损失，应该使用StringBuilder。</p><p>这样的建议是否准确呢？我们可以设计基准测试来验证，使用的当然是我们的主角 JMH，下面是代码实现 (源码可以在 GitHub 上查看：<a href="https://github.com/KevinZhangMe/jmh-demo" rel="external nofollow noopener noreferrer" target="_blank">地址</a>)</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.util.concurrent.TimeUnit;<br><br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.Benchmark;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.BenchmarkMode;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.Mode;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.OutputTimeUnit;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.runner.Runner;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.runner.RunnerException;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.runner.options.Options;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.runner.options.OptionsBuilder;<br><br><span class="hljs-comment">/**<br> * 字符串拼接基准测试<br> *<br> * 测试结果：<br> * &lt;pre&gt;<br> * Benchmark                                         Mode  Cnt   Score    Error  Units<br> * StringAppendBenchmarkTenK.stringAddBenchmark      avgt   25  82.590 ± 14.824  ms/op<br> * StringAppendBenchmarkTenK.stringBufferBenchmark   avgt   25   0.127 ±  0.005  ms/op<br> * StringAppendBenchmarkTenK.stringBuilderBenchmark  avgt   25   0.146 ±  0.010  ms/op<br> * &lt;/pre&gt;<br> *<br> * <span class="hljs-doctag">@author</span> KevinZhang &lt;kevin.zhang.me@gmail.com&gt;<br> */</span><br><span class="hljs-meta">@BenchmarkMode</span>(Mode.AverageTime)<br><span class="hljs-meta">@OutputTimeUnit</span>(TimeUnit.MILLISECONDS)<br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StringAppendBenchmarkTenK</span> </span>&#123;<br><br>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> TEN_K = <span class="hljs-number">10000</span>;<br><br>    <span class="hljs-meta">@Benchmark</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">stringAddBenchmark</span><span class="hljs-params">()</span> </span>&#123;<br>        String targetString = <span class="hljs-string">""</span>;<br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; TEN_K; i++) &#123;<br>            targetString += <span class="hljs-string">"hello"</span>;<br>        &#125;<br>        <span class="hljs-keyword">return</span> targetString;<br>    &#125;<br><br>    <span class="hljs-meta">@Benchmark</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">stringBuilderBenchmark</span><span class="hljs-params">()</span> </span>&#123;<br>        StringBuilder sb = <span class="hljs-keyword">new</span> StringBuilder();<br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; TEN_K; i++) &#123;<br>            sb.append(<span class="hljs-string">"hello"</span>);<br>        &#125;<br>        <span class="hljs-keyword">return</span> sb.toString();<br>    &#125;<br><br>    <span class="hljs-meta">@Benchmark</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">stringBufferBenchmark</span><span class="hljs-params">()</span> </span>&#123;<br>        StringBuffer sb = <span class="hljs-keyword">new</span> StringBuffer();<br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; TEN_K; i++) &#123;<br>            sb.append(<span class="hljs-string">"hello"</span>);<br>        &#125;<br>        <span class="hljs-keyword">return</span> sb.toString();<br>    &#125;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> RunnerException </span>&#123;<br>        Options opt = <span class="hljs-keyword">new</span> OptionsBuilder()<br>                .include(StringAppendBenchmarkTenK<span class="hljs-class">.<span class="hljs-keyword">class</span>.<span class="hljs-title">getSimpleName</span>())<br>                .<span class="hljs-title">forks</span>(1)<br>                .<span class="hljs-title">build</span>()</span>;<br><br>        <span class="hljs-keyword">new</span> Runner(opt).run();<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><p>可以看到有几个注解:</p><p>@BenchmarkMode 代表基准测试模式，包含</p><ul><li><code>Throughput</code> 模式 ：吞吐量模式，测试单位时间内可以执行测试方法的次数（默认1秒）</li><li><code>Average Time</code> 模式：平均时间模式，测试平均每次操作需要多长时间，它的值等于 <code>Throughput</code> 模式的倒数</li><li><code>Sample Time</code>模式：时间取样模式，测试单位时间运行，自动取样执行的时间进行测量，其结果包含统计信息，比如 P99 、数据分布情况等等</li><li><code>Single Shot Time</code>模式：测试单次方法运行的时间，没有 预热阶段，适合测试冷启动所需要的时间</li><li><code>All</code>模式：同时使用以上所有模式</li></ul><p>@Benchmark 代表这个方法是一个基准测试用例，作为测试用例的入口，类似JUnit 中 @Test 注解</p><p>@OutputTimeUnit 设置输出数据的单位，此处是毫秒，加上AverageTime 模式，最后的单位是 ms/op：每次操作需要多少毫秒</p><h2 id="测试结果"><a class="markdownIt-Anchor" href="#测试结果"></a> 测试结果</h2><p>下面是在我在本地运行的结果:</p><figure class="hljs highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs undefined">Benchmark                                         Mode  Cnt   Score    Error  Units<br>StringAppendBenchmarkTenK.stringAddBenchmark      avgt   25  82.590 ± 14.824  ms/op<br>StringAppendBenchmarkTenK.stringBufferBenchmark   avgt   25   0.127 ±  0.005  ms/op<br>StringAppendBenchmarkTenK.stringBuilderBenchmark  avgt   25   0.146 ±  0.010  ms/op<br></code></pre></td></tr></table></figure><p>我们可以看到 stringAddBenchmark 代表简单Sting 相加的方式，使用平均时间模式，平均每次执行需要82.590毫秒，误差在 ± 14.824 ms。其他两个的意义与之类似。</p><h2 id="结果分析"><a class="markdownIt-Anchor" href="#结果分析"></a> 结果分析</h2><p>从结果中我们可以看到，</p><ol><li><strong>使用简单的字符串相加比使用 StringBuffer 和 StringBuilder 慢了两个数量级；</strong></li><li><strong>StringBuilder 比 StringBuffer要慢一些；</strong></li></ol><p>结果 1 的原因是：String 在字符串拼接时，每次循环会创建新的 StringBuffer 对象(不是 String 对象)，然后把原来的对象销毁，而 StringBuffer / StringBuilder  在初始化时预留了一定的空间，在调用 append 方法时只有在预留空间不足时才会发生数组拷贝。</p><p>结果 2 的原因是：StringBuffer 是线程安全的，append  方法是加锁的；StringBuilder 是非线程安全的。两个类除了在线程安全上的区别，其他几乎没有任何差别。</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// StringBuilder 的 toString 方法</span><br><span class="hljs-function"><span class="hljs-keyword">public</span> StringBuilder <span class="hljs-title">append</span><span class="hljs-params">(String str)</span> </span>&#123;<br>        <span class="hljs-keyword">super</span>.append(str);<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>;<br> &#125;<br><span class="hljs-comment">// StringBuffer 的 toString 方法</span><br><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">synchronized</span> StringBuffer <span class="hljs-title">append</span><span class="hljs-params">(String str)</span> </span>&#123;<br>        toStringCache = <span class="hljs-keyword">null</span>;<br>        <span class="hljs-keyword">super</span>.append(str);<br>        <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>;<br> &#125;<br></code></pre></td></tr></table></figure><h2 id="展望"><a class="markdownIt-Anchor" href="#展望"></a> 展望</h2><p>分析至此，我们还可以得出一个结论，**尽管在没有多线程竞争的情况下，加锁仍会损失一部分性能。**这个结论可以设计另一个基准测试来验证，读者有兴趣可以自己设计。</p><h1 id="线程安全的-long-类型"><a class="markdownIt-Anchor" href="#线程安全的-long-类型"></a> 线程安全的 Long 类型</h1><p>说起线程安全的 long 类型的实现，不难想到有使用悲观锁锁synchronized 、乐观锁 AtomicLong 这两种实现，有没有性能更高的实现呢？肯定是有的，java 8 中发布的 LongAdder 就是为替代 AtomicLong 而存在的。</p><p>LongAdder 原理，简单来讲就是在多线程竞争激烈的情况下，LongAdder 将维护的值分散到多个段中，来减少CAS的重试，当需要获得结果时，只需要把各个段相加就可以了（类比 ConcurrentHashMap 分段锁的实现）。与 AtomicLong 多线程 CAS 更新单个值相比，理论上性能会有提升。</p><p>下面是我设计的基准测试的用例(源码可以在 GitHub 上查看：<a href="https://github.com/KevinZhangMe/jmh-demo" rel="external nofollow noopener noreferrer" target="_blank">地址</a>)，用来验证线程安全Long类型的不同实现。</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> java.util.concurrent.TimeUnit;<br><span class="hljs-keyword">import</span> java.util.concurrent.atomic.AtomicLong;<br><span class="hljs-keyword">import</span> java.util.concurrent.atomic.LongAdder;<br><br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.Benchmark;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.BenchmarkMode;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.Fork;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.Level;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.Measurement;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.Mode;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.OutputTimeUnit;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.Scope;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.Setup;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.State;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.TearDown;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.Threads;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.Warmup;<br><span class="hljs-keyword">import</span> org.openjdk.jmh.runner.RunnerException;<br><br><span class="hljs-keyword">import</span> com.technologiesinsight.jmh.helper.LunchHelper;<br><br><br><span class="hljs-comment">/**<br> *  synchronized 锁 vs AtomicLong vs LongAdder 基准测试<br> *<br> * &lt;pre&gt;<br> * Benchmark                                  Mode  Cnt    Score    Error  Units<br> * ThreadSafeLong.testAtomicLongIncrement     avgt   25  383.194 ± 23.359  ms/op<br> * ThreadSafeLong.testLongAdderIncrement      avgt   25  108.105 ±  2.911  ms/op<br> * ThreadSafeLong.testPrimitiveLongIncrement  avgt   25  908.964 ± 29.782  ms/op<br> * &lt;/pre&gt;<br> * <span class="hljs-doctag">@author</span> KevinZhang &lt;kevin.zhang.me@gmail.com&gt;<br> */</span><br><span class="hljs-meta">@BenchmarkMode</span>(Mode.AverageTime)<br><span class="hljs-meta">@OutputTimeUnit</span>(TimeUnit.MILLISECONDS)<br><span class="hljs-comment">//@Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS)</span><br><span class="hljs-comment">//@Warmup(iterations = 0, time = 1, timeUnit = TimeUnit.SECONDS)</span><br><span class="hljs-comment">//@Fork(1)</span><br><span class="hljs-meta">@State</span>(Scope.Benchmark)<br><span class="hljs-meta">@SuppressWarnings</span>(<span class="hljs-string">"unused"</span>)<br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ThreadSafeLong</span> </span>&#123;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Integer LOOP = <span class="hljs-number">10000000</span>;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Object lock = <span class="hljs-keyword">new</span> Object();<br><br>    <span class="hljs-keyword">private</span> AtomicLong atomicLong;<br>    <span class="hljs-keyword">private</span> LongAdder longAdder;<br>    <span class="hljs-keyword">private</span> <span class="hljs-keyword">long</span> primitiveLong;<br><br><br>    <span class="hljs-meta">@Setup</span>(Level.Iteration)<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setUp</span><span class="hljs-params">()</span> </span>&#123;<br>        <span class="hljs-keyword">this</span>.atomicLong = <span class="hljs-keyword">new</span> AtomicLong();<br>        <span class="hljs-keyword">this</span>.longAdder = <span class="hljs-keyword">new</span> LongAdder();<br>        <span class="hljs-keyword">this</span>.primitiveLong = <span class="hljs-number">0L</span>;<br>    &#125;<br><br>    <span class="hljs-meta">@Benchmark</span><br>    <span class="hljs-meta">@Threads</span>(<span class="hljs-number">2</span>)<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">long</span> <span class="hljs-title">testPrimitiveLongIncrement</span><span class="hljs-params">()</span> </span>&#123;<br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; LOOP; i++) &#123;<br>            <span class="hljs-keyword">synchronized</span> (lock) &#123;<br>                primitiveLong = primitiveLong + <span class="hljs-number">1</span>;<br>            &#125;<br>        &#125;<br>        <span class="hljs-keyword">return</span> primitiveLong;<br>    &#125;<br><br>    <span class="hljs-meta">@Benchmark</span><br>    <span class="hljs-meta">@Threads</span>(<span class="hljs-number">2</span>)<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">long</span> <span class="hljs-title">testAtomicLongIncrement</span><span class="hljs-params">()</span> </span>&#123;<br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; LOOP; i++) &#123;<br>            atomicLong.incrementAndGet();<br>        &#125;<br>        <span class="hljs-keyword">return</span> atomicLong.get();<br>    &#125;<br><br>    <span class="hljs-meta">@Benchmark</span><br>    <span class="hljs-meta">@Threads</span>(<span class="hljs-number">2</span>)<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">long</span> <span class="hljs-title">testLongAdderIncrement</span><span class="hljs-params">()</span> </span>&#123;<br>        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; LOOP; i++) &#123;<br>            longAdder.increment();<br>        &#125;<br>        <span class="hljs-keyword">return</span> longAdder.longValue();<br>    &#125;<br><br>    <span class="hljs-meta">@TearDown</span>(Level.Iteration)<br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">tearDown</span><span class="hljs-params">()</span> </span>&#123;<br>        <span class="hljs-keyword">long</span> atomicResult = atomicLong.get();<br>        <span class="hljs-keyword">long</span> longAdderResult = longAdder.longValue();<br>        System.out.println(String.format(<span class="hljs-string">"primitiveLongResult is %s,atomicResult is :%s;longAdderResult is %s"</span>, primitiveLong, atomicResult, longAdderResult));<br>    &#125;<br><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> RunnerException </span>&#123;<br>        LunchHelper.lunchBenchmark(ThreadSafeLong<span class="hljs-class">.<span class="hljs-keyword">class</span>)</span>;<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><p>相比于上一个案例，我们发现了几个关于 JMH 的“新面孔”</p><ul><li>@State(Scope.Benchmark) ：有时候测试用例中需要维护一些”状态“（测试用不是一个”纯函数“），”状态“的变化可能会影响测试的结果。所以需要由JMH 管理这些状态，并显示的声明这些状态的声明周期（有效范围）。状态生命周期分为三类，由State的参数指定:<ul><li><code>Thread</code>  每一个线程创建自己的状态对象。因此共享对象不会有线程安全问题。</li><li><code>Group</code> 组内共享状态对象。不同分组可以使用  @Group(“groupName”) 指定</li><li><code>Benchmark</code>  一次迭代运行中所有的线程共享状态对象。</li></ul></li><li>@Setup(Level.Iteration) :用于执行基准测试前执行一些操作，比如初始化等等。参数 Level表示改方法说明时候执行：<ul><li><code>Trial</code> 每次一个进程完整运行一遍测试用例之前执行：包括 warmUp 和  正式执行阶段</li><li><code>Iteration</code> 每次迭代前会执行。一次完整的运行包含多次迭代过程，每次迭代运行一次测试的方法。</li><li><code>Invocation</code>  每次方法调用前会执行。</li></ul></li><li>@TearDown : 类似@Setup ,在测试后执行</li><li>@Thread :同时执行的线程数，用于线程下的基准测试</li><li>注释部分 @Measurement 等：用于测试用例开发阶段，提高执行速度，正式测试前需要注释掉</li><li><code>LunchHelper.lunchBenchmark(ThreadSafeLong.class)</code> JMH 启动助手</li></ul><h2 id="测试结果-2"><a class="markdownIt-Anchor" href="#测试结果-2"></a> 测试结果</h2><figure class="hljs highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs undefined">Benchmark                                  Mode  Cnt    Score    Error  Units<br>ThreadSafeLong.testAtomicLongIncrement     avgt   25  383.194 ± 23.359  ms/op<br>ThreadSafeLong.testLongAdderIncrement      avgt   25  108.105 ±  2.911  ms/op<br>ThreadSafeLong.testPrimitiveLongIncrement  avgt   25  908.964 ± 29.782  ms/op<br></code></pre></td></tr></table></figure><p>通过基准测试结果我们可以看到，testAtomicLongIncrement 代表使用 Atomic 类完成自增，运行 25 次后，所得到每次运行平均时间是383.194 ms，误差在± 23.359ms。其他结果的意义与之类似。</p><h2 id="分析"><a class="markdownIt-Anchor" href="#分析"></a> 分析</h2><p>从结果中可以看到，在两个线程竞争同一个 long 类型的情况下，LongAdder 性能最好名副其实，AtomicLong 次之，使用 synchronized 加锁性能最差。</p><p>在日常开发中，我们可以尝试使用 LongAdder 代替 AtomicInteger 和加锁的方式</p><h2 id="展望-2"><a class="markdownIt-Anchor" href="#展望-2"></a> 展望</h2><p>通过这个测试案例，我们可以分析出 JMH 测试框架本身的性能损耗，欢迎有兴趣的读者留言交流。</p><h1 id="其他测试案例"><a class="markdownIt-Anchor" href="#其他测试案例"></a> 其他测试案例</h1><p><a href="https://github.com/KevinZhangMe/jmh-demo" rel="external nofollow noopener noreferrer" target="_blank">源码</a> 中还有其他一些测试案例，比如</p><ul><li>常见几个Map的测试：位于 com.technologiesinsight.jmh.MapBenchmark</li></ul><h1 id="展望-3"><a class="markdownIt-Anchor" href="#展望-3"></a> 展望</h1><p>很多公司编程对测试环节于不太重视，追求的是功能快速上线。这样的做法短期内可以提高开发速度，但如果从长远来看，单元测试，基准测试等测试，会减少后期维护、功能扩展的成本；对于开发者来说，掌握基本的测试理论和实践是一项基本的能力。</p>]]></content>
      
      
      <categories>
          
          <category> Java </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 原创 </tag>
            
            <tag> JMH </tag>
            
            <tag> 基准测试 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>基准测试 (Benchmark) 入门篇</title>
      <link href="/posts/9ee5/"/>
      <url>/posts/9ee5/</url>
      
        <content type="html"><![CDATA[<p>基准测试对我们来说，一个熟悉又陌生的名字。说它熟悉的原因是它在我们生活中无处不在；说他陌生，是因为它常常以各种名字存在于我们生活中。比如”不服跑个分“,其中的“跑分”指的就是基准测试。类似的还有网速测试、Online Judge 的评测结果等等。本文将介绍部分基准测试的理论、以及 Java 官方提供的微基准测试工具的使用。</p><a id="more"></a><h1 id="什么是基准测试"><a class="markdownIt-Anchor" href="#什么是基准测试"></a> 什么是基准测试</h1><p>那么什么是基准测试呢？</p><p>简单来说，<em>基准测试</em>是为了评估测试目标的性能采取的一系列行动，包括运行一个或一组程序或者执行其他的操作。<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p><p>基准测试按照其测试用例是否接近于真实运行的环境，分为微基准测试（Micro-Benchmarking）、模拟、回放、工业标准(生产环境)测试。<sup class="footnote-ref"><a href="#fn2" id="fnref2">[2]</a></sup></p><p><em>微基准测试</em> 使用人造的测试用例对某类特定的操作做测试。这类特定的操作往往指的是软件的一小部分(比如一个方法)。</p><p>这篇文章主要介绍的是微基准测试。</p><h1 id="基准测试应该注意的问题"><a class="markdownIt-Anchor" href="#基准测试应该注意的问题"></a> 基准测试应该注意的问题</h1><h2 id="基准测试不是随意的测试"><a class="markdownIt-Anchor" href="#基准测试不是随意的测试"></a> 基准测试不是随意的测试</h2><p>基准测试不是仅仅执行一下，记一下结果就可以的。比如常常出现的问题就是忽略缓存对于测试结果的影响。</p><p>好的基准测试应该检查以下几点：</p><ol><li>严格检查实际测试的是什么</li><li>确定理解测试了什么：对测试结果进行分析</li><li>得出有效的结论</li></ol><h2 id="控制变量的个数"><a class="markdownIt-Anchor" href="#控制变量的个数"></a> 控制变量的个数</h2><p>为保证得出正确的结论，控制不同的测试中除了被测试的变量，确保没有其他变量对结果造成影响。这个往往是比较难以达到的，比如对 Java 程序进行微基准测试，需要注意编译器优化(包括编译期优化和运行时优化 JIT)、机器负载、操作系统的缓存等等变量。做到这些需要使用合适的工具加上测试者对这些有一定的了解。</p><h2 id="基准测试的环境和配置"><a class="markdownIt-Anchor" href="#基准测试的环境和配置"></a> 基准测试的环境和配置</h2><p>基准测试之前，需尽力保证所测系统运行在最佳的系统和配置中，系统的性能以及达到真正的极限。比如”不服跑分“的测试中，确保被测手机的电量充足、系统没有开启省电模式等。</p><h1 id="java-中的微基准测试-jmh"><a class="markdownIt-Anchor" href="#java-中的微基准测试-jmh"></a> Java 中的微基准测试 JMH</h1><p>Java 官方提供了一个基准测试工具 <a href="https://openjdk.java.net/projects/code-tools/jmh/" rel="external nofollow noopener noreferrer" target="_blank">JMH</a>(Java Microbenchmark Harness)，我们可以用它测试</p><ol><li>一段代码平均执行多长时间</li><li>对比不同实现的性能</li></ol><h2 id="第一个微基准测试工程"><a class="markdownIt-Anchor" href="#第一个微基准测试工程"></a> 第一个微基准测试工程</h2><p>最为便捷的是使用 Maven 创建项目：</p><figure class="hljs highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs shell">mvn archetype:generate \<br>          -DinteractiveMode=false \<br>          -DarchetypeGroupId=org.openjdk.jmh \<br>          -DarchetypeArtifactId=jmh-java-benchmark-archetype \<br>          -DgroupId=com.technologiesinsight.jmh \<br>          -DartifactId=jmh-demo \<br>          -Dversion=1.0<br></code></pre></td></tr></table></figure><p>构建完成后，会发现一个文件夹 jmh-demo。这是一个标准的 maven 工程，会有一个默认的测试类：MyBenchmark,位于 com.technologiesinsight.jmh 包下，也就是上面命令的 <code>-DgroupId</code> 填写的包名。测试类内容如下：</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">package</span> com.technologiesinsight.jmh;<br><br><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.Benchmark;<br><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyBenchmark</span> </span>&#123;<br><br>    <span class="hljs-meta">@Benchmark</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">testMethod</span><span class="hljs-params">()</span> </span>&#123;<br>        <span class="hljs-comment">// This is a demo/sample template for building your JMH benchmarks. Edit as needed.</span><br>        <span class="hljs-comment">// Put your benchmark code here.</span><br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure><p>其中，我们可以在 testMethod 填写基准测试代码。</p><p>这里以声明一个字符串举例：</p><figure class="hljs highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">import</span> org.openjdk.jmh.annotations.Benchmark;<br><br><span class="hljs-comment">/***<br> *<br> * Benchmark                Mode  Cnt          Score         Error  Units<br> * MyBenchmark.testMethod  thrpt   25  217365928.196 ± 6111510.557  ops/s<br> *<br> */</span><br><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyBenchmark</span> </span>&#123;<br>    <span class="hljs-meta">@Benchmark</span><br>    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">testMethod</span><span class="hljs-params">()</span> </span>&#123;<br>        <span class="hljs-keyword">return</span>  <span class="hljs-string">"hello world"</span>;<br>    &#125;<br><br>&#125;<br></code></pre></td></tr></table></figure><p>接下来就可以运行了，进入项目目录，执行 <code>mvn clean package</code> 打包，执行 <code>java -jar target/benchmarks.jar</code> 运行,控制台会出现类似如下信息：</p><figure class="hljs highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><code class="hljs undefined">// 第一部分开始<br># JMH version: 1.21<br># VM version: JDK 1.8.0_201, Java HotSpot(TM) 64-Bit Server VM, 25.201-b09<br># VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/bin/java<br># VM options: -Dfile.encoding=UTF-8<br># Warmup: 5 iterations, 10 s each<br># Measurement: 5 iterations, 10 s each<br># Timeout: 10 min per iteration<br># Threads: 1 thread, will synchronize iterations<br># Benchmark mode: Throughput, ops/time<br># Benchmark: com.technologiesinsight.jmh.MyBenchmark.testMethod<br><br>// 第二部分开始<br># Run progress: 0.00% complete, ETA 00:08:20<br># Fork: 1 of 5<br># Warmup Iteration   1: 188758589.759 ops/s<br># Warmup Iteration   2: 187065201.979 ops/s<br># Warmup Iteration   3: 206670810.488 ops/s<br># Warmup Iteration   4: 211958028.465 ops/s<br># Warmup Iteration   5: 224040261.103 ops/s<br>Iteration   1: 224379265.753 ops/s<br>Iteration   2: 208679880.245 ops/s<br>Iteration   3: 206755976.374 ops/s<br>Iteration   4: 214591855.201 ops/s<br>Iteration   5: 219524929.562 ops/s<br>// ……此处省略迭代信息<br><br>// 第三部分开始<br>Result &quot;com.technologiesinsight.jmh.MyBenchmark.testMethod&quot;:<br>  217365928.196 ±(99.9%) 6111510.557 ops/s [Average]<br>  (min, avg, max) = (204122743.394, 217365928.196, 233217556.736), stdev = 8158691.742<br>  CI (99.9%): [211254417.640, 223477438.753] (assumes normal distribution)<br><br><br># Run complete. Total time: 00:08:22<br><br>REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on<br>why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial<br>experiments, perform baseline and negative tests that provide experimental control, make sure<br>the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.<br>Do not assume the numbers tell you what you want them to tell.<br><br>Benchmark                Mode  Cnt          Score         Error  Units<br>MyBenchmark.testMethod  thrpt   25  217365928.196 ± 6111510.557  ops/s<br></code></pre></td></tr></table></figure><p>输出信息可以分为三部分，使用 // 标注。第一部分是执行的环境、参数信息。第二分每次迭代的详情,第三部分是基准测试的结果。</p><p>我们着重看最后报告部分：</p><figure class="hljs highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs undefined">Benchmark                Mode  Cnt          Score         Error  Units<br>MyBenchmark.testMethod  thrpt   25  217365928.196 ± 6111510.557  ops/s<br></code></pre></td></tr></table></figure><p>最重要的就是 <em>Score</em> 这一列，代表得分，单位是 <em>ops/s</em> 代表每秒钟可以执行多少次操作(其倒数就是平均运行一次测试用例需要多少时间)。因为赋值操作是十分轻量的操作，所以执行速度非常快，约为4.6纳秒就可以完成（未考虑测试框架本身微小的性能损耗）。</p><h1 id="关于jmh-的运行方式"><a class="markdownIt-Anchor" href="#关于jmh-的运行方式"></a> 关于JMH 的运行方式</h1><p>运行方式有多种，除了上面提到的使用命令行 java -jar 运行，也可以使用 IDEA 插件和 mian 方法运行。</p><ul><li>使用 <a href="https://plugins.jetbrains.com/plugin/7529-jmh-plugin" rel="external nofollow noopener noreferrer" target="_blank">IntelliJ IDEA 插件</a>：不需要写main 方法，也不需要打包，直接右键就可以运行。</li><li>使用 main 方法：不需要插件但需要写额外的 main 方法。 本系列的例子中提供了 helper 类方便的执行。</li></ul><p>下一篇文章将会详细介绍 JMH 的使用方式，以及提供<a href="https://github.com/KevinZhangMe/jmh-demo.git" rel="external nofollow noopener noreferrer" target="_blank">几个基准测试案例</a>，来验证几个提高性能的方法。</p><h1 id="参考资料"><a class="markdownIt-Anchor" href="#参考资料"></a> 参考资料</h1><hr class="footnotes-sep"><section class="footnotes"><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>维基百科中对于基准测试的定义  <a href="https://en.wikipedia.org/wiki/Benchmark_(computing)" rel="external nofollow noopener noreferrer" target="_blank">https://en.wikipedia.org/wiki/Benchmark_(computing)</a> <a href="#fnref1" class="footnote-backref">↩︎</a></p></li><li id="fn2" class="footnote-item"><p>Gregg, Brendan. Systems performance: enterprise and the cloud. Pearson Education, 2013. 中文译为 ”性能之巅“ <a href="#fnref2" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content>
      
      
      <categories>
          
          <category> Java </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 原创 </tag>
            
            <tag> JMH </tag>
            
            <tag> 基准测试 </tag>
            
        </tags>
      
    </entry>
    
    
    
    <entry>
      <title>Arthas-在线调试利器</title>
      <link href="/posts/5115/"/>
      <url>/posts/5115/</url>
      
        <content type="html"><![CDATA[<p>我们在开发中不难出现这样的问题：线上某个功能不可用，登上机器查看日志，发现在报错。但为什么报错，翻了半天日志可能也没找到原因，“日志”到用时方恨少，非常后悔当时没有多打日志。<br>此时，要么选择在线 Debug — 有时候并不那么容易；要么修改代码，增加相关日志打印语句，重新发布部署，而这容易导致问题难以复现。<br><a href="https://github.com/alibaba/arthas" rel="external nofollow noopener noreferrer" target="_blank">Arthas</a> 的出现解决了这样的困境。</p><a id="more"></a><p>Arthas(阿尔萨斯),它是由阿里开源的一款 Java 诊断工具，继承自 BTace 和 Greys,提供了 ：</p><ul><li>系统环境信息、虚拟机信息: (线程信息、内存信息、类加载信息)等的查看、监控。</li><li>运行时查看方法执行详情、方法调用链、方法调用统计信息。</li><li>提供内存中编译类、反编译加载的类、热加载类等功能</li></ul><p>更多详细功能请到<a href="https://alibaba.github.io/arthas/" rel="external nofollow noopener noreferrer" target="_blank">官方网站</a>查看。</p><h1 id="本系列文章的目的"><a class="markdownIt-Anchor" href="#本系列文章的目的"></a> 本系列文章的目的</h1><p>Arthas 中文文档写的十分详尽，此处不再做文档的搬运工。文章主要是对其做一些归类总结<br>在阅读下面的内容之前，请先了解 <a href="https://alibaba.github.io/arthas/commands.html" rel="external nofollow noopener noreferrer" target="_blank">官方命令列表</a></p><h1 id="命令分类"><a class="markdownIt-Anchor" href="#命令分类"></a> 命令分类</h1><p>使用 Arthas的第一步就是，熟练运用相关命令，活用相关命令，在真正出现线上问题，排查问题才能行云流水，一气呵成。下面对命令按照使用场景进行了分类分为：</p><ul><li><p>公共工具：sc、sm、stack</p></li><li><p>线上异常排查：watch、tt</p></li><li><p>动态代码执行: ognl</p></li><li><p>热更新: jad、mc、redefine、classloader、dump</p></li><li><p>性能分析：jvm、trace、thread</p></li><li><p><strong>彩蛋：</strong>：july、thanks</p></li></ul><h2 id="公共工具"><a class="markdownIt-Anchor" href="#公共工具"></a> 公共工具</h2><p>用于查找类、方法、方法调用栈。大多数命令使用前都需要知道类和方法，这些公共的工具提供了快速定位目标的方法(通过通配符或者正则表达式)</p><h2 id="线上异常排查"><a class="markdownIt-Anchor" href="#线上异常排查"></a> 线上异常排查</h2><p>线上排查问题的流程一般是：</p><ol><li><p>查看日志，找到具体报错异常栈和报错位置，也可以使用 sc,sm,stack 等工具找到方法调用入口，再使用 tt 或者 watch -e 查看异常栈和报错位置</p></li><li><p>查看方法调用细节，洞悉方法执行现场。</p><p>有两个选择：使用tt (TimeTunnel 时空隧道)，使用 watch</p><p><strong>tt 命令</strong></p><p>可以保存当前方法调用的详细信息，包括入参、返回值、异常信息（如果有的话），方便快速定位。也可以重放调用（但是不一定准确）</p><p><strong>watch 命令</strong></p><p>观察某个方法的详情，具体信息可以在后面跟表达式指定。例如: <code>watch demo.MathGame primeFactors &quot;{params,returnObj}&quot;</code>  可以查看参数，返回值。</p><p><strong>如何选择：</strong></p><p>tt 可以保存多个方法调用现场，方便后续查看、重放。但是有时候查看、重放不一定准确，原因是tt 只是保存当时环境的对象引用，当对象值改变之后，你查看的时候看到的是最新的值，而不是当时调用的值，这时，需要使用 watch 命令准确的查看方法调用的现场</p></li></ol><h2 id="动态代码执行"><a class="markdownIt-Anchor" href="#动态代码执行"></a> 动态代码执行</h2><p>上一步的异常排查发现出错位置和原因，如果仍然不知道如何修改，需要做一些探索，就需要动态代码执行工具了。Arthas 通过 ognl 表达式查看一些静态字段值，执行一些代码片段等。注意，getstatic 可以替代 ognl 查看类的静态字段的功能，但不建议学习，增加学习成本。</p><h2 id="热更新"><a class="markdownIt-Anchor" href="#热更新"></a> 热更新</h2><p>动态代码执行适合于执行一些表达式类短小精悍代码，对于大段代码就显得力不从心。于是就需要热更新技术了。<br>热更新工具箱包括 jad、mc、redefine、classloader、dump。热更新一段代码一般遵循一些顺序：</p><ol><li>反编译：使用jad 反编译已经加载的字节码，如果觉得反编译效果不好，可以使用 dump 转储字节码文件，然后使用其他工具反编译为代码。</li><li>修改代码：修改想要更新的代码</li><li>编译:可以使用 mc 命令编译更新的代码（需要指定class loader），也可以使用 idea 等第三方工具编译好，上传到服务器</li><li>加载新的类：使用 redfine 命令加载</li></ol><h3 id="redefine-使用限制"><a class="markdownIt-Anchor" href="#redefine-使用限制"></a> redefine 使用<strong>限制：</strong></h3><ul><li>不支持新增属性、方法</li><li>正在运行的函数 redefine 后不会生效，只有方法运行完后下次运行才会生效。</li></ul><h1 id="使用技巧"><a class="markdownIt-Anchor" href="#使用技巧"></a> 使用技巧</h1><h2 id="匹配多个方法"><a class="markdownIt-Anchor" href="#匹配多个方法"></a> 匹配多个方法</h2><p>如果想匹配多个方法，可以使用正则表达式，命令后面添加 -E 开启：</p><figure class="hljs highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">trace -E 'io\.netty\.channel\.nio\.NioEventLoop|io\.netty\.util\.concurrent\.SingleThreadEventExecutor'  'select|processSelectedKeys|runAllTasks' '@Thread@currentThread().getName().contains("IO-HTTP-WORKER-IOPool")&amp;&amp;#cost&gt;500'<br></code></pre></td></tr></table></figure><h2 id="退出-arthas"><a class="markdownIt-Anchor" href="#退出-arthas"></a> 退出 Arthas</h2><p>使用 exit/quit 命令结束当前回话，关闭客户端，但不会关闭服务端。使用 jps 查看，会看到Arthas 还在运行。需要使用 shutdown 命令完全关闭服务端。</p><h1 id="参考文档"><a class="markdownIt-Anchor" href="#参考文档"></a> 参考文档</h1><p>官方文档: <a href="https://alibaba.github.io/arthas/" rel="external nofollow noopener noreferrer" target="_blank">https://alibaba.github.io/arthas/</a></p>]]></content>
      
      
      <categories>
          
          <category> Java </category>
          
      </categories>
      
      
        <tags>
            
            <tag> 原创 </tag>
            
            <tag> Arthas </tag>
            
        </tags>
      
    </entry>
    
    
  
  
    
    
    <entry>
      <title></title>
      <link href="/hexo-admin-ehc-images.json"/>
      <url>/hexo-admin-ehc-images.json</url>
      
        <content type="html"><![CDATA[[{"name":"DASHENG-900x700-1.png","date":1518959551960},{"name":"DASHENG-900x700-2.png","date":1518959599098},{"name":"DASHENG-900x700-3.png","date":1518959609913},{"name":"DASHENG-900x700-4.png","date":1518959618105}]]]></content>
      
    </entry>
    
    
    
    <entry>
      <title>about</title>
      <link href="/about/index.html"/>
      <url>/about/index.html</url>
      
        <content type="html"><![CDATA[<p><strong>科技洞见（<a href="http://technologiesinsight.com">technologiesinsight.com</a>）作者专注于Java、大数据相关领域。曾就职于去哪儿大数据部门、美团数据科学平台部门；目前就职于美团，做大数据相关工作</strong></p>]]></content>
      
    </entry>
    
    
    
    <entry>
      <title>categories</title>
      <link href="/categories/index.html"/>
      <url>/categories/index.html</url>
      
        <content type="html"><![CDATA[]]></content>
      
    </entry>
    
    
    
    <entry>
      <title>tags</title>
      <link href="/tags/index.html"/>
      <url>/tags/index.html</url>
      
        <content type="html"><![CDATA[]]></content>
      
    </entry>
    
    
    
    <entry>
      <title>{{title}}</title>
      <link href="/obsidian/template/blog-template.html"/>
      <url>/obsidian/template/blog-template.html</url>
      
        <content type="html"><![CDATA[<a id="more"></a>]]></content>
      
    </entry>
    
    
  
</search>
