Introduce

http://www.liangxiansen.cn/2017/04/06/consul/
http://thesecretlivesofdata.com/raft/

Raft

一套算法,用于解决分布式系统中数据状态的一致性.与其平行的是Paxos算法.

Raft算法实际上模拟的是现实中的选举过程.涉及三个角色(均是server)

terminologydescription
leaderserver的中心节点,主要负责同步注册信息给其它的server,同时负责各个节点的健康监测
follower选民
candidate候选人.任何server都可以成为Candidate(election-timeout最先用完超时的那个,随后他会发起投票)
  • 选举leader
    每个节点(follower)都有150-300ms随机超时时间,如果有接收到leader的心跳则重新开始超时计数.
    最开始没有leader,所以最先用完超时时间的就成为了候选人(candidate)!候选人会向其他所有节点发送投票,超过半数时则成为leader!
    成为leader后,全局变量term++.表示当前任期.
  • leader故障重新选举
    当某个节点(如A)接收不到leader的心跳后,A会term++,自动变为candidate并发起投票.
    原leader收到投票后发现term比自己大,自动放弃leader身份并切换为follower.
    假设同时有俩个节点成为candidate(设另一个为B),则A/B最先获得超过半数投票的成为leader.
    假设A/B获得的投票没有超过半数的,或者得票数相同.则此次选举失败(term++),重新进入"选举leader"的流程.
  • 数据读写
    数据读写都会走leader!即使req的是follower,follower也会把请求转发给leader,再有leader决定由哪个节点负责处理该请求!
    leader会把所有的写请求都记录到本身的log中!然后将append消息发送给所有follower(append消息是定制的,不同节点不一样).
    每个follower收到leader的append小时都会给反馈.leader收到所有反馈后会commit写记录,然后响应此次写请求.最后,再向
    所有follower发送commit消息,要求每个follower尽快commit(数据前期都已经通过append发出了).
    每个follower完事后再回馈leader,至此整个写请求处理完毕!

BFT

Byzantine Fault Tolerant

拜占庭问题: N个节点,叛变

Gossip

该协议的实现在consul中是serf模块,基于udp.

basic

8600: The DNS server, -1 to disable(resolve DNS queries. TCP & UDP)

8500: The HTTP API, -1 to disable(HTTP API. TCP only)

8400: The CLI RPC endpoint(This is used by all agents to handle RPC from the CLI. TCP only)

8302: The Serf WAN port(This is used by servers to gossip over the WAN to other servers. TCP & UDP)

8301: The Serf LAN port(gossip in the LAN. Required by all agents. TCP & UDP)

8300: Server RPC address(This is used by servers to handle incoming requests from other clients. TCP only)

client: 所有注册到当前节点的信息会被转发到server,本身不对信息持久化.

server: 它会把所有的信息持久化的本地,这样遇到故障,信息是可以被保留的

consul

cmd & api

https://www.consul.io/docs/agent/options.html
https://www.consul.io/api/

{  
  "datacenter":"dc1",
  "data_dir": "D:\\consul\\consul\\data",
  "log_level": "INFO",
  "node_name": "node1",
  "server": true,
  "ui":true,
  "bind_addr":"192.168.100.110",
  "bootstrap":true,
  "ui_dir": "D:\\consul\\consul\\webui"
}
# -node=node1:节点的名称,默认是主机名# -node-id=..:版本8才加入,节点唯一ID.不指定会默认生成唯一的.
# -server:    表示这个节点是个server,不写默认是client
# -client:    指定可访问该服务的ip(84/85/86开头的端口).默认是127.0.0.1.公网可访问请设置0.0.0.0
# -ui:         http://localhost:8500/ui可使用-http-port修改端口
# -config-dir  consul.d配置文件的路径,以.json结尾的都会被加载
# -bootstrap-expect:期望提供的server节点数目,达到时激活选举.建议3-5个
# -bind=ip: 绑定的一个node-ip,用于节点之间通信;若有多个ip,可能会让你绑定一个本机的具体IP!
# -join=ip: 启动时要加到的集群,如置顶node1的bind地址.
# -data-dir: /tmp/consul
# -datacenter=dc1
# -dev: 开发者模式,不能用于生产环境,因为该模式下不会持久化任何状态
nohup consul agent -server -client=0.0.0.0 -ui -data-dir=/tmp/consul -node=emogent1 -config-dir=/etc/consul -bootstrap-expect=1 &
docker run -d --name node1 -e 'CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}' consul agent -server -node=node1 -bootstrap-expect=3
JOIN_IP="$(sudo docker run -f '{{.NetworkSettings.IPAddress}}' node1)"
docker run -d --name node2 -e 'CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}' consul agent -server -node=node2 -join $JOIN_IP
docker run -d --name node3 -e 'CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}' consul agent -server -node=node3 -join $JOIN_IP
docker run -d --name node4 -e 'CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}' consul agent -node=node4 -join $JOIN_IP
docker run -d --name node5 -p 8400:8400 -p 8500:8500 -e 'CONSUL_LOCAL_CONFIG={"skip_leave_on_interrupt": true}' consul agent-ui -node=node5 -client=0.0.0.0 -join $JOIN_IP
docker port node5  # 查看容器的端口映射
iptables -t nat -A DOCKER -p tcp --dport 8001 -j DNAT --to-destination 172.17.0.4:8000  # 给运行中的容器添加映射端口,容器中的8000映射到主机的8001
docker exec -it node5 /bin/sh

# common commands
consul reload  # 从新加载配置文件
consul members -detailed # 查看集群的状态
consul join <ip>  # 加入某个集群,类似于启动时的-join=<ip>
consul leave <ip>
consul operator raft list-pears  # 查看leader

# kv
# 设置一个值到 user/config/connections 内容为5
consul kv put user/config/connections 5
consul kv get -detailed user/config/connections
# HTTP API
curl -s <host>/v1/kv/dir/key1  # 返回的value是用base64编码过的
curl -XPUT -d 'value2' <host>/v1/kv/dir/key2
// PUT 注册一个服务
// http://localhost:8500/v1/agent/service/register
{
  "ID": "userServiceId",  // 服务id
  "Name": "userService",  // 服务名
  "Tags": [  // 服务的tag,自定义,可以根据这个tag来区分同一个服务名的服务
    "primary",
    "v1"
  ],
  "Address": "127.0.0.1", // 服务注册到consul的IP,服务发现,发现的就是这个IP
  "Port": 8000,           // 服务注册consul的PORT,发现的就是这个PORT
  "EnableTagOverride": false,
  "Check": {  // 健康检查部分
    "DeregisterCriticalServiceAfter": "90m",
    "HTTP": "http://www.baidu.com",  // 指定健康检查的URL,调用后只要返回20X,consul都认为是健康的
    "Interval": "10s"  // 健康检查间隔时间,每隔10s,调用一次上面的URL
  }
}