사실 Leader election 은 가장 단순하게는 주변 노드들의 이름을 리스트로 가지고 있다가, 이름 순으로 그 다음 노드가 그냥 리더가 되는 느낌으로 구현하면 매우 단순하긴 한데, (Distributed Systems 책들에 소개되는 수준의 Bully algorithm, Ring algorithm 등은 실용적으로 사용하기에는 다소 심플하다.) Split brain 때문에 Term(epoch) 단위 동안에 가장 다수의 투표를 받은 노드가 선출되는등의 기술(?) 이 들어가며 복잡해지곤 하며...문제를 완벽히 해결하기가 쉽지는 않다. 아래 글에서는 하이퍼레저 패브릭상에서 발생되는 2가지 주요 분산합의 상황에서의 리더 선출에 관해 대략적으로 끄젹 꺼려 보겠다. 

* 이 문서는 앞으로 실제 패브릭 구현 상에서의 알고리즘으로 꾸준하게 수정 될 예정이다.

Gossip Protocol 


(gossip protocol 은 주변에 랜덤하게 자신이 가진 최신 정보를 뿌려주고, 랜덤으로 선택된 피어에게 최신 정보를 요청하는 주로 push-pull 모델을 이용하여 전체 네트워크의 상태를 일치시키는 알고리즘이다. 이때 최신정보는 항상 리더로 부터 시작된다. 리드 선출에 관해 공개된 상세한 정보는 없으며, 코드를 봐야 이해 할 수 있다. 공식문서에는 그냥 static으로 설정하거나 dynamic으로 설정 할 수 있다 정도~)

- 상태는 Leader , Follower 2가지이다.
- ID 문자열의 사전상 순서로 우선순위를 가진다. 
- term id , epoch id 란 개념이 없다. 
- 하나의 membership view에서는 하나의 리더를 가진다.
- 네트워크가 분리되면 분리 된 수 만큼 리더를 가지며, (각자 orderer에게 블록 요청) 합쳐지면 하나로 돌아 온다.
- Leadership declaration (내가 리더다) 와 Proposal (내가 리더가 되고 싶어) 메세지가 있다. 
- Leadership declaration (내가 리더다) 를 주기적으로 보내며, 이것을 못받은 Follower는 리더선출에 돌입하며, 이렇게 네트워크는 분리된다..나중에 더 낮은 ID를 가진 노드에서 Leadership declaration 받게되면 리더 자리를 물러나게 되며 자연스럽게 네트워크는 합쳐진다.

 Startup():
 	wait for membership view to stabilize, or for a leadership declaration is received
      or the startup timeout expires.
	goto SteadyState()

// 3 possible ways to stabilize
//   - After a period of time, check whether the number of peer lists before and after is consistent.
//     if they are consistent, it indicates stability.
//   - If a leader is elected, it's also stable
//   - Otherwise if timeout, consider it stable and vote for new leader from proposal set


 SteadyState():
 	while true:
		If leaderKnown is false:
 			LeaderElection()
		If you are the leader:
			Broadcast leadership declaration
			If a leadership declaration was received from
 			a peer with a lower ID,
			become a follower
		Else, you're a follower:
			If haven't received a leadership declaration within
 			a time threshold:
				set leaderKnown to false

 LeaderElection():
 	Gossip leadership proposal message
	Collect messages from other peers sent within a time period
	If received a leadership declaration:
		return
	Iterate over all proposal messages collected.
 	If a proposal message from a peer with an ID lower
 	than yourself was received, return.
	Else, declare yourself a leader

 

RAFT


RAFT도 위와 조금 비슷하긴 한데 차이점은 우선순위(ID문자열 기준)가 없고, Term (epoch) 이라는 1씩 증가하는 공통적으로 공유하는 상태가 있으며 Candidate 상태가 추가 되었다. 특히 RAFT은 Gossip에 비해 split brain 문제를 방어하기 위해서 좀 더 복잡 한 면이 있으며, 리드선출 기간이 피어마다 랜덤하게 설정되서 먼저 자기를 투표해 달라고 하는 놈이 있으면 그 놈에게 그냥 투표해 주기 때문에 결국 그 피어가 리더가 될 확률이 높아진다. 과반수로 부터 표를 받으면 자기가 리더인것을 확정짓고 리더로써의 하트비트를 날려 주게 된다. 만약 운이 안좋아서 비슷한 시간에 2개의 피어가 후보자가 될 경우, 표를 과반 수 이상 못받으면 다음 Term으로 넘어간다. 즉 split brain이 발생하지 않는다. (비잔틴은 없다라고 가정한다) 

두가지 타임아웃이 있다:  heartbeat timeout (리더), election timeout (팔로워) heartbeat타임아웃은 election timeout보다 주기가 짧아야한다. election timeout은 그보다 큰 제한하에  각 노드별로 랜덤값 

