通过Consul Raft库打造自己的分布式系统
通用的CP系统有etcd和consul, 通用的对立面就是专用系统. 所以在某些场合是有这种需求的.
然而etcd embed的可用性极差, Windows上面跑会出现各种问题, 而且不能定制协议, 你必须得用etcd定义好的协议和客户端来和etcd集群通讯. 所以这时候的选择:
1. 忍着
2. 自己实现一个raft算法库, 在这上面做应用
有一定的可能性, 起码MIT 6.824可以做出来, 但是和工业应用还是有很大的差距
3. 找一个工业级raft库, 然后在这上面做应用
这时候到Raft Consensus Algorithm上面看看就能找到几个可选的Raft算法库, 例如braft, hashicorp/raft, lni/dragonboat.
但是呢, C++代码比较难写的, 所以就pass掉了braft. 就剩下consul raft和dragonboat.
本文就用consul raft做一个简单的KeyValue服务.
首先前端用的gin, 提供put/get/inc/delete几个接口, 三个接口都走raft状态机, 因为要支持多节点, 所以内部非leader节点就需要把请求转发给leader节点.
前端的代码类似于这样:
func (this *ApiService) Start() error {
//转发请求给leader节点
this.router.Use(this.proxyHandler())
this.router.POST("/get", this.Get)
this.router.POST("/put", this.Put)
this.router.POST("/delete", this.Delete)
this.router.POST("/inc", this.Inc)
address := fmt.Sprintf(":%d", this.port)
return this.router.Run(address)
}
请求都很简单, 就是直接把命令, 或者叫服务提供的原语塞到Raft状态机里面等候Raft状态Apply, 然后才能拿到结果(future/promise模式), 例如put命令:
func (this *ApiService) Put(ctx *gin.Context) {
req := &Request{}
if err := ctx.ShouldBindJSON(req); err != nil {
ctx.JSON(http.StatusBadRequest, Response{
Error: err.Error(),
})
return
}
result, err := this.raft.ApplyCommand(raft.CommandPut, req.Key, req.Value)
if err != nil {
ctx.JSON(http.StatusInternalServerError, Response{
Error: err.Error(),
})
return
}
ctx.JSON(http.StatusOK, Response{
Value: result.Value,
})
}
前端还有一个转发请求到leader节点的拦截器(? 应该叫这个名字, 实际上是pipeline模式的一种)
func (this *ApiService) proxyHandler() gin.HandlerFunc {
return func(context *gin.Context) {
if this.raft.IsLeader() {
context.Next()
} else {
leaderServiceAddress := this.raft.GetLeaderServiceAddress()
if this.leaderServiceAddress != leaderServiceAddress {
Director := func(req *http.Request) {
req.URL.Scheme = "http"
req.URL.Host = leaderServiceAddress
}
this.leaderProxy = &httputil.ReverseProxy{
Director: Director,
}
this.leaderServiceAddress = leaderServiceAddress
}
this.leaderProxy.ServeHTTP(context.Writer, context.Request)
context.Abort()
}
}
}
下面是对协议的处理:
func (this *FSM) Apply(log *raft.Log) interface{} {
result := &FSMApplyResult{
Success: false,
}
t, cmd, err := raftLogToCommand(log)
if err != nil {
result.Error = err
return result
}
binary.LittleEndian.PutUint64(keyCache, uint64(cmd.Key))
binary.LittleEndian.PutUint64(valueCache, uint64(cmd.Value))
switch t {
case CommandPut:
result.Success, result.Error = this.add(keyCache, valueCache)
case CommandDelete:
result.Success, result.Error = this.delete(keyCache)
case CommandGet:
result.Value, result.Error = this.get(keyCache)
case CommandInc:
result.Value, result.Error = this.inc(keyCache, cmd.Value)
}
return result
}
输入给Raft状态的命令实际上都是序列化好的, Raft状态机会自己把命令保存到Storage里面(可以是内存, 也可以是磁盘/DB等). 所以Apply命令的时候, 先对raft log进行解码, 然后switch去处理.
这边再看看例如inc的处理:
func (this *FSM) inc(key []byte, add int64) (int64, error) {
var value int64 = 0
err := this.db.Update(func(tx *bbolt.Tx) error {
b, err := tx.CreateBucketIfNotExists(BBoltBucket)
if err != nil {
return err
}
valueBytes := b.Get(key)
if len(valueBytes) != 8 {
logging.Errorf("FSM.inc, key:%d, value length:%d, Reset",
int64(binary.LittleEndian.Uint64(key)), len(valueBytes))
valueBytes = make([]byte, 8)
}
value = int64(binary.LittleEndian.Uint64(valueBytes))
value += add
binary.LittleEndian.PutUint64(valueBytes, uint64(value))
err = b.Put(key, valueBytes)
return err
})
if err != nil {
return -1, err
}
return value, err
}
这个指令稍微复杂一点, 需要先到db里面去找, 找到的话, 再加一个N, 然后存储, 然后返回新的值. 因为raft状态机apply log的时候, 是顺序的, 所以不需要加锁啥的, inc本身就是原子的.
至此一个简单的分布式KeyValue服务就实现, 而且还是一个CP系统.
当然这只是一个demo, 实际的应用远远比这个复杂, 本文只是提供一种思路.
不必非要把自己绑死在Etcd上, 条条大路通罗马. 如果你的系统只需要提供有限的操作原语, 那么是可以考虑Consul Raft或者DragonBoat来制作自定义协议的CP服务. 蚂蚁的SOFARaft也可以干这种事.
参考:
1) RaftKV (https://gitee.com/egmkang/raft-kv)
2) Consul Raft (https://github.com/hashicorp/raft)
3) DragonBoat (https://github.com/lni/dragonboat)
4) Dapr (https://github.com/dapr/dapr/tree/master/cmd/placement)
通过Consul Raft库打造自己的分布式系统的更多相关文章
- 结合consul raft库理解raft
一 入口 github.com/hashicorp/consul/agent/consul/server.go func (s *Server) setupRaft() error { 状态机,用于c ...
- Go多组Raft库
Go多组Raft库 https://github.com/lni/dragonboat/blob/master/README.CHS.md 使用用例 https://github.com/lni/dr ...
- Dapr实现分布式有状态服务的细节
Dapr是为云上环境设计的跨语言, 事件驱动, 可以便捷的构建微服务的系统. balabala一堆, 有兴趣的小伙伴可以去了解一下. Dapr提供有状态和无状态的微服务. 大部分人都是做无状态服务(微 ...
- 分布式系统一致性问题与Raft算法(下)
上一篇讲述了什么是分布式一致性问题,以及它难在哪里,liveness和satefy问题,和FLP impossibility定理.有兴趣的童鞋可以看看分布式系统一致性问题与Raft算法(上). 这一节 ...
- 如何快速为团队打造自己的组件库(下)—— 基于 element-ui 为团队打造自己的组件库
文章已收录到 github,欢迎 Watch 和 Star. 简介 在了解 Element 源码架构 的基础上,接下来我们基于 element-ui 为团队打造自己的组件库. 主题配置 基础组件库在 ...
- Consul文档简要整理
什么是Consul? Consul是一个用来实现分布式系统的服务发现与配置的开源工具.他主要由多个组成部分: 服务发现:客户端通过Consul提供服务,类似于API,MySQL,或者其他客户端可以使用 ...
- 基于hashicorp/raft的分布式一致性实战教学
本文由云+社区发表 作者:Super 导语:hashicorp/raft是raft算法的一种比较流行的golang实现,基于它能够比较方便的构建具有强一致性的分布式系统.本文通过实现一个简单的分布式缓 ...
- 什么是Consul
什么是Consul Consul文档简要整理 什么是Consul? Consul是一个用来实现分布式系统的服务发现与配置的开源工具.他主要由多个组成部分: 服务发现:客户端通过Consul提供服务,类 ...
- consul(一)什么是consul
1. consul的基本介绍 在分布式架构中,服务治理是一个重要的问题.在没有服务治理的分布式集群中,各个服务之间通过手工或者配置的方式进行服务关系管理,遇到服务关系变化或者增加服务的时候,人肉配置极 ...
随机推荐
- GO-字符串处理Stings包
目录 常用的字符串处理函数 (1) Contains (2) Join (3) Index (4) Repeat (5) Replace (6) Split (7) Trim (8) Fields 字 ...
- react hooks子给父传值
子给父传值需要通过事件方法来传值,这里我们子组件是触发了一个点击事件传值: <Button onClick={()=>setshowregister(false)}>注册</B ...
- 【转】Setting up SDL Extension Libraries on MinGW
FROM:http://lazyfoo.net/tutorials/SDL/06_extension_libraries_and_loading_other_image_formats/windows ...
- 使用Azure静态Web应用部署Blazor Webassembly应用
上一次演示了如何使用Azure静态web应用部署VUE前端项目(使用 Azure静态web应用+Github全自动部署VUE站点).我们知道静态web应用支持VUE,react,angular等项目的 ...
- [Luogu P2824] [HEOI2016/TJOI2016]排序 (线段树+二分答案)
题面 传送门:https://www.luogu.org/problemnew/show/P2824 Solution 这题极其巧妙. 首先,如果直接做m次排序,显然会T得起飞. 注意一点:我们只需要 ...
- 手写Koa.js源码
用Node.js写一个web服务器,我前面已经写过两篇文章了: 第一篇是不使用任何框架也能搭建一个web服务器,主要是熟悉Node.js原生API的使用:使用Node.js原生API写一个web服务器 ...
- JAVA每日一题20201109
一.标识符的规则? 1.严格区分大小写,不能使用关键字,保留字,不能重复 2.数字不能开头 二.标识符的命名规范 包名:XXXYYYZZZ 类名,接口名:XxYyZz 变量名,方法名:xxxYyyZz ...
- ElasticSearch 集群基本概念及常用操作汇总(建议收藏)
内容来源于本人的印象笔记,简单汇总后发布到博客上,供大家需要时参考使用. 原创声明:作者:Arnold.zhao 博客园地址:https://www.cnblogs.com/zh94 目录: Elas ...
- [阿里DIN] 从模型源码梳理TensorFlow的乘法相关概念
[阿里DIN] 从模型源码梳理TensorFlow的乘法相关概念 目录 [阿里DIN] 从模型源码梳理TensorFlow的乘法相关概念 0x00 摘要 0x01 矩阵乘积 1.1 matmul pr ...
- vue 常用操作
:class="{selects:source==1}" 根据变量添加class :style="{paddingTop: top}" :style=&quo ...