为了账号安全,请及时绑定邮箱和手机立即绑定

深夜读码-zookeeper之节点数据同步利器-缓存(上)

一人一首成名曲,深入研究一套源码非常有意义,尤其是基础组件,会让我们深层次的了解系统架构设计的原理,同时大大提高自己的编码能力;欢迎大家一块探讨zookeeper源码!

众所周知,互联网从一开始就伴随着高并发的问题,高并发的解决方案除了应用分而治之的技术手段,还有就是在内存、硬盘、DB层面的折中处理,大家都知道访问数据的优先级:内存 > 磁盘 > 数据库;由此也催生了众多的缓存系统,例如memcache、redis等等;今天我们要说的zookeeper也是如此,有自己的缓存数据;

zookeeper的各个节点的分工不同,大致如下:

图片描述
leader节点-处理事务与非事务请求;
follower节点-参与决议,转发事务请求;
observer节点-主要是分担系统负载的功能,不参与决议;

那么这些节点是如何进行数据同步的呢,也就是说,zookeeper在启动期间如何快速的完成节点之间的数据同步,来提供对外服务的呢;

zkdatabase.java 中有一个committedLog,它存放的已经提交的事务消息记录,最大存放500个这样的记录;


zkdatabase.java 

protected LinkedList<Proposal> committedLog = new LinkedList<Proposal>();

系统加载期间:


zkdatabase.java 

public long restore(DataTree dt, Map<Long, Integer> sessions, 
            PlayBackListener listener) throws IOException {
        snapLog.deserialize(dt, sessions);
        return fastForwardFromEdits(dt, sessions, listener);
}

这里先从snapshot文件中反序列化出数据,然后再从增量事务日志中获取大于snapshot文件最大zxid的一些数据进行加载; 注意这里传入了一个PlayBackListener,它的含义是数据加载完后的监听处理工作;也就是说我们在初始化期间committedLog里面存放的事务消息,一定是在增量事务日志中的,而不存在snapshot文件中;我们看到在加载期间循环调用了onTxnLoaded


zkdatabase.java 

public long fastForwardFromEdits(DataTree dt, Map<Long, Integer> sessions,
                                     PlayBackListener listener) throws IOException {
        FileTxnLog txnLog = new FileTxnLog(dataDir);
        TxnIterator itr = txnLog.read(dt.lastProcessedZxid+1);
        long highestZxid = dt.lastProcessedZxid;
        TxnHeader hdr;
        try {
            while (true) {
                // iterator points to 
                // the first valid txn when initialized
                hdr = itr.getHeader();
                if (hdr == null) {
                    //empty logs 
                    return dt.lastProcessedZxid;
                }
                if (hdr.getZxid() < highestZxid && highestZxid != 0) {
                    LOG.error("{}(higestZxid) > {}(next log) for type {}",
                            new Object[] { highestZxid, hdr.getZxid(),
                                    hdr.getType() });
                } else {
                    highestZxid = hdr.getZxid();
                }
                try {
                    processTransaction(hdr,dt,sessions, itr.getTxn());
                } catch(KeeperException.NoNodeException e) {
                   throw new IOException("Failed to process transaction type: " +
                         hdr.getType() + " error: " + e.getMessage(), e);
                }
                listener.onTxnLoaded(hdr, itr.getTxn());
                if (!itr.next()) 
                    break;
            }
        } finally {
            if (itr != null) {
                itr.close();
            }
        }
        return highestZxid;
    }

而onTxnLoaded的实例化函数如下:

    public void onTxnLoaded(TxnHeader hdr, Record txn){
            addCommittedProposal(hdr, txn);
        }

其实就是调用了addCommittedProposal;


zkdatabase.java 

    private void addCommittedProposal(TxnHeader hdr, Record txn) {
        Request r = new Request(null, 0, hdr.getCxid(), hdr.getType(), null, null);
        r.txn = txn;
        r.hdr = hdr;
        r.zxid = hdr.getZxid();
        addCommittedProposal(r);
    }
    
    public void addCommittedProposal(Request request) {
        WriteLock wl = logLock.writeLock();
        try {
            wl.lock();
            if (committedLog.size() > commitLogCount) {
                committedLog.removeFirst();
                minCommittedLog = committedLog.getFirst().packet.getZxid();
            }
            if (committedLog.size() == 0) {
                minCommittedLog = request.zxid;
                maxCommittedLog = request.zxid;
            }

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            BinaryOutputArchive boa = BinaryOutputArchive.getArchive(baos);
            try {
                request.hdr.serialize(boa, "hdr");
                if (request.txn != null) {
                    request.txn.serialize(boa, "txn");
                }
                baos.close();
            } catch (IOException e) {
                LOG.error("This really should be impossible", e);
            }
            QuorumPacket pp = new QuorumPacket(Leader.PROPOSAL, request.zxid,
                    baos.toByteArray(), null);
            Proposal p = new Proposal();
            p.packet = pp;
            p.request = request;
            committedLog.add(p);
            maxCommittedLog = p.packet.getZxid();
        } finally {
            wl.unlock();
        }
    }

上述的本质是将事务消息封装为一个Request消息,然后存放到committedLog,然后更新了minCommittedLog和maxCommittedLog;committedLog 存放最大消息数量为500;

    public static final int commitLogCount = 500;

加载完之后,就可以进行数据节点之间的选举流程和同步流程;

系统运行期间:

图片描述
zookeeper的责任链的最后一个环节是FinalRequestProcessor,它处理完后,会去判断是否是事务请求,如果是事务请求则会将该消息添加到committedLog;

FinalRequestProcessor.java

   if (Request.isQuorum(request.type)) {
                zks.getZKDatabase().addCommittedProposal(request);
   }

说白了,就是在运行期间,事务消息请求也会存放到committedLog中,那么这个committedLog在运行期间呢,就起到了一个非常关键的作用,就是数据同步;下节再讲一下初始化期间的同步规则。

····················
欢迎关注课程:
《Zookeeper源码分析》
《透视HashMap让你Get到源码大师的编程内功和编程思想》(限时一元福利)

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
226
获赞与收藏
250

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消