1. When is it started 

     - 리더로 부터 정해진 시간안에
       정식 메세지 , 하트비트 ,  candidate 로 부터의 vote 메세지중 아무것도 못받은 경우   


2. How to elect leader 

     -  리더로 부터 아무것도 못받은 각 못받은 노드는 자신의 election timeout (노드별 랜덤) 이 발생하면 상태를 candidate로 변경하고 term + 1하고 스스로에게 투표한후 나머지 follower들에게 투표를 요청 한다.
     -  election timeout  이 발생되지 않은 follow 노드는 투표를 해 준다. ( 동일 term에 한해 1표, Candidate는 다른 Candidate에게 투표 안함) 
     - follower는 투표해주고 election timeout을 리셋한다. (term도 갱신된다) 
     - 과반수의 투표를 받으면 상태를 leader로 바꾸고 리더 역할을 한다. 
     - 과반수를 못받으면 다시 투표를 요청한다.
     - election timeout이 거의 비슷한 노드가 동시에 candidate 되었을 경우 발생 할 수 있는 split brain 에서 과반수를 받은 candidate가 없어서 leader가 선출이 안됬을 경우, re election을 진행해서 랜덤하게 새로 정해진 (term 마다 항상 새로 선정됨) election timeout을 기반으로 다시 leader 선출을 진행한다. 이때 follower 중 하나가 election timeout이 빨리 도달하면, 기존 candidate를 재치고 term+1을 통해 리더가 될 수도 있다. 
     - term이 높은 메세지가 오면 follower로 돌아간다. 

http://thesecretlivesofdata.com/raft/
 
리더 선출에 대한 간단히 표로 정리 해보았다.

 

 

페이스북 :  엔터프라이즈 블록체인 그룹 

FastFabric: Scaling Hyperledger Fabric to 20,000 Transactions per Second

위 논문에 대한 저자와의 질문/답변을 공개합니다.



Question to U.Waterloo

Improvement 1: Orderer - Seperate transaction header from payload\

- You stated that we should reduce the amount of data sent from Orderer to Kafka by keeping the RWSet at the Orderer and send only the transaction's header to Kafka
- This works well when there's only one Orderer, but in case there's multiple Orderers to distribute load, how can the Orderer that made the block retrieves the body of transactions from the other Orderers?

Improvement 2: Orderer - Process transactions in parallel

