PBFT算法java实现
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
PBFT算法java实现的更多相关文章
- PBFT 算法 java实现(下)
PBFT 算法的java实现(下) 在上一篇博客中(如果没有看上一篇博客建议去看上一篇博客),我们介绍了使用Java实现PBFT算法中节点的加入,view的同步等操作.在这篇博客中,我将介绍PBFT算 ...
- 归并排序算法 java 实现
归并排序算法 java 实现 可视化对比十多种排序算法(C#版) [直观学习排序算法] 视觉直观感受若干常用排序算法 算法概念 归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Di ...
- 快速排序算法 java 实现
快速排序算法 java 实现 快速排序算法Java实现 白话经典算法系列之六 快速排序 快速搞定 各种排序算法的分析及java实现 算法概念 快速排序是C.R.A.Hoare于1962年提出的一种划分 ...
- 堆排序算法 java 实现
堆排序算法 java 实现 白话经典算法系列之七 堆与堆排序 Java排序算法(三):堆排序 算法概念 堆排序(HeapSort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,可以利用数组的特 ...
- Atitit 电子商务订单号码算法(java c# php js 微信
Atitit 电子商务订单号码算法(java c# php js 微信 1.1. Js版本的居然钱三爷里面没有..只好自己实现了. 1.2. 订单号标准化...长度16位 1.3. 订单号的结构 前 ...
- 无向图的最短路径算法JAVA实现
一,问题描述 给出一个无向图,指定无向图中某个顶点作为源点.求出图中所有顶点到源点的最短路径. 无向图的最短路径其实是源点到该顶点的最少边的数目. 本文假设图的信息保存在文件中,通过读取文件来构造图. ...
- 无向图的最短路径算法JAVA实现(转)
一,问题描述 给出一个无向图,指定无向图中某个顶点作为源点.求出图中所有顶点到源点的最短路径. 无向图的最短路径其实是源点到该顶点的最少边的数目. 本文假设图的信息保存在文件中,通过读取文件来构造图. ...
- 基于FP-Tree的关联规则FP-Growth推荐算法Java实现
基于FP-Tree的关联规则FP-Growth推荐算法Java实现 package edu.test.ch8; import java.util.ArrayList; import java.util ...
- 双色球机选算法java实现
双色球机选算法java实现 一.代码 package com.hdwang; import java.util.Random; /** * Created by admin on 2017/1/10. ...
随机推荐
- 多态(C++)
#include <iostream> using namespace std; class HeroFighter { public: virtual int power() { ; } ...
- 有哪些让人相见恨晚的Python库(一)
对于我这个经常用python倒腾数据的人来说,下面这个库是真·相见恨晚 记得有一次我在服务器上处理数据时,为了解决Pandas读取超过2000W条数据就内存爆炸的问题,整整用了两天时间来优化.最后通过 ...
- Vue中echarts的基本用法
前言:同大多数的前端框架一样,先读官网的使用方法.学会基本使用后,在实例中找到自己想要demo.拿过来改一改,一个echarts图表就形成,毕竟人家做就是为了方便使用. 我是在vue中下面直接使用的e ...
- 从头学pytorch(二十):残差网络resnet
残差网络ResNet resnet是何凯明大神在2015年提出的.并且获得了当年的ImageNet比赛的冠军. 残差网络具有里程碑的意义,为以后的网络设计提出了一个新的思路. googlenet的思路 ...
- SpringBoot + Apache Shiro权限管理
之前配置过Spring + SpringMVC + JPA + Shiro后台权限管理 + VUE前台登录页面的框架,手动配置各种.xml,比较繁琐,前几天写了个SpringBootShiro的Dem ...
- 前端笔记6-js2
1.break 和continue用法 break结束本次循环,如果想结束外层循环,可以通过这个label来指定要结束的循环. continue可以用来跳过当次循环,如果想跳过外次循环,也可以通过这个 ...
- python爬虫——scrapy的使用
本文中的知识点: 安装scrapy scrapy的基础教程 scrapy使用代理 安装scrapy 由于小哥的系统是win7,所以以下的演示是基于windows系统.linux系统的话,其实命令都一样 ...
- 使用使用django-cors-headers解决跨域问题
安装 pip3 install -i https://pypi.douban.com/simple django-cors-headers 注册App INSTALLED_APPS = [ ... ' ...
- HBase的安装、配置与实践
本教程运行环境是在Ubuntu-64位系统下,HBase版本为hbase-1.1.2,这是目前已经发行的已经编译好的稳定的版本,带有src的文件是未编译的版本,这里我们只要下载bin版本hbase-1 ...
- List自定义排序 (例子省份排序)
//待排序集合 List<String> list=new ArrayList<String>(); list.add("辽宁"); list.add(&q ...