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