- We think it's a good idea to distribute transactions into multiple threads inside Orderer as this utilizes hardware more efficiently
- However the disruptor pattern (https://lmax-exchange.github.io/disruptor/) maybe faster for this task. What are your thoughts on applying this pattern?

Improvement 3: Peer - Replacing the world state database with a hash table

- As we understood, you wanted to replace LevelDB with a completely in-memory lightweight structure like a hash table. We think this is a good improvement, but it comes with its own problems:
- What if the stateDB grows in size to be more than the machine's memory?
   @There must be a mechanism to save memory to disk, change the hardware and boot up the node a again
   @Physically upgrade the node in this way cost a lot of time and resources
- What if the node goes down suddenly? All data stored in memory would be lost, whereas with LevelDB it would be recovered partly from disk.

Improvement 4: Peer- Store blocks using a cluster

- As we understood, you suggest peers should store files in a Hadoop or Spark cluster
- We think this will greatly increase the complexity of the Peer with little benefit, since State DB storage has more impact on TPS than block storage. Do you have any other reason for this suggestion than to increase block write speed?

Improvement 5: Peer - Separate commitment and endorsement

- We agree with splitting committer and endorser into different servers since this will reduce context switching.
- We will make the endorser's DB a read-only replica of the committer's State DB as designed after Improvement 3.
- This approach comes with committer - endorser synchronization overhead (due to the need to use networking to synchronize states). We think more testing is needed to see if this overhead is worth the benefits.

Improvement 6: Peer - Parallelize validation

- From what we've read, this mean parallelize packet consistency check, signature check an RWSet correctness check.
- We see there's a potential for double spending when we process transactions to the same account in parallel. Do you have any solution for this without impacting performance?
- Also, concurrent processing comes with context switching overhead, will this be a problem when the system's under load?
- We intend to offload signature verification to hardware using FPGA or ASIC, do you think this will result in good improvements in processing speed?

Improvement 7: Peer - Cache marshaled blocks

- We mostly agree with this since this make the cache more versatile and reduce memory allocation.
- We will perform benchmark on this to see how much performance improvements can be gained while not increasing the complexity of the system.

Response from U.Waterloo

  1. We found that Kafka got exorbitantly slower for bigger message sizes. That is why we split of the body from the tx header. For multiple orderers, each would need to broadcast their tx bodies to other orderers out of band of Kafka, which we found to be faster in preliminary tests, but we didn’t fully implement the broadcast. Note that this might be obsolete now that Fabric is using Raft.
  2. I’m not very familiar with the disruptor, but it seems to me that this is an orthogonal optimization. Currently, all orderer cores but one are idle, so we definitely need to optimize for concurrent execution. It seems to me that the disruptor is simply managing access to the tx queue. Therefore it would need to be tested if the disruptor is faster than using a go channel for this.
  3. For most reasonable workloads a peer’s memory should be able to store billions of keys in memory. If this is not enough, some paging mechanism would need to be implemented, but we did not address this in our paper. Peers are still writing to disk, they just outsource the task to a secondary storage server. Therefore the latest permeated state could be recovered from there. We did not check if Fabric’s recovery mechanisms still work with the external storage, but it should be “relatively” easy to reroute recovery to the storage server.
  4. We suggest Hadoop or Spark as a possibility to be able to easily do data analytics on the peer backend. However, it is not necessary and in our proof of concept implementation we still use LevelDB (the storage server simply has to catch up to the fast peer whenever it is not running at maximum throughput)
  5. After implementing all our improvements, the throughput was still CPU bound (crypto computations), so we didn’t feel the network overhead yet.
  6. In FastFabric, we only parallelize the crypto verification, RWset correctness validation is still done sequentially to prevent double spending. In our most current work (https://arxiv.org/abs/1906.11229) we add a tx dependency analyzer to be able to parallelize RWset correctness validation for independent txs as well. I think offloading crypto to specialized hardware is the way to go forward to increase performance further.
  7. Using a unmarshalling cache as described in the paper is a workaround for better performance so we didn’t need to completely rewrite Fabric. In our most current implementation, each block is caching it’s own unmarshalled data, so we don’t need a centralized cache anymore (so no potential locking issues with multiple threads reading/writing blocks). Ideally, the unmarshalling would happen directly at the network interfaces and, internally, Fabric would only deal with domain model objects.

 

페이스북 :  엔터프라이즈 블록체인 그룹 

예제로 이용된 재료는 이전에 만든  400라인의 go코드로 구현한 하이퍼레저 패브릭 [2]- 블록전파/Gossip 프로토콜 소스(앞으로는 gossip분산 서비스로 지칭)를 사용였는데 간단한 분산 네트워킹 예제이므로 도커/쿠버네이트 공부를 위한 좋은 재료가 될 것 입니다. 이 글은 그 예제의 연속성 상에서 기획된 글이며, 도커/쿠버네이트 내용을 한번 정도 읽어 봤다는 혹은 아래 참고 링크를 공부하면서 진행한다는 가정하에 실습을 위해 정리 한 포스트임을 알려드립니다. 각각의 기술에 대해 구체적으로 알고 싶은 분은 아래 레퍼런스를 참고 하시거나, 추가 구글링을 통해 확인 하십시요.

도커

아래 처럼 Dockerfile 을 만듭니다.

# Start from a Debian image with the latest version of Go installed
# and a workspace (GOPATH) configured at /go.
FROM golang

# Copy the local package files to the container's workspace.
ADD . /go/src/github.com/wowlsh93/hyperledger-fabric-400-gossip

# Build the gossip command inside the container.
# (You may fetch or manage dependencies here,
# either manually or with a tool like "godep".)
RUN go install github.com/wowlsh93/hyperledger-fabric-400-gossip/gossip

위의 도커파일을 가지고 gossip이라는 이름의 도커이미지를 만듭니다. 

docker build -t gossip .

기반 이미지는 golang 을 사용하며, 빌드시 현재 프로젝트(예제를 확인하시고 github 에서 가져오세요)를 컨네이너의 /go/src/github.com/wowlsh93/hyperledger-fabric-400-gossip 위치에 복사해 놓습니다. 그리고 소스를 인스톨하여 go/src/gossip 에 실행파일을 위치 시켜 놓습니다.

docker images

만들어진 이미지를 확인 하는 명령어는 위와 같습니다. 

docker run -p 28000:28000 --name gossip1 --rm gossip /go/bin/gossip -name 127.0.0.1:28000 -ip 127.0.0.1 -port 28000 -leader

실행은 run 명령어를 사용하며 옵션이 의미하는 바는  

-p 옵션으로 외부포트:내부포트를 포워딩 하고
-name : 이름은 gossip1 으로 임의로 정합니다.
-rm :  옵션으로 컨테이너가 내려가면 자동으로 삭제되게 하고 
컨테이너 실행시  /go/bin/gossip -name 127.0.0.1:28000 -ip 127.0.0.1 -port 28000 -leader 명령을 실행해 리더피어 서비스를 시작해 줍니다.  

두번째 터미널을 열어서 리더피어 말고 일반 피어를 위해 컨테이너를 띄어 봅시다. 
docker run -p 28001:28001 --net host --name  gossip2 --rm 
gossip /go/bin/gossip -name 127.0.0.1:28001 -ip 127.0.0.1 -port 28001  -bootstrap 127.0.0.1:28000
근데 컨테이너를 띄우면 에러가 나는데  컨테이너간 네트워킹을 할 수 없기 때문이며 이를 해결하기 위해 네트워킹 설정을 해야 합니다. 

도커 네트워킹 

1) 같은 노드에 있는 컨테이너 실습 - host 방식 

도커를 처음 설치하면 none, host, bridge 세 가지 종류의 네트워크가 자동으로 설정되는데  none방식(-net=none) 으로 하면 격리된 네트워킹을 갖기 때문에 안되고,  -net 옵션으로 host를 주면 모든 컨테이너가 호스트 네트워크를 같이 사용하게 되기 때문에 컨테이너간에 네트워킹이 가능해 집니다.

docker run -p 28000:28000 --net host --name gossip1 --rm gossip /go/bin/gossip -name 127.0.0.1:28000 -ip 127.0.0.1 -port 28000 -leader

위 처럼 -net host 를 각 컨테이너를 시작 할 때 넣어 주면 됩니다. 동일한 네트워크에 있기 때문에 포트만으로도 통신이 되는거죠.


2) 같은 노드에 있는 컨테이너 실습 - bridge 방식 

