关于索引:得物推荐引擎-DGraph

1 前言随着得物业务规模的一直减少,举荐业务也越来越简单,对举荐零碎也提出了更高的要求。咱们于2022年下半年启动了DGraph的研发,DGraph是一个C++我的项目,指标是打造一个高效易用的举荐引擎。举荐场景的特点是表多、数据更新频繁、单次查问会波及多张表。理解这些特点,对于举荐引擎的设计十分重要。通过浏览本文,心愿能对大家理解举荐引擎有肯定帮忙。为什么叫DGraph?因为举荐场景次要是用x2i(KVV)表举荐为主,而x2i数据是图(Graph)的边,所以咱们给得物的举荐引擎取名DGraph。 2 注释2.1 整体架构DGraph能够划分为索引层&服务层。索引层实现了索引的增删改查。服务层则蕴含Graph算子框架、对外服务、Query解析、输入编码、排序框架等偏业务的模块。 图1 2.2 索引框架在DGraph外面参考图1,索引的治理被形象成5个模块:Reader 索引查问、Writer 索引写入、Compaction 增量全量合并、LifeCycle 索引生命周期治理、Schema 索引配置信息。 不同类型的索引只须要实现下面的5个类即可,不同类型的索引只须要关注索引自身的实现形式,而不须要关怀索引的治理问题,通过这种模式,索引治理模块实现了索引的形象治理,如果业务须要,能够疾速在DGraph面退出一种新的索引。 DGraph数据的治理都是按表(table)进行的(图2),简单的索引会应用到DGraph的内存分配器D-Allocator,比方KVV/KV的增量局部 & 倒排索引 & 向量索引等。在DGraph所有数据更新都是DUMP(耗时)->索引构建(耗时)->引擎更新(图3),索引平台会依据DGraph引擎的内存状况主动抉择在线更新还是分批重启更新。这种形式让DGraph引擎的索引更新速度&服务的稳定性失去了很大的晋升。 图2图3 2.3 索引数据一致性 相比订单、交易等对于数据一致性要求十分严格的场景。在搜推场景,数据不须要严格的一致性,只须要最终一致性。若一个集群有N个引擎,通过增量向集群写入一条数据,每个引擎是独立更新这条数据的,因为是独立的,所以有些机器会更新快一点,有些机器会更新慢一点,这个时间尺度在毫秒级左近,实践上在某一时刻,不同引擎上的数据是不统一的,但这对业务影响不大,因为最终这些数据会保持一致。 最终一致性这个个性十分重要,因为实现严格的一致性很简单,2PC&3PC等操作在分布式场景下,代价很高。所以事件就变得简略了很多,引擎的读写模型只须要满足最终一致性即可。这能够让咱们的零碎,更偏差于提供更高的读性能。这个前提也是DGraph目前很多设计的根因。 读写模型 举荐场景须要反对在线服务更新数据,因而引擎有读也有写,所以它也存在读写问题。另外引擎还须要对索引的空间进行治理,相似于JAVA零碎外面JVM的内存管理工作,不过引擎做的简略很多。读写问题常见的解决方案是数据加锁。数据库和大部分业务代码外面都能够这么做,这些场景加锁是解决读写问题最靠谱的抉择。然而在举荐引擎外面,对于读取的性能要求十分高,外围数据的拜访如果引入锁,会让引擎的查问性能受到很大的限度。 举荐引擎是一个读多写少的场景,因而咱们在技术路线上抉择的是无锁数据结构RCU。RCU在很多软件系统外面有利用,比方Linux 内核外面的kfifo。大部分RCU的实现都是基于硬件提供的CAS机制,反对无锁下的单写单读、单写多读、多写单读等。DGraph抉择的是单写多读+提早开释类型的无锁机制。效率上比基于CAS机制的RCU构造好一点,因为CAS尽管无锁,然而CAS会锁CPU缓存总线,这在肯定水平上会影响CPU的吞吐率。 如果简略形容DGraph的索引构造,能够了解为实现了RcuDoc(正排)、RcuRoaringBitMap(倒排)、RcuList、RcuArray、RcuList、RcuHashMap等。用举荐场景可推池来举一个例子,可推池表的存储构造能够形象成RcuHashMap<Key, RcuDoc> table。这里用RcuList来举例子,能够用来了解DGraph的RCU机制。其中MEMORY_BARRIER是为了禁止编译器对代码重排,避免乱序执行。 图4 图5 图5是删除的例子,简略讲一下,在RcuList外面,删除一个元素的时候,比方Node19,因为删除期间可能有其余线程在拜访数据,所以对List的操作和惯例的操作有些不同,首先将Node11的Next节点指向Node29,保障前面进来的线程不会拜访Node19,而后把Node19的Next指向Null,因为这个时候可能还有线程在拜访Node19,因而咱们不能立刻把Node19删除,而是把Node19放入删除队列,提早15秒之后再删除,另外删除的动作不是被动的,而是由下一个须要申请内存的操作触发,因而删除是延时且Lazy的。 数据长久化 在DGraph外面咱们构建了一个内存分配器D-Allocator(每个索引只能申请一个/可选),用于存储增量或者倒排索引等简单数据结构。采纳了相似TcMalloc按大小分类的管理模式。D-Allocator利用Linux零碎的mmap办法每次从固定的空间申请128M ~ 1GB大小,而后再按块划分&组织。由零碎的文件同步机制保证数据的长久化。目前64位x86 CPU理论寻址空间只有48位,而在Linux下无效的地址区间是 0x00000000 00000000 ~ 0x00007FFF FFFFFFFF 和 0xFFFF8000 00000000 ~ 0xFFFFFFFF FFFFFFFF 两个地址区间。而每个地址区间都有128TB的地址空间能够应用,所以总共是256TB的可用空间。在Linux下,堆的增长方向是从下往上,栈的增长方向是从上往下,为了尽可能保证系统运行的安全性,咱们把0x0000 1000 0000 0000 到 0x0000 6fff ffff ffff调配给索引空间,一共96TB,每个内存分配器能够最大应用100GB空间。为了方便管理,咱们引入了表keyID,用于固定地址寻址,表地址 = 0x0000 1000 0000 0000 + keyId * 100GB, 引擎治理平台会对立治理每个集群的keyId,偶数位调配给表,奇数位保留作为表切换时应用。keyId 0 - 600 调配给集群独享表,keyId 600-960调配给全局表。因而单个集群能够最多加载300个独享表+最多180共享表(备注:不是所有表都须要D-Allocator,目前没有增量的KVV/KV表不受这个规定限度)。 ...

August 29, 2023 · 1 min · jiezi

关于索引:技术分享-OceanBase-使用全局索引的必要性

作者:杨涛涛 资深数据库专家,专研 MySQL 十余年。善于 MySQL、PostgreSQL、MongoDB 等开源数据库相干的备份复原、SQL 调优、监控运维、高可用架构设计等。目前任职于爱可生,为各大运营商及银行金融企业提供 MySQL 相干技术支持、MySQL 相干课程培训等工作。 本文起源:原创投稿 *爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。 OceanBase 从索引和主表的关系来讲,有两种索引:部分索引和全局索引。 部分索引等价于咱们通常说的本地索引,与主表的数据结构放弃一对一的关系。部分索引没有独自分区的概念,一般来讲,主表的分区形式决定部分索引的分区形式,也就是说假如主表有10个分区,那么对于每个分区来讲,都有一个对应的部分索引。 全局索引区别于部分索引,与主表数据结构放弃一对多、多对多的关系,全局索引次要利用于分区表。对于分区表来讲,一个非分区全局索引对应主表的多个分区;一个分区全局索引也对应主表的多个分区,同时主表每个分区也对应多个全局索引的索引分区。 引入全局索引的指标就是补救部分索引在数据过滤上的一些有余,比方防止分区表的全分区扫描,把过滤条件下压到匹配的表分区中。 针对查问过滤条件来讲,部分索引和全局索引的简略应用场景总结如下:1. 带分区键的查问,适宜用部分索引。这也是分区表设计的初衷,以过滤条件来反推分区表的设计。比方语句:select * from p1 where id = 9; id 为分区键,能够间接定位到具体的表分区partitions(p9),仅需扫描一行记录。 <mysql:5.6.25:ytt>explain select * from p1 where id = 9\G*************************** 1. row ***************************Query Plan: ==================================|ID|OPERATOR |NAME|EST. ROWS|COST|----------------------------------|0 |TABLE GET|p1 |1 |46 |==================================Outputs & filters: -------------------------------------0 - output([p1.id], [p1.r1], [p1.r2]), filter(nil), access([p1.id], [p1.r1], [p1.r2]), partitions(p9)1 row in set (0.005 sec)2. 不带分区键的查问有两个思考方向,次要在于是否克服全局索引的毛病:全局索引势必会带来查问的分布式执行!(1)表的并发写不大,能够思考用全局索引。 (2)表的并发写很大,用全局索引与否就有待商讨, 能够依据以后的业务模型做个压力测试,取一个折中点。 ...

April 18, 2023 · 1 min · jiezi

关于索引:ElasticSearch必知必会Reindex重建索引

