Introduce
http://www.liangxiansen.cn/2017/04/06/consul/
http://thesecretlivesofdata.com/raft/
Raft
一套算法,用于解决分布式系统中数据状态的一致性.与其平行的是Paxos算法.
Raft算法实际上模拟的是现实中的选举过程.涉及三个角色(均是server)
terminology | description |
---|---|
leader | server的中心节点,主要负责同步注册信息给其它的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
: 它会把所有的信息持久化的本地,这样遇到故障,信息是可以被保留的
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
}
}