브리지방식은 도커의 디폴트 방식이기에 -net 옵션 없으면 자동으로 선택되는데 브리지 방식으로는 각각의 컨테이너가 다른 사설IP 를 갖게 되므로 포트만을 구분하는 예제(Gossip 서비스)에서는 안되는게 당연하며 추가정보가 필요합니다. 

docker network create --driver bridge gossip-network

일단 bridge형식의 네트워크를 하나 추가로 만듭니다. 이름은 gossip-network 로 하구요.

docker network ls

NETWORK ID NAME DRIVER SCOPE d87103af938c bridge bridge local 659368a11889 composer_default bridge local 17b35b1ad8c5 gossip-network bridge local 541d637a227c host host local de0e0715ded6 net_basic bridge local 6b6f56779a9a none null local

docker network ls 명령어로 gossip-network 가 만들어진것을 확인합니다.

docker run -it --network=gossip-network --name gossip1 gossip /go/bin/gossip -name gossip1:28000 -ip gossip1 -port 28000 -leader
docker run -it --network=gossip-network --name gossip2 gossip /go/bin/gossip -name gossip2:28001 -ip gossip2 -port 28001 -bootstrap gossip1:28000
docker run -it --network=gossip-network --name gossip3 gossip /go/bin/gossip -name gossip2:28002 -ip gossip3 -port 28002 -bootstrap

이제 각 도커 컨테이너를 시작시 옵션으로 -network=gossip-network 로 네트워크 설정을 하고, 불편한 IP주소 대신 gossip1~3처럼 컨네이너 명으로 컨테이너 끼리 통신 하게 할 수 있습니다. 컨테이너 끼리 NAT 테이블 안에서 브리지로 통신하기 때문에 굳이 -p 옵션으로 포트포워딩은 필요 없습니다. 참고로 다시 시작 할 때 컨네이터가 중단된 채 존재하기 때문에 안될 수 있는데 삭제 한 후에 해야합니다. 전체 컨테이너 삭제 명령어는 docker rm `docker ps -a -q` 입니다.


3) 같은 노드에 있는 컨테이너 실습 -  Docker Compose 

version: '2'
services:
leader:
image: gossip
container_name: gossip1
command: /go/bin/gossip -name gossip1:28000 -ip gossip1 -port 28000 -leader
restart:
on-failure


peer1:
image: gossip
container_name: gossip2
command: /go/bin/gossip -name gossip2:28001 -ip gossip2 -port 28001 -bootstrap gossip1:28000
restart:
on-failure
depends_on:
- leader
peer2:
image: gossip
container_name: gossip3
command: /go/bin/gossip -name gossip3:28002 -ip gossip3 -port 28002 -bootstrap gossip1:28000
restart:
on-failure
depends_on:
- leader

도커 컴포즈는 여러개의 컨테이너를 한방에 시작 시킬때 유용합니다. docker-compose.yaml  파일을 만들어 위의 내용을 작성합니다. 각 서비스를 구분했으며  옵션은 쉽게 이해 될 것입니다. 주의 할 점은 일반 피어는 리더피어가 생성된 후에 떠야하기때문에 depends_on옵션이 추가되었습니다. 

docker-compose -p gossipnetwork up -d

-p옵션으로 네트워크를 설정해주며,  -d 는 detached mode으로 로그가 안 보이기 때문에. -d 옵션을 빼거나 

docker logs -t -f gossip1

같은 명령어로 각 컨테이너들의 stdout 로그를 확인합니다.


4) 다른 노드에 있는 컨테이너 실습 - Overlay Network by etcd

일단 만들어진 이미지를 도커허브에 올려서 각 노드에서 편하게 사용 하려고 합니다.  참고

