IT教程 ·

PBFT算法java实现

ASP.NET Core 配置和使用环境变量

 

PBFT 算法的java完成(上)

在这篇博客中,我会经由历程Java 去完成PBFT中结点的到场,以及认证。个中运用socket完成收集信息传输。

关于PBFT算法的一些引见,人人能够去看一看网上的博客,也能够参考我的上上一篇博客,关于怎样构建P2P收集能够参考我的上一篇博客。

该项目标地点:GitHub

 

运用前的预备

运用maven构建项目,固然,也能够不运用,这个就看本身的主意吧。

须要运用到的Java包:

  • t-io:运用t-io举行收集socket通讯,emm,这个框架的文档须要收费(699RMB),然则这里我们只是简朴的运用,不须要运用到个中很庞杂的功用。
  • fastjson:Json 数据剖析
  • lombok:疾速的get,set以及toString
  • hutool:万一要用到呢?
  • lombok:节约代码
  • log4j:日记
  • guava:Google的一些并发包

 

结点的数据结构

起首的起首,我们须要来定义一下结点的数据结构。

起首是结点Node的数据结构:

@Data
public class Node extends NodeBasicInfo{

    /** * 单例设想形式 * @return */
    public static Node getInstance(){
        return node;
    }
    private Node(){}
    
    private static Node node = new Node();

    /** * 推断结点是不是运转 */
    private boolean isRun = false;

    /** * 视图状况,推断是不是ok, */
    private volatile boolean viewOK;
}

@Data
public class NodeBasicInfo {
    /** * 结点地点的信息 */
    private NodeAddress address;
    /** * 这个代表了结点的序号 */
    private int index;

}

@Data
public class NodeAddress {
    /** * ip地点 */
    private String ip;
    /** * 通讯地点的端口号 */
    private int port;

}

上面的代码看起来有点多,但实际上很少(上面是3个类,为了展现,我把它们放在了一同)。上面定义了Node应当包括的属性信息:ip,端口,序列号index,view是不是ok。

结点的信息很简朴。接下来我们就能够看一看PbftMsg的数据结构了。PbftMsg代表的是举行Pbft算法发送信息的数据结构。

@Data
public class PbftMsg {
    /** * 音讯范例 */
    private int msgType;

    /** * 音讯体 */
    private String body;

    /** * 音讯提议的结点编号 */
    private int node;

    /** * 音讯发送的目标地 */
    private int toNode;

    /** * 音讯时刻戳 */
    private long time;

    /** * 检测是不是经由历程 */
    private boolean isOk;

    /** * 结点视图 */
    private int viewNum;

    /** * 运用UUID举行生成 */
    private String id;

    private PbftMsg() {
    }

    public PbftMsg(int msgType, int node) {
        this.msgType = msgType;
        this.node = node;
        this.time = System.currentTimeMillis();
        this.id = IdUtil.randomUUID();
        this.viewNum = AllNodeCommonMsg.view;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        PbftMsg msg = (PbftMsg) o;
        return node == msg.node &&
                time == msg.time &&
                viewNum == msg.viewNum &&
                body.equals(msg.body) &&
                id.equals(msg.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(body, node, time, viewNum, id);
    }
}

PBFTMSG这里我只是简朴的定义了一下,并非很严谨。在这里主要说下主要的属性:

msgType代表的是Pbft算法的音讯范例,由于pbft算法有差别范例的要求音讯。

一样,我们须要保留一些状况数据:

public class AllNodeCommonMsg {
    /** * 取得最大失效结点的数目 * * @return */
    public static int getMaxf() {
        return (size - 1) / 3;
    }

    /** * 取得主节点的index序号 * * @return */
    public static int getPriIndex() {
        return (view + 1) % size;
    }

    /** * 保留结点对应的ip地点和端口号 */
    public static ConcurrentHashMap<Integer, NodeBasicInfo> allNodeAddressMap = new ConcurrentHashMap<>(2 << 10) ;

