手撸golang etcd raft协议之2

督邮
• 阅读 2699

手撸golang etcd raft协议之2

缘起

最近阅读 [云原生分布式存储基石:etcd深入解析] (杜军 , 2019.1)
本系列笔记拟采用golang练习之
gitee: https://gitee.com/ioly/learning.gooop

raft分布式一致性算法

分布式存储系统通常会通过维护多个副本来进行容错,
以提高系统的可用性。
这就引出了分布式存储系统的核心问题——如何保证多个副本的一致性?

Raft算法把问题分解成了领袖选举(leader election)、
日志复制(log replication)、安全性(safety)
和成员关系变化(membership changes)这几个子问题。

Raft算法的基本操作只需2种RPC即可完成。
RequestVote RPC是在选举过程中通过旧的Leader触发的,
AppendEntries RPC是领导人触发的,目的是向其他节点复制日志条目和发送心跳(heartbeat)。

目标

  • 根据raft协议,实现高可用分布式强一致的kv存储

子目标(Day 2)

  • 定义raft rpc接口
  • 定义raft lsm有限状态自动机接口(状态模式)

设计

  • config/IRaftConfig.go: 集群配置接口。简单起见, 使用静态配置模式定义节点数量和地址。
  • config/IRaftNodeConfig.go: 节点配置接口
  • roles/roles.go:raft三种角色常量
  • timeout/timeout.go:超时时间常量
  • rpc/IRaftRPC.go: raft协议的基本RPC接口及参数定义。简单起见,拟采用net/rpc实现之。
  • rpc/IRaftRPCServer.go: 支持raft协议的服务器接口。简单起见,拟采用net/rpc实现之。
  • lsm/IRaftLSM.go: raft有限状态机接口
  • lsm/IRaftState.go: 状态接口
  • lsm/tRaftStateBase.go: 基本状态数据
  • lsm/tFollowerState: follower状态的实现,未完成

config/IRaftConfig.go

集群配置接口。简单起见, 使用静态配置模式定义节点数量和地址。

package config

type IRaftConfig interface {
    ID() string
    Nodes() []IRaftNodeConfig
}

config/IRaftNodeConfig.go

节点配置接口

package config

type IRaftNodeConfig interface {
    ID() string
    Endpoint() string
}

roles/roles.go

raft三种角色常量

package roles

type RaftRole int

const Follower RaftRole = 1
const Candidate RaftRole = 2
const Leader RaftRole = 3

timeout/timeout.go

超时时间常量

package timeout

import "time"

const HeartbeatInterval = 150 * time.Millisecond
const HeartbeatTimeout = 5 * HeartbeatInterval
const ElectionTimeout = HeartbeatTimeout

rpc/IRaftRPC.go

raft协议的基本RPC接口及参数定义。简单起见,拟采用net/rpc实现之。

package rpc

type IRaftRPC interface {
    RequestVote(cmd *RequestVoteCmd, ret *RequestVoteRet) error
    AppendEntries(cmd *AppendEntriesCmd, ret *AppendEntriesRet) error
}

type RequestVoteCmd struct {
    CandidateID  string
    Term         int
    LastLogIndex int
    LastLogTerm  int
}

type RequestVoteRet struct {
    Term        int
    VoteGranted bool
}

type AppendEntriesCmd struct {
    Term         int
    LeaderID     string
    PrevLogTerm  int
    PrevLogIndex int
    LeaderCommit int
    Entries      []*LogEntry
}

type LogEntry struct {
    Tag     int
    Content []byte
}

type AppendEntriesRet struct {
    Term    int
    Success bool
}

rpc/IRaftRPCServer.go

支持raft协议的服务器接口。简单起见,拟采用net/rpc实现之。

package rpc

type IRaftRPCServer interface {
    BeginServeTCP(port int, r IRaftRPC)
}

lsm/IRaftLSM.go

raft有限状态机接口

package lsm

import "learning/gooop/etcd/raft/rpc"

// IRaftLSM raft有限状态自动机
type IRaftLSM interface {
    rpc.IRaftRPC

    State() IRaftState
}