이제 어디서에나 도커만 있다면 이미지를 다운로드 (이미지 이름이 gossip 에서 wowlsh93/gossip:1 로 변경됨) 받아서 실행 할 수 있습니다.

docker pull wowlsh93/gossip:1

도커(컨테이너)의 장점이 gossip 서비스를 돌리기 위한 어떤 환경(go컴파일러등)이 갖추어져 있지 않더라도, 도커만 있는 곳이라면 실행 할 수 있어서 인프라 설치를 위한 공을 들일 필요가 없는거니까요.

이제 본격적으로 여러 노드에서 gossip분산 서비스를 gossip-network 를 만들어서 실행시켜 보시면 잘 될리가 없습니다. 각 네트워크는 각 노드 내부에서만 활동하기 때문인데요. 외부 네트워크주소를 입력하는 부분도 없잖아요. 당연한거죠. 다른 방법이 필요 합니다. 

여기서 다룰 오버레이 네트워크는 여러 머신에서 각각의 네트워크에서 돌아가는 서비스들이 하나의 네트워크에 있는 것 처럼 만들어 주는 방식(docker swarm, hadoop yarn, apache mesos, kubernetes 등) 으로 다양하게 이루어 질 수 있는데, 여기서는 Docker 자체에서 제공하는 방식으로 만들어 볼 예정입니다.

* 사실 우리 프로그램 경우 그냥 실행시 name 옵션에서 정확한 외부 ip 를 입력해주면 잘 동작합니다. 굳이 이딴거(오버레이/스웜/쿠버네티스) 사용 할 필요도 없긴합니다. 실제 하이퍼레저 패브릭도 마찬가지입니다. 도커,쿠버네티스 같은거 사용 안해도 돌아가는 건 상관이 없어요. 각 호스트에서 디펜던시를 따로 다 설치하는 귀찮음이 있긴 하지만 오히려 첨에 실습해보는데는 더 클리어 하구요. 다만 초기 실습이 아니라 일이 되면 여러모로 추상화/자동화가 편하겠지요. 

도커 없이 ip만 입력해 주면 됨) 
 gossip -name  192.168.0.5:28001 -ip  192.168.0.5 -port 28001  -bootstrap   192.168.0.2:28000 

도커만 사용해도 ip만 입력해 주면 됨) 
 docker run -p 28001:28001 --net host --name  gossip1 --rm wowlsh93/gossip:1 /go/bin/gossip -name   192.168.0.5::28001 -ip   192.168.0.5  -port 28001  -bootstrap  192.168.0.2:28000

                                          (이미지 출처: https://blog.naver.com/alice_k106/220772125819)

위 그림처럼 각각의 서버에 위치하더라도 하나의 네트워크처럼 작동하도록 하기 위해 이 글에서는  etcd (분산 key-value store로 zookeeper 같은 분산코디네이팅 역할을 함) 라는 것을 사용 할 건데요. 5개의 서버를 활용하여 3개에는 도커를 설치하여 우리가 만든 이미지를 다운로드 해주시고, 2개에는 etcd를 설치합니다.  저 같은 경우는 VirtualBox에 Host-only-adapter 방식으로 5개의 호스트 네트워크를 구성하였습니다. bridge 로 해도 되고 서로 ping 날려서 확인만 되면 됩니다. 

etcd 설치 - etcd node 1

ubuntu@docker-node1:~$ wget https://github.com/coreos/etcd/releases/download/v3.0.12/etcd-v3.0.12-linux-amd64.tar.gz
ubuntu@docker-node1:~$ tar zxvf etcd-v3.0.12-linux-amd64.tar.gz
ubuntu@docker-node1:~$ cd etcd-v3.0.12-linux-amd64

ubuntu@docker-node1:~$ nohup ./etcd --name docker-node1 --initial-advertise-peer-urls http://192.168.205.10:2380 \
--listen-peer-urls http://192.168.205.10:2380 \
--listen-client-urls http://192.168.205.10:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://192.168.205.10:2379 \
--initial-cluster-token etcd-cluster \
--initial-cluster docker-node1=http://192.168.205.10:2380,docker-node2=http://192.168.205.11:2380 \
--initial-cluster-state new&


etcd 설치 - etcd node 2

ubuntu@docker-node2:~$ wget https://github.com/coreos/etcd/releases/download/v3.0.12/etcd-v3.0.12-linux-amd64.tar.gz
ubuntu@docker-node2:~$ tar zxvf etcd-v3.0.12-linux-amd64.tar.gz
ubuntu@docker-node2:~$ cd etcd-v3.0.12-linux-amd64/

ubuntu@docker-node2:~$ nohup ./etcd --name docker-node2 --initial-advertise-peer-urls http://192.168.205.11:2380 \
--listen-peer-urls http://192.168.205.11:2380 \
--listen-client-urls http://192.168.205.11:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://192.168.205.11:2379 \
--initial-cluster-token etcd-cluster \
--initial-cluster docker-node1=http://192.168.205.10:2380,docker-node2=http://192.168.205.11:2380 \
--initial-cluster-state new&

ubuntu@docker-node2:~/etcd-v3.0.12-linux-amd64$ ./etcdctl cluster-health
member 21eca106efe4caee is healthy: got healthy result from http://192.168.205.10:2379
member 8614974c83d1cc6d is healthy: got healthy result from http://192.168.205.11:2379
cluster is healthy


ondocker-node1
if docker version >= 17.09

ubuntu@docker-node1:~$ sudo service docker stop ubuntu@docker-node1:~$ sudo /usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-store=etcd://192.168.205.10:2379 --cluster-advertise=192.168.205.12:2375

On docker-node2

ubuntu@docker-node2:~$ sudo service docker stop ubuntu@docker-node2:~$ sudo /usr/bin/docker daemon -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-store=etcd://192.168.205.10:2379 --cluster-advertise=192.168.205.13:2375

On docker-node3

ubuntu@docker-node2:~$ sudo service docker stop ubuntu@docker-node2:~$ sudo /usr/bin/docker daemon -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-store=etcd://192.168.205.10:2379 --cluster-advertise=192.168.205.14:2375


모든 설치가 끝나고, 아무 도커호스트에서 아래처럼 오버레이 네트워크를 만듭니다.

docker network create -d overlay gossip-overlay

모든 노드에서 동일한 네트워크가 만들어 졌음을 확인합니다. 이제 해당 네트워크를 통해서 2)번의 명령어와 동일하게 실행 할 수 있습니다. 물론 네트워크이름은 바꿔야죠.