    /** * view的值,0代表view未被初始化 * 当前视图的编号,经由历程这个编号能够算出主节点的序号 */
    public volatile static int view = 0;
    /** * 区块链中结点的总结点数 */
    public static int size = allNodeAddressMap.size()+1;
}

 

逻辑流程

上面的定义看一看就好了,在这里我们主假如明白好PBFT算法的流程。在下面我们将好好的剖析一下PBFT算法的流程。

合抱之木始于毫末,万丈高楼起于垒土。一切一切的入手下手,我们都须要从节点的到场入手下手提及。

在前前面的博客,我们晓得一个在PBFT算法中有一个主节点,那末主节点是怎样出来的呢?固然是经由历程view算出来的。

设:结点数为N,当前视图为view,则主结点的id为:

$$primaryId = (view +1) mod N$$

因而,当一个节点启动的时刻,他一定是渺茫的,不晓得本身是谁,这个时刻就须要找一个节点问问如今是什么状况,问谁呢?一定是问主节点,然则主节点是谁呢?在区块链中的节点固然都晓得主节点是谁。这个时刻,新启动的节点(权且称之为小弟)就会向一切的节点去讯问:老大们,你们的view是多大啊,能不能行行好通知小弟我!然后老大们会将本身的view通知小弟。然则小弟又忧郁老大们骗他给他毛病的view,所以决议当返回的view满足一定的数目的时刻,就决议运用该view。

那末这个一定数目是若干呢?

quorum:到达共鸣须要的结点数目 $quorum = lceil frac {N + f +1 }{2 }rceil $

说了这么多理论方面的东西,如今让我们来讲一讲代码方面是怎样斟酌。

定义好两个简朴的数据结构,我们就能够来想想Pbft算法的流程了。

 

代码流程

起首的起首,我们先定义:节点的序号从0入手下手,view也从0入手下手,固然这个时刻size一定不是0,是1。so,主节点的序号是$primaryId = (0+1)%1 = 0$。

既然我们运用socket通讯,运用的是t-io框架。我们就从效劳端和客户端的方面来明白这个view的猎取历程。神笔马良来了!!

 

这个从socket的角度的诠释下历程。

起首区块链中的节点作为效劳端,新到场的节点叫做客户端(遵照哲学立场,client发送要求讯问server)。由于有多个server,因而关于D节点来讲,就须要多个客户端离别对应差别的效劳端发送要求。然后效劳端将view返回给client。

然后说下代码,效劳端接受到client发送的要求后,就将本身的view返回给client,然后client依据view的num决议哪个才是真正的view。这里能够分为3个步骤:客户端要求view,效劳端返回view,客户端处置惩罚view。

客户端要求view:

    /** * 发送view要求 * * @return */
    public boolean pubView() {
        log.info("结点入手下手举行view同步操纵");
        // 初始化view的msg
        PbftMsg view = new PbftMsg(MsgType.GET_VIEW, node.getIndex());
        // 将音讯举行播送
        ClientUtil.clientPublish(view);
        return true;
    }

上面的代码很简朴,就是客户端向效劳端播送PbftMsg,然后该音讯的范例是GET_VIEW范例(也就是通知老大们,我是来要求view的)。

既然客户端播送了PBFT音讯,固然效劳端就会接受到。

下面是server端的代码,至于效劳端是怎样接收到的,参考我的上一篇博客,或许他人的博客。当效劳端接受到view的要求音讯后,就会将本身的view发送给client。

    /** * 将本身的view发送给client * * @param channelContext * @param msg */
    private void onGetView(ChannelContext channelContext, PbftMsg msg) {
        log.info("server结点复兴视图要求操纵");
        int fromNode = msg.getNode();
        // 设置音讯的发送方
        msg.setNode(node.getIndex());
        // 设置音讯的目标地
        msg.setToNode(fromNode);
        // 设置音讯的view
        msg.setViewNum(AllNodeCommonMsg.view);
        String jsonView = JSON.toJSONString(msg);
        MsgPacket msgPacket = new MsgPacket();
        try {
            msgPacket.setBody(jsonView.getBytes(MsgPacket.CHARSET));
            // 将音讯发送给client
            Tio.send(channelContext, msgPacket);
        } catch (UnsupportedEncodingException e) {
            log.error(String.format("server结点发送view音讯失利%s", e.getMessage()));
        }
    }

然后是client接受到server返回的音讯,然后举行处置惩罚。