作者: 京东物流 康睿 1.重建索引需要背景1.1 集群版本升级ES版本兼容性同一大版本范畴内降级,索引读写兼容不同大版本升级,索引读写不兼容,须要重建索引 1.2 集群迁徙集群索引迁徙集群迁徙,索引服务不停机,数据提前迁徙1.3 索引分片数量调整分片数量变更原有分片数量太少,重建变多原有分片数量太多,重建变少ES索引分片,一旦创立,原索引是不能批改分片数量的 1.4 索引文档构造变更文档构造变更字段类型变更,已有索引字段类型是不能够批改的字段属性变更,历史数据的字段属性是不会刷新的文档对象构造变更 2.罕用重建索引形式2.1 Reindex初识索引重建阐明重建是创立新索引,原有索引保留原有索引_source必须开启,否则找不到原始数据索引重建倡议,严格业务场景,指标索引mapping构造倡议先创立好,不应用es动静揣测字段类型的形式。POST_reindex{  "source": {    "index": "原始索引" },  "dest": {    "index": "新指标索引" }}2.2 Url参数解读URL参数refresh,指标索引是否立刻刷新waif\_for\_active_shards, 重建索引分片响应设置Scroll, 快照查问工夫slicing, 重建并行任务切片Max_docs , 单次最大数据量,条数requests\_per\_second . 单次执行的重建文档数据量2.3 Request body参数解读申请参数confilicts, 索引数据抵触如何解决,间接笼罩还是中断source: 原索引配置信息dest,新索引配置信息script,脚本解决,批改原索引信息后再写入新索引 2.4 Response 参数解读响应参数胜利数更新数新增数失败数 2.5 重建索引工作管控工作管控必要性重建索引是一个异步工作,由ES后盾过程实现调度执行,集群可能有并行其余异步工作,有时须要中断进行或查看进度工作管控API_tasks,服务端API 3.高级索引重建形式3.1 单秒数据量阈值管制单秒数据量管制requests\_per\_second默认1000,设置-1则不限度生产重建时,倡议管制范畴500-1000左右管制重建速度,避免集群霎时IO过大 3.2人工切片数据切片利用人为指定切片数量,并行任务用于升高索引redinx速度POST_reindex{  "source": {    "index": "my-index-000001",    "slice": {      "id": 0,  // 执行下标,从0开始,即0切第一批数据做迁徙,1切第二批数据做迁徙      "max": 2  // 切分分片数   } },  "dest": {    "index": "my-new-index-000001" }} ...

March 14, 2023 · 1 min · jiezi

关于索引:百万并发场景中倒排索引与位图计算的实践

作者:京东物流 郎元辉背景Promise时效控单零碎作为时效域的控制系统,在用户下单前、下单后等多个节点均提供服务,是用户下单黄金链路上的重要节点;控单零碎次要逻辑是针对用户申请从规定库中找出符合条件的最优规定,并将该规定的时效管制后果返回客户端,比方因为长期疫情等起因针对仓、配、商家、客户四级地址等不同维度进行精密粒度的时效管制。 该零碎也是Promise侧并发量最大的零碎,双11顶峰集群流量TPS在百万级别,对系统的性能要求十分高,SLA要求在5ms以内,因而对海量申请在规定库(几十万)中如何疾速正确匹配规定是该零碎的技术挑战点。 奢侈的解决方案依照奢侈的思维,在工程建设上,通过异步形式将规定库逐行缓存到Redis,Key为规定条件,Value为规定对应后果;当用户申请过去时,对申请Request(a,b,c,d..)中的参数做全组合,依据全组合出的Key尝试找出所有可能命中的规定,再从中筛选出最优的规定。如下图所示 该计划面临的问题是全组合的工夫复杂度是2n,n≈12;算法的工夫复杂度高且算法稳定性差**,最差状况一次申请须要4096次计算和读取操作。当然在工程上咱们能够应用本地缓存做一些优化,然而无奈解决最基本的性能问题。架构简图如下所示: ![]() 新的解决方案下面计划是从行的角度对待匹配定位的,可能命中的行的每一列必然也是符合条件的,这外面存在某种隐约的内在联系。是否反过来思考这个问题,为此咱们尝试进行新的计划,当然架构简图仍然如上图所示,外围优化的是命中算法。 新的计划整体采纳列的倒排索引和倒排索引位运算的形式,使得计算复杂度由原来的2n降至n**,且算法稳定性有十分好的保障。其中列的倒排索引是对每列的值和所散布的行ID(即Posting List)建设KV关系,倒排索引位运算是对符合条件的列倒排索引进行列间的位运算,即通过联结查问以便疾速找到符合条件的规定行。 算法具体设计1.预计算生成列的倒排索引和位图通过对每列的值进行分组合并生成Posting List,建设列值和Posting List的KV关系。以下图为例,列A可生成的倒排索引为:301={1},201={2,3,4,5}等,须要阐明的一点,空值也是一种候选项,也须要生成KV关系,如nil={7}。 2.生成列的倒排索引对应位图将步骤1的倒排索引转成成位图,不便后续的位图计算,转换规则为行ID对应位图的下标位(步骤1、2能够合并操作)。 3.依据用户申请查找列位图,通过位图计算生成候选规定集将用户申请中的入参作为Key,查找符合条件的位图,对每一列进行列内和空值做||运算,最初列间位图做&运算,失去的后果是候选规定集,如下图所示: ![]() 4.从候选规定库中,依据业务优先级排序,查找最优的规定以候选规定为基点,依照业务优先级排序,进行逐级位运算&,当遍历完或位运算为0时,找到最初不为空的即为最优规定,该过程是从候选规定库逐步放大最优范畴的过程。须要阐明某列当用户申请位图不存在时,须要应用对应的空位图进行参加,以B列为例,入参B_1102不存在,须要应用B_nil参加&。 复杂度剖析通过下面的例子咱们能够看到,在工夫复杂度方面查找候选规定集时,进行一轮||运算,一轮&运算;在查找最优规定时进行一轮&运算,所以整体复杂度是3n≈n。 在空间复杂度方面,相比原来的行式存储,倒排索引的存储形式,每列都须要存储行ID,相当于多了 (n-1)*Posting List存储空间,当然这是粗略计算,因为实际上行ID的存储最终转换为位图存储,在空间上有十分大的压缩空间。 工程问题-压缩位图如果倒排索引位图十分稠密,零碎会存在十分大的空间节约。咱们举一个极其case,若千万规定库中命中的行ID是第1000万位,依照传统形式BitSet进行存储,须要耗费1.2MB空间,在内存中占用存在重大节约,有没有压缩优化计划,在RoaringBitMap压缩位图计划中咱们找到,雷同场景在压缩位图形式下仅占144bytes;即便在1000万的位图空间,咱们随机存储1万个值,两者比也是在31K vs 2MB,近100倍的差距,总的来说RoaringBitMap压缩率十分大。 RoaringBitMap实质上是将大块的bitmap拆分成各个小块,其中每个小块在须要存储数据的时候才会存在,所以当进行交加或并集运算的时候,RoaringBitMap只须要去计算存在的块而不须要像bitmap那样对整个大块进行计算,既做到了压缩的存储又做到计算性能的晋升。 以下图821697800为例,对应的16进制数为30FA1D08, 其中高16 位为30FA,低16位为1D08。先用二分查找从一级索引(即Container Array)中找到数值为 30FA 的容器,该容器是一个Bitmap容器,而后在该容器查找低16位的数值1D08,即十进制下7432,在Bitmap中找到相应的地位,将其置为1即可。 实用场景剖析回顾下面的设计方案咱们能够看到,这种形式仅实用于PostingList简略如行ID的模式,如果是简单对象就不适宜用位图来存储。另外仅实用于等值查问,不适用于like、in的范畴查问,为什么有这种局限性?因为这种形式依赖于搜寻条件的空间,在计划中咱们将值的条件作为搜寻的Key,值的条件空间心愿尽可能是一个无限的、不便穷举的、小的空间。而范畴查问导致这个空间变成难以穷举、近乎有限扩张的、所以不实用。 其余优化形式除了应用位运算的形式对倒排索引减速,思考到Posting List的有序性,还有其余的形式比方应用跳表、Hash表等形式,以ES中采纳的跳表为例,进行&运算理论就是在查找两个有序Posting List公共局部,以互相二分查找的模式,将工夫复杂度管制在log(n)的级别。 具体参见工业界如何利用跳表、哈希表、位图进行倒排索引减速?

January 4, 2023 · 1 min · jiezi

关于索引:索引设计高并发场景微服务实战六

索引设计—高并发场景微服务实战(六)你好,我是程序员Alan. 我在上一篇文章《 表结构设计—高并发场景微服务实战(五)》中,具体的写了如何抉择适合的类型创立一张表,但表结构设计只是设计数据库最后的环节之一,咱们还短少数据库设计中最为重要的一个环节——索引设计,只有正确设计索引,业务能力达到上线的初步规范。 索引如果开展来讲有很多须要关注的中央,例如索引设计、业务利用与调优等等,本篇文章我会重点讲一下索引设计相干常识。 索引是什么?索引是一门排序的艺术,索引是晋升查问速度的一种数据结构。无效的设计并创立索引,会晋升数据库系统的整体性能。索引之所以能晋升查问速度,在于它在插入时对数据进行了排序(不言而喻,它的毛病是影响插入或者更新的性能)。索引是对记录进行排序。 在目前的 MySQL 8.0 版本中,InnoDB 存储引擎反对的索引有 B+ 树索引、全文索引、R 树索引。这里咱们先关注应用最为宽泛的 B+ 树索引。 B+树索引构造B+ 树索引是数据库系统中最为常见的一种索引数据结构,简直所有的关系型数据库都反对它。 那你晓得为什么关系型数据库都热衷反对 B+树索引吗?因为B+数是目前为止排序最有效率的数据结构。 B+树索引的特点是: 基于磁盘的均衡树,但树十分矮,通常为 3~4 层,能寄存千万到上亿的排序数据。树矮意味着拜访效率高,从千万或上亿数据里查问一条数据,只用 3、4 次 I/O。 又因为当初的固态硬盘每秒能执行至多 10000 次 I/O ,所以查问一条数据,哪怕全副在磁盘上,也只须要 0.003 ~ 0.004 秒。另外,因为 B+ 树矮,在做排序时,也只须要比拟 3~4 次就能定位数据须要插入的地位,排序效率十分不错。 优化 B+ 树索引的插入性能B+ 树在插入时就对要对数据进行排序,但排序的开销其实并没有你设想得那么大,因为排序是 CPU 操作(以后一个时钟周期 CPU 能解决上亿指令)。 真正的开销在于 B+ 树索引的保护,保证数据排序,这里存在两种不同数据类型的插入状况。 数据程序(或逆序)插入: B+ 树索引的保护代价十分小,叶子节点都是从左往右进行插入,比拟典型的是自增 ID 的插入、工夫的插入(若在自增 ID 上创立索引,工夫列上创立索引,则 B+ 树插入通常是比拟快的)。数据无序插入: B+ 树为了保护排序,须要对页进行决裂、旋转等开销较大的操作,另外,即使对于固态硬盘,随机写的性能也不如程序写,所以磁盘性能也会收到较大影响。你不可能要求所有插入的数据都是有序的,因为索引的自身就是用于数据的排序,插入数据都曾经是排序的,那么你就不须要 B+ 树索引进行数据查问了。 所以对于 B+ 树索引,在 MySQL 数据库设计中,仅要求主键的索引设计为程序,比方应用自增,或应用排序的 UUID,而不必无序值做主键。 ...

November 1, 2022 · 1 min · jiezi

关于索引:学习型索引在数据库中的应用实践

9月29日,咱们邀请到开务数据库研发工程师邹彤老师与大家一起研读大咖论文,主题为《学习型索引在数据库中的利用实际》。 索引是数据库引擎的重要组成部分,在当下数据井喷式暴发的阶段,如何高效精确地在海量数据中疾速检索某条或某个特定范畴的数据就显得尤为要害。 通用的数据库系统为不同的利用需要与数据类型提供了对立的解决形式,在获得了巨大成功的同时,也暴露出肯定的局限性:因为没有联合具体利用的数据分布与工作负载,零碎往往难以保障性能的最优。 为了解决这一问题,“学习式数据库系统”成为了目前数据库畛域的钻研热点,它利用机器学习技术无效捕捉负载与数据的个性,从而对数据库系统进行优化。 学习式数据库系统当中,呈现了一个对数据库索引构造产生颠覆性影响的畛域—学习型索引构造 Learned Index。 论文重点回顾012018 年 Jeff Dean 等人在 SIGMOD 发表了《The Case for Learned Index Structures》,成为 Learned Index 的开山之作。近年来,SIGMOD 每年都有肯定数目的论文聚焦此畛域,论证学习型索引在各种存储构造中的合理性和可行性,也逐步拓宽了学习型索引的实用场景,反对增删改操作、多维索引、数据歪斜负载等。 索引是实现数据高效拜访的重要途径,有助于疾速失去键的相干信息,如地址、存在与否等。学习型索引应用机器学习中的回归模型建设起键值与数据之间的对应关系,或建设分类模型判断键值是否存在,从而利用数据分布或特色对索引构造进行优化,使索引变得专用。 在 《The Case for Learned Index Structures》论文中,咱们解读了机器学习模型如何与传统的 B 树、布隆过滤器等构造联合,优化索引构造。该文章的根本观点:索引就是模型。 Range Index(以 B-Tree 为代表)能够看做是从给定 key 到一个排序数组 position 的预测过程;Point Index(以 Hash 为代表)能够看做是从给定 key 到一个未排序数组的 position 的预测过程;Existence Index(Bloom Filter)能够看做是预测一个给定 key 是否存在。因而,索引零碎是能够用机器学习模型去替换的。 索引个别是通用构造,对数据分布不做任何假如,也没有利用利用负载中实在数据的模式。形容数据分布的连续函数,能够用来构建更无效的数据结构或算法(机器学习模型)。假如用于 Range Index 的 key 的范畴是 1-100M,那么能够用非凡的函数或者模型(间接把 key 自身当成是 offset)做索引而不用再用 B-Tree。 以 B-Tree 为例,它能够被看做 Regression Tree。B-Tree 的建设过程也是依赖数据的,只不过它不是通过梯度降落失去,而是依赖事后定义的法令。查问时给定一个 key,B-Tree 会索引到蕴含该 key 的对应范畴的叶子节点,在叶子节点内对 key 进行搜寻。 ...

October 10, 2022 · 2 min · jiezi

关于索引:数据库索引总结

索引简介索引是一种数据结构,索引的呈现是为了进步数据查问的效率。查问效率个别能够从等值查问和范畴查问两个方面进行评判。 索引的模型哈希表哈希表是一种以键 - 值(key-value)存储数据的构造,哈希的思路很简略,把值放在数组里,用一个哈希函数把 key 换算成一个确定的地位,而后把 value 放在数组的这个地位。 不同的key 值通过哈希函数的换算,会呈现同一个值的状况。解决这种状况的一种办法是,拉出一个链表。 假如,当初保护着一个身份证信息和姓名的表,须要依据身份证号查找对应的名字,这时对应的哈希索引的示意图如下所示: 哈希表因为能够疾速的找到数据的地位,等值查问效率十分高,但哈希表中的值因为不保障程序,如果要查问某个范畴内的数据,就须要把整个哈希表中的值进行扫描判断。所以哈希表这种数据结构适宜比拟适宜等值查问,不适宜范畴查问的场景。 有序数组有序数组保障了数组中的元素的值是依照肯定顺序存储的,还是下面的例子,假如身份证是不反复的,如下图:如果要查 ID_card_n2 对应的名字,用二分法就能够疾速失去,这个工夫复杂度是 O(log(N))。 同时,这个索引构造反对范畴查问。要查身份证号[ID_card_X, ID_card_Y]区间的 User,能够先用二分法找到 ID_card_X(如果不存在 ID_card_X,就找到大于 ID_card_X 的第一个 User),而后向右遍历,直到查到第一个大于 ID_card_Y 的身份证号,退出循环。 如果仅仅看查问效率,有序数组就是比拟好的数据结构了。然而,在须要更新数据的时候就比拟麻烦了,往两头插入一个记录就必须得移动前面所有的记录,老本太高。所以,有序数组索引只实用于动态存储引擎。 二叉树还是下面依据身份证号查名字的例子,如果咱们用二叉搜寻树来实现的话,示意图如下所示:二叉搜寻树的特点是:父节点左子树所有结点的值小于父节点的值,右子树所有结点的值大于父节点的值。这样如果要查 ID_card_n2 的话,依照图中的搜寻程序就是依照 UserA -> UserC -> UserF -> User2 这个门路失去。这个工夫复杂度是 O(log(N))。 当然为了维持 O(log(N)) 的查问复杂度,你就须要放弃这棵树是均衡二叉树。为了做这个保障,更新的工夫复杂度也是 O(log(N))。 树能够有二叉,也能够有多叉。多叉树就是每个节点有多个儿子,儿子之间的大小保障从左到右递增,如图: 二叉树是搜寻效率最高的,然而实际上大多数的数据库存储却并不应用二叉树。其起因是,索引不止存在内存中,还要写到磁盘上。 如果数据库存储应用二叉树,那么树的高度就比拟大,设想一下一棵 100 万节点的均衡二叉树,树高 20。一次查问可能须要拜访 20 个数据块。在机械硬盘时代,从磁盘随机读一个数据块须要 10 ms 左右的寻址工夫。也就是说,对于一个 100 万行的表,如果应用二叉树来存储,独自拜访一个行可能须要 20 个 10 ms 的工夫。 为了让一个查问尽量少地读磁盘,就必须让查问过程拜访尽量少的数据块。那么,就不应该应用二叉树,而是要应用“N 叉”树来实现缩小树的高度。这里,“N 叉”树中的“N”取决于数据块的大小。 对于雷同个数的数据构建 N 叉树索引,N 叉树中的 N 越大,那树的高度就越小,那 N 叉树中的 N 是不是越大越好呢?到底多大才最合适呢? ...

April 17, 2022 · 1 min · jiezi

关于索引:第32期索引设计索引设计详细规范

通过后面一些对于索引设计的相干介绍与示例,置信大家曾经对索引设计这块有了一些系统的意识,那本篇来做下总结,给出一个索引设计的具体标准。 索引命名标准:单值索引,倡议以 idx_ 为结尾,字母全副小写。 例如:alter table t1 add key idx_r1(r1);组合索引,倡议以 dx_multi_ 结尾,字母全副小写。 例如:alter table t1 add key idx_multi_1(r1,r2,r3) ;惟一索引,倡议以 udx_ 为结尾,字母全副小写;如果是多值惟一索引,则命名形式相似 udx_multi_1 等。 例如:alter table t1 add unique key udx_f1(r1);或者alter table t1 add key udx_multi_1(r1,r2,r3);全文索引,倡议以 ft_ 结尾,字母全副小写,并且倡议默认用 ngram 插件。 例如:alter table t1 add fulltext ft_r1(r1) with parser ngram;前缀索引,倡议以 idx_ 结尾,以 _prefix 结尾。 例如: alter table t1 add key idx_r1_prefix(r1(10));函数索引,倡议以 idx_func_ 结尾,字母全副小写。 例如: alter table t1 add key idx_func_r1((mod(r1,4)));索引列抉择标准:##### 索引列的字段类型: ...

July 21, 2021 · 2 min · jiezi

关于索引:技术分享-在长字符串上创建索引

作者:姚远 MySQL ACE,华为云 MVP ,专一于 Oracle、MySQL 数据库多年,Oracle 10G 和 12C OCM,MySQL 5.6,5.7,8.0 OCP。当初鼎甲科技任技术顾问,为共事和客户提供数据库培训和技术支持服务。 本文起源:原创投稿 *爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。 当在很长的字符串的字段上创立索引时,索引会变得很大而且低效,一个解决办法是 crc32 或 md5 函数对长字符串进行哈希计算,而后在计算的后果上创立索引。在 MySQL 5.7 当前的版本,能够创立一个主动生成的字段,例如能够创立上面一个表: create table website(id int unsigned not null,web varchar(100) not null,webcrc int unsigned generated always as (crc32(web)) not null,primary key (id));向这个表中插入记录: mysql> insert into website(id,web) values(1,"https://www.scutech.com");Query OK, 1 row affected (0.07 sec)mysql> select * from website;+----+-------------------------+-----------+| id | web | webcrc |+----+-------------------------+-----------+| 1 | https://www.scutech.com | 851176738 |+----+-------------------------+-----------+1 row in set (0.00 sec)能够看到字段 webcrc 中主动生成了 web 字段的循环冗余校验值,在这个字段上创立索引,能够失去一个占用空间少,而且高效的索引。 ...

July 12, 2021 · 2 min · jiezi

关于索引:第31期索引设计索引数量探讨

个别提到索引,大家都只关注索引的长处,一个优良的索引确实会让查问申请的效率大幅晋升、亦或是大幅晋升带有索引键进行过滤的写入申请(update、delete)效率。 不可否认,索引在这样的场景下带来微小的性能晋升,然而对于整张表的写申请来讲,则刚好相同,索引的存在会在肯定水平上升高写入申请的效率。为了保护索引数据的准实时性而让优化器基于索引做出更优化的执行打算,数据库会自动更新索引数据分布,比方带来索引页的拆分与合并等。 那索引的存在对写入申请影响到底有多大? 也就是说,索引的数量多少与写申请效率的升高有没有一个对应的关系?这篇我用几个简略的例子看看是否失去一个明确的论断。(简略基于笔记本虚拟机测试,次要是基于 MySQL 单实例。) 我的例子次要针对四类语句:insert、update、delete、load data 。用 mysqlsh 部署一个洁净的实例:(带入端口,近程管理员,report_host 选项) MySQL Py > dba.deploy_sandbox_instance(3500,{"password":"root","allowRootFrom":"%","mysqldOptions":["report_host=debian-ytt1"]})A new MySQL sandbox instance will be created on this host in/home/ytt/mysql-sandboxes/3500...Deploying new MySQL instance...Instance localhost:3500 successfully deployed and started.Use shell.connect('root@localhost:3500') to connect to the instance.这里用来测试索引数量的表有 10 个,别离为 t1 到 t10 ,除了每张表索引数量不一样外,字段定义等都一样:t1 有 1 个索引,t2 有个 2 个,顺次类推,t10 有 10 个索引,字段类型都是整形(索引数量不蕴含主键,只蕴含二级索引)。表 t0 表构造如下: (debian-ytt1:3500)|(ytt)>show create table t0\G*************************** 1. row *************************** Table: t0Create Table: CREATE TABLE `t0` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `r0` int DEFAULT NULL, `r1` int DEFAULT NULL, `r2` int DEFAULT NULL, `r3` int DEFAULT NULL, `r4` int DEFAULT NULL, `r5` int DEFAULT NULL, `r6` int DEFAULT NULL, `r7` int DEFAULT NULL, `r8` int DEFAULT NULL, `r9` int DEFAULT NULL, `r10` int DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci1 row in set (0.00 sec)在 shell 命令行批量建这 10 张表,并且别离加上对应的索引: ...

July 7, 2021 · 5 min · jiezi

关于索引:索引的正确打开姿势

摘要:本文章先形容了罕用的索引,并针对B-tree和Psort两种索引具体介绍,上面给出索引的利与弊。除了索引,还介绍了分区、PCK等其余查问提速的伎俩。最初给出各种索引和调优伎俩的应用场景。本文分享自华为云社区《DWS 索引的正确“关上姿态”》,原文作者:hoholy 。 索引能干什么呢,一言以蔽之:查问减速。常见的索引有上面几种: 1. 罕用索引介绍1.1 B-btree索引B-tree存储构造示意如下: B-tree是均衡树,有序存储索引KEY值和TID;对于索引上的过滤条件,通过KEY疾速找到对应的叶子节点,而后再通过TID找到理论记录;索引中的数据以非递加的顺序存储(页之间以及页内都是这种程序),同级的数据页由双向链表连贯;反对单列索引和复合(多列)索引,多列复合索引实用于多列组合查问,B-tree索引对于查问条件的程序有要求;B-tree索引能够解决等值和范畴查问;索引页面不存储事务信息;在数据库外面举个例子,如何创立B-tree索引: 1.2 Psort索引Psort索引数据结构示意如下图所示: Psort索引自身是个列存表,蕴含索引列和tid,在索引列上部分排序,利用MIN/MAX块过滤减速TID获取;Psort索引自身有可见性,但删除、更新数据不会作用到Psort索引;Psort索引更适宜做范畴过滤,点查问速度较差;批量导入场景下无效,对于单条导入有效;横向比照B-tree、Psort如下: 1.3 非凡索引表达式索引比方对于查问“select * from test1 where lower(col1) = ‘value’;”能够建设在Lower表达式之上的索引“create index on test1(lower(col1));”,后续对于相似在lower(col1)表达式上的过滤条件,就能够间接应用这个索引减速,对于其余表达式该索引不会对查问失效。但须要留神的是:索引表达式的保护代价较为低廉,因为在每一个行被插入或更新时都得为它从新计算相应的表达式。 局部索引比方创立一个局部索引“create index idx2 on test1(ip) where not (ip > ’10.185.178.100’ and ip < ’10.185.178.200’);”,应用该缩影减速的典型查问是这样“select from test1 where ip = ’10.185.178.150’”,然而对于查问“select from test1 where ip = ’10.185.178.50’”就不能应用该索引。局部索引用来缩小索引的大小,排除掉查问不感兴趣的数据,同时能够减速索引的检索效率. 惟一索引(1)只有B-tree索引反对惟一索引; (2)当一个索引被申明为惟一时,索引中不容许多个表行具备雷同的索引值; (3)空值被视为不雷同,一个多列惟一索引将会回绝在所有索引列上具备雷同组合值的表行; (4)对于主键列会主动创立一个惟一索引; (5)唯一性查看会影响索引插入性能; 1.4 索引的利与弊索引的长处如下: 点查问提速显著,间接定位到须要的地位,缩小有效IO;多条件组合查问,过滤大量数据,放大扫描范畴;利用倒排索引减速全文检索;利用等值条件索引查问速度快的劣势,联合nestloop进步多表join效率;提供主键和唯一性束缚,满足业务须要;利用btree索引人造有序的特点,优化查问打算;索引的毛病如下: 索引页面占用额定空间,导致肯定的磁盘收缩;每次数据导入同时须要更新索引,影响导入性能;索引页面没有可见性,存在垃圾数据,须要定期清理;索引扫描性能并不总是比程序扫描性能更好,一旦优化器判断有误,可能导致查问性能反向劣化;索引须要记录XLOG,减少日志量;每个索引至多一个文件,减少备份复原、扩容等操作的代价;鉴于索引的应用是一把双刃剑,创立索引要审慎,只给有须要的列创立,不能过滤大量数据的条件列不要创立索引。除了索引能够优化查问效率,存储层还有没有其余优化伎俩呢?上面给大家再介绍几种DWS查问提速的伎俩。 2. DWS查问提速2.1 分区分区是最罕用的提速伎俩之一,而且成果很好,举荐大家联合场景多多应用。 目前反对的分区是range分区,分区反对merge、split、exchange等操作;在工夫维度或者空间维度等具备肯定数据法则的列上创立分区,分区列上的过滤条件会先做分区剪枝,缩小物理扫描量;相比拟索引,分区间接把原始数据物理划分,一旦分区剪枝失效,会极大的缩小IO;应用分区和应用索引并不抵触,能够给分区创立索引;应用分区的注意事项如下: 分区对于导入的影响是减少内存应用(内存不足时会下盘),但不产生额定的磁盘占用;应用分区肯定要留神分区列的抉择和分区数量的管制,分区过多会导致小文件问题,分区数量倡议最多不超过1600个;分区剪枝适宜范畴查问,对于点查问效率晋升无限;上面举个例子,别离创立同样数据类型的分区表和非分区表,导入雷同的数据640万条,用同样的查问会看到分区剪枝对性能进步了7倍多,筹备数据: 分区和非分区查问耗时比照,其中test1是分区表,test2是非分区表,test1的查问scan耗时6ms,test2的查问scan耗时46ms,差距7倍还多: 2.2 PCK(partial cluster key)PCK的实质就是通过排序晋升查问过滤的效率,创立表时指定PCK列,该列上的数据会部分排序,有序的数据带来更好的数据聚簇性,每个数据块的min/max等稠密索引就能更好的发挥作用,粗过滤掉大量的数据,晋升IO效率,默认状况下420万行数据部分排序。 注意事项如下: 只有列存表反对PCK,部分排序对每次导入的批量数据失效,不会做全排序;PCK更实用于范畴查问,点查场景下配套应用PCK和索引能够达到最佳成果;带PCK导入因为排序的起因会应用更多的内存,影响导入速度,须要衡量导入和查问性能;举个例子,对于查问select * from tab where col > 65,如果不应用PCK,很可能一个CU都无奈过滤掉,但如果应用了PCK,下图所示的5个CU就能过滤掉一半还多,晋升查问性能至多50%: ...

April 28, 2021 · 1 min · jiezi

关于存储:阿里巴巴开源容器镜像加速技术

简介:近日阿里巴巴开源了其云原生容器镜像减速技术,它推出的 overlaybd 镜像格局,相比于传统的分层 tar 包文件格式,实现了基于网络的按需读取,从而使得容器能够疾速启动。 作者 |陈博 起源 | 阿里巴巴云原生公众号 近日阿里巴巴开源了其云原生容器镜像减速技术,它推出的 overlaybd 镜像格局,相比于传统的分层 tar 包文件格式,实现了基于网络的按需读取,从而使得容器能够疾速启动。 该技术计划本来是阿里云外部 DADI 我的项目的一部分, DADI 是 Data Accelerator for Disaggregated Infrastructure 的缩写,旨在为计算存储拆散架构提供各种可能的数据拜访减速技术。镜像减速是 DADI 架构在容器及云原生畛域的一次突破性尝试,该技术自 2019 年投产以来,已在线上部署了大量机器,累计启动容器次数超过 10 亿,反对了阿里巴巴团体及阿里云的多个业务线,极大进步了利用的公布和扩容效率。2020 年,团队在国内顶级会议发表了论文"DADI: Block-Level Image Service for Agile and Elastic Application Deployment. USENIX ATC'20"[1],并随后启动了开源我的项目,打算将技术该奉献给社区,通过建设规范并打造生态,吸引更多的开发者投入到容器及云原生性能优化这个畛域上来。 背景简介随着 Kubernetes 和云原生的大暴发,容器在企业外部的大规模利用曾经越来越宽泛。部署启动快是容器的外围劣势之一,这个启动快是指本地镜像实例化的工夫十分短,即“热启动”工夫短。然而对于“冷启动”,即在本地无镜像的状况下,须要先从 Registry 下载镜像能力创立容器。业务的镜像通过长期保护和更新,无论是镜像层数还是整体大小都会达到一个较大的量级,比方可能达到数百 MB 或者几个 GB。因而生产环境中,容器的冷启动往往耗时数分钟,并且随规模扩大会导致 Registry 因集群内网络拥挤而无奈疾速地下载镜像。 例如,在之前某年的双十一流动中,阿里外部一个利用因为容量有余触发紧急扩容,但因并发量过大,整体扩容耗时较长,这期间对局部用户的应用体验造成了影响。而到了 2019 年,随着 DADI 的部署上线,新镜像格局的容器在“镜像拉取+容器启动”上消耗的总工夫比一般容器缩短了 5 倍,且 p99 长尾工夫更是比后者快了 17 倍。 如何解决存储在远端的镜像数据,这是解决容器冷启动慢这个问题的外围点。历史上业界对这一问题做出的尝试有:应用块存储或者NAS保留容器镜像,实现按需读取;应用基于网络的散发技术(如 p2p),将镜像从多个源头下载、或者提前预热到主机上,避免出现网络单点瓶颈。近年来,针对新镜像格局的探讨也逐步被提上议题,依据 Harter 等人的钻研表明,拉取镜像占用了容器启动工夫的 76%,而只有 6.4% 的工夫用来读取数据。因而,反对 On-demand Read 技术的镜像,曾经成为默认的潮流风向。Google 提出的 stargz 格局,其全称是 Seekable tar.gz,顾名思义,能够有选择地从存档中搜查并提取特定的文件,无需扫描或者解压整个镜像。stargz 旨在进步镜像拉取的性能,其提早拉取技术(lazy-pull)不会拉取整个镜像文件,实现了按需读取。为了进一步提高运行时效率,stargz 又推出了一个 containerd 的 snapshotter 插件,在存储层面对 I/O 做了进一步优化。 ...

April 15, 2021 · 2 min · jiezi

关于负载均衡:扒一扒能加速互联网的QUIC协议

简介:家喻户晓,QUIC(Quick UDP Internet Connection)是谷歌制订的一种互联网传输层协定,它基于UDP传输层协定,同时兼具TCP、TLS、HTTP/2等协定的可靠性与安全性,能够无效缩小连贯与传输提早,更好地应答以后传输层与应用层的挑战。目前阿里云CDN线上提供GQUIC版本服务,曾经有Tbps级别的流量承载,并对客户来带了显著的提早收益。本文将由低向上分层探讨QUIC协定的特点。家喻户晓,QUIC(Quick UDP Internet Connection)是谷歌制订的一种互联网传输层协定,它基于UDP传输层协定,同时兼具TCP、TLS、HTTP/2等协定的可靠性与安全性,能够无效缩小连贯与传输提早,更好地应答以后传输层与应用层的挑战。目前阿里云CDN线上提供GQUIC版本服务,曾经有Tbps级别的流量承载,并对客户来带了显著的提早收益。本文将由低向上分层探讨QUIC协定的特点。 作者:黎叔 QUIC协定是一系列协定的汇合,次要包含:传输协定(Transport)丢包检测与拥塞管制(Recovery)平安传输协定(TLS)HTTP3协定HTTP头部压缩协定(QPACK)负载平衡协定(Load Balance)本文针对QUIC的系列协定进行科普性简略介绍,细节读者依然须要通读协定原文。本文基于quic的探讨均基于quic-34系列版本。 QUIC协定相似快递公司,在收到用户数据后,将数据打包,传输到对端,再进行拆包,将用户数据交给了最终目标用户。QUIC是基于UDP协定,实现了相似TCP的牢靠传输,并在此基础上,联合HTTP3/QPACK,更好地服务互联网上海量的HTTP Request/Response需要。如其名发音,QUIC(quick),其指标就是心愿比基于TCP的HTTP交互有更好的体验。 QUIC/HTTP3的特点:有序传输:用stream的概念,确保数据有序。不同的stream或者packet,不保障有序达到。报文压缩,进步荷载比率:比方QUIC引入了variable-length integer encoding。又比方引入QPACK进行头部压缩牢靠传输:反对丢包检测和重传平安传输:TLS 1.3平安协定分层的协定QUIC是在UDP的根底上,构建相似TCP的牢靠传输协定。HTTP3则在QUIC根底上实现HTTP事务。 网络总是分层探讨的,在此咱们由低向上分层探讨quic协定 UDP层: 在UDP层传输的是UDP报文,此处关注的是UDP报文荷载内容是什么,以及如何高效发送UDP报文Connection层: Connection通过CID来确认惟一连贯,connection对packet进行牢靠传输和平安传输Stream层: Stream在相应的Connection中,通过StreamID进行惟一流确认,stream对stream frame进行传输治理HTTP3层:HTTP3建设在QUIC Stream的根底上,绝对于HTTP1.1和HTTP2.0,HTTP3提供更有效率的HTTP事务传输。HTTP3中通过QPACK协定进行头部压缩UDP层本章节探讨QUIC发包的UDP局部的相干问题。 UDP荷载大小荷载大小受限于3个对象:QUIC协定规定;门路MTU;终端承受能力 1、QUIC不能运行在不反对1200字节的单个UDP传输网络门路上 QUIC有规定initial包大小不得小于1200,如果数据自身有余1200(比方initial ack),那么须要用padding形式至多填充到1200字节 2、QUIC不心愿呈现IP层分片景象本要求意味着udp交给ip层的数据不会大于1个MTU,假如mtu为1500,ipv4场景下,udp的荷载下限为1472字节(1500-20-8),ipv6下,udp荷载下限为1452(1500-40-8)。QUIC倡议应用PMTUD以及DPLPMTUD进行mtu探测。在实战中,咱们倡议设置IPv6的MTU为1280,大于这个值,某些网络会存在丢包景象。 3、终端能承受 transport paraments的max\_udp\_payload\_size(0x03)的是终端承受单个udp包大小的能力,发送端该当听从这一约定。 UDP荷载内容UDP荷载内容即为quic协定中的packet。协定规定,如果不超过荷载大小的限度,那么多个packet能够组成一个udp报文收回去。在quic实现中,如果每个udp报文只蕴含一个quic packet,会更容易呈现乱序问题。 高效发UDP包和tcp不同,quic须要在应用层就实现udp数据组装,且每个udp报文不大于1个mtu,如果不加以优化,比方每个包间接用sendto/sendmsg发送,势必会造成大量的零碎调用,影响吞吐 1、通过sendmmsg接口进行优化,sendmmsg能够将用户态的多个udp quic包通过一次零碎调用发到内核态。内核态对于每个udp quic包独立作为udp包收回去 2、在1.)解决了零碎调用次数问题,开启GSO能够提高一分包提早到发给网卡驱动前一刻,能够进一步提高吞吐,升高CPU耗费 3、在2.)的根底上,当初支流网卡曾经反对硬件GSO offload计划,能够进一步提高吞吐,升高cpu耗费 下面介绍的发送形式,事实上能够了解为udp burst发送形式,这带来了一个问题,拥塞管制须要pacing能力! Connection层在咱们探讨时,可知1个udp报文里传输的其实是一个或多个quic协定定义的packet。那么在Connection这一层面,其实是以packet为单位进行治理的。一个packet到来,终端须要解析出指标ConnectionID(DCID)字段,并将该packet交给找到对应的quic connection。一个packet是由header加payload两局部组成。 connection id不同于tcp的4元组惟一确认一条连贯的形式,QUIC定义了一个和网络路由无关的ConnectionID来确认惟一连贯的。这带来一个益处,能够在四元组发生变化时(比方nat rebinding或者终端网络切换wifi->4G),仍然放弃连贯。当然,尽管连贯状态仍然放弃,但因为门路发生变化,拥塞管制也须要可能及时调整。 packet头部IETF的quic header分为两种类型,long header, short header。其中long header有分为 initial, 0rtt, handshake, retry四种类型。类型的定义能够间接参考rfc文档,此处不再赘述。 quic规定packet number始终为自增的,就算某个packet的内容为重传的frame数据,其packet number也必须自增,这绝对于TCP来说,带来一个长处,可能更加准确的采集到门路的RTT属性。 packet number编解码: packet number是一个0~262 -1的取值范畴,quic为了节约空间,在计算packet number时,引入了unacked的概念,通过截断(只保留无效bit位)的形式,只用了1-4个字节,即能够encode/decode出正确的packet number。rfc文档中有附录具体解说了enc/dec的过程。 packet头在平安传输中是被爱护对象,这也意味着在没有ssl信息的状况下,无奈应用wireshake对packet进行时序剖析。两头网络设备也无奈向TCP那样取得packet number进行乱序重组。 ...

April 15, 2021 · 1 min · jiezi

关于索引:order-by-limit-造成优化器选择索引错误

原创 https://developer.aliyun.com/... MySQL · 捉虫动静 · order by limit 造成优化器抉择索引谬误简介: 问题形容 bug 触发条件如下: 优化器先抉择了 where 条件中字段的索引,该索引过滤性较好; SQL 中必须有 order by limit 从而疏导优化器尝试应用 order by 字段上的索引进行优化,最终因代价问题没有胜利。 复现case 表构造 create table t 问题形容bug 触发条件如下: 优化器先抉择了 where 条件中字段的索引,该索引过滤性较好;SQL 中必须有 order by limit 从而疏导优化器尝试应用 order by 字段上的索引进行优化,最终因代价问题没有胜利。复现case表构造 create table t1( id int auto_increment primary key, a int, b int, c int, key iabc (a, b, c), key ic (c)) engine = innodb; 结构数据 insert into t1 select null,null,null,null;insert into t1 select null,null,null,null from t1;insert into t1 select null,null,null,null from t1;insert into t1 select null,null,null,null from t1;insert into t1 select null,null,null,null from t1;insert into t1 select null,null,null,null from t1;update t1 set a = id / 2, b = id / 4, c = 6 - id / 8; 触发SQL ...

April 4, 2021 · 3 min · jiezi

关于索引:揭秘京东城市时空数据引擎JUST如何助力交通流量预测

2014年跨年夜上海外滩人流隐患事件,使得公共安全问题受到了整体社会的宽泛关注。解决这一问题的很重要一项工作就是:如何实时监控和疾速预测城市中每个中央的人流量。当某个中央的人流量超过给定的值或者有超过给定值的趋势时,相干部门能及时地采取相干措施,例如:疏散人群,交通引流等,这样能力避免喜剧的再次发生。 为防止相似公共安全隐患,解决因人流问题造成的交通、社会治安等问题,搭建城市实时人流监控预测零碎势在必行。 ▲图1 上海外滩的踩踏事件 京东城市作为零碎建设中标单位,对整个零碎的需要进行了初步剖析,发现一个区域的人流量与多种数据相干,如图2所示。 比方:(1)手机基站数据。当一个中央的手机信令数据越多时,阐明四周的人也越多; (2)视频监控数据。从视频的画面中,可能辨认出大概有多少人; (3)交通流量数据。某条路的交通流量直观的反映了某个区域的流入流出人数; (4)出行轨迹数据。轨迹数据可能反映出人的流向; (5)天气数据。人的出行受天气的影响,比方下雨天,人们都很少出门,因而天气数据也有助于人流量的预测; (6)不同的地点所能承载的最大人流量是不同的。比方大型火车站,2万的人其实不会造成公共安全威逼,然而如果一个小区忽然有2万人,那就须要留神了。因而,每个区域的修建类型(咱们称之为趣味点或POI)、修建密度、路线构造等都能够帮忙人流的预警; (7)最初,如果当时理解到诸如举办演唱会等事件信息,也有助于人流量的预测。 ▲图2 业务数据 下面说的这些数据,单从一项数据是无奈实时监测和无效预测某个区域的人流量的,因为一项数据仅仅反映某一方面的信息。必须综合利用尽可能多的数据,能力无效地实现人流量的监控与预警,确保公共安全。 通过剖析论证后,确定的大体思路是:(1)利用上述的多种数据计算出某个时间段每个区域的流入、流出的人流量;(2)采纳AI算法模型对城市中每个区域的人流量进行建模;(3)利用建设好的模型,依据最近一段时间各区域的人流量,疾速预测将来一段时间内每个区域的人流量,并给出潜在的预警。 整体解决思路确定之后,最要害的就是钻研数据并敲定建模办法。但问题是:(1)这么多品种的数据须要如何无效治理?(2)如何从各种类型数据中疾速地提取特色指标,例如人流量?(3)如何不便疾速地构建模型,并对模型进行有效性验证? 如果下面三个问题无奈高效解决,那人流监控及预测根本无法保障实时性。只有疾速监控和预测人流量,能力无效地实施交通管制、人流疏散,避免相似于踩踏事件的公共安全事故产生。 但这个问题要解决并不容易,首先,同类型的不同起源业务数据,数据格式可能不一样,没法对立建表,这样只能为每一份数据独自设计一张表,而且当入库数据量达到1T的时候,MySQL数据库间接解体。即便好不容易能把数据导入到MySQL数据库,往往不足更深刻的MySQL数据库调优教训,一个简略的数据查问过程,耗时费劲。 针对这些窘境,京东城市自研了——时空数据引擎(JUST引擎),通过把带有工夫、空间、地位属性的数据统称为时空数据,并且借助JUST引擎弱小的数据建模能力,将数据归类成6大类时空数据模型,所有的时空数据咱们都能够依照6大类数据模型进行入库治理。这6种数据类型的分类形式为: 一方面,世间万事万物都能够由实体对象以及实体对象之间的关系组成。若实体对象之间不存在关联,咱们称之为点数据;若实体之间存在关联,咱们称之为网数据。 另一方面,依据数据的工夫和空间的动静个性,咱们能够将数据分成4类:时空静态数据、空间动态工夫动态数据、空间动静工夫静态数据、时空动态数据。然而,因为同一物体在同一时刻只能呈现在一个中央,空间动静工夫静态数据不会存在。因而,依据时空动静个性,咱们将时空数据最终分成了3类,即:时空静态数据、空间动态工夫动态数据和时空动态数据。 综上,依据城市数据的时空个性以及实体间的关联性,咱们能够将城市数据划分成(4-1)×2 = 6类,如图4所示。 (1)时空动态点数据:以空间点的模式存在,空间地位和读数都不随工夫变动。上述数据中,趣味点就是这类数据,例如,火车站一旦建好,它的地位、大小、分类等信息将不再随工夫变动; (2)空间动态工夫动静点数据:以空间点的模式存在,其地位信息不随工夫变动,但会连续不断地产生读数。上述数据中,监控视频数据、天气数据就是这类数据; (3)时空动静点数据,以空间点的模式存在,但地位和读数均随工夫变动。下面用到的数据中,事件数据就是典型的时空动静点数据。生存中的打车数据、订单数据也是这类数据; (4)时空动态网数据,以网络的模式存在,地位和读数均不变动。下面用到的数据中,路网数据就属于此类; (5)空间动态工夫动态网数据,是指空间网络上产生的一系列读数。例如交通流量,每条路上每隔一段时间都会产生一条读数; (6)时空动态网数据,以网的模式存在,且空间地位和读数一直变动。下面用到的轨迹数据就是一种非凡的时空动态网数据。 回到人流预测场景,计算某个区域的相对流出人数,就是计算出某个时刻的总人数绝对于上一时刻的总人数的差值。这是典型的时空范畴查问的问题。传统的关系型数据库,例如MySQL、Oracle以及PostGIS,尽管整合了时空数据管理的模块,可能满足小数据量的时空范畴查问。然而一旦数据量很大,零碎就会解体。 针对海量数据,目前采纳的支流办法是分布式非关系型数据库,例如HBase。然而,原生的HBase是一个键值(key-value)数据库,只能依据一维的键值疾速找到记录,没有无效的时空索引(时空数据能够看成是3维的:经度、纬度、工夫),无奈高效实现时空范畴查问等查问剖析。此外,HBase自身没有对时空数据进行优化存储,因而占用的磁盘空间十分大。以轨迹数据为例,传统的存储形式如图5所示,每个GPS点占用一行数据,造成数据条目数与GPS点的数目雷同,导致存储空间开销很大。 ▲图5 传统HBase轨迹数据存储形式 针对这些问题,JUST引擎为HBase创立了多种高效时空索引,将多维的时空信息编码到一维的键当中,可能疾速定位诸如时空范畴查问等查问的数据。以后JUST反对的时空索引如图6所示,别离对应不同的数据查问场景。 这就好比你在图书馆的书架上找书的过程 ,没有创立时空索引的HBase,须要你在书架上一本一本地查找你要的书。而领有时空索引的JUST会通知你,你所须要的书在哪个书架、第几层、第几本中,大大减少你的查找时间。 除此之外,JUST还对6种时空数据类型的每种数据类型设计了最佳的索引存储形式以及数据分析办法。还是以轨迹数据为例,咱们预置了多种开箱即用的轨迹解决办法,包含轨迹异样值过滤、轨迹分段、轨迹地图匹配、轨迹插值等;对于每条分段后的轨迹,咱们将这条轨迹的GPS点存储在同一条数据记录中,并采纳GZip压缩形式,这样可能大大减少数据的条目数和占用空间,如图7所示。通过咱们的试验,采纳JUST的轨迹存储办法与原来传统的非关系型数据库的存储办法相比,磁盘空间放大至1/8。更小的存储空间不仅节约磁盘空间,在无限的网络带宽下还减速了查问效率。好比一扇不大的门,对于瘦子们来说,一次只能一个人穿过,而对于胖子来说,能够容许两个人同时通过。 ▲图7 JUST中轨迹数据存储形式 咱们还提出了更为准确形容轨迹形态的办法。如图8所示。传统的形容轨迹形态的办法是应用一个矩形框,该矩形框很大空间都与轨迹地位无关。因而咱们提出了采纳多个小格子来形容轨迹形态的办法。更准确的轨迹形态形容容许咱们设计出了更好的过滤办法,更进一步的进步了查问效率。 ▲图8 准确形容轨迹形态 正是先进的索引办法和存储办法,使得JUST的计算效率有了微小的晋升。其中,存储和索引效率相较于原生HBase晋升超过7倍,查问效率绝对于其余时空查问框架有了上100倍的晋升,如图9所示。目前相干钻研工作申请了多项国家专利,相干论文也已被国内顶尖会议ICDE 2020接管,受到了国内同行的认可。(TrajMesa: A Distributed NoSQL Storage Engine for Big Trajectory Data.ICDE 2020) ▲图9 性能比照 ...

February 25, 2021 · 1 min · jiezi

关于索引:技术分享-EXPLAIN-执行计划详解2Extra

作者:胡呈清爱可生 DBA 团队成员,善于故障剖析、性能优化,集体博客:https://www.jianshu.com/u/a95...,欢送探讨。 本文起源:原创投稿*爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。 ExtraExtra 是 EXPLAIN 输入中另外一个很重要的列,该列显示 MySQL 在查问过程中的一些详细信息。 Using index应用索引笼罩的状况下,执行打算的 extra 会显示为 "Using index": 查问的字段都蕴含在应用的索引中;where 子句应用的字段也都蕴含在应用的索引中。比方: 有组合索引:idx_a (first_name,last_name,birth_date) mysql> explain select first_name,last_name,birth_date from employees where \first_name='Mayuri' and last_name like 'Alpay' and birth_date > '1968-01-01'\G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: employees partitions: NULL type: rangepossible_keys: idx_a key: idx_a key_len: 127 ref: NULL rows: 1 filtered: 100.00 Extra: Using where; Using indexUsing index condition查问数据时如果应用 index condition down 索引条件下推就会在执行打算的 extra 字段中呈现 "Using index condition"。 ...

February 8, 2021 · 3 min · jiezi

关于索引:Mysql的Innodb引擎索引总结

索引的目标是什么?答:数据库增加索引的目标是为了放慢查问速度。 索引的的数据结构是什么?答:(这里的B是balance)B+树来存储索引,B+树相似于二叉树。 B+树是怎么查找数据的?答:B+树索引并不能找到一个给定值的具体行。B+树索引能找到的只是被查问数据行所在的页。而后数据库通过页读入到内存,再在内存中进行查找,最初失去要查找的数据。 什么是B+树答:B+树是为磁盘或其余直接存取辅助设备设计的一种均衡查找树。在B+树中,所以记录点都依照值的大小程序寄存在同一层的叶子节点上,由各叶子节点指针进行链接。 汇集索引的定义(每张表只能有一个汇集索引)答:汇集索引就是依照每张表的主键结构一颗B+树,同时叶子节点中寄存的即为整张表的行记录,也将汇集索引的叶子节点称为数据页。汇集索引的这个个性决定了索引组织表中数据也是索引的一部分(因为存储是依照KEY-VALUE这样存储)。每个数据页都通过双向链表进行链接。 聚簇索引和非聚簇索引答:将数据存储与索引放到了一块,找到索引也就找到了数据。将数据存储于索引离开构造,索引构造的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当须要拜访数据时(通过索引拜访数据),在内存中间接搜寻索引,而后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的起因 辅助索引(非汇集索引)答:辅助索引,叶子节点并不蕴含行记录的全副数据 联结索引答:就是对表中的多个字段增加索引。联结索引也是一颗B+树,只不过联结索引的叶子节点存储的字段是大于等于2。联结索引的第二个益处是曾经对第二个键值进行了排序解决。 笼罩索引答:辅助索引中就能够失去查问的记录,而不须要查问汇集索引的记录(意思就是不必回表了)。笼罩索引是联结索引的最优状况。 最左前缀准则索引下推

February 2, 2021 · 1 min · jiezi

关于索引:新特性解读-高效获取不连续主键区间

引言明天碰到一个需要:客户有张表,主键自增。因为种种原因,主键值并非间断,两头有空隙。为了使主键间断,反复利用这些空隙,目前是用 MySQL 的非凡语法:INSERT IGNORE。 这种办法非常简单,不过会带来额定的失败重试。比方我上面往表 ytt_t0 插入一条存在的记录,前期须要不停的重试能力保障插入实现。 mysql> insert ignore ytt_t0 (id) values (1);Query OK, 0 rows affected, 1 warning (0.00 sec)mysql> show warnings;+---------+------+----------------------------------------------+| Level | Code | Message |+---------+------+----------------------------------------------+| Warning | 1062 | Duplicate entry '1' for key 'ytt_t0.PRIMARY' |+---------+------+----------------------------------------------+1 row in set (0.00 sec)客户纠结的问题是:那有没有一种从数据库角度来讲疾速找出这些不间断主键值的办法呢? 一、shell 端的实现办法必定是有,不过我自己还是感觉这一块放在非数据库端会比拟好。 比方思考在 Shell 端来实现这种需要,非常简单,效率又十分高。举个例子: 表 ytt_t0 蕴含以下数据: 最大值为 28,须要返回的后果为:5,6,7,8,9,10,11,16,17,18,20,21,22,23,24,25,26 mysql> select id from ytt_t0;+----+| id |+----+| 1 || 2 || 3 || 4 || 12 || 13 || 14 || 15 || 19 || 27 || 28 |+----+11 rows in set (0.00 sec在 Shell 端用几条常用命令就可拿到这些空缺 ID 串: ...

January 12, 2021 · 5 min · jiezi

关于索引:第17期索引设计主键设计

表的主键指的针对一张表中的一列或者多列,其后果必须能标识表中每行记录的唯一性。InnoDB 表是索引组织表,主键既是数据也是索引。 主键的设计准则对空间占用要小上一篇咱们介绍过 InnoDB 主键的存储形式,主键占用空间越小,每个索引页里寄存的键值越多,这样一次性放入内存的数据也就越多。 最好是有肯定的排序属性如 INT32 类型来做主键,数值有严格的排序,那新记录的插入只有往原先数据页前面增加新记录或者在数据页后新增空页来填充记录即可,这样有严格排序的主键写入速度也会十分快。 数据类型为整形数据类型早就曾经讲过,依照前两点的需要,最现实的当然是抉择整数类型,比方 int32 unsigned。数据程序增长,要么是数据库本人生成,要么是业务主动生成。 一、与业务无关的属性做主键1.1 自增字段做主键这是 MySQL 最举荐的形式。个别用 INT32 能够满足大部分场景,单库单表能够最大保留 42 亿行记录;含有自增字段的新增记录会程序增加到以后索引节点的后续地位直到数据页写满为止,再写新页。这样会极大水平的缩小数据页的随机 IO。用自增字段做主键可能须要留神两个问题:第一个问题:MySQL 原生自增键拆分如果随着数据前期增长,有拆库拆表预期,能够思考用 INT64;MySQL 原生反对拆库拆表的自增主键,通过自增步长与起始值来确定。起码要有 2 个 MySQL 节点,每个节点自增步长为 2,假如 server_id 别离为 1,2,那自增起始值也能够是 1,2。假如上面是第 1 个 MySQL 节点,设置好了步长和起始值后,表 tmp 插入三行,每行严格依照设置的形式插入数据。 mysql> set @@auto_increment_increment=2;Query OK, 0 rows affected (0.00 sec)mysql> set @@auto_increment_offset=1;Query OK, 0 rows affected (0.00 sec)mysql> insert into tmp values(null),(null),(null);Query OK, 3 rows affected (0.01 sec)Records: 3 Duplicates: 0 Warnings: 0mysql> select * from tmp;+----+| id |+----+| 1 || 3 || 5 |+----+3 rows in set (0.00 sec)然而这块 MySQL 并不能保障其余的值不抵触,比方插入一条节点 2 的值,也能胜利插入,MySQL 默认对这块没有什么束缚,最好是数据入库前就校验好。 ...

December 2, 2020 · 4 min · jiezi

关于索引:索引该怎么创建

最艰难的事件就是意识本人!集体网站 ,欢送拜访!前言:想要彻底明确索引该怎么创立,以及怎么创立出最正当的索引,首先须要对一些常识要有所理解; 本文将从以下几方面来进行论述: 索引的相干常识(包含索引数据结构等);索引创立的准则/根据;学会查看sql执行打算,以及哪些sql执行时会导致索引生效;索引基本知识:1、索引的数据结构:索引的数据机构是 B+Tree,B+Tree 是一个多路均衡查找树。1.1、至于为什么索引应用此数据机构呢?最次要一个起因就是:应用B+Tree这种数据机构的索引,在进行sql查问时只须要较少的几次磁盘IO 即可,所以会大大晋升查问效率;具体起因可参考:【深刻学习MySQL】MySQL的索引构造为什么应用B+树? 1.2、索引 B+Tree 构造的个性:①、B+Tree 只有叶子节点会存储实在的数据,非叶子节点只会存储索引字段值; ②、B+Tree的叶子节点之间应用 双向链表 链接,所以更加适宜范畴查问和排序; 2、索引的类型:在平时创立的索引中,能够将索引大体分为两类: ①、聚簇索引(主键索引) ②、非聚簇索引(二级索引) 二级索引依据索引中的字段个数能够分为: ①、单字段索引 ②、联结索引 / 复合索引(多个字段组成的索引) 3、不同类型索引在磁盘中的B+Tree的存储构造:3.1、聚簇索引:(主键索引)聚簇索引:当表中创立了主键,默认就会生成主键索引;聚簇索引的B+Tree索引构造中,非叶子节点中存储的是 ID主键值 ,存储在叶子节点中的实在数据是具体的 行记录 ; 结构图如下: 3.2、单字段索引:(二级索引)单字段索引:手动创立的索引,由 一个字段 组成的索引;单字段索引的B+Tree索引构造中,非叶子节点中存储的是 索引字段值,存储在叶子节点中的数据局部是 主键值 。结构图如下: 3.3、联结索引:(二级索引)联结索引:手动创立的索引,由 多个字段 组成的索引;联结索引的B+Tree索引构造中,非叶子节点中存储的是 多个索引字段值,存储在叶子节点中的数据局部是 主键值 。结构图如下: 4、回表:什么是回表?回表 是产生在 二级索引 中的;在应用二级索引查问数据时,如果 select 投影列 中领有 索引字段和主键 之外的字段时,此时就须要 回表;应用在二级索引树中查问到的 主键值 去主键索引树中查问具体的行记录,而后在具体行记录中获得最终的 select 投影列 数据。 4.1、举例说明:数据表:t_user 字 段:id(主键)、name、age、address、sex 索 引:index( name, age ) 测试的这个索引是 联结索引,单字段索引也是一样的原理 ...

August 29, 2020 · 2 min · jiezi

文件索引工具的实现

原文地址:文件索引工具的实现Introduction实现HashTable,并且用HashTable完成一个文件索引工具。 OBJECTIVESOpen and manipulate filesImplement a hashtable as part of your data structure libraryIndex filesGet familiar with command line parametersSUMMARYThis week we're building a hashtable, then using the hashtable to index the files. After you complete your hashtable implementation, you'll write a piece of code that will traverse a file directory indexing the files. Then, you'll read each of those files and index each row of the file in another hashtable. Finally, you'll output the indexed data to a file. ...

September 19, 2019 · 5 min · jiezi

Timestream开发最佳实践

背景Timestream模型是针对时序场景设计的特有模型,可以让用户快速完成业务代码的开发,实现相关业务需求。但是,如果业务系统不仅想实现基础的相关业务功能,还要达到最佳的性能,并且兼顾到未来的扩展性的话,就不是一件特别容易的事情。 本文会以共享汽车管理平台为例,介绍一系列的timestream最佳设计和使用,给业务设计和使用提供一些参考。关于共享汽车管理平台的场景,细节请参考:《基于Tablestore的共享汽车管理平台》。 场景和模型简介 在共享汽车管理平台这个场景中,主要是对车辆的状态轨迹监控、车俩元数据以及订单元数据进行管理。另外,还会对相关的数据进行计算分析并存储相关结果: 车辆状态轨迹:记录了车辆的状态监控,比如车速、位置、续航等数据,另外还需要记录车辆行驶过程中的违章记录,比如:是否超速、是否闯红灯等等;车辆元数据:记录车辆的基本属性信息,便于用户进行车辆检索,比如:车型、车牌、颜色等;订单元数据:订单相关信息记录,包含行程的起止时间、车辆、用户、费用等信息业务主要是对上面三部分数据进行查询和检索,满足业务场景的需求。其中车辆元数据以及状态轨迹数据是典型的时序序列,可以很方便的映射到Timestream模型中。 下图是数据模型的映射: 下面介绍一下模型设计的细节以及设计中需要注意的一些优化点,这些优化点对于业务功能以及性能上都有一定的提升。 业务模型设计在Timestream模型中,主要包含了元数据和数据点两部分数据,分别使用一个元数据表以及若干个数据表进行存储。下面介绍这两类数据在存储设计的关键点。 元数据表设计在共享汽车这个场景下,元数据表主要存储两类数据:车辆的基本信息、车辆的最近状态数据(位置、续航、状态、违章统计等),业务会根据各类信息进行多条件的组合查询符合条件的车辆。 如上图所示,Timestream的元数据表会通过多元索引来提供丰富的数据检索能力。在Timestream模型的元数据中,包含了name、tags、attributes三类数据,其中name、tags默认会提供数据检索能力,attributes则需要在创建Meta表的时候指定需要索引的attributes字段以及相关信息,默认attributes并不支持检索。需要注意的是,目前并不支持动态修改Meta表的索引字段,所以最好能在设计之初能够考虑到当前以及未来的功能需求,下面介绍一下相关信息是如何映射到模型以及相关的设计。 name设计name字段的选取是很关键的,是数据检索性能的一个重要影响因素,不同的name字段设计可能会导致查询延时相差一个数量级。name字段的选取建议满足以下条件: 绝大多数查询场景都会对该字段进行精确查询该字段单个取值下的最大记录数不宜过多,比如说不超过一千万条记录在共享汽车管理平台这个场景下,管理的是各个平台的车辆,而在车辆检索的时候,一定会指定的条件是平台的名字,并且某个平台的车辆其实也不会太多,一般也就百万量级,所以这里可以将平台作为name。 tags设计在Timestream模型中,Name和Tags可以唯一确定某个元数据,在这个场景中,唯一确定某辆车的信息是:平台、车辆ID,其中平台是name,所以,tags中只需要存储ID即可。tags设计需要注意: tags的总长度尽可能的短,只把唯一确定主体的信息放到tag中,其余信息均放到attributes中tag只支持string类型的数据,如果业务字段是数值类型,需要将其转成string进行存储attributes设计attributes是主体的可变属性,也可以用来存储主体的非唯一属性。在这个场景中,车辆的基本信息以及当前状态都是用attributes来进行存储。attributes设计关键点: 创建meta表的时候需要指定有检索需求的attributes以及相关属性,默认attributes是不支持索引的数值型数据尽可能使用int来存储,attribute支持多类型的数据,但在数据检索过程中,int类型的数据检索效率比string类型高的多,建议使用int,索引类型为LONG考虑未来系统的扩展性,可以预留一列作为扩展字段,索引类型为KEYWORD,并且是Array索引创建示例代码: public void createMetaTable() { db.createMetaTable(Arrays.asList( new AttributeIndexSchema("地区", AttributeIndexSchema.Type.KEYWORD), ... // 数值类型索引 new AttributeIndexSchema("座位", AttributeIndexSchema.Type.LONG), ... // 扩展字段,数组类型索引 new AttributeIndexSchema("配置1", AttributeIndexSchema.Type.KEYWORD).isArray() ));数据表设计Timestream可以支持多个数据表的存储,来满足不同的业务场景需求。另外,为了能够利用底层引擎所做的性能优化,我们推荐append的写入方式,即不会对已有数据进行修改,所以在以下场景中,我们建议业务将数据分到不同数据表中进行存储: 数据精度不同,特别是在监控场景下,这个需求更加突出。按数据精度分表便于后续数据的查询,如果查询长周期的数据可以去查询低精度的表,减少查询的数据量,提高查询效率需要对数据进行加工处理,也就是会对数据进行更新,建议将处理之后的数据写到另外一张表中在共享汽车这个场景中,需要对车辆的状态轨迹数据进行流式处理,比如检测是否超速等违章、车辆状态轨迹是否异常等,然后将处理之后的数据写到另外一张表中,提供给业务进行查询。 sdk使用前面介绍了业务模型设计需要注意的地方,对业务功能拓展能力以及性能都有一定的提升。下面介绍一下timestream sdk使用的一些性能优化点。 数据写入元数据元数据写入支持两种方式:put和update。其中put会删除老的记录,并且插入一个全新行;update则是对原有记录的部分attributes进行更新。建议尽量使用Put的方式进行写入。示例代码: public void writeMeta() { TimestreamIdentifier identifier = new TimestreamIdentifier.Builder("*滴") .addTag("ID", carNo) .build(); TimestreamMeta meta = new TimestreamMeta(identifier) .addAttribute("地区", "杭州") .addAttribute("座位", 4) ... .addAttribute("状态", "闲置"); // 插入车辆信息 metaWriter.put(meta);}数据点数据点写入也提供了两种方式:同步和异步。其中异步接口底层是通过TableStoreWriter进行异步写入,其写入吞吐能力更高,对写入延时不是特别敏感的业务建议使用异步方式。示例代码: ...

August 28, 2019 · 1 min · jiezi

Elasticsearch-7x-常用命令

集群信息查看欢迎信息# urlhttp://112.xx.xx.xx:9200/查看集群是否健康# 查看集群健康状态# urlhttp://112.xx.xx.xx:9200/_cluster/health# KibanaGET /_cluster/health查看节点列表# 查看节点列表# urlhttp://112.xx.xx.xx:9200/_cat/nodes?v# KibanaGET /_cat/nodes?v索引查看所有索引# 查看所有索引GET /_cat/indices查看某个索引的 mapping# 查看某个索引的 mappingGET /kibana_sample_data_ecommerce/_mapping查看某个索引的 settings# 查看某个索引的 settingsGET /kibana_sample_data_ecommerce/_settings文档的增删改查(CRUD)Elasticsearch类比MySQL说明Indexreplcae intoIndex在索引不存在时会创建索引,replace into 并不会创建库或表Createinsert into增加Readselect读取Updateupdate更新Deletedelete删除Index(增加 or 更新)指定 IDPOST /my_index/_doc/1{"user":"walker"}系统自动生成 IDPOST /my_index/_doc{"user":"walker"}Create(增加)指定 IDPOST /my_index/_create/2{"user":"walker"}Read(读取)返回索引的所有文档# 返回索引的所有文档GET /kibana_sample_data_ecommerce/_search根据ID查看文档# 根据ID查看文档GET /kibana_sample_data_ecommerce/_doc/xPGYeWwBVtEez7y_Ku1Uterm 查询精确匹配# term 查询精确匹配GET /_search{ "query": { "term": { "currency": "EUR" } }}# 通过 Constant Score 将查询转换成一个 Filtering# 避免算分,并利用缓存,提高性能GET /_search{ "query": { "constant_score": { "filter": { "term": { "currency": "EUR" } } } }}通配符模糊查询# 通配符模糊查询GET /_search{ "query": { "wildcard": { "currency": "*U*" } }}# 通过 Constant Score 将查询转换成一个 Filtering# 避免算分,并利用缓存,提高性能GET /_search{ "query": { "constant_score": { "filter": { "wildcard": { "currency": "*U*" } } } }}Update(更新)指定 ID 更新POST /my_index/_update/1{ "doc": { "user": "walker", "age": 99 }}Delete(删除)指定 ID 删除DELETE /my_index/_doc/1批量操作上面讲的都是对单文档进行操作,多文档批量操作可自行去翻看官网文档:Document APIs ...

August 27, 2019 · 1 min · jiezi

如何将Elasticsearch的快照备份至OSS

前言Elasticsearch 是一个开源的分布式 RESTful 搜索和分析引擎。它可以在近实时条件下,存储,查询和分析海量的数据。它还支持将快照备份至HDFS/S3上面,而阿里云OSS兼容S3的API,本文将介绍如何使用ES的Repository-S3插件将快照备份至OSS。 部署与配置首先,我们需要安装repository-s3,可以参考官方文档:https://www.elastic.co/guide/en/elasticsearch/plugins/7.2/repository-s3.html 启动ES,我们可以从log中看到,ES已经load了这个plugin: [2019-07-15T14:12:09,225][INFO ][o.e.p.PluginsService ] [master] loaded module [aggs-matrix-stats][2019-07-15T14:12:09,225][INFO ][o.e.p.PluginsService ] [master] loaded module [analysis-common][2019-07-15T14:12:09,225][INFO ][o.e.p.PluginsService ] [master] loaded module [ingest-common][2019-07-15T14:12:09,226][INFO ][o.e.p.PluginsService ] [master] loaded module [ingest-geoip][2019-07-15T14:12:09,226][INFO ][o.e.p.PluginsService ] [master] loaded module [ingest-user-agent][2019-07-15T14:12:09,226][INFO ][o.e.p.PluginsService ] [master] loaded module [lang-expression][2019-07-15T14:12:09,226][INFO ][o.e.p.PluginsService ] [master] loaded module [lang-mustache][2019-07-15T14:12:09,227][INFO ][o.e.p.PluginsService ] [master] loaded module [lang-painless][2019-07-15T14:12:09,227][INFO ][o.e.p.PluginsService ] [master] loaded module [mapper-extras][2019-07-15T14:12:09,227][INFO ][o.e.p.PluginsService ] [master] loaded module [parent-join][2019-07-15T14:12:09,227][INFO ][o.e.p.PluginsService ] [master] loaded module [percolator][2019-07-15T14:12:09,227][INFO ][o.e.p.PluginsService ] [master] loaded module [rank-eval][2019-07-15T14:12:09,228][INFO ][o.e.p.PluginsService ] [master] loaded module [reindex][2019-07-15T14:12:09,228][INFO ][o.e.p.PluginsService ] [master] loaded module [repository-url][2019-07-15T14:12:09,228][INFO ][o.e.p.PluginsService ] [master] loaded module [transport-netty4][2019-07-15T14:12:09,228][INFO ][o.e.p.PluginsService ] [master] loaded plugin [repository-s3][2019-07-15T14:12:12,375][INFO ][o.e.d.DiscoveryModule ] [master] using discovery type [zen] and seed hosts providers [settings][2019-07-15T14:12:12,801][INFO ][o.e.n.Node ] [master] initialized[2019-07-15T14:12:12,802][INFO ][o.e.n.Node ] [master] starting ...然后,我们需要将OSS使用的Access Key和Secret Key配置到ES去,分别执行下面的命令: ...

July 16, 2019 · 2 min · jiezi

MySQL中InnoDB和MyISAM的存储引擎区别

MySQL数据库区别于其他数据库的很重要的一个特点就是其插件式的表存储引擎,其基于表,而不是数据库。由于每个存储引擎都有其特点,因此我们可以针对每一张表来挑选最合适的存储引擎。 作为DBA,我们应该深刻的认识存储引擎。今天介绍两种最常见的存储引擎和它们的区别:InnoDB和MyISAM。 InnoDB存储引擎InnoDB存储引擎支持事务,其设计目标主要就是面向OLTP(On Line Transaction Processing 在线事务处理)的应用。特点为行锁设计、支持外键,并支持非锁定读。从5.5.8版本开始,InnoDB成为了MySQL的默认存储引擎。 InnoDB存储引擎采用聚集索引(clustered)的方式来存储数据,因此每个表都是按照主键的顺序进行存放,如果没有指定主键,InnoDB会为每行自动生成一个6字节的ROWID作为主键。 MyISAM存储引擎MyISAM存储引擎不支持事务、表锁设计,支持全文索引,主要面向OLAP(On Line Analytical Processing 联机分析处理)应用,适用于数据仓库等查询频繁的场景。在5.5.8版本之前,MyISAM是MySQL的默认存储引擎。该引擎代表着对海量数据进行查询和分析的需求。它强调性能,因此在查询的执行速度比InnoDB更快。 MyISAM存储引擎还有一个特点是只缓存索引文件,而不缓存数据文件,这点非常独特。 InnoDB和MyISAM的区别事务为了数据库操作的原子性,我们需要事务。保证一组操作要么都成功,要么都失败,比如转账的功能。我们通常将多条SQL语句放在begin和commit之间,组成一个事务。 InnoDB支持,MyISAM不支持。 主键由于InnoDB的聚集索引,其如果没有指定主键,就会自动生成主键。MyISAM支持没有主键的表存在。 外键为了解决复杂逻辑的依赖,我们需要外键。比如高考成绩的录入,必须归属于某位同学,我们就需要高考成绩数据库里有准考证号的外键。 InnoDB支持,MyISAM不支持。 索引为了优化查询的速度,进行排序和匹配查找,我们需要索引。比如所有人的姓名从a-z首字母进行顺序存储,当我们查找zhangsan或者第44位的时候就可以很快的定位到我们想要的位置进行查找。 InnoDB是聚集索引,数据和主键的聚集索引绑定在一起,通过主键索引效率很高。如果通过其他列的辅助索引来进行查找,需要先查找到聚集索引,再查询到所有数据,需要两次查询。 MyISAM是非聚集索引,数据文件是分离的,索引保存的是数据的指针。 从InnoDB 1.2.x版本,MySQL5.6版本后,两者都支持全文索引。 auto_increment对于自增数的字段,InnoDB要求必须有只有该字段的索引。但MyISAM可以将该字段与其他字段组成联合索引。 表行数很常见的需求是看表中有多少条数据,此时我们需要select count(*) from table_name。 InnoDB不保存表行数,需要进行全表扫描。MyISAM用一个变量保存,直接读取该值,更快。当时当带有where查询的时候,两者一样。 存储数据库的文件都是需要在磁盘中进行存储,当应用需要时再读取到内存中。一般包含数据文件、索引文件。 InnoDB分为: .frm表结构文件.ibdata1共享表空间.ibd表独占空间.redo日志文件MyISAM分为三个文件: .frm存储表定义.MYD存储表数据.MYI存储表索引执行速度如果你的操作是大量的查询操作,如SELECT,使用MyISAM性能会更好。如果大部分是删除和更改的操作,使用InnoDB。 delete调用delete from table时,MyISAM会直接重建表,InnoDB会一行一行的删除,但是可以用truncate table代替。参考: mysql清空表数据的两种方式和区别。 锁MyISAM仅支持表锁,每次操作锁定整张表。InnoDB支持行锁,每次操作锁住最小数量的行数据。 表锁相比于行锁消耗的资源更少,且不会出现死锁,但同时并发性能差。行锁消耗更多的资源,速度较慢,且可能发生死锁,但是因为锁定的粒度小、数据少,并发性能好。如果InnoDB的一条语句无法确定要扫描的范围,也会锁定整张表。 当行锁发生死锁的时候,会计算每个事务影响的行数,然后回滚行数较少的事务。 数据恢复MyISAM崩溃后无法快速的安全恢复。InnoDB有一套完善的恢复机制。 数据缓存MyISAM仅缓存索引数据,通过索引查询数据。InnoDB不仅缓存索引数据,同时缓存数据信息,将数据按页读取到缓存池,按LRU(Latest Rare Use 最近最少使用)算法来进行更新。 如何选择存储引擎创建表的语句都是相同的,只有最后的type来指定存储引擎。 MyISAM大量查询总count查询频繁,插入不频繁没有事务操作InnoDB需要高可用性,或者需要事务表更新频繁参考资料MySQL InnoDB索引原理和算法:https://segmentfault.com/a/11...《MySQL技术内幕 InnoDB存储引擎》 1.3节mysql清空表数据的两种方式和区别:https://segmentfault.com/a/11...Mysql 中 MyISAM 和 InnoDB 的区别有哪些?:https://www.zhihu.com/questio...MySQL存储引擎MyISAM与InnoDB区别总结整理:https://blog.csdn.net/xlgen15...MySQL InnoDB的存储文件:https://blog.csdn.net/chenjia...

July 9, 2019 · 1 min · jiezi

数据人看Feed流架构实践

背景Feed流:可以理解为信息流,解决的是信息生产者与信息消费者之间的信息传递问题。我们常见的Feed流场景有:1 手淘,微淘提供给消费者的首页商品信息,用户关注店铺的新消息等2 微信朋友圈,及时获取朋友分享的信息3 微博,粉丝获取关注明星、大V的信息4 头条,用户获取系统推荐的新闻、评论、八卦 关于Feed流的架构设计,包括以上场景中的很多业内专家给出了相应的思考、设计和实践。本人是大数据方向出身的技术人,所在的团队参与了阿里手淘、微淘Feed流的存储层相关服务,我们的HBase/Lindorm数据存储产品在公有云上也支持着Soul、趣头条、惠头条等一些受欢迎的新媒体、社交类产品。我们在数据存储产品的功能、性能、可用性上的一些理解,希望对真实落地一个Feed流架构可以有一些帮助,以及一起探讨Feed流的未来以及数据产品如何帮助Feed流进一步迭代。 本文希望可以提供两点价值: 1 Feed流当前的主流架构以及落地方案2 一个初创公司如何选择Feed流的架构演进路径 业务分析Feed流参与者的价值信息生产者希望信息支持格式丰富(文字、图片、视频),发布流畅(生产信息的可用性),订阅者及时收到消息(时效性),订阅者不漏消息(传递的可靠性) 信息消费者希望及时收到关注的消息(时效性),希望不错过朋友、偶像的消息(传递的可靠性),希望获得有价值的消息(解决信息过载) 平台希望吸引更多的生产者和消费者(PV、UV),用户更长的停留时间,广告、商品更高的转化率 Feed信息传递方式一种是基于关系的消息传递,关系通过加好友、关注、订阅等方式建立,可能是双向的也可能是单向的。一种是基于推荐算法的,系统根据用户画像、消息画像利用标签分类或者协同过滤等算法向用户推送消息。微信和微博偏向于基于关系,头条、抖音偏向于基于推荐。 Feed流的技术难点互联网场景总是需要一定规模才能体现出技术的瓶颈,下面我们先看两组公开数据: 新浪微博为例,作为移动社交时代的重量级社交分享平台,2017年初日活跃用户1.6亿,月活跃用户近3.3亿,每天新增数亿条数据,总数据量达千亿级,核心单个业务的后端数据访问QPS高达百万级(来自 Feed系统架构与Feed缓存模型) 截止2016年12月底,头条日活跃用户7800W,月活跃用户1.75亿,单用户平均使用时长76分钟,用户行为峰值150w+msg/s,每天训练数据300T+(压缩后),机器规模万级别(来自 今日头条推荐系统架构设计实践) 上面还是两大巨头的历史指标,假设一条消息1KB那么千亿消息约93TB的数据量,日增量在几百GB规模且QPS高达百万,因此需要一个具备高读写吞吐,扩展性良好的分布式存储系统。用户浏览新消息期望百毫秒响应,希望新消息在秒级或者至少1分钟左右可见,对系统的实时性要求很高,这里需要多级的缓存架构。系统必须具备高可用,良好的容错性。最后这个系统最好不要太贵。 因此我们需要一个高吞吐、易扩展、低延迟、高可用、低成本的Feed流架构 主流架构图1是对Feed流的最简单抽象,完成一个从生产者向消费者传递消息的过程。 消息和关系首先,用户在APP侧获得的是一个Feed ID列表,这个列表不一定包含了所有的新消息,用户也不一定每一个都打开浏览,如果传递整个消息非常浪费资源,因此产生出来的消息首先生成主体和索引两个部分,其中索引包含了消息ID和元数据。其次一个应用总是存在关系,基于关系的传递是必不可少的,也因此一定有一个关系的存储和查询服务。 消息本身应该算是一种半结构化数据(包含文字,图片,短视频,音频,元数据等)。其读写吞吐量要求高,读写比例需要看具体场景。总的存储空间大,需要很好的扩展性来支撑业务增长。消息可能会有多次更新,比如内容修改,浏览数,点赞数,转发数(成熟的系统会独立一个counter模块来服务这些元数据)以及标记删除。消息一般不会永久保存,可能要在1年或者3年后删除。 综上,个人推荐使用HBase存储 HBase支持结构化和半结构化数据;具有非常好的写入性能,特别对于Feed流场景可以利用批量写接口单机(32核64GB)达到几十万的写入效率;HBase具备非常平滑的水平扩展能力,自动进行Sharding和Balance;HBase内置的BlockCache加上SSD盘可以提供ms级的高并发读;HBase的TTL特性可以自动的淘汰过期数据;利用数据复制搭建一个冷热分离系统,新消息存储在拥有SSD磁盘和大规格缓存的热库,旧数据存储在冷库。运用编码压缩有效的控制存储成本,见HBase优化之路-合理的使用编码压缩 对于关系服务,其写入操作是建立关系和删除关系,读取操作是获取关系列表,逻辑上仅需要一个KV系统。如果数据量较少可以使用RDS,如果数据量较大推荐使用HBase。如果对关系的QPS压力大可以考虑用Redis做缓存。 消息传递讲到Feed流一定会有关于推模式和拉模式的讨论,推模式是把消息复制N次发送到N个用户的收信箱,用户想看消息时从自己的收信箱直接获取。拉模式相反,生产者的消息写入自己的发信箱,用户想看消息时从关注的M个发信箱中收集消息。 推模式实现相对简单,时效性也比较好。拉模式要想获得好的性能需要多级的缓存架构。推模式重写,拉模式重读,Feed流场景下写的聚合效果要优于读,写可以大批量聚合。N越大,写入造成的数据冗余就越大。M越大,读消耗的资源越大。 随着业务的增长,推模式资源浪费会越发严重。原因在于两点:第一存在着大量的僵尸账号,以及大比例的非活跃用户几天或者半个月才登陆一次;第二信息过载,信息太多,重复信息太多,垃圾信息太多,用户感觉有用的信息少,消息的阅读比例低。这种情况下推模式相当一部分在做无用功,白白浪费系统资源。 是推?是拉?还是混合?没有最好的架构,只有适合的场景~ 基于关系的传递图6是纯推模式的架构,该架构有3个关键的部分 异步化。生产者提交消息首先写入一个队列,成功则表示发布成功,Dispatcher模块会异步的处理消息。这一点非常关键,首先生产者的消息发布体验非常好,不需要等待消息同步到粉丝的收信箱,发布延迟低成功率高;其次Dispatcher可以控制队列的处理速度,可以有效的控制大V账号造成的脉冲压力。多级队列。Dispatcher可以根据消费者的状态,信息的分类等划分不同的处理方式,分配不同的资源。比如对于大V账号的消息,当前活跃用户选择直接发送,保障消息的时效性,非活跃用户放入队列延迟发送。比如转发多的消息可以优先处理等。队列里的消息可以采用批量聚合写的方式提高吞吐。收信箱。假如有两亿用户,每个用户保留最新2000条推送消息。即便存储的是索引也是千亿的规模。收信箱一般的表结构为用户ID+消息序列 + 消息ID + 消息元数据,消息序列是一个递增的ID,需要存储一个偏移量表示上次读到的消息序列ID。用户读取最新消息 select * from inbox where 消息序列 > offset。 推荐使用HBase实现收信箱 HBase单机批量写能力在几十万并且可以水平扩展。HBase的高效前缀扫描非常适合读取最新的消息。HBase的TTL功能可以对数据定义生命周期,高效的淘汰过期数据。HBase的Filter过滤器和二级索引可以有效的实现Inbox的搜索能力。消费者收信箱hbase表设计如下,其中序列号要保证递增,一般用时间戳即可,特别高频情况下可以用一个RDS来制造序列号 Rowkey消息元数据列状态列其它列MD5(用户ID)+用户ID+序列号消息ID、作者、发布时间、关键字等已读、未读 图7是推拉结合的模式 增加发信箱,大V的发布进入其独立的发信箱。非大V的发布直接发送到用户的收信箱。其好处是解决大量的僵尸账号和非活跃账号的问题。用户只有在请求新消息的时候(比如登陆、下拉消息框)才会去消耗系统资源。发信箱的多级缓存架构。一个大V可能有百万粉丝,一条热点消息的传播窗口也会非常短,即短时间内会对发信箱中的同一条消息大量重复读取,对系统挑战很大。终态下我们可能会选择两级缓存,收信箱数据还是要持久化的,否则升级或者宕机时数据就丢失了,所以第一层是一个分布式数据存储,这个存储推荐使用HBase,原因和Inbox类似。第二层使用redis缓存加速,但是大V过大可能造成热点问题还需要第三层本地缓存。缓存层的优化主要包括两个方向:第一提高缓存命中率,常用的方式是对数据进行编码压缩,第二保障缓存的可用性,这里涉及到对缓存的冗余。 基于推荐的传递图8是基于推荐的模型,可以看出它是在推拉结合的模式上融合了推荐系统。 引入画像系统,保存用户画像、消息画像(简单情况下消息画像可以放在消息元数据中)。画像用于推荐系统算法的输入。引入了临时收信箱,在信息过载的场景中,非大V的消息也是总量很大,其中不免充斥着垃圾、冗余消息,所以直接进入用户收信箱不太合适。收信箱和发信箱都需要有良好的搜索能力,这是推荐系统高效运行的关键。Outbox有缓存层,索引可以做到缓存里面;Inbox一般情况下二级索引可以满足大部分需求,但如果用户希望有全文索引或者任意维度的检索能力,还需要引入搜索系统如Solr/ES 用户画像使用HBase存储 画像一般是稀疏表,画像总维度可能在200+甚至更多,但单个用户的维度可能在几十,并且维度可能随业务不断变化。那么HBase的Schema free和稀疏表的能力非常适合这个场景,易用且节省大量存储空间。对画像的访问一般是单行读,hbase本身单行Get的性能就非常好。阿里云HBase在这个方向上做了非常多的优化,包括CCSMAP、SharedBucketCache、MemstoreBloomFilter、Index Encoding等,可以达到平均RT=1-2ms,单库99.9% <100ms。阿里内部利用双集群Dual Service可以做到 99.9% < 30ms,这一能力我们也在努力推到公有云。hbase的读吞吐随机器数量水平扩展。临时收信箱使用云HBase HBase的读写高吞吐、低延迟能力,这里不再重复。HBase提供Filter和全局二级索引,满足不同量级的搜索需求。阿里云HBase融合HBase与Solr能力,提供低成本的全文索引、多维索引能力。初创公司的迭代路径在业务发展的初期,用户量和资源都没有那么多,团队的人力投入也是有限的,不可能一上来就搞一个特别复杂的架构,“够用”就行了,重要的是 可以快速的交付系统要稳定未来可以从容的迭代,避免推倒重来本人水平有限,根据自身的经验向大家推荐一种迭代路径以供参考,如有不同意见欢迎交流 ...

July 3, 2019 · 1 min · jiezi

大数据架构如何做到流批一体

阿里妹导读:大数据与现有的科技手段结合,对大多数产业而言都能产生巨大的经济及社会价值。这也是当下许多企业,在大数据上深耕的原因。大数据分析场景需要解决哪些技术挑战?目前,有哪些主流大数据架构模式及其发展?今天,我们都会一一解读,并介绍如何结合云上存储、计算组件,实现更优的通用大数据架构模式,以及该模式可以涵盖的典型数据处理场景。大数据处理的挑战现在已经有越来越多的行业和技术领域需求大数据分析系统,例如金融行业需要使用大数据系统结合 VaR(value at risk) 或者机器学习方案进行信贷风控,零售、餐饮行业需要大数据系统实现辅助销售决策,各种 IOT 场景需要大数据系统持续聚合和分析时序数据,各大科技公司需要建立大数据分析中台等等。 抽象来看,支撑这些场景需求的分析系统,面临大致相同的技术挑战: 业务分析的数据范围横跨实时数据和历史数据,既需要低延迟的实时数据分析,也需要对 PB 级的历史数据进行探索性的数据分析;可靠性和可扩展性问题,用户可能会存储海量的历史数据,同时数据规模有持续增长的趋势,需要引入分布式存储系统来满足可靠性和可扩展性需求,同时保证成本可控;技术栈深,需要组合流式组件、存储系统、计算组件和;可运维性要求高,复杂的大数据架构难以维护和管控;简述大数据架构发展Lambda 架构 Lambda 架构是目前影响最深刻的大数据处理架构,它的核心思想是将不可变的数据以追加的方式并行写到批和流处理系统内,随后将相同的计算逻辑分别在流和批系统中实现,并且在查询阶段合并流和批的计算视图并展示给用户。Lambda的提出者 Nathan Marz 还假定了批处理相对简单不易出现错误,而流处理相对不太可靠,因此流处理器可以使用近似算法,快速产生对视图的近似更新,而批处理系统会采用较慢的精确算法,产生相同视图的校正版本。 Lambda架构典型数据流程是(http://lambda-architecture.net/)): 所有的数据需要分别写入批处理层和流处理层;批处理层两个职责:(i)管理 master dataset (存储不可变、追加写的全量数据),(ii)预计算batch view;服务层对 batch view 建立索引,以支持低延迟、ad-hoc 方式查询 view;流计算层作为速度层,对实时数据计算近似的 real-time view,作为高延迟batch view 的补偿快速视图;所有的查询需要合并 batch view 和 real-time view;Lambda 架构设计推广了在不可变的事件流上生成视图,并且可以在必要时重新处理事件的原则,该原则保证了系统随需求演进时,始终可以创建相应的新视图出来,切实可行地满足了不断变化的历史数据和实时数据分析需求。 Lambda 架构的四个挑战 Lambda 架构非常复杂,在数据写入、存储、对接计算组件以及展示层都有复杂的子课题需要优化: 写入层上,Lambda 没有对数据写入进行抽象,而是将双写流批系统的一致性问题反推给了写入数据的上层应用;存储上,以 HDFS 为代表的master dataset 不支持数据更新,持续更新的数据源只能以定期拷贝全量 snapshot 到 HDFS 的方式保持数据更新,数据延迟和成本比较大;计算逻辑需要分别在流批框架中实现和运行,而在类似 Storm 的流计算框架和Hadoop MR 的批处理框架做 job 开发、调试、问题调查都是比较复杂的;结果视图需要支持低延迟的查询分析,通常还需要将数据派生到列存分析系统,并保证成本可控。流批融合的 Lambda 架构 针对 Lambda 架构的问题3,计算逻辑需要分别在流批框架中实现和运行的问题,不少计算引擎已经开始往流批统一的方向去发展,例如 Spark 和 Flink,从而简化lambda 架构中的计算部分。实现流批统一通常需要支持: ...

July 2, 2019 · 2 min · jiezi

MySQL单表数据不要超过500万行是经验数值还是黄金铁律

原文地址:梁桂钊的博客博客地址:http://blog.720ui.com 今天,探讨一个有趣的话题:MySQL 单表数据达到多少时才需要考虑分库分表?有人说 2000 万行,也有人说 500 万行。那么,你觉得这个数值多少才合适呢? 曾经在中国互联网技术圈广为流传着这么一个说法:MySQL 单表数据量大于 2000 万行,性能会明显下降。事实上,这个传闻据说最早起源于百度。具体情况大概是这样的,当年的 DBA 测试 MySQL性能时发现,当单表的量在 2000 万行量级的时候,SQL 操作的性能急剧下降,因此,结论由此而来。然后又据说百度的工程师流动到业界的其它公司,也带去了这个信息,所以,就在业界流传开这么一个说法。 再后来,阿里巴巴《Java 开发手册》提出单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。对此,有阿里的黄金铁律支撑,所以,很多人设计大数据存储时,多会以此为标准,进行分表操作。 那么,你觉得这个数值多少才合适呢?为什么不是 300 万行,或者是 800 万行,而是 500 万行?也许你会说这个可能就是阿里的最佳实战的数值吧?那么,问题又来了,这个数值是如何评估出来的呢?稍等片刻,请你小小思考一会儿。 事实上,这个数值和实际记录的条数无关,而与 MySQL 的配置以及机器的硬件有关。因为,MySQL 为了提高性能,会将表的索引装载到内存中。InnoDB buffer size 足够的情况下,其能完成全加载进内存,查询不会有问题。但是,当单表数据库到达某个量级的上限时,导致内存无法存储其索引,使得之后的 SQL 查询会产生磁盘 IO,从而导致性能下降。当然,这个还有具体的表结构的设计有关,最终导致的问题都是内存限制。这里,增加硬件配置,可能会带来立竿见影的性能提升哈。 那么,我对于分库分表的观点是,需要结合实际需求,不宜过度设计,在项目一开始不采用分库与分表设计,而是随着业务的增长,在无法继续优化的情况下,再考虑分库与分表提高系统的性能。对此,阿里巴巴《Java 开发手册》补充到:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。那么,回到一开始的问题,你觉得这个数值多少才合适呢?我的建议是,根据自身的机器的情况综合评估,如果心里没有标准,那么暂时以 500 万行作为一个统一的标准,相对而言算是一个比较折中的数值。 本文作者:白岳阅读原文 本文为云栖社区原创内容,未经允许不得转载。

June 24, 2019 · 1 min · jiezi

MongoDB-42-新特性解读

云数据库 MongoDB 版 基于飞天分布式系统和高性能存储,提供三节点副本集的高可用架构,容灾切换,故障迁移完全透明化。并提供专业的数据库在线扩容、备份回滚、性能优化等解决方案。 了解更多 MongoDB World 2019 上发布新版本 MongoDB 4.2 Beta,包含多项数据库新特性,本文尝试从技术角度解读。 Full Text SearchMongoDB 4.2 之前,全文搜索(Full Text Search)的能力是靠 Text Index 来支持的,在 MongoDB-4.2 里,MongoDB 直接与 Lucene 等引擎整合,在 Atlas 服务里提供全文建索的能力。 MongoDB FTS 原理用户可以在 Atlas 上,对集合开启全文索引,后台会开起 Lucene 索引引擎(索引引擎、查询引擎均可配置),对存量数据建立索引。对于开启全文建索的集合,新写入到 MongoDB 的数据, 后台的服务会通过 Change Stream 的方式订阅,并更新到 Lucene 索引引擎里。索引的查询直接以 MongoDB Query 的方式提供,Mongod 收到请求会把请求转发到 Lucene 引擎,收到建索结果后回复给客户端。Full Text Search 示例下面是一个 Full Text Search 使用的简单示例,整个使用体验非常简单,除了需要在 Atlas 控制台上建索引,其他跟正常使用 MongoDB 毫无差别,随着这块能力的完善,能覆盖很多 Elastic Search 的场景。 Step1: 准备数据 ...

June 24, 2019 · 3 min · jiezi

OceanBase数据库实践入门性能测试建议

概述本文主要分享针对想压测OceanBase时需要了解的一些技术原理。这些建议可以帮助用户对OceanBase做一些调优,再结合测试程序快速找到适合业务的最佳性能。由于OceanBase自身参数很多、部署形态也比较灵活,这里并没有给出具体步骤。 数据库读写特点压测的本质就是对一个会话的逻辑设计很高的并发。首先需要了解单个会话在数据库内部的读写逻辑。比如说,业务会话1对数据库发起一个DML SQL,第一次修改某笔记录,数据库会怎么做呢? 为了便于理解OB的行为,我们先看看ORACLE是怎么做的。后面有对比才可以加深理解。 ORACLE 读写特点ORACLE会话第一次修改一行记录,如果该记录所在块(8K大小)不在内存(Buffer Cache)里时会先从磁盘文件里读入到内存里。这个称为一次物理读,为了性能考虑,ORACLE一次会连续读取相邻的多个块。然后就直接在该块上修改,修改之前会先记录REDO和UNDO(包括UNDO的REDO)。然后这个数据块就是脏块(Dirty Block)。假设事务没有提交,其他会话又来读取这个记录,由于隔离级别是读已提交(READ COMMITTED),ORACLE会在内存里克隆当前数据块到新的位置,新块包含了最新的未提交数据。然后ORACLE在新块上逆向应用UNDO链表中的记录,将数据块回滚到读需要的那个版本(SCN),然后才能读。这个也称为一次一致性读(Consistency Read),这个新块也称为CR块。 即使是修改一条记录一个字段的几个字节,整个块(8K大小)都会是脏块。随着业务持续写入,大量脏块会消耗数据库内存。所以ORACLE会有多重机制刷脏块到磁盘数据文件上。在事务日志切换的时候也会触发刷脏块操作。如果业务压力测试ORACLE,大量的写导致事务日志切换很频繁,对应的刷脏操作可能相对慢了,就会阻塞日志切换,也就阻塞了业务写入。这就是ORACLE的特点。解决办法就是加大事务日志文件,增加事务日志成员或者用更快的磁盘存放事务日志和数据文件。 ORACLE里一个表就是一个Segment(如果有大对象列还会有独立的Segment,这个先忽略),Segment由多个不一定连续的extent组成,extent由连续的Block(每个大小默认8K)组成,extent缺点是可能会在后期由于频繁删除和插入产生空间碎片。 OceanBase 读写特点OceanBase会话第一次修改一行记录,如果该记录所在块(64K大小)不在内存(Block Cache)里时也会先从磁盘文件里读入到内存里。这个称为一次物理读。然后要修改时跟ORACLE做法不同的是,OceanBase会新申请一小块内存用于存放修改的内容,并且链接到前面Block Cache里该行记录所在块的那笔记录下。如果修改多次,每次修改都跟前面修改以链表形式关联。同样在修改之前也要先在内存里记录REDO。每次修改都会记录一个内部版本号,记录的每个版本就是一个增量。其他会话读取的时候会先从Block Cache中该记录最早读入的那个版本(称为基线版本)开始读,然后叠加应用后面的增量版本直到合适的版本(类似ORACLE中SCN概念)。(随着版本演进,这里细节逻辑可能会有变化。) OB的这个读方式简单说就是从最早的版本读起,逐步应用增量(类似REDO,但跟REDO日志无关)。而ORACLE一致性读是从最新的版本读起,逐步回滚(应用UNDO)。在OB里,没有UNDO。当版本链路很长时,OB的读性能会略下降,所以OB也有个checkpoint线程定时将记录的多个版本合并为少数几个版本。这个合并称为小合并(minor compaction)。此外,OB在内存里针对行记录还有缓存, 从上面过程还可以看出,每次修改几个字节,在内存里的变脏的块只有增量版本所在的块(默认写满才会重新申请内存),基线数据块是一直不变化。所以OB里脏块产生的速度非常小,脏块就可以在内存里保存更久的时间。实际上OB的设计就是脏块默认不刷盘。那如果机器挂了,会不会丢数据呢? OB跟ORACLE一样,修改数据块之前会先记录REDO,在事务提交的时候,REDO要先写到磁盘上(REDO同时还会发送往其他两个副本节点,这个先忽略)。有REDO在,就不怕丢数据。此外,增量部分每天还是会落盘一次。在落盘之前,内存中的基线数据和相关的增量数据会在内存里进行一次合并(称Merge),最终以SSTable的格式写回到磁盘。如果说内存里块内部产生碎片,在合并的那一刻,这个碎片空间基本被消弭掉了。所以说OB的数据文件空间碎片很小,不需要做碎片整理。同时OB的这个设计也极大降低了LSM的写放大问题。 当业务压测写OB时,脏块的量也会增长,最终达到增量内存限制,这时候业务就无法写入,需要OB做合并释放内存。OB的合并比较耗IO、CPU(有参数可以控制合并力度),并且也不会等到内存用尽才合并,实际会设置一个阈值。同时为了规避合并,设计了一个转储机制。当增量内存使用率超过阈值后,就开启转储。转储就是直接把增量内存写到磁盘上(不合并)。转储对性能的影响很小,可以高峰期发生,并且可以转储多次(参数配置)。 OB增量内存就类似一个水池,业务写是进水管在放水, 转储和大合并是出水管。水位就是当前增量内存使用率。当进水的速度快于出水,池子可能就会满。这时候业务写入就会报内存不足的错误。这就是OB读写的特点,解决方法就是加大OB内存、或者允许OB自动对业务写入速度限流。 OceanBase部署建议OB 在commit的时候redo落盘会写磁盘。读数据的时候内存未命中的时候会有物理读,转储和大合并的时候落盘会有密集型写IO。这些都依赖磁盘读写性能。所以建议磁盘都是SSD盘,并且建议日志盘和数据盘使用独立的文件系统。如果是NVME接口的闪存卡或者大容量SSD盘,那日志盘和数据盘放在一起也可以。不要使用LVM对NVME接口的大容量SSD做划分,那样瓶颈可能会在LVM自身。 OB的增量通常都在内存里,内存不足的时候会有转储,可以转储多次。尽管如此,建议测试机器的内存不要太小,防止频繁的增量转储。通常建议192G内存以上。 OB集群的节点数至少要有三个。如果是功能了解,在单机上起3个OB进程模拟三节点是可以的,但是如果是性能测试,那建议还是使用三台同等规格的物理机比较合适。机器规格不一致时,最小能力的机器可能会制约整个集群的性能。 OceanBase集群的手动部署请参考《OceanBase数据库实践入门——手动搭建OceanBase集群》。在部署好OceanBase之后,建议先简单了解一下OceanBase的使用方法,详情请参考文章《OceanBase数据库实践入门——常用操作SQL》。 如果要验证OB的弹性缩容、水平扩展能力,建议至少要6节点(部署形态2-2-2)。并且测试租户(实例)的每个Zone里的资源单元数量至少也要为2个,才可以发挥多机能力。这是因为OB是多租户设计,对资源的管理比较类似云数据库思想,所以里面设计有点精妙,详情请参见《揭秘OceanBase的弹性伸缩和负载均衡原理》。 下面是一个租户的测试租户资源初始化建议 登录sys租户create resource unit S1, max_cpu=2, max_memory='10G', min_memory='10G', max_iops=10000, min_iops=1000, max_session_num=1000000, max_disk_size=536870912;create resource unit S2, max_cpu=4, max_memory='20G', min_memory='20G', max_iops=20000, min_iops=5000, max_session_num=1000000, max_disk_size=1073741824;create resource unit S3, max_cpu=8, max_memory='40G', min_memory='40G', max_iops=50000, min_iops=10000, max_session_num=1000000, max_disk_size=2147483648;select * from __all_unit_config;create resource pool pool_demo unit = 'S2', unit_num = 2;select * from __all_resource_pool order by resource_pool_id desc ;create tenant t_obdemo resource_pool_list=('pool_demo');alter tenant t_obdemo set variables ob_tcp_invited_nodes='%';请注意上面的unit_num=2这个很关键。如果unit_num=1,OB会认为这个租户是个小租户,后面负载均衡处理时会有个默认规则。 ...

June 19, 2019 · 2 min · jiezi

揭秘一个高准确率的Flutter埋点框架如何设计

背景用户行为埋点是用来记录用户在操作时的一系列行为,也是业务做判断的核心数据依据,如果缺失或者不准确将会给业务带来不可恢复的损失。闲鱼将业务代码从Native迁移到Flutter上过程中,发现原先Native体系上的埋点方案无法应用在Flutter体系之上。而如果我们只把业务功能迁移过来就上线,对业务是极其不负责任的。因此,经过不断探索,我们沉淀了一套Flutter上的高准确率的用户行为埋点方案。 用户行为埋点定义先来讲讲在我们这里是如何定义用户行为埋点的。在如下用户时间轴上,用户进入A页面后,看到了按钮X,然后点击了这个按钮,随即打开了新的页面B。 这个时间轴上有如下5个埋点事件发生: 进入A页面。A页面首帧渲染完毕,并获得了焦点。曝光坑位X。按钮X处于手机屏幕内,且停留一段时间,让用户可见可触摸。点击坑位X。用户对按钮X的内容很感兴趣,于是点击了它。按钮X响应点击,然后需要打开一个新页面。离开A页面。A页面失去焦点。进入B页面。B页面首帧渲染完毕,并获得焦点。在这里,打埋点最重要的是时机,即在什么时机下的事件中触发什么埋点,下面来看看闲鱼在Flutter上的实现方案。 实现方案进入/离开页面 在Native原生开发中,Android端是监听Activity的onResume和onPause事件来做为页面的进入和离开事件,同理iOS端是监听UIViewController的viewWillAppear和viewDidDisappear事件来做为页面的进入和离开事件。同时整个页面栈是由Android和iOS操作系统来维护。 在Flutter中,Android和iOS端分别是用FlutterActivity和FlutterViewController来做为容器承载Flutter的页面,通过这个容器可以在一个Native的页面内(FlutterActivity/FlutterViewController)来进行Flutter原生页面的切换。即在Flutter自己维护了一个Flutter页面的页面栈。这样,原来我们最熟悉的那套在Native原生上方案在Flutter上无法直接运作起来。 针对这个问题,可能很多人会想到去注册监听Flutter的NavigatorObserver,这样就知道Flutter页面的进栈(push)和出栈(pop)事件。但是这会有两个问题: 假设A、B两个页面先后进栈(A enter -> A leave -> B enter)。然后B页面返回退出(B leave),此时A页面重新可见,但是此时是收不到A页面push(A enter)的事件。假设在A页面弹出一个Dialog或者BottomSheet,而这两类也会走push操作,但实际上A页面并未离开。好在Flutter的页面栈不像Android Native的页面栈那么复杂,所以针对第一个问题,我们可以来维护一个和页面栈匹配的索引列表。当收到A页面的push事件时,往队列里塞一个A的索引。当收到B页面的push事件时,检测列表内是否有页面,如有,则对列表最后一个页面执行离开页面事件记录,然后再对B页面执行进入页面事件记录,接着往队列里塞一个B的索引。当收到B页面的pop事件时,先对B页面执行离开页面事件记录,然后对队列里存在的最后一个索引对应的页面(假设为A)进行判断是否在栈顶(ModalRoute.of(context).isCurrent),如果是,则对A页面执行进入页面事件记录。 针对第二个问题,Route类内有个成员变量overlayEntries,可以获取当前Route对应的所有图层OverlayEntry,在OverlayEntry对象中有个成员变量opaque可以判断当前这个图层是否全屏覆盖,从而可以排除Dialog和BottomSheet这种类型。再结合问题1,还需要在上述方案中加上对push进来的新页面来做判断是否为一个有效页面。如果是有效页面,才对索引列表中前一个页面做离开页面事件,且将有效页面加到索引列表中。如果不是有效页面,则不操作索引列表。 以上并不是闲鱼的方案,只是笔者给出的一个建议。因为闲鱼APP在一开始落地Flutter框架时,就没有使用Flutter原生的页面栈管理方案,而是采用了Native+Flutter混合开发的方案。具体可参考前面的一篇文章《已开源|码上用它开始Flutter混合开发——FlutterBoost》。因此接下来也是基于此来阐述闲鱼的方案。 闲鱼的方案如下(以Android为例,iOS同理): 注:首次打开指的是基于混合栈新打开一个页面,非首次打开指的是通过回退页面的方式,在后台的页面再次到前台可见。 看似我们将何时去触发进入/离开页面事件的判断交给Flutter侧,实际上依然跟Native侧的页面栈管理保持了一致,将原先在Native侧做打埋点的时机告知Flutter侧,然后Flutter侧再立刻通过channel来调用Native侧的打埋点方法。那么可能会有人问,为什么这么绕,不全部交给Native侧去直接管理呢?交给Native侧去直接管理这样做针对非首次打开这个场景是合适的,但是对首次打开这个场景却是不合适的。因为在首次打开这个场景下,onResume时Flutter页面尚未初始化,此时还不知道页面信息,因此也就不知道进入了什么页面,所以需要在Flutter页面初始化(init)时再回过来调Native侧的进入页面埋点接口。为了避免开发人员去关注是否为首次打开Flutter页面,因此我们统一在Flutter侧来直接触发进入/离开页面事件。 曝光坑位先讲下曝光坑位在我们这里的定义,我们认为图片和文本是有曝光意义的,其他用户看不见的是没有曝光意义的,在此之上,当一个坑位同时满足以下两点时才会被认为是一次有效曝光: 坑位在屏幕可见区域中的面积大于等于坑位整体面积的一半。坑位在屏幕可见区域中停留超过500ms。基于此定义,我们可以很快得出如下图所示的场景,在一个可以滚动的页面上有A、B、C、D共4个坑位。其中: 坑位A已经滑出了屏幕可见区域,即invisible;坑位B即将向上从屏幕中可见区域滑出,即visible->invisible;坑位C还在屏幕中央可视区域内,即visible;坑位D即将滑入屏幕中可见区域,invisible->visible; 那么我们的问题就是如何算出坑位在屏幕内曝光面积的比例。要算出这个值,需要知道以下几个数值: 容器相对屏幕的偏移量坑位相对容器的偏移量坑位的位置和宽高容器的位置和宽高其中坑位和容器的宽和高很容易获取和计算,这里就不再累述。 获取容器相对屏幕的偏移量 //监听容器滚动,得到容器的偏移量double _scrollContainerOffset = scrollNotification.metrics.pixels;获取坑位相对容器的偏移量 //曝光坑位Widget的contextfinal RenderObject childRenderObject = context.findRenderObject();final RenderAbstractViewport viewport = RenderAbstractViewport.of(childRenderObject);if (viewport == null) { return;}if (!childRenderObject.attached) { return;}//曝光坑位在容器内的偏移量final RevealedOffset offsetToRevealTop = viewport.getOffsetToReveal(childRenderObject, 0.0);逻辑判断 if (当前坑位是invisible && 曝光比例 >= 0.5) { 记录当前坑位是visible状态 记录出现时间} else if (当前坑位是visible && 曝光比例 < 0.5) { 记录当前坑位是invisible状态 if (当前时间-出现时间 > 500ms) { 调用曝光埋点接口 }}点击坑位 ...

June 13, 2019 · 1 min · jiezi

贷前系统ElasticSearch实践总结

贷前系统负责从进件到放款前所有业务流程的实现,其中涉及一些数据量较大、条件多样且复杂的综合查询,引入ElasticSearch主要是为了提高查询效率,并希望基于ElasticSearch快速实现一个简易的数据仓库,提供一些OLAP相关功能。本文将介绍贷前系统ElasticSearch的实践经验。 一、索引描述:为快速定位数据而设计的某种数据结构。 索引好比是一本书前面的目录,能加快数据库的查询速度。了解索引的构造及使用,对理解ES的工作模式有非常大的帮助。 常用索引: 位图索引哈希索引BTREE索引倒排索引1.1 位图索引(BitMap)位图索引适用于字段值为可枚举的有限个数值的情况。 位图索引使用二进制的数字串(bitMap)标识数据是否存在,1标识当前位置(序号)存在数据,0则表示当前位置没有数据。 下图1 为用户表,存储了性别和婚姻状况两个字段; 图2中分别为性别和婚姻状态建立了两个位图索引。 例如:性别->男 对应索引为:101110011,表示第1、3、4、5、8、9个用户为男性。其他属性以此类推。 使用位图索引查询: 男性 并且已婚 的记录 = 101110011 & 11010010 = 100100010,即第1、4、8个用户为已婚男性。女性 或者未婚的记录 = 010001100 | 001010100 = 011011100, 即第2、3、5、6、7个用户为女性或者未婚。1.2 哈希索引顾名思义,是指使用某种哈希函数实现key->value 映射的索引结构。 哈希索引适用于等值检索,通过一次哈希计算即可定位数据的位置。 下图3 展示了哈希索引的结构,与JAVA中HashMap的实现类似,是用冲突表的方式解决哈希冲突的。 1.3 BTREE索引BTREE索引是关系型数据库最常用的索引结构,方便了数据的查询操作。 BTREE: 有序平衡N阶树, 每个节点有N个键值和N+1个指针, 指向N+1个子节点。 一棵BTREE的简单结构如下图4所示,为一棵2层的3叉树,有7条数据: 以Mysql最常用的InnoDB引擎为例,描述下BTREE索引的应用。 Innodb下的表都是以索引组织表形式存储的,也就是整个数据表的存储都是B+tree结构的,如图5所示。 主键索引为图5的左半部分(如果没有显式定义自主主键,就用不为空的唯一索引来做聚簇索引,如果也没有唯一索引,则innodb内部会自动生成6字节的隐藏主键来做聚簇索引),叶子节点存储了完整的数据行信息(以主键 + row_data形式存储)。 二级索引也是以B+tree的形式进行存储,图5右半部分,与主键不同的是二级索引的叶子节点存储的不是行数据,而是索引键值和对应的主键值,由此可以推断出,二级索引查询多了一步查找数据主键的过程。 维护一颗有序平衡N叉树,比较复杂的就是当插入节点时节点位置的调整,尤其是插入的节点是随机无序的情况;而插入有序的节点,节点的调整只发生了整个树的局部,影响范围较小,效率较高。 可以参考红黑树的节点的插入算法: https://en.wikipedia.org/wiki... 因此如果innodb表有自增主键,则数据写入是有序写入的,效率会很高;如果innodb表没有自增的主键,插入随机的主键值,将导致B+tree的大量的变动操作,效率较低。这也是为什么会建议innodb表要有无业务意义的自增主键,可以大大提高数据插入效率。 注: Mysql Innodb使用自增主键的插入效率高。使用类似Snowflake的ID生成算法,生成的ID是趋势递增的,插入效率也比较高。1.4 倒排索引(反向索引)倒排索引也叫反向索引,可以相对于正向索引进行比较理解。 正向索引反映了一篇文档与文档中关键词之间的对应关系;给定文档标识,可以获取当前文档的关键词、词频以及该词在文档中出现的位置信息,如图6 所示,左侧是文档,右侧是索引。 反向索引则是指某关键词和该词所在的文档之间的对应关系;给定了关键词标识,可以获取关键词所在的所有文档列表,同时包含词频、位置等信息,如图7所示。 反向索引(倒排索引)的单词的集合和文档的集合就组成了如图8所示的”单词-文档矩阵“,打钩的单元格表示存在该单词和文档的映射关系。 ...

June 6, 2019 · 3 min · jiezi

支付宝工程师如何搞定关系数据库的大脑查询优化器

摘要: 本文将深入了解OceanBase在查询优化器方面的设计思路和历经近十年时间提炼出的工程实践哲学。 前言查询优化器是关系数据库系统的核心模块,是数据库内核开发的重点和难点,也是衡量整个数据库系统成熟度的“试金石”。 查询优化理论诞生距今已有四十来年,学术界和工业界其实已经形成了一套比较完善的查询优化框架(System-R 的 Bottom-up 优化框架和 Volcano/Cascade 的 Top-down 优化框架),但围绕查询优化的核心难题始终没变——如何利用有限的系统资源尽可能为查询选择一个“好”的执行计划。 近年来,新的存储结构(如 LSM 存储结构)的出现和分布式数据库的流行进一步加大了查询优化的复杂性,本文章结合 OceanBase 数据库过去近十年时间的实践经验,与大家一起探讨查询优化在实际应用场景中的挑战和解决方案。 查询优化器简介SQL 是一种结构化查询语言,它只告诉数据库”想要什么”,但是它不会告诉数据库”如何获取”这个结果,这个"如何获取"的过程是由数据库的“大脑”查询优化器来决定的。在数据库系统中,一个查询通常会有很多种获取结果的方法,每一种获取的方法被称为一个"执行计划"。给定一个 SQL,查询优化器首先会枚举出等价的执行计划。 其次,查询优化器会根据统计信息和代价模型为每个执行计划计算一个“代价”,这里的代价通常是指执行计划的执行时间或者执行计划在执行时对系统资源(CPU + IO + NETWORK)的占用量。最后,查询优化器会在众多等价计划中选择一个"代价最小"的执行计划。下图展示了查询优化器的基本组件和执行流程。 查询优化器面临的挑战查询优化自从诞生以来一直是数据库的难点,它面临的挑战主要体现在以下三个方面: 挑战一:精准的统计信息和代价模型 统计信息和代价模型是查询优化器基础模块,它主要负责给执行计划计算代价。精准的统计信息和代价模型一直是数据库系统想要解决的难题,主要原因如下: 1、统计信息:在数据库系统中,统计信息搜集主要存在两个问题。首先,统计信息是通过采样搜集,所以必然存在采样误差。其次,统计信息搜集是有一定滞后性的,也就是说在优化一个 SQL 查询的时候,它使用的统计信息是系统前一个时刻的统计信息。 2、选择率计算和中间结果估计:选择率计算一直以来都是数据库系统的难点,学术界和工业界一直在研究能使选择率计算变得更加准确的方法,比如动态采样,多列直方图等计划,但是始终没有解决这个难题,比如连接谓词选择率的计算目前就没有很好的解决方法。 3、代价模型:目前主流的数据库系统基本都是使用静态的代价模型,比如静态的 buffer 命中率,静态的 IO RT,但是这些值都是随着系统的负载变化而变化的。如果想要一个非常精准的代价模型,就必须要使用动态的代价模型。 挑战二:海量的计划空间 复杂查询的计划空间是非常大的,在很多场景下,优化器甚至没办法枚举出所有等价的执行计划。下图展示了星型查询等价逻辑计划个数(不包含笛卡尔乘积的逻辑计划),而优化器真正的计划空间还得正交上算子物理实现,基于代价的改写和分布式计划优化。在如此海量的计划空间中,如何高效的枚举执行计划一直是查询优化器的难点。 挑战三:高效的计划管理机制 计划管理机制分成计划缓存机制和计划演进机制。 1、计划缓存机制:计划缓存根据是否参数化,优化一次/总是优化以及是否缓存可以划分成如下图所示的三种计划缓存方法。每个计划缓存方法都有各自的优缺点,不同的业务需求会选择不同的计划缓存方法。在蚂蚁/阿里很多高并发,低时延的业务场景下,就会选择参数化+优化一次+缓存的策略,那么就需要解决不同参数对应不同计划的问题(parametric query optimization),后面我们会详细讨论。 2、计划演进机制:计划演进是指对新生成计划进行验证,保证新计划不会造成性能回退。在数据库系统中, 新计划因为一些原因(比如统计信息刷新,schema版本升级)无时无刻都在才生,而优化器因为各种不精确的统计信息和代价模型始终是没办法百分百的保证新生成的计划永远都是最优的,所以就需要一个演进机制来保证新生成的计划不会造成性能回退。 OceanBase 查询优化器工程实践下面我们来看一下 OceanBase 根据自身的框架特点和业务模型如何解决查询优化器所面临的挑战。 从统计信息和代价模型的维度看,OceanBase 发明了基于 LSM-TREE 存储结构的基表访问路径选择。从计划空间的角度看,因为 OceanBase 原生就是一个分布式关系数据库系统,它必然要面临的一个问题就是分布式计划优化。从计划管理的角度看,OceanBase 有一整套完善的计划管理机制。 1.基于 LSM - TREE 的基表访问路径选择 基表访问路径选择方法是指优化器选择索引的方法,其本质是要评估每一个索引的代价并选择代价最小的索引来访问数据库中的表。对于一个索引路径,它的代价主要由两部分组成,扫描索引的代价和回表的代价(如果一个索引对于一个查询来说不需要回表,那么就没有回表的代价)。 通常来说,索引路径的代价取决于很多因素,比如扫描/回表的行数,投影的列数,谓词的个数等。为了简化我们的讨论,在下面的分析中,我们从行数这个维度来介绍这两部分的代价。 扫描索引的代价扫描索引的代价跟扫描的行数成正比,而扫描的行数则是由一部分查询的谓词来决定,这些谓词定义了索引扫描开始和结束位置。理论上来说扫描的行数越多,执行时间就会越久。扫描索引的代价是顺序 IO。 ...

May 31, 2019 · 2 min · jiezi

5分钟了解阿里时序时空数据库

简介时序时空数据库(Time Series & Spatial Temporal Database,简称 TSDB)是一种高性能、低成本、稳定可靠的在线时序时空数据库服务,提供高效读写、高压缩比存储、时序数据插值及聚合计算等服务,广泛应用于物联网(IoT)设备监控系统、企业能源管理系统(EMS)、生产安全监控系统和电力检测系统等行业场景;除此以外,还提供时空场景的查询和分析的能力。 三个数据库时序时空数据库文档最近经过几次大的变动,有点乱,看的时候注意一下。 时序数据库TSDB版 经过阿里集团大规模验证的时序数据库,支持分布式集群架构水平扩展,支持千万物联网设备接入,基于自研压缩算法,具备高效压缩比。 - 针对时序数据优化,包括存储模型,多值数据模型,时序数据压缩、聚合、采样,高效压缩算法,列存,边缘一体化;- 具备高性能,内存优先数据处理,分布式MPP SQL并行计算,动态schema,实时流式数据计算引擎,海量时间线自适应索引;- 高可扩展,数据动态分区,水平扩展,动态弹性扩容,动态升降配规格;高可靠性,自动集群控制,线程级读写分离,多层数据备份,分级存储;- 瞄准的是大规模指标数据,事件数据场景协议兼容OpenTSDB,但后面内核实现是阿里自研的。但还是完全可以把它当作OpenTSDB的阿里云版,参见 相比OpenTSDB优势 InfluxDB® 不仅仅是一个数据库,更是一个监控系统,围绕采集,可视化,分析服务,事件和指标存储和计算系统;走的是tick生态,瞄准指标,事件,trace,日志,实时分析场景。 InfluxDB®刚上线不久,现在还处在公测阶段。写入速度经测试,每次500条数据,每秒可以执行26次左右,平均速度达到1万/s,增加每次写入数据条数应该还能提高速度。另外,请求地址是外网,如果使用vpc网络速度应该还会加快不少。 注意:InfluxDB在阿里云上有时间线限制(数据库级别最高1万),时间线的定义参见后面简介。 时空数据库 时空数据库能够存储、管理包括时间序列以及空间地理位置相关的数据。时空数据是一种高维数据,具有时空数据模型、时空索引和时空算子,完全兼容SQL及SQL/MM标准,支持时空数据同业务数据一体化存储、无缝衔接,易于集成使用。 时空数据库主要是空间相关的场景,比如热力图,店铺选址等等。 时序数据库简介(主要是InfluxDB)时序数据库英文全称为 Time Series Database,提供高效存取时序数据和统计分析功能的数据管理系统。主要的时序数据库包括OpenTSDB、Druid、InfluxDB以及Beringei这四个。本人主要了解一点OpenTSDB和InfluxDB,不过时序数据库有很多共性。 基本名词 measurement: tag,field和time列的容器对InfluxDB: measurement在概念上类似于传统DB的table(表格) 从原理上讲更像SQL中表的概念,这和其他很多时序数据库有些不同对其他时序DB: Measurement与Metric等同field(数值列): TSDB For InfluxDB®中不能没有field。注意:field是没有索引的在某种程度上,可以把field理解为k/v表的valuetag(维度列): tag不是必须要有的字段tag是被索引的,这意味着以tag作为过滤条件的查询会更快在某种程度上,可以把field理解为k/v表的keytimestamp(时间戳): 默认使用服务器的本地时间戳时间戳是UNIX时间戳,单位:纳秒最小的有效时间戳是-9223372036854775806或1677-09-21T00:12:43.145224194Z最大的有效时间戳是9223372036854775806或2262-04-11T23:47:16.854775806Zpoint(数据点): 由时间线(series)中包含的field组成。每个数据点由它的时间线和时间戳(timestamp)唯一标识您不能在同一时间线存储多个有相同时间戳的数据点Series(时间线) Series是InfluxDB中最重要的概念,时序数据的时间线就是:一个数据源采集的一个指标随着时间的流逝而源源不断地吐出数据这样形成的一条数据线称之为时间线。 下图中有两个数据源,每个数据源会采集两种指标: Series由Measurement和Tags组合而成,Tags组合用来唯一标识Measurement就是说:1\. Measurement不同,就是不同的时间线2\. Measurement相同,Tags不同也是不同的时间线retention policy(保留策略,简称RP) 一个保留策略描述了: 1.InfluxDB保存数据的时间(DURATION) 2.以及存储在集群中数据的副本数量(REPLICATION) 3.指定ShardGroup Duration注:复本系数(replication factors)不适用于单节点实例。autogen:无限的存储时间并且复制系数设为1RP创建语句如下: CREATE RETENTION POLICY ON <retention_policy_name> ON <database_name>DURATION <duration> REPLICATION <n> [SHARD DURATION <duration> ] [DEFAULT]实例:CREATE RETENTION POLICY "one_day_only" ON "water_database"DURATION 1d REPLICATION 1 SHARD DURATION 1h DEFAULT写入时指定rp进行写入: ...

May 27, 2019 · 2 min · jiezi

mysql高效索引之覆盖索引

什么叫做覆盖索引? 解释一: 就是select的数据列只用从索引中就能够取得,不必从数据表中读取,换句话说查询列要被所使用的索引覆盖。 解释二: 索引是高效找到行的一个方法,当能通过检索索引就可以读取想要的数据,那就不需要再到数据表中读取行了。如果一个索引包含了(或覆盖了)满足查询语句中字段与条件的数据就叫做覆盖索引。 解释三:是非聚集组合索引的一种形式,它包括在查询里的Select、Join和Where子句用到的所有列(即建立索引的字段正好是覆盖查询语句[select子句]与查询条件[Where子句]中所涉及的字段,也即,索引包含了查询正在查找的所有数据)。 不是所有类型的索引都可以成为覆盖索引。覆盖索引必须要存储索引的列,而哈希索引、空间索引和全文索引等都不存储索引列的值,所以MySQL只能使用B-Tree索引做覆盖索引当发起一个被索引覆盖的查询(也叫作索引覆盖查询)时,在EXPLAIN的Extra列可以看到“Using index”的信息。 注:遇到以下情况,执行计划不会选择覆盖查询。 select选择的字段中含有不在 索引 中的字段 ,即索引没有覆盖全部的列。where条件中不能含有对索引进行like的操作。mysql聚集索引,辅助索引,联合索引,覆盖索引 聚集索引:一个表中只能有一个,聚集索引的顺序与数据真实的物理存储顺序一致。查询速度贼快,聚集索引的叶子节点上是该行的所有数据 ,数据索引能加快范围查询(聚集索引的顺序和数据存放的逻辑顺序一致)。主键!=聚集索引。 辅助索引(非聚集索引):一个表中可以有多个,叶子节点存放的不是一整行数据,而是键值,叶子节点的索引行中还包含了一个'书签',这个书签就是指向聚簇索引的一个指针,从而在聚簇索引树中找到一整行数据。 联合索引:就是由多列组成的的索引。遵循最左前缀规则。对where,order by,group by 都生效。 覆盖索引:指从辅助索引中就能获取到需要的记录,而不需要查找聚簇索引中的记录。使用覆盖索引的一个好处是因为辅助索引不包括一条记录的整行信息,所以数据量较聚集索引要少,可以减少大量io操作。 聚集索引与辅助索引的区别:叶子节点是否存放的为一整行数据 最左前缀规则:假设联合索引由列(a,b,c)组成,则一下顺序满足最左前缀规则:a、ab、abc;selece、where、order by 、group by都可以匹配最左前缀。其它情况都不满足最左前缀规则就不会用到联合索引。

May 24, 2019 · 1 min · jiezi

深入搜索引擎原理

之前几段工作经历都与搜索有关,现在也有业务在用搜索,对搜索引擎做一个原理性的分享,包括搜索的一系列核心数据结构和算法,尽量覆盖搜索引擎的核心原理,但不涉及数据挖掘、NLP等。文章有点长,多多指点~~ 一、搜索引擎引题搜索引擎是什么? 这里有个概念需要提一下。信息检索 (Information Retrieval 简称 IR) 和 搜索 (Search) 是有区别的,信息检索是一门学科,研究信息的获取、表示、存储、组织和访问,而搜索只是信息检索的一个分支,其他的如问答系统、信息抽取、信息过滤也可以是信息检索。 本文要讲的搜索引擎,是通常意义上的全文搜索引擎、垂直搜索引擎的普遍原理,比如 Google、Baidu,天猫搜索商品、口碑搜索美食、飞猪搜索酒店等。 Lucene 是非常出名且高效的全文检索工具包,ES 和 Solr 底层都是使用的 Lucene,本文的大部分原理和算法都会以 Lucene 来举例介绍。 为什么需要搜索引擎? 看一个实际的例子:如何从一个亿级数据的商品表里,寻找名字含“秋裤”的 商品。 使用SQL Like select * from item where name like '%秋裤%'如上,大家第一能想到的实现是用 like,但这无法使用上索引,会在大量数据集上做一次遍历操作,查询会非常的慢。有没有更简单的方法呢,可能会说能不能加个秋裤的分类或者标签,很好,那如果新增一个商品品类怎么办呢?要加无数个分类和标签吗?如何能更简单高效的处理全文检索呢? 使用搜索引擎 答案是搜索,会事先 build 一个倒排索引,通过词法语法分析、分词、构建词典、构建倒排表、压缩优化等操作构建一个索引,查询时通过词典能快速拿到结果。这既能解决全文检索的问题,又能解决了SQL查询速度慢的问题。 那么,淘宝是如何在1毫秒从上亿个商品找到上千种秋裤的呢,谷歌如何在1毫秒从万亿个网页中找寻到与你关键字匹配的几十万个网页,如此大的数据量是怎么做到毫秒返回的。 二、搜索引擎是怎么做的?Part1. 分词分词就是对一段文本,通过规则或者算法分出多个词,每个词作为搜索的最细粒度一个个单字或者单词。只有分词后有这个词,搜索才能搜到,分词的正确性非常重要。分词粒度太大,搜索召回率就会偏低,分词粒度太小,准确率就会降低。如何恰到好处的分词,是搜索引擎需要做的第一步。 正确性&粒度 分词正确性 “他说的确实在理”,这句话如何分词?“他-说-的确-实在-理” [错误语义]“他-说-的-确实-在理” [正确语义]分词的粒度 “中华人民共和国宪法”,这句话如何分词?“中华人民共和国-宪法”,[搜索 中华、共和国 无结果]“中华-人民-共和国-宪法”,[搜索 共和 无结果]“中-华-人-民-共-和-国-宪-法”,[搜索其中任意字都有结果]分词的粒度并不是越小越好,他会降低准确率,比如搜索 “中秋” 也会出现上条结果,而且粒度越小,索引词典越大,搜索效率也会下降,后面会细说。 如何准确的把控分词,涉及到 NLP 的内容啦,这里就不展开了。 停用词 很多语句中的词都是没有意义的,比如 “的”,“在” 等副词、谓词,英文中的 “a”,“an”,“the”,在搜索是无任何意义的,所以在分词构建索引时都会去除,降低不不要的索引空间,叫停用词 (StopWord)。 通常可以通过文档集频率和维护停用词表的方式来判断停用词。 词项处理 ...

May 15, 2019 · 3 min · jiezi

Pandas时序数据处理入门

作为一个几乎每天与时间序列数据打交道的人员,我发现panda Python包在时间序列的操作和分析方面有强大优势。 这篇关于panda时间序列数据处理的基本介绍可以带你入门时间序列分析。本文将主要介绍以下操作: 创建一个日期范围处理时间戳数据将字符串数据转换为时间戳在数据框中索引和切片时间序列数据重新采样不同时间段的时间序列汇总/汇总统计数据计算滚动统计数据,如滚动平均值处理丢失数据了解unix/epoch时间的基础知识了解时间序列数据分析的常见陷阱接下来我们一起步入正题。如果想要处理已有的实际数据,你可能考虑从使用panda read_csv将文件读入数据框开始,然而在这里,我们将直接从处理生成的数据开始。 首先导入我们将会使用到的库,然后用它们创建日期范围 import pandas as pdfrom datetime import datetimeimport numpy as npdate_rng = pd.date_range(start='1/1/2018', end='1/08/2018', freq='H')这个日期范围的时间戳为每小时一次。如果我们调用date_rng,我们会看到如下所示: DatetimeIndex(['2018-01-01 00:00:00', '2018-01-01 01:00:00', '2018-01-01 02:00:00', '2018-01-01 03:00:00', '2018-01-01 04:00:00', '2018-01-01 05:00:00', '2018-01-01 06:00:00', '2018-01-01 07:00:00', '2018-01-01 08:00:00', '2018-01-01 09:00:00', ... '2018-01-07 15:00:00', '2018-01-07 16:00:00', '2018-01-07 17:00:00', '2018-01-07 18:00:00', '2018-01-07 19:00:00', '2018-01-07 20:00:00', '2018-01-07 21:00:00', '2018-01-07 22:00:00', '2018-01-07 23:00:00', '2018-01-08 00:00:00'], dtype='datetime64[ns]', length=169, freq='H')我们可以检查第一个元素的类型: type(date_rng[0])#returnspandas._libs.tslib.Timestamp让我们用时间戳数据的创建一个示例数据框,并查看前15个元素: df = pd.DataFrame(date_rng, columns=['date'])df['data'] = np.random.randint(0,100,size=(len(date_rng)))df.head(15) ...

May 10, 2019 · 2 min · jiezi

MySQL-InnoDB索引原理和算法

也许你经常用MySQL,也会经常用索引,但是对索引的原理和高级功能却并不知道,我们在这里一起学习下。 InnoDB存储索引在数据库中,如果索引太多,应用程序的性能可能会受到影响;如果索引太少,又会对查询性能产生影响。所以,我们要追求两者的一个平衡点,足够多的索引带来查询性能提高,又不因为索引过多导致修改数据等操作时负载过高。 InnoDB支持3种常见索引: 哈希索引B+ 树索引全文索引我们接下来要详细讲解的就是B+ 树索引和全文索引。 哈希索引InnoDB存储引擎支持的哈希索引是自适应的,会根据表的使用情况自动为表生成哈希索引,不能人为干预是否在一张表中生成哈希索引。这块内容我们先不展开说,后续补充。 B+ 树索引B+ 树索引是目前关系型数据库系统中查找最为常用和有效的索引,其构造类似于二叉树,根据键值对快速找到数据。B+ 树(balance+ tree)由B树(banlance tree 平衡二叉树)和索引顺序访问方法(ISAM: Index Sequence Access Method)演化而来,这几个都是经典的数据结构。而MyISAM引擎最初也是参考ISAM数据结构设计的。 基础数据结构想要了解B+ 树数据结构,我们先了解一些基础的知识。 二分查找法又称为折半查找法,指的是将数据顺序排列,通过每次和中间值比较,跳跃式查找,每次缩减一半的范围,快速找到目标的算法。其算法复杂度为log2(n),比顺序查找要快上一些。 如图所示,从有序列表中查找48,只需要3步: 详细的算法可以参考二分查找算法。 二叉查找树二叉查找树的定义是在一个二叉树中,左子树的值总是小于根键值,根键值总是小于右子树的值。在我们查找时,每次都从根开始查找,根据比较的结果来判断继续查找左子树还是右子树。其查找的方法非常类似于二分查找法。 平衡二叉树二叉查找树的定义非常宽泛,可以任意构造,但是在极端情况下查询的效率和顺序查找一样,如只有左子树的二叉查找树。 若想构造一个性能最大的二叉查找树,就需要该树是平衡的,即平衡二叉树(由于其发明者为G. M. Adelson-Velsky 和 Evgenii Landis,又被称为AVL树)。其定义为必须满足任何节点的两个子树的高度最大差为1的二叉查找树。平衡二叉树相对结构较优,而最好的性能需要建立一个最优二叉树,但由于维护该树代价高,因此一般平衡二叉树即可。 平衡二叉树查询速度很快,但在树发生变更时,需要通过一次或多次左旋和右旋来达到树新的平衡。这里不发散讲。 B+ 树了解了基础的数据结构后,我们来看下B+ 树的实现,其定义十分复杂,目标是为磁盘或其他直接存取辅助设备设计的一种平衡查找树。在该树中,所有的记录都按键值的大小放在同一层的叶子节点上,各叶子节点之间有指针进行连接(非连续存储),形成一个双向链表。索引节点按照平衡树的方式构造,并存在指针指向具体的叶子节点,进行快速查找。 下面的B+ 树为数据较少时,此时高度为2,每页固定存放4条记录,扇出固定为5(图上灰色部分)。叶子节点存放多条数据,是为了降低树的高度,进行快速查找。 当我们插入28、70、95 3条数据后,B+ 树由于数据满了,需要进行页的拆分。此时高度变为3,每页依然是4条记录,双向链表未画出但是依然是存在的,现在可以看出来是一个平衡二叉树的雏形了。 InnoDB的B+ 树索引InnoDB的B+ 树索引的特点是高扇出性,因此一般树的高度为2~4层,这样我们在查找一条记录时只用I/O 2~4次。当前机械硬盘每秒至少100次I/O/s,因此查询时间只需0.02~0.04s。 数据库中的B+ 树索引分为聚集索引(clustered index)和辅助索引(secondary index)。它们的区别是叶子节点存放的是否为一整行的完整数据。 聚集索引聚集索引就是按照每张表的主键(唯一)构造一棵B+ 树,同时叶子节点存放整行的完整数据,因此将叶子节点称为数据页。由于定义了数据的逻辑顺序,聚集索引也能快速的进行范围类型的查询。 聚集索引的叶子节点按照逻辑顺序连续存储,叶子节点内部物理上连续存储,作为最小单元,叶子节点间通过双向指针连接,物理存储上不连续,逻辑存储上连续。 聚集索引能够针对主键进行快速的排序查找和范围查找,由于是双向链表,因此在逆序查找时也非常快。 我们可以通过explain命令来分析MySQL数据库的执行计划: # 查看表的定义,可以看到id为主键,name为普通列mysql> show create table dimensionsConf;| Table | Create Table | dimensionsConf | CREATE TABLE `dimensionsConf` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) DEFAULT NULL, `remark` varchar(1024) NOT NULL, PRIMARY KEY (`id`), FULLTEXT KEY `fullindex_remark` (`remark`)) ENGINE=InnoDB AUTO_INCREMENT=178 DEFAULT CHARSET=utf8 |1 row in set (0.00 sec)# 先测试一个非主键的name属性排序并查找,可以看到没有使用到任何索引,且需要filesort(文件排序),这里的rows为输出行数的预估值mysql> explain select * from dimensionsConf order by name limit 10\G;*************************** 1. row *************************** id: 1 select_type: SIMPLE table: dimensionsConf type: ALLpossible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 57 Extra: Using filesort1 row in set (0.00 sec)# 再测试主键id的排序并查找,此时使用主键索引,在执行计划中没有了filesort操作,这就是聚集索引带来的优化mysql> explain select * from dimensionsConf order by id limit 10\G;*************************** 1. row *************************** id: 1 select_type: SIMPLE table: dimensionsConf type: indexpossible_keys: NULL key: PRIMARY key_len: 4 ref: NULL rows: 10 Extra: NULL1 row in set (0.00 sec)# 再查找根据主键id的范围查找,此时直接根据叶子节点的上层节点就可以快速得到范围,然后读取数据mysql> explain select * from dimensionsConf where id>10 and id<10000\G;*************************** 1. row *************************** id: 1 select_type: SIMPLE table: dimensionsConf type: rangepossible_keys: PRIMARY key: PRIMARY key_len: 4 ref: NULL rows: 56 Extra: Using where1 row in set (0.00 sec)辅助索引辅助索引又称非聚集索引,其叶子节点不包含行记录的全部数据,而是包含一个书签(bookmark),该书签指向对应行数据的聚集索引,告诉InnoDB存储引擎去哪里查找具体的行数据。辅助索引与聚集索引的关系就是结构相似、独立存在,但辅助索引查找非索引数据需要依赖于聚集索引来查找。 ...

May 10, 2019 · 3 min · jiezi

现代IM系统中的消息系统架构-模型篇

前言在架构篇中我们介绍了现代IM消息系统的架构,介绍了Timeline的抽象模型以及基于Timeline模型构建的一个支持『消息漫游』、『多端同步』和『消息检索』多种高级功能的消息系统的典型架构。架构篇中为了简化读者对Tablestore Timeline模型的理解,概要性的对Timeline的基本逻辑模型做了介绍,以及对消息系统中消息的多种同步模式、存储和索引的基本概念做了一个科普。 本篇文章是对架构篇的一个补充,会对Tablestore的Timeline模型做一个非常详尽的解读,让读者能够深入到实现层面了解Timeline的基本功能以及核心组件。最后我们还是会基于IM消息系统这个场景,来看如何基于Tablestore Timeline实现IM场景下消息同步、存储和索引等基本功能。 Timeline模型Timeline模型以『简单』为设计目标,核心模块构成比较清晰明了,主要包括: Store:Timeline存储库,类似数据库的表的概念。Identifier:用于区分Timeline的唯一标识。Meta:用于描述Timeline的元数据,元数据描述采用free-schema结构,可自由包含任意列。Queue:一个Timeline内所有Message存储在Queue内。Message:Timeline内传递的消息体,也是一个free-schema的结构,可自由包含任意列。Index:包含Meta Index和Message Index,可对Meta或Message内的任意列自定义索引,提供灵活的多条件组合查询和搜索。Timeline Store Timeline Store是Timeline的存储库,对应于数据库内表的概念。上图是Timeline Store的结构图,Store内会存储所有的Timeline数据。Timeline是一个面向海量消息的数据模型,同时用于消息存储库和同步库,需要满足多种要求: 支撑海量数据存储:对于消息存储库来说,如果需要消息永久存储,则随着时间的积累,数据规模会越来越大,需要存储库能应对长时间积累的海量消息数据存储,需要能达到PB级容量。低存储成本:消息数据的冷热区分是很明显的,大部分查询都会集中在热数据,所以对于冷数据需要有一个比较低成本的存储方式,否则随着时间的积累数据量不断膨胀,存储成本会非常大。数据生命周期管理:不管是对于消息数据的存储还是同步,数据都需要定义生命周期。存储库是用于在线存储消息数据本身,通常需要设定一个较长周期的保存时间。而同步库是用于写扩散模式的在线或离线推送,通常设定一个较短的保存时间。极高的写入吞吐:各类场景下的消息系统,除了类似微博、头条这种类型的Feeds流系统,像绝大部分即时通讯或朋友圈这类消息场景,通常是采用写扩散的消息同步模式,写扩散要求底层存储具备极高的写入吞吐能力,以应对消息洪峰。低延迟的读:消息系统通常是应用在在线场景,所以对于查询要求低延迟。Tablestore Timeline的底层是基于LSM存储引擎的分布式数据库,LSM的最大优势就是对写入非常友好,天然适合消息写扩散的模式。同时对查询也做了极大优化,例如热数据进缓存、bloom filter等等。数据表采用Range Partition的分区模式,能提供水平扩展的服务能力,以及能自动探测并处理热点分区的负载均衡策略。为了满足同步库和存储库对存储的不同要求,也提供了一些灵活的自定义配置,主要包括: Time to live(数据生命周期):可自定义数据生命周期,例如永久保存,或者保存N天。Storage type(存储类型):自定义存储类型,对存储库来说,HDD是最好的选择,对同步库来说,SSD是最好的选择。Timeline Module Timeline Store内能存储海量的Timeline,单个Timeline的详细结构图如上,可以看到Timeline主要包含了三大部分: Timeline Meta:元数据部分,用于描述Timeline,包括:Identifier:用于唯一标识Timeline,可包含多个字段。 Meta:用于描述Timeline的元数据,可包含任意个数任意类型的字段。Meta Index:元数据索引,可对元数据内任意属性列建索引,支持多字段条件组合查询和检索。Timeline Queue:用于存储和同步消息的队列,队列中元素由两部分组成:Sequence Id:顺序ID,队列中用于定位Message的位点信息,在队列中顺序ID保持递增。 Message:队列中承载消息的实体,包含了消息的完整内容。Timeline Data:Timeline的数据部分就是Message,Message主要包含:Message:消息实体,其内部也可以包含任意数量任意类型字段。 Message Index:消息数据索引,可对消息实体内任意列做索引,支持多字段条件组合查询和检索。IM消息系统建模 以一个简易版IM系统为例,来看如何基于Tablestore Timeline模型建模。按照上图中的例子,存在A、B、C三个用户,A与B发生单聊,A与C发生单聊,以及A、B、C组成一个群聊,来看下在这个场景下消息同步、存储以及读写流程分别如何基于Tablestore Timeline建模。 消息同步模型 消息同步选择写扩散模型,能完全利用Tablestore Timeline的优势,以及针对IM消息场景读多写少的特性,通过写扩散来平衡读写,均衡整个系统的资源。写扩散模型下,每个接收消息的个体均拥有一个收件箱,所有需要同步至该个体的消息需要投递到其收件箱内。图上例子中,A、B、C三个用户分别拥有收件箱,每个用户不同的设备端,均从同一个收件箱内拉取新消息。 消息同步库 收件箱存储在同步库内,同步库中每个收件箱对应一个Timeline。根据图上的例子,总共存在3个Timeline作为收件箱。每个消息接收端保存有本地最新拉取的消息的SequenceID,每次拉取新消息均是从该SequenceID开始拉取消息。对同步库的查询会比较频繁,通常是对最新消息的查询,所以要求热数据尽量缓存在内存中,能提供高并发低延迟的查询。所以对同步库的配置,一般是需要SSD存储。消息如果已经同步到了所有的终端,则代表收件箱内的该消息已经被消费完毕,理论上可以清理。但设计上来说不做主动清理,而是给数据定义一个较短的生命周期来自动过期,一般定义为一周或者两周。数据过期之后,如果仍要同步拉取新消息,则需要退化到读扩散的模式,从存储库中拉取消息。 消息存储库 消息存储库中保存有每个会话的消息,每个会话的发件箱对应一个Timeline。发件箱内的消息支持按会话维度拉取消息,例如浏览某个会话内的历史消息则通过读取发件箱完成。一般来说,新消息通过在线推送或者查询同步库可投递到各个接收端,所以对存储库的查询会相对来说较少。而存储库用于长期存储消息,例如永久存储,相对同步库来说数据量会较大。所以存储库的选择一般是HDD,数据生命周期根据消息需要保存的时间来定,通常是一个较长的时间。 消息索引库 消息索引库依附于存储库,使用了Timeline的Message Index,可以对存储库内的消息进行索引,例如对文本内容的全文索引、收件人、发件人以及发送时间的索引等,能支持全文检索等高级查询和搜索。 总结本篇文章主要对Tablestore Timeline模型进行了详解,介绍了Timeline各模块包括Store、Meta、Queue、Data和Index等,最后以一个简单的IM场景举例如何基于Timeline来建模。在下一篇实现篇中,会直接基于Tablestore Timeline来实现一个简易版的支持单聊、群聊、元数据管理以及消息检索的IM系统,敬请期待。 本文作者:木洛阅读原文 本文为云栖社区原创内容,未经允许不得转载。

May 8, 2019 · 1 min · jiezi

04-MySQL数据库索引

面试官:当一条查询执行较慢时通常可以如何进行优化我:加索引!面试官:那么到底什么是索引,其底层又是如何实现的呢我:懵逼! 索引的常见模型 索引的出现是为了提高查询效率,就像书的目录一样 常见的实现索引的模型有:哈希表、有序数组和搜索树哈希表:键 - 值(key - value)。哈希思路:把值放在数组里,用一个哈希函数把key换算成一个确定的位置,然后把value放在数组的这个位置哈希冲突的处理办法:链表哈希表适用场景:只有等值查询的场景有序数组:按顺序存储。查询用二分法就可以快速查询,时间复杂度是:O(log(N))有序数组查询效率高,更新效率低有序数组的适用场景:静态存储引擎。二叉搜索树:每个节点的左儿子小于父节点,父节点又小于右儿子二叉搜索树:查询时间复杂度O(log(N)),更新时间复杂度O(log(N))数据库存储大多不适用二叉树,因为树高过高,会适用N叉树 哈希表 是一种以键-值(key-value)存储数据的结构,我们只要输入待查找的值即key,就可以找到其对应的值即Value。哈希的思路很简单,把值放在数组里,用一个哈希函数把key换算成一个确定的位置,然后把value放在数组的这个位置。(与HashMap类似)优点:效率高缺点:因为不是有序的,所以哈希索引做区间查询的速度是很慢的。 你可以设想下,如果你现在要找某字段在[a, b]这个区间的数据,就必须全部扫描一遍了。 所以,哈希表这种结构适用于只有等值查询的场景,不适用于区间查询 有序数组 等值查询和范围查询场景中的性能就都非常优秀 搜索树模型又可以细分为二叉树红黑树B+树 索引的实现由存储引擎来决定,InnoDB索引的实现使用B+树模型二叉树和红黑树的搜索效率很高,但是应用在数据库中时因为数据量较大,二叉树和红黑树每次只分裂出两个分支,导致分裂层数很大,空间占用率高而B+树选择增加分支树,把整颗树的高度维持在很小的范围内,同时在内存里缓存前面若干层的节点,可以极大地降低访问磁盘的次数,提高读的效率。同时要注意的一点是:二叉树类数据结构效率高的前提是数据有序,这也是数据库常存在一个自增主键的原因 扩展:什么是B+树 B-树 即Balance-tree即B树 B+树 B+树索引并不能找到一个给定键值的具体行。B+树索引能找到的只是被查找数据行所在的页。然后数据库通过把页读入到内存,再在内存中进行查找,最后得到要在找的数据。因为页目录中的槽是按照主键顺序排列的,所以在每一个页目录中,通过二分查找,定位到数据行所在的页,然后将整个页读入内存 B*树 是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针 B树模型小结: B(B-)树:多路搜索树,每个结点存储M/2到M个关键字,非叶子结点存储指向关键字范围的子结点;所有关键字在整颗树中出现,且只出现一次,非叶子结点可以命中;B+树:在B-树基础上,为叶子结点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B+树总是到叶子结点才命中;B*树:在B+树基础上,为非叶子结点也增加链表指针,将结点的最低利用率从1/2提高到2/3;索引失效 1、如果条件中有or,即使其中有部分条件带索引也不会使用,除非条件中的列全部有索引。2、like查询是以%开头(但是以%结尾却不会失效)3、如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引。(例如where ID = 3 和 where ID = "3")4、如果mysql估计使用全表扫描要比使用索引快,则不使用索引。(因为server层有优化器)

May 6, 2019 · 1 min · jiezi

基于Tablestore管理海量快递轨迹数据架构实现

快递轨迹管理对于一个快递公司,在全国范围内有着大量的快递点、快递员、运输车辆以及仓储中心。而快递自产生后,就会在这些地点、人物之间流转。因而,一套完善的快递管理追踪系统是快递公司的重要管理工具;用户通过平台客户端下单后,产生唯一的快递单号作为唯一身份标识。快递除了订单号,还会有很多属性信息,如:邮寄人、邮寄人手机、邮寄人地址、收件人、快递类型等信息。生成快递订单后,用户的邮寄物品才会成为“快递”。快递公司配合扫码机器,将快递的流转事件、地点、时间等信息不定期推送至系统。快递流转信息不仅可以是简单的量化数据,也可以是描述性文字、地理位置等特殊信息。系统将流转信息记录成快递的监控数据,同时修改快递状态、实时位置等。直至快递送达收件人手中,结束快递生命周期。通过系统,用户可以管理自己的历史邮寄单列表、收件列表,掌握自己邮寄中的快递轨迹。快递公司也可以查询、修改快递信息、追踪快递时效,并借助海量轨迹监控数据,掌握快递产生、收件的高频路线,在高频位置铺设更多的基础设施、转移调度快递员;功能需求面向用户:1、用户在线下单生成快递单,等候快递员上门取件;2、管理历史订单列表,了解快递明细;3、追踪特定快递周转状态、运送轨迹;面向平台:1、借助扫码器,实现快递周转事件采集、存储;2、统计、查询所有快递订单,实现全订单的管理:CRUD;3、掌握所有邮寄中快递的实时位置;4、掌握任意一个订单的周转状态、运送轨迹;5、基于历史快递数据,分析快递时效;6、方便掌握高频地域、路线,为增设基础设施、快递员提供依据;等等…系统样例,如下所示:官网控制台地址:项目样例实现方案MySQL方案与难点通常,用户会选用MySQL作为方案数据库,因为MySQL作为数库在查询、分析等功能上有优势,用户创建两个表:订单表、事件追踪表实现对快递数据的存储。但是快递场景有几个强需求:第一、需要有强大的查询、统计能力,实现快递单的管理;第二、对于海量快递,有着高并发写入需求,对写入性能要求较高;第三、数据持续膨胀,但历史快递订单、事件数据多为冷数据,存储成本需要尽可能低;第四、数据未来挖掘潜在价值较高,需要有较好的计算生态;而MySQL方案在面对第二、第三个强需求时,劣势凸显,海量并发、不断的数据膨胀、存储成本高一直以来都是关系型数据库的痛点;表格存储方案选择表格存储有以下优势:其一、表格存储的多元索引(SearchIndex)功能轻松满足用户的多维查询、GEO检索、统计等功能需求;其二、基于LSM tree打造的分布式NoSQL数据库,轻松支持海量高并发读、写,零运维轻松应对数据量的不断膨胀,理论上无上限。其三、表格存储按量计费,提供容量型、高性能型两种实例,容量型对冷数据更适宜,提供了更低存储成本。其四、更重要的,表格存储拥有较为完善的计算生态,提供全、增量通道服务,提供流计算、批计算一体的计算体系,对未来监控数据价值挖掘提供渠道。表格存储在时序场景需求的技术点上拥有极高的匹配,而基于时序场景打造的Timestream模型更是将时序场景通用功能,封装成易用的场景接口,使用户更容易的基于表格存储,根据自身需求设计、打造不同特点的轨迹追踪系统;数据结构设计基于快递的时序,将快递的属性信息作为meta数据,而快递的周转路径、状态、位置等则为data数据,下面对两类数据做简单介绍。快递元数据meta数据管理着快递的属性信息,支持指标、标签、属性、地理位置、更新时间等参数,模型会为所有属性创建相应的索引,提供多维度条件组合查询(包含GEO查询)。其中Identifier是时间线的标识,包含两部分:name部分(监控指标标识)、tags部分(固有不可变参数集合)。在快递场景中,用户通常是基于快递单号直接定位快递,因而tags使用空的。而属性信息则存储快递的邮寄人信息、收件人信息、邮寄起/止地址等,location字段,用于最新位置追踪,可不定期根据产生新的状态周转数据时更新。快递轨迹数据data数据记录着快递的状态周转信息,主要为量化数据、地理位置、文字表述等任意类型。data数据按照+有序排列,因而同一快递的所有数据物理上存在一起,且基于时间有序。这种数据存储方式,极大的提升了时间线的查询效率。对应到快递轨迹,监控数据主要记录了:【who】do【something】@【where】with the location【geo】以及联系方式等。读写接口使用介绍写数据写接口根据数据类型分为两类:meta写入(新增快递)、data写入(快递周转数据)新增快递:当用户通过系统直接下快递单后,产生唯一订单号,加上用户填写的快递单信息组成必要的凯迪数据。此时,就会生成一个时间线,产生一个meta数据;快递周转:当快递发生取件、运输周转、派送、取件是,产生的状态转变数据时,就会产生一条追踪数据,通过data数据的写接口不定期的写入;读数据与写数据一样,针对两类数据提供了两类读接口:meta读取(快递查询)、data读取(查询快递轨迹)查询快递:根据快递号、寄件人手机、收件人手机等信息,获取对应快递的列表,掌握所有快递的最新动态;查询快递轨迹:基于单个meta的Identifier,获取该快递从产生到结束整个生命周期内的轨迹周转数据,可以通过列表、地图轨迹展示等方式,直观的了解快递的周转过程;方案核心代码SDK与样例代码SDK使用:时序模型Timestream模型集成于表格存储的SDK中,目前已在4.11.0版本中支持;<dependency> <groupId>com.aliyun.openservices</groupId> <artifactId>tablestore</artifactId> <version>4.11.0</version></dependency>代码开源:https://github.com/aliyun/tablestore-examples/tree/master/demos/MailManagement建表准备在创建完成实例后,用户需要通过时序模型的sdk创建相应的meta表(快递元数据)、data表(快递周转数据):private void init() { AsyncClient asyncClient = new AsyncClient(endpoint, accessKeyId, accessKeySecret, instance); //快递抽象Timestream TimestreamDBConfiguration mailConf = new TimestreamDBConfiguration(“metaTableName”); mailDb = new TimestreamDBClient(asyncClient, mailConf);}public void createTable() { mailDb.createMetaTable(Arrays.asList(//自定义索引 new AttributeIndexSchema(“fromMobile”, AttributeIndexSchema.Type.KEYWORD), new AttributeIndexSchema(“fromName”, AttributeIndexSchema.Type.KEYWORD), new AttributeIndexSchema(“toMobile”, AttributeIndexSchema.Type.KEYWORD), new AttributeIndexSchema(“toName”, AttributeIndexSchema.Type.KEYWORD), new AttributeIndexSchema(“toLocation”, AttributeIndexSchema.Type.GEO_POINT) )); mailDb.createDataTable(“dataTableName”);}写数据数据写入主要分两部分,meta表创建新快递、data表采集快递周转信息创建快递单(meta表写入)//metaWriter对应meta表,提供读、写接口TimestreamMetaTable mailMetaWriter = mailDb.metaTable();//identifier作为时间线的身份标识(unique),仅含:快递单号ID,TimestreamIdentifier identifier = new TimestreamIdentifier.Builder(“mail-id-001”) .build();//基于identifier创建meta对象,并为meta设置更多属性,Attributes为属性参数TimestreamMeta meta = new TimestreamMeta(identifier) .addAttribute(“fromName”, whos.get(Rand.nextInt(whos.size()))) .addAttribute(“fromMobile”, “15812345678”) .addAttribute(“toName”, whos.get(Rand.nextInt(whos.size()))) .addAttribute(“toMobile”, “15812345678”) .addAttribute(“toLocation”, “30,120”);//创建新的时间线,然后写入监控数据mailMetaWriter.put(meta);采集快递周转事件(data表写入)//dataWriter分别对应data表,提供读、写接口TimestreamDataTable mailDataWriter = mailDb.dataTable(“mailDataTableName”);TimestreamMeta meta;//meta上一步已经构建//创建新的时间线,然后写入监控数据mailDataWriter.write( meta.getIdentifier(), new Point.Builder(14500000000, TimeUnit.MILLISECONDS) .addField(“who”, “张三”) .addField(“do”, “取件”) .addField(“where”, “云栖小镇”) .addField(“location”, “30,120”) .build());数据读取数据读取分为两类:快递订单多维查询(meta表读取)//reader对应meta表,提供读、写接口,此处名字为突出读功能TimestreamMetaTable metaReader = mailDb.metaTable();//构建筛选条件Filter filter = AndFilter( Name.equal(“mail-id-001”), Attribute.equal(“fromMobile”, “15812345678”));Iterator<TimestreamMeta> metaIterator = mailDb.metaTable() .filter(filter) .fetchAll();while (iterator.hasNext()) { TimestreamMeta meta = iterator.next();//deal with metas}快递轨迹追踪(data表读取)//dataWriter分别对应data表,提供读、写接口TimestreamDataTable dataReader = db.dataTable(“dataTableName”);TimestreamMeta meta;//基于已获取的meta列表,分别获取每个快递的轨迹追踪Iterator<Point> dataIterator = mailDb.dataTable(mailDataTableName) .get(meta.getIdentifier()) .fetchAll();while (iterator.hasNext()) { Point point = iterator.next();//deal with points long timestamp = point.getTimestamp(TimeUnit.MILLISECONDS);//毫秒单位时间戳 String location = point.getField(“location”).asString();//获取该点String类型的位置信息}本文作者:潭潭阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

April 18, 2019 · 1 min · jiezi

阿里巴巴复杂搜索系统的可靠性优化之路

背景搜索引擎是电商平台成交链路的核心环节,搜索引擎的高可用直接影响成交效率。闲鱼搜索引擎作为闲鱼关键系统,复杂度和系统体量都非常高,再加上闲鱼所有导购场景都依靠搜索赋能,搜索服务的稳定可靠成为了闲鱼大部分业务场景可用能力的衡量标准;如何保障搜索服务的稳定和高可用成为了极大的挑战。闲鱼搜索作为闲鱼核心系统,有以下几个突出的特点:数据体量大:对接闲鱼数十亿的商品,引擎有效商品数亿;索引庞大:闲鱼非结构化商品需要与算法团队合作,预测抽取有价值的结构化信息,建立索引;已创建数百的索引字段,整个引擎索引数据量为T级别;增量消息多:日常增量消息QPS 数十万,峰值QPS可以达到 数百万;查询复杂:很多特殊业务场景,查询条件要求苛刻而复杂;比如召回GROUP分组统计,聚合/打散/去重,关键词复合运算查询等;实时性性要求高:闲鱼中都是二手商品,卖家商品的库存都是1;商品上下架频繁,对引擎数据的同步更新实时性要求非常高;智能化扩展:由于闲鱼商品非结构化特性,为保障召回数据的效果以及相关性;需要引擎具备智能插件扩展的能力,能与算法开发人员协同;鉴于闲鱼商品搜索引擎以上主要特点,本文详细介绍闲鱼搜索在系统高可用上做的各种努力,希望能给读者一些启发。闲鱼搜索整体架构正式引出搜索稳定性保障方案之前,我们需要对闲鱼搜索技术有一个简单大致的了解;我们比较过很多外部开源的搜索引擎,都不能完美支持背景中所列的需求点;闲鱼使用的是阿里巴巴最新研发的搜索引擎平台Ha3,Ha3是一款非常高效,智能强大的搜索引擎,它完全满足闲鱼搜索的要求;Elasticsearch是基于Lucene的准实时搜索引擎,也是比较常用的开源搜索引擎,但是其在算法扩展支撑/绝对实时的能力上与Ha3相差甚远;在同等硬件条件下,基于1200万数据做单机性能对比测试发现,Ha3比ElasticSearch开源系统的QPS高4倍,查询延迟低4倍;Elasticsearch在大规模数据量场景下的性能和稳定性与HA3相比尚有很大的差距。01闲鱼搜索体系运行流程下图是闲鱼搜索体系系统结构图,主要分在线和离线两个流程;索引构建流程索引构建即我们所谓的离线流程,其执行者BuildService①,负责将不同存储类型的纯文本商品数据构建成搜索引擎格式的索引文件。原始的商品数据有两类,一类是存放在存储上的全量商品数据,这个定期(一般以天为周期)通过DUMP②产出,另一类为实时变更的数据,在商品信息变更后,由业务系统即时同步给消息系统Swift③。最终分发给在线服务的Searcher④更新索引。搜索查询流程搜索查询即我们所谓的在线流程;闲鱼搜索服务应用A发起搜索请求,通过SP⑤进行服务能力编排;首先SP发起QP⑥算法服务调用,进行用户意图预测,并获取排序辅助信息;然后结合QP返回的结果加上业务系统的查询参数,向Ha3搜索引擎发起查询请求;Ha3搜索引擎QueryService⑦中Qrs⑧接收到查询请求后,分发给QueryService中的Searcher进行倒排索引召回、统计、条件过滤、文档打分及排序、摘要生成;最后Qrs将Searcher返回的结果进行整合后返回给SP,SP经过去重再返回给业务系统;02闲鱼搜索体系团队构成闲鱼搜索的运维体系,是一个相当复杂的构成;其中涉及很多团队的鼎力协作;首先必须有Ha3搜索引擎团队在底层提供核心的搜索引擎能力支持;主要负责Ha3搜索引擎核心能力的建设维护;提供并维护引擎运维操作平台和实时引擎搜索服务。然后是算法团队,在Ha3搜索引擎上进行定制,优化用户搜索体验;对闲鱼非结构化的商品通过算法模型进行理解,预测抽取出结构化信息,供搜索引擎商品索引使用;监控维护QP集群服务;开发并使用Ha3引擎排序插件,进行召回数据分桶实验,验证调优。最后是我们业务工程团队,串联整个搜索流程,监控维护整个搜索链路的可用性;主要维护搜索对接的数据,Ha3搜索引擎接入管理,进行SP搜索服务编排,制定合理的查询计划;以及闲鱼搜索统一在线查询服务的研发维护工作。本文亦是从业务工程团队的工作角度出发,阐述如何对复杂搜索业务系统进行稳定性的保障;稳定性治理01部署架构优化独立网关部署Ha3引擎通过SP提供基于HTTP协议的搜索服务API,对类似闲鱼这样复杂的搜索场景,每个闲鱼上层的业务如果都通过拼接SP HTTP接口参数的形式来使用搜索服务,所有上游业务都需要关心SP的拼接语法,会使开发成本剧增,而且如果由于特殊原因SP进行了语法调整或者不兼容升级,那么所有上层业务都需要修正逻辑,这样的设计不合理;为了让业务系统与搜索系统完全解耦,并且提高搜索服务的易用性,闲鱼搜索通过统一的业务搜索网关来提供简单一致的分布式服务,供闲鱼各上层搜索业务使用,并与SP对接,屏蔽掉SP对上游业务系统的穿透;最开始闲鱼搜索服务与其他很多不相关的业务场景共建在一个比较庞大的底层应用中;这种部署方式对稳定性要求很高的业务模块来说有非常大的安全隐患;1.各业务模块会相互影响;存在一定程度的代码耦合,同时还涉及机器资源的竞争,风险比较高;2.应用太过庞大,严重影响开发协作的效率和代码质量;于是将闲鱼搜索服务部署到独立的容器分组,新增应用A供闲鱼搜索服务专用,作为各业务使用搜索服务的独立网关,同时对接下游的SP搜索服务;保证服务是隔离和稳定的。前后部署图如下所示;多机房容灾部署在最初,闲鱼商品搜索服务对接的Ha3搜索引擎只部署在一个机房;当此机房出现比较严重的问题时,对上游业务影响非常大,甚至会引发故障;鉴于此,对闲鱼商品搜索引擎的在线离线集群进行双机房部署容灾;在详细展开之前,我们先大致理解下Ha3引擎DUMP流程的原理;如上图所示,Ha3引擎DUMP流程大致流程可以简单分为以下几步:准备源数据:评估业务需求,将需要接入引擎的数据准备好;一般业务数据大部分都是DB数据表,也有少数的ODPS⑨离线数据表;算法团队提供的数据绝大部分都是ODPS离线数据表;DUMP拉取数据:通过Ha3引擎团队提供的运维平台,可以将这些表的某些数据字段接入到创建好的搜索引擎中,后续DUMP执行的时候,Ha3离线引擎会拉取这些接入的表字段数据,形成一份引擎自用的镜像数据表,在这一步中,我们可以使用引擎团队提供的UDF工具,对数据进行清洗/过滤等处理;数据Merge:引擎将所有的镜像表数据,通过我们指定的主键进行Join;最终形成一份数据大宽表;供引擎创建索引使用;这一步数据Join后,还可以对最终的数据通过UDF进行进一步的清洗/过滤处理,验证通过的数据才会进入到大宽表;创建更新索引:Ha3离线引擎通过buildService,使用大宽表的数据,与事先我们在Ha3引擎运维平台指定好的索引Schame对齐,重新构建索引;以上流程可以通过Ha3引擎运维平台手动触发执行,执行完上述流程后,会生成一份新的索引;新的索引集群服务可用后,在线实时模块会将查询服务切换到新的索引集群上,完成一次索引的更新;这个完整流程我们将其称之为"全量";全量完成后,当系统有新的商品信息变动,且相应的数据表有启用实时更新(我们称之为增量功能,DB表是通过binlog/ODPS表是通过Swift消息通知的方式实现),则离线DUMP引擎会感知到此次变动,进而将相应的镜像数据表中商品数据更新,并会按上述离线DUMP流程中的步骤,将这个改动信息一直向引擎上层投递,直至成功更新引擎索引中的相应数据,或者中途被系统规则丢弃为止;这个实时数据更新的流程我们称之为"增量";增量更新还有一条通道:算法同学可以使用特殊的方式,通过Swift增量消息的方式直接将需要更新的数据不通过DUMP流程,直接更新到Ha3引擎索引中。闲鱼商品量飞速增长,目前已经达到数十亿;接入了数百的索引字段,由于闲鱼商品非结构化的原因,索引字段中只有一小部分供业务使用;另外大部分都是算法接入的索引,比如大量抽出来的标签数据,向量化数据等,这些向量化数据非常大;最终的情形表现为闲鱼商品搜索引擎的DUMP处理逻辑比较复杂,而且索引数据总量异常庞大,增量消息量也处在非常高的水位,再加上闲鱼商品单库存的现状;因此对数据更新的实时性要求非常高,这些都给稳定性带来了极大的制约。索引数据是搜索引擎的内容核心,如果进入引擎的索引数据有问题,或者新变更的数据没有更新到引擎索引中,将直接影响搜索服务的质量;搜索引擎单机房部署期间,时常会因为一些不稳定的因素,导致DUMP全量失败,或者增量延迟,甚至停止;一旦引擎DUMP出现问题,需要恢复基本都很困难,很多场景下甚至需要重新跑全量才能解决问题;但是闲鱼商品索引数据体量较大,做一次全量往往要大半天,没有办法快速止血,对业务造成了较大的影响;于是对搜索引擎进行双机房部署容灾(M/N机房),互为备份;两个离线DUMP机房采用相同的引擎配置和相同的数据源,产出相同的索引数据,分别供两个在线机房使用,两个机房的在线流量比例也可以按需实时调整;当M机房出现不可逆问题时,自动或手动将流量全部切换到N机房,实现线上快速止血,然后再按部就班排查解决M机房的问题。下图为最终的搜索机房部署情况;进行引擎双机房部署虽然增大了机器资源成本,但是除了上述业务容灾优点外,还有以下好处;引擎需求的发布,之前缺乏有效的灰度流程;当搜索引擎有重大变更/升级,出现高风险的发布时,可以先在单机房小流量beta测试,数据对比验证通过后,再发布到另一个机房;平常单机房能支撑全部搜索查询业务的流量,当遇到大促或大型活动时,将两个机房同时挂载提供服务,这样搜索服务能力和容量直接能翻倍;避免了单机房频繁扩缩容的困扰;性能评估时,可以单独对未承载流量的机房进行压测,即使由于压测导致宕机也不会对线上业务造成影响;02流量隔离上文独立网关部署一节中讲到,闲鱼搜索通过统一的业务搜索网关来提供简单一致的分布式服务,供闲鱼各上层搜索业务使用;使用统一的微服务,就必然带来上游不同业务优先级和可靠性保障的问题。闲鱼搜索服务支撑了种类繁多的上游业务,为了统一对各业务场景的流量/服务质量进行度量和管理,在上游业务接入闲鱼搜索服务时,需要申请使用相应的业务来源,这个业务来源标示会伴随着整个搜索查询的生命周期;在日志采集时直接使用,从而可以针对业务维度进行监控告警,实时感知业务运行的健康情况(简单监控视图如下图),也可以对具体业务进行流量管控,降级限流等;搜索业务来源生命周期图03分级监控体系对高稳定性系统,当出现问题,或者即将产生问题时,能即时感知,显得尤为重要;方便实时进行跟踪处理,防止继续扩大;目前使用的主要手段是建立健全完善的多维度监控告警体系;引擎基础服务监控使用监控可以快速发现问题,如果监控的粒度够细还能进行问题的快速定位;不过有时也会存在误报或者漏报的情况,因此真实的监控一定要结合每个业务自身系统的特性,梳理出关键链路,针对关键链路进行多维度360度无死角监控,并且进行合理的预警规则设置,监控预警才会比较有效;闲鱼搜索引擎在线离线流程/各上游重要应用系统的核心链路上,建立了完备的日志数据采集模块,对关键指标进行了精准的监控预警设置;做到任何问题都能及时被感知到。下图是搜索服务相应核心日志以及监控告警情况。模拟用户行为的在线业务监控上文提到,闲鱼搜索引擎索引体量比较大,需要很多团队共同协作,搜索流程复杂度比较高;而且有算法同学的加入,对闲鱼非结构化的商品做了很多AI识别,加上闲鱼都是单库存商品,对引擎实时性要求非常高;前面已经做了一些容灾的保障方案;但是对实时性的感知上还需要更进一步,才能及时知道数据的准确情况,是否存在更新延迟,以及延迟时间大概是多久等一系列健康度信息;解法是从业务层面进行实时性的监控告警;提取出闲鱼商品量比较大更新也比较频繁的类目K,在闲鱼的后台业务系统中,通过jkeins间隔一定时间(时间步长可以实时调整),使用类目K作为关键词和品类,根据商品更新时间索引降序招回,模拟用户轮询的方式发送搜索查询请求,召回满足要求的第一页商品;然后根据引擎召回数据的商品更新时间与当前系统时间进行差值比对,大于阈值时长(可以实时调整)说明存在较严重的数据更新延迟,则进行告警信息发送;04压测全链路压测对搜索服务以及各上游业务系统进行全链路压测改造;并使用线上真实的用户请求构造大批量的压测数据,在保证不影响线上业务正常进行的前提下,验证链路在超大流量模型下系统的容量和资源分配是否合理,找到链路中的性能瓶颈点,验证网络设备和集群容量。引擎单链路压测Ha3搜索引擎在线流程,支持通过回放线上高峰时段查询流量的方式,进行引擎在线服务性能压测。Ha3搜索引擎离线流程,支持通过回放一段时间Swift增量消息的方式,进行引擎DUMP增量性能压测。05灰度发布闲鱼商品的非结构化特性,离不开算法赋能;在我们的研发周期中,与两个算法团队,相当多的算法同学保持着深度合作;给闲鱼搜索带来了跨越式的发展,但是在团队协作和研发效率上也给我们带来了极大的挑战。算法团队、引擎团队、加上业务工程团队,非常大的搜索项目开发小组,每周都有非常多的新算法模型,新的引擎改造,新的业务模块需要上线。大量的新增逻辑改动直接上线,会带来很多问题;首先是代码层面,虽然预发环境有做充分测试,但也难保没有边缘逻辑存在测试遗漏的情况;即使预发测试都完全覆盖,但线上和预发终究环境不同,线上大流量环境及有可能会暴露一些隐藏的代码问题;第二方面,假使代码没有任何质量问题,但所有功能全部绑定上线,所有逻辑都混杂在一起,如何评定某个模块上线后的效果成为极大的困扰,特别是算法模型的优化,和业务上新模式的尝试,都需要根据详细的效果反馈数据指标来指导进行下一步的优化方向;因此急需一套灰度实验保障体系,不仅可以用来协调和隔离整个搜索业务中各个模块,做到对各模块进行单独的效果评估;并且还能提高大家的协作效率,让各模块能进行快速试错,快速迭代;为了解决以上非常重要的问题,业务工程团队开发了一套实验管理系统,用来进行搜索实验灰度调度管理,系统功能如上图所示;其具有以下特点。实验灵活方便,一个实验可以包含多个实验组件,一个实验组件可供多个实验使用;一个实验组件又可以包含多个实验分桶;各页面模块的实验都可以在系统中实时调控,包括实验的开/关;以及实验之间的关系处理;搜索实验埋点全链路打通,统计各种实验数据报表;统计数据接入了闲鱼门户和通天塔,可查看各个指标不同分桶的实验曲线;提升实验迭代速度,提升算法/业务效率,快速试错,加速搜索成交转化的增长;06应急预案根据评估分析或经验,对搜索服务中潜在的或可能发生的突发事件的关键点,事先制定好应急处置方案;当满足一定的条件时进行多维度多层级的自动降级限流,或者配置手动预案进行人工干预;任何时候发现线上问题,首先需要快速止血,避免问题的扩大;具有自动预案会自动发现问题,自动熔断,我们需要密切关注系统的运行情况,防止反弹;若出现反弹,并且对业务有较大影响时,快速人工介入执行降级预案;完成止血后再详细排查具体原因,当短时间无法确定问题根源时,如在问题出现时有过变更或发布,则第一时间回滚变更或发布。对系统中各级的依赖服务,熔断降级已经系统负载保护,我们使用的是阿里巴巴自主研发的资源调用控制组件Sentinel[4],目前已经开源;或者也可以使用Hytrix降级限流工具;07问题排查将闲鱼搜索链路接入阿里搜索问题排查平台,搜索实时查询请求的各个步骤输入的参数信息/产出的数据信息都会在此工具平台详细展示,方便各种问题的排查跟进,以及数据信息对比;可以对各查询条件下各个分桶的实验召回数据进行可视化显示,方便各个实验间的效果对比;以及每个召回商品的各类细节信息查看,包括业务数据和算法标签数据,还包含每个商品对应的各引擎插件算分情况,都能详细阅览;还可以根据商品Id,卖家Id,卖家Nick进行商品索引信息的披露;可以排查相应商品在引擎索引中的详细数据,如果数据和预想的有出入,具体是离线DUMP哪一步的处理逻辑导致的状态异常,都能一键查询。接入此问题排查平台后,能非常直观的掌握引擎的运行状况,搜索召回的链路状态;对快速发现问题根源,即时修复问题都有非常重大的作用!总结与展望本文主要介绍闲鱼如何保障复杂场景下搜索引擎服务的稳定性,主要从架构部署,隔离性,容量评估,风险感知&管控等方面进行阐述,介绍了如何稳定支撑20+线上搜索业务场景,做到了快速发现恢复线上问题,高效提前预知规避风险案例50+,极大程度提升了搜索服务的用户体验,保障了闲鱼搜索全年无故障;经过上述治理方案后,闲鱼搜索系统稳定性得到了极大的保障,同时我们也会继续深耕,在搜索能力的高可用、更易用上更进一步,让上游业务更加顺滑;希望给各位读者带来一些思考和启发。本文作者:元茂阅读原文本文来自云栖社区合作伙伴“阿里技术”,如需转载请联系原作者。

March 18, 2019 · 1 min · jiezi

PHP面试MySQL数据库的索引

你好,是我琉忆,PHP程序员面试笔试系列图书的作者。本周(2019.3.4至3.8)的一三五更新的文章如下:周一:PHP面试MySQL数据库的基础知识周三:PHP面试MySQL数据库的索引周五:PHP面试MySQL数据库的面试真题自己整理了一篇“索引有哪些优缺点和使用原则?”的文章,关注公众号:“琉忆编程库”,回复:“索引”,我发给你。以下内容部分来自《PHP程序员面试笔试宝典》如需转载请注明出处。一、什么是索引?索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。它主要提供指向存储在表的指定列中的数据值的指针,然后根据指定的排序顺序对这些指针排序。数据库使用索引以找到特定值,然后顺指针找到包含该值的行。这样可以使对应于表的SQL语句执行得更快,可快速访问数据库表中的特定信息。索引的特点如下:①可以提高数据库的检索速度;②降低了数据库插入、修改、删除等维护任务的速度;③可以直接或间接创建;④只能创建在表上,不能创建在视图上;⑤使用查询处理器执行SQL语句时,一个表上,一次只能使用一个索引;⑥可以在优化隐藏中使用索引。索引的分类和使用如下:1.直接创建索引和间接创建索引直接创建索引:CREATE INDEX mycolumn_index ON mytable (myclumn)。间接创建索引:定义主键约束或者唯一性键约束,可以间接创建索引。2.普通索引和唯一性索引普通索引:CREATE INDEX mycolumn_index ON mytable (myclumn)。唯一性索引:保证在索引列中的全部数据是唯一的,对聚簇索引和非聚簇索引都可以使用。CREATE UNIQUE COUSTERED INDEX myclumn_cindex ON mytable(mycolumn)3.单个索引和复合索引单个索引:即非复合索引。复合索引:又称为组合索引,在索引建立语句中同时包含多个字段名,最多16个字段。CREATE INDEX name_index ON username(firstname,lastname)4.聚簇索引和非聚簇索引(聚集索引,群集索引)聚簇索引:物理索引,与基表的物理顺序相同,数据值的顺序总是按照顺序排列。CREATE CLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn) WITHALLOW_DUP_ROW(允许有重复记录的聚簇索引)非聚簇索引:CREATE UNCLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn)。二、索引的原理索引的目的在于提高查询效率,与我们查阅图书所用的目录是一个道理:先定位到章,然后定位到该章下的一个小节,然后找到页数。相似的例子还有:查字典,查火车车次,飞机航班等本质都是:通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据。数据库也是一样,但显然要复杂的多,因为不仅面临着等值查询,还有范围查询(>、<、between、in)、模糊查询(like)、并集查询(or)等等。数据库应该选择怎么样的方式来应对所有的问题呢?我们回想字典的例子,能不能把数据分成段,然后分段查询呢?最简单的如果1000条数据,1到100分成第一段,101到200分成第二段,201到300分成第三段……这样查第250条数据,只要找第三段就可以了,一下子去除了90%的无效数据。但如果是1千万的记录呢,分成几段比较好?稍有算法基础的同学会想到搜索树,其平均复杂度是lgN,具有不错的查询性能。但这里我们忽略了一个关键的问题,复杂度模型是基于每次相同的操作成本来考虑的。而数据库实现比较复杂,一方面数据是保存在磁盘上的,另外一方面为了提高性能,每次又可以把部分数据读入内存来计算,因为我们知道访问磁盘的成本大概是访问内存的十万倍左右,所以简单的搜索树难以满足复杂的应用场景。自己整理了一篇“索引有哪些优缺点和使用原则?”的文章,关注公众号:“琉忆编程库”,回复:“索引”,我发给你。三、索引的数据结构任何一种数据结构都不是凭空产生的,一定会有它的背景和使用场景,我们现在总结一下,我们需要这种数据结构能够做些什么,其实很简单,那就是:每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。那么我们就想到如果一个高度可控的多路搜索树是否能满足需求呢?就这样,b+树应运而生。如上图,是一颗b+树,关于b+树的定义可以参见B+树,这里只说一些重点,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。b+树的查找过程如图所示,如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。b+树性质1.索引字段要尽量的小:通过上面的分析,我们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于1时将会退化成线性表。2.索引的最左匹配特性(即从左往右匹配):当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。预告:本周五(3.8)将更新PHP面试MySQL数据库的面试题,敬请期待。以上内容摘自《PHP程序员面试笔试宝典》书籍,目前本书没有电子版,可到各大电商平台购买纸质版。更多PHP相关的面试知识、考题可以关注公众号获取:琉忆编程库对本文有什么问题或建议都可以进行留言,我将不断完善追求极致,感谢你们的支持。

March 6, 2019 · 1 min · jiezi

mysql innodb索引原理

聚集索引(clustered index)innodb存储引擎表是索引组织表,表中数据按照主键顺序存放。其聚集索引就是按照每张表的主键顺序构造一颗B+树,其叶子结点中存放的就是整张表的行记录数据,这些叶子节点成为数据页。聚集索引的存储并不是物理上连续的,而是逻辑上连续的,叶子结点间按照主键顺序排序,通过双向链表连接。多数情况下,查询优化器倾向于采用聚集索引,因为聚集索引能在叶子结点直接找到数据,并且因为定义了数据的逻辑顺序,能特别快的访问针对范围值的查询。聚集索引的这个特性决定了索引组织表中的数据也是索引的一部分。由于表里的数据只能按照一颗B+树排序,因此一张表只能有一个聚簇索引。在Innodb中,聚簇索引默认就是主键索引。如果没有主键,则按照下列规则来建聚簇索引:没有主键时,会用一个非空并且唯一的索引列做为主键,成为此表的聚簇索引;如果没有这样的索引,InnoDB会隐式定义一个主键来作为聚簇索引。由于主键使用了聚簇索引,如果主键是自增id,那么对应的数据也会相邻地存放在磁盘上,写入性能较高。如果是uuid等字符串形式,频繁的插入会使innodb频繁地移动磁盘块,写入性能就比较低了。B+树(多路平衡查找树)我们知道了innodb引擎索引使用了B+树结构,那么为什么不是其他类型树结构,例如二叉树呢?计算机在存储数据的时候,有最小存储单元,这就好比人民币流通最小单位是分一样。文件系统的最小单元是块,一个块的大小是4k(这个值根据系统不同并且可设置),InnoDB存储引擎也有自己的最小储存单元—页(Page),一个页的大小是16K(这个值也是可设置的)。文件系统中一个文件大小只有1个字节,但不得不占磁盘上4KB的空间。同理,innodb的所有数据文件的大小始终都是16384(16k)的整数倍。所以在MySQL中,存放索引的一个块节点占16k,mysql每次IO操作会利用系统的预读能力一次加载16K。这样,如果这一个节点只放1个索引值是非常浪费的,因为一次IO只能获取一个索引值,所以不能使用二叉树。B+树是多路查找树,一个节点能放n个值,n = 16K / 每个索引值的大小。例如索引字段大小1Kb,这时候每个节点能放的索引值理论上是16个,这种情况下,二叉树一次IO只能加载一个索引值,而B+树则能加载16个。B+树的路数为n+1,n是每个节点存在的值数量,例如每个节点存放16个值,那么这棵树就是17路。从这里也能看出,B+树节点可存储多个值,所以B+树索引并不能找到一个给定键值的具体行。B+树只能找到存放数据行的具体页,然后把页读入到内存中,再在内存中查找指定的数据。附:B树和B+树的区别在于,B+树的非叶子结点只包含导航信息,不包含实际的值,所有的叶子结点和相连的节点使用链表相连,便于区间查找和遍历。辅助索引也称为非聚集索引,其叶子节点不包含行记录的全部数据,叶子结点除了包含键值以外,每个叶子结点中的索引行还包含一个书签,该书签就是相应行的聚集索引键。如下图可以表示辅助索引和聚集索引的关系(图片源自网络,看大概意思即可):当通过辅助索引来寻找数据时,innodb存储引擎会通过辅助索引叶子节点获得只想主键索引的主键,既然后再通过主键索引找到完整的行记录。例如在一棵高度为3的辅助索引树中查找数据,那需要对这颗辅助索引树进行3次IO找到指定主键,如果聚集索引树的高度同样为3,那么还需要对聚集索引树进行3次查找,最终找到一个完整的行数据所在的页,因此一共需要6次IO访问来得到最终的数据页。创建的索引,如联合索引、唯一索引等,都属于非聚簇索引。联合索引联合索引是指对表上的多个列进行索引。联合索引也是一颗B+树,不同的是联合索引的键值数量不是1,而是大于等于2。例如有user表,字段为id,age,name,现发现如下两条sql使用频率最多:Select * from user where age = ? ;Select * from user where age = ? and name = ?;这时候不需要为age和name单独建两个索引,只需要建如下一个联合索引即可:create index idx_age_name on user(age, name)联合索引的另一个好处已经对第二个键值进行了排序处理,有时候可以避免多一次的排序操作。覆盖索引覆盖索引,即从辅助索引中就可以得到查询所需要的所有字段值,而不需要查询聚集索引中的记录。覆盖索引的好处是辅助索引不包含整行记录的所有信息,故其大小要远小于聚集索引,因此可以减少大量的IO操作。例如上面有联合索引(age,name),如果如下:select age,name from user where age=?就能使用覆盖索引了。覆盖索引的另一个好处是对于统计问题,例如:select count(*) from userinnodb存储引擎并不会选择通过查询聚集索引来进行统计。由于user表上还有辅助索引,而辅助索引远小于聚集索引,选择辅助索引可以减少IO操作。注意事项索引只建合适的,不建多余的因为每当增删数据时,B+树都要进行调整,如果建立多个索引,多个B+树都要进行调整,而树越多、结构越庞大,这个调整越是耗时耗资源。如果减少了这些不必要的索引,磁盘的使用率可能会大大降低。索引列的数据长度能少则少。索引数据长度越小,每个块中存储的索引数量越多,一次IO获取的值更多。匹配列前缀可用到索引 like 9999%,like %9999%、like %9999用不到索引;Where 条件中in和or可以使用索引, not in 和 <>操作无法使用索引;如果是not in或<>,面对B+树,引擎根本不知道应该从哪个节点入手。匹配范围值,order by 也可用到索引;多用指定列查询,只返回自己想到的数据列,少用select ;不需要查询无用字段,并且不使用可能还会命中覆盖索引哦;联合索引中如果不是按照索引最左列开始查找,无法使用索引;最左匹配原则;联合索引中精确匹配最左前列并范围匹配另外一列可以用到索引;联合索引中如果查询中有某个列的范围查询,则其右边的所有列都无法使用索本文作者:信~仰阅读原文本文为云栖社区原创内容,未经允许不得转载。

March 4, 2019 · 1 min · jiezi

MySQL8.0.14 - 新特性 - InnoDB Parallel Read简述

最近的MySQL8.0.14版本增加了其第一个并行查询特性,可以支持在聚集索引上做SELECT COUNT()和check table操作。本文简单的介绍下这个特性。用法增加了一个session级别参数: innodb_parallel_read_threads要执行并行查询,需要满足如下条件(ref: row_scan_index_for_mysql)无锁查询聚集索引不是Insert…select需要参数设置为>1相关代码入口函数:row_scan_index_for_mysql parallel_select_count_star // for select count() parallel_check_table // for check tableInnoDB里实现了两种查询方式,一种是基于key的(key reader), 根据叶子节点上的值做分区,需要判断可见性;另外一种是基于page的(physical read),根据page no来做分区,无需判断可见性。目前支持的两种查询都是key reader的方式。使用如下代码创建一个reader,并调用接口函数,read()函数里的回调函数包含了如何对获取到的行数据进行处理:Key_reader reader(prebuilt->table, trx, index, prebuilt, n_threads);reader.read(func), 其中func是回调函数,用于告诉线程怎么处理得到的每一行分区并计算线程数分区入口:template <typename T, typename R>typename Reader<T, R>::Ranges Reader<T, R>::partition()流程:搜集btree的最左节点page no从root page开始向下,尝试构建子树:如果该level的page个数不足线程数,继续往下走否则,使用该level, 搜集该level的每个page的最左记录向下直到叶子节点的最左链表如上搜集到的是多条代表自上而下的page no数组,需要根据这些数组创建分区range,这里有两种创建方式:Key_reader::Ranges Key_reader::create_ranges: 基于键值创建分区找到每个链表的叶子节点的第一条记录,存储其cursor作为当前range的起点和上一个range的终点Phy_reader::Ranges Phy_reader::create_ranges:基于物理页创建分区找到每个链表的叶子节点,相邻链表的叶子节点组成一个range线程数取分区数和配置线程数的最小值启动线程启动线程各自扫描: start_parallel_load为每个分区创建context(class Reader::Ctx),加入到队列中实现了一个Lock-free的队列模型,多线程可以并发的从队列中取context: 实现细节在文件include/ut0mpmcbq.h中,对应类 class mpmc_bq, 实现思路见链接线程函数:dberr_t Reader<T, R>::worker(size_t id, Queue &ctxq, Function &f)每取一个分区,调用处理函数去遍历分区:Key_reader::traverse对于获得的每条记录,判断其可见性(共享事务对象trx_t),调用回调函数处理记录(在Key_reader::read()作为参数传递),对于select count(), 就是累加记录的计数器Phy_reader::traverse读取每条非标记删除的记录并调用回调函数处理,无需判断可见性对于异常情况,只返回最后一个context的错误码。该特性只是MySQL在并行查询的第一步,甚至定义了一些接口还没有使用,例如接口函数pread_adapter_scan_get_num_threads, 估计是给未来server层做并行查询使用的。代码里对应两个适配类:Parallel_reader_adapterParallel_partition_reader_adapter另外一个可以用到的地方是创建二级索引,我们知道InnoDB创建二级索引,是先从聚集索引读取记录,生成多个merge file,然后再做归并排序,但无论是生成merge file,还是排序,都可以做到并行化。官方也提到这是未来的一个优化点,相信不久的将来,我们就能看到MySQL更为强大的并行查询功能。ReferenceWL#11720: InnoDB: Parallel read of indexMySQL 8.0.14: A Road to Parallel Query Execution is Wide Open!本文作者:zhaiwx_yinfeng.阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 28, 2019 · 1 min · jiezi

深入解读MySQL8.0 新特性 :Crash Safe DDL

前言在MySQL8.0之前的版本中,由于架构的原因,mysql在server层使用统一的frm文件来存储表元数据信息,这个信息能够被不同的存储引擎识别。而实际上innodb本身也存储有元数据信息。这给ddl带来了一定的挑战,因为这种架构无法做到ddl的原子化,我们在线上经常能够看到数据目录下遗留的临时文件,或者类似server层和innodb层列个数不一致之类的错误。甚至某些ddl可能还遗留元数据在innodb内,而丢失了frm,导致无法重建表…..(我们为了解决这个问题,实现了一个叫drop table force的功能,去强制做清理….)(以下所有的讨论都假定使用InnoDB存储引擎)到了8.0版本,我们知道所有的元数据已经统一用InnoDB来进行管理,这就给实现原子ddl带来了可能,几乎所有的对innodb表,存储过程,触发器,视图或者UDF的操作,都能做到原子化:- 元数据修改,binlog以及innodb的操作都放在一个事务中- 增加了一个内部隐藏的系统表mysql.innodb_ddl_log,ddl操作被记录到这个表中,注意对该表的操作产生的redo会fsync到磁盘上,而不会考虑innodb_flush_log_at_trx_commit的配置。当崩溃重启时,会根据事务是否提交来决定通过这张表的记录去回滚或者执行ddl操作- 增加了一个post-ddl的阶段,这也是ddl的最后一个阶段,会去:1. 真正的物理删除或重命名文件; 2. 删除innodb_ddl_log中的记录项; 3.对于一些ddl操作还会去更新其动态元数据信息(存储在mysql.innodb_dynamic_metadata,例如corrupt flag, auto_inc值等)- 一个正常运行的ddl结束后,其ddl log也应该被清理,如果这中间崩溃了,重启时会去尝试重放:1.如果已经走到最后一个ddl阶段的(commit之后),就replay ddl log,把ddl完成掉;2. 如果处于某个中间态,则回滚ddl由于引入了atomic ddl, 有些ddl操作的行为也发生了变化:- DROP TABLE: 在之前的版本中,一个drop table语句中如果要删多个表,比如t1,t2, t2不存在时,t1会被删除。但在8.0中,t1和t2都不会被删除,而是抛出错误。因此要注意5.7->8.0的复制问题 (DROP VIEW, CREATE USER也有类似的问题)- DROP DATABASE: 修改元数据和ddl_log先提交事务,而真正的物理删除数据文件放在最后,因此如果在删除文件时崩溃,重启时会根据ddl_log继续执行drop database测试:MySQL很贴心的加了一个选项innodb_print_ddl_logs,打开后我们可以从错误日志看到对应的ddl log,下面我们通过这个来看下一些典型ddl的过程root@(none) 11:12:19>SET GLOBAL innodb_print_ddl_logs = 1; Query OK, 0 rows affected (0.00 sec)root@(none) 11:12:22>SET GLOBAL log_error_verbosity = 3; Query OK, 0 rows affected (0.00 sec)CREATE DATABASEmysql> CREATE DATABASE test;Query OK, 1 row affected (0.02 sec)创建数据库语句没有写log_ddl,可能觉得这不是高频操作,如果创建database的过程中失败了,重启后可能需要手动删除目录。CREATE TABLEmysql> USE test;Database changedmysql> CREATE TABLE t1 (a INT PRIMARY KEY, b INT);Query OK, 0 rows affected (0.06 sec)[InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=428, thread_id=7, space_id=76, old_file_path=./test/t1.ibd][InnoDB] DDL log delete : by id 428[InnoDB] DDL log insert : [DDL record: REMOVE CACHE, id=429, thread_id=7, table_id=1102, new_file_path=test/t1][InnoDB] DDL log delete : by id 429[InnoDB] DDL log insert : [DDL record: FREE, id=430, thread_id=7, space_id=76, index_id=190, page_no=4][InnoDB] DDL log delete : by id 430[InnoDB] DDL log post ddl : begin for thread id : 7InnoDB] DDL log post ddl : end for thread id : 7从日志来看有三类操作,实际上描述了如果操作失败需要进行的三项逆向操作:删除数据文件,释放内存中的数据词典信息,删除索引btree。在创建表之前,这些数据被写入到ddl_log中,在创建完表并commit后,再从ddl log中删除这些记录。另外上述日志中还有DDL log delete日志,其实在每次写入ddl log时是单独事务提交的,但在提交之后,会使用当前事务执行一条delete操作,直到操作结束了才会提交。加列(instant)mysql> ALTER TABLE t1 ADD COLUMN c INT;Query OK, 0 rows affected (0.08 sec)Records: 0 Duplicates: 0 Warnings: 0[InnoDB] DDL log post ddl : begin for thread id : 7[InnoDB] DDL log post ddl : end for thread id : 7注意这里执行的是Instant ddl, 这是8.0.13新支持的特性,加列操作可以只修改元数据,因此从ddl log中无需记录数据删列mysql> ALTER TABLE t1 DROP COLUMN c;Query OK, 0 rows affected (2.77 sec)Records: 0 Duplicates: 0 Warnings: 0[InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=487, thread_id=7, space_id=83, old_file_path=./test/#sql-ib1108-1917598001.ibd][InnoDB] DDL log delete : by id 487[InnoDB] DDL log insert : [DDL record: REMOVE CACHE, id=488, thread_id=7, table_id=1109, new_file_path=test/#sql-ib1108-1917598001][InnoDB] DDL log delete : by id 488[InnoDB] DDL log insert : [DDL record: FREE, id=489, thread_id=7, space_id=83, index_id=200, page_no=4][InnoDB] DDL log delete : by id 489[InnoDB] DDL log insert : [DDL record: DROP, id=490, thread_id=7, table_id=1108][InnoDB] DDL log insert : [DDL record: RENAME SPACE, id=491, thread_id=7, space_id=82, old_file_path=./test/#sql-ib1109-1917598002.ibd, new_file_path=./test/t1.ibd][InnoDB] DDL log delete : by id 491[InnoDB] DDL log insert : [DDL record: RENAME TABLE, id=492, thread_id=7, table_id=1108, old_file_path=test/#sql-ib1109-1917598002, new_file_path=test/t1][InnoDB] DDL log delete : by id 492[InnoDB] DDL log insert : [DDL record: RENAME SPACE, id=493, thread_id=7, space_id=83, old_file_path=./test/t1.ibd, new_file_path=./test/#sql-ib1108-1917598001.ibd][InnoDB] DDL log delete : by id 493[InnoDB] DDL log insert : [DDL record: RENAME TABLE, id=494, thread_id=7, table_id=1109, old_file_path=test/t1, new_file_path=test/#sql-ib1108-1917598001][InnoDB] DDL log delete : by id 494[InnoDB] DDL log insert : [DDL record: DROP, id=495, thread_id=7, table_id=1108][InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=496, thread_id=7, space_id=82, old_file_path=./test/#sql-ib1109-1917598002.ibd][InnoDB] DDL log post ddl : begin for thread id : 7[InnoDB] DDL log replay : [DDL record: DELETE SPACE, id=496, thread_id=7, space_id=82, old_file_path=./test/#sql-ib1109-1917598002.ibd][InnoDB] DDL log replay : [DDL record: DROP, id=495, thread_id=7, table_id=1108][InnoDB] DDL log replay : [DDL record: DROP, id=490, thread_id=7, table_id=1108][InnoDB] DDL log post ddl : end for thread id : 7这是个典型的三阶段ddl的过程:分为prepare, perform 以及commit三个阶段:Prepare: 这个阶段会修改元数据,创建临时ibd文件#sql-ib1108-1917598001.ibd, 如果发生异常崩溃,我们需要能把这个临时文件删除掉, 因此和create table类似,也为这个idb写了三条日志:delete space, remove cache,以及free btreePerform: 执行操作,将数据拷贝到上述ibd文件中,(同时处理online dmllog), 这部分不涉及log ddl操作Commit: 更新数据词典信息并提交事务, 这里会写几条日志:DROP : table_id=1108RENAME SPACE: #sql-ib1109-1917598002.ibd文件被rename成t1.ibdRENAME TABLE: #sql-ib1109-1917598002被rename成t1RENAME SPACE: t1.ibd 被rename成#sql-ib1108-1917598001.ibdRENAME TABLE: t1表被rename成#sql-ib1108-1917598001DROP TABLE: table_id=1108DELETE SPACE: 删除#sql-ib1109-1917598002.ibd实际上这一步写的ddl log描述了commit阶段操作的逆向过程:将t1.ibd rename成#sql-ib1109-1917598002, 并将sql-ib1108-1917598001 rename成t1表,最后删除旧表。其中删除旧表的操作这里不执行,而是到post-ddl阶段执行Post-ddl: 在事务提交后,执行最后的操作:replay ddl log, 删除旧文件,清理mysql.innodb_dynamic_metadata中相关信息DELETE SPACE: #sql-ib1109-1917598002.ibdDROP: table_id=1108DROP: table_id=1108加索引mysql> ALTER TABLE t1 ADD KEY(b);Query OK, 0 rows affected (0.14 sec)Records: 0 Duplicates: 0 Warnings: 0[InnoDB] DDL log insert : [DDL record: FREE, id=431, thread_id=7, space_id=76, index_id=191, page_no=5][InnoDB] DDL log delete : by id 431[InnoDB] DDL log post ddl : begin for thread id : 7[InnoDB] DDL log post ddl : end for thread id : 7创建索引采用inplace创建的方式,没有临时文件,但如果异常发生的话,依然需要在发生异常时清理临时索引, 因此增加了一条FREE log,用于异常发生时能够删除临时索引.TRUNCATE TABLEmysql> TRUNCATE TABLE t1;Query OK, 0 rows affected (0.13 sec)[InnoDB] DDL log insert : [DDL record: RENAME SPACE, id=439, thread_id=7, space_id=77, old_file_path=./test/#sql-ib1103-1917597994.ibd, new_file_path=./test/t1.ibd][InnoDB] DDL log delete : by id 439[InnoDB] DDL log insert : [DDL record: DROP, id=440, thread_id=7, table_id=1103][InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=441, thread_id=7, space_id=77, old_file_path=./test/#sql-ib1103-1917597994.ibd][InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=442, thread_id=7, space_id=78, old_file_path=./test/t1.ibd][InnoDB] DDL log delete : by id 442[InnoDB] DDL log insert : [DDL record: REMOVE CACHE, id=443, thread_id=7, table_id=1104, new_file_path=test/t1][InnoDB] DDL log delete : by id 443[InnoDB] DDL log insert : [DDL record: FREE, id=444, thread_id=7, space_id=78, index_id=194, page_no=4][InnoDB] DDL log delete : by id 444[InnoDB] DDL log insert : [DDL record: FREE, id=445, thread_id=7, space_id=78, index_id=195, page_no=5][InnoDB] DDL log delete : by id 445[InnoDB] DDL log post ddl : begin for thread id : 7[InnoDB] DDL log replay : [DDL record: DELETE SPACE, id=441, thread_id=7, space_id=77, old_file_path=./test/#sql-ib1103-1917597994.ibd][InnoDB] DDL log replay : [DDL record: DROP, id=440, thread_id=7, table_id=1103][InnoDB] DDL log post ddl : end for thread id : 7Truncate table是个比较有意思的话题,在早期5.6及之前的版本中, 是通过删除旧表创建新表的方式来进行的,5.7之后为了保证原子性,改成了原地truncate文件,同时增加了一个truncate log文件,如果在truncate过程中崩溃,可以通过这个文件在崩溃恢复时重新truncate。到了8.0版本,又恢复成了删除旧表,创建新表的方式,与之前不同的是,8.0版本在崩溃时可以回滚到旧数据,而不是再次执行。以上述为例,主要包括几个步骤:将表t1.ibd rename成#sql-ib1103-1917597994.ibd创建新文件t1.ibdpost-ddl: 将老文件#sql-ib1103-1917597994.ibd删除RENAME TABLEmysql> RENAME TABLE t1 TO t2;Query OK, 0 rows affected (0.06 sec)DDL LOG:[InnoDB] DDL log insert : [DDL record: RENAME SPACE, id=450, thread_id=7, space_id=78, old_file_path=./test/t2.ibd, new_file_path=./test/t1.ibd][InnoDB] DDL log delete : by id 450[InnoDB] DDL log insert : [DDL record: RENAME TABLE, id=451, thread_id=7, table_id=1104, old_file_path=test/t2, new_file_path=test/t1][InnoDB] DDL log delete : by id 451[InnoDB] DDL log post ddl : begin for thread id : 7[InnoDB] DDL log post ddl : end for thread id : 7这个就比较简单了,只需要记录rename space 和rename table的逆操作即可. post-ddl不需要做实际的操作DROP TABLEDROP TABLE t2[InnoDB] DDL log insert : [DDL record: DROP, id=595, thread_id=7, table_id=1119][InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=596, thread_id=7, space_id=93, old_file_path=./test/t2.ibd][InnoDB] DDL log post ddl : begin for thread id : 7[InnoDB] DDL log replay : [DDL record: DELETE SPACE, id=596, thread_id=7, space_id=93, old_file_path=./test/t2.ibd][InnoDB] DDL log replay : [DDL record: DROP, id=595, thread_id=7, table_id=1119][InnoDB] DDL log post ddl : end for thread id : 7先在ddl log中记录下需要删除的数据,再提交后,再最后post-ddl阶段执行真正的删除表对象和文件操作代码实现:主要实现代码集中在文件storage/innobase/log/log0ddl.cc中,包含了向log_ddl表中插入记录以及replay的逻辑。隐藏的innodb_log_ddl表结构如下 def->add_field(0, “id”, “id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT”); def->add_field(1, “thread_id”, “thread_id BIGINT UNSIGNED NOT NULL”); def->add_field(2, “type”, “type INT UNSIGNED NOT NULL”); def->add_field(3, “space_id”, “space_id INT UNSIGNED”); def->add_field(4, “page_no”, “page_no INT UNSIGNED”); def->add_field(5, “index_id”, “index_id BIGINT UNSIGNED”); def->add_field(6, “table_id”, “table_id BIGINT UNSIGNED”); def->add_field(7, “old_file_path”, “old_file_path VARCHAR(512) COLLATE UTF8_BIN”); def->add_field(8, “new_file_path”, “new_file_path VARCHAR(512) COLLATE UTF8_BIN”); def->add_index(0, “index_pk”, “PRIMARY KEY(id)”); def->add_index(1, “index_k_thread_id”, “KEY(thread_id)”);记录类型根据不同的操作类型,可以分为如下几类:FREE_TREE_LOG目的是释放索引btree,入口函数log_DDL::write_free_tree_log,在创建索引和删除表时会调用到对于drop table中涉及的删索引操作,log ddl的插入操作放到父事务中,一起要么提交要么回滚对于创建索引的case, log ddl就需要单独提交,父事务将记录标记删除,这样后面如果ddl回滚了,也能将残留的index删掉。DELETE_SPACE_LOG入口函数:Log_DDL::write_delete_space_log用于记录删除tablespace操作,同样分为两种情况:drop table/tablespace, 写入的记录随父事务一起提交,并在post-ddl阶段replay创建tablespace, 写入的记录单独提交,并被父事务标记删除,如果父事务回滚,就通过replay删除参与的tablespaceRENAME_SPACE_LOG入口函数:Log_DDL::write_rename_space_log用于记录rename操作,例如如果我们把表t1 rename成t2,在其中就记录了逆向操作t2 rename to t1.在函数Fil_shard::space_rename()中,总是先写ddl log, 再做真正的rename操作. 写日志的过程同样是独立事务提交,父事务做未提交的删除操作DROP_LOG入口函数: Log_DDL::write_drop_log用于记录删除表对象操作,这里不涉及文件层操作,写ddl log在父事务中执行RENAME_TABLE_LOG入口函数: Log_DDL::write_rename_table_log用于记录rename table对象的逆操作,和rename space类似,也是独立事务提交ddl log, 父事务标记删除REMOVE_CACHE_LOG入口函数: Log_DDL::write_remove_cache_log用于处理内存表对象的清理,独立事务提交,父事务标记删除ALTER_ENCRYPT_TABLESPACE_LOG入口函数: Log_DDL::write_alter_encrypt_space_log用于记录对tablespace加密属性的修改,独立事务提交. 在写完ddl log后修改tablespace page0 中的加密标记综上,在ddl的过程中可能会提交多次事务,大概分为三类:独立事务写ddl log并提交,父事务标记删除, 如果父事务提交了,ddl log也被顺便删除了,如果父事务回滚了,那就要根据ddl log做逆操作来回滚ddl独立事务写ddl log 并提交, (目前只有ALTER_ENCRYPT_TABLESPACE_LOG)使用父事务写ddl log,在ddl结束时提交。需要在post-ddl阶段处理post_ddl如上所述,有些ddl log是随着父事务一起提交的,有些则在post-ddl阶段再执行, post_ddl发生在父事提交或回滚之后: 若事务回滚,根据ddl log做逆操作,若事务提交,在post-ddl阶段做最后真正不可逆操作(例如删除文件)入口函数: Log_DDL::post_ddl –>Log_DDL::replay_by_thread_id根据执行ddl的线程thread id通过innodb_log_ddl表上的二级索引,找到log id,再到聚集索引上找到其对应的记录项,然后再replay这些操作,完成ddl后,清理对应记录崩溃恢复在崩溃恢复结束后,会调用ha_post_recover接口函数,进而调用innodb内的函数Log_DDL::recover(), 同样的replay其中的记录,并在结束后删除记录。但ALTER_ENCRYPT_TABLESPACE_LOG类型并不是在这一步删除,而是加入到一个数组ts_encrypt_ddl_records中,在之后调用resume_alter_encrypt_tablespace来恢复操作,参考文档1. 官方文档2. wl#9536: support crash safe ddl本文作者:zhaiwx_yinfeng阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 26, 2019 · 5 min · jiezi

20分钟数据库索引设计实战

在后端开发的工作中如何轻松、高效地设计大量数据库索引呢?通过下面这四步,20分钟后你就再也不会为数据库的索引设计而发愁了。顺畅地阅读这篇文章需要了解数据库索引的组织方式,如果你还不熟悉的话,可以通过另一篇文章来快速了解一下——数据库索引融会贯通。这篇文章是一系列数据库索引文章中的第三篇,这个系列包括了下面四篇文章:数据库索引是什么?新华字典来帮你 —— 理解数据库索引融会贯通 —— 深入20分钟数据库索引设计实战 —— 实战数据库索引为什么用B+树实现? —— 扩展这一系列涵盖了数据库索引从理论到实践的一系列知识,一站式解决了从理解到融会贯通的全过程,相信每一篇文章都可以给你带来更深入的体验。1. 整理查询条件我们设计索引的目的主要是为了加快查询,所以,设计索引的第一步是整理需要用到的查询条件,也就是我们会在where子句、join连接条件中使用的字段。一般来说会整理程序中除了insert语句之外的所有SQL语句,按不同的表分别整理出每张表上的查询条件。也可以根据对业务的理解添加一些暂时还没有使用到的查询条件。对索引的设计一般会逐表进行,所以按数据表收集查询条件可以方便后面步骤的执行。2. 分析字段的可选择性整理出所有查询条件之后,我们需要分析出每个字段的可选择性,那么什么是可选择性呢?字段的可选择性指的就是字段的值的区分度,例如一张表中保存了用户的手机号、性别、姓名、年龄这几个字段,且一个手机号只能注册一个用户。在这种情况下,像手机号这种唯一的字段就是可选择性最高的一种情况;而年龄虽然有几十种可能,但是区分度就没有手机号那么大了;性别这样的字段则只有几种可能,所以可选择性最差。所以俺可选择性从高到低排列就是:手机号 > 年龄 > 性别。但是不同字段的值分布是不同的,有一些值的数量是大致均匀的,例如性别为男和女的值数量可能就差别不大,但是像年龄超过100岁这样的记录就非常少了。所以对于年龄这个字段,20-30这样的值就是可选择性很小的,因为每一个年龄都有非常多的记录;但是像100这样的值,那它的可选择性就非常高了。如果我们在表中添加了一个字段表示用户是否是管理员,那么在查询网站的管理员信息列表时,这个字段的可选择性就非常高。但是如果我们要查询的是非管理员信息列表时,这个字段的可选择性就非常低了。从经验上来说,我们会把可选择性高的字段放到前面,可选择性低的字段放在后面,如果可选择性非常低,一般不会把这样的字段放到索引里。3. 合并查询条件虽然索引可以加快查询的效率,但是索引越多就会导致插入和更新数据的成本变高,因为索引是分开存储的,所有数据的插入和更新操作都要对相关的索引进行修改。所以设计索引时还需要控制索引的数量,不能盲目地增加索引。一般我们会根据最左匹配原则来合并查询条件,尽可能让不同的查询条件使用同一个索引。例如有两个查询条件where a = 1 and b = 1和where b = 1,那么我们就可以创建一个索引idx_eg(b, a)来同时服务两个查询条件。同时,因为范围条件会终止使用索引中后续的字段,所以对于使用范围条件查询的字段我们也会尽可能放在索引的后面。4. 考虑是否需要使用全覆盖索引最后,我们会考虑是否需要使用全覆盖索引,因为全覆盖索引没有回表的开销,效率会更高。所以一般我们会在回表成本特别高的情况下考虑是否使用全覆盖索引,例如根据索引字段筛选后的结果需要返回其他字段或者使用其他字段做进一步筛选的情况。例如,我们有一张用户表,其中有年龄、姓名、手机号三个字段。我们需要查询在指定年龄的所有用户的姓名,已有索引idx_age_name(年龄, 姓名),目前我们使用下面这样的查询语句进行查询:SELECT *FROM 用户表WHERE 年龄 = ?;一般情况下,将一个索引优化为全覆盖索引有两种方式:增加索引中的字段,让索引字段覆盖SQL语句中使用的所有字段在这个例子中,我们可以创建一个同时包含所有字段的索引idx_all(年龄, 姓名, 手机号),以此提高查询的效率。减少SQL语句中使用的字段,使SQL需要的字段都包含在现有索引中在这个例子中,其实更好的方法是将SELECT子句修改为SELECT 姓名,因为我们的需求只是查询用户的姓名,并不需要手机号字段,去掉SELECT子句多余的字段不仅能够满足我们的需求,而且也不用对索引做修改。

February 14, 2019 · 1 min · jiezi

数据库索引为什么用B+树实现?

为什么大多数数据库索引都使用B+树来实现呢?这涉及到数据结构、操作系统、计算机存储层次结构等等复杂的理论知识,但是不用担心,这篇文章20分钟之后就会给你答案。这篇文章是一系列数据库索引文章中的最后一篇,这个系列包括了下面四篇文章:数据库索引是什么?新华字典来帮你 —— 理解数据库索引融会贯通 —— 深入20分钟数据库索引设计实战 —— 实战数据库索引为什么用B+树实现? —— 扩展这一系列涵盖了数据库索引从理论到实践的一系列知识,一站式解决了从理解到融会贯通的全过程,相信每一篇文章都可以给你带来更深入的体验。为什么使用B+树?大家在数学课上一定听说过一个例子,在一堆已经排好序的数字当中找出一个特定的数字的最好办法是一种叫“二分查找”的方式。具体的过程就是先找到这些数字中间的那一个数,然后比较目标数字是大于还是小于这个数;然后根据结果继续在前一半或者后一半数字中继续查找。这就类似于数据结构中的二叉树,二叉树就是如下的一种结构,树中的每个节点至多可以有两个子节点,而B+树每个节点则可以有N个子节点。这里就不具体展开讲解二叉树了,我们只需要知道,平衡的二叉树是内存中查询效率最高的一种数据结构就可以了。但是目前常用的数据库中,绝大多数的索引都是使用B+树实现的。那么为什么明明是二叉树查询效率最高,数据库中却偏偏要使用B+树而不是二叉树来实现索引呢?计算机存储层次结构计算机中的存储结构分为好几个部分,从上到下大致可以分为寄存器、高速缓存、主存储器、辅助存储器。其中主存储器,也就是我们常说的内存;辅助存储器也被称为外存,比较常见的就是磁盘、SSD,可以用来保存文件。在这个存储结构中,每一级存储的速度都比上一级慢很多,所以程序访问越上层存储中的数据,速度就会越快。有过编程经验的小伙伴都知道,程序运行过程中操作的基本都是内存,对外存中数据的访问往往需要写一些文件的读取和写入代码才能实现。这正是因为CPU的计算速度比存储的I/O速度(输入/输出速度)快很多所做的优化,因为CPU在每次计算完成之后就需要等待下一批的数据进入,这个等待的时间越短,计算机运行得越快。所以对于数据库索引来说,因为数据量很大,所以基本都是保存在外存中的,这样的话数据库读取一个索引节点的成本就非常大了。在数据量一样大的情况下,我们可以知道,B+树的单个节点中包含的值个数越多那么树中需要的节点总数就会越少,这样查询一次数据需要访问的节点数就更少了。如果你对B+树还不熟悉,可以到这篇文章中找到答案——数据库索引融会贯通 。如果我们把二叉树看做是特殊的B+树(每个节点只有一个值和前后两个指针的B+树),那么就可以得出结论:因为B+树的节点中包含的值个数(多个值)比二叉树(1个值)更多,所以在B+树中查询所需要的节点数就更少。那么如果每次读取的成本是一样的话,因为总成本=读取次数*单次读取成本,我们就可以证明B+树的查询成本就比二叉树小得多了。节点读取成本但是我们知道,读取更多数据肯定会需要更大的成本,那么为什么数据库索引使用B+树还是会比二叉树更好呢?这就需要一些更高深的操作系统知识来解释了。在现代的操作系统中,把数据从外存读到内存所使用的单位一般被称为“页”,每次读取数据都需要读入整数个的“页”,而不能读入半页或者0.8页。一页的大小由操作系统决定,常见的页大小一般为4KB=4096字节。所以不管我们是要读取1字节还是2KB,最后都是需要读入一个完整的4KB大小的页的,那么一个节点的读取成本就取决于需要读入的页数。在这样的情况下,如果一个节点的大小小于一页的大小,那么就会有一部分时间花在读取我们根本不需要的数据上(节点之外的数据),二叉树在这方面就会浪费很多时间;而如果一个节点的大小大于一页,哪怕是一页的整数倍,那我们也可能在一个节点的中间就找到了我们需要的指针进入了下一级的节点,这样这个指针后面的数据都白白读取了,如果不需要这些数据可能我们就可以少读几页了。所以,综上所述,数据库索引使用节点大小恰好等于操作系统一页大小的B+树来实现是效率最高的选择。

February 14, 2019 · 1 min · jiezi

数据库索引融会贯通

索引的各种规则纷繁复杂,不了解索引的组织形式就没办法真正地理解数据库索引。通过本文,你可以深入地理解数据库索引在数据库中究竟是如何组织的,从此以后索引的规则对于你将变得清清楚楚、明明白白,再也不需要死记硬背。顺畅地阅读这篇文章需要了解索引、联合索引、聚集索引分别都是什么,如果你还不了解,可以通过另一篇文章来轻松理解——数据库索引是什么?新华字典来帮你。这篇文章是一系列数据库索引文章中的第二篇,这个系列包括了下面四篇文章:数据库索引是什么?新华字典来帮你 —— 理解数据库索引融会贯通 —— 深入20分钟数据库索引设计实战 —— 实战数据库索引为什么用B+树实现? —— 扩展这一系列涵盖了数据库索引从理论到实践的一系列知识,一站式解决了从理解到融会贯通的全过程,相信每一篇文章都可以给你带来更深入的体验。索引的组织形式通过之前的内容,我们已经对数据库索引有了相当程度的抽象了解,那么在数据库中,索引实际是以什么样的形式进行组织的呢?同一张表上的多个索引又是怎样分工合作的呢?目前绝大多数情况下使用的数据库索引都是使用B+树实现的,下面就以MySQL的InnoDB为例,介绍一下数据库索引的具体实现。聚集索引下面是一个以B+树形式组织的拼音索引,在B+树中,每一个节点里都有N个按顺序排列的值,且每个值的中间和节点的头尾都有指向下一级节点的指针。在查找过程中,按顺序从头到尾遍历一个节点中的值,当发现要找的目标值恰好在一个指针的前一个值之前、后一个值之后时,就通过这个指针进入下一级节点。当最后到达叶子节点,也就是最下层的节点时,就能够找到自己希望查找的数据记录了。在上图中如果希望找到险字,那么我们首先通过拼音首字母在根节点上按顺序查找到了X和Y之间的指针,然后通过这个指针进入了第二级节点···, xia, xian, xiang, ···。之后在该节点上找到了xian和xiang之间的指针,这样就定位到了第519页开始的一个目标数据块,其中就包含了我们想要找到的险字。因为拼音索引是聚集索引,所以我们在叶子节点上直接就找到了我们想找的数据。非聚集索引下面是一个模拟部首索引的组织形式。我们由根节点逐级往下查询,但是在最后的叶子节点上并没有找到我们想找的数据,那么在使用这个索引时我们是如何得到最终的结果的呢?回忆之前字典中“检字表”的内容,我们可以看到,在每个字边上都有一个页码,这就相当于下面这一个索引中叶子节点上险字与院字中间的指针,这个指针会告诉我们真正的数据在什么地方。下图中,我们把非聚集索引(部首索引)和聚集索引(拼音索引)合在一起就能看出非聚集索引最后到底如何查找到实际数据了。非聚集索引叶子节点上的指针会直接指向聚集索引的叶子节点,因为根据聚集索引的定义,所有数据都是按聚集索引组织存储的,所以所有实际数据都保存在聚集索引的叶子节点中。而从非聚集索引的叶子节点链接到聚集索引的叶子节点查询实际数据的过程就叫做——回表。全覆盖索引那么如果我们只是想要验证险字的偏旁是否是双耳旁“阝”呢?这种情况下,我们只要在部首索引中阝下游的叶子节点中找到了险字就足够了。这种在索引中就获取到了SQL语句中需要的所有字段,所以不需要再回表查询的情况中,这个索引就被称为这个SQL语句的全覆盖索引。在实际的数据库中,非聚集索引的叶子节点上保存的“指针”就是聚集索引中所有字段的值,要获取一条实际数据,就需要通过这几个聚集索引字段的值重新在聚集索引上执行一遍查询操作。如果数据量不多,这个开销是非常小的;但如果非聚集索引的查询结果中包含了大量数据,那么就会导致回表的开销非常大,甚至超过不走索引的成本。所以全覆盖索引可以节约回表的开销这一点在一些回表开销很大的情况下就非常重要了。范围查询条件上图是一个联合索引idx_eg(col_a, col_b)的结构,如果我们希望查询一条满足条件col_a = 64 and col_b = 128的记录,那么我们可以一路确定地往下找到唯一的下级节点最终找到实际数据。这种情况下,索引上的col_a和col_b两个字段都能被使用。但是如果我们将查询条件改为范围查询col_a > 63 and col_b = 128,那么我们就会需要查找所有符合条件col_a > 63的下级节点指针,最后不得不遍历非常多的节点及其子节点。这样的话对于索引来说就得不偿失了,所以在这种情况下,数据库会选择直接遍历所有满足条件col_a > 63的记录,而不再使用索引上剩下的col_b字段。数据库会从第一条满足col_a > 63的记录开始,横向遍历之后的所有记录,从里面排除掉所有不满足col_b = 128的记录。这就是范围条件会终止使用联合索引上的后续字段的原因。

February 14, 2019 · 1 min · jiezi

数据库索引是什么?新华字典来帮你

学过服务器端开发的朋友一定知道,程序没有数据库索引也可以运行。但是所有学习数据库的资料、教程,一定会有大量的篇幅在介绍数据库索引,各种后端开发工作的面试也一定绕不开索引,甚至可以说数据库索引是从后端初级开发跨越到高级开发的屠龙宝刀,那么索引到底在服务端程序中起着怎样的作用呢?这篇文章是一系列数据库索引文章中的第一篇,这个系列包括了下面四篇文章:数据库索引是什么?新华字典来帮你 —— 理解数据库索引融会贯通 —— 深入20分钟数据库索引设计实战 —— 实战数据库索引为什么用B+树实现? —— 扩展这一系列涵盖了数据库索引从理论到实践的一系列知识,一站式解决了从理解到融会贯通的全过程,相信每一篇文章都可以给你带来更深入的体验。什么是数据库索引?用一句话来描述:数据库索引就是一种加快海量数据查询的关键技术。现在还不理解这句话?不要紧,往下看,20分钟以后你就能自己做出这样的总结来了。首先给大家看一张图片这本书大家一定都很熟悉,小学入门第一课一定就是教小朋友们学习如何使用这本书。那这和我们的数据库索引有啥关系呢?别着急,我们翻开第一页看看。请大家注意右上角的那一排文字,原来目录就是传说中的索引呀!从前面的“一句话描述”我们可以知道,索引的目的就是为了加快数据查询。那么我们查字典时翻的第一个地方是哪里呢,我相信大部分人都会先翻到拼音目录,毕竟现在很多人都是提笔忘字了????。数据库索引的作用和拼音目录是一样的,就是最快速的锁定目标数据所在的位置范围。比如我们在这里要查险这个字,那么我们找到了Xx部分之后就能按顺序找到xian这个拼音所在的页码,根据前后的页码我们可以知道这个字一定是在519页到523页之间的,范围一下子就缩小到只有4页了。这相比我们从头翻到尾可是快多了,这时候就出现了第一个专业术语——全表扫描,也就是我们说的从头找到尾了。果然,我们在第521页找到了我们要找的“险”字。那么现在我们就知道数据库索引大概是一个什么东西了:数据库索引是一个类似于目录这样的用来加快数据查询的技术。什么是联合索引?相信大家都见过一些包含多个字段的数据库索引,比如INDEX idx_test(col_a, col_b)。这种包含多个字段的索引就被称为“联合索引”。那么在多个字段上建索引能起到什么样的作用呢?下面还是以新华字典为例,来看看到底什么是联合索引。新华字典里还有一种目录被称为“部首目录”,下面可以看到,要使用这个目录我们首先会根据部首的笔画数找到对应该能的部分,然后可以在里面找到我们想找的部首。比如如果我们还是要找险字所在的位置:找到部首后,右边的页码还不是险字真正的页码,我们还需要根据右边的页码找到对应部首在检字表中的位置。找到第93页的检字表后我们就可以根据险字余下的笔画数(7画)在“6-8画”这一部分里找到险字真正的页码了。在这个过程中,我们按顺序使用了“两个目录”,一个叫做“部首目录”,一个叫做“检字表”。并且我们可以看到上图中检字表的内容都是按部首分门别类组织的。这两个部分合在一起就是我们在本节讨论的主题——联合索引。即通过第一个字段的值(部首)在第一级索引中找到对应的第二级索引位置(检字表页码),然后在第二级索引中根据第二个字段的值(笔画)找到符合条件的数据所在的位置(险字的真正页码)。最左前缀匹配从前面使用部首目录的例子中可以看出,如果我们不知道一个字的部首是什么的话,那基本是没办法使用这个目录的。这说明仅仅通过笔画数(第二个字段)是没办法使用部首目录的。这就引申出了联合索引的一个规则:联合索引中的字段,只有某个字段(笔画)左边的所有字段(部首)都被使用了,才能使用该字段上的索引。例如,有索引INDEX idx_i1(col_a, col_b),如果查询条件为where col_b = 1,则无法使用索引idx_i1。但是如果我们知道部首但是不知道笔画数,比如不知道“横折竖弯勾”是算一笔还是两笔,那我们仍然可以使用“部首目录”部分的内容,只是要把“检字表”对应部首里的所有字都看一遍就能找到我们要找的字了。这就引申出了联合索引的另一个规则:联合索引中的字段,即使某个字段(部首)右边的其他字段(笔画)没有被使用,该字段之前(含)的所有字段仍然可以正常使用索引。例如,有索引INDEX idx_i2(col_a, col_b, col_c),则查询条件where col_a = 1 and col_b = 2在字段col_a和col_b上仍然可以走索引。但是,如果我们在确定部首后,不知道一个字到底是两画还是三画,这种情况下我们只需要在对应部首的两画和三画部分中找就可以了,也就是说我们仍然使用了检字表中的内容。所以,使用范围条件查询时也是可以使用索引的。最后,我们可以完整地表述一下最左前缀匹配原则的含义:对于一个联合索引,如果有一个SQL查询语句需要执行,则只有从索引最左边的第一个字段开始到SQL语句查询条件中不包含的字段(不含)或范围条件字段(含)为止的部分才会使用索引进行加速。这里出现了一个之前没有提到的点,就是范围条件字段也会结束对索引上后续字段的使用,这是为什么呢?具体原因的解释涉及到了更深层次的知识,在接下来的第二篇文章的最后就可以找到答案。什么是聚集索引?从上文的部首目录和拼音目录同时存在但是实际的字典内容只有一份这一点上可以看出,在数据库中一张表上是可以有多个索引的。那么不同的索引之间有什么区别呢?我们在新华字典的侧面可以看到一个V字形的一个个黑色小方块,有很多人都会在侧面写上A, B, C, D这样对应的拼音字母。因为字典中所有的字都是按照拼音顺序排列的,有时候直接使用首字母翻开对应的部分查也很快。像拼音目录这样的索引,数据会根据索引中的顺序进行排列和组织的,这样的索引就被称为聚集索引,而非聚集索引就是其他的一般索引。因为数据只能按照一种规则排序,所以一张表至多有一个聚集索引,但可以有多个非聚集索引。在MySQL数据库的InnoDB存储引擎中,主键索引就是聚集索引,所有数据都会按照主键索引进行组织;而在MyISAM存储引擎中,就没有聚集索引了,因为MyISAM存储引擎中的数据不是按索引顺序进行存储的。

February 14, 2019 · 1 min · jiezi

leetCode算法-268(缺失数字)

给定一个包含 0, 1, 2, …, n 中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。示例 1:输入: [3,0,1]输出: 2示例 2:输入: [9,6,4,2,3,5,7,0,1]输出: 8下面我用两种方法,一块了解一下。求合法 // 开始之前我先说一下我的思路 //0-n个有序数字累加和,数学里边是有公式的,我们重温一下推导过程。 // 0, 1 n为1 个数为2, 累加和为1 // 0, 1, 2 n为2 个数为3,累加和为3 // 0, 1, 2, 3 n为3 个数为4,累加和为6 // 0, 1, 2, 3 ,4 n为4 个数为5, 累加和为10 // 思考一下,n已知所以我们从n上找规律 //以上我们推出 (n*个数)/2 = 累加和,是不是这个理? // n知道 累加和知道,少那个我们不就知道了吗? // 完整的n序列和-缺失数字的累加和=缺失数字 // 翻译成代码 var arr = [0, 1, 3] function findNum(nums) { let len = nums.length let n = len * (len + 1) / 2 let n1 = nums.reduce((num, item) => { return num + item }, 0) console.log(n, n1) return n - n1 }; console.log(findNum(arr))//2索引查找法//先解析思路,0-n序列,数组的索引和0-n序列正好能一一对应,不过因为有可能是乱序,所以不能直接挨个匹配索引对比,我们需要换个思路。//因为是序列,虽然乱序,只是和遍历n的索引值缺一个,这就简单了,我们通过遍历n来查找对应的剩余数组,找不到的就是我们要找的缺失数字,怎么找对应数字呢?你猜对了我们用indexOf() function findNum1(nums) { let len = nums.length + 1 for (let i = 0; i < len; i++) { if (nums.indexOf(i) === -1) { return i } } }; findNum1(arr)完美,只不过没有第一种的效率高!毕竟每一次indexOf也是一次遍历过程。 ...

February 2, 2019 · 1 min · jiezi

阿里资深技术专家:优秀的数据库存储引擎应具备哪些能力?

摘要: 作为数据库的底盘,一个成熟的存储引擎如何实现高效数据存取?导读本文作者是阿里巴巴OLTP数据库团队资深技术专家——曲山。作为自研高性能、低成本存储引擎X-Engine的负责人,曲山眼中的优秀关系型数据库存储引擎应该具备哪些能力呢?正文数据库内核按层次来分,就是两层:SQL & Storage。SQL Layer负责将你输入的SQL statement通过一系列步骤(parse/resolve/rewrite/optimize…)转换成物理执行计划,同时负责计划的执行,执行计划通常是一颗树的形式,其中树的叶子节点(执行器算子)部分往往负责单表的数据操作,这些操作算子就要在storage layer来执行了。因此,一个数据库存储引擎的主要工作,简单来讲就是存取数据,但是前提是保证数据库的ACID(atomicity/consistency/isolation/durability)语义。存储引擎对外提供的接口其实比较简单,主要就是数据写入/修改/查询,事务处理(start transaction/commit/rollback…),修改schema对象/数据字典(可选), 数据统计,还有一些周边的运维或数据导入导出功能。仅仅从功能上来说,要实现一个存储引擎似乎并不困难,如今也有很多Key-Value Store摇身一变就成为了数据库存储引擎,无非是加上一套事务处理机制罢了。但是作为数据库的底盘,一个成熟的存储引擎必须要考虑效率,如何高效(性能/成本最大化)的实现数据存取则成了在设计上做出种种权衡的主要考量。可以从存储引擎的几个主要组件来讨论:数据组织数据在内存和磁盘中的组织方式很大程度上决定了存取的效率,不同的应用场景选择也不同,典型的如:数据按行存储(NSM),对事务处理比较友好,因为事务数据总是完整行写进来, 多用于OLTP场景。按列存储(DSM),把tuples中相同的列值物理上存储在一起,这样只需要读取需要的列,在大规模数据扫描时减少大量I/O。另外列存做压缩的效果更好,适合OLAP场景,但是事务处理就不那么方便,需要做行转列。所以大部分AP数据库事务处理效率都不怎么高,某些甚至只支持批量导入。混合存储(FSM),行列混合布局,有把数据先按行分组(Segment, SubPage),组内使用DSM组织,如PAX, RCFile,也有先按列分组(Column Group),组内指定的列按NSM组织,如Peloton的Tile。此种格式试图结合NSM和DSM两者的优点,达到处理混合负载(HTAP)的目的,但是同时也继承了两者的缺点。所以做存储引擎,一开始就要面临选择何种存储格式的问题。即便选定了大类,每种格式中也有无数的细节需要考虑,每种数据类型的字段如何编码(Encoding),行存中null/not null如何存储,是否需要列索引加快project operation,是否需要对列值进行重排,列存如何进行数据压缩,等等,都要存储空间和存取速度中做平衡。现代数据库为了应对复杂的应用场景,往往使用不只一种存储格式,比如Oracle有In-memory Column Store在内存中将行存的页面转换为列存方式的page,用来加速复杂查询。当数据选定存储格式以后,还要选择数据在磁盘和内存中的聚集方式。以按行存储为例,大部分存储引擎使用固定大小的页面(page)来存储连续的若干行。当然,数据行如何连续排列,有堆表(随机)和索引组织表(按索引序)两种,现在较为流行的LSM-Like的存储引擎使用不定大小的页面(称为DataBlock),只支持按主键索引序聚集;这两种方式主要区别在于前者被设计为可更新的,每个page中会留有空间,后者是只读的,数据紧密存储不带padding,便于压缩。两者的区别实际上是因为事务处理机制有较大的区别导致的,后面再论。对于In-Memory Database来说,数据组织的方式会有较大区别,因为不需要在内存和持久化存储中交换数据,内存中一般不会使用page形式,而是直接使用索引存储结构(比如B+Tree)直接索引到记录(tuples),无需page这一层间接引用,减少cpu cache miss。缓存管理缓存的粒度一般是page,关键在于缓存替换算法。目前用的比较广泛的LRU,LFU,ARC..以及各种变种的算法都有在数据库中使用。另外还有一个是如何更有效的管理内存的问题,这点上,定长的page会比不定长的更有优势。当然还要考虑各种query pattern对cache的影响,如果单行查询较多,选用更细粒度(比如row)的cache会更有效率,但是淘汰的策略会更复杂,很多新的研究开始尝试引入机器学习的方法来优化cache淘汰算法,以及有效的管理cache.事务处理存储引擎之核心,保证数据库的ACID。要保证D,大家的做法差不多,都是写WAL(Write Ahead Log)来做recovery,关键是如何高效的实现ACI,也就是所谓的多版本并发控制(MVCC)机制。MVCC的完整实现比较复杂,暂不详细阐述,这里面的关键在于如何处理并发执行过程中的数据冲突(data race),包括写写冲突,读写冲突;因为数据库的负载一般是读多写少的,要做到高效,只读事务不能被读写事务阻塞,这就要求我们的写不能直接去更新当前的数据,而是要有一套维护多版本数据的能力,当前的存储引擎管理多版本数据的办法无非两种:写入数据原地更新,被更新的旧版本写到undo链中,写入代价大,事务处理复杂, 但是回收旧版本数据高效。写入数据不直接更新原来的数据,而是追加为新版本,写入代价小,但是读,尤其是扫描需要读取层次较多,更为严重的问题是回收旧版本的数据需要做compact,代价很大。前一种称为ARIES算法比大多数主流数据库存储引擎使用,后一种称为LSM-Tree的结构也被很多新存储引擎使用,受到越来越多的关注。Catalog与KV store有区别的是,数据库是有严格的schema的,所以多数存储引擎中的记录都是有结构的,很多KV store在作为数据库存储引擎时,都是在中间做一层转换,将上层处理的tuples以特定的编码方式转换为binary key-value,写入KVStore,并在读取到上层后,依靠schema解释为tuples格式供上层处理。这种方法当然可以工作,但是诸多优化无法实施:a. 数据迭代必须是整行,即便只需要其中一列,序列化/反序列化开销是免不了的。b. project和filter的工作无法下放到存储层内部进行处理; c. 没有列信息,做按列编码,压缩也不可能。d. schema change只能暴力重整数据… 因此要做到真正的高效,越来越多的存储引擎选择完全感知schema,存储细微结构。总结以上所探讨的,还只是单机数据库的存储引擎几个大的问题,而现代数据库对存储引擎提出了更高的要求,可扩展,高可用已经成为标配,现在要考虑的是如何给你的存储引擎加上分布式的能力,而这又涉及到高可用一致性保证,自动扩展,分布式事务等一系列更为复杂的问题,这已远超出本文的范畴,需要另开篇章。最后介绍下我们正在开发的阿里自研分布式数据库X-DB,其中的存储引擎就使用了我们自研的X-Engine。X-Engine使用了一种对数据进行分层的存储架构,因为目标是面向大规模的海量数据存储,提供高并发事务处理能力和尽可能降低成本。我们根据数据访问频度(冷热)的不同将数据划分为多个层次,针对每个层次数据的访问特点,设计对应的存储结构,写入合适的存储设备。X-Engine使用了LSM-Tree作为分层存储的架构基础,并在这之上进行了重新设计。简单来讲,热数据层和数据更新使用内存存储,利用了大量内存数据库的技术(Lock-Free index structure/append only)提高事务处理的性能,我们设计了一套事务处理流水线处理机制,把事务处理的几个阶段并行起来,极大提升了吞吐。而访问频度低的冷(温)数据逐渐淘汰或是合并到持久化的存储层次中,结合当前丰富的存储设备层次体系(NVM/SSD/HDD)进行存储。我们对性能影响比较大的compaction过程做了大量优化,主要是拆分数据存储粒度,利用数据更新热点较为集中的特征,尽可能的在合并过程中复用数据,精细化控制LSM的形状,减少I/O和计算代价,并同时极大的减少了合并过程中的空间放大。同时使用更细粒度的访问控制和缓存机制,优化读的性能。当然优化是无止境的,得益于丰富的应用场景,我们在其中获得了大量的工程经验。X-Engine现在已经不只一个单机数据库存储引擎,结合我们的X-Paxos(分布式强一致高可用框架), GMS(分布式管理服务), 和X-Trx(分布式事务处理框架),已经演变为一个分布式数据库存储系统。本文作者:七幕阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 29, 2019 · 1 min · jiezi

阿里云图数据库GraphDB上线,助力图数据处理

GraphDB简介GraphDB图数据库适用于存储,管理,查询复杂并且高度连接的数据,图库的结构特别适合发现大数据集下数据之间的共性和特性,特别善于释放蕴含在数据关系之间的巨大价值。GraphDB引擎本身并不额外收费,仅收取云hbase费用。适合的业务场景在如下多种场景中图数据库比其他类型数据库(RDBMS和NoSQL)更合适推荐及个性化几乎所有的企业都需要了解如何快速并且高效地影响客户来购买他们的产品并且推荐其他相关商品给他们。这可能需要用到云服务的推荐,个性化,网络分析工具。如果使用得当,图分析是处理推荐和个性化任务的最有效武器,并根据数据中的价值做出关键决策。举个例子,网络零售商需要根据客户过往消费记录及订单推荐其他商品给这个客户。为了能成功的达到目的,当前回话下用户浏览操作等都可以实时集成到一张图中。图非常适合这些类似的分析用例,如推荐产品,或基于用户数据,过去行为,推荐个性化广告。电商商品推荐案例如何使用GraphDB做商品实时推荐安全和欺诈检测在复杂及高度相关的用户,实体,事务,时间,交互操作的网络中,图数据库可以帮助检测哪些实体,交易,操作是有欺诈性质的,从而规避风险。简而言之,图数据库可以帮助在数不清金融活动中产生的关系及事件组成的海量数据集中找到那根坏针。某深圳大数据风控案例客户介绍:该大数据有限公司专注于为银行、消费金融、三方支付、P2P、小贷、保险、电商等客户解决线上风险和欺诈问题。案例背景及痛点近几年互联网金融行业兴起,诞生了很多互联网金融企业,用户参加线上贷款,金融消费,P2P融资等金融活动门槛大大降低,在这些金融行为中如何有效规避风险,进行风控是每个金融企业面临的比较严峻的问题。用户的金融行为中会沉淀大量有价值的数据,在白骑士客户小贷场景中会产生一笔笔贷款记录关联的手机号,身份证,银行卡号,设备号等。这些数据代表一个个实体人,正常金融活动中,贷款,金融服务不是高频行为,一个实体人一般有一个唯一身份证,常用银行卡号,手机号,设备号。这几者顶点见不会产生高密度图,但有一些高危低信用用户可能会使用同一手机设备申请贷款进行骗贷。客户痛点在于如何高效识别这些高危低信用用户。解决方案建立图模型分别创建手机号,设备号,身份证,银行卡号四类顶点及相互关联的边,扩展属性便于查询。从原数据仓库清洗后通过graph-loader工具导入GraphDB在线评估用户信用资质在申请贷款流程中,可以通过使用图库可以实时查询图中任意一手机号关联的身份证数量(一跳/二跳查询),恶意申请有如下特点,关联子图各类顶点过多,并且可能关联上离线分析标注过得黑名单用户,说明当前用户存在恶意申请风险,实时拒掉贷款申请。下图显示如何与自身小贷平台打通,做实时风控预警,箭头方向代表数据流方向。主动识别黑名单用户借助spark graphframes分析能力,离线计算全图中各个顶点出入度及pagerank,主动挖掘超级顶点,超级顶点如一个手机号关联了多个身份证顶点,说明该用户金融活动频繁,背后的故事是一个实体人有多笔申请记录,分别关联了不同的身份证,手机号,说明该用户在进行恶意欺诈活动,人工标注黑名单用户,从源头禁掉用户金融活动。物联网物联网(IoT)是另一个非常适合图数据库领域。 物联网使用案例中,很多通用的设备都会产生时序相关的信息如事件和状态数据。在这种情况下,图数据库效果很好,因为来自各个独立的终端的流汇聚起来的时候产生了高度复杂性此外,涉及诸如分析根本原因之类的任务时,也会引入多种关系来做整体检查,而非隔离检查。GraphDB特性整体架构使用Apache TinkerPop构建GraphDB是Apache TinkerPop3接口的一个实现,支持Tinkerpop全套软件栈,支持Gremlin语言,可以快速上手。在GraphDB中,为应对不同的业务场景,数据模型已经做到尽可能的灵活。例如,GraphDB中点和边均支持用户自定义ID;自定义ID可以是字符串或数字;属性值可以是任意类型,包括map,数组,序列化的对象等。因此,应用不需要为了适应图数据库的限制而做多余的改造,只需要专注在功能的实现上面。GraphDB具有完善的索引支持。支持对顶点建立label索引和属性索引;支持对边建立label索引,属性索引和顶点索引;支持顶点索引和边索引的范围查询和分页。良好的索引支持保证了顶点In/Out查询和根据属性查找顶点/边的操作都具有很好的性能。与HBase深度集成GraphDB使用企业认证的HBase版本作为其持久数据存储。 由于与HBase的深度集成,GraphDB继承了HBase的所有主要优势,包括服务可用性指标,写/读/时刻都在线高可用功能,线性可扩展性,可预测的低延迟响应时间,hbase专家级别的的运维服务。 在此基础上,GraphDB增强了性能,其中包括自适应查询优化器,分片数据位置感知能力。使用spark graphframes做图分析借助阿里云HBase X-Pack提供的Spark产品,可以对GraphDB中的图数据进行分析。作为优秀的大数据处理引擎,Spark能够对任意数据量的数据进行快速分析,Spark支持scala、java、python多种开发语言,可本地调试,开发效率高。此外,阿里云HBase X-Pack的Spark服务通过全托管的方式为用户提供企业级的服务,大大降低了使用门槛和运维难度。Spark GraphX中内置了常见的图分析操作,例如PageRank、最短路径、联通子图、最小生成树等。云上大规模GraphDB优势全托管,全面解放运维,为业务稳定保驾护航大数据应用往往涉及组件多、系统庞杂、开源与自研混合,因此维护升级困难,稳定性风险极高。云HBase GraphDB提供的全托管服务相比其他的半托管服务以及用户自建存在天然的优势。依托持续8年在内核和管控平台的研发,以及大量配套的监控工具、跨可用区、跨域容灾多活方案,GraphDB的底层核心阿里云HBase提供目前业界最高的4个9的可用性(双集群),11个9的可靠性的高SLA的支持,满足众多政企客户对平台高可用、稳定性的诉求。使用阿里云GraphDBGraphDB引擎包含在HBase 2.0版本中,用户在购买云上HBase数据库服务时,可以选择GraphDB作为其图数据引擎。GraphDB引擎本身并不额外收费,对于需要使用图数据功能的用户而言,将大幅降低应用和开发成本。了解更多关于阿里云云数据库HBase及图引擎GraphDB请戳链接:产品入口:https://cn.aliyun.com/product/hbase?spm=5176.224200.100.35.7f036ed6YlCDxm帮助文档:https://help.aliyun.com/document_detail/92186.html?spm=a2c4g.11174283.6.610.260d3c2eONZbgs本文作者:恬泰阅读原文本文为云栖社区原创内容,未经允许不得转载。

January 2, 2019 · 1 min · jiezi

毫秒级从百亿大表任意维度筛选数据,是怎么做到的...

1、业务背景随着闲鱼业务的发展,用户规模达到数亿级,用户维度的数据指标,达到上百个之多。如何从亿级别的数据中,快速筛选出符合期望的用户人群,进行精细化人群运营,是技术需要解决的问题。业界的很多方案常常需要分钟级甚至小时级才能生成查询结果。本文提供了一种解决大数据场景下的高效数据筛选、统计和分析方法,从亿级别数据中,任意组合查询条件,筛选需要的数据,做到毫秒级返回。2、技术选型分析从技术角度分析,我们这个业务场景有如下特点:需要支持任意维度的组合(and/or)嵌套查询,且要求低延迟;数据规模大,至少亿级别,且需要支持不断扩展;单条数据指标维度多,至少上百,且需要支持不断增加;综合分析,这是一个典型的OLAP场景。2.1 OLTP与OLAP下面简单对比下OLTP和OLAP: OLTPOLAP定义联机事务处理联机分析处理应用场景日常业务操作分析决策,报表统计事务要求需要支持事务无需事务支持常用数据操作读/写高并发查询为主,并发要求相对低实时性要求高要求不严格DB大小MB-GBGB-TB数据行数几万到几百万亿级别甚至几十亿上百亿数据列数几十至上百列百级别至千级别最常见的数据库,如MySql、Oracle等,都采用行式存储,比较适合OLTP。如果用MySql等行数据库来实现OLAP,一般都会碰到两个瓶颈:数据量瓶颈:mysql比较适合的数据量级是百万级,再多的话,查询和写入性能会明显下降。因此,一般会采用分库分表的方式,把数据规模控制在百万级。查询效率瓶颈:mysql对于常用的条件查询,需要单独建立索引或组合索引。非索引字段的查询需要扫描全表,性能下降明显。综上分析,我们的应用场景,并不适合采用行存储数据库,因此我们重点考虑列存数据库。2.2 行式存储与列式存储下面简单对比一下行式存储与列式存储的特点: 行式存储列式存储存储特点同一行数据一起存储同一列数据一起存储读取优点一次性读取整行数据快捷读取单列数据快捷读取缺点单列读取,也需要读取整行数据整行查询,需要重组数据数据更新特点INSERT/UPDATE比较方便INSERT/UPDATE比较麻烦索引需要单独对查询列建索引每一列都可以作为索引压缩特点同一行数据差异大,压缩比率低一列数据由于相似性,压缩比率高行存适合近线数据分析,比如要求查询表中某几条符合条件的记录的所有字段的场景。列存适合用于数据的统计分析。考虑如下场景:一个用于存放用户的表中有20个字段,而我们要统计用户年龄的平均值,如果是行存,则要全表扫描,遍历所有行。但如果是列存,数据库只要定位到年龄这一列,然后只扫描这一列的数据就可以得到所有的年龄,计算平均值,性能上相比行存理论上就会快20倍。而在列存数据库中,比较常见的是HBase。HBase应用的核心设计重点是rowkey的设计,一般要把常用的筛选条件,组合设计到rowkey中,通过rowkey的get(单条记录)或者scan(范围)查询。因此HBase比较适合有限查询条件下的非结构化数据存储。而我们的场景,由于所有字段都需要作为筛选条件,所以本质上还是需要结构化存储,且要求查询低延迟,因此也无法使用HBase。我们综合考虑集团内多款列式存储的DB产品(ADS/PostgreSQL/HBase/HybridDB),综合评估读写性能、稳定性、语法完备程度及开发和部署成本,我们选择了HybridDB for MySQL计算规格来构建人群圈选引擎。2.3 HybridDB for MySQL计算规格介绍HybridDB for MySQL计算规格对我们的这个场景而言,核心能力主要有:任意维度智能组合索引(使用方无需单独自建索引)百亿大表查询毫秒级响应MySql BI生态兼容,完备SQL支持空间检索、全文检索、复杂数据类型(多值列、JSON)支持那么,HybridDB for MySQL计算规格是如何做到大数据场景下的任意维度组合查询的毫秒级响应的呢?首先是HybridDB的高性能列式存储引擎,内置于存储的谓词计算能力,可以利用各种统计信息快速跳过数据块实现快速筛选;第二是HybridDB的智能索引技术,在大宽表上一键自动全索引并根据列索引智能组合出各种谓词条件进行过滤;第三是高性能MPP+DAG的融合计算引擎,兼顾高并发和高吞吐两种模式实现了基于pipeline的高性能向量计算,并且计算引擎和存储紧密配合,让计算更快;第四是HybridDB支持各种数据建模技术例如星型模型、雪花模型、聚集排序等,业务适度数据建模可以实现更好的性能指标。综合来说,HybridDB for MySQL计算规格是以SQL为中心的多功能在线实时仓库系统,很适合我们的业务场景,因此我们在此之上构建了我们的人群圈选底层引擎。3、业务实现在搭建了人群圈选引擎之后,我们重点改造了我们的消息推送系统,作为人群精细化运营的一个重要落地点。3.1 闲鱼消息推送简介消息推送(PUSH)是信息触达用户最快捷的手段。闲鱼比较常用的PUSH方式,是先离线计算好PUSH人群、准备好对应PUSH文案,然后在第二天指定的时间推送。一般都是周期性的PUSH任务。但是临时性的、需要立刻发送、紧急的PUSH任务,就需要BI同学介入,每个PUSH任务平均约需要占用BI同学半天的开发时间,且操作上也比较麻烦。本次我们把人群圈选系统与原有的PUSH系统打通,极大地改善了此类PUSH的准备数据以及发送的效率,解放了开发资源。3.2 系统架构离线数据层:用户维度数据,分散在各个业务系统的离线表中。我们通过离线T+1定时任务,把数据汇总导入到实时计算层的用户大宽表中。实时计算层:根据人群的筛选条件,从用户大宽表中,查询符合的用户数量和用户ID列表,为应用系统提供服务。人群圈选前台系统:提供可视化的操作界面。运营同学选择筛选条件,保存为人群,用于分析或者发送PUSH。每一个人群,对应一个SQL存储。类似于:select count(*) from user_big_table where column1> 1 and column2 in (‘a’,‘b’) and ( column31=1 or column32=2)同时,SQL可以支持任意字段的多层and/or嵌套组合。用SQL保存人群的方式,当用户表中的数据变更时,可以随时执行SQL,获取最新的人群用户,来更新人群。闲鱼PUSH系统:从人群圈选前台系统中获取人群对应的where条件,再从实时计算层,分页获取用户列表,给用户发送PUSH。在实现过程中,我们重点解决了分页查询的性能问题。分页查询性能优化方案:在分页时,当人群的规模很大(千万级别)时,页码越往后,查询的性能会有明显下降。因此,我们采用把人群数据增加行号、导出到MySql的方式,来提升性能。表结构如下:批次号人群ID行号用户ID100111123100112234100113345100114456批次号:人群每导出一次,就新加一个批次号,批次号为时间戳,递增。行号:从1开始递增,每一个批次号对应的行号都是从1到N。我们为"人群ID"+“批次号”+“行号"建组合索引,分页查询时,用索引查询的方式替换分页的方式,从而保证大页码时的查询效率。另外,为此额外付出的导出数据的开销,得益于HybridDB强大的数据导出能力,数据量在万级别至百万级别,耗时在秒级至几十秒级别。综合权衡之后,采用了本方案。4、PUSH系统改造收益人群圈选系统为闲鱼精细化用户运营提供了强有力的底层能力支撑。同时,圈选人群,也可以应用到其他的业务场景,比如首页焦点图定投等需要分层用户运营的场景,为闲鱼业务提供了很大的优化空间。本文实现了海量多维度数据中组合查询的秒级返回结果,是一种OLAP场景下的通用技术实现方案。同时介绍了用该技术方案改造原有业务系统的一个应用案例,取得了很好的业务结果,可供类似需求或场景的参考。5、未来人群圈选引擎中的用户数据,我们目前是T+1导入的。这是考虑到人群相关的指标,变化频率不是很快,且很多指标(比如用户标签)都是离线T+1计算的,因此T+1的数据更新频度是可以接受的。后续我们又基于HybridDB构建了更为强大的商品圈选引擎。闲鱼商品数据相比用户数据,变化更快。一方面用户随时会更新自己的商品,另一方面,由于闲鱼商品单库存(售出即下架)的特性,以及其他原因,商品状态会随时变更。因此我们的选品引擎,应该尽快感知到这些数据的变化,并在投放层面做出实时调整。我们基于HybridDB(存储)和实时计算引擎,构建了更为强大的“马赫”实时选品系统。本文作者:闲鱼技术-才思阅读原文本文为云栖社区原创内容,未经允许不得转载。

November 28, 2018 · 1 min · jiezi

一个案例彻底弄懂如何正确使用 mysql inndb 联合索引

有一个业务是查询最新审核的5条数据SELECT id, titleFROM th_contentWHERE audit_time < 1541984478 AND status = ‘ONLINE’ORDER BY audit_time DESC, id DESCLIMIT 5;查看当时的监控情况 cpu 使用率是超过了100%,show processlist看到很多类似的查询都是处于create sort index的状态。查看该表的结构CREATE TABLE th_content ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, title varchar(500) CHARACTER SET utf8 NOT NULL DEFAULT ’’ COMMENT ‘内容标题’, content mediumtext CHARACTER SET utf8 NOT NULL COMMENT ‘正文内容’, audit_time int(11) unsigned NOT NULL DEFAULT ‘0’ COMMENT ‘审核时间’, last_edit_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘最近编辑时间’, status enum(‘CREATED’,‘CHECKING’,‘IGNORED’,‘ONLINE’,‘OFFLINE’) CHARACTER SET utf8 NOT NULL DEFAULT ‘CREATED’ COMMENT ‘资讯状态’, PRIMARY KEY (id), KEY idx_at_let (audit_time,last_edit_time)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;索引有一个audit_time在左边的联合索引,没有关于status的索引。分析上面的sql执行的逻辑:从联合索引里找到所有小于该审核时间的主键id(假如在该时间戳之前已经审核了100万条数据,则会在联合索引里取出对应的100万条数据的主键 id)对这100万个 id 进行排序(为的是在下面一步回表操作中优化 I/O 操作,因为很多挨得近的主键可能一次磁盘 I/O 就都取到了)回表,查出100万行记录,然后逐个扫描,筛选出status=‘ONLINE’的行记录最后对查询的结果进行排序(假如有50万行都是ONLINE,则继续对这50万行进行排序)最后因为数据量很大,虽然只取5行,但是按照我们刚刚举的极端例子,实际查询了100万行数据,而且最后还在内存中进行了50万行数据库的内存排序。所以是非常低效的。画了一个示意图,说明第一步的查询过程,粉红色部分表示最后需要回表查询的数据行。图中我按照索引存储规律来YY伪造填充了一些数据,如有不对请留言指出。希望通过这张图大家能够看到联合索引存储的方式和索引查询的方式改进思路 1范围查找向来不太好使用好索引的,如果我们增加一个audit_time, status的联合索引,会有哪些改进呢?ALTER TABLE th_content ADD INDEX idx_audit_status (audit_time, status);mysql> explain select id, title from th_content where audit_time < 1541984478 and status = ‘ONLINE’ order by audit_time desc, id desc limit 5;+—-+————-+————+——-+——————————————+——————+———+——+——–+————-+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+—-+————-+————+——-+——————————————+——————+———+——+——–+————-+| 1 | SIMPLE | th_content | range | idx_at_ft_pt_let,idx_audit_status | idx_audit_status | 4 | NULL | 209754 | Using where |+—-+————-+————+——-+——————————————+——————+———+——+——–+————-+细节:因为audit_time是一个范围查找,所以第二列的索引用不上了,只能用到audit_time,所以key_len是4。而下面思路2中,还是这两个字段key_len则是5。还是分析下在添加了该索引之后的执行过程:从联合索引里找到小于该审核时间的audit_time最大的一行的联合索引然后依次往下找,因为< audit_time是一个范围查找,而第二列索引的值是分散的。所以需要依次往前查找,匹配出满足条件(status=‘ONLINE’)的索引行,直到取到第5行为止。回表查询需要的具体数据在上面的示意图中,粉红色标识满足第一列索引要求的行,依次向前查询,本个叶子节点上筛选到了3条记录,然后需要继续向左,到前一个叶子节点继续查询。直到找到5条满足记录的行,最后回表。改进之处因为在索引里面有status的值,所以在筛选满足status=‘ONLINE’行的时候,就不用回表查询了。在回表的时候只有5行数据的查询了,在iops上会大大减少。该索引的弊端如果idx_audit_status里扫描5行都是status是ONLINE,那么只需扫描5行;如果idx_audit_status里扫描前100万行中,只有4行status是ONLINE,则需要扫描100万零1行,才能得到需要的5行记录。索引需要扫描的行数不确定。改进思路 2ALTER TABLE th_content DROP INDEX idx_audit_status;ALTER TABLE th_content ADD INDEX idx_status_audit (status, audit_time);这样不管是排序还是回表都毫无压力啦。本文作者:周梦康阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 21, 2018 · 1 min · jiezi

阿里云梁楹:这样的青春,别样的精彩

人的青春应该怎样度过?相信一千个人心中,有一千个答案。我是郭嘉梁,花名梁楹,在不少人眼中,我是一个来自北方的大男孩,一个自带“古典气质的少年”,其实我是一个喜欢晋级打怪,热爱挑战自我的阿里云工程师。1024程序员节之际,分享我的成长经历,且看别样的“青春修炼手册”。学生时代:热爱、执著、前进早在读书的时候,我就一直很喜欢接触一些新的技术。本科毕业后,我被保送到中科院计算所读研,机缘巧合,我接触到了很有前瞻性的光网络互连技术。当时,在国内做光网络研究的人还是很少的。在导师的指导下,我专注于根据高性能数据中心流量模型,利用光交换机对数据中心的网络拓扑进行快速重构。通过 RYU 控制器完成了控制层的拓扑发现,路由计算等工作。在模拟系统中,实现并验证了 HyperX、Torus、DragonFly 等高性能网络常见拓扑结构 的重构算法 FHTR(fast and hitless data center topology reconfiguration)。在基于 AWGR 的光网络中利用该算法达到了微秒级的拓扑重构,并在小规模拓扑的评测中比之前的最新研究成果降低了 50%的丢包率。终于在2017年投中了欧洲光通信领域顶会ECOC的文章。虽然实验室内接触的技术大多偏重计算机硬件,但当时实验室的同学也喜欢利用业余时间探讨一些互联网的相关技术。研二时,我看到了阿里云正在举办中间件性能挑战赛,我和实验室的小伙伴一拍即合,决定以赛代练,多接触接触工业界的先进技术。当时的赛题是需要实现自定制数据库,满足双十一脱敏数据的高并发写入和查询需求。于是在两个多月的时间里,我们几乎从0开始调研数据库的索引机制,整个暑假的时间都泡在实验室里。最终,在索引阶段,我们通过 TeraSort 的排序算法对 4 亿订单进行聚集索引,并采用多线程同步的方式控制磁盘 I/O。 在查询阶段,通过多线程完成 Join 操作,充分利用了 CPU 资源。同时,利用 AVRO 实现了数据的压缩,将原始数据压 缩到了 46%。使用 LRU 算法完成了基于块的缓存机制,查询的命中率达到 83%。日常学习的沉淀积累、平时练就的细致全面的解题思路、敢打硬仗的勇气,终于帮助我们克服了重重困难,翻越高山和大海,我们拿到了决赛冠军的好成绩!从此,我也结下了与阿里巴巴的缘分。阿里体验:我挑战,我能行2017年,我参加了阿里巴巴的校园招聘,了解到当时正在打算开辟新的业务,也是国内第一个和Elasticsearch官方合作的项目。当时内心就十分向往,虽然对全文搜索技术了解不多,但我依然觉得这是一个不错的挑战机遇。心里有个声音告诉我,如果刚工作的时候,能把一件未知的事情干好,以后职场上没有什么事情是做不好的!十分幸运,我加入阿里就赶上了Elasticsearch项目的启动,以及长达三个月的封闭开发。“一个新人+ 一个新项目”,挑战模式全面升级,而这正是我加入阿里所期待的。还记得刚入职的时候,很多问题搞不清楚,阿里的“老员工”濒湖同学,就像高年级的学长一样,耐心与我共同探讨问题、结对开发,极大的缩短了我融入团队的时间。但毕竟是新项目,压力和焦虑感也随之而来,漫长的封闭开发期,需要我用最快的速度了解阿里云的相关业务,以及适应阿里的开发节奏,这种“折磨”感让我无论是在技术方面还是对公司文化的理解方面,感觉都是经历了一场脱胎换骨式的洗礼。记忆里,几乎所有的场景都是与时间赛跑的拼搏画面,项目也终于在进入封闭开发室两个月后,进入了公测阶段。阿里的工程师每个人都肩负着重要的开发任务,以及相应的责任。主管万喜对我说的一句话,至今记忆犹新,“阿里云上的业务很重要,对待每一行代码都要非常认真,这是客户沉甸甸的信任。”每一次开发新功能时,每一次版本迭代时,我都心怀敬畏。如今,我参与开发的产品和相关技术在国内同行业中已经处于领先位置,获得行业认可和用户的好评。马老师说过,阿里人要有家国情怀。阿里云的业务涉及到的中小型企业非常多,因此我们每一天要做的,就是要完成好这一份重托,这份嘱托,支撑我迎接挑战、面对困难、赢得胜利!不忘初心,迎接未来来到阿里已经一年多了,在这个欢乐的大家庭,我收获很多,不但认识了新的同事,开发了新的产品,身份也从一名学生,正是转变成了工程师,我的“青春”再升级。对于工程师的身份,我感到十分骄傲。目前,我参与的阿里云Elasticsearch产品,提供基于开源Elasticsearch及商业版X-Pack插件,致力于数据分析、数据搜索等场景服务。在开源Elasticsearch基础上提供企业级权限管控、安全监控告警、自动报表生成等功能。Elasticsearch公有云,目前已经部署了4个国内区域以及6个国际区域,在线的弹性调度,配置管理,词典更新,集群监控,集群诊断,集群网络管理等功能均已提供服务。如果有志同道合的小伙伴,欢迎加入我们的团队。从学生时代到阿里巴巴,所有获得的成绩,都来源于对未知的好奇心。所有事情都是这样,做了不一定有机会,但不做一定没机会。未来,我感到身上的责任更重了,我会认真写好每一行代码,做好每一个云产品。认真生活,快乐工作!点击了解阿里云Elasticsearchhttps://data.aliyun.com/product/elasticsearch本文作者:山哥在这里阅读原文本文为云栖社区原创内容,未经允许不得转载。

November 16, 2018 · 1 min · jiezi

TableStore轻松实现轨迹管理与地理围栏

摘要: 基于TableStore轻松实现亿量级轨迹管理与地理围栏一、方案背景轨迹管理系统日常生活中使用非常普遍,如外卖派送轨迹、快递物流流转、车辆定位轨迹等。该场景与地理位置管理类似,核心点与瓶颈都在数据库的存储性能与查询能力,同时需要时间字段正序排列,保证轨迹点顺序;一方面,存储服务需要应对海量数据的低延迟存、读,另一方面,存储服务也要提供高效的多维度数据检索与排序。表格存储(TableStore)对于轨迹管理场景,依然可以胜任,完全具备实现轨迹管理系统的能力。不妨来体验一下基于TableStore打造的【亿量级摩托车管理系统】样例;需求场景某城市市区出于安全考虑,限制摩托车进入一定的区域范围。某摩托车租赁公司,为了更好管理所辖摩托车的违章问题,对自己所辖摩托车安装定位系统,定时采集摩托车位置。摩托车租赁公司,可以通过轨迹管理平台,查询统计违章情况,也可作为依据,提醒违章的租赁用户,过多违章拉入黑名单;查询场景:【2018年11月01日】编号【id00001】的摩托车行驶轨迹与违章情况查询;样例如下:注:该样例提供了【亿量级】轨迹数据。官网控制台地址:项目样例样例内嵌在表格存储控制台中,用户可登录控制台体验系统(若为表格存储的新用户,需要点击开通服务后体验,开通免费,订单数据存储在公共实例中,体验不消耗用户存储、流量、Cu)。表格存储(TableStore)方案采用表格存储(TableStore)轻松搭建一套:亿量级摩托车管理系统。多元索引功能提供GEO检索、多维查询的能力,通过对时间的排序获取追踪设备的轨迹。同时,用户可随时创建索引然后完成自动同步,不用担心存量数据问题。TableStore作为阿里云提供的一款全托管、零运维的分布式NoSql型数据存储服务,具有【海量数据存储】、【热点数据自动分片】、【海量数据多维检索】等功能,有效的地解决了GEO数据量大膨胀这一挑战;SearchIndex功能在保证用户数据高可用的基础上,提供了数据多维度搜索、排序等能力。针对多种场景创建多种索引,实现多种模式的检索。用户可以仅在需要的时候创建、开通索引。由TableStore来保证数据同步的一致性,这极大的降低了用户的方案设计、服务运维、代码开发等工作量。二、搭建准备若您对于基于TableStore实现的【亿量级摩托车管理系统】体验不错,并希望开始自己系统的搭建之旅,只需按照如下步骤便可以着手搭建了:1、开通表格存储通过控制台开通表格存储服务,表格存储即开即用(后付费),采用按量付费方式,已为用户提供足够功能测试的免费额度。表格存储官网控制台、免费额度说明。2、创建实例通过控制台创建表格存储实例,选择支持多元索引的Region。(当前阶段SearchIndex功能尚未商业化,暂时开放北京,上海,杭州和深圳四地,其余地区将逐渐开放)创建实例后,提交工单申请多元索引功能邀测(商业化后默认打开,不使用不收费)。邀测地址:提工单,选择【表格存储】>【产品功能、特性咨询】>【创建工单】,申请内容如下:问题描述:请填写【申请SearchIndex邀测】机密信息:请填写【地域+实例名】,例:上海+myInstanceName3、SDK下载使用具有多元索引(SearchIndex)的SDK,官网地址,暂时java、go、node.js三种SDK增加了新功能java-SDK<dependency> <groupId>com.aliyun.openservices</groupId> <artifactId>tablestore</artifactId> <version>4.7.4</version></dependency>go-SDK$ go get github.com/aliyun/aliyun-tablestore-go-sdk4、表设计店铺检索系统样例,仅简易使用一张店铺表,主要包含字段:店铺类型、店铺名称、店铺地理位置、店铺平均评分、人均消费消等。表设计如下:表名:geo_track列名数据类型索引类型字段说明_id(主键列)String MD5(mId + timestamp)避免热点mIdStirng 摩托车编号timestamplongLONG时间点(毫秒时间戳)posStringGEO_POINT车辆位置:“30.132,120.082”(纬度,精度)…………三、开始搭建(核心代码)1、创建数据表用户仅需在完成邀测的实例下创建“摩托车轨迹表”:通过控制台创建、管理数据表(用户也可以通过SDK直接创建):其他表如租赁用户表、摩托车信息表等,根据需求创建:这里仅展示轨迹表,表名:geo_track2、创建数据表索引TableStore自动做全量、增量的索引数据同步:用户可以通过控制台创建索引、管理索引(也可以通过SDK创建索引)3、数据导入插入部分测试数据(控制台样例中插入了1.08亿条(1万辆摩托70天24小时*6个"10分钟点")数据,用户自己可以通过控制台插入少量测试数据);表名:geo_track摩托车编号轨迹点md5(mId + timestamp)(主键)时间店铺位置id00001f50d55bec347253c24dc9144dff3e3b7154110360000030.30094,120.01278表名:moto_user摩托车编号(主键)摩托车颜色摩托车品牌摩托车租赁用户id00001银灰色H牌摩托车杨六4、数据读取数据读取分为两类:主键读取(摩托车信息查询)基于原生表格存储的主键列获取:getRow, getRange, batchGetRow等。主键读取用于索引(自动)反查,用户也可以提供主键(摩托车编号)单条查询的页面,查询速度极快。单主键查询方式不支持多维度检索;索引读取(轨迹信息查询)基于新SearchIndex功能Query:search接口。用户可以自由设计索引字段的多维度条件组合查询。通过设置选择不同的查询参数,构建不同的查询条件、不同排序方式;目前支持:精确查询、范围查询、前缀查询、匹配查询、通配符查询、短语匹配查询、分词字符串查询,并通过布尔与、或组合。如【2018年11月01日,id00001号摩托车,行驶轨迹及违章查询】Query条件如下:List<Query> mustQueries = new ArrayList<Query>();List<String> polygonList = Arrays.asList(//地理围栏,禁摩区域 “30.262348,120.092127”, “30.311668,120.079761”, “30.332413,120.129371”, …);String mId = “id00001”;Long timeStart = [2018-11-01时间戳];Long timeEnd = [2018-11-02时间戳];GeoPolygonQuery geoPolygonQuery = new GeoPolygonQuery();geoPolygonQuery.setPoints(polygonList);geoPolygonQuery.setFieldName(“pos”);mustQueries.add(geoPolygonQuery);TermQuery termQuery = new TermQuery();termQuery.setFieldName(“mId”);termQuery.setTerm(ColumnValue.fromString(request.getmId()));mustQueries.add(termQuery);RangeQuery rangeQuery = new RangeQuery();rangeQuery.setFieldName(“timestamp”);rangeQuery.setFrom(ColumnValue.fromDouble(timeStart, true);rangeQuery.setTo(ColumnValue.fromDouble(timeEnd, false);mustQueries.add(rangeQuery);BoolQuery boolQuery = new BoolQuery();boolQuery.setMustQueries(mustQueries);这样,系统的核心代码已经完成,基于表格存储搭亿量级“摩托车管理系统”,是不是很简单?本文作者:潭潭阅读原文本文为云栖社区原创内容,未经允许不得转载。

November 15, 2018 · 1 min · jiezi