docker run -it --network=gossip-overlay --name gossip1 gossip /go/bin/gossip -name gossip1:28000 -ip gossip1 -port 28000 -leader
docker run -it --network=gossip-overlay --name gossip2 gossip /go/bin/gossip -name gossip2:28001 -ip gossip2 -port 28001 -bootstrap gossip1:28000
docker run -it --network=gossip-overlay --name gossip3 gossip /go/bin/gossip -name gossip2:28002 -ip gossip3 -port 28002 -bootstrap


5) 다른 노드에 있는 컨테이너 실습 - Docker Swarm

..진행중..


참고:

도커 / 도커허브
https://subicura.com/2017/02/10/docker-guide-for-beginners-create-image-and-deploy.html

도커 네트워크
https://mesosphere.com/blog/networking-docker-containers/

도커 컴포즈 네트워크
https://medium.com/@caysever/docker-compose-network-b86e424fad82

도커 오버레이 네트워크 by etcd
https://docker-k8s-lab.readthedocs.io/en/latest/docker/docker-etcd.html

쿠버네티스 개론 및 실습 동영상
https://www.youtube.com/watch?v=l42GttmnnZ4

쿠버네티스 설치
https://medium.com/@dirty49374/kubeadm%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-kubernetes-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0-ubuntu-18-04-61710f0b4db8




얼마전에 블록체인사 CTO분들하고 대화중에 하이퍼레저 패브릭의 합의 알고리즘에 대해서 얘기하다가 "뇌정지"가 온적이 있다. 비트코인 POW부터 POS,DPOS,PBFT,텐더민트식,캐스퍼등에 대한 이해를 거친후에 더 이상은 컨센서스 알고리즘에 대한 공부는 그만 뒀고, (분산네트워킹만이라도 잘하자 싶어서..) 하이퍼레저 패브릭은 그냥 실행-오더링-커밋이지 뭐...라고 평소에 생각했었는데 이번 기회를 통해 정리를 좀 해보려 한다. 알고리즘 자체에 대한 내용이라기 보다는 전체조망이랄까? 주저리주저리 해 볼 생각이다.

거의 유일한 하이퍼레저 패브릭 서적이라고 볼 수 있는데,
이 책을 완독한 후에도 합의 관련된 이해는 할 수 는 없다. 관련 내용이 (구체적으로) 없으니까~
즉 모든 원흉은 이 책이다. 농담이다..
400페이지가 넘어가며 정말 많은 내용을 충실히 작성해 놓은 이 책에서 합의알고리즘 내용은 
58page에 합의 항목이 나오는데 "하이퍼레저패브릭에서 합의 시스템은 보증-오더링-검증모델 기반하에 플러그인 될 수 있다. 하이퍼레저 패브릭의 오더링 서비스는 합의 시스템을 표현한다." 정도의 표현만 있다. 
(근데 사실 이게 핵심이자 모든것이기도 하다.) 

이제 본격적으로 히스토리를 살펴보자.
하이퍼레저 패브릭 0.6버전에서는 PBFT를 사용했다고 한다. (PBFT에 대한 좀 더 구체적인 것은 여기 참고)

그림 - 하이퍼레저 패브릭 0.6

