Go-micro golang版本的微服务框架,其构建了go语言微服务框架的经典设计架构。其抽象出的结构定义轻量,灵活,方便企业做二次开发。
限流器
限流器 顾名思义用来对高并发的请求进行流量限制的组件。
为啥需要进行流量限制。首先后端服务由于各个业务的不同和复杂性,各自在容器部署的时候都可能会有单台的瓶颈,超过瓶颈会导致内存或者cpu的瓶颈,进而导致发生服务不可用或者单台容器直接挂掉或重启。
流量的限制在众多微服务和service mesh系统中多有应用。笔者在本文的程序示例均以golang作为示例。
Promethues Golang 客户端 源码解析
Promethues Golang 客户端 源码解析
监控利器 promethues,线上产品必备的监控组件。笔者此处不做过多的介绍,开始client端源码旅程。
源码一共分为三个部分
- Register
- Collector
- Push Gateway client
TCP协议详解
TCP协议详解
众所周知,TCP协议是一个可靠的的协议,也是一个流量控制协议
Netty源码解析-Epoll篇
Epoll是Linux系统中高性能IO的底层机制,由操作系统底层内核提供,Linux版本在2.6之后的版本才会有此功能及api暴露出来,应用层一般采用c或c++去编写。
本文主要讲述Netty源码中Epoll的实现原理和源码设计思想。
Netty源码解析-ByteBuf篇
Netty是如今互联网最流行的java通讯库,在众多优秀组件 dubbo,grpc-java,jetty,RocketMQ中都使用了netty作为其通讯组件。
在netty的使用中,最常见的就是ByteBuf类,这个类最常见是由于其是字节数据的容器,白话一点就是存放字节数据的数组。
因为所有的网络通信最终都是基于底层的字节流传输,所以需要一个方便、易用的数据接口进行字节流的基本操作,正好ByteBuf提供了
基础的操作。
rxjava2 源码解析(二)
前一篇文章我们讲述到rxjava2 的内部设计模式与原理机制,包括观察者模式和装饰者模式,其本质上都是rxjava2的事件驱动,那么本篇文章将会讲到rxjava2的另外一个功能:异步功能
rxjava2 源码解析
依旧是阅读源码,还是从源码开始带着疑惑去读,前一篇文章我们讲到subcribeOn方法内部的实现涉及线程池:Scheduler.Worker w = scheduler.createWorker() 这边涉及两个重要组件:
- scheduler调度器
- 自定义线程池
scheduler调度器源码解析
12345678910111213141516171819202122public final class Schedulers {@NonNull// 针对单一任务设置的单个定时线程池static final Scheduler SINGLE;@NonNull// 针对计算任务设置的定时线程池的资源池(数组)static final Scheduler COMPUTATION;@NonNull// 针对IO任务设置的单个可复用的定时线程池static final Scheduler IO;@NonNull// trampoline翻译是蹦床(佩服作者的脑洞)这个调度器的源码注释是:任务在当前线程工作(不是线程池)但是不会立即执行,任务会被放入队列并在当前的任务完成之后执行。简单点说其实就是入队然后慢慢线性执行(这里巧妙的方法其实和前面我们所讲的回压实现机制基本是一致的,值得借鉴)static final Scheduler TRAMPOLINE;@NonNull// 单个的周期线程池和single基本一致唯一不同的是single对thread进行了一个简单的NonBlocking封装,这个封装从源码来看基本没有作用,只是一个marker interface标志接口static final Scheduler NEW_THREAD;
一共五种调度器,分别对应不同的场景,当然企业可以针对自身的场景设置自己的调度器
computation调度器源码分析
computation调度器针对大量计算场景,在后端并发场景会更多的用到,那么其是如何实现的呢?接下来带着疑惑进行源码分析
上述代码清晰的展示了computation调度器的实现细节,这里需要说明的是定时线程池的core设置为1,线程池的个数最多为cpu数量,这里涉及到ScheduledThreadPoolExecutor定时线程池的原理,简单的说起内部是一个可自动增长的数组(队列)类似于ArrayList,也就是说队列永远不会满,线程池中的线程数不会增加。
接下来结合订阅线程和发布线程分析其之间如何进行沟通的本质
发布线程在上一篇的文章已经提到,内部是一个worker,那么订阅线程也是么,很显然必须是的,接下来我们来看下源代码:
为何要将订阅者这样区别设置呢,其实原因很简单,订阅者和发布者需要不同的线程机制异步地执行,比如订阅者需要computation的线程机制来进行大量的耗时数据计算,但又要保持一致的装修者模式,所以源码的做法是订阅者这边打破回调的调用流,采用数据队列进行两个线程池之间的数据传送。
本文总结
笔者喜欢总结,总结意味着我们反思和学习前面的知识点,应用点以及自身的不足。
- rxjava2线程调度的原理机制,不同场景下线程机制需要进行定制
- rxjava2生产和消费的异步原理和实现方式
rxjava2 源码解析(一)
ReactiveX 响应式编程库,这是一个程序库,通过使用可观察的事件序列来构成异步和事件驱动的程序。
其简化了异步多线程编程,在以前多线程编程的世界中,锁,可重入锁,同步队列器,信号量,并发同步器,同步计数器,
并行框架等都是具有一定的使用门槛,稍有不慎或者使用不成熟或对其源码理解不深入都会造成相应的程序错误和程序性能的低下。
观察者模型
24种设计模式的一种,观察者Observer和主题Subject之间建立组合关系:Subject类实例中包含观察者Observer的引用,
增加引用的目的就是为了通知notify,重要点就是要在Subject的notify功能中调用Observer的接受处理函数receiveAndHandle
个人理解:观察者模型其实是一种异步回调通知,将数据的处理者先注册到数据的输入者那边,这样通过数据输入者执行某个函数
去调用数据处理者的某个处理方法。
RxJava2
Rx有很多语言的实现库,目前比较出名的就是RxJava2。这里主讲Rxjava2的部门源码解读,内部设计机制和内部执行的线程模型
基本使用
使用rxjava2大致分为四个操作:
- 建立数据发布者
- 添加数据变换函数
- 设置数据发布线程池机制,订阅线程池机制
- 添加数据订阅者12345678910// 创建flowableFlowable<Map<String, Map<String,Object>>> esFlowable = Flowable.create(new ElasticSearchAdapter(), BackpressureStrategy.BUFFER);Disposable disposeable = esFlowable// map操作 1.采集、2.清洗.map(DataProcess::dataProcess).subscribeOn(Schedulers.single())//计算任务调度器.observeOn(Schedulers.computation())// 订阅者 consumer 执行运算.subscribe(keyMaps -> new PredictEntranceForkJoin().predictLogic(keyMaps));
以上就是一个实际的例子,里面的ElasticSearchAdapter实际隐藏了一个用户自定义实现数据生产的subscribe接口:FlowableOnSubscribe
用户需要实现这个接口函数:void subscribe(@NonNull FlowableEmitter
emitter 英文翻译发射器,很形象,数据就是由它产生的,也是业务系统需要对接的地方,一般业务代码实现这个接口类然后发射出需要处理的原始数据。
map函数作为数据变换处理的功能函数将原来的数据输入变换为另外的数据集合,然后设置发布的线程池机制subscribeOn(Schedulers.single()),订阅的线程池
机制observeOn(Schedulers.computation()),最后添加数据订阅函数,也就是业务系统需要实现另外一个地方,从而实现数据的自定义处理消费。
rxjava2支持的lambda语法
- 创建操作符:just fromArray empty error never fromIterable timer interval intervalRange range/rangeLong defer
- 变换操作符:map flatMap flatmapIterable concatMap switchmap cast scan buffer toList groupBy toMap
- 过滤操作符:filter take takeLast firstElement/lastElement first/last firstOrError/lastOrError elementAt/elementAtOrError ofType skip/skipLast
ignoreElements distinct/distinctUntilChanged timeout throttleFirst throttleLast/sample throttleWithTimeout/debounce - 合并聚合操作符:startWith/startWithArray concat/concatArray merge/mergeArray concatDelayError/mergeDelayError zip combineLatest combineLatestDelayError
reduce count collect - 条件操作符:all ambArray contains any isEmpty defaultIfEmpty switchIfEmpty sequenceEqual takeUntil takeWhile skipUntil skipWhile
有一篇博客详细介绍了rxjava的各种操作符,链接https://maxwell-nc.github.io/android/rxjava2-1.html
rxjava2 源码解析
阅读源码个人比较喜欢带着疑惑去看,这样与目标有方向。接下来的分析以Flowable为例,这里所有的例子都是按照Flowable为例,因为Flowable在实际
项目中比Observable可能用的多,因为实际场景中数据生产速度和数据消费速度都会有一定的不一致甚至数据生产速度远大于数据消费速度。
数据发布和订阅
首先从数据订阅者开始,点进源码看进一步解析,里面有很多subscribe重载接口:
操作符与线程池机制原理剖析
首先在进行源码分析之前讲述一下一种模式:装饰者模式 24种模式中的一种,在java io源码包中广泛应用
简单的来说是与被装饰者具有相同接口父类同时又对被装饰者进行一层封装(持有被装饰者的引用),以此用来加上自身的特性。
回归主题,当我们使用操作符和线程池机制的时候做法都是在数据发布者后面进行相应的函数操作:
那么为何这么做,接下来我们进行源码分析:
- subscribeOn map 方法都在Flowable类中:12345678public final <R> Flowable<R> map(Function<? super T, ? extends R> mapper) {ObjectHelper.requireNonNull(mapper, "mapper is null");return RxJavaPlugins.onAssembly(new FlowableMap<T, R>(this, mapper));}public final Flowable<T> subscribeOn(@NonNull Scheduler scheduler, boolean requestOn) {ObjectHelper.requireNonNull(scheduler, "scheduler is null");return RxJavaPlugins.onAssembly(new FlowableSubscribeOn<T>(this, scheduler, requestOn));}
这里是实例方法调用,传进了this对象这个很关键,这里其实就是我们前面提到的装修者模式,持有上游对象也就是数据源source的引用
以FlowableSubscribeOn为例进行分析,这个类经常会用到,因为其内部设置了线程池的机制所以在实际使用项目中会大量使用,那么是如何
做到线程池方式的呢?进一步利用源码进行分析:
- 装饰者的内部代码分析
以subscribeOn 为例12345678910111213141516171819202122232425262728293031323334353637383940414243//很明显 实现的抽象类其实是装修者抽象类public final class FlowableSubscribeOn<T> extends AbstractFlowableWithUpstream<T , T>// 这个在前面我们重点分析过这是实际订阅执行的类方法,其实也就是我们说的装饰方法,里面实现了每个类自己的特定“装修”方法@Overridepublic void subscribeActual(final Subscriber<? super T> s) {// 获取订阅者,下一篇文章会重点讲述rxjava的线程池分配机制Scheduler.Worker w = scheduler.createWorker();final SubscribeOnSubscriber<T> sos = new SubscribeOnSubscriber<T>(s, w, source, nonScheduledRequests);// 跟前面一样调用数据订阅者的onSubscribe方法s.onSubscribe(sos);// 由分配的调度者进行订阅任务的执行w.schedule(sos);}// 开始分析SubscribeOnSubscriber这个静态内部类的内部代码// 实现了Runable用来异步执行static final class SubscribeOnSubscriber<T> extends AtomicReference<Thread>implements FlowableSubscriber<T>, Subscription, Runnable// 下游订阅引用final Subscriber<? super T> downstream;// 上游发射类引用final AtomicReference<Subscription> upstream;// 上游数据源引用 跟上游引用有区别,简单的说每个上游数据源引用有自己的上游发射类Publisher<T> source;// 这里是装饰的核心代码@Overridepublic void run() {lazySet(Thread.currentThread());// source即为上游,表示其所装饰的源Publisher<T> src = source;source = null;// 调用上游的自身的subscribe方法,在上面一开始我们说这个方法内部会去调用自身实现的subscribeActual方法// 从而实现上游自己的特定方法,比如假设source是FlowCreate那么此处就会调用前面一开始我们所讲到的数据的发射src.subscribe(this);}// 既然已经保证了数据的发射那么数据的处理是不是也要处理// 很明显这是调用了下游订阅者的onNext方法@Overridepublic void onNext(T t) {downstream.onNext(t);}
本文总结
笔者喜欢总结,总结意味着我们反思和学习前面的知识点,应用点以及自身的不足。
- 设计模式:观察者模式和装修者模式
- 并发处理技巧:回压策略(其实本质是缓存)的实现原理以及细节点
分布式CAP理论与BASE理论
CAP理论
CAP理论是分布式工程项目邻域的基石理论。
CAP一共三个概念:C:Consistency 一致性;A:Availability 可用性;P:Partition tolerance 分区容忍性
- 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点保持同一份数据的最新版本)
- 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(读,写均需要满足)
- 分区容忍性(P):以实际效果而言,分区相当于对通信的时限要求。(不会出现延迟,丢包等)
C与A的抉择
由于当前的网络不能保证一定不会出现延迟,丢包等问题所以P是必须选择的一项,那么剩下的C和A需要选择其中一个。
那么为啥不能全选呢?
CAP三者不能全部满足
在分布式系统的设计中,没有一种设计可以同时满足一致性,可用性,分区容错性 3个特性
我们来看一个简单的问题:
我们来看一个简单的问题, 一个DB服务 搭建在两个机房(北京,广州),两个DB实例同时提供写入和读取
假设DB的更新操作是同时写北京和广州的DB都成功才返回成功
在没有出现网络故障的时候,满足CA原则,C 即我的任何一个写入,更新操作成功并返回客户端完成后,分布式的所有节点在同一时间的数据完全一致,A 即我的读写操作都能够成功,但是当出现网络故障时,不能同时保证CA,即P条件无法满足假设DB的更新操作是只写本地机房成功就返回,通过binlog/oplog回放方式同步至侧边机房
这种操作保证了在出现网络故障时,双边机房都是可以提供服务的,且读写操作都能成功,意味着他满足了AP ,但是它不满足C,因为更新操作返回成功后,双边机房的DB看到的数据会存在短暂不一致,且在网络故障时,不一致的时间差会很大(仅能保证最终一致性)假设DB的更新操作是同时写北京和广州的DB都成功才返回成功且网络故障时提供降级服务
降级服务,如停止写入,只提供读取功能,这样能保证数据是一致的,且网络故障时能提供服务,满足CP原则,但是他无法满足可用性原则
C与A
一致性,这里的一致性并不是弱一致性或者强一致性,一致性指的是完全一致性。
对于大多数互联网应用来说,因为机器数量庞大,部署节点分散,网络故障是常态,可用性是必须需要保证的,所以只有设置一致性来保证服务的AP,通常常见的高可用服务追求稳定性本质都是放弃C选择AP,对于需要确保强一致性的场景,如银行,通常会权衡CA和CP模型,CA模型网络故障时完全不可用,CP模型具备部分可用性,实际的选择需要通过业务场景来权衡(并不是所有情况CP都好于CA,只能查看信息不能更新信息有时候从产品层面还不如直接拒绝服务)
BASE理论
BASE是 Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性) 三个短语的简写,BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。接下来我们着重对BASE中的三要素进行详细讲解。
基本可用
基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性——但请注意,这绝不等价于系统不可用,以下两个就是“基本可用”的典型例子。
响应时间上的损失:正常情况下,一个在线搜索引擎需要0.5秒内返回给用户相应的查询结果,但由于出现异常(比如系统部分机房发生断电或断网故障),查询结果的响应时间增加到了1~2秒。
功能上的损失:正常情况下,在一个电子商务网站上进行购物,消费者几乎能够顺利地完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。
弱状态也称为软状态,和硬状态相对,是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
最终一致性
最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。亚马逊首席技术官Werner Vogels在于2008年发表的一篇文章中对最终一致性进行了非常详细的介绍。他认为最终一致性时一种特殊的弱一致性:系统能够保证在没有其他新的更新操作的情况下,数据最终一定能够达到一致的状态,因此所有客户端对系统的数据访问都能够获取到最新的值。同时,在没有发生故障的前提下,数据达到一致状态的时间延迟,取决于网络延迟,系统负载和数据复制方案设计等因素。在实际工程实践中,最终一致性存在以下五类主要变种。
- 因果一致性:
因果一致性是指,如果进程A在更新完某个数据项后通知了进程B,那么进程B之后对该数据项的访问都应该能够获取到进程A更新后的最新值,并且如果进程B要对该数据项进行更新操作的话,务必基于进程A更新后的最新值,即不能发生丢失更新情况。与此同时,与进程A无因果关系的进程C的数据访问则没有这样的限制。 - 读己之所写:
读己之所写是指,进程A更新一个数据项之后,它自己总是能够访问到更新过的最新值,而不会看到旧值。也就是说,对于单个数据获取者而言,其读取到的数据一定不会比自己上次写入的值旧。因此,读己之所写也可以看作是一种特殊的因果一致性。 - 会话一致性:
会话一致性将对系统数据的访问过程框定在了一个会话当中:系统能保证在同一个有效的会话中实现“读己之所写”的一致性,也就是说,执行更新操作之后,客户端能够在同一个会话中始终读取到该数据项的最新值。 - 单调读一致性:
单调读一致性是指如果一个进程从系统中读取出一个数据项的某个值后,那么系统对于该进程后续的任何数据访问都不应该返回更旧的值。 - 单调写一致性:
单调写一致性是指,一个系统需要能够保证来自同一个进程的写操作被顺序地执行。
以上就是最终一致性的五类常见的变种,在实践中其实可以将其中的若干个变种相互结合起来,以构建一个具有最终一致性特性的分布式系统。
其实,最终一致性并不是只有那些大型分布式系统才设计的特性,许多现代的关系型数据库都采用了最终一致性模型。在现代关系型数据库中,大多都会采用同步和异步方式来实现主备数据复制技术。在同步方式中,数据的复制通常是更新事务的一部分,因此在事务完成后,主备数据库的数据就会达到一致。而在异步方式中,备库的更新往往存在延时,这取决于事务日志在主备数据库之间传输的时间长短,如果传输时间过长或者甚至在日志传输过程中出现异常导致无法及时将事务应用到备库上,那么很显然,从备库中读取的的数据将是旧的,因此就出现了不一致的情况。当然,无论是采用多次重试还是人为数据订正,关系型数据库还是能搞保证最终数据达到一致——这就是系统提供最终一致性保证的经典案例。
总结
总的来说,BASE理论面向的是大型高可用可扩展的分布式系统,和传统事务的ACID特性使相反的,它完全不同于ACID的强一致性模型,而是提出通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。但同时,在实际的分布式场景中,不同业务单元和组件对数据一致性的要求是不同的,因此在具体的分布式系统架构设计过程中,ACID特性与BASE理论往往又会结合在一起使用。
浅谈智能运维
浅谈智能运维
现在智能运维很火,很热门,大家都在研究这个课题项目。在国内学术界,清华大学裴丹教授已经
研究此课题一年有余,也在APM大会上进行了相关落地的阐述。在国外工业界,SIGCOMM 2016大会,
微软提出基于数据中心的故障定位方案,先用实验床把所有可能故障都模拟一下,同时收集各类监控
指标,然后通过机器学习建立模型,这个模型可以根据实际发生的监控指标的症状, 推断根因的
大致位置,以便加速止损。 在相关文献中用到的基础算法包括随机森林,故障指纹构建,逻辑回归,
马尔科夫链,狄利克雷过程等方法来进行故障定位。