    /** * 取得view * * @param msg */
    private void getView(PbftMsg msg) {
        // 假如节点的view好了,固然也就不要下面的处置惩罚了
        if (node.isViewOK()) {
            return;
        }
        // count代表有若干位老大返回该view
        long count = collection.getViewNumCount().incrementAndGet(msg.getViewNum());
        // count >= 2 * AllNodeCommonMsg.getMaxf()则代表该view 能够
        if (count >= 2 * AllNodeCommonMsg.getMaxf() + 1 && !node.isViewOK()) {
            collection.getViewNumCount().clear();
            node.setViewOK(true);
            AllNodeCommonMsg.view = msg.getViewNum();
            log.info("视图初始化完成OK");
        }
    }

在这里人人大概会发明一个问题,我在第二个if中照样运用了!node.isViewOK()。那是由于我发如今多线程的状况下,纵然view设置为true了,下面的代码照样会实行,也就是说log.info("视图初始化完成OK");会实行两次,因而我又加了一个view检测。

一样,我们能够来完成一下视图变动(ViewChange)的算法。

什么时刻会发生viewChange呢?固然是主节点失效的时刻,就会举行viewchange的实行。当某一个节点发明主节点失效时(也等于断开衔接的时刻),他就会通知一切的节点(举行播送):啊!!不好了,主节点GG了,让我们从新挑选一个主节点吧。因而,当节点收到quorum个从新推举节点的音讯时,他就会将转变本身的视图。

这里有一个条件,就是当主节点和客户端断开的时刻,客户端会察觉到。

client的代码:

从新推举view就是将如今的veiw+1,然后讲该view播送出去。

    /** * 发送从新推举的音讯 * 这个onChangeView是经由历程别的函数挪用的,msg的内容以下所示 * PbftMsg msg = new PbftMsg(MsgType.CHANGE_VIEW,node.getIndex()); */
    private void onChangeView(PbftMsg msg) {
        // view举行加1处置惩罚
        int viewNum = AllNodeCommonMsg.view + 1;
        msg.setViewNum(viewNum);
        ClientUtil.clientPublish(msg);
    }

效劳端代码:

效劳端代码和前面的的代码很相似。

    /** * 从新设置view * * @param channelContext * @param msg */
    private void changeView(ChannelContext channelContext, PbftMsg msg) {
        if (node.isViewOK()) {
            return;
        }
        long count = collection.getViewNumCount().incrementAndGet(msg.getViewNum());

        if (count >= 2 * AllNodeCommonMsg.getMaxf() + 1 && !node.isViewOK()) {
            collection.getViewNumCount().clear();
            node.setViewOK(true);
            AllNodeCommonMsg.view = msg.getViewNum();
            log.info("视图变动完成OK");
        }
    }

 

总结

在这里,人人大概会有个迷惑,为何举行播送音讯不是运用效劳端去播送音讯,反而是运用client一个一个的去播送音讯。缘由有一下两点:

  • 由于没有购置t-io文档,因而我也不晓得server怎样举行播送音讯。由于它取消了门生优惠,如今须要699¥,实在是太贵了(固然这个贵是针对与我而言的,不过这个框架照样真的挺好用的)舍不得买。
  • 为了是思绪清晰,client就是为了要求数据,而server就是为了返回数据。如许想的时刻,不会是本身的思绪断掉

在这里为止,我们就简朴的完成了节点到场和view的变迁(固然是最简朴的完成,emm,大佬勿喷)。在下篇博客中,我将会引见共鸣历程的完成。假如这篇博客有毛病的处所,望大佬斧正。能够在批评区留言或许邮箱联络。

项目地点:GitHub

asp.net core 使用newtonsoft完美序列化WebApi返回的ValueTuple

参与评论