위의 0.6 도식도와 같이 오더러가 없으며, (컨센서스가 대행함) 보증하는 부분과 커미팅하는 부분이 나뉘지도 않았다.
이 Peer들끼리 아래와 같은 PBFT를 수행했으며 간락하게 설명하면 

그림 - PBFT 

Pre-prepare단계에서 트랜잭션을 모두 공유하고, 
Prepare단계에서 각자 Peer들은  트랜잭션을 받았다는 것을 알리며,
Commit단계에서 각자 Peer들은 이 트랜잭션에 대해서 합의를 해준다라는 것을 모두에게 알리며 
마지막으로 동료Peer들의 합의 상태에 따라서 OK인지 아닌지를 각자 확인해서 Reply 해준다.

이 알고리즘은 보통 2/3 이상의 합의가 있으면 통과이고, 대략 2n^2번의 커뮤케이션이 필요하게 된다.
즉 Peer숫자가 50개만되도 엄청 느려진다는 말이다. 처음 하이퍼레저패브릭을 설계했을때, 허가형블록체인으로 소수의 노드를 대상으로 서비스 한다고 생각 했을 테니 일리가 있지만, 무리가 따랐나보다. 1.0에서는 아키텍쳐가 달라진다.
* 하이퍼레저 0.6 PBFT에 대한 좀 더 자세한 설명은 여기  
근데 중국어라는게 함정 (간자체로 번역하세요)

그림 - 하이퍼레저 패브릭 1.0  Endorsing - Ordering - Validating 3단계 구조 

PBFT식으로 Peer들간에 커뮤니케이션 하는 부분이 너무 느려서 없어지고,  Endorsing - Ordering - Validating 3단계 구조를 기반으로 합의 알고리즘이 완성된다.  Endorsing 에서는 보증역할을 맡은 Peer들이 트랜잭션에 대해 실행을 하고 결과 값 사인한후에 클라이언트에 돌려준다. 여기서 Peer들간의 커뮤니케이션은 없다. 이런 값을 오더러에게 전달하면 오더러는 트랜잭션에 순서를 매기고, 블록으로 완성 한후 Validator 에게 전달하면 Validatior에서는 보증이 어떻게 되있는지 확인하여 만족 시키면 장부에 기입한다.(좀 더 자세한 설명 여기) 자 이렇게 되면 커뮤니케이션 비용은 확실히 낮아지게 되는데 병목 부분을 생각해보면 3군데 있는데
1. Endorsing 하는 부분에서 체인코드를 실행하는 비용
2. Signning (서명 및 확인)부분이다. 
3. 앞으로 말할 Ordering 서비스에 어떤 합의 알고리즘이 들어가냐의 문제이다.

그럼 글 서두에 말한 "하이퍼레저 패브릭의 Ordering 서비스는 합의 시스템을 표현한다." 는 무엇인가? 
https://github.com/hyperledger/fabric/tree/master/orderer  다음 링크를 살펴보면 아래와 같이 나온다.

  • Solo ordering 서비스 (테스팅 목적): 쉽게 이용되도록 만들어진 극히 간단한 서비스로써 프로덕션용은 아니다. 싱글 프로세스로 구성되어 있으며, 하나가 모든 클라이언트들에 대해 서비스 한다. 따라서 사실상 합의라는게 없다. availability 나 scalability에 대해 고려되지 않으며 따라서 이것은 그냥 테스트용~
  • Kafka기반 오더링 서비스(프로덕션)Pub/Sub구조의 메세지큐 미들웨어인 Kafka 기반의 ordering service 이다. Peer 오더러 클라이언트 코드가 카프카에 대해 상세히 작성되지 않도록 ab.proto 정의로 이것을 추상화 한다.Kafka는 현재 높은 처리량과 고가용성을 요구하지만 byzantine 내결함성은 요구하지 않는 운영 배치에서 선호되는 선택이다.
  • PBFT 오더링 서비스(지연됨):  BFT적인 방식에 의해 메세지들을 나열하고 순서 매이기 위한 구현으로 만들어 질 것이다. (현재 개발중) 

