Apache顶级项目8-Geode源码深度分析

作者: erixhao | 来源:发表于2016-12-21 22:26 被阅读1108次

    @erixhao 技术极客TechBooster

    Apache顶级项目介绍之8,我们重新恢复Apache顶级项目系列,较之前介绍系列,我们本文直入代码分析,原因有二,Geode即上文我们分析的Gemfire的开源版本,其二被逼无奈,阅读源码查找问题。

    1. Geode

    2016年11月21日,Apache 软件基金会(ASF)宣布 Apache® Geode™ 已从 Apache 孵化器毕业成为顶级项目(TLP),表明该项目的社区和产品已根据 ASF 的精英流程和原则得到良好管理。

    Apache Geode 是一个数据管理平台,提供实时的、一致的、贯穿整个云架构地访问数据关键型应用,最初由 GemStone Systems 公司开发,商标为 GemFire。 此项技术初期被广泛应用在金融领域,用于华尔街交易平台,作为事务性、 低延时的数据引擎。2015年4月将代码提交给 Apache 孵化器作为孵化项目。目前 Apache Geode 有超过600家大中型企业级用户, 主要是必须满足低延时和24x7 高可靠要求的,高可扩展的关键业务应用系统。

    2. 适用场景

    可以参考上文,分布式缓存利器-Gemfire.

    4个主要使用场景:

    -高可用性的分布式缓存

    -网格计算

    -事件通知和处理(CEP类似)

    -交易处理(Transaction),采用最终一致性

    Geode 池化了服务器上的内存, CPU, 网络资源, 和本地磁盘,跨多个进程来管理应用对象和行为. 它使用了动态数据复制和分区技术来实现高可用, 高性能, 高可扩展性, 和容错. 另外, 对于一个分布式数据容器, Apache Geode 是一个基于内存的数据管理系统, 提供了可靠的异步事件通知和可靠的消息投递.

    3. 数据结构源码

    如上文所说,系统目前遇到一个分布式系统异常复杂之难题,难到不遍历源码无法解开谜题的地步。注意,笔者写此文时,此难题尚无答案,我们希望当此文发布时,已有解药。

    另外,强烈建议看官先自行了解Gemfire功能以及部分原理,否则此文较为吃力。

    3.1 Region

    进入正题,先来看一下核心类Region。

    Region继承Java的ConcurrentMap,实现了分布式K,V的HashMap,并提供了高阶的事务,Persistence,分区等分布式功能。

    接口继承Map的get, put无须多说,看几个比较重要的。

    Geode的Region可以像树形一样,形成一种Hierarchy结构嵌套,使用"/"来分隔多个子Region.所以有向上获取父亲,自然也有向下导航获取子女:getSubregion(String path)

    RegionDistributedLock

    第一个分布式锁,全局Region。注释写很到位,其中提到当region创建时会自动开启全局region锁,所有对region级别的操作,如invalidateRegion, destroyRegion也可能会自动用到region级别锁,这个么可以理解,毕竟是重量级操作。

    getDistributedLock(key)

    提供了更细粒度的region级别分布式锁控制。

    然而,除非必要,所有分布式操作不建议用如此之重的锁,否则后患无穷。

    public void writeToDisk();

    当然,支持持久化到磁盘,分布式的一种表现形式,相对于普通的HashMap。

    public void becomeLockGrantor();

    不赘述,如注释所描述,LockService的担保,前提当前region必须是Global类型,有点类似对于分布式全局变量加锁,所以每当调用此方法,线程将阻塞直到担保被转移到当前member。

    超级重量级方法,请自保。注释强调了,相互调用可能造成死锁,说的比较委婉,back and force。

    public void registerInterest(K key);

    后续很多分布式,多个member交互的入口之一。

    注释提到用于向CacheServer提交注册一个感兴趣的key,后续还有按照正则表达式注册,或者注册所有key。其注册目的是用于被通知,类似ems queue的subscription功能,或者回调通知。

    public List getInterestList();

    返回当前region已经注册的所有感兴趣key。

    对了,每个类代码小于500行都是骗人的,随便看看这些开源代码,少则1000,多则6-7千行代码。

    3.2 AbstractRegion

    AbstractRegion抽象类,主要封装实现了关于RegionAttributes, AttributesMutator, and some no-brainer method implementations。

    其中,主要cache操作的读写则由上面loader与writer代理实现。Usually there will be only one CacheWriter in the distributed system.所以,通常整个分布式系统只有一个cache writer,其它特殊情况暂时不提。

    截获entry更新事件,其中entryEvent包含了整个操作的信息,如KeyInfo, EventID, newValue, oldValue, Operation, originated member等。BTW, 这个看起来普通的EntryEvent类也有2856行,此处省略无数。。。

    protected Set asyncEventQueueIds;

    隐隐觉得,其分布式的消息事件处理会放到queue中,这里用了set结构,我们继续看下去。

    protected booleanenableSubscriptionConflation;

    这个参数对于高频写region非常有用,主要是同一个key高速写,类似实时报价。这个参数效果类似于Leaky Bucke。

    漏桶算法(Leaky Bucket)是网络世界中流量整形(Traffic Shaping)或速率限制(Rate Limiting)时经常使用的一种算法,它的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量。漏桶算法提供了一种机制,通过它,突发流量可以被整形以便为网络提供一个稳定的流量。漏桶可以看作是一个带有常量服务时间的单服务器队列,如果漏桶(包缓存)溢出,那么数据包会被丢弃。

    3.3 Regions UML

    当我们越加深入,发现代码越来越复杂,很多类少则几千,多则上万行,类也有上千万个,对于大型开源项目,还是来一张类图吧,掌握整体宏观很重要。

    可以看到LocalRegion继承自AbstractRegion,最重要的是内部封装了Region的数据结构。

    了解HashMap内部数据结构的朋友应该知道,其内部为数组+链表组合的二维空间;同理,Geode的Region内部数据结构则由RegionMap来抽象,持有真正的数据,而究其内部则是内置了一个ConcurrentHashMap,想想让我们来设计的话也大概如此吧。

    3.4 AbstractRegionMap

    可以看到里面内置一个CustomEntryConcurrentHashMap map;应该是这个map, 这是什么神器?

    如果你看到了Doug Lea的大名,不要惊奇,难道Doug也为Gemfire/Geode操刀?非也。

    Gemfire/Geode当年直接copy Doug老人家的ConcurrentHashMap, 你可以去比较一下源码,当然,GemStone也进行了一些定制化,如读锁等。

    了解完其真实内部存储数据结构后,心中略有一二,而然这也是预期之中,我们继续看如何实现分布式的精髓。

    3.5 DistributedRegion

    我们还是先上UML图吧。

    DistributinRegion代表了分布式的ReplicatedRegion, 而其中则是靠CacheDistributionAdvisor来帮忙跟踪保持数据同步的。

    3.6 Partitioned Regions

    Partitioned Region为PatitionedRegion的实例化,内部则基于key的hash code来检测定位Bucket。

    PartitionedRegion,Geode中的核心分布式结构。通过hash算法(可以自定义)来把key映射到相应的bucket从而达到分布式存储,并提供routing消息到相应的bucket。

    PartionedRegion中的PartitionedRegionDataStore则实际负责分布式管理bucket,以及存储。

    PRHARedundancyProvider

    类如其名:

    主要提供针对Reduancy Copy的管理,包括:

    bucket创建,以及node的管理。

    当创建并配bucket的时候,其内部创建出BucketRegion的实例, 来看一下BucketRegion中经典的virtualPut:

    注释很好,

    如果当前是Primary, 则op locally, 之后分发操作op到secondaries及bridge server,同时cache listerner同步其结果;

    反之,则首先op locally,之后更新local bridge server, gateway

    来个序列图吧:

    4. 分布式管理Client + Server

    Geode的分布式消息通信分为主要两大类,一类是Peer to Peer,主要使用了InternalDistributedMember。

    4.1 Peer to Peer 消息机制

    InternalDistributedMember封装了分布式member的信息,当需要发送p2p消息时,需要用该对象表明目标。

    DM提供了获取所有当前cluster中的peers列表,并通过listerners来获取动态信息,而其底层则是通过DistributionAdvisor实现,统一的模式,所以真正筹谋画册的都是幕僚。DM主要封装了相关的网络通信信息,包含netMember, dcPort(direct channel port), vmPid(member machine's process id)等,当然实现特定内部序列化接口(DataSerializableFixedID)是必不可少的。

    而消息则是DistributionMessage封装,包含了上述InternalDistributedMember信息的sender与destination,以及多个recipients的InternalDistrbutedMember[]数组,还有万恶的用于direct ack的ReplySender等。

    整个消息通信过程大概是先创建DistributionMessage, 设定recipient(可以多个),之后调用(DistrbutManager DM) DM.putOutgoing(message)。

    如果需要不但发送消息,而且还要确认消息回复response,则要使用著名的ReplyProcessor21, 如果你看到过Gemfire/Geode的ThreadDump, 你就明白我说什么了。

    还是来个序列图吧,一图抵千言。

    最后在远端则是通用的处理:

    最后,远端返回replyMessage,并使用相同的unique id,sender收到消息后,找到ReplyProcessor,并wakeup thread waitForRepliesUninterrupibly()。

    4.2 Client / Server 通信

    Client/Server通信机制与p2p略有不同。Server端封装了command,引入智囊团BridgeServerImpl, AcceptorImpl, Client端则封装了AbstractOp。

    Server:

    Acceptorimpl主要监听socket端口,接受客户端的链接。而其中又通过区分max-connections, max-threads而分为使用dedicated thread还是selector模式的NIO处理。

    Client:

    客户端则使用PoolImpl来管理连接connections,client region则持有serverRegionProxy以便实现调用。

    值得注意的是,客户端可以通过subscribe events于特定/多个region或者通过内置的continuous queries,这样当服务器端有数据更新则client端可以受到更新的通知event。

    5. 总结

    好吧,时间,篇幅受限,我们先到此。

    我们把核心的数据结构以及分布式通信的核心类大概介绍了一下,详细细节还需要仔细阅读源码,有时间有精力的话再来一篇。

    相关文章

      网友评论

      本文标题:Apache顶级项目8-Geode源码深度分析

      本文链接:https://www.haomeiwen.com/subject/vxccvttx.html