lsm/IRaftState.go

状态接口

package lsm

import (
    "learning/gooop/etcd/raft/roles"
    "learning/gooop/etcd/raft/rpc"
)

type IRaftState interface {
    rpc.IRaftRPC

    Role() roles.RaftRole
    Start()
}

lsm/tRaftStateBase.go

基本状态数据

package lsm

import (
    "learning/gooop/etcd/raft/config"
    "learning/gooop/etcd/raft/roles"
)

//
type tRaftStateBase struct {
    // 当前角色
    role roles.RaftRole

    // 当前任期号
    term int

    // leader.id
    leaderID string

    // 集群配置
    cfg config.IRaftConfig
}

func newRaftStateBase(term int, cfg config.IRaftConfig) *tRaftStateBase {
    it := new(tRaftStateBase)
    it.init(term, cfg)
    return it
}

// init initialize self, with term and config specified
func (me *tRaftStateBase) init(term int, cfg config.IRaftConfig) {
    me.cfg = cfg
    me.role = roles.Follower
    me.term = term
    me.leaderID = ""
}

func (me *tRaftStateBase) Role() roles.RaftRole {
    return me.role
}

lsm/tFollowerState

follower状态的实现,未完成

package lsm

import (
    "learning/gooop/etcd/raft/config"
    "learning/gooop/etcd/raft/timeout"
    "sync"
    "time"
)

type tFollowerState struct {
    tRaftStateBase

    mInitOnce  sync.Once
    mStartOnce sync.Once
    mEventMap  map[tFollowerEvent][]tFollowerEventHandler
}

type tFollowerEvent int

const evStart tFollowerEvent = 1

type tFollowerEventHandler func(e tFollowerEvent, args ...interface{})

func newFollowerState(term int, cfg config.IRaftConfig) *tFollowerState {
    it := new(tFollowerState)
    it.init(term, cfg)

    // todo: to implement IRaftState
    return it
}

func (me *tFollowerState) init(term int, cfg config.IRaftConfig) {
    me.mInitOnce.Do(func() {
        me.tRaftStateBase = *newRaftStateBase(term, cfg)

        // init event map
        me.mEventMap = make(map[tFollowerEvent][]tFollowerEventHandler)
        me.registerEventHandlers()
    })
}

func (me *tFollowerState) registerEventHandlers() {
    me.mEventMap[evStart] = []tFollowerEventHandler{
        me.afterStartThenBeginWatchLeaderTimeout,
    }
}

func (me *tFollowerState) raise(e tFollowerEvent, args ...interface{}) {
    if handlers, ok := me.mEventMap[e]; ok {
        for _, it := range handlers {
            it(e, args...)
        }
    }
}

func (me *tFollowerState) Start() {
    me.mStartOnce.Do(func() {
        me.raise(evStart)
    })
}

func (me *tFollowerState) afterStartThenBeginWatchLeaderTimeout(e tFollowerEvent, args ...interface{}) {
    go func() {
        iCheckingTimeoutInterval := timeout.HeartbeatTimeout / 3
        for range time.Tick(iCheckingTimeoutInterval) {
            // todo: watch leader.AppendEntries rpc timeout
        }
    }()
}

(未完待续)

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
3年前
Java爬虫之JSoup使用教程
title:Java爬虫之JSoup使用教程date:201812248:00:000800update:201812248:00:000800author:mecover:https://imgblog.csdnimg.cn/20181224144920712(https://www.oschin
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
JavaWeb 调用接口
JavaWeb 如何调用接口CreateTime2018年4月2日19:04:29Author:Marydon1.所需jar包!(https://oscimg.oschina.net/oscnet/0f139
Wesley13 Wesley13
3年前
34.TCP取样器
阅读文本大概需要3分钟。1、TCP取样器的作用   TCP取样器作用就是通过TCP/IP协议来连接服务器,然后发送数据和接收数据。2、TCP取样器详解!(https://oscimg.oschina.net/oscnet/32a9b19ba1db00f321d22a0f33bcfb68a0d.png)TCPClien
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这