즉 위와 같이 구현 할 수 있으며, 현재는 Zookeeper/Kafka기반하여 속도중시/부하분산/복제와리더재선출에 의의한 HA/오더러가 순서매기는데 도움/ 정도의 역할로 외부시스템을 이용해서 이루어져 있는 것이다. 하지만 카프카가 오더러들에 대한 HA까지 보장해주진 않는다. 멀티오더러에 대해서는 node sdk를 사용하는 미들웨어 단에서 잘 작동하는 오더러에 대한 선택을 하는 수동적인 방식이 있는 것 같다. 만약 좀 더 신뢰/오더러에 대한HA를 중시하고 싶으면 BFT계열(PBFT등)를 추가 구현 할 수도 있을것이다. 개인적으로도 허가과정등을 통해 신뢰비용에 대해 어느정도 보완 했다고 판단하기 때문에 Kafka기반이면 충분하지 않나 싶기도 하는데 외부모듈을 무겁게 가져다 사용하는건 문제다. 그리고 Kafka가 하는 역할에 대해  CFT (Crash Fault Tolerance) 같은 용어를 쓰는데, BFT처럼 제대로된(?) 합의 알고리즘이 아니라, 주키퍼-카프카 구성으로 장애에 대한 대처가 가능한 상태에서 오더링을 하기 때문이다. 아무튼 개인적으로는 현재 카프카-주키퍼 오더링 부분과 앞으로 나올 Raft식의 오더링에 대한 행위에 대해서 '합의' 는 POW식의 '신뢰의 합의' 는 아니고 고전적인 분산시스템에서의 내고장 합의를 말하는 것이다. (오더러 부분도 각 조직들에 의해 권한을 분산해서 나누어 갖는 형태가 되면 신뢰의 합의까지 되겠지만 확실하지 않다. 만약 그렇게 까지 하는건 좀 오버인거 같다.)

그림 - 하이퍼레저 패브릭 로드맵 

구글링을 해보면 위의 표를 많이 볼 수 있는데 1.3 에서는 RAFT오더러, 1.4에서는 BFT오더러의 구현을 계획했는데 2019년 1월 현재 1.4가 출시된 상황에서 어떻게 되었을까?

What’s new in v1.3 
에는 RAFT오더러에 관련된 내용이 없다.
What’s new in v1.4 에도 BFT 오더러에 대한 내용은 없다.

그럼 2.0버전에서는 어떻게 될까? 프로젝트 현황에 대해 지라 와 제안문서 살펴보니 RAFT / etcd/raft 기반으로 개발중인듯 싶은데 비교적 단순한 알고리즘으로 
BFT처럼 무겁지 않고 CFT 역할에 그치더라도 1) 카프카-주키퍼에 의존하지 않는 오더링 서비스를 위함. 즉 굳이 외부미들웨어 가져다 복잡하게 설치해서 쓸 필요 있냐 하는 2) 차후에 개발될 BFT 개발을 위한 기반쯤으로 생각하는 듯하다. 카프카-주키퍼 모델에 비해 성능이 어떨지에 대한 예측 및 어떻게 BFT개발을 위한 기반이 될 까에 대해서는 깊이 파보진 않았다. 

제안문서 B.II)   Raft 는 어떻게 BFT개발의 주춧돌이 되어 줄 수 있나?
RAFT는 BFT와 마찬가지로 리더 기반 프로토콜입니다. 잘 검증 된 리더 기반 합의 프로토콜을 통합한다는 것은 Fabric이이 프로토콜 군에 대한 합의 된 (confensus) 플러그인 작성자에게 제공하는 인터페이스 개선에 초점을 맞추는 것을 의미합니다. 그러한 의미에서 Raft에 대한 실험은 우리가 PBFT 기반 오더링 서비스의 토대를 마련하는 데 도움이됩니다. 연장선 상에서 Raft가 CFT 프로토콜 임에도 불구하고 BFT 컨텍스트에서만 의미가 있는 제안 된 구현에서 특정한 결정을 내립니다. 우리는 그것을 의도적으로 수행하여 BFT 기반 서비스로 전환 할 때 Fabric 핵심 변경 사항의 수를 최소로 유지 하려고 합니다. (역주: Kafka 분산복제처리도 리더기반인데..리더가 읽기/쓰기 담당하고 팔로워는 복제만) 

결국 Pluggable 하기 때문에 업체에 따라서 자신들이 만들어 추가 할 수도 있다지만 하이퍼레저 패브릭 자체에서도 추가하는데 오래걸리는데 이걸 직접 자신의 입맛에 맞게 요리해서 사용하려는 업체가 얼마나 될런지는 모르겠다. 특히 BFT알고리즘에 대한 논문은 많지만 성공적으로 구현하는것은 굉장히 어렵다. 

참고로 BFT계열의 컨센서스 플러거블 라이브러리(BFT-SMART)도 있다. 현재 정식버전으로 들어 간것은 아니며 추후 RAFT 이후에 BFT계열로 SBFT가 들어갈지, BFT-SMART가 들어갈지 모르겠다. BFT-SMART는 논문상에 10,000TPS 급의 성능을 보여주며, SBFT는 아직 테스팅된게 없는것으로 알고 있다.

결론 )

개인적으로는 카프카 대체제로는 RAFT로 충분하고 BFT계열은 간단하든 복잡하는 오버스펙이라 시작할맘도 없을 거 같습니다.참고로 JP모건 애들이 만드는 콘소시엄체인은 RAFT랑 IstabulBFT 라는것을 쓰는데 패브릭도 BFT계열을 겨우 오더링하는데 사용 할 바엔 E-O-V 프로세스를 폐기하는게 낫다고 봅니다. 아마 거기 아키텍트도 똑같은 생각하고 있을듯..ㅎㅎ



+ Recent posts