JavaのPBFTアルゴリズムの実装(上)
このブログでは、私は、ノードPBFTで追加、およびJavaによって認定を達成するために行くだろう。前記情報伝送ネットワークは、ソケットを使用して実装しました。
いくつかの入門PBFTアルゴリズムについては、我々はオンラインのブログを見て行くことができる、あなたも私の最後に1を参照することができますブログ私の前に参照することができ、P2Pネットワークを構築する方法については、ブログ。
プロジェクトの住所:GitHubの
使用前の準備
使用できない場合があり、当然のことながら、プロジェクトをビルドするためにMavenを使用し、これはそれのあなたの考えに依存します。
あなたは、Javaパッケージを使用する必要があります。
- T-IO:T-IOネットワークソケット通信、EMM、電荷(699RMB)の枠組み文書を使用しますが、ここでは単に非常に複雑な機能を使用する必要がない、使用しています。
- fastjson:JSONデータ分析
- ロンボク島:高速の取得、設定、およびのtoString
- hutool:場合はそれを使用するには?
- ロンボク島:コードの保存
- log4jの:ログ
- グアバ: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つのカテゴリでは、ショーのために、私はそれらを一緒に入れて)。上記で定義されたノードの属性情報が含まれている必要があります。ip、ポート、シーケンス番号インデックス、ビュー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は、ここで私は、単に非常に厳密ではない、少しを定義します。ここでは次の主要な重要な属性を参照:
アルゴリズムpbft要求メッセージの異なるタイプがあるためMSGTYPEは、メッセージタイプ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アルゴリズムを分析します。
取り囲む木々は、基本土壌から構築そびえ立つ、Haomoを開始しました。すべてすべてのスタートでは、我々は、参加するノードの先頭から開始する必要があります。
前の前回のブログでは、我々はPBFTアルゴリズムでマスターノードが存在することを知って、マスターノードは、どのようにそれの外にありますか?もちろん、ビューによってカウントされます。
提供:ノードの数Nは、現在のビューがビューで、メインノードIDがあります。
$$ primaryId =(ビュー+1)mod nを$$
、彼は混乱しなければならない場合、ノードを開始するため、私たちは現在の状況が何であるかを尋ねるためにノードを見つける必要があり、この時間は、聞いて、彼が誰であるか分からないのですか?確かにマスターノード、マスターノードを尋ねるが、それは誰ですか?チェーンブロック内のノードは、当然のことながら、はい誰がマスターノードを知っています。ブラザーは、あなたのビューには、私の弟を伝えるために良い十分ではないことができますどのくらいああ:この時間は、新しいノード(レッツ・コールそれ弟)の開始は、すべてのノードを聞いて行こう!その後、兄者は、独自のビューの兄を教えてくれます。しかし、また、特定の数を満たすためにビューを返すために、ときに私が決めた、弟は彼に間違った見解を与えるために彼に嘘をついていたことを心配し、私は、ビューを使用することにしました。
したがって、この特定の量はどのくらいですか?
定足数:ノードの数に達しコンセンサスが必要$定足数= \ lceil \ FRAC {+1 F N +} {2} \ rceil $
彼は今、考慮すべきコードの側面についての話はどのようにしましょう、理論的には非常に多くの事を言います。
私たちは、プロセスPbftアルゴリズムを考えることができますので、それは、2つの単純なデータ構造を定義しました。
コードの流れ
最初のものは最初に、のは定義してみましょう:0から始まるノードの数、ビューだけでなく、ゼロからは、当然のことながら、これは確かにサイズ0、1時間ではありません。特許ので、マスタノードは、$ primaryId =(0 + 1)%1 = 0 $です。
我々は、T-IOのフレームワークを使用して、ソケット通信を使用しているため。私たちは、このビューを取得するプロセスを理解するために、サービスとクライアントの観点を持っています。今後マジックペン馬梁!!
ソケット解釈プロセスからの角度。
サーバ・ノードとして最初のブロック鎖、新しいノードが(クライアントがサーバに依頼する要求を送信し、哲学的な態度に従ってください)、クライアントと呼ばれています。サーバーが複数あるので、そのためにD节点
も、別のクライアントの対応する複数のそれぞれのサーバに要求を送信が必要です。その後、サーバーはクライアントへのビューを返します。
然后说下代码,服务端接受到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