关于分布式系统:为什么要使用zookeeper

本文题目为《为什么要应用zookeeper》,然而本文并不是专门介绍zookeeper原理及其应用办法的文章。如果你在网上搜寻为什么要应用zookeeper,肯定能能到从zookeeper原理、实用场景到Zab算法原理等各种各样的介绍,然而看过之后是不是还是懵懵懂懂,只是学会了一些全面的、具体的知识点,还是不能文章题目的问题。zookeeper应用一种名为Zab的共识算法实现,除了Zab算法之外还有Paxos、Multi-Paxos、Raft等共识算法,实现上也有cubby、etcd、consul等独立的中间件和像Redis哨兵模式一样的嵌入式实现,这些实现都是基于相似的底层逻辑为了实用于不同场景下的工程学落地,本文的重点内容是共性的底层原理而不是具体的软件应用领导。 多线程与锁我以如何实现分布式锁为切入点,将多线程编程、锁、分布式系统、分布式系统一致性模型(线性一致性、最终一致性)、CAP定理、复制冗余、容错容灾、共识算法等一众概念有机联合起来。采纳层层递进的形式对相干概念及其互相分割开展阐述,不仅让你能将零散的知识点串连成线,而且还能站在理论利用的角度对相干概念从新思考。 之所以用分布式锁来举例,是因为在编程畛域,锁这个概念太广泛了,在多线程编程场景有同步锁、共享锁、排他所、自旋锁和锁降级等与锁无关的概念,在数据库畛域也有行级锁、表级锁、读锁、写锁、谓词锁和间隙锁等各种名词概念。实质上锁就是一种有肯定排他性的资源占有机制,一旦一方持有某个对象的锁,另一方就不能持有同一对象雷同的锁。 那么什么是分布式锁呢?要答复这个问题咱们须要先理解单机状况下锁的原理。在单机多线程编程中,咱们须要同步机制来保障共享变量的可见性和原子性。如何了解可见性和原子性呢?我用一个经典的计数器代码举例。 class Counter{ private int sum=0; public int count(int increment){ return sum += increment }}代码很简略,有过多线程编程教训的人都应该晓得count()办法在单线程下工作失常,然而在多线程场景下就会生效。原则上一个线程循环执行一百遍count(1)和一百个线程每个线程执行一遍count(1)后果应该都是100,然而理论执行的后果大概率是不雷同,这种单线程下执行正确然而多线程下执行逻辑不正确的状况咱们称之为线程不平安。 为什么在多线程下执行后果不正确呢?首先当两个线程同时执行sum=sum+1这条语句的时候,语句并不是原子性的,而是一个读操作和一个写操作。有可能两个线程都同时读取到了sum的值为0,加1操作后sum的值被两次赋值为1,这就像第一个线程的操作被第二个线程笼罩了一下,咱们称之为笼罩更新(表1)。 工夫/线程T1T2t1读取到sum值为0 t2 读取到sum值为0t3执行sum=0+1操作 t4 执行sum=0+1操作(表1) 接下来咱们再说可见性,即便两个线程不是同时读取sum的值,也有可能当一个线程批改了sum值之后,另一个线程不能及时看到最新的批改后的值。这是因为当初的CPU为了执行效率,为每个线程调配了一个寄存器,线程对内存的赋值不是间接更新,而是先更新本人的寄存器,而后CPU异步的将寄存器的值刷新到内存。因为寄存器的读写性能远远大于内存,所以这种异步的读写形式能够大幅度晋升CPU执行效率,让CPU时钟不会因为期待IO操作而暂停。 工夫/线程T1T2t1读取到sum值为0 t2执行sum=0+1操作 t3 读取到sum值为0t4 执行sum=0+1操作(表2) 咱们须要同步机制保障count()的原子性和可见性 class Counter{ private int sum=0; public synchronized int count(int increment){ return sum += increment }}如果替换为锁的语义,这段代码就相当于 class Counter{ private int sum=0; public int count(int increment){ lock(); sum += increment unlock(); return sum; }}lock()和unlock()办法都是伪代码,相当于加锁和解锁操作。一个线程调用了lock()办法获取到锁之后才能够执行前面的语句,执行结束后调用unlock()办法开释锁。此时如果另一个线程也调用lock()办法就会因无奈获取到锁而期待,直到第一个线程执行结束后开释锁。锁岂但能保障代码执行的原子性,还能保障变量的可见性,获取到锁之后的线程读取的任何共享变量肯定是它最新的值,不会获取到其余线程批改后的过期值。 咱们再来放大一下lock()外部细节。显然为了保障获取锁的排他性,咱们须要先去判断线程是否曾经取得了锁,如果还没有线程取得锁就给以后线程加锁,如果曾经有其余线程曾经获取了锁就期待。显然获取锁自身也须要保障原子性和可见性,所以lock()办法必须是一个同步(synchronized)办法,unlock()也是一样的情理。 在强调一下,加解锁办法自身都要具备原子性和可见性是一个重要的概念,前面咱们会用到。 public synchronized void lock(){ if(!hasLocked()){ locked(); return; }else{ awaited(); }}注:以上所有代码均为伪代码,只为阐明锁的作用及原理,无需深究 ...

September 25, 2023 · 1 min · jiezi

关于分布式系统:背压的本质

咱们搞技术要造就见微知著的能力,用逻辑剖析和结构来代替经验主义。例如“背压”(back-pressure)的话题就能够从很简略的模型登程,通过一步步的推导来欠缺解决方案。 生产者-消费者模型这个很简略的模型就是过程内的生产者-消费者(producer-consumer)模型。生产者和消费者在不同线程。生产者和消费者通过有界队列连贯,生产者往队列写入音讯,消费者从队列拿取音讯用于计算。若生产者速率过快,队列会写满,生产者暂停写入;若消费者速率过快,队列会排空,消费者暂停拿取。这就是简洁的压力反馈机制。 背压指的是上游速率较慢时反馈给上游的压力,心愿上游也减慢速率,然而实际上也要思考上游速率较快而压力有余的状况。 拉和推进一步剖析这一模型。如果咱们关注上游拿取数据的形式,这其实是拉模式(pull mode)。上游被动从队列拉取数据,若没有就期待(线程期待队列的信号量),而上游则是在被动往队列推送数据,这其实是推模式(push mode),若推不动就期待(线程期待队列的信号量)。当初上下游在同一个过程内,共享一个队列,拉模式和推模式同时存在。如果是分布式系统,上下游在不同的过程(以及不同的机器),那又如何呢?分布式系统个别会在拉模式和推模式之间二选一,两种模式的压力主导方不同。 在拉模式中:队列在上游,上游的生产者往队列写数据,写满就期待;上游不须要队列,消费者间接向上游申请数据,上游从队列取出数据作为响应返回给上游,这时上游的队列必定是不满的,生产者就能够持续写入数据。这里有一个问题,上游申请上游时处于期待状态,它的计算能力被节约了。更高效的形式是上游超量拉取一些数据,放在上游的外部队列里缓缓解决,同时上游能够申请上游并且期待响应。所以实际上,上游也是有队列的。但这里的重点是,用于跨过程压力反馈的是上游的队列。 在推模式中:队列在上游,生产者间接往上游发送数据,上游若队列已满而不能持续承受数据,就须要告诉上游。一种形式是申请-响应(request-response)模型:上游发送带有数据的申请给上游,并且期待上游返回一个“胜利”的响应,若不能解决就返回相应的“谬误”响应,上游减慢或暂停推送,而后再试探何时能复原推送。这种形式显然不是很快,因而也有发射后不必管(fire-and-forget)模型:上游只管发送数据,上游须要从另一个通道发送反馈音讯给上游。然而若上游不及时处理反馈音讯,还持续发送数据,而上游的队列已满,就只能产生网络丢包了,上游依据丢包率来调整推送速率。实际上,若申请-响应模型每次批量地把多个数据放在一个申请里,只需为多个数据期待一个响应,而发射后不必管模型其实也是在收到多个数据后发回一个相当于响应的反馈音讯,两者又有什么区别呢?区别只有“谁”来决定批量的大小,申请-响应模型是由上游决定批量的大小,发射后不必管模型是由上游决定批量的大小。总之这里的重点是,用于跨过程压力反馈的是上游的队列。 拉模式的次要益处是,队列更凑近上游,反馈更及时。上游总是能够去申请上游,能够通过长轮询(long-polling)来满足实时性,若没有数据就期待在那(Kafka和SQS就是这么干的)。推模式的上游须要很大致力来晓得上游的状态,无论上游是满还是空,上游都要慢一拍才晓得,还可能有丢包的节约。拉模式须要上游每次给上游发申请,这个申请是额定的开销,但只有每次多申请几个数据,申请的开销也不算多大,能承受。 TCP协定中的背压既然说到丢包,就说说TCP协定中的背压吧,学名Flow Control。TCP恰好是推模式,它用滑动窗口(sliding window)来管制推送速率。上游一边推送数据包一边承受ACK包,如果滑动窗口的大小是10,就最多能有10个已收回但未被ACK的数据包。每当有ACK,就空出了一个窗口空位,能再推送一个数据包。这个滑动窗口相当于队列。若有数据包未被ACK,即丢包,能够进行无限次数的重发。未被ACK的数据包要暂存在上游的一个缓冲区里以备重发之需,称之为send buffer。上游收到数据还须要期待程序来解决,要把数据暂存在一个buffer里,称之为receive buffer。TCP的背压机制(用buffer)和应用层的背压机制(用queue)往往是同时存在的,如下图: 滑动窗口只是一个根底机制。为了达到更高效率,TCP还有拥塞管制(Congestion Control)机制,依据丢包率和一些算法来预测和调整推送速率。咱们编写分布式系统时也能够想一些预测性的方法来提高效率,而不是只靠最根本的压力反馈机制。在一个多级数据流水线中,有上游、中游和上游,压力一级级向上传导是有提早的。推模式能够利用水位线(watermark)来提高效率:上游在队列用量达到某个水位线时,把队列的余量信息反馈给上游,让上游提前调整速率而不是等到上游队列写满才做反馈。拉模式也能够利用水位线,当一个上游对应多个上游时(fan-out traffic),水位线很有帮忙(详情待补充)。 总结从根本的生产者-消费者模型登程,能够一步步推导出像数据流水线这样的分布式系统的背压机制。咱们还晓得了拉模式和推模式,对于大多数利用场景,我比拟举荐拉模式。

September 5, 2023 · 1 min · jiezi

关于分布式系统:分布式计算技术上经典计算框架MapReduceSpark-解析

当一个计算工作过于简单不能被一台服务器独立实现的时候,咱们就须要分布式计算。分布式计算技术将一个大型工作切分为多个更小的工作,用多台计算机通过网络组装起来后,将每个小工作交给一些服务器来独立实现,最终实现这个简单的计算工作。本篇咱们介绍两个经典的计算框架MapReduce和Spark。 — MapReduce批处理引擎 —MapReduce是第一个比拟胜利的计算引擎,次要用于数据批处理。因为企业的大数据业务多是围绕结构化数据等价值密度更高的数据开展,所有的大数据公司开始在大数据平台上打造SQL引擎或散布数据库。2012年开始到随后两年中呈现20多个基于Hadoop的SQL引擎,以解决结构化数据问题。 MapReduce框架是Hadoop技术的外围,它的呈现是计算模式历史上的一个重大事件,在此之前行业内大多是通过MPP(Massive Parallel Programming)的形式来加强零碎的计算能力,个别都是通过简单而低廉的硬件来减速计算,如高性能计算机和数据库一体机等。而MapReduce则是通过分布式计算,只须要便宜的硬件就能够实现可扩大的、高并行的计算能力。一个MapReduce程序会蕴含一个Map过程和一个Reduce过程。在Map过程中,输出为(Key, Value)数据对,次要做过滤、转换、排序等数据操作,并将所有Key值雷同的Value值组合起来;而在Reduce过程中,解析Map阶段生成的(Key, list(value))数据,并对数据做聚合、关联等操作,生成最初的数据后果。每个worker只解决一个file split,而Map和Reduce过程之间通过硬盘进行数据交换,如果呈现任何谬误,worker会从上个阶段的磁盘数据开始从新执行相干的工作,保证系统的容错性和鲁棒性。 图片来源于《MapReduce: simplified data processing on large clusters》MapReduce在设计上并不是为了高性能,而是为了更好的弹性和可扩展性。在等同规模的硬件以及同等量级的数据上,与一些基于关系数据库的MPP数据库相比,MapReduce的剖析性能个别会慢一个数量级,不过MapReduce能够反对的集群规模和数据量级要高几个数量级。在2014年Jeff Dean提出MapReduce的论文里提及的相干集群曾经是1800台服务器的规模,而当初放眼国内,单个集群超过几千个服务器、解决数据量达到PB级别的集群有超过数百个。 除了能够反对PB级别的弹性化数据计算外,MapReduce还有几个很好的架构个性,这些个性也都被起初的一些计算框架(如Spark等)无效地继承。第一个个性是简化的编程接口设计,与之前的MPP畛域风行的MPI等编程接口不同,MapReduce不须要开发者本人解决并行度、数据分布策略等简单问题,而是须要关注于实现Map和Reduce对应的业务逻辑,从而大大简化开发过程。另外MapReduce的计算基于key-value的数据对,value域能够蕴含各种类型的数据,如结构化数据或图片、文件类非结构化数据,因而MapReduce计算框架可能很好地反对非结构化数据的解决。 此外,在容错性方面,因为MapReduce的分布式架构设计,在设计之初即设定了硬件故障的常态性,因而其计算模型设计了大量的容错逻辑,如工作心跳、重试、故障检测、重散布、工作黑/灰名单、磁盘故障解决等机制,笼罩了从JobTracker、TaskTracker到Job、Task和Record级别的从大到小各个层级的故障解决,从而保障了计算框架的良好容错能力。 而随着企业数据分析类需要的逐步深刻,MapReduce计算框架的架构问题从2010年后也逐步裸露进去。首先就是其性能问题,无论是框架启动开销(个别要数分钟),还是工作自身的计算性能都有余,尤其是在解决中小数据量级的数据工作上与数据库相差太大,不能用于交互式数据分析场景。有意思的是,从2010年开始,学术界有大量的论文钻研如何优化MapReduce性能,也有多个开源框架诞生进去,但都未能实现性能在量级上的晋升,因而也逐步淡出了历史。第二个重要问题是不能解决实时类数据,因而不能满足一些疾速倒退的互联网场景需要,如实时举荐、实时调度、准实时剖析等。后续Spark框架的呈现就优先解决了这几个问题,框架启动开销降到2秒以内,基于内存和DAG的计算模式无效的缩小了数据shuffle落磁盘的IO和子过程数量,实现了性能的数量级上的晋升。随着更好的计算框架呈现,MapReduce逐步退出了支流利用场景,不过其作为分布式计算的第一代技术架构,其在计算技术演进的过程中施展了重要的历史价值。 — Spark计算框架 —随着大量的企业开始通过Hadoop来构建企业应用,MapReduce的性能慢的问题逐步成为瓶颈,只能用于离线的数据处理,而不能用于对性能要求高的计算场景,如在线交互式剖析、实时剖析等。在此背景下,Spark计算模型诞生了。尽管实质上Spark依然是一个MapReduce的计算模式,然而有几个外围的翻新使得Spark的性能比MapReduce快一个数量级以上。第一是数据尽量通过内存进行交互,相比拟基于磁盘的替换,可能防止IO带来的性能问题;第二采纳Lazy evaluation的计算模型和基于DAG(Directed Acyclic Graph, 有向无环图)的执行模式,能够生成更好的执行打算。此外,通过无效的check pointing机制能够实现良好的容错,防止内存生效带来的计算问题。 Spark 实现了一种分布式的内存形象,称为弹性分布式数据集(RDD,Resilient Distributed Datasets)。它反对基于工作集的利用,同时具备数据流模型的特点 主动容错、地位感知调度和可伸缩性。RDD 容许用户在执行多个查问时显式地将工作集缓存在内存中,后续的查问可能重用工作集,这极大地晋升了查问速度。RDD提供了一种高度受限的共享内存模型,即RDD是只读的记录分区的汇合,只能通过在其余RDD执行确定的转换操作(如map、join和 groupBy) 而创立,然而这些限度使得实现容错的开销很低。与分布式共享内存零碎须要付出昂扬代价的检查点和回滚机制不同,RDD通过Lineage来重建失落的分区一个RDD中蕴含了如何从其余 RDD衍生所必须的相干信息,从而不须要检查点操作就能够重构失落的数据分区。 除了Spark Core API以外,Spark还蕴含几个次要的组件来提供大数据分析和数据挖掘的能力,次要包含Spark SQL、Spark Streaming、Spark MLLib。 Spark SQL Spark SQL是基于Spark引擎提供应用SQL来做统计分析的模块,因为有较好的SQL兼容性,对一般数据开发者应用比较简单,因而在用户中应用比拟宽泛。SparkSQL充沛排汇了Hive等我的项目的架构优缺点,通过无效的模块化以及与Hive元数据模块的兼容,使得开发者能够间接用Spark SQL来剖析Hive中的数据表,而比间接应用Hive做剖析可能大幅度提高性能。尔后,Spark SQL陆续减少了对JSON等各种内部数据源的反对,并提供了一个标准化的数据源API。数据源API给Spark SQL提供了拜访结构化数据的可插拔机制。各种数据源有了简便的路径去进行数据转换并接入到Spark平台进行计算,此外由API提供的优化器,在大多数状况下,能够将过滤和列修剪都下推到数据源,从而极大地缩小了待处理的数据量,可能显著进步Spark的工作效率。通过这些架构上的翻新,Spark SQL能够无效地剖析多样化的数据,包含Hadoop、Alluxio、各种云存储,以及一些内部数据库。 Spark Streaming Spark Streaming 基于 Spark Core 实现了可扩大、高吞吐和容错的实时数据流解决。Spark Streaming 是将流式计算分解成一系列短小的批处理作业。这里的批处理引擎是Spark,也就是把Spark Streaming的输出数据依照micro batch size(如500毫秒)分成一段一段的数据(Discretized Stream),每一段数据都转换成 Spark中RDD(Resilient Distributed Dataset),而后将Spark Streaming中对DStream的转换操作变为针对Spark中对RDD的转换操作,将RDD通过操作变成两头后果保留在内存中。 ...

April 10, 2023 · 1 min · jiezi

关于分布式系统:分布式系统关键路径延迟分析实践

作者 | 月色如海 导读 随着对用户体验的一直谋求,提早剖析成为大型分布式系统中不可或缺的一环。本文介绍了目前在线服务中罕用的提早分析方法,重点解说了要害路径分析的原理和技术实现计划,实际表明此计划效果显著,在耗时优化方面施展了重要作用,心愿这些内容可能对有趣味的读者产生启发,并有所帮忙。 全文4528字,预计浏览工夫12分钟。 01 背景近年来,互联网服务的响应提早(latency)对用户体验的影响愈发重要,然而以后对于服务接口的提早剖析却没有很好的伎俩。特地是互联网业务迭代速度快,性能更新周期短,必须在最短的工夫内定位到提早瓶颈。然而,服务端个别都由分布式系统形成,外部存在着简单的调度和并发调用关系,传统的提早分析方法效率低下,难以满足当下互联网服务的提早剖析需要。 要害路径分析(Critical Path Tracing)作为近年来崛起的提早分析方法,受到Google,Meta,Uber等公司的青眼,并在在线服务中取得了广泛应用。百度App举荐服务作为亿级用户量的大型分布式服务,也胜利落地利用要害门路提早剖析平台,在优化产品提早、保障用户体验方面施展了重要的作用。本文介绍面向在线服务罕用的提早分析方法,并具体介绍要害路径分析的技术实现和平台化计划,最初结合实际案例,阐明如何在百度App举荐服务中播种理论业务收益。 02 罕用分布式系统提早分析方法以后业界罕用的服务提早剖析有RPC监控(RPC telemetry),CPU分析(CPU Profiling),分布式追踪(Distributed Tracing),上面以一个具体的系统结构进行举例说明: △图1 系统结构示例 A、B、C、D、E别离为五个零碎服务,A1到A4、B1到B5别离为A、B零碎内的子组件(能够了解为A、B零碎外部进一步的细化组成部分),箭头标识服务或组件之间的调用关系。 2.1 RPC监控RPC是目前微服务零碎之间罕用的调用形式,业界次要开源的RPC框架有BRPC、GRPC、Thrift等。这些RPC框架通常都集成了统计打印性能,打印的信息中含有特定的名称和对应的耗时信息,内部的监控零碎(例如:Prometheus)会进行采集,并通过仪表盘进行展现。 △图2 RPC耗时监控UI实例 此剖析形式比较简单间接,如果服务之间的调用关系比较简单,则此形式是无效的,如果零碎简单,则基于RPC剖析后果进行的优化往往不会有预期的成果。如图1,A调用B,A2和A3是并行调用,A3外部进行简单的CPU计算工作,如果A2的耗时高于A3,则剖析A->B的RPC延时是有意义的,如果A3高于A2,则缩小A->B的服务调用工夫对总体耗时没有任何影响。此外RPC剖析无奈检测零碎外部的子组件,对整体提早的剖析具备很大的局限性。 2.2 CPU ProfilingCPU剖析是将函数调用堆栈的样本收集和聚合,高频呈现的函数认为是次要的提早门路,下图是CPU火焰图的展现成果: △图3 cpu火焰图 程度的宽度示意抽样的次数,垂直方向示意调用的关系,火焰图通常是看顶层的哪个函数宽度最大,呈现“平顶”示意该函数存在性能问题。 CPU Profiling能够解决下面说的RPC监控的有余,然而因为仍然无奈通晓并行的A2和A3谁的耗时高,因而依照RPC链路剖析后果还是依照CPU剖析的后果进行优化哪个真正有成果将变得不确定,最好的形式就是都进行优化,然而这在大型简单的零碎中老本将会变得很大。可见CPU Profiling同样具备肯定的局限性。 2.3 分布式追踪分布式追踪目前在各大公司都有了很好的实际(例如Google的Dapper,Uber的Jaeger)。 △图4 分布式追踪成果示例 分布式追踪将要追踪的“节点”通过span标识,将spans依照特定形式构建成trace,成果如图4所示,从左到右示意工夫线上的不同节点耗时,同一个起始点示意并发执行。这须要收集所有跨服务申请的信息,包含具体的工夫点以及调用的父子关系,从而在内部还原零碎调用的拓扑关系,蕴含每个服务工作的开始和完结工夫,以及服务间是并行运行还是串行运行的。 通常,大多数分布式跟踪默认状况下包含RPC拜访,没有服务外部子组件信息,这须要开发人员依据本身零碎的构造进行补全,然而零碎外部本身运行的组件数目有时过于宏大,甚者达到成千盈百个,这就使得老本成为了分布式跟踪进行具体提早剖析的次要阻碍,为了在老本和数据量之间进行衡量,往往会放弃细粒度的追踪组件,这就使得剖析人员须要破费额定的精力去进一步剖析提早真正的“消耗点”。 上面介绍要害路径分析的基本原理和理论的利用。 03 要害路径分析3.1 介绍要害门路在服务外部定义为一条耗时最长的门路,如果将下面的子组件形象成不同的节点,则要害门路是由一组节点组成,这部分节点是分布式系统中申请处理速度最慢的有序汇合。一个零碎中可能有成千盈百个子组件,然而要害门路可能只有数十个节点,这样数量级式的放大使得老本大大降低。咱们在上图的根底上加上各个子模块的耗时信息。 △图5 加上耗时信息的示例系统结构 如图5所示,在B中B1并行调用B3、B4、B5,提早别离为100,150,120,而后再调用外部的B2,进行返回,要害门路为B1->B4->B2,提早为10 + 150 + 10 = 170,在A中A1并行调用A2,A3。A2和A3都实现后再调用A4,而后返回,要害门路为A1->A2->A4,提早为15 + 170 + 10 = 195 ,因而这个零碎的要害门路为红色线条的门路 A1->A2->B1->B4->B2->A4。 通过这个简略的分布式系统构造表述出要害门路,其形容了分布式系统中申请处理速度最慢步骤的有序列表。可见优化要害门路上的节点必定能达到升高整体耗时的目标。理论零碎中的要害门路远比以上形容的简单的多,上面进一步介绍要害路径分析的技术实现和平台化计划。 3.2 理论利用解决方案要害门路数据的采集到可视化剖析的流程如图所示: ...

December 27, 2022 · 1 min · jiezi

关于分布式系统:探究分布式全局唯一Id生成器

原文首发于我的博客:https://kaolengmian7.com/post... PC 端拜访我的博客能够取得最优质的浏览体验同时也能够翻阅我的其余博文。 分布式全局惟一Id生成器有啥用?首先咱们有一个共识,那就是咱们的业务零碎大量用到惟一 Id,比方电商零碎的订单、内容社区的帖子等等。在大部分传统场景中,咱们依赖 Mysql 存储数据,并依附 Mysql 的自增 Id 来取得一个惟一 Id。 如果某个场景咱们无奈依赖 Mysql 的自增 Id(比方分库分表),亦或者应用 Mysql 很节约性能(应用 Mysql 不是为了存储数据,仅仅是为了取得一个惟一 Id),那么此时应用分布式全局惟一 Id 生成器是一个适合的计划。 有哪些分布式全局惟一Id生成形式?UUIDUUID 全称:Universally Unique Identifier。规范型式蕴含 32 个 16 进制数字,以连字号分为五段,模式为 8-4-4-4-12 的 36 个字符,示例:9628f6e9-70ca-45aa-9f7c-77afe0d26e05。 长处:代码实现简略。本机生成,简直没有性能问题。毛病:无奈保障递增。UUID 是字符串,存储和查问相比于整形更麻烦(须要的存储空间大、不好索引等)。拓展:基于 MAC 地址生成 UUID 的算法可能会造成 MAC 地址泄露,这个破绽曾被用于寻找梅丽莎病毒的制作者地位。 不过咱们这里仅探讨 UUID 作为惟一 Id 的场景,所以不能算作毛病。 利用场景:生成 token 令牌的场景。链路追踪场景。不实用要求 Id 趋势递增的场景。不适宜作为高性能需要的场景下的数据库主键。Mysql && RedisMysql 与 Redis 的原理相似,都是利用数字自增来实现。 长处:整型,且从 0 开始。枯燥递增(区别趋势递增)。查问效率高。可读性好。毛病:存在单点问题,重大依赖 Mysql or Redis。数据库压力大,高并发抗不住。主从 Redis 存在同步提早。(这一点求教了公司团队的大佬)即使应用集群 Redis,仍无奈防止 Redis 单点。(这一点求教了公司团队的大佬)SnowFlakeSnowFlake 是 twitter 开源的分布式 ID 生成算法,也叫雪花算法,基于工夫戳来实现。 ...

November 1, 2022 · 1 min · jiezi

关于分布式系统:分布式链路追踪体验skywalking框架入门

背景旁友,你的线上服务是不是偶然来个超时,或者忽然抖动一下,造成用户一堆反馈投诉。而后你费了九牛二虎之力,查了一圈圈代码和日志才总算定位到问题起因了。或者公司外部有链路追踪零碎,尽管能够很轻松地通过监控判断问题呈现的起因,然而对其中的逻辑齐全摸不着头脑。只能上网搜寻一番。 旁友,skywalking分布式链路追踪框架理解一下。 有的旁友会有纳闷,我的Spring Boot 就是一个单体利用么,不须要链路追踪?有问题间接翻日志就行了,然而即便是一个 Spring Boot 单体利用,也会和以下服务打交道: 关系数据库,例如说 MySQL、PostgreSQL 等等。缓存数据库,例如说 Redis、Memcached 等等。内部三方服务,例如说微信公众号、微信领取、支付宝领取、短信平台等等可见,仅仅一个 Spring Boot 单体利用,就曾经波及到散布在不同过程中的服务了。此时,就十分有必要用上skywalking。例如说,线上某个 接口拜访十分慢,用SkyWalking 能够定位是MySQL 查问比较慢呢,还是调用的第三方服务比较慢。 而在分布式服务中,各个大厂外部零碎成千盈百的,链路关系更加简单。比方你在外卖平台上的一个点击申请可能跨了外部几十个Java利用了,在这么长的链路里去排查问题,没有好使的工具怎么行呢。如图是以后分布式系统的现状,图片起源:鹰眼下的淘宝分布式调用跟踪零碎介绍 依据上图,咱们构想: 1.零碎中有可能每天都在减少新服务或删除旧服务,也可能进行降级,当零碎呈现谬误,咱们如何定位问题? 2.当用户申请时,响应迟缓,怎么定位问题? 3.服务可能由不同的编程语言开发,1、2 定位问题的形式,是否适宜所有编程语言? Skywalking框架1.介绍SkyWalking 是什么? 官网网址 http://skywalking.apache.org/ skywalking是一个优良的国产开源框架,2015年由集体吴晟(华为开发者)开源 , 2017年退出Apache孵化器。短短两年就被Apache支出麾下,实力可见一斑。 分布式系统的应用程序性能监督工具,专为微服务、云原生架构和基于容器(Docker、K8s、Mesos)架构而设计。 提供分布式追踪、服务网格遥测剖析、度量聚合和可视化一体化解决方案。 代码无侵入,通信形式采纳GRPC,实现形式是java探针,反对告警,JVM监控,反对全局调用统计等等 skywalking的架构参考了谷歌的Dapper框架的论文,Dapper并没有开源,只给了篇论文,感兴趣但又不喜英文文档的旁友能够看看论文的中文翻译Dapper,大规模分布式系统的跟踪零碎 整体架构如下: Tracing Metrics Logging :负责从利用中,收集链路信息,发送给 SkyWalking OAP 服务器。目前反对 SkyWalking、Zikpin、Jaeger 等提供的 Tracing 数据信息。Java利用通常应用SkyWalking Agent 收集数据SkyWalking OAP :skywalking服务端(Transport layer,Receiver cluster,Aggregator cluster)负责接管 Agent 发送的 Tracing 数据信息,而后进行剖析,存储到内部存储器( Storage ),最终提供查问性能。Storage option :Tracing 数据存储。目前反对 ES、H2 多种存储器。咱们用ES存储即可 。GUI :负责提供可视化控台,查看链路等Alarm:提供告警性能,这里不展现讲2.Docker形式搭建Skywalking环境为了疾速搭建环境,防止各种零碎、配置环境不同造成踩坑的状况。咱们用docker间接创立ElasticSearch、Skywalking-OAP、Skywalking-UI以及ES的管理工具Kibana。这样一套运行环境间接就能用了。话不多说,间接开干 ...

September 25, 2022 · 2 min · jiezi

关于分布式系统:BI系统的分布式部署原理和技术实现

1.什么是分布式对于“分布式系统”的定义,咱们先看下书中是怎么说的。《分布式系统原理和范型》一书中是这样定义分布式系统的:“分布式系统是若干独立计算机的汇合,这些计算机对于用户来说就像是单个相干零碎”。对于这个定义,咱们直观的感触就是:首先,这种零碎相对来说很厉害,由好几台主机组成。以谷歌、亚马逊等服务商而言,他们的数据中心都由上万台主机撑持起来的。其次,尽管很它很厉害,但对于外人来说,是感觉不到这些主机的存在。也就是说,咱们只看到是一个零碎在运作。以最近的“亚马逊 S3 宕机事件”为例,平时,咱们压根不晓得亚马逊所提供的服务背地是由多少台主机组成,然而等到 S3 宕机才晓得,这货曾经是占了互联网世界的半壁江山了。从过程角度看,两个程序别离运行在两个台主机的过程上,它们相互协作最终实现同一个服务(或者性能),那么实践上这两个程序所组成的零碎,也能够称作是“分布式系统”。当然,这个两个程序能够是不同的程序,也能够是雷同的程序。如果是雷同的程序,咱们又能够称之为“集群”。所谓集群,就是将雷同的程序,通过一直横向扩大,来进步服务能力的形式。举一个生存中的例子来阐明:小饭店原来只有一个厨师,切菜洗菜备料炒菜全干。起初客人多了,厨房一个厨师忙不过来,又请了个厨师,两个厨师都能炒一样的菜,两个厨师的关系是集群。为了让厨师分心炒菜,把菜做到极致,再请了个配菜师负责切菜,备菜,备料 ... , 厨师和配菜师的关系是分布式。一个配菜师也忙不过来了,又请了个配菜师,两个配菜师关系是集群。一个配菜师因故销假了,然而其余的配菜师还是该啥就干啥,只是没销假的配菜师工作平均的加量了,但他们的工作和职责是不变的,这是集群。店里生意很好,当店长接到订单后,看哪个厨师活儿不重,就将新的订单分给谁,这就是负载平衡。集群:多集体在一起做同样的事 。分布式 :多集体在一起做不同的事 。负载平衡:决定将工作以某种规定分给谁做。 2.为什么应用分布式部署理解了什么是分布式之后,为什么要应用散布部署呢;首先分布式部署长处很显著,次要体现在上面4个方面:零碎可用性晋升传统的集中式计算或集中式存储在遇见单点故障时很容易造成整个服务不可用,分布式下的服务体系,单台机器有故障,不致于造成整个服务不可用。零碎并发能力晋升比方双 11 流动,平时订单少 50 台机器就够了,到了 11 订单量剧增,服务器减少到 100 台,每台机器之间互相独立,互不影响。零碎容错能力晋升 同一组服务别离部署在北京上海杭州,杭州的机房突发断电或者火灾,杭州机房的流量会被主动散发到北京和上海的机房,不影响用户应用。低提早参考上一个图,北京的用户申请主动散发到北京,上海的用户申请被散发到上海,服务器会依据用户的 IP 抉择间隔本人最近的机房,升高网络提早。同样分布式部署带来益处的同时也会有一些毛病,只有是上面3个方面:分布式服务依赖网络服务器间通信依赖网络,不牢靠网络包含网络延时,丢包、中断、异步,一个残缺的服务申请依赖一连串服务调用,任意一个服务节点网络呈现问题,都可能造成本次申请失败。保护老本高传统单体式服务只须要保护一个站点就能够。分布式服务零碎被拆分成若干个小服务,服务从 1 变为几十个上百个服务后,减少运维老本。 一致性,可用性,分区容错性无奈同时满足这个是最次要的,这三种个性就是平时说的 CAP 定理,在分布式系统中,这三种个性最多只能满足两种,无奈同时满足,须要依据理论状况去调整就义掉其中哪个。 3.BI零碎的分布式部署原理和技术实现随着数据的爆炸性增长,BI零碎须要解决的数据越来越多,动辄TB级,甚至PB级,于是服务器宕机,反应迟钝,查问迟缓等各种性能问题接踵而来,BI零碎的用户,心里几乎又苦又难~~~ 各路BI厂商也意识到这些问题,纷纷推出各种解决方案。 这里提供一种应用分布式部署解决方案。架构图如下: 那么分布式部署呢,次要是对ReportWorker,CotWorker,DashboardWorker组件进行横向扩大,这几个组件次要是负责仪表板和报表运算的组件,能够部署多个以提供零碎的计算性能。为了升高用户部署老本,提供了在线的近程部署形式,UI界面化操作,能够在线增加节点,近程为每个节点部署须要的组件,以及对节点组件进行在线启停,进一步升高用户部署老本。 同时也能够在线的运维治理和系统诊断性能,能够查看系统资源耗费,系统日志下载,不便对BI零碎进行运维治理,升高用户的运维老本。大家如果感兴趣欢送拜访各种在线demo,领会BI工具为数据可视化带来的便当:https://www.grapecity.com.cn/...

September 6, 2022 · 1 min · jiezi

关于分布式系统:分布式系统中亚稳定失败状态

这篇文章的初衷,是记录拜读由Nathan Bronson, Aleksey Charapko, Abutalib Aghayev, and Timothy Zhu独特发表的论文Metastable Failures in Distributed Systems的播种,这篇论文形容了一个在大规模分布式系统中很常见的失败场景:亚稳固失败(metastable failures),它们为什么通常在高负载分布式系统中产生,以及解决问题的思路框架:如何辨认和从亚稳固失败中复原,甚至如何防止产生亚稳固失败。 事实世界中的亚稳固失败下图是某个公园中十分驰名的徒步路线中十分要害的一部分:两座山之间一段狭长的山脊,在两座山之间徒步,只有扶着铁链穿过这段山梁能力保障平安。能够假想这段铁链就是一个分布式系统。当公园不对徒步者人数做限度时,就有可能引起人群拥挤以至于长时间在终点期待去尝试减低负载。你能够设想一下”零碎“经验了以下的状态转换。 稳固状态(Stable state)当人群低于某个平安阈值时,任何一个危险因素(例如:迟缓通过铁链的徒步者)都可能引起降速,然而零碎依然可能自愈。软弱状态(Vulnerable state)拥挤人群的数量继续减少超过了某个阈值,对于每段铁链的竞争也会减少 —— 下山的人必须要期待上山的人通过,或者罗唆冒险不应用铁链而绕过他们。同样,上山的人也必须期待下山的人通过。当这种模式产生时,只有拥挤人群的数量低于某个阈值,零碎依然是能够工作的,但它当初是十分软弱的,可能变成不可自愈的失败状态。亚稳固失败状态(Metastable Failure State)当零碎处于软弱状态,某些危险因素可能导致状况好转。设想一下,一组徒步者须要破费更长的工夫通过铁链,这将会使其余上山和下山的人速度降下来。越来越多的人期待,导致在铁链两端和身在其中的残余空间越来越狭隘,进而导致人们须要更多的工夫通过,进一步导致更多的人期待...状况循环好转 —— 进入亚稳固失败状态。放弃零碎处于亚稳固失败状态的正反馈循环因为这个自续的反馈循环,零碎将放弃在这个状态,即便移除最后的诱发危险因素。为了复原,须要采取其余的措施,例如将负载升高到特定阈值以下。 亚稳固失败定义对于亚稳固失败的定义论文中形容如下 亚稳固失败产生在对于负载起源没有管制的开放系统中,一个危险因素导致系统进入一个蹩脚的状态,并且会继续存在甚至危险因素被移除。在这个状态零碎的效率通常会很低,并且会产生继续影响——通常使工作负载放大或者整体效率升高——阻止零碎从这个蹩脚的状态中复原。Metastable failures occur in open systems with an uncontrolled source of load where a trigger causes the system to enter a bad state that persists even when the trigger is removed. In this state the goodput (i.e., throughput of useful work) is unusably low, and there is a sustaining effect — often involving work amplification or decreased overall efficiency — that prevents the system from leaving the bad state.如果这样,那么为什么不总是运行在稳固状态?诚如,在许多零碎中高效的资源利用是十分重要的,所以许多零碎抉择运行在软弱状态而不是稳固状态,已取得更高的效率。当零碎处于亚稳固失败状态,零碎不会自愈,想要复原须要采取重要措施,例如升高负载。 ...

July 8, 2022 · 1 min · jiezi

关于分布式系统:算法领头羊丨分布式系统如何选举领导

大家好,我是本期的实验室研究员——李帅。领导选举是分布式系统中最辣手的事件之一。同时,了解 Leader 是如何选举产生的以及Leader 的职责,是了解分布式系统的要害。 在分布式系统中,通常一个服务由多个节点或实例组成服务集群,提供可扩展性、高可用的服务。 这些节点能够同时工作,晋升服务解决、计算能力,然而,如果这些节点同时操作共享资源时,那就必须要协调它们的操作,避免每个节点笼罩其余节点所做的更改,从而产生数据错乱的问题。 所以,咱们须要在所有节点中选出一个领导者 Leader,来治理、协调集群的所有节点,这种也是最常见的 Master-Slave 架构。 在分布式环境中的多个节点中选取主节点 Leader,通常会应用以下几种策略: 依据过程 Id 或者实例 Id,抉择最大值,或者最小值作为主节点。实现一种常见的领导者选举算法,比方 Bully 等。通过分布式互斥锁,保障只有一段时间只有一个节点能够获取到锁,并成为主节点。在本文中,我会介绍几种常见的选举算法,包含 ZAB、Bully、Token Ring Election。 Bully 算法Garcia-Monila 在 1982 年的一篇论文中创造了 Bully 算法,这是分布式系统中很常见的选举算法,它的选举准则是“长者”为大,也就是在所有存活的节点中,选取 ID 最大的节点作为主节点。 如果有一个集群, 各个节点能够相互连接,并且每个节点都晓得其余节点的信息( Id 和节点地址),如下: 集群初始化时,各个节点首先判断本人是不是存活的节点中 ID 最大的,如果是,就向其余节点发送 Victory 音讯,发表本人成为主节点,依据规定,此时,集群中的 P6 节点成为 Master 主节点。 当初集群中呈现了一些故障,导致节点下线。如果下线的是从节点, 集群还是一主多从,影响不大, 然而如果下线的是 P6 主节点,那就变成了一个群龙无首的局面。 当初咱们须要从新选举一个主节点! 咱们的节点是能够相互连接,并且节点间定时进行心跳查看, 此时 P3 节点检测到 P6 主节点失败,而后 P3 节点就发动了新的选举。 首先 P3 会向比本人 ID 大的所有节点发送 Election 音讯。 因为 P6 曾经下线,申请无响应,而 P4,P5 能够接管到 Election 申请,并响应 Alive 音讯。P3 节点收到音讯后,进行选举,因为当初有比本人 Id 大的节点存活,他们接管了选举。 ...

April 6, 2022 · 2 min · jiezi

关于分布式系统:MIT-6824-分布式系统课程lab实现-2-lab2A-Leader-Election

前言代码上传到集体github仓库6.824多线程编程因为状况过于简单,单单通过运行几次go test -run 2A命令失去PASS是无奈证实代码的可靠性的.能够通过该门课程助教提供的脚本test集体最开始写过的一版代码能通过1000次测试,起初通过从新设计思考后才发现有显著的bug.所以同学们能够多应用该脚本多跑几次,看看有无出错,查找log日志发现错误在哪里.有须要的同学还能够批改测试代码.该版本代码能通过上述脚本运行2000/2000次测试无谬误,集体不敢保障bug free,如有发现错误望能斧正. 运行模型raft次要由三个局部组成: 主部(只能由此局部批改raft状态, 计时等工作都由此局部进行)发送信息局部(进行rpc调用)解决接管信息局部(响应其它raft的rpc调用,响应本人rpc调用收到的reply)同步变量 type Raft struct { rpcMutex sync.Mutex // 将收到,收回rpc调用实现之后的数据处理串行化 但并发调用rpc stateMutex sync.Mutex // 爱护raft状态的读写平安 //用于外部数据同步 requestChan chan InnerRequest responseChan chan InnerResponse timer *time.Timer}raft相当于状态机,要扭转raft的状态只有两种办法 超时 和 接管信息运行形式如下 raft主部串行执行,在加锁的状态下批改完状态之后,依据状况抉择是否reset计时器,再开释锁,应用select监听超时信号或者是解决接管信息局部发送的信号发送信息局部较为简单,只须要加锁状态下拷贝raft状态,而后并发进行rpc调用即可(留神:发送时加锁会导致不同的raft实体循环调用从而导致死锁)接管的信息是并发达到的,在所有处理函数前加锁rpcMutex,函数退出后再开释该锁,使得该阶段串行化. 此外,在执行时如果发现须要告诉raft批改状态,还要通过requestChan和responseChan与主部进行通信,因为都是0缓存,往这两个channel写数据未被读出时,写者处于阻塞状态.代码次要局部1. MainProcess通过rf.state判断执行分支 func (rf *Raft) MainProcess() { for { switch rf.state { case FOLLOWERSTATE: rf.FollowerProcess() case CANDIDATESTATE: rf.currentTerm += 1 rf.votedFor = rf.me rf.numOfVotedPeers = 1 for i := range rf.peers { if i != rf.me { rf.votedStateOfPeers[i] = false } } rf.CandidateProcess() case LEADERSTATE: rf.LeaderProcess() case DEADSTATE: return } }}2. CandidateProcessfunc (rf *Raft) CandidateProcess() { //candidate一个Term内能够发送多轮申请选票 //因而多设置了一个tmpTimer这一个计时器 tmpTimer := time.NewTimer(HEARTBEATS_INTERVAL) //Term计时器 rf.timer.Reset(GetTimeoutInterval()) //并发发送选票申请 go rf.sendAllRequestVote() for { //在进入监听状态之前要对stateMutex进行解锁 rf.stateMutex.Unlock() select { //该轮Term超时 case <-rf.timer.C: rf.stateMutex.Lock() //这里的冗余代码是为了不便揭示,该版本有多处冗余代码 rf.state = CANDIDATESTATE return //超时,发动该Term内的又一轮选票申请 case <-tmpTimer.C: rf.stateMutex.Lock() tmpTimer.Reset(GetTimeoutInterval()) go rf.sendAllRequestVote() continue //解决接管信息函数发来的信号 case tmp := <-rf.requestChan: rf.stateMutex.Lock() //操作类型 operation := tmp.operation //该信号对应的Term, term := tmp.term extraInf := tmp.extraInf //Term过期,摈弃 if term < rf.currentTerm { rf.responseChan <- InnerResponse{false, UNDIFINE, rf.currentTerm, []int{}} continue } switch operation { case NEWTERM: //这里判断条件能够是== if term <= rf.currentTerm { //因为解决局部阻塞在该channel上,即便不操作,也要开释信号 rf.responseChan <- InnerResponse{false, UNDIFINE, rf.currentTerm, []int{}} //continue用于不须要重启计时的操作的分支的退出 continue } else { if !rf.timer.Stop() { <-rf.timer.C } rf.state = FOLLOWERSTATE rf.currentTerm = extraInf[0] rf.votedFor = -1 rf.responseChan <- InnerResponse{true, UNDIFINE, rf.currentTerm, []int{}} //return用于须要重启计时的操作的分支的退出 //返回至MainProcess() //因而这种分支之前须要排空rf.timer.C return } case LEGALLEADER: if !rf.timer.Stop() { <-rf.timer.C } rf.state = FOLLOWERSTATE rf.currentTerm = extraInf[0] rf.responseChan <- InnerResponse{true, UNDIFINE, rf.currentTerm, []int{}} return case LATERCANDIDATE: if !rf.timer.Stop() { <-rf.timer.C } rf.state = FOLLOWERSTATE rf.currentTerm = extraInf[0] rf.votedFor = extraInf[1] rf.responseChan <- InnerResponse{true, UNDIFINE, rf.currentTerm, []int{}} return case VOTEFOR: rf.responseChan <- InnerResponse{false, UNDIFINE, rf.currentTerm, []int{}} continue case GETVOTE: if !rf.votedStateOfPeers[extraInf[1]] { rf.numOfVotedPeers += 1 rf.votedStateOfPeers[extraInf[1]] = true if rf.numOfVotedPeers > rf.numOfAllPeers/2 { if !rf.timer.Stop() { <-rf.timer.C } rf.state = LEADERSTATE rf.responseChan <- InnerResponse{true, UNDIFINE, rf.currentTerm, []int{}} return } else { continue } } else { continue } case BEDEAD: rf.state = DEADSTATE rf.responseChan <- InnerResponse{true, UNDIFINE, rf.currentTerm, []int{}} if !rf.timer.Stop() { <-rf.timer.C } return } } }}须要留神的细节1. 计时器timer的应用在assignment的页面里提到了能够应用time.Sleep()来代替计时性能,因为Timer和Ticker难以使用正确,然而应用time.Sleep()办法终归是不优雅.timer通过time.NewTimer(Duration)创立,在通过指定的Duration工夫之后,会往timer.C这个channel里发送信号,应用timer.Stop()能够进行计时,应用timer.Reset(Duration)能够重设工夫,这些是通过简略地浏览文档就能失去的信息.然而须要留神的一点是调用timer.Stop()的返回值在调用timer.Stop()后正确将计时器进行后,timer.Stop()返回值为true.然而当timer.Stop()在计时器进行后再调用则会返回false,为了不影响后序的信号传递,须要将timer.C排空 ...

March 31, 2022 · 2 min · jiezi

关于分布式系统:分布式系统设计简卷1Raft

前言本篇是对于 2022-MIT 6.828 的对于 Raft 的试验记录;如果发现内容上的纰漏,请不要悭吝您的键盘。Raft 基本概念Raft 是怎么运行的Raft 对于 Paxos 来说是个绝对简略的共识算法,但再怎么简略“Raft 是怎么运行的”也不是喋喋不休就能搞定的,而且我原本就没想喋喋不休搞定。所以在看了 Raft 论文 之后,这里有两个网页能够帮忙你了解 Raft 是怎么运行的: Raft Understandable Distributed ConsensusRaft Visualization另外还有三张我认为对我本人了解 Raft 很有帮忙的图,第一张是试验领导书上的 How the replicated service and Raft communicate,第二三张是 Morris 传授授课的板书截图: 协定对四个指针的束缚Figure 3 中的束缚是 Raft 共识算法运行正确的根底,咱们将要实现的 Raft 代码库就要是实现这些束缚: lastApplied <= commitIndex <= matchIndex < nextIndex 其中 matchIndex 和 nextIndex 是 Leader 才有的字段;在所有节点中,都有上述 lastApplied 和 commitIndex 的关系;在 Leader 中,对与它保护的过半数的 peer 的 commitIndex 和 matchIndex 都有上述关系。在 Leader 中,对与它保护的任一一个 peer 的 matchIndex 和 nextIndex 都有上述关系。无论是 Leader 的 lastApplied 还是 Follower 的 lastApplied 它们都是要尽全力地追赶各自的 commitIndex,途中须要把通过的 LogEntry 通过 applyCh 返回到下层利用中去。Leader 的 commitIndex: ...

February 18, 2022 · 17 min · jiezi

关于分布式系统:Alluxio大型银行科技赋能金融兴业银行按下大数据处理加速键

对于银保监会对银行业,包含保险业在金融科技方面提出的一些要求。咱们后续会有几方面的重点建设方向: 第一个就是鼎力推动云化转型,包含云原生的转型和大数据云等一系列云化的转型,对于咱们的要求也是越来越高。 第二也是比拟重要的,继续优化科技与业务交融,用数字化反对企业数字化转型,通过为业务赋能为业务开展提供重要的科技根底。 第三就是夯实技术根底,加大数据治理和资产管理体系的建设。 第四是深入麻利转型 第五就是强化人才和文化的保障,这块也是十分的重要。 当初对银行的科技,包含银行的基础设施要求十分的高,很多传统基础设施的建设,曾经没有方法满足咱们当初面临的疾速扩大数据要求的场景,所以须要对原先的整体网络和零碎架构须要做一些调整。咱们须要从新扫视咱们的整体网络架构的设计,推动网络架构重整,次要有几个方面的挑战: 一方面是咱们在数据文件的集成过程中,因为咱们这边的数据交换基本上还是以数据文件的形式,就是咱们会有规范的数据文件卸载,卸载完之后会通过一个相似于HDFS或GPFS这种分布式文件系统来进行共享,然而随着数据量的增大,随着加工次数、加工工作的减少,数据集成的工作的减少,导致GPFS或者HDFS文件系统在性能上和网络带宽上的压力十分的大,当初为了满足这些性能要求,GPFS跟HDFS的存储全副是全场存储,能力保障根本生产和 I/O存储量的要求。网络带宽的话,在本机房的网络根本能满足,然而一旦跨机房并发量就十分的高。 第二点是跨机房的数据加载的场景,咱们之前的计划是用 Spark 间接跨机房把数据拉过来,然而对整个网络的开销十分的大。咱们Spark的数据拉取作业一旦提起来的时候,基本上把咱们两个同城机房的网络带宽全部打满了,就是万兆网全部打满,会影响到其余更加重要的交易业务的发展,所以对咱们的挑战十分大。咱们作为软件和零碎设计者很少去思考网络对咱们带来的耗费,然而当初咱们不得不面对这些给咱们零碎带来的压力。 另外一点就是信创这方面的压力也是十分大的,咱们之前次要抉择的是GPFS,它是IBM 的软件产品,在信创方面是不符合国家要求的,所以咱们当初也是在逐渐地换成国产的合乎生产信创要求的文件存储系统。但因为文件存储系统对咱们整个替换体系是十分要害外围模块,所以咱们也不敢轻易更换,咱们心愿通过Alluxio来做一个缓存,来屏蔽咱们上面不同的文件系统,通过这种形式来达到平稳过渡切换文件系统的目标,这就是咱们机构为什么抉择Alluxio的思考。讲一下跟Alluxio利用接触的历程,其实咱们很早就开始关注Alluxio这个产品,然而那个时候可能还没有那么迫切的须要,咱们只是作为一个前沿技术去钻研,然而到了2019年呈现了网络带宽那个问题之后,咱们感觉能够通过Alluxio来解决咱们跨机房网络加载的问题,所以咱们在那个工夫点开始测试Alluxio,而后逐渐地在2020年的时候开始小规模地试用。 到了2021年的时候,因为咱们过后打算重构咱们的大数据平台,所以在大数据平台上全面推广Alluxio的应用,包含撑持整个大数据的Spark,Impala等组件的运行,在拜访HDFS之前都先用Alluxio做一层缓存,包含所有的数据的入湖、归档这些操作之前,都会用Alluxio进行缓存,前面咱们也会更多地去应用Alluxio。当初介绍咱们这边以后的Alluxio利用的具体的状况,咱们这边分成两个机房,DC-A 机房跟DC-B 机房, DC-A机房是咱们外围的替换域,替换域就是作为文件替换、数据加载的技术平台。当初这个域上部署的是Alluxio,用Alluxio进行缓存和近程拉取,咱们的数据交换、数据入湖,包含数据计算、查问都是在Alluxio平台上运行的,所以咱们其实曾经在外围畛域引入了Alluxio,作为们的重点文件系统做撑持,前面咱们可能心愿Alluxio平台可能通过两个不同的门路进行迭代演变。咱们当初一个场景一个场景地来介绍一下,首先介绍最开始应用Alluxio进行缓存的一个场景,咱们原先在GPFS上把数据进行集成,数据集成之后要分发给不同的子公司或者分行,原先的做法就是间接在 GPFS上解压,解压之后往外发给分行,然而因为GPFS跟Spark之间没有原生的接口,所以咱们的做法就是在 GPFS跟Spark之间加了Alluxio,先把GPFS上的压缩文件解压到Alluxio上,利用Alluxio跟Spark的集成能力。当初应用了Alluxio之后能够做到一次解压,把解压后的文件缓存到 Alluxio上,再用Spark从Alluxio拉取数据。解压的过程可能从原来的屡次解压缩小成一次解压。通过Alluxio的内存,咱们又可能放慢 Spark的数据的基层解决的速度。另外一块是打算介绍缓存的分层,咱们当初刚刚做,包含分层以及存算拆散的一些做法,想通过Alluxio把咱们的数据依照冷热关系进行合成,对于内存、SSD包含近程的HDFS,咱们都想依据不同的须要进行合成,依据每天的理论运行状况,动静地往Alluxio上事后加载一些咱们比较关心的热数据,加大查问引擎的性能。咱们当初的查问引擎次要是用到Spark和Impala,Spark次要面向离线计算和解决的场景,对于大数据的查问产品,个别还是抉择Impala来做,通过Alluxio预加载或者内存缓存,对一些要害的对时效性要求十分高的利用进行减速,从而保障这些要害利用在时效上能满足咱们的要求,比方很多银行开门前的需要,或者一些疾速查问和热点查问的需要,咱们能够依据须要定制化数据寄存地位来实现冷热数据的交互。这个性能很好用也能解决咱们的问题,因为毕竟不可能所有数据都占SSD和内存,这样的话老本切实不太经济。跨机房数据加载解决了咱们一个十分大的问题,之前没有用Alluxio时,间接Spark近程拉取GPFS上的数据,咱们两个计算机房之间的网络是万兆的带宽,基本上一拉取的时候,整个网络带宽打满,影响了其余交易系统的运行零碎,导致常常被数据中心报故障,只能先暂停加载,起初通过引入Alluxio近程地把数据先拉到Alluxio上。通过Alluxio,咱们能够把有须要的数据一一从GPFS上拉到Alluxio,通过Alluxio跟Spark的原生联合能力,就可能反对十分高并发的峰值读写,这样在性能包含网络带宽还有并发性方面都能满足咱们的要求。在Alluxio上咱们也通过一些 TTL、Pin的策略实现了数据的自动式清理,所以也不须要去思考太多的数据清理的工作。这点也是咱们抉择一个专用的零碎来做这件事件的起因,大家都晓得,这件事件能够通过手工或利用上的管制去做,然而如果咱们有一个分布式文件系统可能间接把这个文件都清理和过滤掉,对咱们来讲十分有帮忙,节俭了咱们的很多工作量。对立的数据生命周期的治理,往年开始想做的一个事件就是说把Alluxio作为咱们底层多个文件系统之间的对立的接口,比如说咱们当初的GPFS,包含咱们很有可能引入的信创文件系统,包含 HDFS以及当初比拟炽热的一些对象存储,咱们都想通过Alluxio对立的接口来进行接入,上端提供不同的拜访接口,给各种计算,比如说数据采集、数据加工、报表,包含数据服务,数据迷信等一系列的利用场景提供对立的文件拜访入口。这个拜访入口又可能提供一些数据,依据咱们的编排规定可能对一些要害数据进行减速的解决。这个对于咱们整体的布局是一个十分有意义的事件,既能做到对立又能做到各种档次的不同的逐项的抉择,能够依据不同的倒退状况,抉择不同的存储系统,也能够抉择不同的计算引擎来做不同的事件。然而文件治理包含元数据管理,包含咱们看到的文件都可能通过同一个接口,我感觉对于咱们所有做这种数据体系的人来讲都是一个十分幸福的事件,有这方面教训同学应该有同感。往年在技术畛域上,包含银行的技术畛域上,对于存算拆散架构的钻研十分炽热,因为尽管个别状况下,咱们金融行业的技术热上会比互联网和社区要晚一些,然而当初曾经传导到咱们这边了,减速了咱们在存算拆散这种新的大数据架构上的钻研,预言或者试用,这块对咱们来讲也是很好的。因为咱们当初的计算资源,包含计算资源的治理,包含租户的弹性的治理,都能够通过存算拆散这种形式来实现,而后通过Alluxio进行租户存储资源的治理,这对咱们来讲是很重要的,因为以前对于计算资源的弹性治理、租户治理其实是很简略的,都有很成熟的做法,然而对于存储这一块的资源隔离,包含资源的动态分配,都比拟难做到,我感觉有可能通过Alluxio的权限管制,比方租户之间的数据拜访的隔离,通过对立的存储可能实现不同数据、不同租户的数据按需存储,这对于咱们进行大数据平台的租户治理来讲是一个很有帮忙的方向。 咱们来看一些测试,包含一些理论应用状况下的比照,对咱们平台带来的读写能力的晋升。数据拜访模式这边的话,咱们以HDFS进行比照,拜访都是一次写入屡次读取,依据咱们的测试与理论生产环境的一些数据的验证,Alluxio缓存命中的状况下,相比HDFS效率能进步一倍,就是可能缩短50%的期待拜访时效,相当于升高了HDFS的NameNode的压力,然而如果没有预加载就间接冷读,效率可能要比HDFS再延后一点,然而命中的状况下,有预加载的状况下晋升是很大的,这点还是十分的有成果。另外一点就是网络带宽压力,以方才跨机房网络加载的状况为例,没用Alluxio的时候,咱们的网络峰值会达到30GB/s,峰值十分的高,通过Alluxio的话,咱们基本上可能把峰值升高到2GB/s,也就是占用1/5的万兆光纤的带宽,近程加载了之后,峰值十分明显降低,对咱们的帮忙也十分大。1、咱们前面可能须要Alluxio社区对咱们理论企业应用在一些性能上提供帮忙,或者在将来把一些性能集成到开源社区版本外面去,一个是Cache缓存的优化以及监控,最次要是监控这一块,因为对企业应用来讲,Cache的技能是怎么样的,监控哪些数据,正在产生哪些数据,这些货色咱们都是很关怀的,一旦零碎呈现故障,咱们须要马上可能解决问题,所以监控这块十分重要。 2、第二点的话就是数据中台在存算拆散架构的演进和技术的计划,心愿可能把这种计划更多地共享进去,或者在社区内进行分享,做好租户隔离或者SLA管制,权限治理这块可能更加的优化一些。而后就是 Alluxio对计算引擎的深度优化,我晓得Alluxio对Presto零碎有很多的深度钻研跟优化,因为咱们这边用Impala,咱们心愿社区包含Alluxio这边可能对Impala引擎进行深度的优化,包含与Kubernetes的集成这一块。 3、另外一方面是咱们本人的思考,兴业银行这边总行和分行之间,咱们心愿可能通过Alluxio来做一个数据共享的计划,比如说有核心节点和分部的分中心节点这种企业架构,怎么可能通过Alluxio来实现数据的共享,在不物理搬移的状况下,通过内存的形式实现数据共享,谢谢大家。

February 17, 2022 · 1 min · jiezi

关于分布式系统:分布式系统设计简卷0MapReduce

前言本篇是对于 2022-MIT 6.828 的对于 MapReduce 的课程笔记及其试验记录;课堂笔记局部根本摘自 Lecture,齐全能够跳过;如果发现内容上的纰漏,请不要悭吝您的键盘。注释课堂笔记You should try everything else, before you try to build a distributed system, because they're not simpler.Lab's Goal? Lab 1: MapReduce implement your own version of the paperLab 2: replication for fault-tolerance using RaftLab 3: fault-tolerant key/value store use your Raft implementation to build a replicated K/V serverLab 4: sharded key/value store clone the K/V server into a number of independent groups and split the data in your K/V storage system to get parallel speed-up and moving chunks of data between servers as they come and go without dropping any ballsWhy do people build distributed systems? ...

February 2, 2022 · 3 min · jiezi

关于分布式系统:Distributed-Mutual-Exclusion算法详解

原文地址:Distributed Mutual Exclusion算法详解Introduction应用Distributed Mutual Exclusion算法,实现一个分布式系统。 RequirementThis assignment covers material presented during the lectures on distributed systems and builds upon the work in the practicals on distributed systems. After submission you will give a mandatory demo to demonstrate and discuss your solution to the proposed problem in the light of the content of the module on distributed systems (second part of the module). A detailed schedule of demos will follow, starting from available slots in the week. ...

December 27, 2021 · 7 min · jiezi

关于分布式系统:MIT-6824-2021-Lab1-不完全攻略

前言最近的ddl属实有些密集,在ddl的夹缝中艰巨地实现了Lab1...因为对Go的语法还是不太纯熟,最初实现还是借鉴了很多sample。心愿之后的lab能够齐全独立地实现吧具体的代码我挂在了GitHub上。 工作形容Lab1次要是实现分布式的MapReduce demo,其中具备worker和coordinator两种角色。worker负责并行地进行Map或Reduce工作,coordinator负责在worker申请或提交工作时进行解决,并设置超时工夫(通常为10秒),查看各worker执行工作状况。如果一个worker在规定工夫内未实现工作,coordinator会在其余worker申请工作时,将该工作将调配给其余worker。 可能会踩的坑测试脚本中的timeout指令和wait -n指令找不到的问题这是因为Mac自带的Bash版本过老,大家能够用brew install bash来装置新版本的bash。批改默认bash如同比拟麻烦,我是间接在GoLand中配置了新bash的门路,通过GoLand去run这个脚本

December 3, 2021 · 1 min · jiezi

关于分布式系统:分布式系统资料汇总

引子时下,随着通信技术的倒退、挪动互联网的遍及、物联网车联网人工智能的衰亡,每天所产生的数据呈爆炸性的增长。这种尺度的数据不是传统单机零碎能够独立解决的,而只能借助于大规模的分布式系统,因此分布式系统慢慢的变成一门“显学”。而作为一个分布式系统初学者,面对网上未加归类、浩如烟海的学习材料,很容易两眼抓瞎。但分布式系统有其根本钻研内容和独特倒退脉络,比方: 一些根本钻研问题:时序问题、一致性问题、容错技术、共识算法、并发管制等等。一些根本定理:CAP、PACELC、FLP渐次倒退的工业零碎:MapReduce、Spark、GFS、Dynamo、Cosmos因而只须要在“时空”两个维度对分布式系统进行把握,就能提纲挈领,愈学愈明。“时”示意分布式系统的演进脉络,能够通过浏览不同期间、学术界工业界的一些论文来把握。“空”示意分布式系统中所钻研的根本问题的拆解,能够通过浏览一些书籍建设分布式系统的常识体系。本文将我在学习分布式系统常识过程收集到的一些材料,按类别简略汇总,以飨诸君。材料排名没有先后,请按需采纳。 注: 文中举荐的材料大多为英文,如果浏览有艰难,举荐应用 Chrome 浏览器,并且给 Chrome 装一个 “google 翻译”的插件,能够点击一键“翻译此页面”。 作者:木鸟杂记 https://www.qtmuniao.com/2021..., 转载请注明出处 书籍Dr. Martin Kleppmann. Designing Data-Intensive Applications《构建数据密集型利用》,https://dataintensive.net/buy.html,作者提供收费英文版下载,网上也能够搜到。 全书分为三大部分: 零碎基石(Foundations of Data System)扩散数据(Distributed Data)衍生数据(Derived Data)零碎基石局部探讨了数据系统的一些通用侧面: 可靠性、可扩展性、可维护性(Reliable, Scalable, and Maintainable Applications)数据模型和查询语言(Data Models and Query Languages)数据存储和检索(Storage and Retrieval)数据编码和演进(Encoding and Evolution)扩散数据局部探讨了构建扩散在多机上的数据系统和一些准则和面临的问题: 冗余(replication)分片(Partition)事务(Transactions)分布式系统存在的问题(The Trouble With Distributed Systems)一致性和共识(Consistency and Consensus)衍生数据局部其实是在探讨扩散在多机上的零碎的解决问题。包含: 批处理(Batch Processing)流式解决(Stream Processing)数据系统的将来(The Future of Data Systems)近年来流批零碎趋于交融,从而让用户可能更加灵便、高效的对原始数据进行解决和变换。 这些章节拆分的都十分棒。熟读本书,让你在遇到一个新零碎时,能够如庖丁解牛个别纯熟拆解成为多个构件,并了每个构件背地的衡量取舍(trade off)。 M. van Steen and A.S. Tanenbaum, Distributed Systems, 3rd ed., distributed-systems.net, 2017.《分布式系统》第三版,https://www.distributed-systems.net/index.php/books/ds3/。作者提供英文版 PDF 收费下载链接,简介: ...

October 12, 2021 · 1 min · jiezi

关于分布式系统:分布式系统CAP定理

CAP 定理:一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition Tolerance),这三个因素最多只能同时实现两点,不可能三者兼顾。 一致性: 在分布式系统实现某写操作后任何读操作,都应该获取到该写操作写入的那个最新的值 => 要求分布式系统中各节点时刻保持数据一致性可用性:始终能够失常读写。客户端始终能够失常拜访并失去零碎的失常响应。=> 要求不呈现零碎操作失败或响应超时。分区容错性:某个节点或网络分区呈现故障时,整个零碎仍能对外提供满足一致性和可用性的服务。即局部故障不影响整体应用。

October 11, 2021 · 1 min · jiezi

关于分布式系统:来学习五个免费充电资源

假期要完结啦~ 放松完了就收收心持续奋斗吧,五个收费线上充电资源拿去! 保持学习,升职加薪不可企及。 (本次举荐课程适宜有肯定编程根底的同学) 分布式系统 主讲人:Frans Kaashoek 赫赫有名的 MIT 6.824 分布式系统(Distributed Systems)课程,授课内容应用 Go 语言。解说分布式系统设计原理的同时,也有入手编码的 Lab。 这门课程总共有 20 节课,4 个试验,试验都是基于 Go 实现,课程配套了试验相干的测试用例,入手实现试验能够加深对于相干常识的了解。 (原学习链接:https://pdos.csail.mit.edu/6.824/schedule.html) 学习链接:https://url.leanapp.cn/W11JWYx (B 站含机翻中文字幕) 计算机程序设计:编程原理 主讲人:Peter Norvig 来自 Google 大牛 Peter Norvig ,也是十分经典的课程,内容分享了编程概念、模式和办法,通过课程你还能够学到技术大牛解决编程问题时的一些小技巧。 课程实用于有肯定根底的 Python 程序员,进一步晋升业余能力。 学习链接:https://url.leanapp.cn/GSRry3f 密码学及其利用 主讲人:Christof Paar, Professor for Embedded Security at Ruhr University Bochum B 站有课程搬运,但无中英文字幕,德国的传授,英文讲的很好,授课过程中也没有应用高难度的单词,加快速度高三听力程度还是能够听懂的。 课程可作为密码学入门,传授讲课格调由浅入深,延展性很强,每个公式定理都会现场推演一遍,手写板书的格调也让教学节奏不至于太快。 学完能够手推大部分密码学算法,同时会打下很好的实践根底。 学习链接:https://url.leanapp.cn/pnnyU6M C++ 教程 主讲人:Yan Chernikov 百万播放量,好评如潮,被网友评为互联网解说最具体的 C++ 教程。 学习链接:https://url.leanapp.cn/hUHJlLU 后端开发学习及实际 主讲人:TDS 工程师 由 TDS 团队的工程师分享的一系列后端学习课程,蕴含后端开发的一些实际教程。帮忙工程师实现开发初阶到高阶的转变。 学习链接:https://space.bilibili.com/50642811/video ...

October 7, 2021 · 1 min · jiezi

关于分布式系统:分布式系统的乐趣与收益

原文:http://book.mixu.net/distsys/... 译:祝坤荣 1.高层面看分布式系统分布式编程是通过应用多计算机来实现与单机算机雷同问题的艺术任何计算机系统都须要解决以下两个根本工作: 存储计算分布式编程是通过应用多计算机来实现与单机算机雷同问题的艺术 - 通常这是因为这个问题曾经不适宜在单个计算机解决了。 对于分布式系统没什么是真正须要的。给你有限的金钱和有限的研发工夫,咱们不须要分布式系统。所有计算和存储都能够在一个神奇盒子中实现 - 你能够花钱请人为你设一个单点,极速与极牢靠的零碎。 尽管如此,只有极少数人有有限的资源。因而,须要找到真实世界老本与收益的平衡点。在很小规模时,降级硬件是个间接的策略。然而,当问题域规模变大时,你会达到一个点,此时硬件降级曾经不能帮你解决一个单个节点能解决的问题,或者解决这个问题老本极为昂扬。在那个点上,欢送你来到分布式系统的世界。 当初的事实是只有中档配置的商用硬件最有价值 - 保护的老本能够通过采纳可容错软件来升高。 高端硬件最次要的计算收益来自于它们能够用外部内存拜访来代替迟缓的网络拜访。高端硬件的性能劣势在须要在节点间进行大量通信的工作时非常受限。 http://book.mixu.net/distsys/... 上图引自Barroso, Clidaras & Hölzle 的 https://www.morganclaypool.co... ,当假如所有节点都应用对立的内存拜访模式时,高端硬件与一般商用硬件的性能差距会变小。 个别感觉上,填加新机器会线性增长零碎的性能和容量。但理论这是不可能的,因为实际上这取决于这些独立的计算机。数据须要被复制,计算工作须要被协调等等。这也是学习分布式算法是值得的 - 它们为特定的问题提供了无效的解决方案,其领导了什么是可行的,一个正确的实现能够破费最小的老本是多大,什么是不可能的。 本文关注的是分布式编程,而零碎则是世俗的然而商业上常见的:数据中心。比方,我不会探讨一个特定网络配置上的特定问题,或者一个共享内存畛域的问题。另外,关注点会关注在零碎设计畛域而不是优化某个特定设计 - 后者是个更特定畛域的问题。 咱们想要达到:可伸缩能力和其余好货色我能看到,所有事件都是从解决规模大小开始的。 在规模很小时大部分事件都很简略 - 而当达到一个特定的规模后同样的问题则变得艰难起来,从流量或其余物理上的限度。举起一片巧克力很简略,举起一座山就很难了。数一个屋子里有多少人很简略,数一个国家里有多少人就很难。 所以事件都是从规模开始的 - 可扩大能力。按正式的说法,一个可伸缩的零碎中,当咱们从小变大,事件不应该在增长中变得更糟。这是另一个定义: 可伸缩(https://en.wikipedia.org/wiki... 是零碎,网络或处理器的一种能力, 以一种牢靠的形式解决一直增长的工作量或能本人变大来适应这种增长。那么什么是增长?实际上你能够通过任何形式来计算增长(人数,应用的电量等)。但有次要有以下三种须要关注: 数量伸缩:退出更多的节点能够让零碎线性增长;数据集的增长不应该导致提早的增长天文上的伸缩: 实践上能够通过多个数据中心来升高用户查问时的响应工夫,多个数据中心之间的提早能够以一种正当的形式解决。管理员伸缩: 填加更多的节点不应该减少管理员管理系统的老本(例如:管理员对机器量的比例)当然,在真实世界的增长同时产生在很多不同的维度;每种指标只捕获了增长中的其中一些局部。 一个可伸缩的零碎能够在用户规模增长时继续满足需要。这外面有两个特定的相干畛域 - 性能与可用性 - 其也能够被通过多种形式来掂量。 本文来自祝坤荣(时序)的微信公众号「麦芽面包」,公众号id「darkjune_think」 开发者/科幻爱好者/硬核主机玩家/业余翻译转载请注明。 微博:祝坤荣B站: https://space.bilibili.com/23... 交换Email: zhukunrong@yeah.net

August 1, 2021 · 1 min · jiezi

关于分布式系统:B站崩了豆瓣也崩了HTTP弊端暴露无疑IPFS优势慢慢突显

北京工夫7月13晚间,B站“停电”网站404打不开了,音讯一出迅速刷屏,占据各大热搜榜第一。 同时还有AcFun和豆瓣解体,由此看来解体的都是流量微小的网站,解体一分钟损失上千。 B站官媒发文称,B站的局部服务器机房收到故障,从而导致无奈失常拜访,技术团队立刻开展了排障和培修,当初服务器已恢复正常运作。 此音讯一出B站美股涨幅立马收窄! 由此看来咱们当初所应用的HTTP网络底层协定并不靠谱,经不起挫折和考验。咱们当初所应用的网络底层协定叫作HTTP,它是一个中心化存储形式,意思就是将所有的数据贮存在一个宏大的存储设备上,一旦这个宏大的设施呈现丝毫问题就会导致数据失落或者是数据异样,就像B站和豆瓣的这种景象,咱们在上网的时候经常会遇到403、403等等状况,其实这种状况就是数据失落或者损坏了。如果遇到十分重要的文件在HTTP上存储失落了这是十分低廉的损失,因为文件一旦失落或者损毁基本上是无奈找回的,HTTP除了容易失落还有很多弊病。比方、核心服务器被黑客入侵,所有的数据将没有隐衷可言,小到个人隐私大到国家机密都会被盗。你上传到云空间外面的机密照片可能会被播放给寰球70亿人观看。当初咱们破费在网络安全上的资金达到百亿甚至更高。 网络速度迟缓和存储老本低廉都是HTTP的弊病,当初一个几十G的云空间一年的租金随随便便大几百块钱,而这就是中心化存储的弊病,咱们在浏览一个网页时同时浏览人数过多就会导致加载速度迟缓,而这并不是网络速度的问题。大哥比如HPPT是一条马路,在这条路上的多了就会堵车相比而言IPFS并没有下面的这些故障,IPFS是一个联合区块链去中心化技术的分布式存储协定,它的理念是将一个数据别离存储在各个中央,这间接的保障了数据的平安,如果被入侵也只是一个小存储的数据,IPFS解决了HTTP当初空间适度冗余的问题。也就是说你想在IPFS上看一部电影,那么IPFS会问整个网络“有人有这个文件对应的哈希值吗?”在IPFS网络上就会有节点来返回文件,此时你就可能拜访到它。通俗易懂的说也就是同样一个文件只须要在网络上存储一次,而当前只有存储这颁布这个数据所有的IPFS用户只有有须要就能够应用。 因为IPFS是分布式存储,所以它也不会呈现HTTP那样的忽然打不开或者是加载迟缓,打个比方,HTTP是一条马路,那么IPFS就是无数条马路,这条走不通就走另一条。在数据 存储和替换过程中 IPFS 无疑是区块链的一场重大的反动 ,FIL矿机薇hbjky327作为有奉献有价值的网络,颠覆的便是如同亚马逊云这样的巨头 他是属于数据奉献型的我想在将来 更有价值的肯定是数据奉献型的 应用型根底的公链。 因为他们真正能做到大规模的数据共享。

July 14, 2021 · 1 min · jiezi

关于分布式系统:分布式ID生成方案选型详细解析雪花算法Snowflake

分布式惟一ID应用RocketMQ时,须要应用到分布式惟一ID音讯可能会产生反复,所以要在生产端做幂等性,为了达到业务的幂等性,生产者必须要有一个惟一ID, 须要满足以下条件: 同一业务场景要全局惟一该ID必须是在音讯的发送方进行生成发送到MQ生产端依据该ID进行判断是否反复,确保幂等性在哪里产生以及生产端进行判断做幂等性与该ID无关,此ID须要保障的个性: 部分甚至全局惟一趋势递增 Snowflake算法Snowflake是Twitter开源的分布式ID生成算法, 后果是一个Long型的ID,核心思想是: 应用1位作为符号位,确定为0, 示意正应用41位作为毫秒数应用10位作为机器的ID : 高5位是数据中心ID, 低5位是机器ID应用12位作为毫秒内的序列号, 意味着每个节点每秒能够产生4096(2^12^) 个ID该算法通过二进制的操作进行实现,单机每秒内实践上最多能够生成1000*(2^12), 即409.6万个ID SnowflakeIdWorkerSnowflake算法Java实现SnowflakeIdWorker: /** * Twitter_Snowflake<br> * SnowFlake的构造如下(每局部用-离开):<br> * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br> * 1位标识,因为long根本类型在Java中是带符号的,最高位是符号位,负数是0,正数是1,所以id个别是负数,最高位是0<br> * 41位工夫截(毫秒级),留神,41位工夫截不是存储以后工夫的工夫截,而是存储工夫截的差值(以后工夫截 - 开始工夫截) * 失去的值),这里的的开始工夫截,个别是咱们的id生成器开始应用的工夫,由咱们程序来指定的(如下上面程序IdWorker类的startTime属性)。41位的工夫截,能够应用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br> * 10位的数据机器位,能够部署在1024个节点,包含5位datacenterId和5位workerId<br> * 12位序列,毫秒内的计数,12位的计数顺序号反对每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br> * 加起来刚好64位,为一个Long型。<br> * SnowFlake的长处是,整体上依照工夫自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作辨别),并且效率较高,经测试,SnowFlake每秒可能产生26万ID左右。 */public class SnowflakeIdWorker { // ==============================Fields=========================================== /** 开始工夫截 (2015-01-01) */ private final long twepoch = 1420041600000L; /** 机器id所占的位数 */ private final long workerIdBits = 5L; /** 数据标识id所占的位数 */ private final long datacenterIdBits = 5L; /** 反对的最大机器id,后果是31 (这个移位算法能够很快的计算出几位二进制数所能示意的最大十进制数) */ private final long maxWorkerId = -1L ^ (-1L << workerIdBits); /** 反对的最大数据标识id,后果是31 */ private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); /** 序列在id中占的位数 */ private final long sequenceBits = 12L; /** 机器ID向左移12位 */ private final long workerIdShift = sequenceBits; /** 数据标识id向左移17位(12+5) */ private final long datacenterIdShift = sequenceBits + workerIdBits; /** 工夫截向左移22位(5+5+12) */ private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */ private final long sequenceMask = -1L ^ (-1L << sequenceBits); /** 工作机器ID(0~31) */ private long workerId; /** 数据中心ID(0~31) */ private long datacenterId; /** 毫秒内序列(0~4095) */ private long sequence = 0L; /** 上次生成ID的工夫截 */ private long lastTimestamp = -1L; //==============================Constructors===================================== /** * 构造函数 * @param workerId 工作ID (0~31) * @param datacenterId 数据中心ID (0~31) */ public SnowflakeIdWorker(long workerId, long datacenterId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); } if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); } this.workerId = workerId; this.datacenterId = datacenterId; } // ==============================Methods========================================== /** * 取得下一个ID (该办法是线程平安的) * @return SnowflakeId */ public synchronized long nextId() { long timestamp = timeGen(); //如果以后工夫小于上一次ID生成的工夫戳,阐明零碎时钟回退过这个时候该当抛出异样 if (timestamp < lastTimestamp) { throw new RuntimeException( String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } //如果是同一时间生成的,则进行毫秒内序列 if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; //毫秒内序列溢出 if (sequence == 0) { //阻塞到下一个毫秒,取得新的工夫戳 timestamp = tilNextMillis(lastTimestamp); } } //工夫戳扭转,毫秒内序列重置 else { sequence = 0L; } //上次生成ID的工夫截 lastTimestamp = timestamp; //移位并通过或运算拼到一起组成64位的ID return ((timestamp - twepoch) << timestampLeftShift) // | (datacenterId << datacenterIdShift) // | (workerId << workerIdShift) // | sequence; } /** * 阻塞到下一个毫秒,直到取得新的工夫戳 * @param lastTimestamp 上次生成ID的工夫截 * @return 以后工夫戳 */ protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * 返回以毫秒为单位的以后工夫 * @return 以后工夫(毫秒) */ protected long timeGen() { return System.currentTimeMillis(); } //==============================Test============================================= /** 测试 */ public static void main(String[] args) { SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0); for (int i = 0; i < 1000; i++) { long id = idWorker.nextId(); System.out.println(Long.toBinaryString(id)); System.out.println(id); } }}长处: ...

July 10, 2021 · 3 min · jiezi

关于分布式系统:Zookeeper-详细解析分布式架构中的协调服务框架的最佳选型

Zookeeper概念Zookeeper是分布式协调服务,用于治理大型主机,在分布式环境中协调和治理服务是很简单的过程,Zookeeper通过简略的架构和API解决了这个问题 Zookeeper实现分布式锁分布式锁三要素: 加锁 解锁 锁超时Zookeeper数据结构相似树结构,由节点Znode组成Znode分为四种类型: 长久节点(PERSISTENT): 默认节点类型,创立节点的客户端与Zookeeper断开连接后,节点仍旧存在长久节点程序节点(PERSISTENT_SEQUENTIAL): 长久节点程序节点就是在创立长久节点时,Zookeeper依据创立节点的工夫程序给节点进行编号长期节点(EPHEMERAL): 创立节点的客户端与Zookeeper断开连接后,长期节点会被删除长期节点程序节点(EPHEMERAL_SEQUENTIAL): 长期节点程序节点就是在创立长期节点时,Zookeeper依据创立节点的工夫程序给节点进行编号利用Zookeeper的长期程序节点,实现分布式锁Zookeeper与Redis分布式锁比拟: 分布式锁ZookeeperRedis长处1.有封装好的框架,容易实现2.有期待锁队列,晋升抢锁的效率Set和Del指令性能高毛病增加和删除节点性能低1.实现简单,须要思考原子性,误删,锁超时问题2.没有期待锁的队列,只能客户端自旋来等锁,效率低Zookeeper的数据模型相似数据结构中的树,文件系统中的目录Zookeeper的数据存储基于节点ZnodeZnode的援用形式是门路援用,每一个Znode节点领有惟一的门路 Znode中的元素data: Znode存储的数据信息ACL: 记录Znode的拜访权限,即哪些过程和IP能够拜访本节点stat: Znode的各种元数据(数据的数据)child: 以后节点的子节点援用Zookeeper的利用场景是读多写少的利用场景:Znode不用来存储大规模的业务数据,用于存储大量的状态和配置信息(Znode存储数据不能超过1MB) Zookeeper基本操作创立节点:create删除节点:delete判断节点是否存在:exists取得一个节点的数据:getData设置一个节点的数据:setData获取节点下的所有子节点:getChildrenexists,getData,getChildren属于读操作,Zookeeper客户端在申请读操作时,能够抉择是否设置watch Zookeeper事件告诉Watch能够了解成注册在特定Znode上的触发器当Znode产生扭转的时候,调用create,delete,setData办法,将会触发Znode上注册的对应事件,申请的Watch的客户端会接管到异步告诉Zookeeper事件告诉的交互过程: 客户端调用getData办法,watch的参数是true,服务端接管到申请,返回节点数据,在对应的Hash表中插入被Watch的Znode门路以及Watcher列表当被Watch的Znode删除,服务端会查找Hash表,找到该Znode对应的所有Watcher,异步告诉客户端,并且删除Hash表中对应的key-value Zookeeper的一致性Zookeeper Service集群是一主多从构造在更新数据时,首先更新到主服务器,再同步到从服务器在读数据时,间接读取任意节点采纳ZAB协定,为了保障主从节点数据的一致性 ZAB协定ZAB(Zookeeper Automic Broadcast): 解决Zookeeper集群解体复原,主从数据同步问题ZAB三种节点状态: Looking:选举状态Following:Following节点(从节点)所处的状态Leading:Leading(主节点)所处的状态最大ZXID: 节点本地的最新事务编号,蕴含epoch和计数两局部 ZAB集群解体复原当Zookeeper的主节点服务器宕机后,集群就会进行解体复原,分成三个阶段: Leader election(选举阶段): 集群中的节点处于Looking状态,各自向其它节点发动投票,投票当中蕴含本人服务器的ID和最新事务ID(ZXID)节点用本身的ZXID和其它节点收到的ZXID作比拟,如果发现其它节点的ZXID比本身大,即数据比本人新,就从新发动投票,投票给目前已知最大ZXID所属节点每次投票后,服务器都会统计投票数量,判断是否某个节点失去半数以上的投票,这样的节点将会成为准Leader,状态变为Leading,其它节点状态变为FollowingDiscovery(发现阶段): 在从节点发现最新的ZXID和事务日志,目标是为了避免在意外状况,选举产生多个LeaderLeader接管所有Follower发送的最新的epoch值,Leader从中选出最大的epoch,基于此值+1,生成新的epoch分发给各个Follower各个Follower接管到最新的epoch,返回ACK(响应码)给Leader,带上各自最大的ZXID和历史事务日志,Leader选出最大的ZXID,并更新本身历史日志Synchronization(同步阶段): 将Leader收集失去的最新历史事务日志,同步给集群中的所有Follower,只有当半数Follower同步胜利,这个准Leader能力成为正式Leader.集群解体复原正式实现 ZAB主从数据同步BroadcastZookeeper惯例状况下更新数据的时候,由Leader播送到所有的Follower: 客户端收回写入数据申请给任意的FollowerFollower把写入数据申请转发给LeaderLeader采取二阶段提交形式:(先保留提交日志,再提交数据)先发送Propose播送给FollowerFollower接管到Propose音讯,写入日志胜利后,返回ACK音讯给LeaderLeader接管到半数以上的ACK音讯,返回胜利给客户端,并且播送commit申请给Follower 数据一致性: 强一致性 弱一致性 程序一致性:Zookeeper,依附事务ID和版本号,保证数据的更新和读取是有序的Zookeeper利用场景分布式锁: 利用Zookeeper的长期程序节点,实现分布式锁服务注册与发现: 利用Znode和Watcher,实现分布式服务注册与发现,如Dubbo共享配置和状态信息: Redis的分布式解决方案Codls,利用Zookeeper存放数据路由表和codls-proxy节点元信息,同时colds-config发动的命令都会通过Zookeeper同步到各个存活的codls-proxy高可用实现: Kafka,HBase,Hadoop都依附Zookeeper同步节点信息,实现高可用 基于Docker创立Zookeeper1.创立docker-compose.ymlzoo: image: zookeeper restart: always hostname: zoo ports: - 2181:2181 environment: - ZOO_MY_ID: 1 - ZOO_SERVER: server.1(id)=zoo(IP):2888:38882.执行docker-compose up -dZookeeper三种工作模式单机模式: 存在单点故障集群模式: 在多台服务器上部署Zookeeper集群伪集群模式: 在同一台服务器上运行多个Zookeeper实例,依然有单点故障问题,其中配置的端口号要错开 Zookeeper三种端口号2181: 客户端连贯Zookeeper集群应用的监听端口号3888: 选举Leader应用2888: 集群内机器通信应用(Leader和Follower之间数据同步应用的端口号,Leader监听此端口)

May 18, 2021 · 1 min · jiezi

关于分布式系统:bigtable论文阅读笔记

0. AbstractBigtable是一个存储pb量级结构化数据的分布式存储系统。google外部很多我的项目都在应用Bigtable,包含web indexing、Google Earth、Google Finance等,这些利用在数据大小、提早要求等方面对Bigtable的要求各不相同。Bigtable胜利的提供了一个灵便的、高性能的解决方案。本篇论文形容了Bigtable提供的数据模型,以及Bigtable的设计与实现。 1. Introduction次要是一些概括性的内容,以及对前面各节次要内容的详情。 2. Data ModelBigtable是一个稠密的、分布式的、长久化的多维度有序map。这个map被row key、column key和一个工夫戳索引,map的每个value是未解析的字节数组,即:(row:string, column:string, time:int64) -> stringrow key是任意的字符串(目前的下限是64KB,对于大部分使用者而言10-100 bytes够了)。单个row key下的单次读或者写是原子的(即便是不同的column),这样设计的起因是为了让客户分明并发更新雷同row时的零碎行为。Bigtable依照row key的字典序保留数据。表的row range动静分区,每个row range被称为tablet,是散布和负载平衡的单位。因而,对short row range的读取很高效,个别仅须要和少部分机器进行通信。客户能够利用这个性质,通过抉择适合的row keys达到对数据拜访良好的局部性。 column keys被组织为column families,是管制的根本单位。保留在同一个column family的所有数据往往领有雷同的类型,一个表中反对较少的column families(最多几百个),并且很少扭转。相同的,一个表能够领有的column没有下限(从设计上来说)。一个column key的名字应用如下语法:family:qualifier。column family的名字必须是可打印的,而qualifiers能够是任意字符串。 timestamps是一个64-bit的整数。Bigtable中能够保留一份数据的多个版本;这些版本通过timestamps索引。timestamps能够由Bigtable赋值(real time),也能够由客户利用显式指定。须要防止抵触的利用须要本人产生惟一的timestamps。不同版本的数据依照timestamp降序存储。为了更轻松的治理多个版本的数据,Bigtable反对两种垃圾回收策略(在单个column-family外部)。基于工夫的和基于数量的,在此不赘述,见论文原文。 3. APIBigtable提供了创立和删除table以及column families的函数,也提供了扭转集群、table和column family元数据的函数,比方访问控制权限。Bigtable反对单行事务,可用来执行原子的读-批改-写操作,但不反对跨行事。本节其余局部略过,见原文。 4. Building BlocksBigtable建设在Google其余的基础设施上,比方应用GFS存储日志和数据文件。Bigtable也依赖于集群管理系统来调度工作、治理机器和资源、存储机器谬误、监控机器状态等。Bigtable的数据应用Google SSTable文件格式存储。一个SSTable提供了长久的、有序的不可变kv map,并且key和value都是任意字符串。每个SSTable外部含有一系列block(每个block个别是64kb,当然这是可配置的)。SSTable的最初存储有block index,可用来定位block。Bigtable依赖于一个高可用的、长久化的分布式锁服务Chubby(应该是一个相似于zk的零碎,这里不赘述),Bigtable应用它确保master的唯一性;存储数据的bootstrap location(见5.1);发现tablet服务和确定tablet服务death(见5.2);存储Bigtable的架构信息(每个表的column family信息);存储拜访权限列表。 5. ImplementationBigtable分为三个组件:一个master服务、多个tablet服务、和一个连贯客户端的library。tablet服务能够被动静增加以适应工作负载的变动。接着简要介绍了master服务和tablet服务的工作,见原文。 5.1 Tablet Location

May 6, 2021 · 1 min · jiezi

关于分布式系统:分布式系统架构中高可用方案技术选型Hystrix-框架实现服务保护使用详解

HystrixHystrix是Netflix开源的高可用框架,可能完满解决分布式系统架构中高可用服务的问题 断路器服务降级服务熔断服务隔离机制服务雪崩效应Hystrix具备自我爱护能力 服务爱护概念在微服务高可用分布式系统中会呈现:服务间的调用超时,服务间的调用时失败问题 服务雪崩效应默认状况下,Tomcat只有一个线程池解决客户端发送的申请,这样在高并发的状况下客户端所有申请沉积在同一个服务接口,就会产生Tomcat所有线程池去解决服务接口,会导致其它服务接口无法访问,这样在其它接口拜访的时候就会产生提早和期待服务雪崩效应重大会造成连环雪崩效应,可能会导致所有微服务接口无法访问,导致整个服务瘫痪 Tomcat中有个线程池,每个线程去解决客户端发送的每次申请基于Hystrix解决服务雪崩效应的机制: 服务降级:服务熔断:服务隔离:服务降级在高并发的状况下,避免用户期待,服务调用fallBack办法,返回一个敌对提醒间接给客户端而不会去解决申请,目标是为了晋升用户体验 当Tomcat中没有线程解决客户端申请的时候,不应该让界面统一转圈,让用户期待如果服务在调用其它接口超时的时候(默认1秒),默认状况下,业务逻辑是能够执行的,如果服务没有响应间接执行的是服务降级办法服务熔断在高并发的状况下,设定服务的阈值,当流量过高超出给定的阈值,会主动开启爱护性能,应用服务降级形式返回一个敌对提醒给客户端熔断机制和服务降级是一起作用的服务熔断的目标是为了爱护服务 服务隔离服务隔离有两种:线程池隔离和信号量隔离线程池隔离: 每个服务接口都有本人独立的线程池,每个线程池互补影响因为线程池CPU占用率十分高,不是所有服务接口都采纳线程池隔离,只有外围要害的接口才会采纳线程池隔离 Hystrix环境搭建导入Hystrix依赖:spring-cloud-starter-netflix-hystrix在服务消费者(Consumer)我的项目中的配置文件中开启Hystrix断路器 feign.hystrix.enabled=true在配置文件中设置hystrix服务超时工夫,避免业务服务响应不及时,执行服务降级 hystrix.command.default.execution.isolation.thread. timeoutInMilliseconds=10000在主类上标注@EnableFeignClient开启Fegin的Hystrix性能在服务实现的办法上标注 @HystrixCommand注解应用Hystrix框架 @HystrixCommand默认开启了服务降级,服务熔断,服务隔离@HystrixCommand中的服务隔离默认开启线程池隔离形式@HystrixCommand(fallback="服务降级提醒办法名称"),其中的fallback用于服务降级fallback接口Hystrix应用类形式fallback进行服务降级的办法解决 1.新建fallback类2.类上标注@Component注解将类加载到容器中3.调用fallback类时,在@FeignClient正文中增加fallback参数@FeignClient(fallback=Fallback.class)

May 4, 2021 · 1 min · jiezi

关于分布式系统:干货丨时序数据库DolphinDB作业管理概述

作业(Job)是DolphinDB中最根本的执行单位,能够简略了解为一段DolphinDB脚本代码在DolphinDB零碎中的一次执行。Job依据阻塞与否可分成同步作业和异步作业。 同步作业 同步作业也称为交互式作业(Interactive Job),它的次要起源有: Web notebookDolphinDB GUIDolphinDB命令行界面通过DolphinDB提供的各个编程语言API接口因为这种类型的作业对实时性要求较高,DolphinDB在执行过程中会主动给予较高的优先级,使其更快地失去计算资源。 异步作业 异步作业是在DolphinDB后盾执行的作业,包含: 通过submitJob或submitJobEx函数提交的批处理作业。通过scheduleJob函数提交的定时作业。Streaming 作业。这类工作个别对后果的实时反馈要求较低,且须要长期执行,DolphinDB个别会给予较低的优先级。 子工作 在DolphinDB中,若数据表数据量过大,个别都须要进行分区解决。如果一个Job A里含有分区表的查问计算工作(如SQL查问),将会分解成多个子工作并送到不同的节点上并行执行,期待子工作执行结束之后,再合并后果,持续Job A的执行。相似的,DolphinDB的分布式计算也会产生子工作。因而,Job也能够了解成一系列的子工作。 Worker与Executor DolphinDB是一个P2P架构的零碎,即每一个Data Node的角色都是雷同的,它们都能够执行来自用户提交的Job,而因为一个Job可能产生子工作,每个Data Node须要有负责Job外部执行的调度者,咱们称它为Worker,它负责解决用户提交的Job,简略计算工作的执行,并执行Job的工作合成,工作散发,并会集最终的执行后果。Job中合成进去的子工作将会被散发到集群中的Data Node上(也有可能是本地Data Node),并由Data Node上的Worker或Executor线程负责执行。 具体Worker与executor在执行job的时候次要有以下几种状况: 当一个表没有进行分区,对其查问的Job将会有Worker线程执行掉。当一个表被分区寄存在单机上时候,对其的查问Job可能会分解成多个子工作,并由该节点上的多个Executor线程执行,达到并行计算的成果。当一个表被分区存储在DFS时,对其查问的Job可能会被分解成多个子工作,这些子工作会被分发给其余Node的Worker上执行,达到分布式计算的成果。为了最大化性能,DolphinDB会将子工作发送到数据所在的Data Node上执行,以缩小网络传输开销。比方: 对于存储在DFS中的分区表,Worker将会依据分区模式以及分区以后所在Data Node来进行工作合成与散发。对于分布式计算,Worker将会依据数据源信息,发送子工作到相应的数据源Data Node执行。Job调度 Job优先级 在DolphinDB中,Job是依照优先级进行调度的,优先级的取值范畴为0-9,取值越高优先级则越高。对于优先级高的Job,零碎会更及时地给与计算资源。每个Job个别默认会有一个default priority,取值为4,而后依据Job的类型又会有所调整。 Job调度策略 基于Job的优先级,DolphinDB设计了多级反馈队列来调度Job的执行。具体来说,系统维护了10个队列,别离对应10个优先级,零碎总是调配线程资源给高优先级的Job,对于处于雷同优先级的Job,零碎会以round robin的形式调配线程资源给Job;当一个优先级队列为空的时候,才会解决低优先级的队列中的Job。 Job并行度 因为一个Job可能会分成多个并行子工作,DolphinDB的Job还领有一个并行度parallelism,示意在一个Data Node上,将会最多同时用多少个线程来执行Job产生的并行任务,默认取值为2,能够认为是一种工夫片单位。举个例子,若一个Job的并行度为2,Job产生了100个并行子工作,那么Job被调度的时候零碎只会调配2个线程用于子工作的计算,因而须要50轮调度能力实现整个Job的执行。 Job优先级的动态变化 为了避免处于低优先级的Job被长时间饥饿,DolphinDB会适当升高Job的优先级。具体的做法是,当一个job的工夫片被执行结束后,如果存在比其低优先级的Job,那么将会主动升高一级优先级。当优先级达到最低点后,又回到初始的优先级。因而低优先级的工作迟早会被调度到,解决了饥饿问题。 设置Job的优先级 DolphinDB的Job的优先级能够通过以下形式来设置: 对于console、web notebook以及API提交上来的都属于interactive job,其优先级取值为min(4,一个可调节的用户最高优先级),因而能够通过扭转用户本身的优先级值来调整。对于通过submitJob提交上的batch job,零碎会给与default priority,即为4。用户也能够应用submitJobEx函数来指定优先级。定时工作的优先级无奈扭转,默认为4。计算容错 DolphinDB database 的分布式计算含有肯定的容错性,次要得益于分区正本冗余存储。当一个子工作被发送到一个分区正本节点上之后,若节点呈现故障或者分区正本产生了数据校验谬误(正本损坏),Job Scheduler(即某个Data Node的一个worke线程)将会发现这个故障,并且抉择该分区的另一个正本节点,从新执行子工作。用户能够通过设置dfsReplicationFactor参数来调整这种冗余度。 计算与存储耦合以及作业之间的数据共享 DolphinDB的计算是尽量凑近存储的。DolphinDB之所以不采纳计算存储拆散,次要有以下几个起因: 计算与存储拆散会呈现数据冗余。思考存储与计算拆散的Spark+Hive架构,Spark应用程序之间是不共享存储的。若N个Spark应用程序从Hive读取某个表T的数据,那么首先T要加载到N个Spark应用程序的内存中,存在N份,这将造成机器内存的的节约。在多用户场景下,比方一份tick数据可能会被多个剖析人员共享拜访,如果采取Spark那种模式,将会进步IT老本。拷贝带来的提早问题。尽管说当初数据中心逐步装备了RDMA,NVMe等新硬件,网络提早和吞吐曾经大大提高。然而这次要还是在数据中心,DolphinDB零碎的部署环境可能没有这么好的网络环境以及硬件设施,数据在网络之间的传输会成为重大的性能瓶颈。综上这些起因,DolphinDB采取了计算与存储耦合的架构。具体来说: 对于内存节约的问题,DolphinDB的解决方案是Job(对应Spark应用程序)之间共享数据。在数据通过分区存储到DolphinDB的DFS中之后,每个分区的正本都会有本人所属的节点,在一个节点上的分区正本将会在内存中只存在一份。当多个Job的子工作都波及到同一个分区正本时,该分区正本在内存中能够被共享地读取,缩小了内存的节约。对于拷贝带来的提早问题,DolphinDB的解决方案是将计算发送到数据所在的节点上。一个Job依据DFS的分区信息会被分解成多个子工作,发送到分区所在的节点上执行。因为发送计算到数据所在的节点上相当于只是发送一段代码,网络开销大大减少。

January 14, 2021 · 1 min · jiezi

关于分布式系统:分布式锁的演化常用锁的种类以及解决方案

前言上一篇分布式锁的文章中,通过超市寄存物品的例子和大家简略分享了一下Java锁。本篇文章咱们就来深入探讨一下Java锁的品种,以及不同的锁应用的场景,当然本篇只介绍咱们罕用的锁。咱们分为两大类,别离是乐观锁和乐观锁,偏心锁和非偏心锁。 乐观锁和乐观锁乐观锁老猫置信,很多的技术人员首先接触到的就是乐观锁和乐观锁。老猫记得那时候是在大学的时候接触到,过后是上数据库课程的时候。过后的利用场景次要是在更新数据的时候,当然多年工作之后,其实咱们也晓得了更新数据也是应用锁十分次要的场景之一。咱们来回顾一下个别更新的步骤: 检索出须要更新的数据,提供给操作人查看。操作人员更改须要批改的数值。点击保留,更新数据。这个流程看似简略,然而如果一旦多个线程同时操作的时候,就会发现其中暗藏的问题。咱们具体看一下: A检索到数据;B检索到数据;B批改了数据;A批改了数据,是否可能批改胜利呢?上述第四点A是否可能批改胜利当然要看咱们的程序如何去实现。就从业务上来讲,当A保留数据的时候,最好的形式应该零碎给出提醒说“以后您操作的数据已被其他人批改,请从新查问确认”。这种其实是最正当的。 那么这种形式咱们该如何实现呢?咱们看一下步骤: 在检索数据的时候,咱们将相干的数据的版本号(version)或者最初的更新工夫一起检索进去。当操作人员更改数据之后,点击保留的时候在数据库执行update操作。当执行update操作的时候,用步骤1检索出的版本号或者最初的更新工夫和数据库中的记录做比拟;如果版本号或者最初更新工夫统一,那么就能够更新。如果不统一,咱们就抛出上述提醒。其实上述流程就是乐观锁的实现思路。在Java中乐观锁并没有确定的办法,或者关键字,它只是一个解决的流程、策略或者说是一种业务计划。看完这个之后咱们再看一下Java中的乐观锁。 乐观锁,它是假如一个线程在取数据的时候不会被其余线程更改数据。就像上述形容相似,然而只有在更新的时候才会去校验数据是否被批改过。其实这种就是咱们常常听到的CAS机制,英文全称(Compare And Swap),这是一种比拟替换机制,一旦检测到有抵触。它就会进行重试。直到最初没有抵触为止。 乐观锁机制图示如下:上面咱们来举个例子,置信很多同学都是C语言入门的编程,老猫也是,大家应该都接触过i++,那么以下咱们就用i++做例子,看看i++是否是线程平安的,多个线程并发执行的时候会存在什么问题。咱们看一下上面的代码: /** * @author kdaddy@163.com * @date 2020/12/15 22:42 */public class NumCountTest { private int i=0; public static void main(String[] args) { NumCountTest test = new NumCountTest(); //线程池:50个线程 ExecutorService es = Executors.newFixedThreadPool(50); //闭锁 CountDownLatch cdl = new CountDownLatch(5000); for (int i = 0;i < 5000; i++){ es.execute(()->{ test.i++; cdl.countDown(); }); } es.shutdown(); try { //期待5000个工作执行实现后,打印出执行后果 cdl.await(); System.out.println("执行实现后,i="+test.i); } catch (InterruptedException e) { e.printStackTrace(); } }}下面的程序中,咱们用50个线程同时执行i++程序,总共执行5000次,依照惯例的了解,失去的应该是5000,然而咱们间断运行三次,失去的后果如下: ...

January 3, 2021 · 3 min · jiezi

关于分布式系统:干货丨如何水平扩展和垂直扩展DolphinDB集群

随着业务的扩大,数据量一直积攒,数据库系统的数据容量和计算能力会逐步不堪重负,因而优良的数据库系统必须具备良好的扩展性。DolphinDB集群中的数据节点是集计算和存储于一体的,所以要进步计算能力和数据容量,只需针对数据节点即可。DolphinDB既反对程度扩大,即减少节点,也反对垂直扩大,即减少节点的存储。 在扩大集群前,须要对DolphinDB集群有根本的概念。DolphinDB集群由3个角色组成:管制节点(Controller)、代理节点(Agent)和数据节点(Data Node)。每个角色任务分配如下: 管制节点负责管理元数据,提供Web集群管理工具。代理节点负责节点的启动和进行,每台服务器上必须有一个代理节点。数据节点负责计算和存储。与集群相干的配置文件,个别位于config目录下: controller.cfg:位于管制节点所在的服务器,负责定义管制节点的相干配置,如IP、端口号、管制节点连接数下限等。 cluster.cfg:位于管制节点所在的服务器,负责定义集群内每一个节点的个性化配置,如存储门路、连接数、内存限度等。 cluster.nodes:位于管制节点所在的服务器,集群的成员配置文件,蕴含节点的IP、端口、节点别名和角色。 agent.cfg:蕴含代理节点的IP、端口和管制节点的IP和端口。每个物理服务器必须有一个代理节点。 如果是程度扩大集群,须要批改集群的成员配置文件(cluster.nodes),如果数据节点位于新的物理服务器上,那么还须要部署一个新的代理节点(agent.cfg)来负责新物理机上节点的启停,而后重启管制节点来加载新的数据节点。当新的数据节点启动后,节点的计算能力会即时纳入集群的计算资源兼顾,然而曾经存储在集群中的数据不会调整到新的数据节点,零碎会将后续新进入的数据按策略调配到各个数据节点。 如果是垂直扩大集群,只须要批改数据节点的配置文件(cluster.cfg),为指定节点的volumes参数减少门路。 上面将具体介绍扩大集群的步骤。 1. 集群配置阐明集群部署能够参考教程多物理服务器集群部署。 示例集群有3个数据节点,每个数据节点位于一台物理服务器上,管制节点位于另外一台物理服务器上: 管制节点:172.18.0.10 数据节点1:172.18.0.11 数据节点2:172.18.0.12 数据节点3:172.18.0.13 各个配置文件的信息如下: controller.cfg localSite=172.18.0.10:8990:ctl8990cluster.nodes localSite,mode172.18.0.11:8701:agent1,agent172.18.0.12:8701:agent2,agent172.18.0.13:8701:agent3,agent172.18.0.11:8801:node1,datanode172.18.0.12:8802:node2,datanode172.18.0.13:8803:node3,datanode数据节点1所在物理服务器上的agent.cfg localSite=172.18.0.11:8701:agent1controllerSite=172.18.0.10:ctl8900为了体现扩大后的成果,咱们首先在集 群中创立一个分布式数据库,并写入数据: data = table(1..1000 as id,rand(`A`B`C,1000) as name)//分区时预留了1000的余量,准备后续写入测试用db = database("dfs://scaleout_test_db",RANGE,cutPoints(1..2000,10))tb = db.createPartitionedTable(data,"scaleoutTB",`id)tb.append!(data)执行完后通过Web的DFS Explorer察看数据的散布状况: 扩大集群后,咱们能够通过追加新的数据来察看新的节点或存储是否启用。 2. 程度扩大因为业务数据量增大,集群的存储和计算能力不能满足要求,现新增一台服务器,并把它退出原来的集群作为一个新的节点。新增的服务器IP地址为172.18.0.14,采纳8804端口号,别名为node4。新增服务器须要部署代理节点,采纳8701端口,别名为agent4. 步骤如下: (1)部署新的代理节点 把DolphinDB的安装包拷贝至新的服务器,并解压。在server文件夹下新增config文件夹,并创立agent.cfg,减少以下内容: #指定Agent自身的ip和端口localSite=172.18.0.14:8701:agent4#通知Agent本集群的controller地位controllerSite=172.18.0.10:8990:ctl8990mode=agent(2)批改集群成员配置 到管制节点所在的物理服务器,批改config/cluster.nodes,新增集群成员信息。批改后的文件内容为: localSite,mode172.18.0.11:8701:agent1,agent172.18.0.12:8701:agent2,agent172.18.0.13:8701:agent3,agent172.18.0.14:8701:agent4,agent172.18.0.11:8801:node1,datanode172.18.0.12:8802:node2,datanode172.18.0.13:8803:node3,datanode172.18.0.14:8804:node4,datanode(3)重启集群 Linux环境下,应用命令pkill dolphindb,敞开集群。期待端口资源开释后,重新启动controller和各个agent,命令如下: 启动controller: nohup ./dolphindb -console 0 -mode controller -script dolphindb.dos -config config/controller.cfg -logFile log/controller.log -nodesFile config/cluster.nodes &启动agent: ./dolphindb -mode agent -home data -script dolphindb.dos -config config/agent.cfg -logFile log/agent.log在浏览器地址栏中输出管制节点的IP和端口号,如172.18.0.10:8990,来拜访Web,咱们能够看到新减少的代理节点agent4曾经启动,数据节点node4处于关停状态。 ...

December 29, 2020 · 1 min · jiezi

关于分布式系统:干货丨时序数据库DolphinDB数据导入教程

企业在应用大数据分析平台时,首先须要把海量数据从多个数据源迁徙到大数据平台中。 在导入数据前,咱们须要了解 DolphinDB database 的基本概念和特点。 DolphinDB数据表按存储介质分为3种类型: 内存表:数据只保留在本节点内存,存取速度最快,然而节点敞开后,数据将会失落。本地磁盘表:数据保留在本地磁盘上,即便节点重启,也能够不便地通过脚本把数据加载到内存中。分布式表:数据在物理上散布在不同的节点,通过DolphinDB的分布式计算引擎,逻辑上依然能够像本地表一样做对立查问。DolphinDB数据表按是否分区分为2种类型: 一般表分区表在传统的数据库中,分区是针对数据表的,即同一个数据库中的每个数据表能够有不同的分区计划;而DolphinDB的分区是针对数据库的,即一个数据库只能应用一种分区计划。如果两个表的分区计划不同,它们不能放在同一个数据库中。 DolphinDB提供了3种灵便的数据导入办法: 通过CSV文本文件导入通过HDF5文件导入通过ODBC导入1.通过CSV文本文件导入通过CSV文件进行数据直达是比拟通用的数据迁徙形式。DolphinDB提供了loadText、ploadText和loadTextEx三个函数来导入CSV文件。上面咱们通过一个示例CSV文件candle_201801.csv来阐明这3个函数的用法。 1.1 loadText 语法:loadText(filename, [delimiter=','], [schema]) 参数: _filename_是文件名。 _delimiter_和_schema_都是可选参数。 _delimiter_用于指定不同字段的分隔符,默认是“,”。 _schema_用于数据导入后每个字段的数据类型,它是一个table类型。DolphinDB提供了字段类型自动识别性能,然而某些状况下零碎自动识别的数据类型不合乎需要,比方咱们在导入示例CSVcandle_201801.csv时,volume字段会被辨认成INT类型,实际上咱们须要LONG类型,这时就须要应用schema参数。 创立schema table的脚本: nameCol = `symbol`exchange`cycle`tradingDay`date`time`open`high`low`close`volume`turnover`unixTimetypeCol = [SYMBOL,SYMBOL,INT,DATE,DATE,INT,DOUBLE,DOUBLE,DOUBLE,DOUBLE,INT,DOUBLE,LONG]schemaTb = table(nameCol as name,typeCol as type)当表的字段十分多时,创立schema table的脚本会非常简短。为了防止这个问题,DolphinDB提供了extractTextSchema函数,它能够从文本文件中提取表的构造,咱们只需批改须要指定的字段类型即可。 dataFilePath = "/home/data/candle_201801.csv"schemaTb=extractTextSchema(dataFilePath)update schemaTb set type=`LONG where name=`volume tt=loadText(dataFilePath,,schemaTb)1.2 ploadText ploadText把数据文件作为分区表并行加载到内存中,语法和loadText完全相同,然而ploadText的速度更快。ploadText次要用于疾速载入大文件,它在设计上充分利用了多个core来并行载入文件,并行水平取决于服务器自身core数量和节点的localExecutors配置。 上面咱们比照loadText和ploadText的性能。 首先,通过脚本生成一个4G左右的CSV文件: filePath = "/home/data/testFile.csv"appendRows = 100000000dateRange = 2010.01.01..2018.12.30ints = rand(100, appendRows)symbols = take(string('A'..'Z'), appendRows)dates = take(dateRange, appendRows)floats = rand(float(100), appendRows)times = 00:00:00.000 + rand(86400000, appendRows)t = table(ints as int, symbols as symbol, dates as date, floats as float, times as time)t.saveText(filePath)别离应用loadText和ploadText来导入文件,该节点是4核8线程的CPU。 ...

December 11, 2020 · 2 min · jiezi

关于分布式系统:分布式电商项目一分布式思想项目搭建

分布式思维分布式计算阐明: 一项工作由多个服务器共同完成的.例子: 假如一项工作独自实现须要10天,如果有10集体同时执行则一天实现. 大数据处理技术. 分布式系统阐明: 将我的项目依照特定的功能模块及层级进行拆分.从而升高整个零碎架构的耦合性问题. 分布式我的项目拆分外围:无论未来我的项目怎么拆分,都是同一个零碎. 口诀: 对外对立,对内互相独立 依照模块拆分因为单体架构中耦合性太高,所以采纳了分布式思维,将我的项目依照模块进行拆分,使得各个模块之间相互不影响.进步了整体的扩展性. 依照层级拆分阐明:因为某些我的项目性能实现起来比较复杂,须要多人协同单干,则须要将我的项目依照层级再次拆分. 分布式系统引发的问题1.分布式系统中jar包文件如何对立治理?2.分布式系统中工具API如何对立治理? 京淘我的项目后端搭建创立父级工程jt新建我的项目打包形式: pom 示意:该我的项目是一个聚合工程,里边蕴含了很多的小我的项目,并且该我的项目能够对立治理公共的jar包文件. 编辑POM.xml文件<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.jt</groupId> <artifactId>jt2007</artifactId> <version>1.0-SNAPSHOT</version> <!--1.设定打包形式 为聚合工程--> <packaging>pom</packaging> <!--2.对立治理jar包--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <java.version>1.8</java.version> <skipTests>true</skipTests> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <!--spring整合mybatis-plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.2.0</version> </dependency> <!--springBoot整合JSP增加依赖 --> <!--servlet依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> </dependency> <!--jstl依赖 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <!--使jsp页面失效 --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <!--增加httpClient jar包 --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency> <!--引入dubbo配置 --> <!--<dependency> <groupId>com.alibaba.boot</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>0.2.0</version> </dependency>--> <!--增加Quartz的反对 --> <!--<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>--> <!-- 引入aop反对 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!--spring整合redis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> </dependency> </dependencies> <!-- 注意事项: 聚合工程自身不须要公布,所以不要增加 build标签 --></project>编辑工具API jt-common打包类型: jar ...

November 23, 2020 · 5 min · jiezi

关于分布式系统:分布式系统如何实现用户追踪和认证

世界上最快的捷径,就是好高鹜远,本文已收录【架构技术专栏】关注这个喜爱分享的中央。在一些互联网公司的面试中,面试官往往会问这样一个问题: 如果禁用浏览器 cookie,如何实现用户追踪和认证? 遗憾的是仍然有大量候选人答非所问,无奈搞清楚 cookie 和 session 之间的区别。 而在工作中也有让人诧异的实在案例: 把 user ID 存储到 local storage 中当做 token 应用,起因是他们宣称弃用了 cookie 这种落后的货色; 一个挪动端我的项目,服务器给出的 API 中须要客户端模仿一个 cookie,从而像浏览器中 ajax 那样生产 API。 互联网是基于 HTTP 协定构建的,而 HTTP 协定因为简略风行开来,然而 HTTP 协定是无状态(通信层面上虚电路比数据报低廉太多)的,为此人们为了追踪用户想出了各种方法,包含 cookie/session 机制、token、flash 跨浏览器 cookie 甚至浏览器指纹等。 把用户身份藏在每一个中央(浏览器指纹技术甚至不须要存储介质) 讲应用 spring security 等具体技术的材料曾经很多了,这篇文章不打算写框架和代码的具体实现。 咱们会探讨认证和受权的区别,而后会介绍一些被业界宽泛采纳的技术,最初会聊聊怎么为 API 构建抉择适合的认证形式。 认证、受权、凭证首先,认证和受权是两个不同的概念,为了让咱们的 API 更加平安和具备清晰的设计,了解认证和受权的不同就十分有必要了,它们在英文中也是不同的单词。 认证( authentication) 指的是以后用户的身份,当用户登陆过后零碎便能追踪到他的身份做出合乎相应业务逻辑的操作。 即便用户没有登录,大多数零碎也会追踪他的身份,只是当做来宾或者匿名用户来解决。 认证技术解决的是 “我是谁?”的问题。 受权是 (authorization) 指的是什么样的身份被容许拜访某些资源,在获取到用户身份后持续检查用户的权限。 繁多的零碎受权往往是随同认证来实现的,然而在凋谢 API 的多系统结构下,受权能够由不同的零碎来实现,例如 OAuth。 受权技术是解决“我能做什么?”的问题。 ...

November 10, 2020 · 3 min · jiezi

关于分布式系统:分布式存储高可用方案探究

为防止单点瓶颈,进步存储的可用性及负载能力,零碎通常部署多个节点。但此时会呈现一些问题: 客户端写入数据胜利,零碎各内节点的数据是否也都写入胜利如果零碎内一个节点挂掉,零碎是否仍旧可用如果零碎内因为网络故障产生分区,零碎是否仍旧可用这些问题是多节点的分布式存储系统必须面对并解决的问题,即保证系统的数据统一,可用和分区容忍。 零碎的高可用有两种策略: 主从模式:分主从节点,主节点挂了从节点主动选主切换为主节点。只有主节点可进行写操作,从节点复制主节点数据,只可读以减轻负担。如:zookeeper,redis sentinel复制模式:节点角色平等,相互通信替换信息,一个节点挂掉会被踢出集群,不影响零碎的应用。如:Eureka,redis cluster(数据分片,元信息通过gossip保障统一)咱们心愿所有节点最终均保留残缺的数据,以便客户端可从任意节点读取数据,进步读取性能。零碎某一节点挂掉后也能复原数据。 为进步存储服务的写入性能,会对数据持续分片,每个分片服务要做到高可用,个别为主从部署。如Redis cluster,es,kafka。客户端读取或写入数据时,先路由到相应节点再读取或写入。三者不同的是,读取redis cluster须要客户端找到指定分片节点,如果cluster发现数据不在客户端申请的分片(slot产生迁徙),会返回客户端正确的分片地址,客户端再次发动申请。es不必客户端找到指定分片,它会在外部进行路由,客户端申请一次即可失去数据。 分布式一致性协定探讨了多个节点的数据如何达成统一,即不同节点如何替换数据,包含: 如何写入数据,数据如何同步到其余节点主/从节点挂了如何切换至从节点并使其余节点晓得集群状态感知,如新节点退出其余节点如何晓得或者不须要晓得按节点间的关系可分为两大类: 有主节点,即主从模式:raft zab paxos。集群的节点分主从,主节点负责写操作,而后同步到从节点,从节点负责读操作。当主节点挂了,一个从节点被选举晋升为主节点。无主节点,即复制模式:Gossip协定。节点的角色雷同,无主从之分,任何节点都可进行读写。如果一个节点写入数据,会随机同步至n个节点,这n个节点持续随机向n个节点同步,最终集群所有节点状态雷同。几个实践:CAP:分布式系统的个性,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。是NOSQL数据库的基石。 Consistency(一致性):分布式系统中各节点在同一时刻,同一key的value雷同Availability(可用性):集群中某一节点挂掉后依然可用Partition tolerance(分区容错性):集群内如果网络分区,一些节点的数据不会被分区外的客户端拜访到,因而要求数据保留在所有的节点。Base:是Basically Available(根本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的简写,BASE是对CAP中一致性和可用性衡量的后果。其核心思想是即便无奈做到强一致性(Strong consistency),但每个利用都能够依据本身的业务特点,采纳适当的形式来使零碎达到最终一致性(Eventual consistency)。 Basically Available(根本可用):假如零碎,呈现了不可预知的故障,但还是能用,相比拟失常的零碎而言: 响应工夫上的损失:失常状况下的搜索引擎 0.5 秒即返回给用户后果,而根本可用的搜索引擎能够在 1 秒作用返回后果。性能上的损失:在一个电商网站上,失常状况下,用户能够顺利完成每一笔订单,然而到了大促期间,为了爱护购物零碎的稳定性,局部消费者可能会被疏导到一个降级页面。Soft state(软状态):绝对于原子性而言,要求多个节点的数据正本都是统一的,这是一种 “硬状态”。 软状态指的是:容许零碎中的数据存在中间状态,最终并认为该状态不影响零碎的整体可用性,即容许零碎在多个不同节点的数据正本存在数据延时。如设置key为1,其中在A节点的值已设为1,但在B节点仍为2,但最终会为1.。容许一些节点的值不为1Eventually consistent(最终一致性):不可能始终是软状态,必须有个工夫期限。在期限过后,该当保障所有正本保持数据一致性。从而达到数据的最终一致性。这个工夫期限取决于网络延时,零碎负载,数据复制方案设计等等因素。零碎可能保障在没有其余新的更新操作的状况下,数据最终肯定可能达到统一的状态,因而所有客户端对系统的数据拜访最终都可能获取到最新的值。Acid:atomic原子性,consistence一致性,isolation隔离性,duration持久性,保障事务的强一致性 分布式系统不会是一个完满的零碎,即数据写入时所有节点会立刻更新最新的数据且始终提供服务,如果零碎内某个节点挂掉,不影响读写操作整个零碎像是一个单体利用,满足acid。因为数据同步到各个节点须要肯定工夫,在客户端收到写入胜利的响应后,所有节点是否实现数据的变更,此时再申请,是否会失去最新数据。如果一个节点挂掉,零碎是否会响应客户端申请。多节点的存在使得它受CAP原理的限度。满足CP:ZK,AP:eureka

October 19, 2020 · 1 min · jiezi

关于分布式系统:分布式系统之美知乎圆桌精选大放送快来看看有没有你关注的问题吧

「分布式系统之美」知乎圆桌已上线一周, 局部问答引发了网友的热烈探讨,接下来就追随小编一起来盘点大家最关怀的问题吧! 圆桌精选问答:1. 在什么状况下你须要思考换个数据库了?作者:kylin (伴鱼技术中台负责人) 业务零碎为了取得的良好程度扩大能力,都偏向于将业务服务无状态化,将状态存储到数据库中,这样数据库很多时候都是业务零碎最外围的局部,所以换数据库是一件须要审慎决策事件。 然而,产生换数据库这个念头,是不须要做一个审慎的决定,这还只是一个想法,能够先调研,小范畴试用,我感觉在上面两个状况下,能够动动换数据库的这个念头: 1、数据库技术上呈现重大的改革 一般来说,都是业务推动技术改革,那其实也就阐明了在呈现技术改革的公司,肯定是呈现了新的业务场景用当初的技术不能很好解决,这个新的业务场景可能目前公司还没有碰到,然而很可能在不久的未来会遇到。一个很好的例子,Google 在 2000 年前后碰到大数据的问题,而后推出了 GFS、MapReduce 和 Bigtable 技术创新(其实也不是严格意义上的技术创新)来解决这一问题,在 Google 遇到大数据问题的时候,大部分公司应该还感知不强烈,然而在明天看来,大数据的浪潮又放过了谁? 所以,在技术呈现重大改革的时候,咱们须要去思考推动技术改革的业务需要是什么,咱们公司的业务当前会有呈现这个业务需要吗? 如果答案是必定的,那么能够动一动这个念头,先调研,小范畴试用。这其实就是重要不紧急的事件,如果咱们把它变成了重要紧急的事件,这其实就是技术方向把控上的谬误。 对于数据库来说,更是如此,它的业务差异比拟小,个别都是比拟共性的业务需要,所以如果数据库技术呈现重大的改革的时候,先调研,小范畴试用,是凋谢的技术思维,防止将重要不紧急的事件变成重要紧急的事件。 2、业务场景上呈现重大的变动 ... 点击以下链接查看残缺答复 https://www.zhihu.com/question/413947496/answer/1406278473 2. 云数据库时代,将来 DBA 如何给本人升职加薪?作者:笨猫儿 (送外卖的资深互联网 DBA,酷爱 MySQL、分布式数据库) ... 咱们可能看到随着云技术的大规模应用,数据库逐渐走向云原生方向,云厂商承接了数据库的基础设施撑持,企业在逐渐缩减在基础架构的建设上投入。 将来,云厂商在挤压企业外部传统 DBA 的生存空间的同时,也带了新的机会,云厂商不足云上数据库管控平台研发工程师、云数据库的架构师/技术布道师、云厂商的技术售前、云数据库的运维 DBA 等。 DBA 将依靠于云平台从原来服务于一家企业转变为服务云上成千上万家企业,在取得更高的成就感和集体影响力,也能播种更高的职位和薪资 ... 点击以下链接查看残缺答复 https://www.zhihu.com/question/413947284/answer/1406992021 3. 如何系统性的学习分布式系统?作者:kylin ( 伴 鱼技术中台负责人 ) 学习一个常识之前,我感觉比拟好的形式是先了解它的前因后果:即这个常识产生的过程,它解决了什么问题,它是怎么样解决的并且它带来了哪些问题,这样咱们能力比拟好的抓到它的脉络和关键点,不会一开始就迷失在细节中。 所以,咱们要解决的第一个问题是:分布式系统解决了什么问题? 第一个是单机性能瓶颈导致的老本问题,因为摩尔定律生效,便宜 PC 机性能的瓶颈无奈持续冲破,小型机和大型机能进步更高的单机性能,然而老本太大高,个别的公司很难接受; 第二个是用户量和数据量爆炸性的增大导致的老本问题,进入互联网时代,用户量爆炸性的增大,用户产生的数据量也在爆炸性的增大,然而单个用户或者单条数据的价值其实比软件时代(比方银行用户)的价值是只低不高,所以必须寻找更经济的计划; 第三个是业务高可用的要求,对于互联网的产品来说,都要求 7 * 24 小时提供服务,无奈容忍进行服务等故障,而要提供高可用的服务,惟一的形式就是减少冗余来实现,这样就算单机零碎能够撑持的服务,因为高可用的要求,也会变成一个分布式系统。 基于下面的三个起因能够看出,在互联网时代,单机零碎是无奈解决老本和高可用问题的,然而这两个问题对简直对所有的公司来说都是十分要害的问题,所以,从单机零碎到分布式系统是无奈防止的技术大潮流。 那么,分布式系统是怎么来解决单机零碎面临的老本和高可用问题呢? 点击以下链接查看残缺答复 https://www.zhihu.com/question/320812569/answer/1386491563 4. 为什么简直所有的开源数据库中间件都是国内公司开源的?并且简直都进行了更新?作者:cx3ptr ( 伴 鱼基础架构负责人 ) ...

August 26, 2020 · 1 min · jiezi

分布式离不开的Gossip协议

Gossip协议

July 5, 2020 · 1 min · jiezi

海外直播软件-Bigo-的-TiDB-40-线上实践

作者介绍:徐嘉埥,Bigo DBA,TUG 华南区大使。Bigo 于 2014 年成立,是一家高速发展的科技公司。Bigo 基于强大的音视频处理技术、全球音视频实时传输技术、人工智能技术、CDN 技术,推出了一系列音视频类社交及内容产品,包括 Bigo Live、Likee、imo、Hello 语音等,在全球已拥有近 4 亿月活用户,产品及服务已覆盖超过 150 个国家和地区。 TiDB 4.0 在 Bigo 的使用情况我们在今年年初开始使用 TiDB 4.0 测试版本,在测试的时候搭建了一个测试环境的集群,它始终会跟随着 TiDB 的最新版本迭代,所以我们前不久也迅速升级到了 4.0 GA 版。 对于 TiDB 在生产环境的上线,我们非常勇敢,也是非常大胆的部署了 2 套生产环境集群,这两个集群规模不算非常大,更多的是偏分析类的业务。一套是网络监控的分析,特点是数据量增长大,且 SQL 偏分析类,同时对响应时间由一定要求;还有一套是做大数据的下游存储,大数据分析后的数据提供线上的实时服务来使用,单表数据量通常也不小,大多是运营类的后台汇总业务。我们使用了 TiUP 进行集群部署,这也是官方相对比较推荐的部署方式,简单来说 TiUP 这个部署的方式比之前的 TiDB Ansible 好很多,解决了我们一大部分的问题。 另外,我们使用 TiDB 4.0 更多的组件和功能,包括 Pump、TiFlash 等等。由于 Bigo 的业务覆盖全球,所以我们希望在全球各个大洲(或者说各个大区)都能够部署上自己的服务,而服务的跨大洲延迟,对于一部分业务来说是不可接受的,因此我们会通过 Pump 之类同步的方式,来进行各洲之间的数据同步。关于 TiFlash,稍后我会花更多篇幅分享实践经验,熟悉我的 TiDB 社区伙伴们应该都知道,我总在各个场合夸“TiFlash 是真的香”。 我们为什么会使用 TiDB 4.0?一方面业务上有新的需求,通常作为 DBA 我们会尽量去满足业务的需求。 比如 TiDB 4.0 支持通过字符集排序规则来控制大小写是否敏感,在此之前我们是没有办法控制的,所以说经常有业务同学向我们吐槽说你们 TiDB 的服务部署了之后,字符级的排序就跟“假”的一样,当然确实之前好像也是假的,因为没有办法控制。 ...

June 24, 2020 · 2 min · jiezi

MIT6824Lab2A

Lab2ALab2A的地址:https://pdos.csail.mit.edu/6.824/labs/lab-raft.html Lab2A需要我们做Leader Election部分。根据论文, 我们在Lab2A需要完成的内容是: 初始选举Candidate发布RequestVote rpcLeader发布AppendEntry rpc, 包括心跳server的状态转换(Follower, Candidate, Leader)基本上就是照着paper figure 2去做。 Lab2A的Hint(真多): 为raft.go添加需要的状态.定义log entry的结构.填写RequestVoteArgs和RequestVOteReply结构.修改Make()去创建后台goroutine, 必要时这个goroutine会发送RequestVoteRPC.实现RequestVote()RPC handler.定义AppendEntriesRPC结构和handler.处理election timeout和只vote一次.tester要求heartbeat发送速率不能超过10个/s,5s内选出新leader, 即使因为split vote导致多轮选举.所以要选择恰当的时间参数.因为tester的限制, 所以不能按照论文中的election timeout设置为150ms~300ms, 必须更大.但不能太大, 否则没办法达到5s内选出leader.切记, 在Go中, 大写字母开头的函数和结构(方法和成员)才能被外部访问!测试lab 2A有两个测试:TestInitialElection()和TestReElection(). 前者较为简单, 只需要完成初始选举并且各节点就term达成一致就可以通过. 构思经过不断重构, 最后的程序结构如下: MainBody(), 负责监听来自rf.Controller的信号并转换状态.Timer(), 计时器.Voter(), 负责发送RequestVote RPC和计算选举结果.APHandler(), 负责发送心跳,并且统计结果.四个通过channel来通信(自定义整型信号).先前的设计是MainBody() 监听来自若干个channel的信号,后来了解到channel一发多收, 信号只能被一个gorouine接受, 可能会有出乎意料的后果. 随后修改为MainBody主循环只监听rf.Controller, 然后向其他channel中发送信号(MPSC). BugsTestReElection(), 时不时fail, 预算笔者给TestReElection()添加一些输出, 将其分为多个阶段, 方便debug: 选出leader1# 1 ------------------------------ leader1 下线 选出新leader# 2 ------------------------------ leader1 上线, 变为follower# 3 ------------------------------ leader2 和 (leader2 + 1 ) % 3 下线 等待2s, 没有新leader产生# 4 ------------------------------ (leader2 + 1 ) % 3 产生 选出新leader# 5 ------------------------------ leader2 上线# 6 ------------------------------但是测试会是不是失败, 都是在第3-4阶段时失败。原因是:此时系统中应该不存在leader, 但是某节点仍声称是leader. ...

November 3, 2019 · 1 min · jiezi

MPI入门

MPI入门分布式系统中经常用到MPI,这里简单地学习一下基础用法,并做个笔记。 教程 通讯器(communicator)。通讯器定义了一组能够互相发消息的进程。在这组进程中,每个进程会被分配一个序号,称作(rank). 点对点通信自己先把自己想要发送的数据写在一个buffer里,该buffer可以是MPI_Datatype类型的指针所指向的一片内存区域,调用Send的时候就将该类型指针转为void *. MPI_Send( void* data, int count, MPI_Datatype datatype, int destination, int tag, MPI_Comm communicator)MPI_Recv( void* data, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm communicator, MPI_Status* status)注意 MPI_Recv的source可以是任意tag,表示接收来自任何source的msg,但是MPI_Send的destination应该不可以用来发送消息给任何des吧目前MPI_Recv的status还没有用到,想忽略这个就直接置为MPI_STATUS_IGNORE。如果调用MPI_Recv时提供了MPI_Status参数,假设是一个名为stat的MPI_Status,就会往里面填入一些信息,主要是以下3个: The rank of the sender: 通过stat.MPI_SOURCE去access;tag of the message: stat.MPI_TAG;length of the message: 不能直接通过访问stat的某个元素去access,需要调用下面的方法去access:MPI_Get_count( MPI_Status* status, MPI_Datatype datatype, int* count)The count variable is the total number of datatype elements that were received. 到这里就有一个疑问?为什么需要这3个信息? ...

October 16, 2019 · 4 min · jiezi

从-SOA-到微服务企业分布式应用架构在云原生时代如何重塑

阿里妹导读:从十余年前的各种分布式系统研发到现在的容器云,从支撑原有业务到孵化各个新业务,企业的发展离不开统一的、与时俱进的技术架构。本篇文章从企业分布式应用架构层面介绍了云原生计算架构带来的变化,希望能够帮助更多企业的 IT 转型,利用云计算技术推动其成为市场竞争中的敏捷力量。进入 21 世纪以来,我们见证了企业分布式应用架构从 SOA (Service-oriented Architecture),到微服务架构,再到云原生应用架构的演化。 为了说明企业架构演化背后的思考,我们先谈一些玄学。 第一,企业 IT 系统的复杂性(熵)符合热力学第二定律。随着时间的推演,业务的变化,企业 IT 系统的复杂度会越来越高;第二,在计算机交互设计中有一个著名的复杂性守恒定律[1]。应用交互的复杂性不会消失,只会换一种方式存在。这个原理也同样适用于软件架构。引入新的软件架构,不会降低IT系统的整体复杂性。听到这里,是否让生命不息、折腾不止的我们感到一丝凉凉? 现代软件架构的核心任务之一就是定义基础设施与应用的边界,合理切分复杂性,减少应用开发者需要面对的复杂性。换句话说,就是让开发者专注在核心价值创新上,而把一些问题交给更合适的人和系统来解决。 我们就从下面这张图开始,探究企业分布式应用架构演进背后的逻辑。 本图来自 Bilgin Ibryam 的 twitter[2] 蜕变之痛:SOA2004 年,IBM 建立 SOA 全球设计中心,我作为研发 TL 和架构师参与了一系列全球客户的 pilot 项目,帮助 Pepboys, Office Depot 等国际企业利用 SOA 优化企业内部和企业间的业务流程,提升业务敏捷性。 当时的大背景是:随着经济全球化逐渐深入,企业面对的竞争加剧,商业变革也开始提速。在大型企业内部的 IT 系统已经经过了数十年的演化,整个的技术体系变得异常复杂,并存着诸如主机系统上的 CISC/COBOL 交易应用,小型机 AS400 中的 RPG 业务系统,和 X86/Power 等分布式系统的 C/JEE/.Net 应用。 大量应用系统由三方供应商提供,一些系统甚至已经无人维护。而且随着业务迭代,一些新的业务系统被持续构建出来,由于缺乏合理的方法论指导,系统之间缺乏有机的链接,形成了若干的孤岛,持续加剧了 IT 架构的复杂性,无法支撑业务的发展诉求。这就仿佛各派高手为了帮助受伤的令狐冲,把异种真气输入体中,虽然短时间可以缓解伤势。可是多道真气无法融合,互相激荡,长时间下来会伤上加伤。 因此,企业 IT 所面临的首要挑战就是整合企业中大量竖桶型(silo-ed)的 IT 系统,支撑日益复杂的业务流程,进行高效的业务决策和支撑业务快速变化。 在这种背景下,IBM 等公司提出了 SOA(面向服务的架构)理念,将应用系统抽象成一个个粗粒度的服务,构建松耦合服务架构,可以通过业务流程对服务进行灵活组合,提升企业 IT 资产复用,提高了系统的适应性、灵活性和扩展性,解决“信息孤岛”问题。 SOA 提出了一系列构建分布式系统的原则,这些思考直到今天也依然适用: 服务具备明确定义的标准化的接口。通过服务定义描述,将服务消费者(Service Consumer)和服务提供者 (Service Provider) 的实现进行解耦,并且服务应该采用 contract-first 而非 code-first 方式进行开发。服务间通信采用面向文档的消息而非特定语言 RPC 协议,一方面可以解决服务与实现语言的解耦,另一方面可以灵活选择同步或者异步的通信实现,提升系统可用性和可伸缩性;服务应该是松耦合的,服务之间不应存在时间、空间、技术、团队上的依赖;服务应该是无状态的,使得服务调用与会话上下文状态实现解耦;服务应该是自治和自包含的,服务的实现是可以独立进行部署、版本控制、自我管理和恢复;服务是可发现、可组合的。比如可以通过 Service Registry 进行服务发现,实现了服务消费者和服务提供者的动态绑定。业务流程中可以对来自不同系统的的业务服务进行编排组装。在初始构建 SOA 系统的时候,大多采用点对点的通信连接,服务调用和集成逻辑被内嵌在应用实现中。这种方式在服务数量比较少的时候,确实是一种简单和高效的开发方式。但其最大的问题是,随着服务规模的增长,服务之间通信愈发复杂,连接路径和复杂性会剧增,给服务治理带来巨大的挑战。 ...

October 8, 2019 · 2 min · jiezi

TiDB-在小红书从-0-到-200-节点的探索和应用

作者介绍:张俊骏,小红书数据库与中间件团队负责人小红书使用 TiDB 历史可以追溯到 2017 年甚至更早,那时在物流、仓库等对新技术比较感兴趣的场景下应用,在 2018 年 5 月之后,我们就开始逐步铺开,延展到其他适合 TiDB 的场景中去。截止目前,小红书使用的 TiDB 节点数在 200+ 个,未来也有更大扩展空间。 本文根据近两年 TiDB 在小红书的落地过程,和大家一起探讨一下,小红书在新数据库选型的考虑因素、以及 TiDB 从场景分类的角度是如何考量及逐步推广使用的。具体包括以下内容: 目前小红书数据服务整体架构,以及从数据流角度如何对不同数据库服务进行定义和划分。从基本功能、数据同步、部署管理、运维、二次开发及优化、安全等多个维度解读小红书在数据库选型的考虑因素及思考。TiDB 的适用场景,以及在小红书如何进行场景选择、如何逐步进行上线规划。一、小红书数据服务整体架构 <center>图 1</center> 如图 1 所示,小红书数据服务整体架构最上层是在线应用层(online app),应用层往下肯定会依赖一些离线(offline)或者在线(online)的 database(其实它更多的意义应该算存储,比如 Redis 也被我们理解为 database,所以称之为“数据服务”可能会更好),这些在线数据服务(online database)会有两条线: 通过实时数据流(dataflow)将数据导入到离线数据库(offline database)支撑离线分析以及实时展示的场景,也就是图 1 最下层的展示类服务(presentation)和数仓(data warehouse)。这些数据还可能会回灌到线上其他 database 上,有些是离线,有些是实时。图 1 蓝框中的部分基本上都由我们团队负责。我们首先需要保证在线数据库(online database) 的稳定性、安全性以及性能优化等,其次我们的多种数据库数据同步服务(database to database replication) 有点像阿里提出的 data replication center 这个概念,这部分也基本上由我们团队全权负责。 二、小红书数据服务组件选型 RoadMap对于一个新的数据库或数据服务组件选型(如 TiDB),我们该从哪些方面去入手搞清楚它的特性?下面分享一下我们的经验。 1. 产品的基本功能第一步,我们需要考察该数据服务/组件的基本功能,首先,我们要了解它的读写场景,包括点查、批量获取(batch get)、范围扫描(range scan)、过滤查询(filter query)、聚合查询(aggregation)等等。然后我们看看它是否符合响应时间(latency) 以及带宽(bandwidth,即能承接多少并发)的要求。最后我们会关注可扩展性,比如 TiDB 可能最大的特点就是扩展性非常好。这几点是大家都会想到的最基本的要求,这里我就一笔略过。 2. 数据同步与处理相关解决方案第二部分是数据同步与处理相关解决方案。这里我们有以下 4 点考虑: ...

July 12, 2019 · 3 min · jiezi

浅析分布式事务中的2PC和3PC

分布式事务大型的分布式系统架构都会涉及到事务的分布式处理的问题,基本来说,分布式事务和普通事务都有一个共同原则: A(Atomic) 原子性,事务要么一起完成,要么一起回滚C(Consistent) 一致性,提交数据前后数据库的完整性不变I(Isolation) 隔离性,不同的事务相互独立,不互相影响D(Duration)持久性,数据状态是永久的另外一个设计原则,分布式系统要符合以下原则: C(Consistent)一致性,分布式系统中存在的多个副本,在数据和内容上要一模一样A(Availability)高可用性,在有限时间内保证系统响应P(Partition tolerance)分区容错性,一个副本或一个分区出现宕机或其他错误时,不影响系统整体运行由于在设计分布式系统时,难以保证CAP全部符合,最多保证其中两个,例如在一个分布式系统中:要保证系统数据库的强一致性,需要跨表跨库占用数据库资源,在复杂度比较高的情况下,耗时难以保证,就会出现可用性无法保证的情况。 故而又出现了BASE理论 Basic Available基本可用,分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用Soft state柔性状态,允许系统存在中间状态,这个状态不影响系统可用性Eventually Consistent 最终一致性 指经过一段时间之后,所有数据状态保持一致的结果实际上BASE理论是基于AP的一个扩展,一个分布式事务不需要强一致性,只需要达到一个最终一致性就可以了。 分布式事务就是处理分布式中各个节点的数据操作,使之能够在节点相互隔离的情况下完成多个节点数据的一致性操作。 分步式事务 - 2PC2PC即二阶段提交,在这里需要了解两个概念 参与者:参与者即各个实际参与事务的节点,这些节点相互隔离,彼此不知道对方是否提交事务。协调者:收集和管理参与者的事务信息,负责统一管理节点信息,也就是分布式事务中的第三方。准备阶段,也称投票阶段准备阶段中,协调者向参与者发送precommit的消息,参与者在本地执行事务逻辑,如果没有超时以及运行无误,那么会记录redo和undo日志,将ack信息发送给协调者,当协调者收到所有节点参与者发送的ack信息时,准备进入下一阶段,否则会发送回滚信息给各个节点,各个节点根据之前记录好的undo信息回滚,本次事务提交失败。 提交阶段提交阶段中,协调者已经收到所有节点的应答信息,接下来发送commit消息给各个节点,通知各个节点参与者可以提交事务,各个参与者提交完毕后一一发送完成信息给协调者,并释放本地占用的资源,协调者收到所有完成消息后,完成事务。 二阶段提交的图示如下: 二阶段提交的问题二阶段提交虽然能够解决大多数的分布式事务的问题,且发生数据错误的概率极小,但仍然有以下几个问题: 单点故障:如果协调者宕机,那么参与者会一直阻塞下去,如果在参与者收到提交消息之前协调者宕机,那么参与者一直保持未成功提交的状态,会一直占用资源,导致同时间其他事务可能要一直等待下去。同步阻塞问题:因为参与者是在本地处理的事务,是阻塞型的,在最终commit之前,一直占用锁资源,如果遇到时间较长而协调者未发回commit的情况,那么会导致锁等待。数据一致性问题:假如协调者在第二阶段中,发送commit给某些参与者失败,那么就导致某些参与者提交了事务,而某些没有提交,这就导致了数据不一致。二阶段根本无法解决的问题:假如协调者宕机的同时,最后一个参与者也宕机了,当协调者通过重新选举再参与到事务管理中时,它是没有办法知道事务执行到哪一步的。针对于以上提出的一些问题,衍生出了3PC。 分布式事务 - 3PC3PC即三阶段提交协议。 3PC相对于2PC,有了以下变化: 引入超时机制,在参与者和协调者中都加入了各自的超时策略。加入了一个新的阶段,canCommit,所以阶段分成了三个:canCommit、PreCommit、DoCommit。CanCommit这是一个准备阶段,在这一阶段中,协调者向参与者发送CanCommit消息,参与者接收后,根据自身资源情况判断是否可以执行事务操作,如果可以并且未超时,则发送yes给协调者,反之,协调者会中断事务。 PreCommit这是预备阶段,在这一阶段中,协调者向参与者发送PreCommit消息,参与者接收后,会执行事务操作,记录undo和redo日志,事务执行完毕后会将Ack消息发送给协调者,协调者在未超时的情况下收集所有参与者的信息,否则中断事务,通知所有参与者Abort消息。 DoCommit当所有参与者Ack消息完毕之后,协调者会确认发送DoCommit消息给每一个参与者,执行提交事务。原则上所有参与者执行提交完毕之后,需要发送Committed给协调者,协调者完成事务。否则协调者中断事务,参与者接收abort消息会根据之前记录的undo回滚。但也要注意,此阶段中如果参与者无法及时收到协调者发来的Docommit消息时,也会自行提交事务,因为从概率上来讲,PreCommit这个阶段能够不被abort说明全部节点都可以正常执行事务提交,所以一般来讲单个节点提交不影响数据一致性,除非极端情况。 2PC 和 3PC的区别相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。 消息中间件上面提到的关于协调者在实践过程中由谁来担任是个问题,一般来讲这个协调者是异步的,可以在参与者和协调者之间双向通信的,有良好的消息传递机制,能够保证消息稳定投递。故而一般协调者的担任者是高性能的消息中间件例如RocketMq、Kafka、RabbitMq等。 如图就是一个2PC的实践:

July 12, 2019 · 1 min · jiezi

通俗易懂地介绍分布式锁实现

文章来源:www.liangsonghua.me作者介绍:京东资深工程师-梁松华,长期关注稳定性保障、敏捷开发、JAVA高级、微服务架构 一般情况下我们会通过下面的方法进行资源的一致性保护 // THIS CODE IS BROKENfunction writeData(filename, data) { var lock = lockService.acquireLock(filename); if (!lock) { throw 'Failed to acquire lock'; } try { var file = storage.readFile(filename); var updated = updateContents(file, data); storage.writeFile(filename, updated); } finally { lock.release(); }}但是很遗憾的是,上面这段代码是不安全的,比如客户端client-1获取锁后由于执行垃圾回收GC导致一段时间的停顿(stop-the-word GC pause)或者其他长时间阻塞操作,此时锁过期了,其他客户如client-2会获得锁,当client-1恢复后就会出现client-1client-2同时处理获得锁的状态 我们可能会想到通过令牌或者叫版本号的方式,然而在使用Redis作为锁服务时并不能解决上述的问题。不管我们怎么修改Redlock生成token的算法,使用unique random随机数是不安全的,使用引用计数也是不安全的,一个redis node服务可能会出宕机,多个redis node服务可能会出现同步异常(go out of sync)。Redlock锁会失效的根本原因是Redis使用getimeofday作为key缓存失效时间而不是监视器(monitonic lock),服务器的时钟出现异常回退无法百分百避免,ntp分布式时间服务也是个难点 分布式锁实现需要考虑锁的排它性和不能释放它人的锁,作者不推荐使用Redlock算法,推荐使用zookeeper或者数据库事务(个人不推荐:for update性能太差了) 补充:使用zookeeper实现分布式锁 可以通过客户端尝试创建节点路径,成功就获得锁,但是性能较差。更好的方式是利用zookeeper有序临时节点,最小序列获得锁,其他节点lock时需要阻塞等待前一个节点(比自身序列小的最大那个)释放锁(countDownLatch.wait()),当触发watch事件时将计数器减一(countDownLatch.countDown()),然后此时最小序列节点将会获得锁。可以利用Curator简化操作,示例如下 public static void main(String[] args) throws Exception { //重试策略 RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); //创建工厂连接 final CuratorFramework curatorFramework = CuratorFrameworkFactory.builder().connectString(connetString) .sessionTimeoutMs(sessionTimeOut).retryPolicy(retryPolicy).build(); curatorFramework.start(); //创建分布式可重入排他锁,监听客户端为curatorFramework,锁的根节点为/locks final InterProcessMutex mutex = new InterProcessMutex(curatorFramework, "/lock"); final CountDownLatch countDownLatch = new CountDownLatch(1); for (int i = 0; i < 100; i++) { new Thread(new Runnable() { @Override public void run() { try { countDownLatch.await(); //加锁 mutex.acquire(); process(); } catch (Exception e) { e.printStackTrace(); }finally { try { //释放锁 mutex.release(); System.out.println(Thread.currentThread().getName() + ": release lock"); } catch (Exception e) { e.printStackTrace(); } } } },"Thread" + i).start(); } Thread.sleep(100); countDownLatch.countDown(); } }补充:redis实现分布式锁 ...

July 11, 2019 · 2 min · jiezi

Redis-Cluster配置传播及故障恢复笔记

本笔记是对Redis Cluster Spec - Configuration handling, propagation, and failovers的归纳总结。 Epoch可以把Epoch当作是一个版本号,是一个64位无符号整形每个Node自己有一份Cluster.currentEpoch、MySelf.configEpoch、其他Node.configEpoch,详见文档。每个Master有自己的ConfigEpoch且在整个Cluster中唯一Slave的ConfigEpoch随其MasterCluster.currentEpoch,该值等于所有Node中最大的ConfigEpoch的值Master的ConfigEpoch初始值是0,也就是说Cluster.CurrentEpoch的初始值也是0Node之间Gossip传输消息时,Receiver发现Sender的ConfigEpoch比自己大,那么就更新自己的Cluster.CurrentEpoch为该值,随时间收敛,所有Node的Cluster.CurrentEpoch都变成一样。Slave PromotionSlave的动作下面是总结的在发生Slave Promotion时,Slave做的事情。 Master的动作下面是总结的在发生Slave Promotion时,Master做的事情。 传播Slots的配置Slave赢得选举之后会在己侧更新Slots上的归属信息,然后在定时的PING/PONG中将这个信息传播出去。 PING/PONG总是会携带上Slots所属Master的信息(包括ConfigEpoch) PING的Reciever如果发现Sender的某个Slot上的Master.ConfigEpoch比自己这里记录的小,那么就会返回UPDATE告诉Sender更新Slots归属信息。 下面是两个规则: 如果一个Slot不属于任何Master,然后有一个Master宣称拥有它,那么就修改己侧的Slots信息把这个Slot关联到这个Master上。如果一个Slot已经归属一个Master,然后又有一个Master宣称拥有它,那么就看谁的ConfigEpoch大,大的那个赢Node复活后遇到的问题Node A有两个Slot,然后它死了,它被顶替了,等它复活时发现两个Slot一个被Node B接管,另一个被Node C接管了,那么它: 因为自己的ConfigEpoch已经很旧了,所以它复活后不负责任何Slot然后它会成为最后一个Slot的Master的SlaveSlave迁移算法Slave迁移时一个自动过程。 举个例子,现在有Master A、B,它们对应的Slave有A1、B1、B2。现在A死了,A1顶替上去,不过这个时候A1就是一个光棍Master(它没有Slave),B有富余的Slave(B1和B2),把其中一个匀给A1当Slave。 这个过程不需要共识,因为只是修改Slave的归属,也不会修改ConfigEpoch。 Slave迁移有两个规则: 当有多个Slave富余时,选择NodeID字典顺最小的那个来迁移只有当Master的Slave数量>=cluster-migration-barrier时,才会挑选它的Slave做Migration两个跳过共识修改ConfigEpoch的操作下面两个操作比较危险,最好确定一个成功后再执行另一个: CLUSTER_FAILOVER TAKEOVER(手动Failover)直接将一个Slave提升为Master,不需要大多数Master同意。Slot Migration同样不需要大多数Master同意。所以就有可能出现同一个Slot有两个相同ConfigEpoch的Master宣称由自己负责,这种冲突的解决算法是: 如果Master A发现Master B也宣称了对Slot X的主权,并且两者的ConfigEpoch一样如果Master A的NodeID的字典顺比Master B的小那么Master A就把己侧的CurrentEpoch+1,同时ConfigEpoch改成和CurrentEpoch一样Node重制略,见文档。 移除Node略,见文档。 一些自问自答Q:ConfigEpoch何时变化? A:Slave Promotion时、手动Failover时、Slot Migration时 Q:ConfigEpoch怎么变化? A:Node->ConfigEpoch = Cluster->CurrentEpoch + 1,结果也就是Cluster->CurrentEpoch加1了。源码见这里。 Q:两个Master的ConfigEpoch一样怎么办? A:这个会出现在两个Slave同时Promotion时,解决办法是NodeID字典序比较小的那个会再一次Bump ConfigEpoch,源码见这里。 Q:ConfigEpoch有什么用? A:当有两个Master宣称自己拥有同一个/批Slot时,ConfigEpoch大的那个赢,因为大的那个代表最新信息,其他Node只会采用赢的那方所宣称的信息。 Q:CurrentEpoch有什么用? A:1)用来判定Node所获得的Cluster信息的新旧。2)当Node要变更ConfigEpoch时派用处。 参考资料官方文档: Redis Cluster Spec - Configuration handling, propagation, and failovers下面是饿了么工程师写的文章,比较透彻: ...

July 11, 2019 · 1 min · jiezi

分布式系统之一致性和数据复制

1、一致性常见问题 这些问题离我们并不遥远,数据分散在多处会导致数据不一致,必须尽可能地解决此问题,才能保证良好的用户体验,最终的期望是任何人、任何时间、任何地点、任何接入方式、任何服务,数据都是一致的 2、一致性模式1)、顺序一致性(Sequencial Consistency)每个线程内部的指令都是按照程序规定的顺序执行的(单个线程的视角)。线程执行的交错顺序可以是做任意的,但是所有线程所看见的整体程序总体执行顺序都是一样的(整体程序的视角) 2)、弱一致性-因果一致性(Casual Consistency)如果节点A在更新完某个数据后通知了节点B,那么节点B之后对该数据的访问和修改都是基于A更新后的值。于此同时,和节点A无因果关系的节点C的数据访问则没有这样的限制 3)、弱一致性-入口一致性(Entry Consistency)入口一致性要求每个普通的共享数据都要与某种同步变量如锁(lock)或屏障(barrier)相关联 进程2在没有获取”y”数据的访问锁时,读取的值将为NIL(In the following figures, since Process2 does not hold the access right (= synchronous variable) to the data item “y”, the reading result becomes NIL) 4、弱一致性-最终一致性(Eventual consistency) 5)、弱一致性-以客户为中心的一致性(Client-centric consistency model)包括以下四种体现 (1)、单调读一致性(Monotonic reading)如果一个进程从系统中读取出一个数据项X的某个值后,该进程对于X后续访问都不应该返回更旧的值(If a process reads data item x, any subsequent reads on x by that process will either reply with the same value or reply with a newer value) ...

July 10, 2019 · 2 min · jiezi

分布式系统全局发号器的几点思考

原文链接:何晓东 博客 文章起源于 康神交流群的 panda大佬和boss li关于发号器的一些交流,特此感谢让我们学到了新知识。为什么需要发号器在分布式系统中,经常需要对大量的数据、消息、http 请求等进行唯一标识,例如:对于分布式系统,服务间相互调用需要唯一标识,调用链路分析,日志追踪的时候需要使用这个唯一标识。此时需要一个全局唯一的 ID。 需要什么样子的发号器持久化 要满足长期全局唯一,持久化是必须的,肯定不能让已经使用的再次产生一遍,同时需要强一致性。可用选择存储在 Redis 或者 Etcd 中。高可用 这个时候需要提供发号器服务的机器主从同步,能够在主服务器宕机的时候,自动选择从服务器,切换过程中,发号器生成的 ID 可能不连续,服务正常就可以。其他特性 主要是看具体业务了,需要认证和权限控制都是可选的,可用在请求层限制来源 IP,只允许固定的 IP 访问。发号器的几种常用方案UUIDUUID 是 Universally Unique Identifier 的缩写,它是在一定的范围内(从特定的名字空间到全球)唯一的机器生成的标识符,UUID 是16字节128位长的数字,通常以36字节的字符串表示,比如:3F2504E0-4F89-11D3-9A0C-0305E82C3301。 UUID经由一定的算法机器生成,为了保证 UUID 的唯一性,规范定义了包括网卡 MAC 地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素,以及从这些元素生成 UUID 的算法。UUID 的复杂特性在保证了其唯一性的同时,意味着只能由计算机生成。 优缺点: 本地生成,性能高,延迟低,位数长,不适用当作索引字段,同时是无序的,难以根据特征分析趋势。 类snowflake算法snowflake是twitter开源的分布式ID生成算法,其核心思想为,一个long型的ID:41bit作为毫秒数 - 10bit作为机器编号 - 12bit作为毫秒内序列号算法单机每秒内理论上最多可以生成1000*(2^12),也就是400W的ID, 优缺点: 整个 ID 都是自增的,这个非常适合查看趋势,同时生成不依赖于第三方系统,可靠性很高,可调整性也很高。缺点就是严重依赖机器时钟。 基于MySQL的发号器字段设置 auto_increment_increment 和 auto_increment_offset 来保证 ID 自增,每次业务使用下列 SQL 读写 MySQL 得到 ID。 begin;REPLACE INTO Tickets64 (stub) VALUES ('a');SELECT LAST_INSERT_ID();commit;为了保证高可用,需要有多台 MySQL 机器,也需要为每台机器设置不同的自增起始值和步长,例如: ...

June 25, 2019 · 1 min · jiezi

分布式消息队列详解10min搞懂同步和异步架构等问题

分布式消息队列是是大型分布式系统不可缺少的中间件,主要解决应用耦合、异步消息、流量削锋等问题。实现高性能、高可用、可伸缩和最终一致性架构。 对于一个架构师来说,在大型系统设计中,会经常需要面对同步和异步等架构问题,搞明白这些问题,能更好地实现程序并行执行,减少等待或无效操作,以及充分利用计算机的性能! 本文将详细讲解:1.同步架构和异步架构的区别 2.异步架构的主要组成部分:消息生产者、消息消费者、分布式消息队列 3.异步架构的两种主要模型:点对点模型和发布订阅模型。 4.消息队列的好处 5.消息队列相关产品 建议用10min通读,搞懂分布式消息队列的核心内容。 一、同步架构和异步架构的区别 1.同步调用 是指从请求的发起一直到最终的处理完成期间,请求的调用方一直在同步阻塞等待调用的处理完成。 如图,在这个例子中客户端代码ClientCode,需要执行发送邮件sendEmail这样一个操作,它会调用EmailService进行发送,而EmailService会调用SmtpEmailAdapter这样一个类来进行处理,而这个类会调用远程的一个服务,通过SMTP和TCP协议把请求发送给它。 而远程服务器收到消息以后会对消息进行一系列的操作,然后将邮件发送出去,再进行返回。Adapter收到返回后,再返回给EmailService,EmailService收到返回后再把返回结果返回给Clientcode。 ClientCode在sendEmail发出请求后,就一直都阻塞在这里,等待最终调用结果的返回,是成功还是失败。因为这个过程是阻塞等待的,所以这个过程也就是同步调用。 2.异步调用 是指在请求发起的处理过程中,客户端的代码已经返回了,它可以继续进行自己的后续操作,而不需要等待调用处理完成,这就叫做异步调用。 异步调用过程,同样看刚刚发送邮件的例子,用户Clientcode调用EmailService以后,EmailService会把这个调用请求发送给消息队列,然后就立即返回了。Clientcode收到返回以后继续向下处理,不会继续阻塞等待。实际上消息发送到Queue后,还没有被处理,我们看到后面的消息消费,其实要比EmailService返回可能还要晚一点,EmailService返回以后消息才会被消费处理。 有一个QueueConsumer消息队列的消费者,从消息队列中取出这个消息,再把这个消息发送给SmtpAdapter,也就是调用SmtpAdapter,处理逻辑跟同步调用一样,SmtpAdapter通过SMTP的通讯协议,把消息发送给远程的一个服务器,进行邮件发送,通过RemoteServer进行处理,处理完了收到返回,再把返回结果通知消息队列Queue。 在这个过程中,客户端的调用,也就是应用程序的调用,和业务逻辑真正发送邮件的操作是不同步的。 二、异步架构的主要组成部分 使用异步调用架构的主要手段,就是通过消息队列构建,如下是它的架构图。 消息的生产者将消息发送到消息队列以后,由消息的消费者从消息队列中获取消息,然后进行业务逻辑的处理,消息的生产者和消费者是异步处理的,彼此不会等待阻塞,所以叫做异步架构。 使用消息队列构建一个异步调用架构,你需要了解如下3种角色。 1.消息的生产者 是客户端应用程序代码的一部分,用来初始化异步调用处理流程。在基于消息队列的处理中,生产者的职责非常少,它要做的就是创建一个合法的消息,并把这个消息发送到消息队列中,由应用开发者决定生产者的代码在哪里执行,什么时候发送消息。 2.消息队列 消息队列是消息发送的目的地和发给消费者的一个缓冲。消息队列实现的方法有好多种,可以用共享文件夹,也可以用关系数据库或者NoSQL系统,当然最主要的还是使用专门的分布式消息队列服务器来实现。 3.消息的消费者 消息的消费者从消息队列中接受并处理消息,消息的消费者也是由应用开发者实现的,但是它是一个异步处理的组件。消息的消费者不需要知道生产者存在,它只依赖消息队列中的消息。消息的消费者通常部署在独立的服务器上,和消息的生产者完全隔离,并且可以通过添加硬件的方式进行伸缩。 三、异步架构的两种主要模型 使用消息队列构建异步的调用架构,你还需要知道两种模型:点对点模型和发布订阅模型。 1.点对点模型 消费者和生产者只需要知道消息队列的名字,生产者发送消息到消息队列中,而消息队列的另一端是多个消费者竞争消费消息,每个到达消息队列的消息只会被路由到一个消费者中去,所以消费者看到的是全部消息的一个子集。我们看这张图,消息的生产者有多个,消息的消费者也有多个,多个生产者将消息发送到消息队列中,而有多个消费者去消息队列中对消息进行竞争性的消费。每个消息只会被一个消费者消费,每个消费者只会消费消息队列中的一部分消息。 2.发布订阅模型 在发布订阅模型中,消息可能被发送到不止一个消费者,生产者发送消息到一个主题,而不是队列中。消息被发布到主题后,就会被克隆给每一个订阅它的消费者,每个消费者接收一份消息复制到自己的私有队列。消费者可以独立于其他消费者使用自己订阅的消息,消费者之间不会竞争消息。常用的分布式消息队列都支持发布订阅模型,也就是说消息的发布订阅模型是分布式消息队列的一个功能特性。 3.两个模型的应用 点对点模型:主要用于一些耗时较长的、逻辑相对独立的业务。 比如说我前面的讲到的发送邮件这样一个操作。因为发送邮件比较耗时,而且应用程序其实也并不太关心邮件发送是否成功,发送邮件的逻辑也相对比较独立,所以它只需要把邮件消息丢到消息队列中就可以返回了,而消费者也不需要关心是哪个生产者去发送的邮件,它只需要把邮件消息内容取出来以后进行消费,通过远程服务器将邮件发送出去就可以了。而且每个邮件只需要被发送一次。所以消息只被一个消费者消费就可以了。 发布订阅模型:如新用户注册这样一个消息,需要使用按主题发布的方式。 比如新用户注册,一个新用户注册成功以后,需要给用户发送一封激活邮件,发送一条欢迎短信,还需要将用户注册数据写入数据库,甚至需要将新用户信息发送给关联企业的系统,比如淘宝新用户信息发送给支付宝,这样允许用户可以一次注册就能登录使用多个关联产品。一个新用户注册,会把注册消息发送给一个主题,多种消费者可以订阅这个主题。比如发送邮件的消费者、发送短信的消费者、将注册信息写入数据库的消费者,跨系统同步消息的消费者等。 四、消息队列的好处 1.实现异步处理,提升处理性能 对一些比较耗时的操作,可以把处理过程通过消息队列进行异步处理。这样做可以推迟耗时操作的处理,使耗时操作异步化,而不必阻塞客户端的程序,客户端的程序在得到处理结果之前就可以继续执行,从而提高客户端程序的处理性能。 2.可以让系统获得更好的伸缩性 耗时的任务可以通过分布式消息队列,向多台消费者服务器并行发送消息,然后在很多台消费者服务器上并行处理消息,也就是说可以在多台物理服务器上运行消费者。那么当负载上升的时候,可以很容易地添加更多的机器成为消费者。 如图中的例子,用户上传文件后,通过发布消息的方式,通知后端的消费者获取数据、读取文件,进行异步的文件处理操作。那么当前端发布更多文件的时候,或者处理逻辑比较复杂的时候,就可以通过添加后端的消费者服务器,提供更强大的处理能力。 3.可以平衡流量峰值,削峰填谷 使用消息队列,即便是访问流量持续的增长,系统依然可以持续地接收请求。这种情况下,虽然生产者发布消息的速度比消费者消费消息的速度快,但是可以持续的将消息纳入到消息队列中,用消息队列作为消息的缓冲,因此短时间内,发布者不会受到消费处理能力的影响。 从这张图可以看到,因为消息的生产者是直接面向用户请求的,而用户的请求访问压力是不均衡的。如淘宝每天的访问高峰是在上午10点左右,而新浪微博则可能在某个明星半夜发一条微博后突然出现访问高峰。 在访问高峰,用户的并发访问数可能超过了系统的处理能力,所以在高峰期就可能会导致系统负载过大,响应速度变慢,更严重的可能会导致系统崩溃。这种情况下,通过消息队列将用户请求的消息纳入到消息队列中,通过消息队列缓冲消费者处理消息的速度。 如图中所示,消息的生产者它有高峰有低谷,但是到了消费者这里,只会按照自己的最佳处理能力去消费消息。高峰期它会把消息缓冲在消息队列中,而在低谷期它也还是使用自己最大的处理能力去获取消息,将前面缓冲起来、来不及及时处理的消息处理掉。那么,通过这种手段可以实现系统负载消峰填谷,也就是说将访问的高峰消掉,而将访问的低谷填平,使系统处在一个最佳的处理状态之下,不会对系统的负载产生太大的冲击。 4.失败隔离和自我修复 因为发布者不直接依赖消费者,所以分布式消息队列可以将消费者系统产生的错误异常与生产者系统隔离开来,生产者不受消费者失败的影响。 当在消息消费过程中出现处理逻辑失败的时候,这个错误只会影响到消费者自身,而不会传递给消息的生产者,也就是应用程序可以按照原来的处理逻辑继续执行。 所以,这也就意味着在任何时候都可以对后端的服务器执行维护和发布操作。可以重启、添加或删除服务器,而不影响生产者的可用性,这样简化了部署和服务器管理的难度。 5.可以使生产者和消费者的代码实现解耦合 也就是说可以多个生产者发布消息,多个消费者处理消息,共同完成完整的业务处理逻辑,但是它们的不需要直接的交互调用,没有代码的依赖耦合。在传统的同步调用中,调用者代码必须要依赖被调用者的代码,也就是生产者代码必须要依赖消费者的处理逻辑代码,代码需要直接的耦合,而使用消息队列,这两部分的代码不需要进行任何的耦合。 耦合程度越低的代码越容易维护,也越容易进行扩展。 比如新用户注册,如果用传统同步调用的方式,那么发邮件、发短信、写数据库、通知关联系统这些代码会和用户注册代码直接耦合起来,整个代码看起来就是完成用户注册逻辑后,后面必然跟着发邮件、发短信这些代码。如果要新增一个功能,比如将监控用户注册情况,将注册信息发送到业务监控系统,就必须要修改前面的代码,至少增加一行代码,发送注册信息到监控系统,我们知道,任何代码的修改都可能会引起bug。 而使用分布式消息队列实现生产者和消费者解耦合以后,用户注册以后,不需要调用任何后续处理代码,只需要将注册消息发送到分布式消息队列就可以了。如果要增加新功能,只需要写个新功能的消费者程序,在分布式消息队列中,订阅用户注册主题就可以了,不需要修改原来任何一行代码。 这种解耦的特点对于团队的工作分工也很有帮助!从消息生产者的视角看,它只需要构建消息,将消息放入消息队列中,开发就完成了。而从消费者的开发视角看,它只需要从消息队列中获取消息,然后进行逻辑处理。它们彼此之间不进行任何耦合。消息的生产者不关心放入消息队列中下一步会发生什么,而消费者也不需要知道消息从哪里来。这两部分程序的开发者也可以不关心彼此的工作进展,他们开发的代码也不需要集成在一起,只要约定好消息格式,就可以各自开发了。 ...

June 6, 2019 · 1 min · jiezi

区块链发展史

区块链技术并不是一项凭空出世的神奇技术,而是站在前人几百年的研究基础之上,将多学科进行融合发展而成的一项技术。为了让大家能够明白区块链技术,到底是对哪些传统技术的融合和发展,秘猿科技区块链小课堂给大家带来了通俗易懂的第二篇文章,诉说区块链的发展历程。秘猿科技区块链小课堂第 2 期 区块链发展史Cypherpunk 发展和成果众所周知,区块链发源于比特币这个项目,也可以说比特币是区块链的第一个应用。但其实在比特币诞生之前,就已经有很多东西在酝酿之中了,而这其中就包含区块链所依赖的最关键的两个技术,一个是分布式系统,一个是密码学。(所以有人称区块链是分布式数据库,更准确一点说是采用了密码学加密的分布式数据库) 密码学是一个很古老的学问,发展到上世纪 70 年代的时候出现了一些质变,包括民用的对称加密算法,非对称签名算法,还有密钥交换算法。这给互联网的隐私传输加密创造了条件。其中一个非常重要的变化,是非对称对抗成为可能。而这也直接导致了 Cypherpunk 的诞生。 在 1997 年亚当·贝克(Adam Back)创建了“Hashcash”匿名交易系统。就其本质而言,它其实是一种反垃圾邮件机制,通过增加发送电子邮件的时间和计算能力,从而使发送垃圾邮件的成本提高:发件人必须证明他们已经花费了算力在电子邮件标题中创建“邮票”(这是比特币中工作量证明 PoW 的雏形) 1998年,戴伟(Wei Dai)发布了B-Money提案,并推出了两种维护交易数据的方法。在提案中,对记录数据进行监管的用户组表现诚实的话,就会获得激励。为此,他们不仅需要把自己的钱存入到一个特殊账户中,如果他们表现的不诚实,就会损失这笔钱。这种方法被称为“权益证明(Proof of stake)”,用户特定组(或主节点)如果试图处理任何欺诈性交易,那么将会失去自己所有的资金。 2004年,哈尔·芬尼(Hal Finney)借鉴亚当·贝克的 Hashcash 原则,创造了可重复使用的工作量证明(Proof of Work)。2005年,尼克·萨博(Nick Szabo)发布了 Bitgold 提案,该提案的理念正是建立在哈尔·芬妮和其他加密项目的基础之上的。 分布式系统的发展和成果在 80 年代的时候,分布式系统的研究已经开展了一段时间,在这些人中有两类人,一类人是比较实际的,他们研究的是数据库的技术,那时候已经有分布式的数据库了,他们研究的是怎么样把这个分布式的数据库做得更加稳定和可靠;而另一类人则偏重于理论,他们会研究一些在实践中基本碰不到的问题,比如 Leslie Lamport 在 1982 年提出的拜占庭将军问题。 到了 2000 年以后,在这两个领域都已经有了很多研究成果。正是在这样的背景下,出现了区块链这样一个技术。2008 年 10 月,中本聪向 metzdowd.com 的“密码朋克”邮件列表中发布了论文《比特币:P2P电子现金系统》。这篇论文直接引用了戴伟的 B-Money 和亚当·贝克 Hashcash,同时还解决了早期开发者所面临的许多问题,比如双重支付。至此,比特币登上了加密货币的历史舞台,区块链技术也应运而生。

June 4, 2019 · 1 min · jiezi

分布式系统关注点21构建易测试系统的六脉神剑

如果第二次看到我的文章,欢迎「文末」扫码订阅我个人的公众号(跨界架构师)哟~ 每周五早8点 按时送达。当然了,也会时不时加个餐~ 这篇是「分布式系统理论」系列的第20篇。提前预告一下,后面还有一篇文章,这个系列就结束了。 在之前,核心的概念都讲的差不多了。前面Z哥带你已经聊过了「数据一致性」、「高可用」、「易扩展」、「高性能」主题下的一些实践思路。 这篇讲怎么构建一个「易测试」的系统。 作为一位开发人员,可能一听到测试就想关掉这篇文章了。那我只能说too young,too naive。 作为关注我这个号的“跨界者“们,你不能将自己的边界划的太清楚,特别在当下这个变化越来越快、适者生存的时代。要活的像“水”一样,与所处的环境结合的更紧密。 除此之外,测试工作并不是单单测试人员的事,开发人员是不是编写了一个易测试的系统也至关重要。 在Z哥我过去的几年coding经验中,总结了六点认为有助于构建出一个易测试的系统建议,在这里分享给你。 第一点,分层。分层其实除了之前聊到的「易扩展」之外,对于测试工作的进行也是有很大帮助,规模越大的系统越是如此。 脑子里想象一下,一条业务线好比一根管道,每一次的业务操作会经历整根管道的流转最终到达终点。 往往很多时候,其实我们已经定位到了问题可能产生的范围,但是由于项目没有做好分层,导致每一次的测试工作不得不“从头开始”。这是多么痛苦的一件事。 做好分层只要记住一个概念就行,「高内聚低耦合」。具体可以参考之前的文章,文末放链接。 第二点,无状态。前面的文章里说过,满足无状态的功能点意味着可以动态的进行扩容而不用考虑“状态丢失”问题。其实同时它也支持了一种测试场景,就是「容量规划」。 为了支撑业务的不断发展以及不定期举行的大型活动,我们需要清楚的知道,到底部署多少台机器为宜。 当然,你也可以选择拍脑袋的方式进行,尽量多加一些就好了。但这不是一个科学的方法,也容易造成更多的浪费。 进行容量规划的过程就好比通过水龙头装水到一组杯子里。比如,你现在的要求是1分钟装入3L水,那么通过不断的调整杯子的数量和大小,理想情况是刚刚好达到这个要求为宜。 如果此时支持无状态,那么整个过程中水龙头一直开着就好了,你只要专心调整杯子的数量和大小就行。做好无状态具体也可以参考之前的文章,文末放链接。 第三点,避免硬编码,尽量配置化。可能你一看到那些庞杂的配置项就头疼,但是不得不说,配置对于测试工作的开展是有很大帮助的。 反而用“眼不见为净”的方式,硬编码到逻辑代码中是“掩耳盗铃”的办法。 特别是以下这些用途的变量,尽量放到配置中去,否则每次配置的变更都需要重新打包编译代码,是多么麻烦的一件事情。 容量类的配置次数类的配置开关类的配置时间类的配置这些类型的配置之间的共同点是,没有永远正确、永远合理的配置。你要根据你当前的需求,不断的调整他们。 如果可以引入一个集中式的配置中心就更好了,这样可以不用一个个登陆服务器去修改配置。 第四点,依赖注入。如果你平时经常编写单元测试的话,对这个应该感受颇深。因为支持依赖注入的代码,更容易编写单元测试。 但它的价值还不止于此,随着系统规模越来越大,对于直接在生产环境进行故障演练需求越迫切,因为这才足够真实。 但是又要求不能对正常的业务数据产生影响,怎么做?那就只能单独准备演练数据,然后写入到单独的数据库中。 这个时候,依赖注入就起作用了。我们可以将载入数据源的地方设计成支持依赖注入的,如此一来,你就可以灵活的切换到不同的数据源,进行故障演练。 public interface IDataSource{ public string getName(int id);}public class DataSourceMysql implements IDataSource{ public string getName(int id){ // 从正常的数据库里中获取数据。 }}public class DataSourceDrill implements IDataSource{public string getName(int id){ // 从故障演练的数据库里中获取数据。 }}public class UserBLL{ private IDataSource _database; public UserBLL(IDataSource database){ _database = database; } public void MethodA(int id){ // do something... var name = _database.getName(id); // do something... }}//以下是调用的时候new UserBLL(new DataSourceMysql()).MethodA(id); //处理的是正常数据new UserBLL(new DataSourceDrill()).MethodA(id); //处理的是演练数据第五点,打日志。测试工作最终做的好不好,看的是数据,是结果。这就意味着,对一个系统要求是「可观测」的。 ...

May 31, 2019 · 1 min · jiezi

Nacos-Namespace-和-Endpoint-在生产环境下的最佳实践

随着使用 Nacos 的企业越来越多,遇到的最频繁的两个问题就是:如何在我的生产环境正确的来使用 namespace 以及 endpoint。这篇文章主要就是针对这两个问题来聊聊使用 nacos 过程中关于这两个参数配置的最佳实践方式。 namespce关于 namespace ,以下主要从 namespace 的设计背景 和 namespace 的最佳实践 两个方面来讨论。 namespace 的设计背景namespace 的设计是 nacos 基于此做多环境以及多租户数据(配置和服务)隔离的。即: 从一个租户(用户)的角度来看,如果有多套不同的环境,那么这个时候可以根据指定的环境来创建不同的 namespce,以此来实现多环境的隔离。例如,你可能有日常,预发和生产三个不同的环境,那么使用一套 nacos 集群可以分别建以下三个不同的 namespace。如下图所示: 从多个租户(用户)的角度来看,每个租户(用户)可能会有自己的 namespace,每个租户(用户)的配置数据以及注册的服务数据都会归属到自己的 namespace 下,以此来实现多租户间的数据隔离。例如超级管理员分配了三个租户,分别为张三、李四和王五。分配好了之后,各租户用自己的账户名和密码登录后,创建自己的命名空间。如下图所示。 注意: 该功能还在规划中。 namespace 的最佳实践关于 namespace 的最佳实践 ,这部分主要包含有两个 Action: 如何来获取 namespace 的值namespace 参数初始化方式如何来获取 namespace 的值无论您是基于 Spring Cloud 或者 Dubbo 来使用 nacos,都会涉及到 namespace 的参数输入,那么这个时候 namespace 的值从哪里可以获取呢? 如果您在使用过程中没有感知到这个参数的输入,那么 nacos 统一会使用一个默认的 namespace 作为输入,nacos naming 会使用 public 作为默认的参数来初始化,nacos config 会使用一个空字符串作为默认的参数来初始化。。如果您需要自定义自己的 namespace,那么这个值该怎么来产生?可以在 nacos 的控制台左边功能侧看到有一个 命名空间 的功能,点击就可以看到 新建命名空间 的按钮,那么这个时候就可以创建自己的命名空间了。创建成功之后,会生成一个命名空间ID,主要是用来避免命名空间名称有可能会出现重名的情况。因此当您在应用中需要配置指定的 namespace 时,填入的是命名空间ID。重要的事情说三遍, 当您在应用中需要配置指定的 namespace 时,填入的是命名空间 ID当您在应用中需要配置指定的 namespace 时,填入的是命名空间 ID当您在应用中需要配置指定的 namespace 时,填入的是命名空间 ID说明: namesace 为 public 是 nacos 的一个保留控件,如果您需要创建自己的 namespace,最好不要和 public 重名,以一个实际业务场景有具体语义的名字来命名,以免带来字面上不容易区分自己是哪一个 namespace。 ...

May 28, 2019 · 1 min · jiezi

简单聊会-Docker

本文来自于我的慕课网手记:简单聊会 Docker,转载请保留链接 ;)最近在工作中一直在忙基础设施构建,发现在选型的时候,大家心里基本上都有一个自己的成熟架构。而在服务部署这块发现公司的同事们都大多数考虑Docker,在业余闲聊了后,发现他们对Docker只是在停留在使用,对一些Docker的基本知识还是不了解,并不清楚 Docker 到底是什么,要解决什么问题,好处又在哪里?今天就来详细解释,帮助大家理解它,还带有简单易懂的实例,教你如何将它用于日常开发并用其部署微服务。 Docker简介Docker是基于Go语言实现的云开源项目,诞生于2013年初,最初发起者是dotCloud公司。Docker自开源后受到广泛的关注和讨论,目前已有多个相关项目,逐渐形成了围绕Docker的生态体系。dotCloud公司后来也改名为Docker Inc,专注于Docker相关技术和产品的开发。Docker 一直广受瞩目,被认为可能会改变软件行业。那么什么是Docker呢?我查阅了网上的一些相关资料,现用一段话总结了一下。Docker是一个开源的容器引擎,它可以帮助我们更快地交付应用。Docker可将应用程序和基础设施层隔离,并且能将基础设施当作程序一样进行管理。使用Docker,可更快地打包、测试以及部署应用程序,并可减少从编写到部署运行代码的周期。对一个事物有了一定了解后,我们的继续学习Docker官方的给出文档和源码。(这个今天不在此文章扩展,不然聊不完。) TIPS (1) Docker官方网站:https://www.docker.com/ (2) Docker GitHub:https://github.com/docker/docker Docker快速入门执行如下命令,即可启动一个Nginx容器``docker run -d -p 91:80 nginx`` Docker架构我们来看一下来自Docker官方文档的架构图,如图所示。 我们来讲解上图中包含的组件。(1) Docker daemon(Docker守护进程) Docker daemon是一个运行在宿主机(DOCKER_HOST)的后台进程。我们可通过Docker客户端与之通信。 (2) Client(Docker客户端) Docker客户端是Docker的用户界面,它可以接受用户命令和配置标识,并与Docker daemon通信。图中,docker build等都是Docker的相关命令。 (3) Images(Docker镜像) Docker镜像是一个只读模板,它包含创建Docker容器的说明。它和系统安装光盘有点像——我们使用系统安装光盘安装系统,同理,我们使用Docker镜像运行Docker镜像中的程序。 (4) Container(容器) 容器是镜像的可运行实例。镜像和容器的关系有点类似于面向对象中,类和对象的关系。我们可通过Docker API或者CLI命令来启停、移动、删除容器。 (5) Registry Docker Registry是一个集中存储与分发镜像的服务。我们构建完Docker镜像后,就可在当前宿主机上运行。但如果想要在其他机器上运行这个镜像,我们就需要手动拷贝。此时,我们可借助Docker Registry来避免镜像的手动拷贝。 一个Docker Registry可包含多个Docker仓库;每个仓库可包含多个镜像标签;每个标签对应一个Docker镜像。这跟Maven的仓库有点类似,如果把Docker Registry比作Maven仓库的话,那么Docker仓库就可理解为某jar包的路径,而镜像标签则可理解为jar包的版本号。 Docker Registry可分为公有Docker Registry和私有Docker Registry。最常用的Docker Registry莫过于官方的Docker Hub,这也是默认的Docker Registry。Docker Hub上存放着大量优秀的镜像,我们可使用Docker命令下载并使用。 Docker应用场景常用的8个Docker的真实使用场景,分别是简化配置、代码流水线管理、提高开发效率、隔离应用、整合服务器、调试能力、多租户环境、快速部署。我们一直在谈Docker,Docker怎么使用,在怎么样的场合下使用? 首先你在享有Docker带来的虚拟化能力的时候无需担心它带来的额外开销。其次,相比于虚拟机,你可以在同一台机器上创建更多数量的容器。 Docker的另外一个优点是容器的启动与停止都能在几秒中内完成。Docker公司的创始人 Solomon Hykes曾经介绍过Docker在单纯的LXC之上做了哪些事情,你可以去看看。 下面是我总结的一些Docker的使用场景,它为你展示了如何借助Docker的优势,在低开销的情况下,打造一个一致性的环境。 简化配置这是Docker公司宣传的Docker的主要使用场景。虚拟机的最大好处是能在你的硬件设施上运行各种配置不一样的平台(软件、系统),Docker在降低额外开销的情况下提供了同样的功能。它能让你将运行环境和配置放在代码中然后部署,同一个Docker的配置可以在不同的环境中使用,这样就降低了硬件要求和应用环境之间耦合度。 代码流水线(Code Pipeline)管理前一个场景对于管理代码的流水线起到了很大的帮助。代码从开发者的机器到最终在生产环境上的部署,需要经过很多的中间环境。而每一个中间环境都有自己微小的差别,Docker给应用提供了一个从开发到上线均一致的环境,让代码的流水线变得简单不少。 提高开发效率这就带来了一些额外的好处:Docker能提升开发者的开发效率。如果你想看一个详细一点的例子,可以参考Aater在DevOpsDays Austin 2014 大会或者是DockerCon上的演讲。 ...

May 2, 2019 · 1 min · jiezi

一篇读懂分布式架构下的负载均衡技术分类原理算法常见方案等

1、引言关于“负载均衡”的解释,百度词条里:负载均衡,英文叫Load Balance,意思就是将请求或者数据分摊到多个操作单元上进行执行,共同完成工作任务。 负载均衡(Load Balance)建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。 负载均衡有两方面的含义: 1)首先,大量的并发访问或数据流量分担到多台节点设备上分别处理,减少用户等待响应的时间; 2)其次,单个重负载的运算分担到多台节点设备上做并行处理,每个节点设备处理结束后,将结果汇总,返回给用户,系统处理能力得到大幅度提高。 简单来说就是: 1)其一是将大量的并发处理转发给后端多个节点处理,减少工作响应时间; 2)其二是将单个繁重的工作转发给后端多个节点处理,处理完再返回给负载均衡中心,再返回给用户。 目前负载均衡技术大多数是用于提高诸如在Web服务器、FTP服务器和其它关键任务服务器上的Internet服务器程序的可用性和可伸缩性。 总之,它的目的就通过调度集群,达到最佳化资源使用,最大化吞吐率,最小化响应时间,避免单点过载的问题。 内容概述:本文将从负载均衡技术的分类、技术原理、常见实现算法、常用方案等入手,为您详细讲解负载均衡技术的方方面面。这其中,四层和七层负载均衡技术最为常用,它们也是本文介绍的重点。 内容点评:对于IM或消息推送应用的开发者来说,本文所介绍的传统负载均衡技术,可能对于IM等即时通讯分布式场景来说,没有办法直接套用。原因是IM这类socket长连接场景,所处的网络通信层级比较低,而且即时通讯相关的技术实现跟具体的业务逻辑紧密相关,因而无法像HTTP短连接这样基于标准化的负载均衡方法来实现。但本文所介绍的负载均衡原理、算法和一些方案实现,仍然可以为IM或消息推送应用的开发者带来一些借鉴和参考意义,值得深 入一读。 补充:另一篇《快速理解高性能HTTP服务端的负载均衡技术原理》,也讲述了负载均衡方面的知识,有兴趣也可以阅读之。 (本文同步发布于:http://www.52im.net/thread-24...) 2、相关文章深入阅读以下文章,有助于您更好地理解本篇内容: 《网络编程懒人入门(一):快速理解网络通信协议(上篇)》 《网络编程懒人入门(二):快速理解网络通信协议(下篇)》 《网络编程懒人入门(三):快速理解TCP协议一篇就够》 《腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面》 《快速理解高性能HTTP服务端的负载均衡技术原理》 《新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践》 《通俗易懂:基于集群的移动端IM接入层负载均衡方案分享》 《IM开发基础知识补课:正确理解前置HTTP SSO单点登陆接口的原理》 3、负载均衡分类TCP/IP协议的OSI模型: (本图高清版,请从《计算机网络通讯协议关系图(中文珍藏版)[附件下载]》一文中下载之) 根据OSI模型可将负载均衡分为: 1)二层负载均衡(一般是用虚拟mac地址方式,外部对虚拟MAC地址请求,负载均衡接收后分配后端实际的MAC地址响应); 2)三层负载均衡(一般采用虚拟IP地址方式,外部对虚拟的ip地址请求,负载均衡接收后分配后端实际的IP地址响应); 3)四层负载均衡(在三次负载均衡的基础上,用 ip+port 接收请求,再转发到对应的机器); 4)七层负载均衡(根据虚拟的url或是IP,主机名接收请求,再转向相应的处理服务器)。 这其中,最常见的是四层和七层负载均衡,也是本文接下来介绍的重点。 当客户端发起请求,会经过层层的封装,发给服务器,服务器收到请求后经过层层的解析,获取到对应的内容。 下图是一个典型的HTTP请求分层传递原理: 4、二层负载均衡二层负债均衡是基于数据链路层的负债均衡,即让负债均衡服务器和业务服务器绑定同一个虚拟IP(即VIP),客户端直接通过这个VIP进行请求。 那么如何区分相同IP下的不同机器呢?没错,通过MAC物理地址,每台机器的MAC物理地址都不一样,当负载均衡服务器接收到请求之后,通过改写HTTP报文中以太网首部的MAC地址,按照某种算法将请求转发到目标机器上,实现负载均衡。 这种方式负载方式虽然控制粒度比较粗,但是优点是负载均衡服务器的压力会比较小,负载均衡服务器只负责请求的进入,不负责请求的响应(响应是有后端业务服务器直接响应给客户端),吞吐量会比较高。 5、三层负载均衡三层负载均衡是基于网络层的负载均衡,通俗的说就是按照不同机器不同IP地址进行转发请求到不同的机器上。 这种方式虽然比二层负载多了一层,但从控制的颗粒度上看,并没有比二层负载均衡更有优势,并且,由于请求的进出都要经过负载均衡服务器,会对其造成比较大的压力,性能也比二层负载均衡要差。 6、四层负载均衡四层的负载均衡就是基于IP+端口的负载均衡:在三层负载均衡的基础上,通过发布三层的IP地址(VIP),然后加四层的端口号,来决定哪些流量需要做负载均衡,对需要处理的流量进行NAT处理,转发至后台服务器,并记录下这个TCP或者UDP的流量是由哪台服务器处理的,后续这个连接的所有流量都同样转发到同一台服务器处理。 对应的负载均衡器称为四层交换机(L4 switch),主要分析IP层及TCP/UDP层,实现四层负载均衡。 此种负载均衡器不理解应用协议(如HTTP/FTP/MySQL等等),常见例子有:LVS,F5。 7、七层负载均衡七层的负载均衡就是基于虚拟的URL或主机IP的负载均衡:在四层负载均衡的基础上(没有四层是绝对不可能有七层的),再考虑应用层的特征,比如同一个Web服务器的负载均衡,除了根据VIP加80端口辨别是否需要处理的流量,还可根据七层的URL、浏览器类别、语言来决定是否要进行负载均衡。 举个例子,如果你的Web服务器分成两组,一组是中文语言的,一组是英文语言的,那么七层负载均衡就可以当用户来访问你的域名时,自动辨别用户语言,然后选择对应的语言服务器组进行负载均衡处理。 对应的负载均衡器称为七层交换机(L7 switch),除了支持四层负载均衡以外,还有分析应用层的信息,如HTTP协议URI或Cookie信息,实现七层负载均衡。此种负载均衡器能理解应用协议,常见例子有: haproxy,MySQL Proxy。 8、四层负载均衡和七层负载均衡的区别8.1 技术原理上的区别 所谓四层负载均衡,也就是主要通过报文中的目标地址和端口,再加上负载均衡设备设置的服务器选择方式,决定最终选择的内部服务器。 以常见的TCP为例,负载均衡设备在接收到第一个来自客户端的SYN 请求时,即通过上述方式选择一个最佳的服务器,并对报文中目标IP地址进行修改(改为后端服务器IP),直接转发给该服务器。TCP的连接建立,即三次握手是客户端和服务器直接建立的,负载均衡设备只是起到一个类似路由器的转发动作。在某些部署情况下,为保证服务器回包可以正确返回给负载均衡设备,在转发报文的同时可能还会对报文原来的源地址进行修改。 所谓七层负载均衡,也称为“内容交换”,也就是主要通过报文中的真正有意义的应用层内容,再加上负载均衡设备设置的服务器选择方式,决定最终选择的内部服务器。 以常见的TCP为例,负载均衡设备如果要根据真正的应用层内容再选择服务器,只能先代理最终的服务器和客户端建立连接(三次握手)后,才可能接受到客户端发送的真正应用层内容的报文,然后再根据该报文中的特定字段,再加上负载均衡设备设置的服务器选择方式,决定最终选择的内部服务器。负载均衡设备在这种情况下,更类似于一个代理服务器。负载均衡和前端的客户端以及后端的服务器会分别建立TCP连接。所以从这个技术原理上来看,七层负载均衡明显的对负载均衡设备的要求更高,处理七层的能力也必然会低于四层模式的部署方式。 8.2 应用场景的需求 七层应用负载的好处,是使得整个网络更"智能化"。参考这篇《利用负载均衡优化和加速HTTP应用》,就可以基本上了解这种方式的优势所在。 ...

April 29, 2019 · 2 min · jiezi

分布式系统关注点18缓存穿透和缓存雪崩到底啥区别

如果第二次看到我的文章,欢迎文末扫码订阅我个人的公众号(跨界架构师)哟~ 本文长度为2805字,建议阅读8分钟。坚持原创,每一篇都是用心之作~ 有句话说得好,欲要使其毁灭,先要使其疯狂。当你沉浸在缓存所带来的系统tps飙升的喜悦中时,使你系统毁灭的种子也已经埋在其中。 而且,你所承载的tps越高,它所带来的毁灭性更大。 在前两篇《360°全方位解读「缓存」》和《先写DB还是「缓存」?》中,我们已经对缓存有了一定的认识,并且知道了关于缓存相关的「一致性」问题的最佳实践。 这次,我们就来聊聊隐藏在缓存中的毁灭性种子是什么? 我们从前一篇文章《先写DB还是「缓存」?》中多次提到的「cache miss」说起。 缓存雪崩在前一篇文章《先写DB还是「缓存」?》中,我们多次提到了「cache miss」这个词,利用「cache miss」来更好的保障DB和缓存之间的数据一致性。 然而,任何事物都是有两面性的,「cache miss」在提供便利的同时,也带来了一个潜在风险。 这个风险就是「缓存雪崩」。 在图中的第二步,大量的请求并发进入,这里的一次「cache miss」就有可能导致产生「缓存雪崩」。 不过,虽然「cache miss」会产生「缓存雪崩」,但「缓存雪崩」并不仅仅产生于「cache miss」。 雪崩一词源于「雪崩效应」,是指像「多米勒骨牌」这样的级联反应。前面没顶住,导致影响后面,如此蔓延。(关于对应雪崩的方式参考之前的文章,文末放链接) 所以「缓存雪崩」的根本问题是:缓存由于某些原因未起到预期的缓冲效果,导致请求全部流转到数据库,造成数据库压力过重。 因此,流量激增、高并发下的缓存过期、甚至缓存系统宕机都有可能产生「缓存雪崩」问题。 怎么解决这个问题呢?宕机可以通过做高可用来解决(可以参考之前的文章,文末放链接)。而在“流量激增”、“高并发下的缓存过期”这两种场景下,也有两种方式可以来解决。 加锁排队通过加锁或者排队机制来限制读数据库写缓存的线程数量。比如,下面的伪代码就是对某个key只允许一个线程进入的效果。 key = "aaa";var cacheValue = cache.read(key);if (cacheValue != null) { return cacheValue;}else { lock(key) { cacheValue = cache.read(key); if (cacheValue != null) { return cacheValue; } else { cacheValue = db.read(key); cache.set(key,cacheValue); } } return cacheValue;} 这个比较好理解,就不废话了。 ...

April 25, 2019 · 1 min · jiezi

分布式系统中的那些开源软件

本文来自于我的慕课网手记:开篇-分布式系统中的那些开源软件,转载请保留链接 ;)我们来讨论一个大型话题,把分布式系统所能采用的开源或者商业软件,方方面面都来讨论一下。这里做个记录,也算是我加入慕课网认证作者的一个里程碑,今后的文章也是会和这些软件相关的,毕竟单体的项目已经不复返,分布式的项目已经成为了主流。不管你看到这个大纲可能有的熟悉,还是有的不了解,没关系,我们今后会一个个掰开的学习掌握它们,(熟悉的就要更加熟悉,不会的就要学会并掌握它。)当然,这篇文章不能代表所有分布式所用到的技术,也欢迎各位在后面评论中留言补充。基础框架Spring Cloud,Dubbo,Motan,Sofa分布式注册中心Eureka(Netflix),Consul,Nacos,Etcd,Zookeeper分布式监控中心CAT,SBA,Prometheus,Grafana分布式配置中心Apollo,Nacos,DisConf,Spring Cloud Config分布式网关F5,Ngnix+(打通Consul),ESB,Kong,zuul, gateway分布式事务Seata,dts,tcc-transaction,hmily,ByteTCC,myth,EasyTransaction,tx-lcn分布式日志系统ELK(Kibana,ElasticSearch,Logstash),Kafka,Flume,Splunk分布式定时任务调度和管理Elastic Job,XXL Job分布式限流熔断降级Sentinel,Redis,Guava分布式服务权限控制系统OAuth,JWT,单点登录,Hystrix,shiro分布式监控中心CAT,SBA,Prometheus,Grafana,Graphite,Statsd,Solarwinds,Zabbix,Centreon,appDynamics,new relic,Kaeger分布式服务和系统诊断Arthas分布式调用链CAT,SkyWalking+RocketBolt,Zipkin,DynaTrace分布式流程和服务编排Coroutine,Akka,Kilim,Flowable,Axon分布式锁Redisson,Redis,Zookeeper分布式压测平台JMeter,LoadRunner分布式全局主键系统Redis,Zookeeper,Twitter Snowflake分布式自动化测试Postman、Jenkins分布式自动化API文档Swagger分布式分库分表中间件多数据源Sharding Sphere,MyCat分布式消息队列中间件RocketMQ,Kafka,ActiveMQ,Tibco分布式缓存Redis、MongoDB分布式数据库分析诊断系统慢SQL,听云分布式自动化数据库脚本升级Flyway异构系统Spring Cloud Sidecar,Service Mesh,istio,Sofa mesh异构网关运维发布DevOps,CICD和Pipeline,容器(Docker)化,K8S,Jenkins,蓝鲸,TriAquae,Choerodon(猪齿鱼)

April 20, 2019 · 1 min · jiezi

开源|ns4_frame分布式服务框架开发指南

导语:宜信于2019年3月29日正式开源nextsystem4(以下简称“NS4”)系列模块。此次开源的NS4系列模块是围绕当前支付系统笨重、代码耦合度高、维护成本高而产生的分布式业务系统解决方案。NS4系列框架允许创建复杂的流程/业务流,对于业务服务节点的实现可串联,可分布式。其精简、轻量,实现了“脱容器”(不依赖tomcat、jetty等容器)独立运行。NS4系列框架的设计理念是将业务和逻辑进行分离,开发人员只需通过简单的配置和业务实现就可以实现逻辑复杂、性能高效、功能稳定的业务系统。【点击查看框架整体介绍】NS4系列包括4个开源模块,分别是:ns4_frame分布式服务框架、ns4_gear_idgen ID 生成器组件(NS4框架Demo示例)、ns4_gear_watchdog 监控系统组件(服务守护、应用性能监控、数据采集、自动化报警系统)和ns4_chatbot通讯组件。本文将详细介绍ns4_frame分布式服务框架开发指南。项目地址:https://github.com/newsettle/…一、框架介绍ns4_frame本质上是两个应用加三套开发框架组合起来的分布式业务框架。1.1 使用范围ns4_frame分布式框架主要适用于业务类型为消息流或者业务核心模型为流式业务的业务系统。它支持消息分发、传递、追踪,支持分步骤、分批次的消息处理,对于信息流、数据流等消息驱动型的业务尤为契合。1.2 项目结构ns4_frame框架是一套MAVEN父子项目,由五个项目组成:NS_MQ :负责和底层消息队列进行通信,提供了对消息队列进行操作的API。NS_TRANSPORTER:通过调用NS_MQ提供的API,对业务消息进行收取、处理、转发。NS_CHAIN :一个可选开发框架,负责对同一个jvm中的业务处理步骤进行链条式的整合,组成当前业务模块的业务处理流程。NS_CONTROLLER:一个业务消息转发应用,负责将接收到的消息转给对应的业务模块进行处理,同时负责将业务模块根据整体业务进行关联。NS_CONTROLLER本质是一个独立的应用系统,构建于 NS_TRANPORTOR和NS_CHAIN之上。NS_DISPATCHER :ns4_frame架构规定的消息入口,通过提供的http服务接受业务系统边界外的http请求,并将请求转化成业务系统内部通信使用的消息协议格式。二、基础入⻔2.1 开发环境配置开发语言:JAVAJDK版本:JDK1.7MAVEN版本:3.3以上REDIS版本:3.0以上以上是开发环境必备的组件和配置,其中java为开发语言,maven为项目编译打包部署必备, redis作为消息中间件使用。2.2 运行ns4_frame运行至少需要启动三个jvm项目才能完整运行。启动整个项目分为如下三步:第一步: ns4_frame消息入口是一个http接口,http服务是由NS_DISPATCHER项目提供的,所以我们第一件事情就是要把NS_DISPATCHER运行起来。要运行NS_DISPATCHER,直接运行Bootstrap类的main方法即可。 默认的NS_DISPATCHER会在本机(127.0.0.1)的8027端口上监听http请求,如果收到http请求,默认会将http请求转换成内部通信消息,并存储到本机(127.0.0.1)的 redis中,默认访问的redis端口号是6379。第二步: NS_CONTROLLER负责接收NS_DISPATCHER传入的消息,并根据配置进行消息分发。 所以我们随后需要运行NS_CONTROLLER项目(为了方便,以下简称“CONTROLLER”) 。在CONTROLLER项目中我们不能直接运行,需要配置一些东⻄。CONTROLLER要运行至少需要指定一个配置文件位置。这个配置文件需要通过java命令参数来指定。假设我现在指定java运行参数 -Dconfigfile=nscontroller.xml 这个参数本质上是给CONTROLLER底层的NS_TRANSPORTER使用的,它指明了 NS_TRANSPORTER必须得配置文件位置,使得CONTROLLER能顺利利用 NS_TRANSPORTER进行消息收发。默认情况下,CONTROLLER还会到classpath下去找关于NS_CHAIN需要的配置文件, 默认路径是classpath下的nschainconfig目录,在这个目录下所有的xml文件会被认作是NS_CHAIN需要的配置文件集合。当配置文件配置好后,可以通过调用com.creditease.ns.transporter.context.XmlAppTransporterContext的 main方法来启动NS_CONTROLLER 。第三步: 在这个步骤中我们需要启动自己的业务项目,在这个业务项目中,必须有以下三个前置条件:业务项目需要建立在NS_TRANSPORTER框架之上。业务项目的消息队列名称必须和CONTROLLER项目中配置的队列名一致。业务启动必须通过 com.creditease.ns.transporter.context.XmlAppTransporterContext的 main方法来启动。完成以上的三个步骤,一个基本的ns4_frame系统就搭建好并运行起来了。三、项目架构3.1 层次划分上图展示了ns4_frame每个系统的层次结构。底层是以redis作为消息中间件,对消息中间件的操作被封装入了NS_MQ项目,它向上层提供了对消息队列的操作API接口。在NS_MQ的上层是NS_TRANSPORTER,它本质是一套消息收发处理框架,它负责接收消息后反向回调业务代码,并将消息交给业务层处理。当业务层处理完毕后,它负责将处理后的消息返回到redis中。NS_CHAINS是一套开发辅助框架,它负责将一个模块的业务处理步骤解耦成一个个零散的任务,并可以随意以任何顺序做关联。NS_CONTROLLER是一个项目,它本质上是一个独立的应用,它负责将整体业务分解成一个个节点,并通过配置将他们以一定的顺序关联起来,并通过消息机制,将这些节点结合起来 形成一套业务系统。NS_DISPATCHER也是一个项目,它是以NETTY框架作为基础,开发出的一个能提供基本的 http服务的独立应用。同时它也是业务系统和外部通讯的唯一边界。3.2 运行流程ns4_frame整套系统本质上其实就是一套消息中间件服务加开发框架,整体的结构图如下:上图显示了一个ns4_frame整体分布式项目的运行流程,一个消息的运转流程按如下顺序:NS_DISPATCHER收到http请求,并将http请求转化为内部消息协议放入指定的消息队列中(根据配置文件)。NS_CONTORLLER从步骤1指定的队列接收到消息,并根据配置的服务编排开始按照顺序将消息发送到每个服务步骤对应的消息队列中。业务系统收到步骤2中NS_CONTROLLER指定的消息队列接收到消息并开始处理,处理完毕后,将结果返回。NS_CONTROLLER收到业务系统的响应,开始根据配置好的服务,将返回的消息结果发送到下一个服务对应的消息队列中。四、NS_MQ框架介绍4.1 核心类和接口RedisMQTemplate类:封装了所有和消息队列的操作相关的APIMQConfig:存储了所有和底层消息中间件相关的配置。4.2 配置方案默认的,在没有做任何配置的情况下,NS_MQ会自动访问本机(127.0.0.1)的6379端口的redis,如果没有,则会报异常。通常,NS_MQ会去找classpath下一个名为ns_mq.properties的配置文件,这个配置文件中存储着所有和底层消息中间件相关的属性。列举一些关键的配置元素:redis.type 1 代表redis单机 2 代表redis集群 默认为1redis.single/cluster.host redis单机或者集群的主机地址(包含端口)redis.single/cluster.maxTotal redis单机或者集群的最大连接数redis.single/cluster.miniIdle redis单机或者集群的最小闲置连接数redis.single/cluster.maxIdle redis单机或者集群的最大闲置连接数redis.single/cluster.connectionTimeout redis单机或者集群的尝试连接的超时时间(尚未连接到服务需要等待的时间)redis.single/cluster.socketTimeout redis单机或者集群连接后socket闲置的超时时间五、NS_TRANSPORTER框架介绍5.1 框架架构上图展示了整个NS_TRANSPORTER的整体架构,整套框架收发处理消息分为如下三个步骤:首先由接收消息的线程(Fetcher线程)通过NS_MQ从底层消息中间件获取消息并放入到本地消息缓存。消息处理线程(Handler线程)从本地消息缓存中取出消息,并调用业务层的方法实现对消息进行处理,处理完毕后,将处理后的消息放到本地发送缓存。发送线程(Sender线程)从本地发送缓存取出消息后,将消息通过NS_MQ将消息放入底层消息中间件。5.2 核心类和接口ServiceMessage:对各个模块之间传递的消息的java封装,包含了模块间通信需要知道的任何信息;Worker:业务层需要实现此接口的doWork方法,实现此接口的对象会被NS_TRANSPORTER的Handler线程回调用来对ServiceMessage中的信息进行处理。ActionWorker:已经部分封装好的抽象类,实现了Worker接口,业务层可以直接继承这个抽象类,简化开发。5.3 配置方案默认的,NS_TRANSPORTER会去找名为configfile的系统变量,这个系统变量的值就是NS_TRANSPOTER需要的配置文件所在的路径,NS_TRANSPORTER会找到这个xml配置文件,并在解析相关的配置后启动。 NS_TRANSPORTER相关的配置文件模板如下:<queues> <prefix></prefix> <launchers> <launcher><class name=“类的全名” method=“method方法名” property="" /><class name=“类的全名” static-method=“method方法名” /> </launcher> </launchers> <inqueues> <queue> <name></name><fetchernum></fetchernum> <buffersize></buffersize> <handlersize></handlersize> <serviceClass></serviceClass> <sendernum></sendernum> </queue> </inqueues></queues>以上xml模板中有如下几个关键元素需要注意:Launcher:用来定义在整个框架完全运行之前需要执行的方法。queue:在这个元素下 name元素表示需要监听的底层消息中间件的队列名。Fetchernum:表示监听消息队列并获取消息的线程数,默认是1。buffersize:表示本地接收/发送消息队列的大小默认是100。handlersize:表示处理消息的线程数,默认是10。Serviceclass:表示具体的处理消息的业务类,这个类必须实现了Worker接口。Sendernum:表示从本地发送消息队列中获取消息后发送到底层消息中间件的线程数。六、NS_CHAIN框架介绍6.1 框架架构由于NS_CHAIN本质是一个纯开发框架,故暂时忽略此框架的框架架构。6.2 核心类和接口暂略6.3 配置方案本节将详细介绍NS_CHAINS的配置。NS_CHAINS启动时会去找系统变量chainconfig,这个变量的值就是NS_CHAINS配置文件所在的路径。NS_CHAINS支持配置目录(目录下的所有xml格式文件都被视作NS_CHAINS框架的配置文件)和配置文件。 对于NS_CHAINS的配置格式我们大致列举出关键要素如下:catalog:这个相当于一个完整的服务或者一个命名空间,是NS_CHAINS对外服务的基本单位,NS_CHAINS外部系统只能看到catalog。Command:这是NS_CHAINS任务执行的最小单位,所有执行任务都可以以command的形式被调用执行。Chain:这是一个command的容器,可以将多个command的任务组合成一个执行链路。Group:这个一个command的组合,它可以将多个command组合成一个整体,并按照配置顺序执行。同时NS_CHAINS的配置具有完整的逻辑语法,支持if条件判断,while循环结构和顺序结构。七、NS_DISPATCHER应用介绍7.1 框架架构NS_DISPATCHER本质是一个独立的建立在Netty框架上的一个能提供http服务的独立应用,所以框架结构此处从略。7.2 核心类和接口NS_DISPATCHER是以NETTY框架为基础的,所以其核心类就是如下的几个协议处理器:HttpDispatcherServerHandler:主要负责解析传入的http请求,并封装成对应的java对象交给 HttpRPCHandler做进一步处理。HttpRPCHandler:主要接收上一步封装好的java对象,并取出对应的请求参数、请求内容等,封装成系统内部传输用的协议对象,并可以以同步请求响应模式/异步发送模式将协议对象放入底层消息中间件。7.3 配置方案NS_DISPATCHER启动会去找ns_dispatcher.properties文件,下面介绍配置的关键元素:http.port:指定了http服务的监听端口。dispatcher.pool.num:指定了dispatcher的并发线程数,dispatcher的性能和这个参数有非常大的关系。dispatcher.queuename:封装好的内部协议消息要放入的队列的名字,NS_DISPATCHER也支持https,所以,如果在ns_dispatcher.properties文件中有如下几个选项,那么NS_DISPATCHER也会启动对应的https服务。ca.path:指明了可信任证书的路径。key.path:指明了公钥的路径。https.port:指明了https服务监听的端口。八、NS_CONTROLLER应用介绍8.1 框架架构NS_CONTROLLER本质是建立在NS_TRANSPORTER和NS_CHAINS上的独立应用,核心就是 NS_TRANSPORTER的架构加NS_CHAINS的辅助,故不再重复列举其架构。8.2 核心类和接口DefaultPublishCommand:这是NS_CONTROLLER对于NS_CHAINS的一个扩展,它支持同步发送消息,并等待消息的响应,并可以设置等待响应的超时时间。同时,还支持异步发送消息,不需要等待消息的响应。8.3 配置方案遵循NS_TRANSPORTER和NS_CHAINS的配置规则,所以不再赘述。 注意:在NS_CONTROLLER中对于NS_CHAINS的配置做了一些功能扩展,主要是添加了publish的配置元素,这个随后可以提供配置模板。九、项目部署9.1 部署方案如果要部署整个ns4_frame项目,请按照以下步骤进行:部署NS_DISPATCHER项目:NS_DISPATCHER项目是一个Maven项目,首先需要通过mvn:package deploy将整个项目打成一个zip包上传到服务器,然后解压成一个目录。在这个目录中,有如下几个子目录:bin、config、lib、logs。其中,bin目录中包含了DISPATCHER的启动脚本;config目录存放了NS_DISPATCHER必须的配置文件;lib目录存放了NS_DISPATCHER所需要的所有jar包;logs目录存放了所有NS_DISPATCHER打印的日志。部署NS_CONTROLLER项目:NS_CONTROLLER项目也是一个Maven项目,需要通过mvn:package deploy将整个项目打成一个zip包。目录结构同NS_DISPATCHER项目,此处不再赘述。部署业务代码:业务代码请自行按照各个团队的规则部署。十、运行日志10.1 日志分类ns4_frame项目将日志大致分成了四类:fram.log:系统日志,属于整个ns4_frame底层系统内部的日志,包括系统的启动,线程的启动关闭等信息。biz.log:业务日志,所有业务相关的日志统统会被导向到这里。flow.log:消息流日志,这里记录了系统所有消息的流转信息。mq.log:这里记录所有对底层消息中间件进行操作的信息。10.2 如何查看日志业务报错:如果业务报错,基本所有的报错信息都会在biz.log中查到。消息流转: 如果是消息发送响应的问题,基本上在flow.log中可以查到或者推断出相关的信息。底层消息中间件交互: 如果消息流转无法推断出问题,或者无法查到对应的消息,就需要转到mq.log中进行查询。十一、其他11.1 常⻅问题ns4_frame系统本质是一个以消息为通信机制的分布式系统,经常出现的问题分成以下两部分:业务异常由于业务本身是由底层NS_TRANSPORTER回调来执行的,当业务出现异常的时候,很可能由于没有合适的被catch到,从而被底层的NS_TRANSPOTER框架捕获。 对于没有在biz.log和stdoout.log中查找到的问题,可以去查看下flow.log的日志,看是否出现了异常被底层NS_TRANSPOTER捕获了。底层异常有些情况,业务本身并没有出现问题,但是由于消息通信出现了问题,会导致业务没有执行,对于 这种情况我们需要首先从消息入口处即NS_DISPATCHER的flow.log中查找到对应的 messageId,然后根据消息流转路径,一步步去对应的部署机器上查询。 内容来源:宜信技术学院 ...

April 18, 2019 · 1 min · jiezi

开源|宜信开源专注业务逻辑的轻量级服务框架nextsystem4

宜信于2019年3月29日正式开源nextsystem4(以下简称“NS4”)系列模块。此次开源的NS4系列模块是围绕当前支付系统笨重、代码耦合度高、维护成本高而产生的分布式业务系统解决方案。NS4系列框架允许创建复杂的流程/业务流,对于业务服务节点的实现可串联,可分布式。其精简、轻量,实现了“脱容器”(不依赖tomcat、jetty等容器)独立运行。NS4系列框架的设计理念是将业务和逻辑进行分离,开发人员只需通过简单的配置和业务实现就可以实现逻辑复杂、性能高效、功能稳定的业务系统。NS4系列包括4个开源模块,分别是:ns4_frame 分布式服务框架、ns4_gear_idgen ID 生成器组件(NS4框架Demo示例)、ns4_gear_watchdog 监控系统组件(服务守护、应用性能监控、数据采集、自动化报警系统)和ns4_chatbot通讯组件。NS4系列模块的核心优势主要体现在以下几个方面:具有很好的伸缩性,可以优雅地扩容和降级;集中化管理,对各个节点的消息进行集中式管理和分发;易维护,将复杂的流程性业务拆分成多个模块系统进行交互,减少代码耦合;完善的调用链路,对于链路复杂的系统可以准确地定位出错的环节。可以通过在线聊天机器人实现及时的自动提醒。项目开源地址:https://github.com/newsettle一、ns4_frame开源地址:https://github.com/newsettle/…ns4_frame是一个高性能优秀的分布式服务框架,允许创建复杂的流程/业务流,对于业务服务节点的实现可串联,可分布式。其精简、轻量,实现了“脱容器”(不依赖tomcat、jetty等容器)独立运行。ns4_frame将业务和逻辑进行分离,开发人员只需通过简单的配置和业务实现就可以实现逻辑复杂、性能高效、功能稳定的业务系统。项目结构ns4_frame是一套MAVEN父子项目,由五个子项目组成:NS_MQ :负责和底层消息队列进行通信,提供了对消息队列进行操作的API。目前NS4底层支持redis作为消息中间件,同时提供通用的接口,可以扩展多种消息中间件,对消息中间件的操作被封装入了NS_MQ项目中。NS_TRANSPORTER:通过调用NS_MQ提供的API,对业务消息进行收取、处理、转发。它本质是一套消息收发处理框架,主要负责接收消息后反向回调业务代码,并将消息交给业务层处理,当业务层处理完毕后,再将处理后的消息返回给redis中。NS_CHAIN:一个可选开发框架,负责对同一个JVM中的业务处理步骤进行链条式的整合,组成当前业务模块的业务处理流程。NS_CONTROLLER:一个业务消息转发应用,负责将接收到的消息转给对应的业务模块进行处理,同时根据整体业务将业务模块进行关联.NS_CONTROLLER本质是一个独立的应用系统,构建于 NS_TRANPORTOR和NS_CHAIN之上。NS_DISPATCHER :NS4架构规定的消息入口,以NETTY框架作为基础,通过提供的HTTP服务接受业务系统边界外的http请求,并将请求转化成业务系统内部通信使用的消息协议格式。上图展示了NS4每个系统的层次结构。运行流程NS4整套系统本质上其实就是一套消息中间件服务加开发框架,整体的结构图如下: 上图展示了一个NS4整体分布式项目的运行流程。一个消息的运转流程按如下顺序:NS_DISPATCHER收到http请求并将其转化为内部消息协议放入指定的消息队列中(根据配置文件) 。NS_CONTORLLER从步骤1指定的队列接收到消息,并根据配置的服务编排开始按照顺序将消息发送到每个业务系统步骤对应的消息队列中。业务系统收到步骤2中NS_CONTROLLER指定的消息队列的信息,开始处理,处理完毕后,将结果返回。NS_CONTROLLER收到业务系统的响应,开始根据配置好的服务将返回的消息结果发送到下一个业务系统对应的消息队列中。消息被所有的业务系统处理完成后,NS_CONTROLLER把消息处理结果放入到指定的消息队列里,NS_DISPATCHER从对应的消息队列里取出消息结果,响应给http调用者。二、ns4_gear_idgen开源地址:https://github.com/newsettle/…ns4_gear_idgen (ID生成器)是基于NS4框架实现的,它支持分布式部署,生成全局唯一的 ID,其中长度、前缀、后缀、步长、进制也可根据自己的业务自由配置,还可以通过ns4_gear_idgen对NS4.0框架进行测试。优点很方便的线性扩展,能够支撑大多数业务场景。生成ID规则多样,可根据业务需求自由配置,且支持10进制、36进制、62进制。业务之间ID相互隔离,互不影响。获取ID不用频繁操作数据库,快消耗完号段内ID时才会操作数据库,减轻了数据库的压力。提前初始化号段内的ID,保证在每个号段内ID使用完之前完成初始化,避免业务使用完ID后再初始化所带来的影响。可以自定义 key_value 的大小,业务可以很方便地从原有的ID方式迁移过来。容灾性高,服务内部有号段缓存,即使DB宕机,短时间内仍能正常对外提供服务。三、ns4_gear_watchdog开源地址:https://github.com/newsettle/…ns4_gear_watchdog是ns4_frame进程的父进程,守护并管理ns4_frame进程。它的职责包括以下几个方面:对ns4_frame进行远程启动和停止、实时监测ns4_frame进程的健康状态、内存消耗、CPU使用、内部线程;收集ns4_frame实现的业务日志归集、实现业务内部实时流转的业务数据,达到实时对ns4_frame进程在线上的运行状态、实现的业务以及业务数据的流转状态等方面的监控,并精准、快速、便捷地定位出异常以及CPU、线程等运行状态。ns4_gear_watchdog是作为父进程存在的,通过父进程启动目标项目(子进程),并针对子进程应用生存的环境因素(包括系统层面的内存消耗、CPU 使用、负载、线程等)、实现功能的代码因素(代码健康程度)、业务因素等数据进行实时监控。父子进程通过 jmx 方式进行通讯,采集以上因素数据,并将这些数据保存到 ElasticSearch 中,进一步通过分析数据和现实运行情况总结制定出的指标相结合,将该以上因素数据通过微信机器人实时通知提醒相关负责人。[ns4_gear_watchdog基本结构图]四、ns4_chatbot开源地址:https://github.com/newsettle/…ns4_chatbot是一个机器人的聊天框架,集成了qqbot、WxChat、rasa以及web服务。ns4_chatbot提供微信和qq聊天接口,可以对某个群组发送系统监控消息等,还可以把ns4_gear_watchdog监控信息发送到对应的群组中。ns4_chatbot实现的功能:接受内部系统(如监控系统)的系统调用,从而把消息推送给 QQ 或者微信用户。内部系统调用服务时,需要提供以下信息:发给哪个群组发给这个群组中的哪个用户发送的消息内容可以接受 QQ、微信用户的对话,理解其意图,并且回应用户。未来展望宜信一直践行以科技推动金融发展的技术信念,并愿意将技术实践成果开源分享,以期通过宜信的实践经验推动金融科技行业的发展和创新。 目前,宜信技术学院已开源了多个宜信的技术成果与研发实践,面向软件研发行业分享宜信的技术理念,本次NS4系列模块的开源将保持长期更新和维护,也希望有更多的技术伙伴加入到开源项目中,共同维护与发展开源成果。内容来源:宜信技术学院

April 16, 2019 · 1 min · jiezi

近万字长文,设计分布式系统需要考虑因素的都在这里!

本周三晚20:30,Kubernetes Master Class在线培训第三期《Kubernetes应用商店:Harbor与Istio》即将开播,点击http://live.vhall.com/231749318 即可免费预约注册!介 绍今天的应用程序可以说是分布式系统开发中的一项奇迹。基于不同的系统架构,构成应用程序的每个功能或服务可能在不同的系统上执行,而系统位于不同的地理位置,使用不同的计算机语言编写。应用程序的组件可能托管在一个功能强大的系统上,该系统由用户自己携带,并且可以和世界各地的应用程序组件或服务进行通信(他们都是数据中心的副本)。而令人惊讶的是,使用这些应用程序的用户通常并不会对复杂环境的请求作出响应。这样的请求包含了像本地时间、本地天气或前往酒店的方向等等。让我们慢慢开始介绍,看看使这一切成为可能的工业魔法,并思考开发人员在处理这种复杂性时应该牢记哪些思想和规则。系统设计的演变史图1:系统设计的历史演变从程序员编写应用程序,手工将它们编译成他们正在使用的机器的语言,然后使用切换开关将单个机器指令和数据直接输入到计算机的内存开始,应用程序开发已经走过了漫长的道路。随着处理器越来越强大,系统内存和在线存储容量增加,计算机网络能力显著增强,开发方法也产生了变化。现在,数据可以从地球的一段传递到另一端,而速度比早期电脑将数据从系统内存转移到处理器本身的速度还要快!让我们看看这一惊人转变中的一些亮点。单体设计早期的计算机程序都是基于单体设计的,所有的应用程序组件都被设计成在一台机器上执行。这意味着像用户界面(如果用户实际能与程序交互)、应用程序处理规则、数据管理、存储管理和网络管理(如果计算机连接到计算机网路上)等功能都包含在了程序中。这些虽然编写起来简单,但这些程序会变得越来越复杂,越来越难以形成文档,也难以更新和更改。这时,机器本身对企业来说就成了最大的开销,因此应用程序的设计是为了尽最大可能使用机器。客户端/服务器架构随着处理器越来越强大,系统和在线存储容量增加,数据通信更快、更经济,应用程序的设计也随之发展,以适应发展的速度。应用程序逻辑被重构或分解,允许每个应用程序在不同的机器上执行,并且在组件之间插入了不断改进的网络。这使得一些特性可以迁移到当前可用的成本最低的计算环境中。这一演变经历了一下几个阶段:终端和终端模拟早期的分布式计算依赖于特别用途的用户访问设备——终端。应用程序必须理解它们使用的通信协议,并直接向设备发出命令。当廉价的个人计算机(PC)出现时,终端被运行终端模拟程序的PC所取代。此时,应用程序的所有组件仍然驻留在单个大型机或小型计算机上轻量客户端随着PC的功能越来越强大,支持更大的内部和在线存储,网络性能进一步提高,企业对其应用程序进行了细分或分解,以便在本地PC上提取和执行用户界面。应用程序的其余部分则继续在数据中心的系统上执行。这些PC通常比它们所替代的终端便宜,并且它们还有额外的优点。这些PC是多功能设备,它们可以运行在它们所替换的终端上无法运行的、却能提高办公效率的应用程序。这种组合促使企业在更新或刷新应用程序时,开始倾向于客户端/服务器应用体系结构。中型客户端PC的发展仍在快速进行。一旦出现了更强大、存储容量更大的系统,企业就会使用它们,将更多的处理操作从数据中心昂贵的系统迁移到便宜的用户办公桌上。这时,用户界面和一些计算任务就迁移到了本地的PC上。这样大型机和小型计算机(现在成为服务器)就有了更长的使用寿命,从而降低了企业总体的计算成本。重型客户端随着PC变得越来越强大,更多的应用程序度可以从后端服务器迁移过来。在这里,除了数据和存储管理功能之外的所有功能都已迁移。进入互联网和万维网这时,公共互联网和万维网出现了。客户端/服务器计算的方式仍然在使用。为了降低总体成本,一些企业开始重新架构它们的分布式应用程序,便于使用标准的internet协议进行通信,并使用Web浏览器代替之前定制的用户界面功能。后来,一些应用程序的特性通过Javascript语言重新编写,这样它们就可以在客户端的计算机上本地执行。服务器的改进行业创新并不仅仅关注客户端侧的通信链路,对服务器也做了很大的改进。企业开始利用许多更小、更便宜的符合行业标准的服务器,通过它们强大的功能来支持部分或者全部原来基于大型机的功能。这样它们可以减少需要部署的昂贵主机系统的数量。接着,远程PC就可以和许多服务器通信,每个服务器都支持自己的应用程序组件。在此环境中使用了专用的数据库和文件服务器。之后,再将其他应用程序功能迁移到应用程序服务器。网络是另一个业界高度关注的领域。企业开始使用提供防火墙以及其他安全功能的专用网络服务器、文件缓存功能来加速应用程序的数据访问,电子邮件服务器、web服务器、web应用程序服务器、分布式命名服务器这些服务器跟踪和控制用户凭证,用于访问数据和应用程序。封装在设备服务器中的网络服务列表一直在增长。基于对象的开发PC和服务器功能的快速变化,加上处理能力、内存和网络这三者的价格的大幅下降,这些都对应用程序开发产生了重大影响。IT领域中最大的成本开销不再是硬件和软件,而是变成了通信、IT服务(员工)、电力以及冷却系统。软件开发、软件维护和IT操作出现了新的重要性,开发过程也发生了变化以迎合新的形势,即系统便宜,而人员、通信和电力越来越贵。图2:全球IT花费预测企业希望通过改进数据和应用程序架构来充分发挥员工的价值。其结果就是面向对象的应用程序和开发方法。许多编程语言,例如下面的语言,都支持这种方式:C++、C#、COBOL、Java、PHP、Python、Ruby在定义和记录数据结构时,应用程序开发者的编写变得更加系统化来适应变化。这种方式还使得维护和改进应用程序更加容易。开源软件Opensource.com为开源软件提供了以下定义:“开源软件是带有源代码的软件,任何人都可以检查、修改和增强代码。”“而有些软件的源代码只有创建它的个人、团队或组织才能修改——并且保有对它的独占控制。人们称这种软件为‘专有’或‘闭源’软件。”只有专有软件的原始作者才能合法地复制、检查和修改该软件。为了使用专有软件,计算机用户必须同意(通常通过接受首次运行该软件时显示的许可证),如果软件作者没有明确允许的话,他们不会对软件做任何的修改。微软Office和Adobe Photoshop都是专有软件的例子。虽然开源软件在计算机早期就已经存在,但直到20世纪90年代,当完整的开源操作系统、虚拟化技术、开发工具、数据库引擎和其他重要功能出现时,它才走到了前台。开源技术通常是基于web和分布式计算的关键组件。其中,以下类别的开源软件很受欢迎:开发工具应用支持数据库(flat文件,SQL,No-SQL,以及内存)分布式文件系统消息传输/队列操作系统聚类分布式计算强大的系统、快速的网络以及复杂软件可用性的结合,已经将主要的应用程序开发从单一转向了更加分布式的形式。然而企业已经意识到,有时候从头开始比尝试重构或分解旧的应用程序要更好。当企业进行创建分布式应用程序的工作时,常常会发现一些有趣的副产品。一个设计得当的应用程序,它已经分解成单独的功能或服务,可以由单独的团队并行开发。快速应用程序开发和部署(也称为DevOps)就是一种利用新环境的方法。面向服务的架构随着行业从客户端/服务器的计算模式,发展到更加分布式的方法,“面向服务的架构”一词出现了。这种方法基于分布式系统的概念、消息队列和交付中的标准,以及将XML消息传递作为共享数据和数据定义的标准方法。各个应用程序的功能被打包成面向网络的服务,这些服务接收一条消息,请求它们执行特定的服务,在它们执行服务之后,将响应发送回请求该服务的函数。这种方法还提供了另一个好处,即可以将给定的服务托管在网络的多个位置。这既提高了整体性能,又增强了可靠性。除此之外,现在还有很多工作负载管理工具,它用于接收服务请求、检查可用容量、将请求转发给具有最大可用容量的服务,然后将响应发送回请求者。如果特定的服务器没有及时响应,工作负载管理器会简单地向服务转发另一个实例。它还会将没有响应的服务标记为失败,并且在它收到一条表明服务仍在运行的消息之前,不会向它发送额外的请求。设计分布式系统的重要考虑因素现在我们已经走过了50多年的计算机历史,下面让我们来看看分布式系统开发人员的一些经验法则。需要考虑的东西很多,因为分布式解决方案可能有组件和服务在许多地方、不同类型的系统中运行,而且必须来回传递消息才能执行工作。要想成功创建这些解决方案,谨慎思考是必须的。除此之外还必须为所使用的每种主机系统、开发工具和消息传递系统提供专门的知识。确定需要做什么我们首先要考虑的事情,是我们究竟需要完成什么。虽然这听起来很简单,但却非常重要。令人惊讶的是,许多开发人员在知道具体需要什么之前就开始构建东西。通常情况下,这意味着他们构建了不必要的功能,浪费了时间。引用Yogi Berra的话就是:“如果你不知道自己要去哪里,你最终会去往别的地方”。首先需要知道要做什么,已经有哪些工具和服务可用,以及使用最终解决方案的人应该看到什么。交互和批处理快速响应和低延迟常常是我们的需求,因此比较明智的做法是考虑在用户等待时应该做什么,以及可以将什么放入批处理过程中,而这些批处理执行在事件驱动或时间驱动的计划中。在考虑了功能的初始分割之后,比较好的做法是计划何时需要执行后台、批处理进程、这些功能操作哪些数据、以及如何确保这些功能是可靠的、何时可用以及如何防止数据丢失。功能应该托管在哪里只有在详细计划了“完成什么”之后,才应该考虑“在哪里”以及“如何做”。开发人员有各自最喜欢的工具和方法,并且经常会调用它们,即使可能不是最佳的选择。Bernard Baruch说过:“如果你只有一把锤子,那么所有东西看起来都像钉子”。了解企业开发的企业标准也很重要。仅仅因为工具目前很流行就选择它是不明智的。这个工具可以完成这些工作,但是需要记住的是,它构建的所有东西都需要维护。如果你构建了一些只有自己才能理解或者维护的东西,那么在你职业生涯的剩下时间中,你可能已经把自己束缚在这一功能上了。我自己也有过这种经历,自认为自己创建的功能工作正常、轻量而且可靠。但在我离开那家公司后的十年里,我不断地收到关于这些功能的电话,因为后来的开发人员无法理解这些功能是如何实现的,而我写的文件又早就被丢掉了。在分布式解决方案中,每个功能或服务都应该分别考虑。该功能应该在企业数据中心?还是使用云服务提供商?还是两者兼有?另外还要考虑到在某些行业中存在法规要求,这些要求指导我们选择需要在何处以及如何维护和存储数据。其他需要考虑的东西还包括:该功能的主机应该是什么类型的系统。有没有系统架构更合适该功能?系统应该基于ARM、x86、SPARC、Precision、Power,还是大型机?会有某种特定的操作系统为该功能提供了更好的计算环境吗?Linux、Windows、UNIX、System I,或是System Z会是更好的平台吗?某特定的开发语言是否更适合该功能?它是一种特定类型的数据管理工具吗?该用Flat文件、SQL数据库还是No-SQL数据库?还是说非结构化的存储机制更好?功能应该托管在虚拟机中还是容器中,方便迁移、自动化以及编排吗?在本世纪初,运行Windows或Linux虚拟机往往是首选。虽然它们为方法提供了重要的隔离,并且在必要时很容易重启或移动他们,但是他们的处理、内存以及存储要求相当高。容器是处理虚拟化的另一种方式,它提供了类似的隔离级别,能够重新启动和迁移方法,而且消耗的处理能力、内存或存储都要小得多。性能性能是另一个重要的考虑因素。在定义组成解决方案的功能或服务时,开发人员应该注意它们是否有重要处理、内存或存储需求。仔细研究这些问题非常重要,这样才能知道是否可以进一步细分或分解这些功能。进一步的分割会允许并行处理的增加,这样能很大可能地提供性能上的改进。当然,这样做的代价是,它也增加了复杂性,可能会更加难以管理和保证安全。可靠性在高风险的企业环境中,解决方案的可靠性至关重要。开发人员必须考虑何时可以要求人们重新输入数据、重新运行功能,或者何时功能将不可用。数据库开发人员在20世纪60年代就遇到了这个问题,并开发了原子功能的概念。也就是说,功能必须完成或者部分的更新必须回滚,以使得数据处于功能开始前的状态。分布式系统也需要这种思维方式,确保即使在出现服务故障和事物中断的情况下也能保证数据完整性。例如,在关键消息传递系统中,在确认消息已经被接收方收到之前,消息必须被一直存储好。如果消息没能成功收到,则必须重新发送原始消息,并向系统管理报告故障。可管理性尽管没有核心应用程序功能那么有趣,但可管理性仍然是保证应用程序正常运转的关键因素。所有分布式功能都必须得到充分的检测,让管理员了解每个功能的当前状态,并在需要时更改功能的参数。毕竟,分布式系统是由比它们所替代的单片系统更多的活动部件组成的。开发人员必须时刻注意让这个分布式计算环境易于使用和维护。这给我们带来了一个绝对的要求,即必须对所有分布式功能进行充分的工具化,让管理员了解到它们的当前状态。毕竟,分布式系统本质上比它们所替代的单片系统更加复杂,并且有更多的活动部件。安全性确保分布式系统安全性,比单片环境中安全问题的难度高了一个数量级。每个功能都必须单独保密,功能之间的通信连接也必须保密。随着网络规模和复杂性的增长,开发人员必须考虑如何控制对功能的访问,如何确保只有授权用户才能访问这些功能,以及如何将服务与其他服务隔离开来。安全性是一个关键元素,必须添加到每个功能中而不是之后才加入。必须避免和汇报对功能和数据的未经授权访问。隐私性关于隐私性的话题有越来越多的规范。对与每个面向客户系统的开发人员来说,欧盟的GDPR以及美国的HIPPA法规都是重要的考虑因素。掌控复杂性开发人员必须花时间考虑如何将复杂计算环境中全部的内容组合在一起。服务应该被封装成一个单一的功能 ,或者少量紧密相关的功能,想要维护这样的规则非常困难。如果一个功能在多个地方实现,那么想要维护和更新就会很困难。当一个功能的实例没有更新会怎样?这个问题非常具有挑战性。这就意味着,对于复杂应用程序的开发人员来说,维护一个用于显示每个功能所在位置的可视化模型就非常有用了,这样,如果规则或业务需求发生变化,就可以对其进行更新。通常情况下,开发人员就必须花时间记录他们做了什么,什么时候做了更改,以及这些更改的目的是什么,这样其他人员就不必为了了解一个功能在哪里或者它是如何工作的而费心思去理解成堆的代码。要成为分布式系统的架构师,开发人员就必须要掌握复杂性。开发人员必须掌握的方法开发人员必须掌握分解和重构应用程序体系结构,从团队的角度思考问题,并提高他们在快速应用程序开发和部署(DevOps)方法方面的技能。毕竟,他们必须能够系统地思考哪些功能彼此独立,哪些功能依赖于其他功能的输出来工作。依赖于其他功能的这部分功能最好作为单个服务来实现。将它们作为独立的功能实现,可能会产生不必要的复杂性,导致应用程序性能低下,并且给网络带来不必要的负担。虚拟化技术涵盖了许多基础虚拟化是一个比虚拟机软件或容器更大的类别。这两个功能都被认为是虚拟化技术。在目前的应用程序中,至少有7种不同类型的虚拟化技术在使用。虚拟化技术可用于增强用户访问应用程序的方式、应用程序在何处以及如何执行、处理在何处以及如何执行、网络功能怎么样、数据在哪里以及如何存储、安全性如何实现以及管理功能如何实现。下面的虚拟化技术模型可能有助于开发人员理解虚拟化的概念。图3:虚拟化系统的架构从软件定义的解决方案角度考虑对于开发人员来说,从“软件定义的”解决方案的角度来考虑也是非常重要的。这也就是说,将控制从实际的处理中分割出来,这样功能就可以被自动化以及编排了。有哪些工具和策略可供使用当开发人员步入这个复杂的世界时,他们不应该觉得自己是独立的。供应商和开源社区提供了许多强大的工具。各种形式的虚拟化技术都可以成为开发人员最好的朋友。虚拟化技术是你最好的朋友容器让轻松地开发功能成为可能,这些功能可以在不互相干扰的情况下执行,并且可以根据工作负载需求从一个系统迁移到另一个系统。编排技术让控制多个功能成为可能,确保它们运行良好且可靠。它还可以在失败的情况下重启或移动它们。支持增量开发:功能可以并行开发,并在准备好时部署。它们还可以用新特性进行更新,而不需要在其他地方进行更改。支持高度分布式系统:既可以在企业数据中心本地部署功能,也可以在云服务提供商的数据中心远程部署功能。从服务的角度考虑这意味着开发人员必须考虑服务以及服务之间如何通信。定义良好的API定义良好的API可以让多个团队更好地协同工作,并且确保一切都能按照计划组合在一起。通常情况下这意味着要做更多的前期工作,但最终是非常值得的。为什么?因为整体开发可以更快。而且它还可以减少文档工作的工作量。支持快速应用程序开发这种方法对于快速应用程序开发和快速原型开发(即DevOps)来说也是完美的。如果执行得当,DevOps还可以只需很短的部署时间。从标准的角度思考分布式系统的开发人员应该充分考虑多供应商的国际标准,而不是单单依赖于一个供应商。这种方法避免了厂商的锁定,并且可以找到在不同领域最出彩的那个供应商。总 结值得注意的一点是,快速应用程序开发和分布式系统部署的指南,是从“慢慢来”开始的。最明智的做法就是先计划好你要去哪里,你要做什么,否则你很可能最终没能达成目标、开发预算耗尽并且毫无成果。

April 16, 2019 · 1 min · jiezi

分布式系统关注点——先写DB还是「缓存」?

如果第二次看到我的文章,欢迎文末扫码订阅我个人的公众号(跨界架构师)哟~ 本文长度为4209字,建议阅读12分钟。坚持原创,每一篇都是用心之作~在前一篇《360°全方位解读「缓存」》中,我们聊了运用缓存的三种思路,以及在一个完整的系统中可以设立缓存的几个位置,并且分享了关于浏览器缓存、CDN缓存、网关(代理)缓存的一些使用经验。这次Z哥将深入到实际场景中,来看一下「进程内缓存」、「进程外缓存」运用时的一些最佳实践。由于篇幅原因,这次先聊三个问题。首当其冲的就是“先写DB还是缓存?”。我想,只要你开始运用缓存,这会是你第一个要好好思考的问题,否则在前方等待你的就是灾难。。。先写DB还是缓存?一个程序可以没有缓存,但是一定要有数据库。这是大家的普遍观点,所以数据库的重要性在你的潜意识里总是被放在了第一位。先DB再缓存如果不细想的话你可能会觉得,数据库操作失败了,自然缓存也不用操作了;数据库操作成功了,再操作缓存,没毛病。但是数据库操作成功,缓存操作的失败的情况该怎么解?(主要在用到redis,memcached这种进程外缓存的时候,由于网络因素,失败的可能性大增)办法也是有的,在操作数据库的时候带一个事务,如果缓存操作失败则事务回滚。大致的代码意思如下:begin trans var isDbSuccess = write db; if(isDbSuccess){ var isCacheSuccess = write cache; if(isCacheSuccess){ return success; } else{ rollback db; return fail; } } else{ return fail; } catch(Exception ex){ rollback db; }end trans如此一来就万无一失了吗?并不是。除了由于事务的引入,增加了数据库的压力之外,在极端场景下可能会出现rollback db失败的情况。是不是很头疼?解决这个问题的方式就是write cache的时候做delete操作,而不是set操作。如此一来,用多一次cache miss的代价来换rollback db失败的问题。就像图上所示,哪怕rollback失败了,通过一次cache miss重新从db中载入旧值。题外话:其实这种做法有一种专业的叫法——Cache Aside Pattern。为了便于记忆,你可以和分布式系统的CAP定理同时记忆,叫「缓存的CAP模式」。是不是看上去妥了?可以开始潇洒了?▲图片来源于网络,版权归原作者所有如果你的数据库没有做高可用的话,的确可以妥了。但是如果数据库做了高可用,就会涉及到主从数据库的数据同步,这就有新问题了。题外话:所以大家不要过度追求技术的酷炫,可能会得不偿失,自找麻烦。什么问题呢?就是如果在数据还未同步到「从库」的时候,由于cache miss去「从库」取到了未同步前的旧值。解决它的第一个方式很简单,也很粗暴。就是定时去「从库」读数据,发现数据和缓存不一样了就set到缓存里去。但是这个方式有点“治标不治本”。不断的从数据库定时读取,对资源的消耗大不说,这个间隔频率也不好定义一个比较合适的统一标准,太短吧,会导致重复读取的次数加大,太长吧,又会导致缓存和数据库不一致的时间变长。所以这个方案仅适用于项目中只有2、3处需要做这种处理的场景,并且还不能是数据会频繁修改的情况。因为在数据修改频次较高的场景,甚至可能还会出现这个定时机制所消耗的资源反而大于主程序的情况。一般情况下,另一种更普适性的方案是采用接下去聊的这种更底层的方式进行,就是“哪里有问题处理哪里”,当「从库」完成同步的时候再额外做一次delete cache或者set cache的操作。如此,虽说也没有100%解决短暂的数据不一致问题,但是已经将脏数据所存在的时长降到了最低(最终由主从同步的耗时决定),并且大大减少了无谓的资源消耗。可能你会说,“不行,这么一点时间也不能忍”怎么办?办法是有,但是会增加「主库」的压力。就是在产生数据库写入动作后的一小段时间内强制读「主库」来加载缓存。怎么实现呢?先得依赖一个共享存储,可以借助数据库或者也可以是我们现在正在聊的分布式缓存。然后,你在事务提交之后往共享存储中临时存一个{ key = dbname + tablename + id,value = null,expire = 3s }这样的数据,并且再做一次delete cache的操作。begin trans var isDbSuccess = write db; if(isDbSuccess){ var isCacheSuccess = delete cache; if(isCacheSuccess){ return success; } else{ rollback db; return fail; } } else{ return fail; } catch(Exception ex){ rollback db; }end trans//在这里做这个临时存储,{key,value,expire}。delete cache;如此一来,当「读数据」的时候发生cache miss,先判断是否存在这个临时数据,只要在3秒内就会强制走「主库」取数据。可以看到,不同的方案各有利弊,需要根据具体的场景仔细权衡。先缓存再DB你工作中的大部分场景对数据准确性肯定是低容忍的,所以一般不建议选择「先缓存再DB」的方案,因为内存是易失性的。一旦遇到操作缓存成功,操作DB失败的情况,问题就来了。在这个时候最新的数据只有缓存里有,怎么办?单独起个线程不断的重试往数据库写?这个方案在一定程度上可行,但不适合用于对数据准确性有高要求的场景,因为缓存一旦挂了,数据就丢了!题外话:哪怕选择了这个方案,重试线程应确保只有1个,否则会存在“ABBA”的「并发写」问题。可能你会说用delete cache不就没问题了?可以是可以,但是要有个前提条件,访问缓存的程序不会产生并发。因为只要你的程序是多线程运行的,一旦出现并发就有可能出现「读」的线程由于cache miss从数据库取的时候,「写」的线程还没将数据写到数据库的情况。所以,哪怕用delete cache的方式,要么带lock(多客户端情况下还得上分布式锁),要么必然出现数据不一致。值得注意的是,如果数据库同样做了高可用,哪怕带了lock,也还需要考虑和上面提到的「先DB再缓存」中一样的由于主从同步的时间差可能会产生的问题。当然了,「先缓存再DB」也不是一文不值。当对写入速度有极致要求,而对数据准确性没那么高要求的场景下就非常好使,其实就是前一篇(《360°全方位解读「缓存」》)提到的「延迟写」机制。小结一下,相比缓存来说,数据库的「高可用」一般会在系统发展的后期才会引入,所以在没有引入数据库「高可用」的情况下,Z哥建议你使用「先DB再缓存」的方式,并且缓存操作用delete而不是set,这样基本就可以高枕无忧了。但是如果数据库做了「高可用」,那么团队必然也形成一定规模了,这个时候就老老实实的做数据库变更记录(binlog)的订阅吧。到这里可能有的小伙伴要问了,“如果上了分布式缓存,还需要本地缓存吗?”。本地缓存还要不要?在解答这个问题之前我们先来思考一个问题,一个分布式系统最重要的价值是什么?是「无限扩展」,只要堆硬件就能应对业务增长。要达到这点的背后需要满足一个特性,就是程序要「无状态」。那么既想引入缓存来加速,又要达到「无状态」,靠的就是分布式缓存。所以,能用分布式缓存解决的问题就尽量不要引入本地缓存。否则引入分布式缓存的作用就小了很多。但是在少数场景下,本地缓存还是可以发挥其价值的,但是我们需要仔细识别出来。主要是三个场景:不经常变更的数据。(比如一天甚至好几天更新一次的那种)需要支撑非常高的并发。(比如秒杀)对数据准确性能容忍的场景。(比如浏览量,评论数等)不过,我还是建议你,除了第二种场景,否则还是尽量不要引入本地缓存。原因我们下面来说说。其实这个原因的根本问题就是在引入了本地缓存后,本地缓存(进程内缓存)、分布式缓存(进程外缓存)、数据库这三者之间的数据一致性该怎么进行呢?本地缓存、分布式缓存、db之间的数据一致性如果是个单点应用程序的话,很简单,将本地缓存的操作放在最后就好了。可能你会说本地缓存修改失败怎么办?比如重复key啊什么的异常。那你可以反思一下为这种数据为什么可以成功的写进数据库。。。但是,本地缓存带来的一个巨大问题就是:虽然一个节点没问题,但是多个本地缓存节点之间的数据如何同步?解决这个问题的方式中有两种和之前我们聊过的Session问题(《做了「负载均衡」就可以随便加机器了吗?》)是类似的。要么是由接收修改的节点通知其它节点变更(通过rpc或者mq皆可),要么借助一致性hash让同一个来源的请求固定落到一个节点上。后者可以让不同节点上的本地缓存数据都不重复,从源头上避免了这个问题。但是这两个方案走的都是极端,前者变更成本太高,比如需要通知上千个节点的话,这个成本难以接受。而后者的话对资源的消耗太高,而且还容易出现压力分摊不均匀的问题。所以,一般系统规模小的时候可以考虑前者,而规模越大越会选择后者。还有一种相对中庸一些的,以降低数据的准确性来换成本的方案。就是设置缓存定时过期或者定时往下游的分布式缓存拉取最新数据。这和前面「先DB再缓存」中提到的定时机制是一样的逻辑,胜在简单,缺点就是会存在更长时间的数据不一致。小结一下,本地缓存的数据一致性解决方案,能彻底解决的是借助一致性hash的方案,但是成本比较高。所以,如非必要还是慎重决定要不要做本地缓存。总结好了,我们一起总结一下。这次呢,Z哥先花了大量的篇幅和你讨论「先写DB还是缓存」的问题,并且带你层层深入,通过一点一点的演进来阐述不同的解决方案。然后与你讨论了「本地缓存」的意义以及如何在「分布式缓存」和「数据库」的基础上做好数据一致性,这其中主要是多个本地缓存节点之间的数据同步问题。希望对你有所启发。这次的缓存实践是一个非常好的例子,从中我们可以看到一件事情的精细化所带来的复杂度需要更加的精细化去解决,但是又会带来新的复杂度。所以作为技术人的你,需要无时无刻考虑该怎么权衡,而不是人云亦云。相关文章:分布式系统关注点——360°全方位解读「缓存」分布式系统关注点——做了「负载均衡」就可以随便加机器了吗?作者:Zachary出处:https://www.cnblogs.com/Zacha…如果你喜欢这篇文章,可以点一下文末的「赞」。这样可以给我一点反馈。: )谢谢你的举手之劳。▶关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描下方的二维码~。定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。 ...

April 11, 2019 · 1 min · jiezi

分布式系统:一致性模型

分布式系统中一个重要的问题就是数据复制,数据复制一般是为了增强系统的可用性或提高性能。而实现数据复制的一个主要难题就是保持各个副本的一致性。本文首先讨论数据复制的场景中一致性模型如此重要的原因,然后讨论一致性模型的含义,最后分析常用的一致性模型。为什么需要一致性模型数据复制主要的目的有两个:可用性和性能。首先数据复制可以提高系统的可用性。在保持多副本的情况,有一个副本不可用,系统切换到其他副本就会恢复。常用的 MySQL 主备同步方案就是一个典型的例子。另一方面,数据复制能够提供系统的性能。当分布式系统需要在服务器数量和地理区域上进行扩展时,数据复制是一个相当重要的手段。有了多个数据副本,就能将请求分流;在多个区域提供服务时,也能通过就近原则提高客户端访问数据的效率。常用的 CDN 技术就是一个典型的例子。但是数据复制是要付出代价的。数据复制带来了多副本数据一致性的问题。一个副本的数据更新之后,其他副本必须要保持同步,否则数据不一致就可能导致业务出现问题。因此,每次更新数据对所有副本进行修改的时间以及方式决定了复制代价的大小。全局同步与性能实际上是矛盾的,而为了提高性能,往往会采用放宽一致性要求的方法。因此,我们需要用一致性模型来理解和推理在分布式系统中数据复制需要考虑的问题和基本假设。什么是一致性模型首先我们要定义一下一致性模型的术语:1. 数据存储:在分布式系统中指分布式共享数据库、分布式文件系统等。2. 读写操作:更改数据的操作称为写操作(包括新增、修改、删除),其他操作称为读操作。下面是一致性模型的定义:一致性模型本质上是进程与数据存储的约定:如果进程遵循某些规则,那么进程对数据的读写操作都是可预期的。上面的定义可能比较抽象,我们用常见的强一致性模型来通俗的解释一下:在线性一致性模型中,进程对一个数据项的读操作,它期待数据存储返回的是该数据在最后一次写操作之后的结果。这在单机系统里面很容易实现,在 MySQL 中只要使用加锁读的方式就能保证读取到数据在最后一次写操作之后的结果。但在分布式系统中,因为没有全局时钟,导致要精确定义哪次写操作是最后一次写操作是非常困难的事情,因此产生了一系列的一致性模型。每种模型都有效限制了在对一个数据项执行读操作所应该返回的值。举个例子:假设记录值 X 在节点 M 和 N 上都有副本,当客户端 A 修改了副本 M 上 X 的值,一段时间之后,客户端 B 从 N 上读取 X 的值,此时一致性模型会决定客户端 B 是否能够读取到 A 写入的值。一致性模型主要可以分为两类:能够保证所有进程对数据的读写顺序都保持一致的一致性模型称为强一致性模型,而不能保证的一致性模型称为弱一致性模型。强一致性模型线性一致性(Linearizable Consistency)线性一致性也叫严格一致性(Strict Consistency)或者原子一致性(Atomic Consistency),它的条件是:1. 任何一次读都能读取到某个数据最近的一次写的数据。2. 所有进程看到的操作顺序都跟全局时钟下的顺序一致。线性一致性是对一致性要求最高的一致性模型,就现有技术是不可能实现的。因为它要求所有操作都实时同步,在分布式系统中要做到全局完全一致时钟现有技术是做不到的。首先通信是必然有延迟的,一旦有延迟,时钟的同步就没法做到一致。当然不排除以后新的技术能够做到,但目前而言线性一致性是无法实现的。顺序一致性(Sequential Consistency)顺序一致性是 Lamport(1979)在解决多处理器系统共享存储器时首次提出来的。参考我之前写的文章《分布式系统:Lamport 逻辑时钟》。它的条件是:任何一次读写操作都是按照某种特定的顺序。所有进程看到的读写操作顺序都保持一致。首先我们先来分析一下线性一致性和顺序一致性的相同点在哪里。他们都能够保证所有进程对数据的读写顺序保持一致。线性一致性的实现很简单,就按照全局时钟(可以简单理解为物理时钟)为参考系,所有进程都按照全局时钟的时间戳来区分事件的先后,那么必然所有进程看到的数据读写操作顺序一定是一样的,因为它们的参考系是一样的。而顺序一致性使用的是逻辑时钟来作为分布式系统中的全局时钟,进而所有进程也有了一个统一的参考系对读写操作进行排序,因此所有进程看到的数据读写操作顺序也是一样的。那么线性一致性和顺序一致性的区别在哪里呢?通过上面的分析可以发现,顺序一致性虽然通过逻辑时钟保证所有进程保持一致的读写操作顺序,但这些读写操作的顺序跟实际上发生的顺序并不一定一致。而线性一致性是严格保证跟实际发生的顺序一致的。弱一致性模型因果一致性(Causal Consistency)因果一致性是一种弱化的顺序一致性模型,因为它将具有潜在因果关系的事件和没有因果关系的事件区分开了。那么什么是因果关系?如果事件 B 是由事件 A 引起的或者受事件 A 的影响,那么这两个事件就具有因果关系。举个分布式数据库的示例,假设进程 P1 对数据项 x 进行了写操作,然后进程 P2 先读取了 x,然后对 y 进行了写操作,那么对 x 的读操作和对 y 的写操作就具有潜在的因果关系,因为 y 的计算可能依赖于 P2 读取到 x 的值(也就是 P1 写的值)。另一方面,如果两个进程同时对两个不同的数据项进行写操作,那么这两个事件就不具备因果关系。无因果关系的操作称为并发操作。这里只是简单陈述了一下,深入的分析见我之前写的文章《分布式系统:向量时钟》。因果一致性的条件包括:1. 所有进程必须以相同的顺序看到具有因果关系的读写操作。2. 不同进程可以以不同的顺序看到并发的读写操作。下面我们来分析一下为什么说因果一致性是一种弱化的顺序一致性模型。顺序一致性虽然不保证事件发生的顺序跟实际发生的保持一致,但是它能够保证所有进程看到的读写操作顺序是一样的。而因果一致性更进一步弱化了顺序一致性中对读写操作顺序的约束,仅保证有因果关系的读写操作有序,没有因果关系的读写操作(并发事件)则不做保证。也就是说如果是无因果关系的数据操作不同进程看到的值是有可能是不一样,而有因果关系的数据操作不同进程看到的值保证是一样的。最终一致性(Eventual Consistency)最终一致性是更加弱化的一致性模型,因果一致性起码还保证了有因果关系的数据不同进程读取到的值保证是一样的,而最终一致性只保证所有副本的数据最终在某个时刻会保持一致。从某种意义上讲,最终一致性保证的数据在某个时刻会最终保持一致就像是在说:“人总有一天会死”一样。实际上我们更加关心的是:1. “最终”到底是多久?通常来说,实际运行的系统需要能够保证提供一个有下限的时间范围。2. 多副本之间对数据更新采用什么样的策略?一段时间内可能数据可能多次更新,到底以哪个数据为准?一个常用的数据更新策略就是以时间戳最新的数据为准。由于最终一致性对数据一致性的要求比较低,在对性能要求高的场景中是经常使用的一致性模型。以客户端为中心的一致性(Client-centric Consistency)前面我们讨论的一致性模型都是针对数据存储的多副本之间如何做到一致性,考虑这么一种场景:在最终一致性的模型中,如果客户端在数据不同步的时间窗口内访问不同的副本的同一个数据,会出现读取同一个数据却得到不同的值的情况。为了解决这个问题,有人提出了以客户端为中心的一致性模型。以客户端为中心的一致性为单一客户端提供一致性保证,保证该客户端对数据存储的访问的一致性,但是它不为不同客户端的并发访问提供任何一致性保证。举个例子:客户端 A 在副本 M 上读取 x 的最新值为 1,假设副本 M 挂了,客户端 A 连接到副本 N 上,此时副本 N 上面的 x 值为旧版本的 0,那么一致性模型会保证客户端 A 读取到的 x 的值为 1,而不是旧版本的 0。一种可行的方案就是给数据 x 加版本标记,同时客户端 A 会缓存 x 的值,通过比较版本来识别数据的新旧,保证客户端不会读取到旧的值。以客户端为中心的一致性包含了四种子模型:1. 单调读一致性(Monotonic-read Consistency):如果一个进程读取数据项 x 的值,那么该进程对于 x 后续的所有读操作要么读取到第一次读取的值要么读取到更新的值。即保证客户端不会读取到旧值。2. 单调写一致性(Monotonic-write Consistency):一个进程对数据项 x 的写操作必须在该进程对 x 执行任何后续写操作之前完成。即保证客户端的写操作是串行的。3. 读写一致性(Read-your-writes Consistency):一个进程对数据项 x 执行一次写操作的结果总是会被该进程对 x 执行的后续读操作看见。即保证客户端能读到自己最新写入的值。4. 写读一致性(Writes-follow-reads Consistency):同一个进程对数据项 x 执行的读操作之后的写操作,保证发生在与 x 读取值相同或比之更新的值上。即保证客户端对一个数据项的写操作是基于该客户端最新读取的值。总结数据复制导致了一致性的问题,为了保持副本的一致性可能会严重地影响性能,唯一的解决办法就是放松一致性的要求。通过一致性模型我们可以理解和推理在分布式系统中数据复制需要考虑的问题和基本假设,便于结合具体的业务场景做权衡。每种模型都有效地限制了对一个数据项执行度操作应返回的值。通常来说限制越少的模型越容易应用,但一致性的保证就越弱。参考资料《分布式系统原理与范型》Distributed systems for fun and profitConsistency_model本文作者:肖汉松阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 13, 2019 · 1 min · jiezi

一致性协议浅析:从逻辑时钟到Raft

前言春节在家闲着没事看了几篇论文,把一致性协议的几篇论文都过了一遍。在看这些论文之前,我一直有一些疑惑,比如同样是有Leader和两阶段提交,Zookeeper的ZAB协议和Raft有什么不同,Paxos协议到底要怎样才能用在实际工程中,这些问题我都在这些论文中找到了答案。接下来,我将尝试以自己的语言给大家讲讲这些协议,使大家能够理解这些算法。同时,我自己也有些疑问,我会在我的阐述中提出,也欢迎大家一起讨论。水平有限,文中难免会有一些纰漏门也欢迎大家指出。逻辑时钟逻辑时钟其实算不上是一个一致性协议,它是Lamport大神在1987年就提出来的一个想法,用来解决分布式系统中,不同的机器时钟不一致可能带来的问题。在单机系统中,我们用机器的时间来标识事件,就可以非常清晰地知道两个不同事件的发生次序。但是在分布式系统中,由于每台机器的时间可能存在误差,无法通过物理时钟来准确分辨两个事件发生的先后顺序。但实际上,在分布式系统中,只有两个发生关联的事件,我们才会去关心两者的先来后到关系。比如说两个事务,一个修改了rowa,一个修改了rowb,他们两个谁先发生,谁后发生,其实我们并不关心。那所谓逻辑时钟,就是用来定义两个关联事件的发生次序,即‘happens before’。而对于不关联的事件,逻辑时钟并不能决定其先后,所以说这种‘happens before’的关系,是一种偏序关系。图和例子来自于这篇博客此图中,箭头表示进程间通讯,ABC分别代表分布式系统中的三个进程。逻辑时钟的算法其实很简单:每个事件对应一个Lamport时间戳,初始值为0如果事件在节点内发生,时间戳加1如果事件属于发送事件,时间戳加1并在消息中带上该时间戳如果事件属于接收事件,时间戳 = Max(本地时间戳,消息中的时间戳) + 1这样,所有关联的发送接收事件,我们都能保证发送事件的时间戳小于接收事件。如果两个事件之间没有关联,比如说A3和B5,他们的逻辑时间一样。正是由于他们没有关系,我们可以随意约定他们之间的发生顺序。比如说我们规定,当Lamport时间戳一样时,A进程的事件发生早于B进程早于C进程,这样我们可以得出A3 ‘happens before’ B5。而实际在物理世界中,明显B5是要早于A3发生的,但这都没有关系。逻辑时钟貌似目前并没有被广泛的应用,除了DynamoDB使用了vector clock来解决多版本的先后问题(如果有其他实际应用的话请指出,可能是我孤陋寡闻了),Google的Spanner 也是采用物理的原子时钟来解决时钟问题。但是从Larmport大师的逻辑时钟算法上,已经可以看到一些一致性协议的影子。Replicated State Machine说到一致性协议,我们通常就会讲到复制状态机。因为通常我们会用复制状态机加上一致性协议算法来解决分布式系统中的高可用和容错。许多分布式系统,都是采用复制状态机来进行副本之间的数据同步,比如HDFS,Chubby和Zookeeper。所谓复制状态机,就是在分布式系统的每一个实例副本中,都维持一个持久化的日志,然后用一定的一致性协议算法,保证每个实例的这个log都完全保持一致,这样,实例内部的状态机按照日志的顺序回放日志中的每一条命令,这样客户端来读时,在每个副本上都能读到一样的数据。复制状态机的核心就是图中 的Consensus模块,即今天我们要讨论的Paxos,ZAB,Raft等一致性协议算法。PaxosPaxos是Lamport大神在90年代提出的一致性协议算法,大家一直都觉得难懂,所以Lamport在2001又发表了一篇新的论文《Paxos made simple》,在文中他自己说Paxos是世界上最简单的一致性算法,非常容易懂……但是业界还是一致认为Paxos比较难以理解。在我看过Lamport大神的论文后,我觉得,除去复杂的正确性论证过程,Paxos协议本身还是比较好理解的。但是,Paxos协议还是过于理论,离具体的工程实践还有太远的距离。我一开始看Paxos协议的时候也是一头雾水,看来看去发现Paxos协议只是为了单次事件答成一致,而且答成一致后的值无法再被修改,怎么用Paxos去实现复制状态机呢?另外,Paxos协议答成一致的值只有Propose和部分follower知道,这协议到底怎么用……但是,如果你只是把Paxos协议当做一个理论去看,而不是考虑实际工程上会遇到什么问题的话,会容易理解的多。Lamport的论文中对StateMachine的应用只有一个大概的想法,并没有具体的实现逻辑,想要直接把Paxos放到复制状态机里使用是不可能的,得在Paxos上补充很多的东西。这些是为什么Paxos有这么多的变种。Basic-PaxosBasic-Paxos即Lamport最初提出的Paxos算法,其实很简单,用三言两语就可以讲完,下面我尝试着用我自己的语言描述下Paxos协议,然后会举出一个例子。要理解Paxos,只要记住一点就好了,Paxos只能为一个值形成共识,一旦Propose被确定,之后值永远不会变,也就是说整个Paxos Group只会接受一个提案(或者说接受多个提案,但这些提案的值都一样)。至于怎么才能接受多个值来形成复制状态机,大家可以看下一节Multi-Paxos.Paxos协议中是没有Leader这个概念的,除去Learner(只是学习Propose的结果,我们可以不去讨论这个角色),只有Proposer和Acceptor。Paxos并且允许多个Proposer同时提案。Proposer要提出一个值让所有Acceptor答成一个共识。首先是Prepare阶段,Proposer会给出一个ProposeID n(注意,此阶段Proposer不会把值传给Acceptor)给每个Acceptor,如果某个Acceptor发现自己从来没有接收过大于等于n的Proposer,则会回复Proposer,同时承诺不再接收ProposeID小于等于n的提议的Prepare。如果这个Acceptor已经承诺过比n更大的propose,则不会回复Proposer。如果Acceptor之前已经Accept了(完成了第二个阶段)一个小于n的Propose,则会把这个Propose的值返回给Propose,否则会返回一个null值。当Proposer收到大于半数的Acceptor的回复后,就可以开始第二阶段accept阶段。但是这个阶段Propose能够提出的值是受限的,只有它收到的回复中不含有之前Propose的值,他才能自由提出一个新的value,否则只能是用回复中Propose最大的值做为提议的值。Proposer用这个值和ProposeID n对每个Acceptor发起Accept请求。也就是说就算Proposer之前已经得到过acceptor的承诺,但是在accept发起之前,Acceptor可能给了proposeID更高的Propose承诺,导致accept失败。也就是说由于有多个Proposer的存在,虽然第一阶段成功,第二阶段仍然可能会被拒绝掉。下面我举一个例子,这个例子来源于这篇博客假设有Server1,Server2, Server3三个服务器,他们都想通过Paxos协议,让所有人答成一致他们是leader,这些Server都是Proposer角色,他们的提案的值就是他们自己server的名字。他们要获取Acceptor1~3这三个成员同意。首先Server2发起一个提案【1】,也就是说ProposeID为1,接下来Server1发起来一个提案【2】,Server3发起一个提案【3】.首先是Prepare阶段:假设这时Server1发送的消息先到达acceptor1和acceptor2,它们都没有接收过请求,所以接收该请求并返回【2,null】给Server1,同时承诺不再接受编号小于2的请求; 紧接着,Server2的消息到达acceptor2和acceptor3,acceptor3没有接受过请求,所以返回proposer2 【1,null】,并承诺不再接受编号小于1的消息。而acceptor2已经接受Server1的请求并承诺不再接收编号小于2的请求,所以acceptor2拒绝Server2的请求; 最后,Server3的消息到达acceptor2和acceptor3,它们都接受过提议,但编号3的消息大于acceptor2已接受的2和acceptor3已接受的1,所以他们都接受该提议,并返回Server3 【3,null】; 此时,Server2没有收到过半的回复,所以重新取得编号4,并发送给acceptor2和acceptor3,此时编号4大于它们已接受的提案编号3,所以接受该提案,并返回Server2 【4,null】。接下来进入Accept阶段,Server3收到半数以上(2个)的回复,并且返回的value为null,所以,Server3提交了【3,server3】的提案。 Server1在Prepare阶段也收到过半回复,返回的value为null,所以Server1提交了【2,server1】的提案。 Server2也收到过半回复,返回的value为null,所以Server2提交了【4,server2】的提案。 Acceptor1和acceptor2接收到Server1的提案【2,server1】,acceptor1通过该请求,acceptor2承诺不再接受编号小于4的提案,所以拒绝; Acceptor2和acceptor3接收到Server2的提案【4,server2】,都通过该提案; Acceptor2和acceptor3接收到Server3的提案【3,server3】,它们都承诺不再接受编号小于4的提案,所以都拒绝。 此时,过半的acceptor(acceptor2和acceptor3)都接受了提案【4,server2】,learner感知到提案的通过,learner开始学习提案,所以server2成为最终的leader。Multi-Paxos刚才我讲了,Paxos还过于理论,无法直接用到复制状态机中,总的来说,有以下几个原因Paxos只能确定一个值,无法用做Log的连续复制由于有多个Proposer,可能会出现活锁,如我在上面举的例子中,Server2的一共提了两次Propose才最终让提案通过,极端情况下,次数可能会更多提案的最终结果可能只有部分Acceptor知晓,没法达到复制状态机每个instance都必须有完全一致log的需求。那么其实Multi-Paxos,其实就是为了解决上述三个问题,使Paxos协议能够实际使用在状态机中。解决第一个问题其实很简单。为Log Entry每个index的值都是用一个独立的Paxos instance。解决第二个问题也很简答,让一个Paxos group中不要有多个Proposer,在写入时先用Paxos协议选出一个leader(如我上面的例子),然后之后只由这个leader做写入,就可以避免活锁问题。并且,有了单一的leader之后,我们还可以省略掉大部分的prepare过程。只需要在leader当选后做一次prepare,所有Acceptor都没有接受过其他Leader的prepare请求,那每次写入,都可以直接进行Accept,除非有Acceptor拒绝,这说明有新的leader在写入。为了解决第三个问题,Multi-Paxos给每个Server引入了一个firstUnchosenIndex,让leader能够向向每个Acceptor同步被选中的值。解决这些问题之后Paxos就可以用于实际工程了。Paxos到目前已经有了很多的补充和变种,实际上,之后我要讨论的ZAB也好,Raft也好,都可以看做是对Paxos的修改和变种,另外还有一句流传甚广的话,“世上只有一种一致性算法,那就是Paxos”。ZABZAB即Zookeeper Atomic BoardCast,是Zookeeper中使用的一致性协议。ZAB是Zookeeper的专用协议,与Zookeeper强绑定,并没有抽离成独立的库,因此它的应用也不是很广泛,仅限于Zookeeper。但ZAB协议的论文中对ZAB协议进行了详细的证明,证明ZAB协议是能够严格满足一致性要求的。ZAB随着Zookeeper诞生于2007年,此时Raft协议还没有发明,根据ZAB的论文,之所以Zookeeper没有直接使用Paxos而是自己造轮子,是因为他们认为Paxos并不能满足他们的要求。比如Paxos允许多个proposer,可能会造成客户端提交的多个命令没法按照FIFO次序执行。同时在恢复过程中,有一些follower的数据不全。这些断论都是基于最原始的Paxos协议的,实际上后来一些Paxos的变种,比如Multi-Paxos已经解决了这些问题。当然我们只能站在历史的角度去看待这个问题,由于当时的Paxos并不能很好的解决这些问题,因此Zookeeper的开发者创造了一个新的一致性协议ZAB。ZAB其实和后来的Raft非常像,有选主过程,有恢复过程,写入也是两阶段提交,先从leader发起一轮投票,获得超过半数同意后,再发起一次commit。ZAB中每个主的epoch number其实就相当于我接下来要讲的Raft中的term。只不过ZAB中把这个epoch number和transition number组成了一个zxid存在了每个entry中。ZAB在做log复制时,两阶段提交时,一个阶段是投票阶段,只要收到过半数的同意票就可以,这个阶段并不会真正把数据传输给follower,实际作用是保证当时有超过半数的机器是没有挂掉,或者在同一个网络分区里的。第二个阶段commit,才会把数据传输给每个follower,每个follower(包括leader)再把数据追加到log里,这次写操作就算完成。如果第一个阶段投票成功,第二个阶段有follower挂掉,也没有关系,重启后leader也会保证follower数据和leader对其。如果commit阶段leader挂掉,如果这次写操作已经在至少一个follower上commit了,那这个follower一定会被选为leader,因为他的zxid是最大的,那么他选为leader后,会让所有follower都commit这条消息。如果leader挂时没有follower commit这条消息,那么这个写入就当做没写完。由于只有在commit的时候才需要追加写日志,因此ZAB的log,只需要append-only的能力,就可以了。另外,ZAB支持在从replica里做stale read,如果要做强一致的读,可以用sync read,原理也是先发起一次虚拟的写操作,到不做任何写入,等这个操作完成后,本地也commit了这次sync操作,再在本地replica上读,能够保证读到sync这个时间点前所有的正确数据,而Raft所有的读和写都是经过主节点的RaftRaft是斯坦福大学在2014年提出的一种新的一致性协议。作者表示之所以要设计一种全新的一致性协议,是因为Paxos实在太难理解,而且Paxos只是一个理论,离实际的工程实现还有很远的路。因此作者狠狠地吐槽了Paxos一把:Paxos协议中,是不需要Leader的,每个Proposer都可以提出一个propose。相比Raft这种一开始设计时就把选主和协议达成一致分开相比,Paxos等于是把选主和propose阶段杂糅在了一起,造成Paxos比较难以理解。最原始的Paxos协议只是对单一的一次事件答成一致,一旦这个值被确定,就无法被更改,而在我们的现实生活中,包括我们数据库的一致性,都需要连续地对log entry的值答成一致,所以单单理解Paxos协议本身是不够的,我们还需要对Paxos协议进行改进和补充,才能真正把Paxos协议应用到工程中。而对Paxos协议的补充本身又非常复杂,而且虽然Paxos协议被Lamport证明过,而添加了这些补充后,这些基于Paxos的改进算法,如Multi-Paxos,又是未经证明的。第三个槽点是Paxos协议只提供了一个非常粗略的描述,导致后续每一个对Paxos的改进,以及使用Paxos的工程,如Google的Chubby,都是自己实现了一套工程来解决Paxos中的一些具体问题。而像Chubby的实现细节其实并没有公开。也就是说要想在自己的工程中使用Paxos,基本上每个人都需要自己定制和实现一套适合自己的Paxos协议。因此,Raft的作者在设计Raft的时候,有一个非常明确的目标,就是让这个协议能够更好的理解,在设计Raft的过程中,如果遇到有多种方案可以选择的,就选择更加容易理解的那个。作者举了一个例子。在Raft的选主阶段,本来可以给每个server附上一个id,大家都去投id最大的那个server做leader,会更快地达成一致(类似ZAB协议),但这个方案又增加了一个serverid的概念,同时在高id的server挂掉时,低id的server要想成为主必须有一个等待时间,影响可用性。因此Raft的选主使用了一个非常简单的方案:每个server都随机sleep一段时间,最早醒过来的server来发起一次投票,获取了大多数投票即可为主。在通常的网络环境下,最早发起投票的server也会最早收到其他server的赞成票,因此基本上只需要一轮投票就可以决出leader。整个选主过程非常简单明了。除了选主,整个Raft协议的设计都非常简单。leader和follower之间的交互(如果不考虑snapshot和改变成员数量)一共只有2个RPC call。其中一个还是选主时才需要的RequestVote。也就是说所有的数据交互,都只由AppendEntries 这一个RPC完成。理解Raft算法,首先要理解Term这个概念。每个leader都有自己的Term,而且这个term会带到log的每个entry中去,来代表这个entry是哪个leader term时期写入的。另外Term相当于一个lease。如果在规定的时间内leader没有发送心跳(心跳也是AppendEntries这个RPC call),Follower就会认为leader已经挂掉,会把自己收到过的最高的Term加上1做为新的term去发起一轮选举。如果参选人的term还没自己的高的话,follower会投反对票,保证选出来的新leader的term是最高的。如果在time out周期内没人获得足够的选票(这是有可能的),则follower会在term上再加上1去做新的投票请求,直到选出leader为止。最初的raft是用c语言实现的,这个timeout时间可以设置的非常短,通常在几十ms,因此在raft协议中,leader挂掉之后基本在几十ms就能够被检测发现,故障恢复时间可以做到非常短。而像用Java实现的Raft库,如Ratis,考虑到GC时间,我估计这个超时时间没法设置这么短。在Leader做写入时也是一个两阶段提交的过程。首先leader会把在自己的log中找到第一个空位index写入,并通过AppendEntries这个RPC把这个entry的值发给每个follower,如果收到半数以上的follower(包括自己)回复true,则再下一个AppendEntries中,leader会把committedIndex加1,代表写入的这个entry已经被提交。如在下图中,leader将x=4写入index=8的这个entry中,并把他发送给了所有follower,在收到第一台(自己),第三台,第五台(图中没有画index=8的entry,但因为这台服务器之前所有的entry都和leader保持了一致,因此它一定会投同意),那么leader就获得了多数票,再下一个rpc中,会将Committed index往前挪一位,代表index<=8的所有entry都已经提交。至于第二台和第四台服务器,log内容已经明显落后,这要么是因为前几次rpc没有成功。leader会无限重试直到这些follower和leader的日志追平。另外一个可能是这两台服务器重启过,处于恢复状态。那么这两台服务器在收到写入index=8的RPC时,follower也会把上一个entry的term和index发给他们。也就是说prevLogIndex=7,prevLogTerm=3这个信息会发给第二台服务器,那么对于第二台服务器,index=7的entry是空的,也就是log和leader不一致,他会返回一个false给leader,leader会不停地从后往前遍历,直到找到一个entry与第二台服务器一致的,从这个点开始重新把leader的log内容发送给该follower,即可完成恢复。raft协议保证了所有成员的replicated log中每个index位置,如果他们的term一致,内容也一定一致。如果不一致,leader一定会把这个index的内容改写成和leader一致。其实经过刚才我的一些描述,基本上就已经把Raft的选主,写入流程和恢复基本上都讲完了。从这里,我们可以看出Raft一些非常有意思的地方。第一个有意思的地方是Raft的log的entry是可能被修改的,比如一个follower接收了一个leader的prepare请求,把值写入了一个index,而这个leader挂掉,新选出的leader可能会重新使用这个index,那么这个follower的相应index的内容,会被改写成新的内容。这样就造成了两个问题,首先第一个,raft的log无法在append-only的文件或者文件系统上去实现,而像ZAB,Paxos协议,log只会追加,只要求文件系统有append的能力即可,不需要随机访问修改能力。第二个有意思的地方是,为了简单,Raft中只维护了一个Committed index,也就是任何小于等于这个committedIndex的entry,都是被认为是commit过的。这样就会造成在写入过程中,在leader获得大多数选票之前挂掉(或者leader在写完自己的log之后还没来得及通知到任何follower就挂掉),重启后如果这个server继续被选为leader,这个值仍然会被commit永久生效。因为leader的log中有这个值,leader一定会保证所有的follower的log都和自己保持一致。而后续的写入在增长committedIndex后,这个值也默认被commit了。举例来说,现在有5台服务器,其中S2为leader,但是当他在为index=1的entry执行写入时,先写到了自己的log中,还没来得及通知其他server append entry就宕机了。 当S2重启后,任然有可能被重新当选leader,当S2重新当选leader后,仍然会把index=1的这个entry复制给每台服务器(但是不会往前移动Committed index)此时S2又发生一次写入,这次写入完成后,会把Committed index移动到2的位置,因此index=1的entry也被认为已经commit了。这个行为有点奇怪,因为这样等于raft会让一个没有获得大多数人同意的值最终commit。这个行为取决于leader,如果上面的例子中S2重启后没有被选为leader,index=1的entry内容会被新leader的内容覆盖,从而不会提交未经过表决的内容。虽然说这个行为是有点奇怪,但是不会造成任何问题,因为leader和follower还是会保持一致,而且在写入过程中leader挂掉,对客户端来说是本来就是一个未决语义,raft论文中也指出,如果用户想要exactly once的语义,可以在写入的时候加入一个类似uuid的东西,在写入之前leader查下这个uuid是否已经写入。那么在一定程度上,可以保证exactly once的语义。Raft的论文中也比较了ZAB的算法,文中说ZAB协议的一个缺点是在恢复阶段需要leader和follower来回交换数据,这里我没太明白,据我理解,ZAB在重新选主的过程中,会选择Zxid最大的那个从成为主,而其他follower会从leader这里补全数据,并不会出现leader从follower节点补数据这一说。后话目前,经过改进的Paxos协议已经用在了许多分布式产品中,比如说Chubby,PaxosStore,阿里云的X-DB,以及蚂蚁的OceanBase,都选用了Paxos协议,但他们都多多少少做了一些补充和改进。而像Raft协议,普遍认为Raft协议只能顺序commit entry,性能没有Paxos好,但是TiKV中使用了Raft,其公开的文章宣传对Raft做了非常多的优化,使Raft的性能变的非常可观。阿里的另外一个数据库PolarDB,也是使用了改进版的Parallel-Raft,使Raft实现了并行提交的能力。相信未来会有更多的基于Paxos/Raft的产品会面世,同时也对Raft/Paxos也会有更多的改进。参考文献《Time, clocks, and the ordering of events in a distributed system》《Implementing fault-tolerant services using the state machine approach- A tutorial》《Paxos Made Simple》《Paxos made live- An engineering perspective》《Multi-Paxos》(Standford大学的一个ppt)《Zab- High-performance broadcast for primary-backup systems》《In search of an understandable consensus algorithm》(raft)本文作者:正研阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 19, 2019 · 1 min · jiezi

分布式系统「伸缩性」大招之——「弹性架构」详解

如果第二次看到我的文章,欢迎下方扫码订阅我的个人公众号(跨界架构师)哟本文长度为3633字,建议阅读10分钟。坚持原创,每一篇都是用心之作~如果我们的开发工作真的就如搭积木一般就好了,轮廓分明,个个分开,坏了哪块积木换掉哪块就好了。但是,实际我们的工作中所面临的可能只有一块积木,而且还是一大块,要换得一起换,要修得一起修。Z哥在之前《分布式系统关注点(13)——「高内聚低耦合」详解》中提到的分层架构它可以让我们有意识的去做一些切分,但是换和修的难度还是根据切分的粒度大小来决定的。有更好的方式吗?这是显然的。事件驱动架构我们来换一个思维看待这个问题。不管是平时的系统升级也好、修复bug也好、扩容也好,其实就是一场“手术”。通过这场“手术”来解决当前面临的一些问题。那么分层架构好比只是将一个人的手、脚、嘴、鼻等分的清清楚楚,但是整体还是紧密的耦合在一起。怎么耦合的呢?我们人是靠“血液”的流动连接起来的。这就好比在分布式系统中通过rpc框架连接起不同的节点一样。但是软件与人不同,有2种不同的连接方式,除了「同步」的方式之外还有「异步」的方式。因为有些时候你不需要知道其他系统的执行结果,只要确保自己将其需要的数据传递给它了即可。恰巧有一种架构是这种模式的典型——事件驱动架构(简称EDA,Event Driven Architecture)。平时常见的MQ、本地消息表等运用于数据传递的中转环节,就是事件驱动架构的思想体现。事件驱动架构又细分为两种典型的实现方式,与Z哥之前在《分布式系统关注点(3)——「共识」的兄弟「事务」》中提到的Saga模式的2种实现方式类似,一种是中心化的、一种是去中心化的。下面Z哥来举个例子,让你看起来更容易理解一些。(例子仅为了阐述是怎么工作的,真正的实施中还需要考虑如何保证数据一致性等问题,这部分可以参考之前发表的系列文章,文末带传送门)传统的电商场景中,用户从购物车中点击“提交”按钮后,至少需要做这几件事:生成一笔订单、生成一笔支付记录、给订单匹配发货的快递公司。在这个场景下,中心化和去中心化有什么不同呢?中心化这种模式拥有一个“上帝”。但是“上帝”不会处理也不知道任何业务逻辑,它只编排事件。除了中心化之外,它还有什么特点呢?Z哥给它的定义是“3+2结构”。这种模式中存在3种类型的主体:事件生产者、“上帝”(调停者)、事件处理者。然后中间夹着两层队列,以此结构就能解耦。就像这样:事件生产者 –> 队列 –> “上帝”(调停者) –> 队列 –> 事件处理者。那么回上面的这个例子中,事件生产者CartService发出了一个“订单创建”事件,通过队列传递给调停者。然后调停者根据事先制定好的编排规则对事件进行相应的转换,也通过队列做二次分发,传递给事件处理者。可能你会问,这些好理解。但是,我之前也经常看到什么编排编排的,到底编排该怎么做呢?其实编排主要做两件事:「事件转换」和「事件发送」(对应「服务编排」类框架的「调用」)。「事件转换」实质就是给将要发送的事件对象的参数进行赋值。赋值的数据来源于哪呢?除了事件产生的源头带入的参数,还有持续累积的「上下文」,就如下图中这样的一个共享存储空间。可能你又会问,我怎么将多个事件处理者之间组合成一个上下文呢?通过一个全局唯一的标识即可,每次向“上帝”丢事件的时候把这个全局唯一标识带过去。题外话:一般来说,还会在一个全局唯一标识之下带一个内部唯一的「子流水号」,为了配合做接下去要讲到的「事件发送」。一是为了后续排查问题的时候清晰的知道这次调用产生的异常是从哪个上游系统来的。二是为了便于观测整个调用的逻辑是否符合编排时的预期。怎么做呢?通过一个x.x.x.x格式的序号。比如,串行就是1,2,3。分支和并行就是2.1,2.2。分支+串行的结合就是1,2,2.1,2.2,3。「事件发送」实质就是负责事件流转的逻辑控制,然后发往「事件处理者」去处理。它决定了是按顺序还是分支进行?是串行还是并行?到这就不再展开了,要不然就跑题了,我们下次再细聊这部分内容。再强调一下,「事件转换」和「事件发送」是你在实现“上帝”(调停者)功能的时候需要满足的最基本的两个功能哦。中心化最大的优势是让流程更加的“可见”了,同时也更容易去做一些监控类的东西,系统规模越大,这个优势产生的效果越明显。但是一个最基本的“上帝”(调停者)实现起来还需要考虑数据一致性问题,所以,会大大增加它的实现复杂度。因此,如果你面对的场景,业务没有特别庞大,并且是比较稳定的,或许用去中心化的方式也是不错的选择。去中心化这个模式由于没有了“上帝”,因此每个事件处理者需要知道自己的下一个事件处理器是什么?需要哪些参数?以及队列是哪个之类的东西。但是整体结构会变得简单很多,从“3+2结构”变成了“2+1结构”。结构简化背后的复杂度都跑到事件处理者开发人员编写的业务代码中去了。因为他需要自己去负责「事件转换」和「事件发送」这两个事情。嗯,改造成事件驱动架构之后,通过「队列」的解耦和异步的事件流转,系统的运转的确会更顺畅。但是有时候你可能想进行更细粒度的控制,因为一般情况下,一个service中会处理很多业务环节,不太会只存在一个对外接口、一条业务逻辑。在这样的情况下,很多时候你可能需要修改的地方仅仅是其中的一个接口。能不能只修改其中的一部分代码并且进行「热更新」呢?微内核架构(插件架构)就适合来解决这个问题。微内核架构顾名思义,微内核架构的关键是内核。所以需要先找到并明确内核是什么?然后将其它部分都视作“可拆卸”的部件。好比我们一个人,大脑就是内核,其它的什么都可以换,换完之后你还是你,但是大脑换了就不是你了。微内核架构整体上由两部分组成:核心系统和插件模块。核心系统内又包含了微内核、插件模块,以及内置的一些同样以插件形式提供的默认功能。其中,微内核主要负责插件的生命周期管理和控制插件模块。插件模块则负责插件的加载、替换和卸载。外部的插件如果要能够接入进来并顺利运行,前提先要有一个满足标准接口规范的实现。一个插件的标准接口至少会有这样的2个方法需要具体的插件来实现:public interface IPlugin{ /// <summary> /// 初始化配置 /// </summary> void InitializeConfig(Dictionary<string,string> configs); /// <summary> /// 运行 /// </summary> void Run(); …}最后,插件之间彼此独立,但核心系统知道哪里可以找到它们以及如何运行它们。 最佳实践知道了这两种具有“弹性”的架构模式,你该如何判断什么情况下需要搬出来用呢?Z哥带你来分析一下每一种架构的优缺点,就能发现它适用的场景。事件驱动架构它的优点是:通过「队列」进行解耦,使得面对快速变化的需求可以即时上线,而不影响上游系统。由于「事件」是一个独立存在的“标准化”沟通载体,可以利用这个特点衔接各种跨平台、多语言的程序。如果再进行额外的持久化,还能便于后续的问题排查。同时也可以对「事件」进行反复的「重放」,对处理者的吞吐量进行更真实的压力测试。更“动态”、容错性好。可以很容易,低成本地集成、再集成、再配置新的和已经存在的事件处理者,也可以很容易的移除事件处理者。轻松的做扩容和缩容。在“上帝”模式下,对业务能有一个“可见”的掌控,更容易发现流程不合理或者被忽略的问题。同时能标准化一些技术细节,如「数据一致性」的实现方式等。它的缺点是:面对不稳定的网络问题、各种异常,想要处理好这些以确保一致性,需要比同步调用花费很大的精力和成本。无法像同步调用一般,操作成功后即代表可以看到最新的数据,需要容忍延迟或者对延迟做一些用户体验上的额外处理。那么,它所适用的场景就是:对实时性要求不高的场景。系统中存在大量的跨平台、多语言的异构环境。以尽可能提高程序复用度为目的的场景。业务灵活多变的场景。需要经常扩容缩容的场景。微内核架构它的优点是:为递进设计和增量开发提供了方便。可以先实现一个稳固的核心系统,然后逐渐地增加功能和特性。和事件驱动架构一样,也可避免单一组件失效,而造成整个系统崩溃,容错性好。内核只需要重新启动这个组件,不致于影响其他功能。它的缺点是:由于主要的微内核很小,所以无法对整体进行优化。每个插件都各自管各自的,甚至可能是由不同团队负责维护。一般来说,为了避免在单个应用程序中的复杂度爆炸,很少会启用插件嵌套插件的模式,所以插件中的代码复用度会差一些。那么,它所适用的场景就是:可以嵌入或者作为其它架构模式的一部分。例如事件驱动架构中,“上帝”的「事件转换」就可以使用微内核架构实现。业务逻辑虽然不同,但是运行逻辑相同的场景。比如,定期任务和作业调度类应用。具有清晰的增量开发预期的场景。总结好了,我们总结一下。这次呢,Z哥向你介绍了「事件驱动架构」的两种实现模式和实现思路,以及「微内核架构」的实现思路。并且奉上了对这两种架构模式的优缺点与适用场景分析的最佳实践。希望对你有所启发。相关文章:分布式系统关注点(1)——初识数据一致性分布式系统关注点(2)——通过“共识”达成数据一致性分布式系统关注点(3)——「共识」的兄弟「事务」作者:Zachary出处:https://www.cnblogs.com/Zacha…如果你喜欢这篇文章,可以点一下文末的「赞」。这样可以给我一点反馈。: )谢谢你的举手之劳。▶关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描下方的二维码。定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。

February 18, 2019 · 1 min · jiezi

分布式事务中间件 Fescar—RM 模块源码解读

前言在SOA、微服务架构流行的年代,许多复杂业务上需要支持多资源占用场景,而在分布式系统中因为某个资源不足而导致其它资源占用回滚的系统设计一直是个难点。我所在的团队也遇到了这个问题,为解决这个问题上,团队采用的是阿里开源的分布式中间件Fescar的解决方案,并详细了解了Fescar内部的工作原理,解决在使用Fescar中间件过程中的一些疑虑的地方,也为后续团队在继续使用该中间件奠定理论基础。目前分布式事务解决方案基本是围绕两阶段提交模式来设计的,按对业务是有侵入分为:对业务无侵入的基于XA协议的方案,但需要数据库支持XA协议并且性能较低;对业务有侵入的方案包括:TCC等。Fescar就是基于两阶段提交模式设计的,以高效且对业务零侵入的方式,解决微服务场景下面临的分布式事务问题。Fescar设计上将整体分成三个大模块,即TM、RM、TC,具体解释如下:TM(Transaction Manager):全局事务管理器,控制全局事务边界,负责全局事务开启、全局提交、全局回滚。RM(Resource Manager):资源管理器,控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。TC(Transaction Coordinator):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。本文将深入到Fescar的RM模块源码去介绍Fescar是如何在完成分支提交和回滚的基础上又做到零侵入,进而极大方便业务方进行业务系统开发。一、从配置开始解读上图是Fescar源码examples模块dubbo-order-service.xml内的配置,数据源采用druid的DruidDataSource,但实际jdbcTemplate执行时并不是用该数据源,而用的是Fescar对DruidDataSource的代理DataSourceProxy,所以,与RM相关的代码逻辑基本上都是从DataSourceProxy这个代理数据源开始的。Fescar采用2PC来完成分支事务的提交与回滚,具体怎么做到的呢,下面就分别介绍Phase1、Phase2具体做了些什么。二、Phase1—分支(本地)事务执行Fescar将一个本地事务做为一个分布式事务分支,所以若干个分布在不同微服务中的本地事务共同组成了一个全局事务,结构如下。那么,一个本地事务中SQL是如何执行呢?在Spring中,本质上都是从jdbcTemplate开始的,比如下面的SQL语句:jdbcTemplate.update(“update storage_tbl set count = count - ? where commodity_code = ?”, new Object[] {count, commodityCode});一般JdbcTemplate执行流程如下图所示:由于在配置中,JdbcTemplate数据源被配置成了Fescar实现DataSourceProxy,进而控制了后续的数据库连接使用的是Fescar提供的ConnectionProxy,Statment使用的是Fescar实现的StatmentProxy,最终Fescar就顺理成章地实现了在本地事务执行前后增加所需要的逻辑,比如:完成分支事务的快照记录和分支事务执行状态的上报等等。DataSourceProxy获取ConnectionProxy:ConnectionProxy获取StatmentProxy:在获取到StatmentProxy后,可以调用excute方法执行sql了而真正excute实现逻辑如下:首先会检查当前本地事务是否处于全局事务中,如果不处于,直接使用默认的Statment执行,避免因引入Fescar导致非全局事务中的SQL执行性能下降。解析Sql,有缓存机制,因为有些sql解析会比较耗时,可能会导致在应用启动后刚开始的那段时间里处理全局事务中的sql执行效率降低。对于INSERT、UPDATE、DELETE、SELECT..FOR UPDATE这四种类型的sql会专门实现的SQL执行器进行处理,其它SQL直接是默认的Statment执行。返回执行结果,如有异常则直接抛给上层业务代码进行处理。再来看一下关键的INSERT、UPDATE、DELETE、SELECT..FOR UPDATE这四种类型的sql如何执行的,先看一下具体类图结构:为结省篇幅,选择UpdateExecutor实现源码看一下,先看入口BaseTransactionalExecutor.execute,该方法将ConnectionProxy与Xid(事务ID)进行绑定,这样后续判断当前本地事务是否处理全局事务中只需要看ConnectionProxy中Xid是否为空。然后,执行AbstractDMLBaseExecutor中实现的doExecute方法基本逻辑如下:先判断是否为Auto-Commit模式如果非Auto-Commit模式,则先查询Update前对应行记录的快照beforeImage,再执行Update语句,完成后再查询Update后对应行记录的快照afterImage,最后将beforeImage、afterImage生成UndoLog追加到Connection上下文ConnectionContext中。(注:获取beforeImage、afterImage方法在UpdateExecutor类下,一般是构造一条Select…For Update语句获取执行前后的行记录,同时会检查是否有全局锁冲突,具体可参考源码)如果是Auto-Commit模式,先将提交模式设置成非自动Commit,再执行2中的逻辑,再执行connectionProxy.commit()方法,由于执行2过程和commit时都可能会出现全局锁冲突问题,增加了一个循环等待重试逻辑,最后将connection的模式设置成Auto-Commit模式如果本地事务执行过程中发生异常,业务上层会接收到该异常,至于是给TM模块返回成功还是失败,由业务上层实现决定,如果返回失败,则TM裁决对全局事务进行回滚;如果本地事务执行过程未发生异常,不管是非Auto-Commit还是Auto-Commit模式,最后都会调用connectionProxy.commit()对本地事务进行提交,在这里会创建分支事务、上报分支事务的状态以及将UndoLog持久化到undo_log表中,具体代码如下图:基本逻辑:判断当前本地事务是否处于全局事务中(也就判断ConnectionContext中的xid是否为空)。如果不处于全局事务中,则调用targetConnection对本地事务进行commit。如果处于全局事务中,首先创建分支事务,再将ConnectionContext中的UndoLog写入到undo_log表中,然后调用targetConnection对本地事务进行commit,将UndoLog与业务SQL一起提交,最后上报分支事务的状态(成功 or 失败),并将ConnectionContext上下文重置。综上所述,RM模块通过对JDBC数据源进行代理,干预业务SQL执行过程,加入了很多流程,比如业务SQL解析、业务SQL执行前后的数据快照查询并组织成UndoLog、全局锁检查、分支事务注册、UndoLog写入并随本地事务一起Commit、分支事务状态上报等。通过这种方式,Fescar真正做到了对业务代码无侵入,只需要通过简单的配置,业务方就可以轻松享受Fescar所带来的功能。Phase1整体流程引用Fescar官方图总结如下:三、Phase2-分支事务提交或回滚阶段2完成的是全局事物的最终提交或回滚,当全局事务中所有分支事务全部完成并且都执行成功,这时TM会发起全局事务提交,TC收到全全局事务提交消息后,会通知各分支事务进行提交;同理,当全局事务中所有分支事务全部完成并且某个分支事务失败了,TM会通知TC协调全局事务回滚,进而TC通知各分支事务进行回滚。在业务应用启动过程中,由于引入了Fescar客户端,RmRpcClient会随应用一起启动,该RmRpcClient采用Netty实现,可以接收TC消息和向TC发送消息,因此RmRpcClient是与TC收发消息的关键模块。public class RMClientAT {public static void init(String applicationId, String transactionServiceGroup) { RmRpcClient rmRpcClient = RmRpcClient.getInstance(applicationId, transactionServiceGroup); AsyncWorker asyncWorker = new AsyncWorker(); asyncWorker.init(); DataSourceManager.init(asyncWorker); rmRpcClient.setResourceManager(DataSourceManager.get()); rmRpcClient.setClientMessageListener(new RmMessageListener(new RMHandlerAT())); rmRpcClient.init();}}上述代码展示是的RmRpcClient初始化过程,有三个关键类RMHandlerAT、AsyncWorker和DataSourceManager。RMHandlerAT具有了分支提交和回滚两个方法,分支提交或回滚的逻辑可以从这里开始看;AsyncWorker是一个异步Worker,主要是完成分支事务异步提交的功能,具有失败重试功能;DataSourceManager对数据源管理和维护。下面分成两部分来讲:分支事务提交、分去事务回滚。3.1、分支事务提交在接收到TC发起的全局提交消息后,经RmRpcClient对通信协议的处理,再交由RMHandlerAT来完成对分支事务的提交,分支事务提交从RMHandlerAT.doBranchCommit()开始,但最后由AsyncWorker异步Worker完成,直接看AsyncWorker中的代码实现:分支事务提交关键逻辑在doBranchCommits方法中:该方法主要是批量删除UndoLog日志,但并未使用ConnectionProxy去执行删除SQL,可能原因是:1、完全没必要 2、考虑效率优先同样,对于分支事务提交也引用Fescar官方一张图来结尾:3.2、分支事务回滚同样,分支事务回滚是从RMHandlerAT.doBranchRollback开始的,然后到了dataSourceManager.branchRollback,最后完成分支事务回滚逻辑的是UndoLogManager.undo方法。@Overrideprotected void RMHandlerAT:doBranchRollback(BranchRollbackRequest request, BranchRollbackResponse response) throws TransactionException { String xid = request.getXid(); long branchId = request.getBranchId(); String resourceId = request.getResourceId(); String applicationData = request.getApplicationData(); LOGGER.info(“AT Branch rolling back: " + xid + " " + branchId + " " + resourceId); BranchStatus status = dataSourceManager.branchRollback(xid, branchId, resourceId, applicationData); response.setBranchStatus(status); LOGGER.info(“AT Branch rollback result: " + status);} @Overridepublic BranchStatus DataSourceManager:branchRollback(String xid, long branchId, String resourceId, String applicationData) throws TransactionException { DataSourceProxy dataSourceProxy = get(resourceId); if (dataSourceProxy == null) { throw new ShouldNeverHappenException(); } try { UndoLogManager.undo(dataSourceProxy, xid, branchId); } catch (TransactionException te) { if (te.getCode() == TransactionExceptionCode.BranchRollbackFailed_Unretriable) { return BranchStatus.PhaseTwo_RollbackFailed_Unretryable; } else { return BranchStatus.PhaseTwo_RollbackFailed_Retryable; } } return BranchStatus.PhaseTwo_Rollbacked;}UndoLogManager.undo方法源码如下:从上图可以看出,整个回滚到全局事务之前状态的代码逻辑集中在如下代码中:AbstractUndoExecutor undoExecutor = UndoExecutorFactory.getUndoExecutor(dataSourceProxy.getDbType(), sqlUndoLog);undoExecutor.executeOn(conn);首先通过UndoExecutorFactory获取到对应的UndoExecutor,然后再执行UndoExecutor的executeOn方法完成回滚操作。目前三种类型的UndoExecutor结构如下:undoExecutor.executeOn源码如下:至此,整个分支事务回滚就结束了,分支事务回滚整体时序图如下:引入Fescar官方对分支事务回滚原理介绍图作为结尾:综合上述,Fescar在Phase2通过UndoLog自动完成分支事务提交与回滚,在这个过程中不需要业务方做任何处理,业务方无感知,因些在该阶段对业务代码也是无侵入的。四、总结本文主要介绍了RM模块的相关代码,将RM模块按2PC模式分成Phase1和Phase2分别进行介绍,从Fescar源码上看,整个源码结构清晰,有利于研发人员快速学习Fescar的原理。在使用方面,只需进行简单的配置,就可以享受Fescar带来的便捷功能,对业务做到了无侵入;同时在性能方面,Fescar在分支事务提交过程中采用异步模式,减少了全局锁的占用时间,进而提升了整体性能。后续,将继续学习Fescar的其它模块(TM、TC)与全局锁的实现逻辑,并做相关总结介绍。本文作者:中间件小哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 14, 2019 · 1 min · jiezi

分布式理论之2PC协议(2阶段提交协议)

系列文章 -> 分布式理论分布式理论之CAP定理(布鲁尔定理)分布式理论之BASE理论分布式理论之2PC协议(2阶段提交协议)2PC是什么同前文,2PC也是缩写,即Two-phase Commit,即二阶段提交目的二阶段提交协议是常用的分布式事务解决方案,它可以保证在分布式事务中,要么所有参与进程都提交事务,要么都取消事务,即实现ACID的原子性(A)。在数据一致中,它的含义是:要么所有副本(备份数据)同时修改某个数值,要么都不更改,以此来保证数据的强一致性。知识预备 - 预写式日志(Write-Ahead logging(WAL))在计算机科学中,预写式日志(Write-ahead logging,缩写 WAL)是关系数据库系统中用于提供原子性和持久性(ACID属性中的两个)的一系列技术二阶段提交算法成立的基本假设分布式系统中,存在一个节点作为协调者,其他节点作为参与者所有节点都采用预写式日志,且日志被写入后即被保持在可靠的存储设备上,即使节点损坏也不会导致日志数据消失所有节点不会永久性损坏,即使损坏后仍然可以恢复,且节点之间可以互相通信基本假设存在的风险所有节点可以互相通信,这个一般不是什么大问题,通常可以重新路由网络通信所有节点不会永久损坏,这个问题很大,比如服务器炸了二阶段提交具体操作提交请求阶段(投票阶段)第一阶段也被称作投票阶段,即各参与者投票是否要继续接下来的提交操作协调者节点向所有参与者节点询问是否可以执行提交操作,并开始等待各参与者节点的响应。参与者节点执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志(预写式日志)。各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个"同意"消息;如果参与者节点的事务操作实际执行失败,则它返回一个"中止"消息提交执行阶段(执行阶段)第二阶段也被称作完成阶段,因为无论结果怎样,协调者都必须在此阶段结束当前事务。成功当协调者节点从所有参与者节点获得的相应消息都为"同意"时:协调者节点向所有参与者节点发出"正式提交"的请求。参与者节点正式完成操作,并释放在整个事务期间内占用的资源。参与者节点向协调者节点发送"完成"消息。协调者节点收到所有参与者节点反馈的"完成"消息后,完成事务。失败如果任一参与者节点在第一阶段返回的响应消息为"终止",或者 协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:协调者节点向所有参与者节点发出"回滚操作"的请求。参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。参与者节点向协调者节点发送"回滚完成"消息。协调者节点收到所有参与者节点反馈的"回滚完成"消息后,取消事务。总结在分布式系统中,每个节点虽然可以知道自己的操作是成功还是失败,却无法知道其他节点的操作状态,当一个事务需要跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌握所有节点(参与者)的操作结果并最终指示这些节点是否需要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。因此,二阶段提交的算法思路可以概括为:参与者将操作结果通知协调者,在由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。参考The Two-Phase Commit Protocol二阶段提交分布式理论(三) - 2PC协议分布式系统的一致性协议之 2PC 和 3PC

January 29, 2019 · 1 min · jiezi

鲜为人知的混沌工程,到底哪里好?

阿里妹导读:混沌工程属于一门新兴的技术学科,行业认知和实践积累比较少,大多数IT团队对它的理解还没有上升到一个领域概念。阿里电商域在2010年左右开始尝试故障注入测试的工作,希望解决微服务架构带来的强弱依赖问题。通过本文,你将了解到:为什么需要混沌工程,阿里巴巴在该领域的实践和思考、未来的计划。一、为什么需要混沌工程?(翻译自Chaos Engineering电子书)1.1 混沌工程与故障测试的区别混沌工程是在分布式系统上进行实验的学科, 目的是建立对系统抵御生产环境中失控条件的能力以及信心,最早由Netflix及相关团队提出。故障演练是阿里巴巴在混沌工程领域的产品,目标是沉淀通用的故障模式,以可控成本在线上重放,以持续性的演练和回归方式运营来暴露问题,不断推动系统、工具、流程、人员能力的不断前进。混沌工程、故障注入和故障测试在关注点和工具中都有很大的重叠。混沌工程和其他方法之间的主要区别在于,混沌工程是一种生成新信息的实践,而故障注入是测试一种情况的一种特定方法。当想要探索复杂系统可能出现的不良行为时,注入通信延迟和错误等失败是一种很好的方法。但是我们也想探索诸如流量激增,激烈竞争,拜占庭式失败,以及消息的计划外或不常见的组合。如果一个面向消费者的网站突然因为流量激增而导致更多收入,我们很难称之为错误或失败,但我们仍然对探索系统的影响非常感兴趣。同样,故障测试以某种预想的方式破坏系统,但没有探索更多可能发生的奇怪场景,那么不可预测的事情就可能发生。测试和实验之间可以有一个重要的区别。在测试中,进行断言:给定特定条件,系统将发出特定输出。测试通常是二进制态的,并确定属性是真还是假。严格地说,这不会产生关于系统的新知识,它只是将效价分配给它的已知属性。实验产生新知识,并经常提出新的探索途径。我们认为混沌工程是一种实验形式,可以产生关于系统的新知识。它不仅仅是一种测试已知属性的方法,可以通过集成测试更轻松地进行验证。混沌实验的输入示例:模拟整个区域或数据中心的故障。部分删除各种实例上的Kafka主题。重新创建生产中发生的问题。针对特定百分比的交易服务之间注入一段预期的访问延迟。基于函数的混乱(运行时注入):随机导致抛出异常的函数。代码插入:向目标程序添加指令和允许在某些指令之前进行故障注入。时间旅行:强制系统时钟彼此不同步。在模拟I/O错误的驱动程序代码中执行例程。在 Elasticsearch 集群上最大化CPU核心。混沌工程实验的机会是无限的,可能会根据分布式系统的架构和组织的核心业务价值而有所不同。1.2 实施混沌工程的先决条件要确定是否已准备好开始采用混沌工程,需要回答一个问题:你的系统是否能够适应现实世界中的事件,例如服务故障和网络延迟峰值?如果答案是“否”,那么你还有一些工作要做。混沌工程非常适合揭露生产系统中未知的弱点,但如果确定混沌工程实验会导致系统出现严重问题,那么运行该实验就没有任何意义。先解决这个弱点,然后回到混沌工程,它将发现你不了解的其他弱点,或者它会让你发现你的系统实际上是有弹性的。混沌工程的另一个基本要素是可用于确定系统当前状态的监控系统。1.3 混沌工程原则为了具体地解决分布式系统在规模上的不确定性,可以把混沌工程看作是为了揭示系统弱点而进行的实验。破坏稳态的难度越大,我们对系统行为的信心就越强。如果发现了一个弱点,那么我们就有了一个改进目标。避免在系统规模化之后问题被放大。以下原则描述了应用混沌工程的理想方式,这些原则来实施实验过程。对这些原则的匹配程度能够增强我们在大规模分布式系统的信心。二、阿里巴巴在混沌工程领域的实践:故障演练混沌工程属于一门新兴的技术学科,行业认知和实践积累比较少,大多数IT团队对它的理解还没有上升到一个领域概念。阿里电商域在2010年左右开始尝试故障注入测试的工作,开始的目标是想解决微服务架构带来的强弱依赖问题。后来经过多个阶段的改进,最终演进到 MonkeyKing(线上故障演练平台)。从发展轨迹来看,阿里的技术演进和Netflix的技术演进基本是同时间线的,每个阶段方案的诞生都有其独特的时代背景和业务难点,也可以看到当时技术的局限性和突破。2.1 建立一个围绕稳定状态行为的假说目前阿里巴巴集团范围内的实践偏向于故障测试,即在一个具体场景下实施故障注入实验并验证预期是否得到满足。这种测试的风险相对可控,坏处是并没有通过故障注入实验探索更多的场景,暴露更多的潜在问题,测试结果比较依赖实施人的经验。当前故障测试的预期比较两级分化,要么过于关注系统的内部细节,要么对于系统的表现完全没有预期,与混沌工程定义的稳态状态行为差异比较大。引起差异的根本原因还是组织形态的不同。2014年,Netflix团队创建了一种新的角色,叫作混沌工程师(Chaos Enigneer),并开始向工程社区推广。而阿里目前并没有一个专门的职位来实施混沌工程,项目目标、业务场景、人员结构、实施方式的不同导致了对于稳定状态行为的定义不太标准。2.2 多样化真实世界的事件阿里巴巴因为多元化的业务场景、规模化的服务节点及高度复杂的系统架构,每天都会遇到各式各样的故障。这些故障信息就是最真实的混沌工程变量。为了能够更体感、有效率地描述故障,我们优先分析了P1和P2的故障(P是阿里对故障等级的描述),提出一些通用的故障场景并按照IaaS层、PaaS层、SaaS层的角度绘制了故障画像。从故障的完备性角度来看,上述画像只能粗略代表部分已出现的问题,对于未来可能会出现的新问题也需要一种手段保持兼容。在更深入的进行分析之后,我们定义了另一维度的故障画像:任何故障,一定是硬件如IaaS层,软件如PaaS或SaaS的故障。并且有个规律,硬件故障的现象,一定可以在软件故障现象上有所体现。故障一定隶属于单机或是分布式系统之一,分布式故障包含单机故障。对于单机或同机型的故障,以系统为视角,故障可能是当前进程内的故障,比如:如FullGC,CPU飙高;进程外的故障,比如其他进程突然抢占了内存,导致当前系统异常等。同时,还可能有一类故障,是人为失误,或流程失当导致,这部分我们今天不做重点讨论。从故障注入实现角度,我们也是参照上述的画像来设计的。之前我们是通过Java字节码技术和操作系统层面的工具来分别模拟进程内和进程外的故障。随着Serverless、Docker等新架构、新技术的出现,故障实现机制和承接载体也将会有一些新的变化。2.3 在生产环境中运行实验从功能性的故障测试角度来看,非生产环境去实施故障注入是可以满足预期的,所以最早的强弱依赖测试就是在日常环境中完成的。不过,因为系统行为会根据环境和流量模式有所不同,为了保证系统执行方式的真实性与当前部署系统的相关性,推荐的实施方式还是在生产环境(仿真环境、沙箱环境都不是最好的选择)。很多同学恐惧在生产环境执行实验,原因还是担心故障影响不可控。实施实验只是手段,通过实验对系统建立信心是我们的目标。关于如何减少实验带来的影响,这点在"最小化爆炸半径"部分会有阐述。2.4 持续自动化运行实验2014年,线下环境的强弱依赖测试用例是默认在每次发布后自动执行的。2015年,开始尝试在线上进行自动化回归。不过发展到最近两年,手动实验的比例逐渐变高。原因也不复杂,虽然故障注入自动化了,业务验证的成本仍然比较高。在业务高速发展、人员变化较快的环境之下,保持一套相对完善的线上回归用例集对是见非常难的事情。虽然也出现了流量录制技术,不过因为混沌工程实验本身会打破系统已有的行为,基于入口和出口的流量比对的参考度就下降许多。为了解决测试成本问题,2017年初开始推进线上微灰度环境的建设。基于业务、比例来筛选特征流量,通过真实的流量来替换原来的测试流量,通过监控&报警数据来替代测试用例结果。目前已经有部分业务基于微灰度+故障演练的模式来做演练验证(比如:盒马APOS容灾演习)。因为故障演练之前是作为一个技术组件被嵌入到常态和大促的流程中,所以在系统构建自动化的编排和分析方面的产品度并不高。演练可视化编排和能力开放会是我们团队未来的一个重点,下文中的规划部分会有所阐述。2.5 最小化爆炸半径在生产中进行试验可能会造成不必要的客户投诉,但混沌工程师的责任和义务是确保这些后续影响最小化且被考虑到。对于实验方案和目标进行充分的讨论是减少用户影响的最重要的手段。但是从实际的实施角度看,最好还是通过一些技术手段去最小化影响。Chaos Engineering和Fault Injection Test的核心区别在于:是否可以进一步减小故障的影响,比如微服务级别、请求级别甚至是用户级别。在MonkeyKing演进的中期阶段,已经可以实现请求级别的微服务故障注入。虽然那个时候演练实施的主要位置在测试环境,但初衷也是为了减少因为注入故障而导致的环境不稳定问题。除了故障注入,流量路由和数据隔离技术也是减少业务影响的有效手段。三、未来的计划线上故障演练发展到今天是第三年,随着阿里安全生产的大环境、业务方的诉求、研发迭代模式的变化,以及大家对混沌工程的接受和认识程度的提高。集团的演练领域会向着未来的几个目标发力:建立高可用专家库,结构化提高应用容错能力(解决"稳定状态定义"的问题)建设故障注入实现标准,集团内开源,提升故障模拟的广度和深度(拓宽"多样化真实世界的事件"的广度)规模化覆盖核心业务(提升"在生产环境中运行实验"的规模)以产品化、平台化思路开放演练能力(探索"自动化运行实验"的方式)四、触手可及的混沌工程MonkeyKing已经提供商业化产品,欢迎在阿里云官网搜索“AHAS”,进行免费公测。地址:https://www.aliyun.com/product/ahas本文作者:中亭阅读原文本文来自云栖社区合作伙伴“阿里技术”,如需转载请联系原作者。

January 29, 2019 · 1 min · jiezi

分布式理论之BASE理论

系列文章 -> 分布式理论分布式理论之CAP定理(布鲁尔定理)分布式理论之BASE理论什么是BASE理论如前文中说CAP定理是三个单词的缩写,BASE也是一样,是由Basically Available(基本可用),Soft state(软状态),和 Eventually consistent(最终一致性)三个短语的缩写。为什么会出现BASE理论我们在CAP理论讨论过CAP三者之间的权衡,而BASE理论是ebay架构师来源于对大型互联网分布式实践的总结,对于互联网来说,分区容错性(P)必须满足,我们权衡的是一致性(C)和可用性(A),而我们又希望在满足P的前提下能让C和A都满足,我们该怎么处理?核心思想虽然无法做到强一致性(Strong consistency),但可以根据业务自身的特点,采用适当的方式来达到最终一致性(Eventual consistency)名词解释Basically Available(基本可用)基本可用是相对于正常的系统来说的,常见如下情况响应时间上的损失:正常情况下的搜索引擎0.5秒即返回给用户结果,而基本可用看的搜索结果可能要1秒,2秒甚至3秒(超过3秒用户就接受不了了)功能上的损失:在一个电商网站上,正常情况下,用户可以顺利完成每一笔订单,但是到了促销时间,可能为了应对并发,保护购物系统的稳定性,部分用户会被引导到一个降级页面Soft state(软状态)软状态是相对原子性来说的原子性(硬状态) -> 要求多个节点的数据副本都是一致的,这是一种"硬状态"软状态(弱状态) -> 允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同节点的数据副本存在数据延迟Eventually consistent(最终一致性)弱一致性 和强一致性相对 系统并不保证连续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。但会尽可能保证在某个时间级别(比如秒级别)之后,可以让数据达到一致性状态。最终一致性是弱一致性的特定形式亚马逊CTO Werner Vogels在于2008年发表的一篇文章中对最终一致性进行了非常详细的介绍【英文】All Things Distributed【译】最终一致性官方解释 系统能够保证在没有其他新的更新操作的情况下,数据最终一定能够达到一致的状态,因此所有客户端对系统的数据访问最终都能够获取到最新的值。对于软状态,我们允许中间状态存在,但不可能一直是中间状态,必须要有个期限,系统保证在没有后续更新的前提下,在这个期限后,系统最终返回上一次更新操作的值,从而达到数据的最终一致性,这个容忍期限(不一致窗口的事件)取决于通信延迟,系统负载,数据复制方案设计,复制副本个数等,DNS是一个典型的最终一致性系统。最终一致性模型变种因果一致性(Causal consistency)如果节点A在更新完某个数据后通知了节点B,那么节点B的访问修改操作都是基于A更新后的值,同时,和节点A没有因果关系的C的数据访问则没有这样的限制读己之所写(Read your writes)因果一致性的特定形式,一个节点A总可以读到自己更新的数据会话一致性(Session consistency)访问存储系统同一个有效的会话,系统应保证该进程读己之所写单调读一致性(Monotonic read consistency)一个节点从系统中读取一个特定值之后,那么该节点从系统中不会读取到该值以前的任何值单调写一致性(Monotonic write consistency)一个系统要能够保证来自同一个节点的写操作被顺序执行(保证写操作串行化)实践中,往往5个系统进行排列组合,当然,不只是分布式系统使用最终一致性,关系型数据库在某个功能上,也是使用最终一致性的,比如备份,数据库的复制过程是需要时间的,这个复制过程中,业务读取到的值就是旧的。当然,最终还是达成了数据一致性。这也算是一个最终一致性的经典案例BASE和ACID的区别与联系参考ACID维基百科 ACID,是指数据库管理系统(DBMS)在写入或更新资料的过程中,为保证事务(transaction)是正确可靠的,所必须具备的四个特性:原子性(atomicity,或称不可分割性)、一致性(consistency)、隔离性(isolation,又称独立性)、持久性(durability)原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。隔离性:数据库允许多个并发事务同时对齐数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。ACID是传统数据库常用的设计理念, 追求强一致性模型。BASE支持的是大型分布式系统,提出通过牺牲强一致性获得高可用性。ACID和BASE代表了两种截然相反的设计哲学。总的来说,BASE 理论面向大型高可用可扩展的分布式系统,与ACID这种强一致性模型不同,常常是牺牲强一致性来获得可用性,并允许数据在一段时间是不一致的。虽然两者处于【一致性-可用性】分布图的两级,但两者并不是孤立的,对于分布式系统来说,往往依据业务的不同和使用的系统组件不同,而需要灵活的调整一致性要求,也因此,常常会组合使用ACID和BASE。参考All Things Distributed[译]最终一致性CAP原理、一致性模型、BASE理论和ACID特性分布式理论(二) - BASE理论ACID从分布式一致性谈到CAP理论、BASE理论

January 25, 2019 · 1 min · jiezi

分布式理论之CAP定理(布鲁尔定理)

定义在理论计算机科学中,CAP定理(CAP theorem),又被称作布鲁尔定理(Brewer’s theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点选项具体意义一致性(Consistency)所有节点访问同一份最新的数据副本可用性(Availability)每次请求都能获取到非错的响应,但是不保证获取的数据为最新数据分区容错性(Partition tolerance)分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务,除非整个网络环境都发生了故障而CAP指的就是上述三个指标的首字母分别解释每个指标一致性(Consistency)在写操作完成后开始的任何读操作都必须返回该值,或者后续写操作的结果也就是说,在一致性系统中,一旦客户端将值写入任何一台服务器并获得响应,那么之后client从其他任何服务器读取的都是刚写入的数据用如下系统进行解释客户端向G1写入数据v1,并等待响应此时,G1服务器的数据为v1,而G2服务器的数据为v0,两者不一致接着,在返回响应给客户端之前,G2服务器会自动同步G1服务器的数据,使得G2服务器的数据也是v1一致性保证了不管向哪台服务器(比如这边向G1)写入数据,其他的服务器(G2)能实时同步数据G2已经同步了G1的数据,会告诉G1,我已经同步了G1接收了所有同步服务器的已同步的报告,才将“写入成功”信息响应给clientclient再发起请求,读取G2的数据此时得到的响应是v1,即使client从未写入数据到G2可用性(Availability)系统中非故障节点收到的每个请求都必须有响应在可用系统中,如果我们的客户端向服务器发送请求,并且服务器未崩溃,则服务器必须最终响应客户端,不允许服务器忽略客户的请求分区容错性(Partition tolerance)允许网络丢失从一个节点发送到另一个节点的任意多条消息,即不同步也就是说,G1和G2发送给对方的任何消息都是可以放弃的,也就是说G1和G2可能因为各种意外情况,导致无法成功进行同步,分布式系统要能容忍这种情况。CAP三者不可能同时满足假设确实存在三者能同时满足的系统那么我们要做的第一件事就是分区我们的系统,由于满足分区容错性,也就是说可能因为通信不佳等情况,G1和G2之间是没有同步接下来,我们的客户端将v1写入G1,但G1和G2之间是不同步的,所以如下G1是v1数据,G2是v0数据。由于要满足可用性,即一定要返回数据,所以G1必须在数据没有同步给G2的前提下返回数据给client,如下接下去,client请求的是G2服务器,由于G2服务器的数据是v0,所以client得到的数据是v0很明显,G1返回的是v1数据,G2返回的是v0数据,两者不一致。其余情况也有类似推导,也就是说CAP三者不能同时出现。CAP三者如何权衡我们权衡三者的关键点取决于业务放弃了一致性,满足分区容错,那么节点之间就有可能失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会容易导致全局数据不一致性。对于互联网应用来说(如新浪,网易),机器数量庞大,节点分散,网络故障再正常不过了,那么此时就是保障AP,放弃C的场景,而从实际中理解,像门户网站这种偶尔没有一致性是能接受的,但不能访问问题就非常大了。对于银行来说,就是必须保证强一致性,也就是说C必须存在,那么就只用CA和CP两种情况,当保障强一致性和可用性(CA),那么一旦出现通信故障,系统将完全不可用。另一方面,如果保障了强一致性和分区容错(CP),那么久具备了部分可用性。实际究竟应该选择什么,是需要通过业务场景进行权衡的(并不是所有情况都是CP好于CA,只能查看信息但不能更新信息有时候还不如直接拒绝服务)参考CAP原则(CAP定理)、BASE理论分布式理论(一) - CAP定理CAP定理An Illustrated Proof of the CAP Theorem

January 24, 2019 · 1 min · jiezi

观远AI实战 | 机器学习系统的工程实践

图片描述「观远AI实战」栏目文章由观远数据算法天团倾力打造,观小编整理编辑。这里将不定期推送关于机器学习,数据挖掘,特征重要性等干货分享。本文8千多字,约需要16分钟阅读时间。机器学习作为时下最为火热的技术之一受到了广泛的关注。我们每天打开公众号都能收到各种前沿进展、论文解读、最新教程的推送。这些文章中绝大多数内容都跟酷炫的新模型、高大上的数学推导有关。但是Peter Norvig说过,“We don’t have better algorithms. We just have more data.”。在实际机器学习应用中,对最终结果起到决定性作用的往往是精心收集处理的高质量数据。从表面看好像也不难,但略微深究就会发现机器学习系统与传统的软件工程项目有着非常大的差异。除了广受瞩目的模型算法,良好的工程化思考与实现是最终达到机器学习项目成功的另一大关键因素。谷歌在2015年发表的论文《Hidden Technical Debt in Machine Learning Systems》中就很好的总结了机器学习工程中的各种不佳实践导致的技术债问题。主要有以下几种:系统边界模糊在传统的软件工程中,一般会进行细致的设计和抽象,对于系统的各个组成部分进行良好的模块划分,这样整个系统的演进和维护都会处于一个比较可控的状态。但机器学习系统天然就与数据存在一定程度的耦合,加上天然的交互式、实验性开发方式,很容易就会把数据清洗、特征工程、模型训练等模块耦合在一起,牵一发而动全身,导致后续添加新特征,做不同的实验验证都会变得越来越慢,越来越困难。数据依赖难以管理传统的软件工程开发中,可以很方便的通过编译器,静态分析等手段获取到代码中的各种依赖关系,快速发现不合理的耦合设计,然后借助于单元测试等手段快速重构改进。在机器学习系统中这类代码耦合分析同样不可或缺。除此之外还多了数据依赖问题。比如销售预测系统可能会对接终端POS系统数据,也会引入市场部门的营销数据,还有仓储、运输等等多种数据来源。在大多数情况下这些数据源都是不同部门维护的,不受数据算法团队的控制,指不定哪天就悄悄做了一个变更。如果变更很大,可能在做数据处理或者模型训练时会直接抛出错误,但大多数情况下你的系统还是能正常运行,而得到的训练预测结果很可能就有问题了。在一些复杂业务系统中,这些数据本身还会被加工成各种中间数据集,同时被几个数据分析预测任务共享,形成复杂的依赖关系网,进一步加大了数据管理的难度。机器学习系统的反模式胶水代码:随着各种开源项目的百花齐放,很多机器学习项目都会调用各种开源库来做数据处理、模型训练、参数调优等环节。于是自然而然在整个项目中大量的代码都是为了把这些不同的开源库粘合在一起的胶水代码,同样会导致之前提到过的边界模糊,与特定的库紧耦合,难以替换模块快速演进等问题。流水线丛林:在数据处理特征工程与模型调优的迭代演进过程中,稍不注意你的整个系统流水线就会变得无比冗长,各种中间结果的写入和前后依赖极其复杂。这时候想添加一个新特征,或是调试某个执行失败都变得如此困难,逐渐迷失在这混乱的丛林中……如果只具备机器学习知识而缺少工程经验,用这种做实验的方式来开发系统显然是不靠谱的,必须有良好的工程化思维,从总体上把控代码模块结构,才能更好的平衡实验的灵活性与系统开发效率,保证整体的高效运作。 失效的实验性代码路径:这一点也是承接前面,很多时候如果以跑实验的心态来给系统“添砖加瓦”,很可能到后面各种小径交叉的代码库就这么出现了,谁都搞不清楚哪些代码有用哪些是不会执行到的。如何复现别人的实验结果,要用哪些数据集和特征,设置哪些变量、做哪些微调都会成为难解之谜。缺乏好的系统抽象:个人觉得sklearn的各种API设计还算蛮好的,现在很多其它库的高层API都参考了这个准业界标准来实现。文中主要提到在分布式训练中缺乏一个很好的业界标准,比如MapReduce显然是不行的,Parameter Server看起来还算靠谱但也还未成为标准。没有好的抽象标准也就导致了各种库在功能、接口设计时不一致,从而有了以上提到的一系列边界模糊,胶水代码等问题。配置项技术债相对于传统软件系统,机器学习系统的配置项往往会更多更复杂。比如要使用哪些特征、各种数据选择的规则、复杂的预处理和后置处理、模型本身的各种参数设置等等。因此除了工程代码外,配置项的精心设计、评审也成了一个不容忽视的点。否则很容易造成系统在实际运行中频繁出错,难以使用。变化无常的外部世界机器学习系统很多时候都是直接与外部世界的数据做交互,而外部世界总是变化无常。而且机器学习系统本身的输出也会影响到外部世界,从而进一步回馈到机器学习系统的输入中来。比如推荐系统给用户展示的内容会影响用户的点击行为,而这些点击浏览行为又会成为训练数据输入到推荐系统来。如何获取到外部世界变化的信息,进而及时改进甚至自动更新算法模型就成了一个非常重要的问题。在谷歌的这篇原始论文中对各种坑都给了一些解决的建议,归纳总结一下,总体上来说就是要转变团队整体的文化氛围,强调良好的工程思维和实践。一个设计良好的机器学习项目系统中往往真正跟机器学习相关的代码只占了很小的一部分。图片描述新模型固然酷炫,但是机器学习工程实践的总结与推广与它在整个项目中扮演的角色的重要性显然是不成正比的。所以今天我们要着重来讲一下这个方面:机器学习系统的最佳工程实践是什么样的?这时候就要请出谷歌的另外一篇论文《The ML Test Score》了。前一篇论文在具体实践落地方面缺乏细节,在这篇论文里,谷歌总结了28个非常具体的机器学习系统相关工程实践准则,可谓是干货满满,十分接地气。文中给出的28个建议都是针对机器学习系统的,没有包含通用软件工程里那些单元测试,发布流程等内容,在实践中这些传统最佳实践也同样非常重要。这些实践在谷歌内部团队广泛使用,但没有一个团队执行的覆盖率超过80%,因此这些测试点都是非常值得关注并有一定的实践难度的。特征与数据测试特征期望值编写到schema中:很多特征的分布情况或数值期望是有一些先验知识可以去校验的。比如一般人身高都在0-3米的范围内、英语中最常见的词是”the”、整体的词频一般服从幂律分布等。我们可以把这些先验领域知识,或是从训练集中计算出的数学期望值编写在数据schema文件中,后续对于新的输入数据,构建完特征后的模型训练数据以及最终上线使用模型时都能进行自动化的检查,避免因为数据不符合预期而导致的错误预测情况。确保所有的特征都是有用的:在之前的机器学习技术债论文中也有提到研发人员总是倾向于不断往系统中添加新的特征,尤其在上线时间比较紧迫的情况下,缺少细致的特征选择和有效性验证工作。这会导致特征数量越来越多,构建训练集需要花费的时间也越来越长,后续的维护成本也会更高。所以跟业务代码一样,没有帮助的特征也要及时清理,轻装前行。文中给出的方法基本是常见的特征选择法,比如计算特征相关度,使用单独或小批量特征来跑模型看是否有预测能力等。去除性价比低的特征:计算添加任何一个特征都需要消耗资源,包括生成和训练模型开销,模型预测开销,甚至还要考虑到对上游数据的依赖,额外的库函数引入,特征本身的不稳定性等等。对于任何一个特征的添加,都要综合考虑这些开销与它能带来的性能提升来决定是否引入。如果只是非常有限的效果提升,我们应该果断放弃那些过于复杂的特征。特征必须遵循业务规范需求:不同的项目对机器学习系统可以使用的数据可能有不同的规范需求,比如可能有些业务禁止我们使用从用户数据中推演出来的特征。所以我们也需要从代码工程层面把这些规范需求进行实现,以避免训练与线上特征出现不一致或违反了业务规范等问题。数据流水线必须有完善的隐私控制:与上一条类似,机器学习系统从数据源获取用户相关隐私数据时已经通过了相应的控制校验,后续在系统内部流水线做处理时我们也要时刻注意对隐私数据的访问控制。比如各种中间数据集,数据字典的存放与访问控制,上游系统的用户数据删除能够级联更新到机器学习系统的整个链路中,诸如此类需要特别注意的问题。能够快速开发新特征:一个新特征从提出到实现,测试,上线的整个流程所需要花费的时间决定了整个机器系统迭代演进,响应外部变化的速度。要实现这一点,良好的工程结构、不同模块的抽象设计都是非常重要的。文中没有给具体的例子,不过我们可以借鉴sklearn中pipeline模块设计的思想,以及类似FeatureHub这样的开源系统的实现来不断优化完善特征工程实践。为特征工程代码写相应的测试:在实验探索阶段,我们经常会写完一个特征之后,粗略地取样一些数据,大致验证通过后就认为这个特征基本没有问题了。但这其中可能就隐藏了不少bug,而且不像业务代码中的错误,要发现这些bug极其困难。所以必须养成良好的习惯,在特征开发阶段就写好相应的测试代码,确保特征的正确性,后续应对各种系统变更也都能很快通过测试来进行快速验证。图片描述模型开发测试模型说明必须通过review并记录在案:随着机器学习模型技术的发展,各种复杂模型,大量的参数配置往往让模型训练和执行变得无比复杂。加上在多人协同的项目中,很多时候需要使用不同的数据,或者做一些特定的调整来重新评估模型效果,这时候有详细的模型说明记录就显得尤为重要了。除了简单的文本记录,市面上也有不少开源项目(比如ModelDB,MLflow等)专注于帮助开发者管理模型,提高实验的可复现性。模型优化指标与业务指标一致:很多机器学习的应用业务中,实际的业务指标并不能直接拿来作为目标函数优化,比如业务营收,用户满意度等等。因此大多数模型在优化时都会选择一个“代理指标”,比如用户点击率的logloss之类。因此在建模,评估过程中必须要考虑到这个代理指标与真实业务指标是否有比较强的正相关性。我们可以通过各种A/B测试来进行评估,如果代理指标的改进无法提升真正的业务指标,就需要及时进行调整。调优模型超参数:这点相信大家都会做,毕竟各种机器学习教程中都会有很大篇幅讲解如何进行调参来提升模型效果。值得注意的是除了暴力的网格搜索,随机搜索同样简单而效果往往更好。另外还有许多更高级的算法例如贝叶斯优化,SMAC等也可以尝试使用。对于同一个数据集,在使用不同的特征组合,数据抽样手段的时候理论上来说都应该进行参数调优以达到最佳效果。这部分的工作也是比较容易通过自动化工具来实现的。对模型时效性有感知:对于很多输入数据快速变化的业务例如推荐系统,金融应用等,模型的时效性就显得极其重要了。如果没有及时训练更新模型的机制,整个系统的运行效果可能会快速下降。我们可以通过保留多个版本的旧模型,使用A/B测试等手段来推演模型效果与时间推移的关系,并以此来制定整体模型的更新策略。模型应该优于基准测试:对于我们开发的复杂模型,我们应该时常拿它与一些简单的基准模型进行测试比较。如果需要花费大量精力调优的模型效果相比简单的线性模型甚至统计预测都没有明显提升的话,我们就要仔细权衡一下使用复杂模型或做进一步研发改进的必要性了。模型效果在不同数据切片上表现都应达标:在验证模型效果时,我们不能仅依赖于一个总体的验证集或是交叉验证指标。应该在不同维度下对数据进行切分后分别验证,便于我们对模型效果有更细粒度上的理解。否则一些细粒度上的问题很容易被总体统计指标所掩盖,同时这些更细粒度的模型问题也能指导我们做进一步细化模型的调优工作。例如将预测效果根据不同国家的用户,不同使用频率的用户,或者各种类别的电影等方式来分组分析。具体划分方式可以根据业务特点来制定,并同时考察多个重要的维度。我们可以把这些测试固化到发布流程中,确保每次模型更新不会在某些数据子集中有明显的效果下降。将模型的包容性列入测试范围:近些年来也有不少关于模型包容性,公平性相关问题的研究。例如我们在做NLP相关问题时通常会使用预训练的word embedding表达,如果这些预训练时使用的语料与真实应用的场景有偏差,就会导致类似种族,性别歧视的公平性问题出现。我们可以检查输入特征是否与某些用户类别有强关联性,还可以通过模型输出的切分分析,去判断是否对某些用户组别有明显的差异对待。当发现存在这个问题时,我们可以通过特征预处理(例如文中提到的embedding映射转换来消除例如性别维度的差异),模型开发中的各种后置处理,收集更多数据以确保模型能学习到少数群体的特性等方式来解决这个问题。图片描述机器学习基础设施测试模型训练是可复现的:在理想情况下,我们对同一份数据以同样的参数进行模型训练应该能获取到相同的模型结果。这样对于我们做特征工程重构验证等都会非常有帮助。但在实际项目中做到稳定复现却非常困难。例如模型中用到的随机数种子,模型各部分的初始化顺序,多线程/分布式训练导致的训练数据使用顺序的不确定性等都会导致无法稳定复现的问题。因此我们在实际工程中对于这些点都要额外注意。比如凡是用到了随机数的地方都应该暴露接口方便调用时进行随机数种子的设置。除了尽量写能确定性运行的代码外,模型融合也能在一定程度上减轻这个问题。模型说明代码需要相应测试:虽然模型说明代码看起来很像“配置文件”,但其中也可能存在bug,导致模型训练没有按预期方式执行。而且要测试这种模型说明代码也非常困难,因为模型训练往往牵涉到非常多的外部输入数据,而且通常耗时较长。文中提到谷歌将会开源一些相关的框架来帮助做相关的测试,一些具体的测试方法如下:用工具去生成一些随机数据,在模型中执行一个训练步骤,检验梯度更新是否符合预期。模型需要支持从任意checkpoint中恢复,继续执行训练。针对算法特性做简单的检验,比如RNN在运行过程中每次应该会接受输入序列中的一个元素来进行处理。执行少量训练步骤,观察training loss变化,确定loss是按预期呈现不断下降的趋势。用简单数据集让模型去过拟合,迅速达到在训练集上100%的正确率,证明模型的学习能力。尽量避免传统的”golden tests”,就是把之前一个模型跑的结果存下来,以此为基准去测试后面新模型是否达到了预期的效果。从长期来看由于输入数据的变化,训练本身的不稳定性都会导致这个方法的维护成本很高。即使发现了性能下降,也难以提供有用的洞察。机器学习pipeline的集成测试:一个完整的机器学习pipeline一般会包括训练数据的收集,特征生成,模型训练,模型验证,部署和服务发布等环节。这些环节前后都会有一定交互影响,因此需要集成测试来验证整个流程的正确性。这些测试应该包括在持续集成和发布上线环节。为了节约执行时间,对于需要频繁执行的持续集成测试,我们可以选择少量数据进行验证,或者是使用较为简单的模型以便于开发人员快速验证其它环节的修改不会引起问题。而在发布流程中还是需要使用与生产环境尽可能一致的配置来执行整体的集成测试。 模型上线前必须验证其效果:这点看起来应该是大家都会遵守的原则。唯一要注意的是需要同时验证模型的长期效果趋势(是否有缓慢性能下降),以及与最近一个版本对比是否有明显的性能下降。模型能够对单个样本做debug:这个就有点厉害了,当你发现一个奇怪的模型输出时,你怎么去寻找问题的原因?这时候如果有一个方便的工具能让你把这个样本输入到模型中去,单步执行去看模型训练/预测的过程,那将对排查问题带来极大的便利和效率提升。文中提到TensorFlow自带了一个debugger,在实际工作中我们也会使用eli5,LIME之类的工具来做黑盒模型解释,但从方便程度和效果上来说肯定还是比不上框架自带的排查工具。模型上线前的金丝雀测试:这点在传统软件工程中基本也是标配,尽管我们有测试环境,预发布环境的各种离线测试,模型在正式上线时还是需要在生产环境中跑一下简单的验证测试,确保部署系统能够成功加载模型并产出符合预期的预测结果。在此基础上还可以进一步使用灰度发布的方式,逐渐把流量从旧模型迁移到新模型上来,增加一层保障。模型能够快速回滚:与其它传统软件服务一样,模型上线后如果发现有问题应该能够快速,安全回滚。要做到这点必须在开发时就确保各种上下游依赖的兼容性。并且回滚演练本身也应该成为常规发布测试的一环,而不是到出了问题才去手毛脚乱的操作,引出更多意料之外的问题。图片描述监控测试依赖变更推送:机器学习系统一般都会大量依赖于各种外部系统提供的数据,如果这些外部系统的数据格式,字段含义等发生了变化而我们没有感知,很容易就会导致模型训练和预测变得不符合预期。因此我们必须订阅这些依赖系统的变更推送,并确保其它系统的维护者知晓我们在使用他们提供的数据。 训练与线上输入数据分布的一致性:虽然模型内部运作过程极为复杂,难以直接监控其运行时正确性,但是模型的输入数据这块还是比较容易监控的。我们可以用之前定义的特征数据schema文件来对线上输入数据进行检测,当出现较大偏差时自动触发告警,以便及时发现外部数据的变化情况。训练与线上服务时生成的特征值一致:在机器学习项目中经常会出现同一个特征在训练时和在线上使用时所采用的计算生成方式是不一样的。比如在训练时特征是通过处理之前的历史日志计算出来的,而到了线上的时候同样的特征可能来自于用户实时的反馈。或者是训练时采用的特征生成函数是非常灵活,易于做各种实验尝试的,而到了线上则改用固化的优化计算性能版本以降低服务响应时间。在理想情况下,虽然采用了不同的计算方式,但生成的特征值应该是相同的,否则就会出现训练和预测使用的数据有偏移,导致模型效果变差等问题。所以我们需要通过各种手段来监控线上线下数据的一致性。例如可以通过对线上数据进行采样打标记录,来与训练集中的对应条目进行直接比较,计算出有偏差的特征数量,及这些特征中相应的有偏差的样本占比数量。另外也可以通过计算线上线下各个特征的统计分布的差别来衡量是否有这类问题的产生。模型的时效性:这一点与之前的模型测试中的时效性类似,我们可以直接使用其中获取到的模型效果与时间推移关系来推断理想情况下模型训练更新的频率,并以此来对模型持续运行时间进行监控和告警。要注意过长的更新周期会提升模型的维护难度。另外哪怕模型本身更新比较可控,但是模型依赖的数据源也有类似的时效性问题,我们同样需要对其进行监控以免出现数据过期的问题。数值稳定性:在模型训练中可能会在没有任何报错的情况下出现奇怪的NaN,inf值,导致非预期的参数更新甚至最终得到不可用的模型。因此我们需要在训练中监控任何训练数据中包含NaN或者inf的情况进行适当的处理。同时对于各模型参数的取值范围,ReLU层后输出为0的单元数量占比等指标进行检测,一旦超出预期范围就进行告警,便于及时定位排查相关问题。模型性能相关监控:机器学习模型的训练速度,预测响应时间,系统吞吐量,以及内存占用等性能指标对于整体系统的可扩展性来说都是至关重要的。尤其是随着数据量的越来越大,越来越复杂模型的引入,更加剧了各种性能问题的出现。在模型演进,数据变化以及基础架构/计算库的更迭中,需要我们谨慎的评估模型性能变化,进行快速响应。在做性能监控时不但要注意不同代码组件,版本的表现,也要关注数据和模型版本的影响。而且除了容易检测到的性能突变,长期,缓慢的性能下降也需要引起足够的重视。模型预测质量的回归问题:总体的目标是希望新部署的模型相对于过去的模型在预测性能上没有下降。但验证集相对于线上的真实情况来说总是有所区别的,只能作为一个性能评估的参考。文中列举了几种手段来做监控:衡量预测的统计偏差,正常情况下模型的预测偏差应该为0,如果不是则说明其中有一定问题。对于能在作出预测后很快得到正确与否反馈的任务类型,我们可以以此进行实时监控,迅速判断模型效果相比之前是否有下降。对于在模型提供服务时无法快速获取到正确标签类型的任务类型,我们可以使用人工标记的验证数据来比较模型效果的变化。最后总结一下前面提到的各种监控,基本上还有两个共通点,一是各种告警的阈值要做精心选择,过低的阈值容易出现警报泛滥导致根本没人去管的情况,而过高的阈值又会掩盖系统中已经存在的各种问题。二是除了短时间内明显的指标急剧下降外,同时也要关注长期的缓慢的下降,后者更难以发现,应该引起足够的重视。图片描述文章后续还给出了这28个测试指标的具体评分标准,帮助大家在实践中更好的对照,应用这些最佳实践。还有很多在谷歌内部使用这套评分系统的各种反馈,以及他们各个团队的平均得分情况等。图片描述对于这个平均得分情况,作者强力安利了一把谷歌自家的TFX。 图片描述比如基础架构的集成测试,谷歌内部的得分也很低(满分为1的情况下平均为0.2分)。TFX可以方便的实现整个工程的pipeline,自然也很容易在此基础上完成相应的集成测试了。除了TFX,像Uber的Michelangelo,Facebook的FBLearner Flow也都是类似的机器学习平台,虽然没有开源,但都有比较详细的介绍文章可以学习参考。![图片描述][9]这些框架基本可以看作各家公司做机器学习工程的最佳实践总结,总体架构基本都遵循“数据管理->模型训练->模型测试验证->部署上线”这样的流程。不过他们在具体设计实现上各有千秋,例如文中作者认为“线下线上训练数据分布偏差监控”是最为重要但却鲜有实现的一个测试点,在很多项目组都曾经因为缺少这个监控而出现过多次线上故障。因此TFX提供了数据验证模块来专门解决这个问题。而像Uber的Michelangelo则侧重快速开发特征这个点引入了Feature Store模块。在实际应用中我们可以根据不同的业务特点灵活的选择工程方案来进行实现。参考资料:https://papers.nips.cc/paper/…https://ai.google/research/pu…本文作者:观远数据算法天团-zijie关注机器学习与分布式系统方向知乎号:zijie0 | Blog:zijie0.github.io观远数据合伙人 / 知乎5000赞文章博主观远算法天团又称余杭区五常街道数据F4,成员多来自于帝国理工大学、纽约大学、清华大学、浙江大学、上海交大等国内外知名学府,并有阿里大数据产品与技术研发的从业背景。2018年凭借先进的算法能力,在微软大中华区智慧零售(Smart Retail)AI挑战大赛中,两度斩获冠军,并从1500多家创新公司中脱颖而出,全票入选腾讯AI加速器第二期。作为本专栏特邀产出者,观远算法天团将在未来持续为大家分享更多精彩干货,敬请期待!

January 17, 2019 · 1 min · jiezi

适合 分布式系统工程师 的 分布式系统理论

适合 分布式系统工程师 的 分布式系统理论原文Gwen Shapira曾在Cloudera做工程师,现在宣传Kafka,他在Twitter问了以下问题,使我有所思考。我想在分布式理论上有所提升。应该从哪开始?有推荐的书?— Gwen (Chen) Shapira (@gwenshap) August 7, 2014我第一反应是“可以看:FLP论文、paxos论文、Byzantine将军论文”。我推荐的主要阅读材料,如果你贸然去读,你至少要阅读6个月才会有感觉。由此可知,推荐一吨的理论论文让你阅读,这是了解分布式系统的错误的方式(除非你在读博士)。 论文一般是深奥、复杂的,而且需要一系列学习和丰富的经验才能感觉到其贡献、才能把其放到对应的场景(以理解和应用)。工程师了解分布式理论有什么好处?很不幸,几乎没有好的引导文章,来总结、提炼、场景化 分布式系统理论中的重要结论和想法; 特别是 通俗易懂的引导文章 更没有。考虑这样的空白区域,让我想问另一个问题:一个分布式系统工程师应该了解什么样的分布式系统理论?这种情况下,了解一点点理论并不是坏事。我日常工作是一个分布式系统工程师,下面会给出 我认为适合我的基本概念 们。你认为我缺失的请告知我!准备下面四个读物解释了构建分布式系统会遇到的困难。这些读物都勾勒了一些列 抽象而非技术 的困难,分布式系统工程师必须要克服这些困难。这些读物的后面章节有更详细的研究。Distributed Systems for Fun and Profit 是一本小书,它想覆盖分布式系统中的一些基本问题,包括 时钟所起的作用、不同策略的复制。Notes on distributed systems for young bloods - 非理论,而是一个很好的实践,以让你落到实处。A Note on Distributed Systems - 一个经典论文,关于 为什么你不能假装所有远程交互像本地对象一样。The fallacies of distributed computing 分布式计算的8个错误的推论,以提醒系统设计者。你应该知道 安全 和 活力:安全 说的是 永远不会发生坏事。比如,不返回不一致的值 是 一种 安全, 同一时刻不会选出两个 主节点 也是 一种 安全。活力 说的是 好事情终究会发生。比如,对于每个api调用,一个系统终究会返回一个结果,这是一种 活力;保证一次写磁盘最终总能结束,这是一种 活力。失败和时钟分布式系统工程师面对的许多困难可以归结为以下两个原因:进程可能_失败_There is no good way to tell that they have done so进程间怎么共用时钟、什么样的失败可以检测、什么样的算法和原语可以被正确实现,这三者之间有很深的联系。一般情况下,我们假设不同节点绝对无法共用时钟(时刻值或流过了多少时间).你应该知道:失败模型的层次:节点崩溃后关机 -> 节点崩溃后死机(经过无限长时间后才响应) -> 恶意节点 (不遵守约定的规则) 。 各个层次间逐渐将限制放松,你应该知道这些限制.两个节点之间,没有任何共用时钟,你怎么确定一个节点上的一个事件和另一个节点上的另一个事件之间的先后顺序. 这就要阅读Lamport时钟和更一般化的Vector时钟, 也可以阅读Dynamo论文.允许单节点失败对实现正确的分布式系统有多大的冲击?(见下面FLP结论处)时钟的不同模型:同步、部分同部 、 异步失败检测是一个基本问题,失败检测可以平衡准确度和完成度(如果能检测到失败了,则可以容许不那么准确、没完全做完),失败检测也可以解决安全和活力间的冲突。把失败检测作为理论来研究的论文是 Chandra and Toueg’s ‘Unreliable Failure Detectors for Reliable Distributed Systems’. 不过也有一些简短的总结-我特别喜欢this random one from Stanford.容错导致的基本矛盾一个系统容忍一些错误而没有降级 必须能当成 就像这些错误没有发生过一样。这意味着系统的一部分要冗余地工作(同样的功能部署多个节点),冗余是绝对必要的,冗余一般会带来性能和资源的消耗。这就是给一个系统添加冗余的基本矛盾。你应该知道:确保串行单复制的多数派技术. 见 Skeen’s original paper, 不过或许更好的是 Wikipedia条目).(多数派中有一个是主节点,其余为从节点,以主节点接收到的写请求序列为准[即串行],主节点单方面的要求从节点们接受主节点的写请求序列[从节点不得反抗、不得有异议:从节点是诚实的非恶意的、遵守全局规则的、非拜占庭的])两步提交、 三步提交 、Paxos, 以及为什么他们不同于容错.最终一致性、其他技术 以 对系统行为做更弱的保证 为代价 来 设法避开 此矛盾 . 可以看 Dynamo 论文 , 不过 必须要读 Pat Helland的论文 经典 Life Beyond Transactions .基本原语在分布式系统中,很少有约定的基本构建块,更多的是处于形成中的基本构建块。你应该知道下面的问题是什么,并且从哪能找到他们的解决方案:主节点选举 (例如 Bully 算法)一致快照 (比如 这个来自 Chandy and Lamport的经典论文 )一致性 (见上面 2PC 、 Paxos 处)分布式状态机复制 (看Wikipedia 就行, Lampson的 论文 是权威但是太枯燥了).广播 - 同时发送消息给集群原子广播 - 你能发送消息给一集群,使得要么集群中的所有节点都收到了这条信息、要么集群中全部节点都没收到此消息?(这就是原子广播)* Gossip (经典论文) * 因果广播 (也可以看看 Birman和forth ).链式复制 (将节点们放进一个虚拟链表中,从而可以干净的确保写请求的一致性和顺序 ).原始论文对负载中读请求占绝大多数的一系列改良@slfritchie给出的 一个经验报告基础结论有些事实只需要主观理解(不需要关注证明).如果节点间可能丢失消息[:P],那么你不可能 既 实现一致性存储[:C] 又 响应所有时刻的请求[:A]. 这就是 CAP理论.在一个异步系统中,一致性不可能以这样一个途径实现:既a) 总是正确的 ; 又b) 总是能结束 即使只有一个节点可能以 崩溃-*停止 失败 (FLP结论). 在看证明之前,看下我以简明的方式解释FLP结论的论文 Papers We Love SF talk . 建议: 没有理解证明的需要.(一个异步系统中,假设节点崩溃后停止而不是奔溃后又恢复;1、要确保结果总是正确的,2、每次写请求能够在有限时间内返回结果。这两点没法同时满足:这就是FLP结论)一般地,只进行少于2轮的消息传递,不可能达成一致性 .原子广播和一致性,二者的难度精确的相等。更直白的说,如果你能解原子广播,那么你也能解一致性,反之亦然。 Chandra 和 Toueg 证明了这一点, 但是你只需要知道这个论断是成立的。真实系统最重要的、应该不断重复的实践是:读新的、真实的系统的描述,并评价他们设计的决定。 下面是建议的系统:Google:GFSSpannerF1ChubbyBigTableMillWheelOmegaDapperPaxos Made LiveThe Tail At ScaleNot Google:DryadCassandraCephRAMCloudHyperDexPNUTSAzure Data Lake StorePostscript 结尾如果你驯服了这个列表中的所有概念和技术,我很乐意和你聊聊Cloudera的分布式系统工程师职位。 ...

January 10, 2019 · 1 min · jiezi

TiDB 在量化派风控系统中的应用

作者:朱劲松,量化派研发中心系统架构师,主要参与了基础组件开发、API Gateway 等项目,现在致力于公司风控系统相关业务的架构设计和研发。一、公司简介量化派(QuantGroup)创办于 2014 年,是数据驱动的科技公司,是国家高新技术企业。量化派以「MOVE THE WORLD WITH DATA, ENLIGHTEN LIFE WITH AI」(数据驱动世界,智能点亮生活)为愿景,利用人工智能、机器学习、大数据技术。为金融、电商、旅游、出行、汽车供应链等多个领域的合作伙伴提供定制化的策略和模型,帮助提升行业效率。量化派已与国内外超过 300 家机构和公司达成深度合作,致力于打造更加有活力的共赢生态,推动经济的可持续发展。我司从 2017 年年中开始调研 TiDB,并在用户行为数据分析系统中搭建 TiDB 集群进行数据存储,经过一年多的应用和研究,积累了丰富的经验。同时,TiDB 官方推出 2.0 GA 版本,TiDB 愈发成熟,稳定性和查询效率等方面都有很大提升。我们于 2018 年 7 月部署 TiDB 2.0.5 版本,尝试将其应用于风控业务中。风控系统主要是在用户申请放款时,根据风控规则结合模型和用户特征进行实时计算并返回放款结果。二、业务背景风控系统中用到的数据主要可以分为两部分:一类是原始数据,用于分析用户当前的特征指标。一类是快照数据,用于计算历史指定时间点的特征指标,供模型训练使用。原始数据主要分为三种:产生自公司内各个产品线的业务系统数据。爬虫组提供的用户联系人、运营商、消费记录等数据。经过处理后的用户特征数据。由于我们的风控策略中用到了大量的模型,包括神经网络模型,评分模型等,这些模型的训练需要依靠大量的历史订单以及相关的用户特征,为了训练出更多精准、优秀的模型,就需要更多维度的特征,此时特征的准确性就直接影响了模型的训练结果,为此我们在回溯每一个订单的用户在指定时间的特征表现时,就需要用到数据快照。我们可以通过拉链表的方式来实现数据快照功能,简单说就是在每张表中增加三个字段,分别是new_id、start_time、end_time,每一次记录的更新都会产生一条新的数据,同时变更原有记录的end_time,以记录数据的变更历史。通过上面的介绍可以看到,业务数据和爬虫数据本身数据量就很大,再加上需要产生对应的拉链数据,数据量更是成倍增长。假设每条数据自创建后仅变更一次,那拉链表的数据量就已经是原始表的两倍了,而实际生产环境下数据的变更远不止一次。通过上述的介绍,我们总结风控系统下的数据存储需求应满足以下几点:业务数据。业务数据拉链表。爬虫数据,如联系人信息、运营商数据,消费记录等。爬虫数据拉链表。其他数据,如预处理数据等。三、当前方案以前方案主要是采用 HBase 进行数据存储。它的水平扩展很好的解决了数据量大的问题。但是在实际使用中,也存在着比较明显的问题,最明显的就是查询的 API 功能性较弱,只能通过 Key 来获取单条数据,或是通过 Scan API 来批量读取,这无疑在特征回溯时增加了额外的开发成本,无法实现代码复用。在实时计算场景中,为了降低开发成本,对于业务数据的获取则是通过访问线上系统的 MySQL 从库来进行查询;爬虫数据由于统一存放在 HBase 中,计算时需要将用到的数据全量拉取在内存中再进行计算。在回溯场景中,针对业务特征回溯,通过查询订单时间之前的数据进行特征计算,这种方式对于已经变更的数据是无能为力的,只能通过 HBase 里的数据快照来实现,但无形增加了很多的开发工作。3.1 TiDB 为我们打开一片新视野通过上面的介绍,我们知道要构建一个风控系统的实时数仓环境,需要满足下面几个特性:高可用,提供健壮、稳定的服务。支持水平弹性扩展,满足日益增长的数据需求。性能好,支持高并发。响应快。支持标准 SQL,最好是 MySQL 语法和 MySQL 协议,避免回溯时的额外开发。可以发现,TiDB 完美契合我们的每个需求。经过 TiDB 在用户行为数据分析系统中的长期使用,我们已经积累了一定的经验,在此过程中 TiDB 官方也给予了长期的技术支持,遇到的问题在沟通时也能够及时的反馈,而且还与我司技术人员进行过多次技术交流及线下分享,在此我们深表感谢。伴随着风控系统需求的持续增长,我们对整体架构进行了新一轮的优化,新的数据接入及存储架构如图 1。<center>图 1 优化后的架构图</center>通过图 1 可以看到,线上业务系统产生的数据统一存放在 MySQL 中,将这些孤立的数据归集在 TiDB 中,能够提供基于 SQL 的查询服务。通过 binlog 的方式直接从 MySQL 实例进行接入,接入后的数据以两种不同的形式分别存放:一种是去分库分表后的源数据,降低了实时特征计算的实现及维护成本。另一种是以拉链数据形式存储实现数据快照功能。经过调研,针对第一种场景,可以通过阿里的 otter 或者 TiDB 周边工具 Syncer 来快速实现,但对于第二个需求都没有现成的成熟解决方案。最终,我们基于阿里的 canal 进行客户端的定制化开发,分别按照不同的需求拼装合并 SQL 并写入到不同的 TiDB 集群中;同时还可以按需将部分表的数据进行组装并发送至 Kafka,用于准实时分析场景。对于来自爬虫组的数据,我们采用直接消费 Kafka 的方式组装 SQL 写入到 TiDB 即可。在实际是使用中,通过索引等优化,TiDB 完全可以支持线上实时查询的业务需求;在特征回溯时只需要通过增加查询条件就可以获得指定时间的特征结果,大大降低了开发成本。3.2 遇到的问题风控业务中用户特征提取的 SQL 相对都比较复杂,在实际使用中,存在部分 SQL 执行时间比在 MySQL 中耗时高。通过 explain 我们发现,他并没有使用我们创建的索引,而是进行了全表扫描,在进一步分析后还发现 explain 的结果是不确定的。经过与 TiDB 官方技术人员的沟通,我们进行了删除类似索引、analyze table 等操作,发现问题仍然存在。通过图 2 可以看到完全相同的 SQL 语句,其执行结果的差异性。最后按官方建议,我们采用添加 use index 的方式使其强制走索引,执行时间由 4 分钟变成了 < 1s,暂时解决了业务上的需求。<center>图 2 explain 示意图</center>同时 TiDB 技术人员也收集相关信息反馈给了研发人员。在整个问题的处理过程中,TiDB 的技术人员给予了高度的配合和及时的反馈,同时也表现出了很强的专业性,大大减少了问题排查的时间,我们非常感谢。四、展望目前我们已经搭建两个 TiDB 集群,几十个物理节点,百亿级数据量,受益于 TiDB 的高可用构架,上线以来一直稳定运行。如上,TiDB 在我们风控业务中的应用才只是开始,部分业务的迁移还有待进一步验证,但是 TiDB 给我们带来的好处不言而喻,为我们在数据存储和数据分析上打开了一片新视野。后续我们会继续加大对 TiDB 的投入,使其更好地服务于在线分析和离线分析等各个场景。我们也希望进一步增加与 PingCAP 团队的交流与合作,进行更深入的应用和研究,为 TiDB 的发展贡献一份力量。 ...

December 11, 2018 · 1 min · jiezi

TiDB 在小米的应用实践

作者:张良,小米 DBA 负责人;潘友飞,小米 DBA;王必文,小米开发工程师。一、应用场景介绍MIUI 是小米公司旗下基于 Android 系统深度优化、定制、开发的第三方手机操作系统,也是小米的第一个产品。MIUI 在 Android 系统基础上,针对中国用户进行了深度定制,在此之上孕育出了一系列的应用,比如主题商店、小米音乐、应用商店、小米阅读等。 <center>图 1 MIUI Android 系统界面图</center>目前 TiDB 主要应用在:小米手机桌面负一屏的快递业务商业广告交易平台素材抽审平台这两个业务场景每天读写量均达到上亿级,上线之后,整个服务稳定运行;接下来我们计划逐步上线更多的业务场景,小米阅读目前正在积极的针对订单系统做迁移测试。二、TiDB 特点TiDB 结合了传统的 RDBMS 和 NoSQL 的最佳特性,兼容 MySQL 协议,支持无限的水平扩展,具备强一致性和高可用性。具有如下的特性:高度兼容 MySQL,大多数情况下无需修改代码即可从 MySQL 轻松迁移至 TiDB,即使已经分库分表的 MySQL 集群亦可通过 TiDB 提供的迁移工具进行实时迁移。水平弹性扩展,通过简单地增加新节点即可实现 TiDB 的水平扩展,按需扩展吞吐或存储,轻松应对高并发、海量数据场景。分布式事务,TiDB 100% 支持标准的 ACID 事务。真正金融级高可用,相比于传统主从(M-S)复制方案,基于 Raft 的多数派选举协议可以提供金融级的 100% 数据强一致性保证,且在不丢失大多数副本的前提下,可以实现故障的自动恢复(auto-failover),无需人工介入。TiDB 的架构及原理在 官网 里有详细介绍,这里不再赘述。<center>图 2 TiDB 基础架构图</center>三、背景跟绝大数互联网公司一样,小米关系型存储数据库首选 MySQL,单机 2.6T 磁盘。由于小米手机销量的快速上升和 MIUI 负一屏用户量的快速增加,导致负一屏快递业务数据的数据量增长非常快,每天的读写量级均分别达到上亿级别,数据快速增长导致单机出现瓶颈,比如性能明显下降、可用存储空间不断降低、大表 DDL 无法执行等,不得不面临数据库扩展的问题。比如,我们有一个业务场景(智能终端),需要定时从几千万级的智能终端高频的向数据库写入各种监控及采集数据,MySQL 基于 Binlog 的单线程复制模式,很容易造成从库延迟,并且堆积越来越严重。对于 MySQL 来讲,最直接的方案就是采用分库分表的水平扩展方式,综合来看并不是最优的方案,比如对于业务来讲,对业务代码的侵入性较大;对于 DBA 来讲提升管理成本,后续需要不断的拆分扩容,即使有中间件也有一定的局限性。同样是上面的智能终端业务场景,从业务需求看,需要从多个业务维度进行查询,并且业务维度可能随时进行扩展,分表的方案基本不能满足业务的需求。了解到 TiDB 特点之后,DBA 与业务开发沟通确认当前 MySQL 的使用方式,并与 TiDB 的兼容性做了详细对比,经过业务压测之后,根据压测的结果,决定尝试将数据存储从 MySQL 迁移到 TiDB。经过几个月的线上考验,TiDB 的表现达到预期。四、兼容性对比TiDB 支持包括跨行事务、JOIN、子查询在内的绝大多数 MySQL 的语法,可以直接使用 MySQL 客户端连接;对于已用 MySQL 的业务来讲,基本可以无缝切换到 TiDB。二者简单对比如下几方面:功能支持TiDB 尚不支持如下几项:增加、删除主键非 UTF8 字符集视图(即将支持)、存储过程、触发器、部分内置函数Event全文索引、空间索引默认设置字符集、排序规则、sql_mode、lower_case_table_names 几项默认值不同。事务TiDB 使用乐观事务模型,提交后注意检查返回值。TiDB 限制单个事务大小,保持事务尽可能的小。TiDB 支持绝大多数的 Online DDL。另,一些 MySQL 语法在 TiDB 中可以解析通过,不会产生任何作用,例如: create table 语句中 engine、partition 选项都是在解析后忽略。详细信息可以访问官网:https://pingcap.com/docs-cn/sql/mysql-compatibility/ 。五、压测5.1 目的通过压测 TiDB 了解一下其 OLTP 性能,看是否满足业务要求。5.2 机器配置组件实例数量CPU 型号内存磁盘版本操作系统TiDB3Intel(R) Xeon(R) CPU E5-2620 v3 @ 2.40GHz128GSSD Raid 52.0.3CentOS Linux release 7.3.1611PD3Intel(R) Xeon(R) CPU E5-2620 v3 @ 2.40GHz128GSSD Raid 52.0.3CentOS Linux release 7.3.1611TiKV4Intel(R) Xeon(R) CPU E5-2620 v3 @ 2.40GHz128GSSD Raid 52.0.3CentOS Linux release 7.3.16115.3 压测内容以及结果5.3.1 标准 Select 压测ThreadsQPSLatency (avg / .95 / max)812650.810.63 / 0.90 / 15.621621956.210.73 / 1.50 / 15.713231534.81.01 / 2.61 / 25.1664382171.67 / 5.37 / 49.8012839943.053.20 / 8.43 / 58.6025640920.646.25 / 13.70 / 95.13<center>图 3 标准 Select 压测图</center>5.3.2 标准 OLTP 压测ThreadsTPSQPSLatency (avg / .95 / max)8428.98578.0918.65 / 21.89 / 116.0616731.6714633.3521.86 / 25.28 / 120.59321006.4320128.5931.79 / 38.25 / 334.92641155.4423108.955.38 / 71.83 / 367.531281121.5522431114.12 / 161.51 / 459.03256941.2618825.1271.94 / 369.77 / 572.88<center>图 4 标准 OLTP 压测图</center>5.3.3 标准 Insert 压测ThreadsQPSLatency (avg / .95 / max)83625.752.20 / 2.71 / 337.94166527.242.45 / 3.55 / 160.843210307.663.10 / 4.91 / 332.416413662.834.68 / 7.84 / 467.5612815100.448.47 / 16.41 / 278.2325617286.8614.81 / 25.74 / 3146.52<center>图 5 标准 Insert 压测图</center>通过压测发现 TiDB 稳定性上与预期稍有差别,不过压测的 Load 会明显高于生产中的业务 Load,参考低 Threads 时 TiDB 的表现,基本可以满足业务对 DB 的性能要求,决定灰度一部分 MySQL 从库读流量体验一下实际效果。六、迁移过程整个迁移分为 2 大块:数据迁移、流量迁移。6.1 数据迁移数据迁移分为增量数据、存量数据两部分。对于存量数据,可以使用逻辑备份、导入的方式,除了传统的逻辑导入外,官方还提供一款物理导入的工具 TiDB Lightning。对于增量备份可以使用 TiDB 提供的 Syncer (新版已经更名为 DM - Data Migration)来保证数据同步。Syncer 结构如图 6,主要依靠各种 Rule 来实现不同的过滤、合并效果,一个同步源对应一个 Syncer 进程,同步 Sharding 数据时则要多个 Syncer 进程。<center>图 6 Syncer 结构图</center>使用 Syncer 需要注意:做好同步前检查,包含 server-id、log_bin、binlog_format 是否为 ROW、binlog_row_image 是否为 FULL、同步相关用户权限、Binlog 信息等。使用严格数据检查模式,数据不合法则会停止。数据迁移之前最好针对数据、表结构做检查。做好监控,TiDB 提供现成的监控方案。对于已经分片的表同步到同一个 TiDB 集群,要做好预先检查。确认同步场景是否可以用 route-rules 表达,检查分表的唯一键、主键在数据合并后是否冲突等。6.2 流量迁移流量切换到 TiDB 分为两部分:读、写流量迁移。每次切换保证灰度过程,观察周期为 1~2 周,做好回滚措施。读流量切换到 TiDB,这个过程中回滚比较简单,灰度无问题,则全量切换。再将写入切换到 TiDB,需要考虑好数据回滚方案或者采用双写的方式(需要断掉 Syncer)。七、集群状况7.1 配置集群配置采用官方推荐的 7 节点配置,3 个 TiDB 节点,3 个 PD 节点,4 个 TiKV 节点,其中每个 TiDB 与 PD 为一组,共用一台物理机。后续随着业务增长或者新业务接入,再按需添加 TiKV 节点。7.2 监控监控采用了 TiDB 的提供的监控方案,并且也接入了公司开源的 Falcon,目前整个集群运行比较稳定,监控如图 7。 <center>图 7 监控图</center>八、遇到的问题、原因及解决办法问题原因及解决办法在一个 DDL 里不能对多个列或者多个索引做操作。ADD/DROP INDEX/COLUMN 操作目前不支持同时创建或删除多个索引或列,需要拆分单独执行,官方表示 3.0 版本有计划改进。部分操作符查询优化器支持不够好,比如 or 操作符会使用 TableScan,改写成 union all 可避免。官方表示目前使用 or 操作符确实在执行计划上有可能不准确,已经在改进计划中,后续 3.0 版本会有优化。重启一个 PD 节点的时候,业务能捕捉到 PD 不可用的异常,会报 PD server timeout 。因为重启的是 Leader 节点,所以重启之前需要手动切换 Leader,然后进行重启。官方建议这里可以通过重启前做 Leader 迁移来减缓,另外后续 TiDB 也会对网络通讯相关参数进行梳理和优化。建表语句执行速度相比 MySQL 较慢多台 TiDB 的时候,Owner 和接收 create table 语句的 TiDB Server 不在一台 Server 上时,可能比 MySQL 慢一些,每次操作耗时在 0.5s 左右,官方表示会在后续的版本中不断完善。pd-ctl 命令行参数解析严格,多一个空格会提示语法错误。官方表示低版本中可能会有这个问题,在 2.0.8 及以上版本已经改进。tikv-ctl 命令手动 compact region 失败。在低版本中通常是因为 tikv-ctl 与集群版本不一致导致的,需要更换版本一致的 tikv-ctl,官方表示在 2.1 中已经修复。大表建索引时对业务有影响官方建议在业务低峰期操作,在 2.1 版本中已经增加了操作优先级以及并发读的控制,情况有改善。存储空间放大问题该问题属于 RocksDB,RocksDB 的空间放大系数最理想的值为 1.111,官方建议在某些场景下通过 TiKV 开启 RocksDB 的 dynamic-level-bytes 以减少空间放大。九、后续和展望目前 TiDB 在小米主要提供 OLTP 服务,小米手机负一屏快递业务为使用 TiDB 做了一个良好的开端,而后商业广告也有接入,2 个业务均已上线数月,TiDB 的稳定性经受住了考验,带来了很棒的体验,对于后续大体的规划如下:MIUI 生态业务中存在大量的类似场景的业务,后续将会与业务开发积极沟通,从 MySQL 迁移到 TiDB。针对某些业务场景,以资源合理利用为目标,推出归档集群,利用 Syncer 实现数据归档的功能。数据分析,结合 TiDB 提供的工具,将支持离线、实时数据分析支持。将 TiDB 的监控融合到小米公司开源的监控系统 Falcon 中。十、致谢非常感谢 TiDB 官方在迁移及业务上线期间给予我们的支持,为每一个 TiDB 人专业的精神、及时负责的响应点赞。更多 TiDB 用户实践: https://www.pingcap.com/cases-cn/ ...

December 4, 2018 · 3 min · jiezi

TiDB 2.1: Battle-Tested for an Unpredictable World

TiDB 是由 PingCAP 开发的分布式关系型数据库,今天我们很高兴地推出 TiDB 2.1 正式版,提供更丰富的功能、更好的性能以及更高的可靠性。回顾 2.0 版本今年 4 月份我们发布了 TiDB 2.0 版本,提升了稳定性、性能以及可运维性,这个版本在接下来的半年中得到了广泛的关注和使用。迄今为止 TiDB 已经在 数百家用户 的生产环境中稳定运行,涉及互联网、游戏、金融、保险、制造业、银行、证券等多个行业,最大集群包含数百个节点及数百 TB 数据,业务场景包含纯 OLTP、纯 OLAP 以及混合负载。另外,既有使用 TiDB 当做关系数据库的场景,也有只用 TiKV 作为分布式 Key Value 存储的场景。这几个月,在这些场景中,我们亲历了跨机房容灾需求、亲历了几十万级别的高吞吐业务、亲历了双十一的流量激增、亲历了高并发点查、高并发写入与上百行复杂 SQL 的混合负载、见到过多次的硬件/网络故障、见到过操作系统内核/编译器的 Bug。简而言之,我们的世界充满了未知,而分布式关系型数据库这样一种应用广泛、功能丰富且非常关键的基础软件,最大的困难就是这些“未知”。在 2.1 版本中,我们引入了不少新的特性来抵御这些未知,适配各种复杂的场景,提升性能和稳定性,帮助我们的用户更好地支撑复杂的业务。新特性更全面的 Optimizer在 2.1 版本中,我们对 TiDB 的 Cost-based Optimizer 做了改进,希望这个优化器能够处理各种复杂的 Query,尽量少的需要人工介入去处理慢 SQL。例如对 Index Join 选择索引、外表的优化,对关联子查询的优化,显著地提升了复杂 SQL 的查询效率。当然,除了自动的查询优化之外,2.1 也增加了更多的手动干预机制,比如对 Join 算子的 Hint、Update/Delete 语句的 Hint。用户可以在优化器没有指定合适的计划时,手动干预结果或者是用来确保查询计划稳定。更强大的执行引擎在 2.1 版本中,我们对部分物理算子的执行效率进行了优化,特别是对 Hash Aggregation 和 Projection 这两个算子进行了并行化改造,另外重构了聚合算子的运行框架,支持向量化计算。得益于这些优化,在 TPC-H 这种 OLAP 的测试集上,2.1 比 2.0 版本有了显著的性能提升,让 2.1 版本更好的面对 HTAP 应用场景。Raft 新特性在 2.1 版本中,我们引入了 Raft PreVote、Raft Learner、Raft Region Merge 三个新特性:PreVote 是在 Raft Group Member 发起投票之前,预先检查是否能被其他成员所支持,以避免集群中被网络隔离的节点重新接入集群中的时候引发性能抖动,提升集群稳定性。2.1 版本已经支持 PreVote 功能,并默认打开。Learner 是只同步数据不参与投票的 Raft Group Member。在新加副本的时候,首先增加 Learner 副本,以避免添加副本过程中,部分 TiKV 节点故障引发丢失多数副本的情况发生,以提升集群的安全性。2.1 版本已经支持 Learner 功能,并默认打开。Region Merge 用于将多个过小的 Region 合并为一个大的 Region,降低集群的管理成本,对于长期运行的集群以及数据规模较大的集群的性能、稳定性有帮助。2.1 版本已经支持 Region Merge 功能,尚未默认打开。这些新特性的引入,有助于提升存储集群尤其是大规模集群的稳定性和性能。自动更新统计信息统计信息的及时性对查询计划的正确性非常重要。在 2.1 版本中,我们提供了基于 Query Feedback 的动态增量更新机制。在制定查询计划时,会根据现有的统计信息估算出需要处理的数据量;在执行查询计划时,会统计出真实处理的数据量。TiDB 会根据这两个值之间的差距来更新统计信息,包括直方图和 CM-Sketch。在我们的测试中,对于一个完全没有统计信息的表,经过十轮左右的更新,可以达到统计信息基本稳定的状态。这对于维持正确的查询计划非常重要。除了动态增量更新之外,我们对自动全量 Analyze 也提供了更多支持,可以通过 系统变量 指定做自动 Analyze 的时间段。并行 DDLTiDB 所有的 DDL 操作都是 Online 进行,不过在 2.0 以及之前的版本中,所有的 DDL 操作都是串行执行,即使 DDL 所操作的表之间没有关联。比如在对 A 表 Add Index 时候,想创建一个 B 表,需要等待 Add Index 操作结束。这在一些场景下对用户使用造成了困扰。在 2.1 版本中,我们对 DDL 流程进行拆分,将 Add Index 操作和其他的 DDL 操作的处理分开。由于在 TiDB 的 DDL 操作中,只有 Add Index 操作需要去回填数据,耗时较长,其他的 DDL 操作正常情况下都可以在秒级别完成,所以经过这个拆分,可以保证大多数 DDL 操作能够不需要等待,直接执行。Explain 和 Explain AnalyzeExplain 对于理解查询计划至关重要,2.1 之前的版本,TiDB 追随 MySQL 的 Explain 输出格式来展示查询计划。但是当 SQL 比较复杂时,MySQL 的格式并不利于展示算子之间的层级关系,不利于用户定位问题。2.1 版本中,我们使用缩进来展示算子之间的层级关系,对每个算子的详细信息也做了优化,希望整个查询计划一目了然,帮助用户尽快定位问题。这篇文档 可以帮助用户了解 TiDB 的查询计划。用户除了通过 Explain 语句查看查询计划之外,在 2.1 版本中还可以通过 Explain Analyze 语句查看语句的运行时信息,包括每个算子运行时的处理时间以及处理的数据量。这样可以通过实际的运行结果,拿到更加精确的信息。热点调度热点是分布式系统最大的敌人之一,并且用户的业务场景复杂多变,让热点问题捉摸不定,也是最狡猾的敌人。2.1 版本中,我们一方面增强热点检测能力,尽可能详细地统计系统负载,更快的发现热点;另一方面优化热点调度策略,用尽可能小的代价,尽快地打散热点。同时我们也提供了手动分裂 Region 的接口,让用户在特殊场景下将单点瓶颈手动分裂开,再由 PD 进行负载均衡。高效的 GC 机制2.1 版本对 GC(垃圾回收) 模块进行优化。一方面减少对线上的写入的影响,另一方面加快了空间回收速度。在内部测试场景中,删除一个 1TB 的表,新的 GC 机制能够在 10 秒内回收 99% 左右的空间。更好的性能OLTP我们针对 OLTP 场景中,点查占多数的特点进行了针对性的优化。当通过 Unique Key 或者 Primary Key 进行数据访问时,在优化器和执行引擎中都做了改进,使得语句的执行效率更高,通过 2.1 和 2.0 版本的 Sysbench 对比 可以看到,点查性能提升 50%。OLAP发布 2.0 的时候,我们同时发布了在 TPC-H Scale 50 的场景中 2.0 和 1.0 的对比结果。其中大多数 Query 都有数量级的提升,部分 Query 在 1.0 中跑不出结果,在 2.0 中可以顺利运行。不过对于 Query17 和 Query18,运行时间依然很长。我们在相同的场景下,对 2.1 和 2.0 进行了 对比测试。从下图可以看到(纵坐标是 Query 的响应时间,越低越好),之前的两个慢 Query 的运行时间大幅缩短,其他的 Query 也有一定程度的提升。这些提升一方面得益于查询优化器以及执行引擎的改进,另一方面 得益于 TiKV 对连续数据扫描的性能优化。完善的生态工具为了让用户更方便的使用 TiDB,我们提供了三个工具:TiDB Lightning 用于将全量数据导入到 TiDB 中,这个工具可以提升全量数据导入速度,目前内部测试场景中,一小时可以导入 100GB 数据。TiDB Binlog 用于将 TiDB 中的数据更新实时同步到下游系统中,可以用于做主从集群同步或者是将 TiDB 中的数据同步回 MySQL。TiDB DM(Data-Migration)用于将 MySQL/MariaDB 中的数据通过 Binlog 实时同步到 TiDB 集群中,并且提供 Binlog 数据转换功能,可以将 Binlog 中的表/库名称进行修改,或者是对数据内容本身做修改和裁剪。上述三个工具可以将 TiDB 和周边的系统打通,既能将数据同步进 TiDB,又可以将数据同步出来。所以无论是迁移、回退还是做数据热备,都有完整的解决方案。Open Source Community我们相信战胜“未知”最好的武器就是社区的力量,基础软件需要坚定地走开源路线。为了让社区更深入的了解 TiDB 的技术细节并且更好地参与到项目中来,我们今年已经完成超过 20 篇源码阅读文章,项目的设计文档(TiDB 和 TiKV)已经在 GitHub 上面公开出来,项目的开发过程也尽量通过 Github Issue/Project 向社区展示。一些 Feature 设计方案的讨论也会通过在线视频会议的方式方便社区参与进来,这里 可以看到会议安排。从 TiDB 2.0 版发布到现在的半年多时间,TiDB 开源社区新增了 87 位 Contributor,其中 杜川 成为了 TiDB Committer,他已经贡献了 76 次 PR,还有一些活跃的 Contributor 有希望成为下一批 Committer。在这里我们对社区贡献者表示由衷的感谢,希望更多志同道合的人能加入进来,也希望大家在 TiDB 这个开源社区能够有所收获! ...

November 30, 2018 · 2 min · jiezi

捕获和增强原生系统的可观测性来发现错误

作者:唐刘在对 TiDB 进行 Chaos 实践的时候,我一直在思考如何更好的发现 TiDB 整个系统的故障。最开始,我们参考的就是 Chaos Engineering 里面的方式,观察系统的稳定状态,注入一个错误,然后看 metrics 上面有啥异常,这样等实际环境中出现类似的 metrics,我们就知道发现了什么故障。但这套机制其实依赖于如何去注入错误,虽然现在我们已经有了很多种错误注入的方式,但总有一些实际的情况我们没有料到。所以后来我们又考虑了另外的一种方式,也就是直接对 metrics 历史进行学习,如果某一段时间 metrics 出现了不正常的波动,那么我们就能报警。但这个对我们现阶段来说难度还是有点大,只使用了几种策略,对 QPS,Latency 这些进行了学习,并不能很好的定位到具体出了什么样的问题。所以我一直在思考如何更好的去发现系统的故障。最近,刚好看到了 OSDI 2018 一篇 Paper,Capturing and Enhancing In Situ System Observability for Failure Detection,眼睛一亮,觉得这种方式也是可以来实践的。大家都知道,在生产环境中,故障是无处不在,随时可能发生的,譬如硬件问题,软件自身的 bug,或者运维使用了一个错误的配置这些。虽然多数时候,我们的系统都做了容错保护,但我们还是需要能尽快的发现故障,才好进行故障转移。但现实世界并没有那么美好,很多时候,故障并不是很明显的,譬如整个进程挂掉,机器坏掉这些,它们处于一种时好时坏的状态,我们通常称为「Gray Failure」,譬如磁盘变慢了,网络时不时丢包。这些故障都非常隐蔽,很难被发现。如果单纯的依赖外部的工具,其实很难检测出来。上面是作者举的一个 ZooKeeper 的例子,client 已经完全不能跟 Leader 进行交互了,但是 Leader 却仍然能够给 Follower 发送心跳,同时也能响应外面 Monitor 发过来的探活命令。如果从外面的 Monitor 看来,这个 ZooKeeper 集群还是正常的,但其实它已经有故障了。而这个故障其实 client 是知道的,所以故障检测的原理很简单,从发起请求的这一端来观察,如果发现有问题,那就是有故障了。而这也是这篇论文的中心思想。在论文里面,作者认为,任何严重的 Gray Failure 都是能够被观察到的,如果发起请求的这边遇到了错误,自然下一件事情就是将这个错误给汇报出去,这样我们就知道某个地方出现了故障。于是作者开发了 Panorama 这套系统,来对故障进行检测。整体架构先来说说 Panorama 一些专业术语。Panorama 整体结构如下:Panorama 通过一些方式,譬如静态分析代码进行代码注入等,将 Observer 跟要观察的 Subject 进行绑定,Observer 会将 Subject 的一些信息记录并且汇报给本地的一个 Local Observation Store(LOS)。本地一个决策引擎就会分析 LOS 里面的数据来判断这个组件的状态。如果多个 LOS 里面都有对某个 Subject 的 observation,那么 LOS 会相互交换,用来让中央的 verdict 更好的去判断这个 component 的状态。故障判定而用来判断一个 component 是不是有故障也比较容易,采用的是一种大多数 bounded-look-back 算法。对于一个 subject,它可能会有很多 observations,首先我们会对这些 observations 按照 observer 进行分组,对每组单独进行分析。在每个组里面,observations 会按照时间从后往前检查,并且按照 context 进行聚合。如果一个被观察的 observation 的 status 跟记录前面相同 context 的 observation status 状态不一样,就继续 loop-back,直到遇到一个新的 status。对于一个 context,如果最后的状态是 unhealthy 或者 healthy 的状态没有达到多数,就会被认为是 unhealthy 的。通过这种方式,我们在每组里面得到了每个 context 的状态,然后又会在多个组里面进行决策,也就是最常用的大多数原则,哪个状态最多,那么这个 context 对应的状态就是哪一个。这里我们需要额外处理下 PENDING 这个状态,如果当前状态是 HEALTHY 而之前老的状态是 PENDING,那么 PENDING 就会变成 HEALTHY,而如果一直是 PENDING 状态并超过了某个阈值,就会退化成 UNHEALTHY。Observability这里再来说说 Observability 的模式。对于分布式系统来说,不同 component 之间的交互并不是同步的,我们会面临如下几种情况:如果两个组件 C1 和 C2 是同步交互,那么当 C1 给 C2 发送请求,我们就完全能在 C1 这一端知道这次请求成功还是失败了,但是对于非同步的情况,我们可能面临一个问题,就是 C1 给 C2 发了请求,但其实这个请求是放到了异步消息队列里面,但 C1 觉得是成功了,可是后面的异步队列却失败了。所以 Panorama 需要有机制能正确处理上面多种情况。为了能更好的从 component 上面得到有用的 observations,Panorama 会用一个离线工具对代码进行静态分析,发现一些关键的地方,注入钩子,这样就能去汇报 observations 了。通常运行时错误是非常有用的能证明有故障的证据,但是,并不是所有的错误都需要汇报,Panorama 仅仅会关系跨 component 边界产生的错误,因为这也是通过发起请求端能观察到的。Panorama 对于这种跨域的函数调用称为 observation boundaries。对于 Panorama 来说,第一件事情就是定位 observation boundaries。通常有两种 boundaries,进程间交互和线程间交互。进程间交互通常就是 socket I/O,RPC,而线程间则是在一个进程里面跨越线程的调用。这些 Panorama 都需要分析出来。当定位了 observation boundaries 之后,下一件事情就是确定 observer 和 subject 的标识。譬如对于进程间交互的 boundaries,observer 的标识就可能是这个进程在系统里面的唯一标识,而对于 subject,我们可以用 method 名字,或者是函数的一个参数,类里面的一个字段来标识。然后我们需要去确定 observation points,也就是观测点。通常这些点就是代码处理异常的地方,另外可能就是一些正常处理返回结果但会对外报错的地方。上面就是一个简单分析代码得到 observation points 的例子,但这个仍然是同步的,对于 indirection 的,还需要额外处理。对于异步请求,我们知道,通过发出去之后,会异步的处理结果,所以这里分为了两步,叫做 ob-origin 和 ob-sink。如下:对于 ob-origin,代码分析的时候会先给这个 observation 设置成 PENDING 状态,只有对应的 ob-sink 调用并且返回了正确的结果,才会设置成 HEALTHY。因为 ob-origin 和 ob-sink 是异步的,所以代码分析的时候会加上一个特殊的字段,包含 subject 的标识和 context,这样就能让 ob-origin 和 ob-sink 对应起来。小结上面大概介绍了 Panorama 的架构以及一些关键的知识点是如何实现的,简单来说,就是在一些关键代码路径上面注入 hook,然后通过 hook 对外将相关的状态给汇报出去,在外面会有其他的分析程序对拿到的数据进行分析从而判定系统是否在正常工作。它其实跟加 metrics 很像,但 metrics 只能看出哪里出现了问题,对于想更细致定位具体的某一个问题以及它的上下文环境,倒不是特别的方便。这点来说 Panorama 的价值还是挺大的。Panorama 的代码已经开源,总的来说还是挺简单的,但我没找到核心的代码分析,注入 hook 这些,有点遗憾。但理解了大概原理,其实先强制在代码写死也未尝不可。另一个比较可行的办法就是进行在代码里面把日志添加详细,这样就不用代码注入了,而是在外面写一个程序来分析日志,其实 Panorama 代码里面提供了日志分析的功能,为 ZooKeeper 来设计的,但作者自己也说到,分析日志的效果比不上直接在代码里面进行注入。那对我们来说,有啥可以参考的呢?首先当然是这一套故障检查的理念,既然 Panorama 已经做出来并且能发现故障量,自然我们也可以在 TiDB 里面实施。因为我们已经有在 Go 和 Rust 代码里面使用 fail 来进行错误注入的经验,所以早期手写监控代码也未尝不可,但也可以直接完善日志,提供一个程序来分析日志就成。如果你对这块感兴趣,想把 Panorama 相关的东西应用到 TiDB 中来,欢迎联系我 tl@pingcap.com。 ...

November 23, 2018 · 2 min · jiezi

阿里如何做到百万量级硬件故障自愈?

随着阿里大数据产品业务的增长,服务器数量不断增多,IT运维压力也成比例增大。各种软、硬件故障而造成的业务中断,成为稳定性影响的重要因素之一。本文详细解读阿里如何实现硬件故障预测、服务器自动下线、服务自愈以及集群的自平衡重建,真正在影响业务之前实现硬件故障自动闭环策略,对于常见的硬件故障无需人工干预即可自动闭环解决。1.背景1.1.面临挑战对于承载阿里巴巴集团95%数据存储及计算的离线计算平台MaxCompute,随着业务增长,服务器规模已达到数十万台,而离线作业的特性导致硬件故障不容易在软件层面被发现,同时集团统一的硬件报障阈值常常会遗漏一些对应用有影响的硬件故障,对于每一起漏报,都对集群的稳定性构成极大的挑战。针对挑战,我们面对两个问题:硬件故障的及时发现与故障机的业务迁移。下面我们会围绕这两个问题进行分析,并详细介绍落地的自动化硬件自愈平台——DAM。在介绍之前我们先了解下飞天操作系统的应用管理体系——天基(Tianji)。1.2.天基应用管理MaxCompute是构建在阿里数据中心操作系统——飞天(Apsara)之上,飞天的所有应用均由天基管理。天基是一套自动化数据中心管理系统,管理数据中心中的硬件生命周期与各类静态资源(程序、配置、操作系统镜像、数据等)。而我们的硬件自愈体系正是与天基紧密结合,利用天基的Healing机制构建面向复杂业务的硬件故障发现、自愈维修闭环体系。透过天基,我们可以将各种物理机的指令(重启、重装、维修)下发,天基会将其翻译给这台物理机上每个应用,由应用根据自身业务特性及自愈场景决策如何响应指令。2.硬件故障发现2.1.如何发现我们所关注的硬件问题主要包含:硬盘、内存、CPU、网卡电源等,下面列举对于常见硬件问题发现的一些手段和主要工具:在所有硬件故障中,硬盘故障占比50%以上,下面分析一下最常见的一类故障:硬盘媒介故障。通常这个问题表象就是文件读写失败/卡住/慢。但读写问题却不一定是媒介故障产生,所以我们有必要说明一下媒介故障的在各层的表象。a. 系统日志报错是指在/var/log/messages中能够找到类似下面这样的报错Sep 3 13:43:22 host1.a1 kernel: : [14809594.557970] sd 6:0:11:0: [sdl] Sense Key : Medium Error [current]Sep 3 20:39:56 host1.a1 kernel: : [61959097.553029] Buffer I/O error on device sdi1, logical block 796203507b. tsar io指标变化是指rs/ws/await/svctm/util等这些指标的变化或突变,由于报错期间会引起读写的停顿,所以通常会体现在iostat上,继而被采集到tsar中。在tsar io指标中,存在这样一条规则让我们区分硬盘工作是否正常 qps=ws+rs<100 & util>90,假如没有大规模的kernel问题,这种情况一般都是硬盘故障引起的。c. 系统指标变化通常也由于io变化引起,比如D住引起load升高等。d. smart值跳变具体是指197(Current_Pending_Sector)/5(Reallocated_Sector_Ct)的跳变。这两个值和读写异常的关系是:媒介读写异常后,在smart上能观察到197(pending) +1,表明有一个扇区待确认。随后在硬盘空闲的时候,他会对这个197(pending)中攒的各种待确认扇区做确认,如果读写通过了,则197(pending) -1,如果读写不通过则 197(pending)-1 且 5(reallocate)+1。总结下来,在整条报错链路中,只观察一个阶段是不够的,需要多个阶段综合分析来证明硬件问题。由于我们可以严格证明媒介故障,我们也可以反向推导,当存在未知问题的时候能迅速地区分出是软件还是硬件问题。上述的工具是结合运维经验和故障场景沉淀出来,同时我们也深知单纯的一个发现源是远远不够的,因此我们也引入了其他的硬件故障发现源,将多种检查手段结合到一起来最终确诊硬件故障。2.2.如何收敛上一章节提到的很多工具和路径用来发现硬件故障,但并不是每次发现都一定报故障,我们进行硬件问题收敛的时候,保持了下面几个原则:指标尽可能与应用/业务无关:有些应用指标和硬件故障相关性大,但只上监控,不作为硬件问题的发现来源。 举一个例子,当io util大于90%的时候硬盘特别繁忙,但不代表硬盘就存在问题,可能只是存在读写热点。我们只认为io util>90且iops<30 超过10分钟的硬盘可能存在硬件问题。采集敏感,收敛谨慎:对于可能的硬件故障特征都进行采集,但最终自动收敛分析的时候,大多数采集项只做参考,不作为报修依据。 还是上一个硬盘io util的例子,如果单纯出现io util>90且iops<30的情况,我们不会自动报修硬盘,因为kernel问题也可能会出现这个情况。只有当 smartctl超时/故障扇区 等明确故障项出现后,两者关联才确诊硬盘故障,否则只是隔离观察,不报修。2.3.覆盖率以某生产集群,在20xx年x月的IDC工单为例,硬件故障及工单统计如下:去除带外故障的问题,我们的硬件故障发现占比为97.6%。3.硬件故障自愈3.1 自愈流程针对每台机器的硬件问题,我们会开一个自动轮转工单来跟进,当前存在两套自愈流程:【带应用维修流程】和【无应用维修流程】,前者针对的是可热拔插的硬盘故障,后者是针对余下所有的整机维修硬件故障。在我们的自动化流程中,有几个比较巧妙的设计:a. 无盘诊断对于宕机的机器而言,无法进无盘(ramos)才开【无故宕机】维修工单,这样能够大量地减少误报,减少服务台同学负担。无盘中的压测可以完全消除当前版本的kernel或软件的影响,真实地判断出硬件是否存在性能问题。b. 影响面判断/影响升级对于带应用的维修,我们也会进行进程是否D住的判断。如果存在进程D住时间超过10分钟,我们认为这个硬盘故障的影响面已扩大到了整机,需要进行重启消除影响。在重启的时候如果出现了无法启动的情况,也无需进行人工干预,直接进行影响升级,【带应用维修流程】直接升级成【无应用维修流程】。c. 未知问题自动化兜底在运行过程中,会出现一些机器宕机后可以进无盘,但压测也无法发现任何硬件问题,这个时候就只能让机器再进行一次装机,有小部分的机器确实在装机过程中,发现了硬件问题继而被修复了。d. 宕机分析整个流程巧妙的设计,使得我们在处理硬件故障的时候,同时具备了宕机分析的能力。不过整机流程还以解决问题为主导向,宕机分析只是副产品。同时,我们也自动引入了集团的宕机诊断结果进行分析,达到了1+1>2的效果。3.2.流程统计分析如果是同样的硬件问题反复触发自愈,那么在流程工单的统计,能够发现问题。例如联想RD640的虚拟串口问题,在还未定位出根因前,我们就通过统计发现了:同个机型的机器存在反复宕机自愈的情况,即使机器重装之后,问题也还是会出现。接下来我们就隔离了这批机器,保障集群稳定的同时,为调查争取时间。3.3.业务关联误区事实上,有了上面这套完整的自愈体系之后,某些业务上/kernel上/软件上需要处理的问题,也可以进入这个自愈体系,然后走未知问题这个分支。其实硬件自愈解决业务问题,有点饮鸩止渴,容易使越来越多还没想清楚的问题,尝试通过这种方式来解决兜底。当前我们逐步地移除对于非硬件问题的处理,回归面向硬件自愈的场景(面向软件的通用自愈也有系统在承载,这类场景与业务的耦合性较大,无法面向集团通用化),这样也更利于软硬件问题分类和未知问题发现。4.架构演进4.1.云化最初版本的自愈架构是在每个集群的控制机上实现,因为一开始时候运维同学也是在控制机上处理各种问题。但随着自动化地不断深入,发现这样的架构严重阻碍了数据的开放。于是我们采用中心化架构进行了一次重构,但中心化架构又会遇到海量数据的处理问题,单纯几个服务端根本处理不过来。因此我们对系统进一步进行分布式服务化的重构,以支撑海量业务场景,将架构中的各个模块进行拆解,引入了 阿里云日志服务(sls)/阿里云流计算(blink)/阿里云分析数据库(ads) 三大神器,将各个采集分析任务由云产品分担,服务端只留最核心的硬件故障分析和决策功能。下面是DAM1与DAM3的架构对比4.2.数据化随着自愈体系的不断深入,各阶段的数据也有了稳定的产出,针对这些数据的更高维分析,能让我们发现更多有价值且明确的信息。同时,我们也将高维的分析结果进行降维,采用健康分给每台机器打标。通过健康分,运维的同学可以快速知晓单台机器、某个机柜、某个集群的硬件情况。4.3.服务化基于对全链路数据的掌控,我们将整个故障自愈体系,作为一个硬件全生命周期标准化服务,提供给不同的产品线。基于对决策的充分抽象,自愈体系提供各类感知阈值,支持不同产品线的定制,形成适合个性化的全生命周期服务。5.故障自愈闭环体系在AIOps的感知、决策、执行闭环体系中,软件/硬件的故障自愈是最常见的应用场景,行业中大家也都选择故障自愈作为首个AIOps落地点。在我们看来,提供一套通用的故障自愈闭环体系是实现AIOps、乃至NoOps(无人值守运维)的基石,应对海量系统运维,智能自愈闭环体系尤为重要。5.1.必要性在一个复杂的分布式系统中,各种架构间不可避免地会出现运行上的冲突,而这些冲突的本质就在于信息不对称。而信息不对称的原因是,每种分布式软件架构在设计都是内敛闭环的。现在,通过各种机制各种运维工具,可以抹平这些冲突,然而这种方式就像是在打补丁,伴随着架构的不断升级,补丁似乎一直都打不完,而且越打越多。因此,我们有必要将这个行为抽象成自愈这样一个行为,在架构层面显式地声明这个行为,让各软件参与到自愈的整个流程中,将原本的冲突通过这种方式转化为协同。当前我们围绕运维场景中最大的冲突点:硬件与软件冲突,进行架构和产品设计,通过自愈的方式提升复杂的分布式系统的整体鲁棒性。5.2.普适性透过大量机器的硬件自愈轮转,我们发现:被纳入自愈体系的运维工具的副作用逐渐降低(由于大量地使用运维工具,运维工具中的操作逐渐趋于精细化)。被纳入自愈体系的人工运维行为也逐渐变成了自动化。每种运维动作都有了稳定的SLA承诺时间,不再是随时可能运行报错的运维脚本。因此,自愈实际上是在复杂的分布式系统上,将运维自动化进行充分抽象后,再构筑一层闭环的架构,使得架构生态形成更大的协调统一。本文作者:钟炯恩阅读原文本文来自云栖社区合作伙伴“阿里技术”,如需转载请联系原作者。

November 20, 2018 · 1 min · jiezi