• 阿里深度树匹配召回体系演进

    导读:目前不管是广告还是推荐业务,最底层的技术都是检索,由于候选集合非常大,可能从千万甚至亿级别取出数十个用户感兴趣的商品。在算力和时间复杂度的约束下,往往采用分阶段漏斗的算法体系。具体来说就是分成召回 ( match ) 以及排序 ( rank )。本文主要介绍阿里在match阶段的最新实践——深度树匹配,分成几个部分: 深度树匹配(TDM)技术演进 总结与展望 检索召回技术现状 2. 两段式Match的经典实现 在我们系统里面最早期的是一个经典的两段式match,例如基于商品的协同过滤,就是说对我们对于每一条用户的请求我们去找到用户历史的item,然后再通过这些item查询离线计算好的I2I相似关系表找到我们需要召回的item,并且最终做合并取topK。这个方法它的优势在于模型非常简单,而且实现成本非常低,我们所有的迭代只需要在离线计算一份I2I表就完成了。但是它的缺点也很明显,因为是一个两段式的召回框架,整个过程是没有办法联合优化的,有一些很多先进的模型,像深度神经网络没有办法很好的应用,导致召回效果受限。一个很自然的想法是我们是否能把这种两段式的检索升级成为一段式,并且对整个全库做一段式检索。 具体实现是三个阶段: 训练完成之后,对这些item embedding做聚类,并且根据乘积量化去构建索引; 这样一个流程它的优势非常显然:面向全库一段式检索,从数据里面获取一些发现能力,而不依赖于用户历史行为触发。但是它的缺点也很明显,对于我们使用的模型结构上有一个非常强的约束,就是我们的模型必须是类似双塔这样的结构,最终user以及item的相似度必须由内积来计算,这样对于模型能力有一个很大的局限。我们有过好几版升级,最早是类似双塔这样的內积模型,接下来是DIN和DIEN,分别是把attention的机制引入了CTR预估问题里面,如果我们只看auc的话,引入user和item之间的交互模型的性能有一个比较大的提升。 这里面启发我们一个自然的思考,如果我们想要把更加先进的模型引入到match,那么我们应该怎么做?第二个问题是我们在向量检索模型里离线做索引构建的时候,它的目标与我们在线上使用的时候检索目标是完全不一致的,离线索引构建的时候优化目标是最小化embedding近似误差,但是实际上线上想要的是最大化topK召回率,彼此之间有missmatch,所以一个很自然的想法,我们是否能够把这两点统一起来联合来优化,然后这启发了我们去做深度树匹配召回这件事情。 深度树匹配(TDM)技术演进 首先,想到散列表,但是散列表同样基于距离度量,融合先进模型比较困难。加入选择图的话,对于模型结构没有任何要求,但是图的一个问题就是它结构非常复杂,并且在图上做检索很难有确定性的检索次数的控制,会存在指数爆炸的问题,所以我们最后选择了树。树的结构相对简单,可以通过控制树的层高来控制检索次数。 虽然有这样一个想法,但是我们后续需要解决非常多的问题: 如何做兴趣建模保证树检索的有效性 如何构建和优化树索引结构 3. 最大堆树:支持Beam Search检索的兴趣建模 我们提出了一套方案,要求模型保证用户的兴趣分布必须服从最大堆的性质 最大堆树保证Beam Search检索得到的TopK一定是全局最优TopK:从根节点递归向下逐层挑选TopK和扩展其子节点至叶子层。 叶子层节点兴趣:用户对叶子节点的行为数据构建序标签 用深度学习模型拟合上述两个序标签 叶子节点:用户行为的隐式反馈来建模叶子节点的兴趣概率 传递性:叶子正样本上溯祖先仍为正样本 5. TDM1.0:容纳任意先进模型 最大堆树的训练模式和检索模式为容纳任意先进模型提供了坚实的理论基础和强大的效率保证。我们主要做的工作是在最大堆树这样一个约束之下,我们引入了一些更加先进的模型,它跟双塔模型最大的区别在于我们模型里面考虑了用户的特征以及Item特征的交叉,它不是一个双塔结构,这里面用户特征简单来说就是用户历史的Item行为序列商品特征。商品特征比如商品的ID,如果是在树里面非叶子层的话,它就是node的ID,在这种模型结构里面,我们把用户的历史行为拆分成一系列时间窗,然后我们在每个时间窗里面做一个基于Item embedding的attention,然后得到最终的attention向量。在这种模型结构之下,我们利用到了用户以及item之间的交叉信息,所以它的模型能力是比双塔结构强的。 6. TDM2.0:模型&索引联合学习 在1.0的时候,我们是在给定的一个树的范式之下,只考虑模型的学习,但其实树的结构本身对于我们召回结果有非常大的影响,比如说考虑左下角这样一个例子,在一些女装女鞋男装拿些这样个商品集合里面去构造一棵树,那么在这种情况下,树构造有两种办法,一种是把女装和男鞋做一个聚合,然后把女鞋和男装聚合,但这种方法可以看到它得到的聚合其实是没有什么意义的。一个更加合理的划分方法是把女装和女鞋、男装和男鞋划分到相同的树节点,这样的话对于高层的非叶子节点我们就有一些抽象的含义。 所以我们在2.0想要解决的问题就是如何对模型以及索引做联合的学习,这个问题抽象出来,其实就像右上角所示。所以在这种情况下,我们整个树模型的学习过程就可以看成是对两项的优化问题。一项是模型的参数,也就是对带权二部图的最大匹配问题,我们用了贪心的算法,最终得到一个分段式树学习算法。这就是我们在2.0所做的一些基本事情。 一个最典型的例子就是我们在训练的时候,所用到的样本是一部分上述的正样本以及一部分随机采样的。没有考虑到在线上检索的时候,实际上是自顶而下的一个扩展的方式,每层用到的Item其实是相对打分比较高的那一部分,这就会导致我们在检索中可能检索到的某些Item在训练的时候没有得到充分的训练。我们希望把线上检索的目标以及过程完全考虑到训练中,保证我们离线训练以及线上使用的一致性。这样的话,实际上我们又从兴趣分类建模这件事情重新回到对于集合召回建模这个事情上。 为了达到这一点,我们做了以下两件事情。 对检索过程建模:对齐训练目标 我们做的第二件事情就是说我们对于本身模型学习的目标做了一次改造,确保我们能够满足最大堆约束,也就是保证我们的BeamSearch的局部最优一定是全局最优。我们与之前的方法做一个对比,在之前的方法,我们对于节点的标签的设定就是它本身以及它的上溯路径全部标为1,然后剩下的节点全部标为0作为负样本,从里面随机采样作为它的训练样本。我们后来发现这样一个设定方式,其实并不符合最大堆的需求,我们对它做BeamSearch得到的topK也不是全局的最优。所以我们换了一个样本构建的方式,就如右图所示,比如说这里面蓝色节点都是我们检索到的集合,那么我们对于每个节点的标签,我们不只依赖于本身item用户是否点击了这个行为,每个节点的标签还依赖于从它的子节点上溯的标签,以及模型对于它的子节点的一个预估分数。 9. TDM显著性效果 离线的话我们也做了一些公开数据集的测试,可以看到不管是从1.0、2.0以及3.0,它的召回率都有一个显著的涨幅,跟现有的方法,比如说ITemCF以及YoutubeDNN这两个经典的方法来比,有个非常大的提升。 10. TDM在定向广告场景落地实践 最后,讲一下我们在定向广告场景的一些落地使用,广告业务本身它有非常显著的一个特点,就是我们商品召回的有效性其实比较低的,因为存在广告主的预算是否花光,商品的上下架以及投放的时间的影响。所以这导致了在广告里面,我们候选广告集合它的动态性非常强,但是TDM这样的树结构,是一个相对比较静态的结构。 所以在实际中我们采用了静态树结构加动态的正排倒排表这样一个实现方式。具体来说就是我们本身这个树的结构是以正常的商品作为叶子,我们在这样的结构之上做了一个实时的商品以及广告的倒排表,以及广告本身的信息做正排表。我们在线上Servicing的时候,树结构是保持不变的,我们根据实时的变化去实时更新广告正排倒排表的顺序结构,这样使得整体能够保持一个比较高的召回有效性。 展望 最后是总结与展望,然后我们还是回到我们本身检索这样一个问题,因为算力的约束,导致形成了大家都认可的一个分阶段漏斗的形式,就是把整个检索过程拆分成召回以及rank这两个阶段。在后续的话,我们希望也把TDM这一套技术把它做得更加扎实一点。大概是三个维度,首先是检索结构上,我们希望利用图对本身树这样一个召回结构做一些近似的扩展,第二我们希望它能够去对多种目标进行召回,比如说多考虑一些多样性发现性这些指标,另外还有一点是我们希望能够把这种形式做一些可解释性的推荐。

    架构师社区 阿里 深度树 TDM

  • 一次I/O问题引发的P0重大故障

    几年前的一个下午,公司里码农们正在安静地敲着代码,突然很多人的手机同时“哔哔”地响了起来。本来以为发工资了,都挺高兴!打开一看,原来是告警短信 故障回顾 既然I/O对系统性能和稳定性影响这么大,我们就来深入探究一下。 所谓的I/O(Input/Output)操作实际上就是输入输出的数据传输行为。程序员最关注的主要是磁盘IO和网络IO,因为这两个IO操作和应用程序的关系最直接最紧密。 网络IO:不同系统间跨网络的数据传输,比如两个系统间的远程接口调用。 下面这张图展示了应用程序中发生IO的具体场景: 通过上图,我们可以了解到IO操作发生的具体场景。一个请求过程可能会发生很多次的IO操作: 1,页面请求到服务器会发生网络IO 2,服务之间远程调用会发生网络IO 3,应用程序访问数据库会发生网络IO 4,数据库查询或者写入数据会发生磁盘IO IO和CPU的关系 就代表CPU已经在满负荷工作,没精力再处理其他任务了。 通过上面内容我们了解到,IO数据传输时,是不占用CPU的。假如CPU大部分消耗在IO等待(wa)上时,即便CPU空闲率(id)是0%,也并不意味着CPU资源完全耗尽了,如果有新的任务来了,CPU仍然有精力执行任务。如下图: 在DMA模式下执行IO操作是不占用CPU的,所以CPU IO等待(上图的wa)实际上属于CPU空闲率的一部分。所以我们执行top命令时,除了要关注CPU空闲率,CPU使用率(us,sy),还要关注IO Wait(wa)。注意,wa只代表磁盘IO Wait,不包括网络IO Wait。 当我们用jstack查看Java线程状态时,会看到各种线程状态。当发生IO等待时(比如远程调用时),线程是什么状态呢,Blocked还是Waiting? 答案是Runnable状态,是不是有些出乎意料!实际上,在操作系统层面Java的Runnable状态除了包括Running状态,还包括Ready(就绪状态,等待CPU调度)和IO Wait等状态。 如上图,Runnable状态的注解明确说明了,在JVM层面执行的线程,在操作系统层面可能在等待其他资源。如果等待的资源是CPU,在操作系统层面线程就是等待被CPU调度的Ready状态;如果等待的资源是磁盘网卡等IO资源,在操作系统层面线程就是等待IO操作完成的IO Wait状态。 5种Linux网络IO模型包括:同步为了更好地理解网络IO模型,我们先了解 Socket(套接字)通信时,一个应用程序将数据写入Socket,然后通过网卡把数据发送到另外一个应用程序的Socket中。5种网络IO模型也都要基于Socket实现网络通信。 阻塞与非阻塞: 空间:内核空间和用户 同步阻塞IO 我们先看一下。 同步非阻塞IO 多路复用IO模型 信号驱动IO模型,应用进程使用sigaction函数,内核会立即返回,也就是说内核准备数据的阶段应用进程是非阻塞的。内核准备好数据后向应用进程发送SIGIO信号,接到信号后数据被复制到应用程序进程。 采用这种方式,CPU的利用率很高。不过这种模式下,在大量IO操作的情况下可能造成信号队列溢出导致信号丢失,造成灾难性后果。 异步IO模型 异步IO模型的基本机制是,应用进程告诉内核启动某个操作,内核操作完成后再通知应用进程。在多路复用IO模型中,socket状态事件到达,得到通知后,应用进程才开始自行读取并处理数据。在异步IO模型中,应用进程得到通知时,内核已经读取完数据并把数据放到了应用进程的缓冲区中,此时应用进程 直接使用数据即可。 很明显,异步IO模型性能很高。不过到目前为止,异步IO和信号驱动IO模型应用并不多见,传统阻塞IO和多路复用IO模型还是目前应用的主流。Linux2.6版本后才引入异步IO模型,目前很多系统对异步IO模型支持尚不成熟。很多应用场景采用多路复用IO替代异步IO模型。 如何避免IO问题带来的系统故障 对于磁盘文件访问的操作,可以采用线程池方式,并设置线程上线,从而避免整个JVM线程池污染,进而导致线程和CPU资源耗尽。 对于网络间远程调用。为了避免服务间调用的全链路故障,要设置合理的TImeout值,高并发场景下可以采用熔断机制。在同一JVM内部采用线程隔离机制,把线程分为若干组,不同的线程组分别服务于不同的类和方法,避免因为一个小功能点的故障,导致JVM内部所有线程受到影响。 此外,完善的运维监控(磁盘IO,网络IO)和APM(全链路性能监控)也非常重要,能及时预警,防患于未然,在故障发生时也能帮助我们快速定位问题。 就分享到这。原创不易,如果感觉本文对您有帮助,有劳点一下“在看”! 曾任职于阿里巴巴,每日优鲜等互联网公司,任技术总监,15年电商互联网经历

    架构师社区 故障 线程 IO

  • 美团某程序员爆料:绩效背c的都要签pip!网友:pip就是变相劝退!

    你听说过pip吗? 近日,有美团员工发帖爆料:美团年底绩效背c的全部要签pip,美团进来了大量阿里的hr,如今已逐渐对齐超过阿里,成为青出于蓝胜于蓝的pua公司。 网友纷纷吐槽阿里简直是业界毒瘤,这就是大家希望阿里尽快倒闭的原因,能给全社会警醒。 美团员工吐槽自家公司:没有阿里的命,还得了阿里的病,如今阿里味越来越重,pua业界第一。 有人说社会大佬来美团被pua,业绩没毛病就拿团队融入说事,被签pip。 在美团工作,最累的就是心,氛围压抑,末位淘汰,人人自危。 也有人说末位淘汰不是所有大厂都这样吗?又不是美团独有的。 另一个美团员工表示,起码其他大厂有各种福利,而美团只有开水。 另外美团每个月都要271,比阿里的361还狠。 如果不幸背了c就要签pip,意味着没有年终奖没有调薪没有晋升机会,简直就是变相劝退。 那么问题来了,pip到底是什么? 美团员工解答,针对绩效为c的员工,需要主管与员工共同制定有针对性的绩效提升计划,辅导并帮助员工实现能力和绩效提升。 pip的全称是Performance Improvement Plan,听起来pip像是为员工着想,但其实是个比末位淘汰更厉害的员工优化利器。pip规定员工在规定时间内必须完成工作绩效,如果不能完成,企业就能合理地与员工解除劳务合同,且不算裁员。 网友说,签了pip,公司就能名正言顺地说你能力和岗位不匹配。 一旦签了就成了人家案板上的肉,任人宰割。 只有抗住不签,才能拿到n+1赔偿。 总之就是不要签。 其实这已经不是美团第一次爆出让员工签pip,有员工说背c一直要签pip,2015年就有了。 2019年的时候,还有美图员工发帖爆料过自己背c要签pip的遭遇。 不过以前是绩效背两个c才会签pip,现在背一个就要签。 虽然签了pip也不一定就会百分百被辞退,只是被辞退的几率比较大,而且pip要求企业给员工制定的工作绩效要在合理范围内,不能随意制定,更不能完不成就直接解除劳动关系。 但公司毕竟处在强势的地位,打工人则相对弱势,许多人都曾在pip上吃过苦头。如果不幸遇到绩效背c、公司让自己签pip的情况,一定要提高警惕,保护好自己,维护自己的合法权利,这是每个打工人都要有的意识。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    架构师社区 美团 程序员 pip

  • 网易严选全链路市场投放的数据产品策略

    01 02 03 04 今天的分享就到这里,谢谢大家。

    架构师社区 网易严选 数据产品

  • 用了很多年Dubbo,连Dubbo线程池监控都不知道,觉得自己很厉害?

    前言 micrometer 中自带了很多其他框架的指标信息,可以很方便的通过 prometheus 进行采集和监控,常用的有 JVM 的信息,Http 请求的信息,Tomcat 线程的信息等。 对于一些比较活跃的框架,有些还是不支持的,比如 Dubbo。如果想监控 Dubbo 的一些指标,比如线程池的状况,我们需要手动去扩展,输出对应的线程池指标才行。 在这种情况下,肯定是没什么思路的,因为你不知道怎么去扩展,下面给大家介绍去做一件事情之前的思考,方式方法很重要。 Dubbo 有没有现成的实现? 参考 micrometer 中指标的实现,依葫芦画瓢? Dubbo 有没有现成的实现? 完整的实现应该没有,至少我还没用过,也没有那种去搜索引擎一搜就大把结果的现状,于是我在 Dubbo 的 Github 上找到了一个相关的项目 dubbo-spring-boot-actuator。 https://github.com/apache/dubbo-spring-boot-project/tree/master/dubbo-spring-boot-actuator dubbo-spring-boot-actuator 看名称就知道,提供了 Dubbo 相关的各种信息端点和健康检查。从这里面也许能发现点有用的代码。 果不其然,在介绍页面中看到了想要的内容,线程池的指标数据,只不过是拼接成了字符串显示而已。 dubbo_thread_pool_max_size_theads{application="$application", instance=~"$instance"} 图片

    架构师社区 线程池 Dubbo micrometer

  • 程序员因王一博与老婆闹不和?

    这年头流量小生逐渐多了,老戏骨逐渐少了。而托起这些流量小生的正是那些追星的妹子们(没有别的意思勿喷,确实有数据表明追星的女生偏多),而这位程序员家中娇妻真是如此,王一博已经成了她的精神偶像,做梦都要和他结婚。 真想上去叫醒她,嗨,你身边还有个大活人老公呢,天天想别的男的算咋回事嘛。这已经构成精神出轨了,楼主你能忍? 底下的评论也是毫不客气:“最讨厌这种追星的人,普遍素质都不高,无脑,傻子。” 这个群体的地位可想而知了,原来在程序员的眼中,追星人是这样的存在。 果然,此言一出,马上由追星族强势反击,怎么你们男的玩游戏可以,我们追星就不可以? 唇枪舌战,好戏开始! 嗯,按照这个道理来看,似乎玩游戏更高明一些? 不过,有人提出,(我先看看有没有一博粉,省得出来锤我)王一博那么丑,怎么想起来追他,根本不配给肖战组cp。 果然,不到一会儿这就变成了粉丝互相安利会:别追王一博了,看看我们肖战。某网友插话:我喜欢胡歌。 害,只要提到这些明星,好好的一个聊天都能变成粉丝互相攻击会。还有人提出要理智正常追星,别整的那么疯魔。可以有爱好,但别太过。超过自己老公,明显就太过分了。还有人举出了其他追星后特别悲剧的例子。 终于有人回归正题,给楼主提出好心的建议:别让老婆辞职带娃,容易出问题。还有看好你家的钱。还有人提出要和老婆好好沟通,动之以情,晓之以理。 其实楼主应该仔细想想原因,媳妇变成今天的样子。是因为她在家太闲了没有目标,还是因为老公之前给予的照顾和支持太少,所以才需要从追星中找到快乐。 找到了原因就去解决它,可从明星本身的黑料八卦,娱乐圈反例以及对孩子的影响更各个方面各个击破。毕竟孩子都有了,离婚是最末等的决定。 希望楼主早日找回媳妇原来的样子,生活越来越好。

    架构师社区 程序员 王一博

  • 真正拖垮年轻人的,是沉没成本

    先来看一位读者的提问: “K哥,我跟我男朋友谈了5年,中间有许多次争吵到无法挽回的地步,每次总是他向我保证会改,我一心软就原谅他,又继续在一起。但是如果跟这个人生活一辈子,我是没有信心的,彻底分开吧,又舍不得这5年的时光,我该怎么办?” 这就是一个典型的沉没成本的例子。 什么是沉没成本?这是经济学的概念,就是如果交易失败,在这之前所有的投入的成本,就会白白损失掉,于是在决策的时候,往往会考虑已经投入的成本,导致决策不理性。 就像前面的案例,妹子明明觉得这个男人不是理想的结婚对象,考虑到已经付出了宝贵的5年青春,就犹豫了。 殊不知,这样拖下去,其实损失更惨重。如果结婚了呢,有小孩了呢,那个时候你再“忍无可忍”,恐怕也只能委屈自己这一生了,许多婚姻的悲剧就是这么来的。 人们为什么会一直跌入沉没成本的陷阱呢? 原因有三个: 第一,人们不喜欢后悔,不愿意接受之前投入的浪费。这是人性,许多职业经理人,会利用这一点来操控老板,不择手段让老板做出有利于自己的决策。一旦决策了,老板大概率不会推翻这个决策。 第二,自我合理化,侥幸心理。总是想着“其实也不是不好,再试试看吧,也许有改变。” 尤其在中国社会,我们都习惯于:给成功找理由,给失败找借口。 第三,不能接受承认错误带来的惩罚。因为有的公司,你一旦承认失败,就要承担相应的后果,轻的扣绩效,重的丢了饭碗。所以就选择一直拖下去,不及时停止,就会对公司造成巨大损失。 真正聪明的作法,就是及时止损。在投资领域叫做“鳄鱼效应”,指的就是,当你的一条腿被鳄鱼咬住的时候,往往越挣扎,鳄鱼咬的越紧,这个时候你唯一的机会就是牺牲一条腿,保住这条命。 虽然例子极端了一些,但是很形象的说明了及时止损的重要性。但是,人们在面临重大决策的时候,通常不容易做到这一点。 如何能够做到及时止损呢?先来看一个故事: 全球最大的CPU生产商--英特尔公司,一开始并不生产CPU的。 它曾经是世界上最大的存储器制造商,从1968年起,它的主要业务和利润都来自于存储器。 1980年,日本公司的存储制造异军突起。1985年,在连续六个季度的收入下降后,英特尔公司总裁格鲁夫,非常沮丧的和董事长摩尔讨论这个问题,如果再没有好办法,格鲁夫就要下台,英特尔也会从此一蹶不振了。 格鲁夫突然问摩尔:“摩尔,如果我们下了台,你认为新进来的这些家伙会采取什么行动?” 摩尔犹豫了一下,说:“他会完全放弃存储器的生意,也许做处理器。” 格鲁夫目不转睛地盯着摩尔:“既然这样,你我为什么不自己动手?” 结果大家都知道了,英特尔果断放弃存储产品,All in到CPU赛道,一举扭转了亏损的局面,帮助英特尔成为了一家伟大的公司,而格鲁夫也一战封神,成为一代管理大师。 这个在危急关头拯救了英特尔的秘密武器,叫做“清零思维”。 格鲁夫的“清零思维”的核心就是,“假装自己一无所有”、 “假装自己一无所知 ” ,重新审视和思考当下,然后作出最客观的决策。 回到前文的案例,妹子采用“清零思维”来做决策,假设没有跟这个男人谈过5年恋爱,就只问自己一个问题:这个男人有没有可能过一生?如果答案是否定的,应该选择立马结束这段感情,避免再浪费青春。在最好的年华,找到那个更适合自己的人,才是明智之举。 这篇文章最早发表在老K的知识星球“老K星际不迷航”,文章最后老K留给球友们一道作业:想想,你有哪些犹豫不决的时刻,是沉没成本在作祟? 其中一位球友回答:我的车开了5年,舍不得换,这是不是沉没成本。 老K回答他:不是,主要是穷。

    架构师社区 经济学 沉没成本 鳄鱼效应

  • 一次 Java 内存泄漏的排查

    由来 前些日子小组内安排值班,轮流看顾我们的服务,主要做一些报警邮件处理、Bug 排查、运营 issue 处理的事。工作日还好,无论干什么都要上班的,若是轮到周末,那这一天算是毁了。 不知道是公司网络广了就这样还是网络运维组不给力,网络总有问题,不是这边交换机脱网了就是那边路由器坏了。 还偶发地各种超时,而我们灵敏地服务探测服务总能准确地抓住偶现的小问题,给美好的工作加点料。 好几次值班组的小伙伴们一起吐槽,商量着怎么避过服务保活机制,偷偷停了探测服务而不让人发现(虽然也并不敢)。 前些天我就在周末处理了一次探测服务的锅。 问题 网络问题? 晚上七点多开始,我就开始不停地收到报警邮件,邮件显示探测的几个接口有超时情况。多数执行栈都在: java.io.BufferedReader.readLine(BufferedReader.java:371) java.io.BufferedReader.readLine(BufferReader.java:389) java_io_BufferedReader$readLine.call(Unknown Source) com.domain.detect.http.HttpClient.getResponse(HttpClient.groovy:122) com.domain.detect.http.HttpClient.this$2$getResponse(HttpClient.groovy) 这个线程栈的报错我见得多了,我们设置的 HTTP DNS 超时是 1s, connect 超时是 2s, read 超时是 3s。 这种报错都是探测服务正常发送了 HTTP 请求,服务器也在收到请求正常处理后正常响应了,但数据包在网络层层转发中丢失了,所以请求线程的执行栈会停留在获取接口响应的地方。 这种情况的典型特征就是能在服务器上查找到对应的日志记录。而且日志会显示服务器响应完全正常。 与它相对的还有线程栈停留在 Socket connect 处的,这是在建连时就失败了,服务端完全无感知。 我注意到其中一个接口报错更频繁一些,这个接口需要上传一个 4M 的文件到服务器,然后经过一连串的业务逻辑处理,再返回 2M 的文本数据。 而其他的接口则是简单的业务逻辑,我猜测可能是需要上传下载的数据太多,所以超时导致丢包的概率也更大吧。 根据这个猜想,群登上服务器,使用请求的 request_id 在近期服务日志中搜索一下,果不其然,就是网络丢包问题导致的接口超时了。 当然这样 leader 是不会满意的,这个结论还得有人接锅才行。于是赶紧联系运维和网络组,向他们确认一下当时的网络状态。 网络组同学回复说是我们探测服务所在机房的交换机老旧,存在未知的转发瓶颈,正在优化,这让我更放心了,于是在部门群里简单交待一下,算是完成任务。 问题爆发 本以为这次值班就起这么一个小波浪,结果在晚上八点多,各种接口的报警邮件蜂拥而至,打得准备收拾东西过周日单休的我措手不及。 这次几乎所有的接口都在超时,而我们那个大量网络 I/O 的接口则是每次探测必超时,难道是整个机房故障了么。 我再次通过服务器和监控看到各个接口的指标都很正常,自己测试了下接口也完全 OK,既然不影响线上服务,我准备先通过探测服务的接口把探测任务停掉再慢慢排查。 结果给暂停探测任务的接口发请求好久也没有响应,这时候我才知道没这么简单。 解决 内存泄漏 于是赶快登陆探测服务器,首先是 top free df 三连,结果还真发现了些异常。 我们的探测进程 CPU 占用率特别高,达到了 900%。 我们的 Java 进程,并不做大量 CPU 运算,正常情况下,CPU 应该在 100~200% 之间,出现这种 CPU 飙升的情况,要么走到了死循环,要么就是在做大量的 GC。 使用 jstat -gc pid [interval] 命令查看了 java 进程的 GC 状态,果然,FULL GC 达到了每秒一次。 这么多的 FULL GC,应该是内存泄漏没跑了,于是 使用 jstack pid > jstack.log 保存了线程栈的现场,使用 jmap -dump:format=b,file=heap.log pid 保存了堆现场,然后重启了探测服务,报警邮件终于停止了。 jstat jstat 是一个非常强大的 JVM 监控工具,一般用法是: jstat [-options] pid interval 它支持的查看项有: -class 查看类加载信息 -compile 编译统计信息 -gc 垃圾回收信息 -gcXXX 各区域 GC 的详细信息 如 -gcold 使用它,对定位 JVM 的内存问题很有帮助。 排查 问题虽然解决了,但为了防止它再次发生,还是要把根源揪出来。 分析栈 栈的分析很简单,看一下线程数是不是过多,多数栈都在干嘛。 > grep 'java.lang.Thread.State' jstack.log | wc -l > 464 才四百多线程,并无异常。 > grep -A 1 'java.lang.Thread.State' jstack.log | grep -v 'java.lang.Thread.State' | sort | uniq -c |sort -n 10 at java.lang.Class.forName0(Native Method) 10 at java.lang.Object.wait(Native Method) 16 at java.lang.ClassLoader.loadClass(ClassLoader.java:404) 44 at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method) 344 at sun.misc.Unsafe.park(Native Method) 线程状态好像也无异常,接下来分析堆文件。 下载堆 dump 文件。 堆文件都是一些二进制数据,在命令行查看非常麻烦,Java 为我们提供的工具都是可视化的,Linux 服务器上又没法查看,那么首先要把文件下载到本地。 由于我们设置的堆内存为 4G,所以 dump 出来的堆文件也很大,下载它确实非常费事,不过我们可以先对它进行一次压缩。 gzip 是个功能很强大的压缩命令,特别是我们可以设置 -1 ~ -9 来指定它的压缩级别,数据越大压缩比率越大,耗时也就越长。 推荐使用 -6~7, -9 实在是太慢了,且收益不大,有这个压缩的时间,多出来的文件也下载好了。 使用 MAT 分析 jvm heap MAT 是分析 Java 堆内存的利器,使用它打开我们的堆文件(将文件后缀改为 .hprof), 它会提示我们要分析的种类,对于这次分析,果断选择 memory leak suspect。 从上面的饼图中可以看出,绝大多数堆内存都被同一个内存占用了,再查看堆内存详情,向上层追溯,很快就发现了罪魁祸首。 分析代码 找到内存泄漏的对象了,在项目里全局搜索对象名,它是一个 Bean 对象,然后定位到它的一个类型为 Map 的属性。 这个 Map 根据类型用 ArrayList 存储了每次探测接口响应的结果,每次探测完都塞到 ArrayList 里去分析。 由于 Bean 对象不会被回收,这个属性又没有清除逻辑,所以在服务十来天没有上线重启的情况下,这个 Map 越来越大,直至将内存占满。 内存满了之后,无法再给 HTTP 响应结果分配内存了,所以一直卡在 readLine 那。而我们那个大量 I/O 的接口报警次数特别多,估计跟响应太大需要更多内存有关。 给代码 owner 提了 PR,问题圆满解决。 小结 其实还是要反省一下自己的,一开始报警邮件里还有这样的线程栈: groovy.json.internal.JsonParserCharArray.decodeValueInternal(JsonParserCharArray.java:166) groovy.json.internal.JsonParserCharArray.decodeJsonObject(JsonParserCharArray.java:132) groovy.json.internal.JsonParserCharArray.decodeValueInternal(JsonParserCharArray.java:186) groovy.json.internal.JsonParserCharArray.decodeJsonObject(JsonParserCharArray.java:132) groovy.json.internal.JsonParserCharArray.decodeValueInternal(JsonParserCharArray.java:186) 看到这种报错线程栈却没有细想,要知道 TCP 是能保证消息完整性的,况且消息没有接收完也不会把值赋给变量,这种很明显的是内部错误,如果留意后细查是能提前查出问题所在的,查问题真是差了哪一环都不行啊。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    架构师社区 内存泄漏 Java

  • 论程序员价值:解决失误bug被涨绩效,零失误时无人问津

    最近某程序员发了一个令自己奇怪的事儿:说自己已加班一年有余,工作从不敢出错,怕出现bug失误,平时和领导沟通的也不多,绩效稳定在3.5。 没想到最近不小心出了两次大事故,一样正常的加班,被客户感谢,领导居然还给涨了绩效。按照这个逻辑,难道是要多制造bug吗? 有网友:原来你做了什么没那么重要,反而让领导感觉到努力最重要。这就是世界运行的规律。 还有人抨击,有公司居然把程序员修复bug的数量作为考核标准,究竟是怎么想的呢?是盼着出bug吗? 真是善战者无赫赫之功,善医者无惶惶之名。这句话是什么意思呢?就是说,如果真是能够领兵当仗的能人,在战争发生以前就把问题解决了。 如果真是名医,在病还没有发出来的时候,就已经治好了病人。名将和名医反而是因打了大仗和解决了疑难杂症才出了名。 在领导的角度,如果不是这个程序员及时解决了bug,负责人就要被追责,故而看到了程序员的价值。加之一直勤勤恳恳,客户也认可。如果程序员能力不够,bug没被解决,那就另当别论了。 所以,这不是一次偶然的幸运,而是长期以来努力的幸运。 从另一个角度也说明了职场上和领导及时保持沟通有多重要。如果不是客户在群里的表扬,领导可能也不知道他这位员工晚上一直加班吧,有可能要默默无闻更长一段时间了。 想想历史上所谓的”小人,奸臣“等哪一个不是因为和皇上离得近,又善察人心人性而得宠,反而是在外打仗的大将军,如不能遇名主,被小人构陷谋反也常常百口莫辩。 当然,不是让我们成为一个趋炎附势之人,而是在有能力的基础上,和上司走的近一些,一定仕途会更平坦。且在团队出现信任危机时,决不会成为最先被牺牲的那一个。 除了会干活,还得会为人处事,才能混好职场。

    架构师社区 程序员 bug

  • 今天说的是必须要熟练掌握的归并排序

    之前给大家介绍了几个简单排序,大家只需了解即可,下面介绍的大家就需要熟练掌握了,是面试高频考点,该文章分别用了递归法和迭代法实现 2 路归并,希望对大家有一丢丢的帮助。 归并排序 (Merge Sort) 归并排序是必须要熟练掌握的排序算法,是面试高频考点,下面我们就一起来扒一扒归并排序吧,原理很简单,大家一下就能搞懂。 袁记菜馆内 第 23 届食神争霸赛开赛啦! 袁厨想在自己排名前4的分店中,挑选一个最优秀的厨师来参加食神争霸赛,选拔规则如下。 第一场 PK:每个分店选出两名厨师,首先进行店内 PK,选出店内里的胜者 第二场 PK: 然后店内的优胜者代表分店挑战其他某一分店的胜者(半决赛) 第三场 PK:最后剩下的两名胜者进行PK,选出最后的胜者。 示意图如下 厨神争霸赛 上面的例子大家应该不会陌生吧,其实我们归并排序和食神选拔赛的流程是有些相似的,下面我们一起来看一下归并排序吧。 归并这个词语的含义就是合并,并入的意思,而在我们的数据结构中的定义是将两个或两个以上的有序表合成一个新的有序表。而我们这里说的归并排序就是使用归并的思想实现的排序方法。 归并排序使用的就是分治思想。顾名思义就是分而治之,将一个大问题分解成若干个小的子问题来解决。小的子问题解决了,大问题也就解决了。分治后面会专门写一篇文章进行描述,这里先简单提一下。 下面我们通过一个图片来描述一下归并排序的数据变换情况,见下图。 归并排序 我们简单了解了归并排序的思想,从上面的描述中,我们可以知道算法的归并过程是比较难实现的,这也是这个算法的重点,我们先通过一个视频来看一下归并函数的具体步骤,看完我们这个视频就能懂个大概啦。 视频中归并步骤大家有没有看懂呀,没看懂也不用着急,下面我们一起来拆解一下,归并过程共分三步走。 第一步:创建一个额外大集合用于存储归并结果,长度则为那两个小集合的和,从视频中也可以看出 第二步:我们从左自右比较两个指针指向的值,将较小的那个存入大集合中,存入之后指针移动,并继续比较,直到某一小集合的元素全部都存到大集合中。见下图 合并 第三步:当某一小集合元素全部放入大集合中,则需将另一小集合中剩余的所有元素存到大集合中,见下图 好啦,看完视频和图解是不是能够写出个大概啦,了解了算法原理之后代码写起来就很简单啦, 下面我们看代码吧。 注:这里用了System.arraycopy(),大家也可以使用其他方法,其中的五个参数分别是,源数组,目的数组,源数组起始索引,目的数组放置的起始索引,复制的长度 class Solution { public int[] sortArray(int[] nums) {         mergeSort(nums,0,nums.length-1); return nums;     } public void mergeSort(int[] arr, int left, int right) { if (left < right) { int mid = left + ((right - left) >> 1);             mergeSort(arr,left,mid);             mergeSort(arr,mid+1,right);             merge(arr,left,mid,right);         }     } //归并 public void merge(int[] arr,int left, int mid, int right) { //第一步,定义一个新的临时数组 int[] temparr = new int[right -left + 1]; int temp1 = left, temp2 = mid + 1; int index = 0; //对应第二步,比较每个指针指向的值,小的存入大集合 while (temp1 <= mid && temp2 <= right) { if (arr[temp1] <= arr[temp2]) { temparr[index++] = arr[temp1++]; } else {                 temparr[index++] = arr[temp2++];             }         } //对应第三步,将某一小集合的剩余元素存到大集合中 if (temp1 <= mid) System.arraycopy(arr, temp1, temparr, index, mid - temp1 + 1); if (temp2 <= right) System.arraycopy(arr, temp2, temparr, index, right -temp2 + 1);             System.arraycopy(temparr,0,arr,left,right-left+1);      } } 归并排序时间复杂度分析 我们一趟归并,需要将两个小集合的长度放到大集合中,则需要将待排序序列中的所有记录扫描一遍所以时间复杂度为O(n)。 归并排序把集合一层一层的折半分组,则由完全二叉树的深度可知,整个排序过程需要进行 logn(向上取整)次,则总的时间复杂度为 O(nlogn)。 另外归并排序的执行效率与要排序的原始数组的有序程度无关,所以在最好,最坏,平均情况下时间复杂度均为 O(nlogn) 。 虽然归并排序时间复杂度很稳定,但是他的应用范围却不如快速排序广泛,这是因为归并排序不是原地排序算法,空间复杂度不为 O(1),那么他的空间复杂度为多少呢? 归并排序的空间复杂度分析 归并排序所创建的临时结合都会在方法结束时释放,单次归并排序的最大空间是 n ,所以归并排序的空间复杂度为 O(n). 归并排序的稳定性分析 归并排序的稳定性,要看我们的 merge 函数,我们代码中设置了 arr[temp1] <= arr[temp2] ,当两个元素相同时,先放入arr[temp1] 的值到大集合中,所以两个相同元素的相对位置没有发生改变,所以归并排序是稳定的排序算法。 等等还没完嘞,不要走呀。 归并排序的 递归实现是比较常见的 ,也是比较容易理解的,下面我们一起来扒一下归并排序的迭代写法。看看他是怎么实现的。 我们通过一个视频来了解下迭代方法的思想 是不是通过视频了解个大概啦,下面我们来对视频进行解析。 迭代实现的归并排序是将小集合合成大集合,小集合大小为 1,2,4,8,…..。依次迭代,见下图 比如此时小集合大小为 1 。两个小集合分别为 [3],[1]。 然后我们根据合并规则,见第一个视频,将[3],[1]合并到临时数组中,则小的先进,进而实现了排序,然后再将临时数组的元素复制到原来数组中。则实现了一次合并。 下面则继续合并[4],[6]。具体步骤一致。所有的小集合合并完成后,则小集合的大小变为 2,继续执行刚才步骤,见下图。 此时子集合的大小为 2 ,则为 [2,5],[1,3] 继续按照上面的规则合并到临时数组中完成排序。这就是迭代法的具体执行过程, 下面我们直接看代码吧。 注:递归法和迭代法的 merge 函数代码一样。 class Solution { public int[] sortArray (int[] nums) { //代表子集合大小,1,2,4,8,16..... int k = 1; int len = nums.length; while (k < len) { mergePass(nums,k,len); k *= 2;         } return nums;     } public void mergePass (int[] array, int k, int len) { int i; for (i = 0; i < len-2*k; i += 2*k) { //归并 merge(array,i,i+k-1,i+2*k-1);          } //归并最后两个序列 if (i + k < len) { merge(array,i,i+k-1,len-1);          }     } public void merge (int[] arr,int left, int mid, int right) { //第一步,定义一个新的临时数组 int[] temparr = new int[right -left + 1]; int temp1 = left, temp2 = mid + 1; int index = 0; //对应第二步,比较每个指针指向的值,小的存入大集合 while (temp1 <= mid && temp2 <= right) { if (arr[temp1] <= arr[temp2]) { temparr[index++] = arr[temp1++]; } else {                 temparr[index++] = arr[temp2++];             }         } //对应第三步,将某一小集合的剩余元素存到大集合中 if (temp1 <= mid) System.arraycopy(arr, temp1, temparr, index, mid - temp1 + 1); if (temp2 <= right) System.arraycopy(arr, temp2, temparr, index, right -temp2 + 1); //将大集合的元素复制回原数组 System.arraycopy(temparr,0,arr,left,right-left+1);      } } 通过上面的视频解析和代码,希望大家能够将归并排序给拿下,下面会给大家写一下,归并排序在实际刷题时的应用,感谢阅读。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    架构师社区 面试 归并排序 分治思想

  • 从入职快手3年股票3000w说起

    突然想起来一个在快手做技术管理的哥们,2017年左右入职的,于是赶紧问问他财富自由没: 真是没想到,这个人生效率的确太赞了。他17年去的快手,当时快手才几百人,公司估值不到40亿美金。这哥们除了快手还拿到了百度阿里的Offer,相比之下快手的薪资不到大厂的2/3,但快手给了一些期权。 能不能压中下一个快手 我们要思考一个问题:这位兄弟的经验对于刚入职场的朋友有没有一些启发? 一位刚毕业1年的女生提问,如何去做判断,加入一家未来比较有IPO前途的公司? 从公开数据可以看到,Wish在2017年、2018年、2019年运营亏损分别为1.47亿美元、2.23亿美元、1.44亿美元;Wish在2020年前9个月运营亏损为1.2亿美元,上年同期的运营亏损为2400万美元。 加入大公司还是小公司 要知道,小A 曾经以为AirbnbAirbnb,比较cool。 你问小A怎么看自己的经历?如果在2019年底2020年初,他的答案大概率是去大公司就挺好,稳定的收入和发展机会。但是当Airbnb是他正确的选择。 所以,提前去做大而空的选择,没有太大的必要性。小A的答案是舒服比较重要,在著名的Google自己比较难受;而关注增长 有读者又说了,我换了几家小公司,为啥都没成。我运气咋就不好了。成功本来就是概率事件,被报道的成功case放在整体分母里面是小概率事件。但是从业务增长可以感受到一家公司业务发展情况,而帮助你做选择判断。 一位哥们经常一起撸串。前2年这位老哥跟我聊起他的情况,确实发展不错。技术团队绝大部分都都是他在管理,相当于副CTO。而且从技术、产品团队延伸到运营,知识体系越来越全面。我说,你们的流量主要还是来自CEO的个人品牌,发一条微博的流量导入占了总流量的7、8成,如果这2年这个模式始终如此,业务的天花板是明显的...... 最近一次撸串,这位老哥已经自己创业大半年了。祝顺利! 程序员如何逆袭,如何实现财务自由? 2、找到靠谱的创业公司和诚信的创始人,拿到百分比左右的股份,坚守到出售或者上市。 4、经历长时间职场竞争,最终成长为公司高管,通过股份实现财富自由。 6、...... 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    架构师社区 程序员 快手

  • 为什么CTO、技术总监、架构师都不写代码,还这么牛逼?

    常常会被问到这样的问题:CTO、技术总监、架构师很少写具体代码,为什么还很牛逼的样子,拿这么高工资? 其实,这个问题本身就错了。就好比问:导演、制片人为什么不懂演戏,还能指导演员,好像比演员厉害似的?其实不难理解,导演、制片人的核心能力并不是演戏,又怎么能跟演员作比较呢? 回答前面的问题,逻辑也是一样的,拿CTO、技术总监、架构师,跟程序员比写代码的能力,本身就是个错误。因为,他们的核心能力是不一样的。 CTO、技术总监、架构师的核心能力是技术判断力。简单来讲,就是判断一个项目、一个系统架构、某个技术方向,是否符合企业当前现状,是否对企业的未来产生价值。 程序员的核心能力是写代码的能力。就是做具体的代码实现。 所以CTO/技术总监/架构师,跟程序员的核心能力,是完全不一样的能力,是没法作直接比较的。 通常我们说,CTO、技术总监、架构师们很牛逼,指的是他们的技术判断力牛逼,而不是他们写代码的能力牛逼。相反,他们写代码的能力可能还比不上一个资深程序员。 但是,他们所做的技术判断,给公司带来非常高的价值。比如,阿里云创始人王坚博士,在所有人反对的时候,他坚持云计算是未来,帮助阿里提前布局云计算,为阿里成长为万亿商业帝国,立下汗马功劳。这就是技术判断力,给企业带来的巨大价值。 大家之所以都很容易混淆这几个角色,以及它们的职责,其实很重要的一个原因就是,人们常常把CTO、架构师、技术经理的头衔,乱授予技术负责人。在国内这种现象尤其严重。 比如,一个初创公司的技术负责人,实际干的活就是个技术经理。老板为了显得高大上,硬要给他安一个CTO的头衔。这种情况很普遍,特别是全民创业的那几年,遍地都是CTO。 为了说清楚技术负责人的职责,我们以一个电商公司的成长为例,讲解企业在初创期、发展期、成熟期的不同阶段,都需要什么样的技术负责人,以及他们具体都干些什么。 第一阶段,高级程序员 实现复杂功能,解决技术难题 一个刚刚起步的创业公司,通常只有几个程序员,甚至连产品经理、项目经理都没有,老板自己就是产品经理,把想法跟开发人员一说,就快速地做出原型。 如果这个阶段对开发的能力不满,那么大概需要的只是一个高级开发人员,他能搞定一般的技术难题,实现复杂功能,思路清晰、干活利索。千万不要去大厂挖个技术总监,你家庙太小,供不起这么大的神,他真来了也发挥不了应有的作用。 老读者知道,老K有过一段创业经历,当时追随我的老领导出来创业,我就是名义上的CTO,带了7、8人的团队,我还同时带了两个项目,每个项目里我都贡献了30%以上的代码量。其实,当时的我,就是个高级程序员而已。 小结一下,高级程序员的主要职责是: 1,实现复杂功能,编写核心代码; 2,处理线上bug,解决技术难题。 第二阶段,技术经理 交付效率提高、质量提升 当公司的业务发展起来后,就需要一支相对完善的技术团队,有了专职产品经理、测试人员等,团队规模在15人左右,专注于一条产品线。 复杂功能、技术难题,高级开发人员可以搞定,但是如果要解决开发团队效率、技术人员能力提升、代码质量和编码规范等,就需要技术经理了。技术经理通常写少量的代码,更多做技术管理、项目团队等工作。 这就是许多创业公司A轮融资前的情况。由技术经理总体负责技术团队,产品经理对接业务需求,做产品规划、竞品分析,而不是抄袭哪个App。 小结一下,技术经理的职责是: 1、开发任务分派。开发工作量评估、分派,最大化资源利用率; 2、代码质量提升。Code Review、编码规范、线上bug分析; 3、项目管理。确保项目的按时交付,建立管理机制; 4、团队管理。团队搭建、人员招聘、人员培养。 第三阶段,技术总监 技术规划、多产品线、项目群管理 当技术团队发展到30人左右,有了多条核心产品线、有了多个技术经理时,就需要一个技术总监了。 技术总监,作为领域专家,站在更高的层面思考技术如何建立壁垒,构建技术竞争力。逐步开始建立公共技术平台,协调多条产品线在统一的技术平台上快速迭代,让产品线跑得快、跑得稳。 技术总监,在领域内有多年沉淀,来自知名互联网企业,能够把技术团队带上一个新的台阶。技术总监,更多是做技术判断了,也有些技术型的技术总监仍然会写些核心代码、做架构设计。 技术总监的职责: 1、搭建公司技术平台部,统一技术栈; 2、建立产品研发体系,让技术团队可持续性地快速交付; 3、管理和协调多条产品线,打造明星产品; 4、建立技术壁垒,形成技术竞争力; 第四阶段,架构师 架构设计、架构实现、架构评审 公司如果“跑到”了B轮,技术团队应该要接近百人了,此时的技术团队跟初创时期相比,已经很不错了。 有技术总监协调着各产品线,有开发经理带领技术团队快速迭代产品。代码规范、最佳实践的总结和推广也在逐步开展。 此时,需要把架构规划和架构评审的职能从技术总监和开发经理身上剥离,即分离专业岗和管理岗,专业人做专业事。 这时候就需要设立架构师岗位,专注于技术架构分析、架构设计、架构实现、推动重构、推行架构原则等工作,让技术总监和技术经理侧重在项目管理、团队管理。 架构师的职责是: 1、业务架构设计和实现。根据业务规划和应用场景,设计切合当前业务要求,并且具备一定前瞻性的应用架构、类、接口、业务抽象及业务建模等。 2、架构设计和实现。识别非功能性需求,如性能、可扩展性、安全性、高可用及易部署等。 3、重构计划及执行。关注全链路监控数据、线上bug、系统预警等信息,识别架构缺陷,提出重构建议并推动执行。 第五阶段,CTO 技术产品战略规划,提升技术竞争力 当技术团队有了几名总监、架构师,人数达到几百人,是时候引入真正意义上的CTO了,除非CTO是联合创始人,否则这个CTO会有“虎落平阳”的感觉,公司也会觉得这个人“满嘴跑火车,却落不了地”。 国内的中大型互联网公司,一般有产品VP和技术VP,有的技术VP就是CTO。如果CTO统管技术和产品,那么产品VP就给CTO汇报,否则他们是平级的。 在国外,CTO主要研究3~5年的技术发展趋势,为公司做中长期的技术规划,是具有行业影响力的技术大咖,公司技术领域的精神领袖。CTO较少关注当下的具体事务,这类工作主要由工程副总裁们处理。 以国内互联网公司CTO为例,总结一下CTO的主要职责: 1、技术赋能商业。敏锐的商业洞察、深入的产业研究、参与公司战略规划,技术引领业务增长,通过技术和产品实现战略落地。 2、技术趋势研究。思考未来3~5年的技术发展趋势,以及新技术发展给企业带来的机遇和风险,为企业提前布局。 3、技术治理体系。持续的过程改进、高效的研发流程、稳定的交付质量、高可用的系统。 4、组织与文化。建设学习型组织、自我完善型组织,建立符合企业特色的文化氛围。 结语 最后,不想当CTO的程序员,不是好骑手。从程序员到CTO的成长过程,需要不断提升技术能力、产品能力、项目能力、管理能力、商业视野、个人影响力、行业人脉等等。除了自身的奋斗之外,机会和运气同样重要,而且是可遇不可求的。但是,梦想还是要有的,万一见鬼了呢。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    架构师社区 代码 架构师 CTO

  • 快手基于 RocketMQ 的在线消息系统建设实践

    作者:黄理,10 多年软件开发和架构经验,热衷于代码和性能优化,开发和参与过多个开源项目。曾在淘宝任业务架构师多年,当前在快手负责在线消息系统建设工作。 为什么建设在线消息系统 在引入 RocketMQ 之前,快手已经在大量的使用 Kafka 了,但并非所有情况下 Kafka 都是最合适的,比如以下场景: 业务希望个别消费失败以后可以重试,并且不堵塞后续其它消息的消费。 业务希望消息可以延迟一段时间再投递。 业务需要发送的时候保证数据库操作和消息发送是一致的(也就是事务发送)。 为了排查问题,有的时候业务需要一定的单个消息查询能力。 为了应对以上这类场景,我们需要建设一个主要面向在线业务的消息系统,作为 Kafka 的补充。在考察的一些消息中间件中,RocketMQ 和业务需求匹配度比较高,同时部署结构简单,使用的公司也比较多,于是最后我们就采用了 RocketMQ。 部署模式和落地策略 在一个已有的体系内落地一个开源软件,通常大概有两种方式: 方式一:在开源软件的基础上做深度修改,很容易实现公司内需要的定制功能。但和社区开源版本分道扬镳,以后如何升级? 方式二:尽量不修改社区版本(或减少不兼容的修改),而是在它的外围或者上层进一步包装来实现公司内部需要的定制功能。 注:上图方式一的图画的比较极端,实际上很多公司是方式一、方式二结合的。 我们选择了方式二。最早的时候,我们使用的是 4.5.2 版本,后来社区 4.7 版本大幅减小了同步复制的延迟,正好我们的部署模式就是同步复制,于是就很轻松的升级了 4.7 系列,享受了新版本的红利。 在部署集群的时候,还会面临很多部署策略的选择: 大集群 vs 小集群 选择副本数 同步刷盘 vs 异步刷盘 同步复制  vs 异步复制 SSD vs 机械硬盘 大集群会有更好的性能弹性,而小集群具有更好的隔离型,此外小集群可以不需要跨可用区 /IDC 部署,所以会有更好的健壮性 。我们非常看重稳定性,因此选择了小集群。集群同步复制异步刷盘,首选 SSD。 客户端封装策略 如上所述,我们没有在 RocketMQ 里面做深度修改,所以需要提供一个 SDK 来实现公司内需要的定制功能,这个 SDK 大概是这样的: 对外只提供最基本的 API,所有访问必须经过我们提供的接口。简洁的 API 就像冰山的一个角,除了对外的简单接口,下面所有的东西都可以升级更换,而不会破坏兼容性。 业务开发起来也很简单,只要需要提供 Topic(全局唯一)和 Group 就可以生产和消费,不用提供环境、NameServer 地址等。SDK 内部会根据 Topic 解析出集群 NameServer 的地址,然后连接相应的集群。生产环境和测试环境环境会解析出不同的地址,从而实现了隔离。 上图分为 3 层,第二层是通用的,第三层才对应具体的 MQ 实现,因此,理论上可以更换为其它消息中间件,而客户端程序不需要修改。 SDK 内部集成了热变更机制,可以在不重启 Client 的情况下做动态配置,比如下发路由策略(更换集群 NameServer 的地址,或者连接到别的集群去),Client 的线程数、超时时间等。通过 Maven 强制更新机制,可以保证业务使用的 SDK 基本上是最新的。 集群负载均衡 & 机房灾备 所有的 Topic 默认都分配到两个可用区,生产者和消费者会同时连接至少两个独立集群(分布在不同的可用区),如下图: 生产者同时连接两个集群,如果可用区 A 出现故障,流量就会自动切换到可用区 B 的集群 2 去。我们开发了一个小组件来实现自适应的集群负载均衡,它包含以下能力: 千万级 OPS 灵活的权重调整策略 健康检查支持/事件通知 并发度控制(自动降低响应慢的服务器的请求数) 资源优先级(类似 Envoy,实现本地机房优先,或是被调服务器很多的时候选取一个子集来调用) 自动优先级管理 增量热变更 实际上它并不仅仅用于消息生产者,而是一个通用的主调方负载均衡类库,可以在 Github 上找到: 总结 得益于简单、几乎 0 依赖的部署模式,使得我们部署小集群的成本非常低;不对社区版本进行魔改,保证我们可以及时升级;统一 SDK 入口方便集群维护和功能升级;通过复合小集群+自动负载均衡实现多机房多活;充分利用 RocketMQ 的功能,比如事务消息、延迟消息(增强)来满足业务的多样性需求;通过自动的分布式对账,对每一个 Broker 以及我们的 SDK 进行正确性监控。 本文也进行了一些性能参数的分享,但写的比较简单,基本只说了怎么调,但没能细说为什么,以后我们会另写文章详述。目前 RocketMQ 已经应用在公司在大多数业务线,期待将来会有更好的发展!

    架构师社区 Kafka RocketMQ 在线消息系统

  • 与一位转行做滴滴司机的前程序员对话引发的思考

    昨天晚上由于没赶上班车,所以打开了滴滴叫了一辆快车,上车后看这司机小伙子挺斯文的,简单聊了几句,没想到居然是位前程序员,一开始还以为是兼职,结果聊完之后才知道是全职,确实是大吃一惊,仔细一问原委才知道,原来是去年因为疫情原因被裁,但是之后一直没找到工作,但生活总得继续吧,于是选择了全职转行做滴滴这条路。由于我司到地铁只有几分钟的车程,很快就到站了,没法再继续细聊下去,但是听完之后,不胜唏嘘。 每个人都有自己的选择,旁人确实不好评价,可能在当时的情况下转行做滴滴是他作出的最利于摆脱当前困境的最佳选择,就像前段时间很热门有三位程序员相约考公成功上岸这事一样,在我们看来,他们放弃了程序员的高薪,可能难以理解,但对他们而言,摆脱了 996,每天准点下班,发季线低了,也能常和老婆孩子热炕头,这样的生活别提有多惬意了,都云作者痴, 谁解其中味? 虽然理解他们的选择,职业也并无贵贱之分,但肯定有好坏之别,那么什么样的职业是好,什么样的职业是坏呢? 好职业与坏职业 我觉得好职业应该是上限高,有想象力的,不好的职业则是有上限,自己的未来一眼就能看穿,看得到头的。 啥意思? 比如说吧以滴滴司机为例,每天的收入与你跑的单数成正比,可能每天你都非常努力地跑单,拼死拼活一天能赚个一千多,但当你停止接单时,你的收入也就停止了,清洁工,餐饮里跑腿的也是一样,这一类工作只要你停止劳动了,你的收入也就戛然而止了,而且这类工作需要付出极大的体力消耗,每天劳累了一天,根本没有时间思考人生,第二天开始又重复机械地劳作,可替代性极高,而我们知道职场的收入与你的不可替代性是成正比的,所以这类工作我认为并不是好职业。 那么好职业又有哪些特性呢,我觉得有两点,一是能持续打造自己的稀缺性,比如程序员行业,随着你技能的不断精进,你的待遇,不可替代性自然会越来越强,二是想象空间足够高,比如说自媒体,可能你写的文章没人看,但也有可能写出 10w+ 的爆文,而且做自媒体长尾效应明显,只要你的文章/视频等足够好,在很长的时间内你都可以借此不断获得关注,长尾效应明显,在当今时代,流量就是钱啊,所以只要你用心写好文章,就可以借此获得源源不断地关注,认可,也就是说当你停止写作时,你过去的作品还在不断地吸引很多人关注你,为你创造价值,这就是为啥我一直坚持写作,也鼓励大家写作的原因,从这个角度来看,自媒体确实是一个好职业! 工程师如何抵御风险 为了避免别人觉得我在说教,我先简单亮一下自己的成绩,目前的被动收入(副业+理财等)已能 cover 包括房贷等在内的生活开支,老婆的一个投资项目每年也能稳定收益 20 w 左右,生活上只要不出现特别大的变故,可以说没有任何问题,所以我说的一些经验可能对大家有些借鉴意义,希望能给大家带来一些启发。 首先当然是把自己的本职工作做好,技术越强,职位越高,你的竞争力也就越强,现在虽然是寒冬时期,招聘标准越来越高,但从全局来看,技术优秀的候选者去头条,拼多多这样的大厂呆个十年运气好挣个千万依然不是什么大问题问题,但不得不承认的是,90% 的工程师想要拿到百万年薪确实很难,我司之前一位总裁就说过,P8 以下我们认为都是可以培养的,P8及以上看天赋,升职除了能力之外,可能多少也掺杂着些运气。 所以我们应该怎么办,之前有提过,这里再搬出之前 linkedln 和 paypal 的联合创始人ReidHoffman 提出的 ABZ 理论吧。 A: 是你正在从事的工作,也是能长期从事下去的工作,值得你持续投入,并可以获得安全感,并且这份工作,你个人还很满意。 B: 是除去 A 计划外,业余时间你给自己其他能力的培训,或者兴趣爱好或梦想,可以认为是副业,这样的话万一主业出现问题,哪天被机器人替代了,由于你有自己的副业,可以立马转为 A,可以让你从容应对。 Z: 即个人资产,是你的保障,也是你的退路。假设有一天你的 AB 计划全部落空失败,你的 Z 计划,可以保证你在未来某一段时间内,可以继续保持现有的生活品质,能给你一次从头再来的机会。我的理解就是理财,比如基金,定投,股票,房产投资等。 在 IT 界并不是每个人都是人中龙凤,达到百万年薪并不是那么容易,那么我们是否可以考虑做斜杠青年呢,可能我们在某种能力上并不突出,但可以在沟通,写作,影响力上多向发展构建自己的 IP 矩阵,这些能力的关系可能是「能力 1 x 能力 2 x 能力 n」这样的乘法关系,无形中会让我们的竞争力大大提高,IT 人可能大部分人听过侯捷,翻译了很多畅销书,我记得业内有位大牛评论侯捷老师时说,单论技术能力算中上,离顶尖有距离,但他翻译的书非常好,基本都是畅销书,就就构建了强大的影响力,这就像现在技术公号领域最知名的博主 stormzhang 一样,技术并非顶尖,但通过投资,写作等构建了坚实的护城河,他们未必有顶尖的技术,但为读者提供了一流的服务,进而构建了自己影响力。 需要注意的是在斜杠青年的尝试中,这些能力最好是对主业有促进作用,反哺主业的,比如写作,通过写作你巩固了对知识点的理解,厘清了各个模糊的概念,由点及面构建了自己的知识体系,也锻炼了自己的写作,表达能力等,这对主业就有极大的促进作用,千万慎重考虑由程序员转行做滴滴这样的事,因为这是两个完全不同的行业,一旦转行,你之前行业的积累就没有,相当于从头开始,而且就我们以上的分析来看,滴滴这样的职业上限有限,一眼望得到头,不是一份好职业。 我最近做了淘客,一些朋友看来有点「不务正业」,但其实一来我的业务从之前负责的金融转到淘客来了,从事淘客有助于我对本身的业务有更深的理解,二来淘客其实也是一个长尾效应很明显的行业,回报也是比较丰厚的(我看到最夸张的案例是一位淘客一个月躺赚 30w,你没有看错,是一个月 30w!),而且要做好淘客,你要懂得些拉新,精细化运营等思路,这样来看又锻炼了你的产品和运营思维,而程序员具备了这些思维或思考,不也打造了自己的业务能力了吗?不管怎么看,都对你的主业有很大的促进作用!这就是我做淘客背后的逻辑。 说了这么多最重要的我觉得是不要给自己设限,在保证自己主业的前提下多去尝试,尽量构建自己的能力矩阵。 再说投资,一般人可能想得到的投资就是股票,理财,还有房子,不过其实还有一种投资回报也很丰厚,比如投资「密室逃脱」这种新兴项目,上文我说的老婆投资每年能躺赚 20w 的项目就是这个,前期投资,后期回本后躺赚的这种,当然这种投资需要有眼光,更需要勇气,我建议是不要超过家庭总收入的四分之一,这样万一失败也不至于影响到正常的家庭生活水平。 最后 希望本文对你有所启发,另外前几天在朋友圈分享了做淘客的一点心得,没想到这么多人感兴趣,本周将会在我的另一个号「程序员坤哥」上分享出来,希望对想做淘客的朋友一定会有帮助!如果想看的可以关注这个号哦。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    架构师社区 程序员 职业 滴滴司机

  • 不会MySQL索引,面试官让回家等通知!

    你是不是对于 MySQL 索引的知识点一直都像大杂烩,好像什么都知道,如果进行深究的话可能一个也答不上来。 假如你去面试,面试官让你聊一下对索引的理解,然而你对索引的理解仅限于,检索数据就是快,是一种数据结构这个层面,那你就只能回家等通知了。 为了避免这种尴尬的事情发生,咔咔用时两天将索引的内容在自己理解的范围内进行了整理,如有整理不全面的地方可以在评论区进行补充和提建议。 MySQL 索引到底是什么 相信大多数伙伴都买过技术类的书籍,看完没看完不知道,但是目录肯定看的次数最多。 看目录有没有自己目前的痛点,如果有就会根据目录对应的页码用最快的速度翻阅到相应内容位置。 那么在 MySQL 中同样也是这样的一个道理,MySQL 的索引就是存储引擎为了快速找到数据的一种数据结构。 同样在 MySQL 索引中又分了几种类型,分别为: B-tree 索引 哈希索引 空间索引 全文索引 下文所有内容均在 InnoDB 的基础上讨论。 为什么要使用索引 ①索引可以加快数据检索速度,这也是使用的索引的最主要原因。 ②索引本身具有顺序性,在进行范围查询时,获取的数据已经排好了序,从而避免服务器再次排序和建立临时表的问题。 ③索引的底层实现本身具有顺序性,通过磁盘预读使得在磁盘上对数据的访问大致呈顺序的寻址,也就是将随机的 I/O 变为顺序 I/O。 这几点不理解就暂时先放着,继续看下文即可,会给你一个满意的解释。 任何事物都存在双面性,既然能提供性能的提升,自然在其他方面也会付出额外的代价: 索引是跟数据共存,因此会占用额外的存储空间。 索引创建和维护需要时间成本,这个成本随着数据量的增大而增大。 索引创建会降低数据的增、删、改的性能,因为在修改数据的同时还需要修改索引数据。 InnoDB 为什么使用 B+Tree 而不使用 BTree 聊到这个问题那就必须得分清楚 BTree、B+tree 的区别,首先来看一下 BTree。 Btree 解析 先来看一下 BTree 的数据结构是怎么样的,这里咔咔给提供一个网站地址,可以看到关于数据结构的一些实现过程: https://www.cs.usfca.edu/~galles/visualization/Algorithms.html 先来看 BTree 的数据结构,下图是咔咔已经将数据填充进去的: 这里有一个陌生区关于 Max. Degree,这个你可以理解为阶,也可以理解为度。 例如现在这个值设置的是 4,那么在一个节点中最多就可以存储 3 条数据,设置为 5那就可以最多放 4 条记录。 现在可以看到目前只插入了 3 条数据: 那么再加一条数据,节点就会进行分裂,这个也就验证了当阶设置为 n 时,一个节点可存 n-1 条数据。 那接着再来插入几条数据看看: 想要达到快速检索数据,那就需要满足俩个特性,一个是有序,另一个就是平衡。 从下图中可以看到 BTree 是有一定的顺序性的,平衡性更满足,可以看上文中生成的第一张图。 那么在 BTree 中找一个值是怎么找呢?例如现在要找一个值 9,看一下寻找过程。 首先看到的数据是 4,9 是大于 4 的,所以会往 4 的右节点寻找。继续找到范围在 6 到 8 的节点,9 又大于 8,所以还需要往右节点寻找。 最有一步就找到了数据 9,这个过程就是 BTree 数据结构查找数据的执行过程。 了解到了 BTree 的数据结构后,我们在来看看在 MySQL 中关于 BTree 是如何存储的。 在下图中 P 代表的是指针,指向的是下一个磁盘块。在第一个节点中的 16、24 就是代表我们的 key 值是什么。date 就是这个 key 值对应的这一行记录是什么。 那么此时想要寻找 key 为 33 的这条记录应该怎么找。33 在 16 和 34 中间,所以会去磁盘 3 进行寻找。 在磁盘 3 中进行判断,指针指向磁盘 8。在磁盘 8 中即可获取到数据 33,然后将 data 返回。 那么在这个过程中到底读取了多少条数据呢?在计算之前需要先了解一些知识点。 从 MySQL 5.7 开始,存储引擎默认为 innodb,并且 innodb 存储引擎用于管理数据的最小磁盘单位就是页。 这个页的类型也分为好几种,分别为数据页,Undo 页,系统页,事物数据页。 一般说到的页都是数据页。默认的页面大小为16kb,每个页中至少存储2条或以上的行记录。 那么根据 BTree 数据查找的过程中可以得知一共读取了三个磁盘,那么每个磁盘的大小就是 16kb。 而目前的给的案例寻找了三层,那么三层存储的数据就是:16kb*16kb*16kb=4096kb。 如果按照一条记录所需内存 1kb,那么这三层的 BTree 就可以存储 4096 条记录。 各位数据库的数据少则几百万,多则几千万数据,那么 BTree 的层级就会越来越深,相对的查询效率也会越来越慢。 这个时候是不是应该思考一个问题,那就是为什么在 Btree 中 48kb 的内存怎么就只能存储 4000 多条记录? 问题就出现在 data 上,要知道在计算数据大小时指针地址和 key 的内存都是没有计算在内的,单单就计算了 data 的内存。 因为在 BTree 结构中,节点中不仅存储的有 key、指针地址还有对应的数据,所以就会造成单个磁盘存储的数据相对很少的原因。 为了解决单个节点存储数据量小的问题,于是就演变出另一种结构,也就是下文提到了 B+Tree。 B+Tree 解析 依然如初看一下 B+Tree 的数据结构。为了方便对比,将 BTree 和 B+Tree 的数据结构放到了一起。 那么可以看到在 B+Tree 中叶子节点是存放了全量的数据,而非叶子节点只存储了 key 值。 咦!这不是就很好的解决了 BTree 带来的问题吗?可以让每个节点存储更多的数据。每个节点存储的数据越多,那么相对的就是树的深度就不会过深。 了解到了 B+Tree 的数据结构后,我们在来看看在 MySQL 中关于 B+Tree 是如何存储的。 从上图很明显就可以看到两点不同: 第一点:B+Tree 所有的数据都存储在叶子节点上。 第二点:B+Tree 所有的叶子节点之间是一种链式环结构。 那么在这个过程中到底读取了多少条数据呢? 如果说 B+Tree 读取数据的深度跟 B-Tree 的深度一样,都是三层,那么同样的道理每个磁盘的大小为 16kb。 那在 B+Tree 中非叶子节点可以存储多少数据呢!一般来说我们每个表都会存在一个主键。 根据三层来计算,第一层跟第二层存储的是 key 值,也就是主键值。 都知道 int 类型所占的内存时 4Byte(字节),指针的存储就给个 6Byte,一共就是 10Tybe,那么第一层节点就可以存储 16*1000/10=1600。 同理第二层每个节点也是可以存储 1600 个 key。 第三层是叶子节点,每个磁盘存储大小同样安装 BTree 的计算一样,每条数据占 1kb。 那么在 B+Tree 中三层可以存储的数据就是 1600*1600*16=40960000。 从这点来看 B+Tree 存储的数据跟 BTree 存储的数据根本就不是一个级别。 所以可以得出结论: B+Tree 能保证检索的数据量相对 BTree 是最多的,而且存储的数据量也是最多的。 B+Tree 选择索引时尽量选择所占内存空间小的类型,比如 int 类型。 key 所占内存越小,在节点中存储的范围就越多。 Hash 索引 先来创建一个 hash 索引: alter table user add index hash_gender using hash(gender); 存储引擎使用的是 innodb: 会发现 name 的索引类型还是为 Btree,在 innodb 上创建哈希索引,被称之为伪哈希索引,和真正的哈希索引不是一回事的,这点一定要明白。 在 Innodb 存储引擎中有一个特殊的功能叫做,自适应哈希索引,当索引值被使用的非常频繁时,它会在内存中基于 BTree 索引之上再创建一个哈希索引,那么就拥有了哈希索引的一些特点,比如快速查找。 哈希索引就是基于哈希表实现的,假设对 name 建立了哈希索引,则查找过程如下图所示,哈希表是根据键值对进行访问的数据结构,它让检索的数据经过哈希函数映射到散列表的对应位置,查找效率非常高。 哈希索引存储的是哈希值和行指针,没有存储 key 值、字段值,但哈希索引多数是在内存完成的,检索数据是非常快的,所以对性能影响不大: 哈希索引不是按照索引值排序的,所以也就无法排序。 哈希索引只支持等值操作,不支持范围查找,在 MySQL 中只能只用 =、in 、<>。 哈希索引在任何时候都不能避免表扫描。 哈希索引在遇到大量哈希冲突时,存储引擎必须遍历链表的所有行指针,逐行比较。 B+Tree 跟 BTree 区别 经过了特别漫长的计算、画图现在基本对俩者的区别有一定认识了吧! 咔咔在这里进行总结一下: B+Tree 叶子节点上存储的是全量数据(key+data),而非叶子节点只存储 key。 B+Tree 在同样的深度下存储的数据是远远大于 BTree 的。 B+Tree 每个叶子节点都有指向下一个叶子节点的链接。这样的好处在于,我们可以从任意一个叶子节点开始遍历,获取接下来所有的数据。 B+Tree 适合做索引的原因 B+Tree 树非叶子节点只存储 key 值,因此相对于 BTree 节点可以存储更多的数据,每次读入内存的 key 值就更多,相对来说 I/O 就降低。 B+Tree 树查询效率稳定,任何数据的查找都是必须从叶子节点到非叶子节点,所以说每个数据查找的效率几乎都是相同的。 B+Tree 树的叶子节点存储的是全量数据,并且是有序的,所以说只需要遍历叶子节点就可以对所有的 key 进行扫描,在范围查找时效率更高。 以上就是关于 InnoDB 存储引擎为什么使用 B+Tree 作为索引的解析。 聚簇索引、非聚簇索引区别 聚簇索引、非聚簇索引也被称之为主索引、二级索引。那么如何区分聚簇索引和非聚簇索引呢? 首先看一下 InnoDB 引擎下,创建表生成的文件,可以看到有两个 ibd 文件。 看到这里不知道大家有没有疑问:为什么看有的文章中也会有 frm 文件呢?但是在这里怎么没有呢? 原因是在 MySQL 8.0 之后将源数据都存储到了表空间中,所以也就不存在 frm 文件喽! 都知道这个 idb 文件会存储数据信息和索引信息。那再来看一下 Myisam 存储引擎创建表生产的文件。 从图中可以看到创建一个表会生成三个文件,扩展名分别为 MYD、MYI、sdi: MYD:是表数据文件(保存数据的文件) MYI:是表索引文件(保存索引的文件) 那么就可以得出一个结论:只要数据跟索引存储在一个文件里,那就是聚簇索引,否则就是非聚簇索引。 这个时候就会有人问了,表中有主键的时候,idb 文件中存储的是主键+数据,那么当没有设置主键时怎么办呢? 记住这一句话,在 InnoDB 中,数据插入时必须跟一个索引值进行绑定,如果没有主键那就选择唯一索引,如果没有唯一索引就会选择一个 6Byte 的 rowid。 表中存在多个索引数据是如何存储的 看了上文的解释,有没有产生过一丝疑问,在 InnoDB 存储引擎下,如果存在多个索引,是不是会产生多个 idb 文件。 在 InnoDB 中数据只会保存一份,如果有多个索引,会维护多个 B+Tree,例如:表字段 id,name,age,sex。 id 设置为主键索引(聚簇索引),name 设置为普通索引,那么数据到底会存储几份呢? 不管一个表中设置多少个索引,数据只会存储一份,但是这张表会维护多个 B+Tree。 按照这个案例中 id 为主键索引,name 为普通索引,那么在这张表中就会维护俩颗 B+Tree。 id 主键索引跟数据存储在一起,name 索引所在的 B+Tree 中叶子节点存储的是主键 id 的值。 对应的图就是以下两幅图,可以好好的看一下: 最后给大家总结一个点:在 InnoDB 中,一定有聚簇索引,其它索引都是非聚簇索引。 这里简单提一下:Myisam 中只有非聚簇索引。 索引的几个技术名词 在面试中往往会问这几个关键词,分别为回表、覆盖索引、最左侧原则、索引下推,一定要知道哈! 回表 网上对回表的解释各种各样,咔咔给你说种简单易懂的,但前提是你需要把聚簇索引、非聚簇索引区分清楚。 还是用上边的案例,id 为主键索引,name 为普通索引。此时查询语句为: select id,name,age from table where name = 'kaka' 那么这条语句会先在 name 的这颗 B+Tree 中寻找到主键 id,然后在根据主键 id 的索引获取到数据并且返回。 其实这个过程就是从非聚簇索引跳转到聚簇索引中查找数据,被称为回表,也就是说当你查询的字段为非聚簇索引,但是非聚簇索引中没有将需要查询的字段全部包含就是回表。 在这个案例中,非聚簇索引 name 的叶子节点只有 id,并没有 age,所以会跳转到聚簇索引中,根据 id 在查询整条记录返回需要的字段数据。 覆盖索引 覆盖索引,根据名字都能理解的差不多,就是查询的所有字段都创建了索引! 此时查询语句为: select id,name from table where name = 'kaka' 那么这条语句就是使用了覆盖索引,因为 id 和 name 都为索引字段,查询的字段也是这俩个字段,所以被称为索引覆盖。 也就是说当非覆盖索引的叶子节点中包含了需要查询的字段时就被称为覆盖索引。 最左匹配 最左匹配原则是在组合索引中存在的。还是用之前表信息:表字段 id,name,age,sex。此时给 name,age 设置成组合索引。 以下语句中那个不符合最左侧原则: select * from table where name = ? and age = ? select * from table where name = ? select * from table where age = ? select * from table where age= ? and name= ? 可以自行做一下测验哈!是只有第三条语句不会用到索引,其他的三条语句都会符合最左侧原则。 关于这个最左侧原则远远不止这么简单的,一试就是一个坑,关于这部分内容咔咔后期会在优化文章中提到。 索引下推 还是使用这条 sql 语句: select * from table where name = ? and age = ? 索引下推是在 MySQL 5.6 及以后的版本出现的。之前的查询过程是,先根据 name 在存储引擎中获取数据,然后在根据 age 在 server 层进行过滤。 在有了索引下推之后,查询过程是根据 name、age 在存储引擎获取数据,返回对应的数据,不再到 server 层进行过滤。 当你使用 Explain 分析 SQL 语句时,如果出现了 Using index condition 那就是使用了索引下推,索引下推是在组合索引的情况出现几率最大的。 索引存储在什么地方 索引的数据文件是存储在磁盘中的,也是需要进行持久化操作。但是当使用索引时会把数据从磁盘读取到内存中,读取方式为分块读取。 这时就要涉及到操作系统的概念,操作系统在磁盘中获取数据,假设现在要取的数据大小是 1kb,但操作系统并不会只取出你需要的这 1kb,而是会取出 4kb 的数据。 为什么会是 4kb,因为在操作系统中一页的数据就是 4kb。那又为什么只需要 1kb 而取出整页的数据呢? 那就又会涉及到另一个概念那就是局部性原理:数据和程序都有聚集成群的倾向,在访问了一条数据之后,在之后有极大的可能再次访问这条数据和这条数据的相邻数据。 所以说 MySQL 的 InnoDB 存储引擎,在读取数据时也会采取这种局部性原理,每次读取的数据是 16kb。 在 InnoDB 存储引擎下每页的大小默认为 16kb,这个参数也可以进行调整,参数为 innodb_page_size。 最后一点: 既然标题问的是索引数据存储在什么地方,在第一句就直接回答了索引是存储在磁盘中,并且以页为单位进行从磁盘往内存读取。 那为什么不直接存储在内存中呢?你有没有这个疑问呢? 如果索引数据只存储在内存中,那么当电脑关机,服务器宕机之后,就需要重新生成索引,这种的效率是十分低的。 总结 以上就是咔咔对索引的理解,在尽最大的可能将知识点说全面。如果还有遗漏,或者文章中有错误的地方还请各位能给出提议。

    架构师社区 索引 MySQL InnoDB

发布文章