通用的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节点.

前端的代码类似于这样:

  1. func (this *ApiService) Start() error {
  2. //转发请求给leader节点
  3. this.router.Use(this.proxyHandler())
  4.  
  5. this.router.POST("/get", this.Get)
  6. this.router.POST("/put", this.Put)
  7. this.router.POST("/delete", this.Delete)
  8. this.router.POST("/inc", this.Inc)
  9.  
  10. address := fmt.Sprintf(":%d", this.port)
  11. return this.router.Run(address)
  12. }

请求都很简单, 就是直接把命令, 或者叫服务提供的原语塞到Raft状态机里面等候Raft状态Apply, 然后才能拿到结果(future/promise模式), 例如put命令:

  1. func (this *ApiService) Put(ctx *gin.Context) {
  2. req := &Request{}
  3. if err := ctx.ShouldBindJSON(req); err != nil {
  4. ctx.JSON(http.StatusBadRequest, Response{
  5. Error: err.Error(),
  6. })
  7. return
  8. }
  9. result, err := this.raft.ApplyCommand(raft.CommandPut, req.Key, req.Value)
  10. if err != nil {
  11. ctx.JSON(http.StatusInternalServerError, Response{
  12. Error: err.Error(),
  13. })
  14. return
  15. }
  16. ctx.JSON(http.StatusOK, Response{
  17. Value: result.Value,
  18. })
  19. }

前端还有一个转发请求到leader节点的拦截器(? 应该叫这个名字, 实际上是pipeline模式的一种)

  1. func (this *ApiService) proxyHandler() gin.HandlerFunc {
  2. return func(context *gin.Context) {
  3. if this.raft.IsLeader() {
  4. context.Next()
  5. } else {
  6. leaderServiceAddress := this.raft.GetLeaderServiceAddress()
  7. if this.leaderServiceAddress != leaderServiceAddress {
  8. Director := func(req *http.Request) {
  9. req.URL.Scheme = "http"
  10. req.URL.Host = leaderServiceAddress
  11. }
  12. this.leaderProxy = &httputil.ReverseProxy{
  13. Director: Director,
  14. }
  15. this.leaderServiceAddress = leaderServiceAddress
  16. }
  17. this.leaderProxy.ServeHTTP(context.Writer, context.Request)
  18. context.Abort()
  19. }
  20. }
  21. }

下面是对协议的处理:

  1. func (this *FSM) Apply(log *raft.Log) interface{} {
  2. result := &FSMApplyResult{
  3. Success: false,
  4. }
  5. t, cmd, err := raftLogToCommand(log)
  6. if err != nil {
  7. result.Error = err
  8. return result
  9. }
  10. binary.LittleEndian.PutUint64(keyCache, uint64(cmd.Key))
  11. binary.LittleEndian.PutUint64(valueCache, uint64(cmd.Value))
  12. switch t {
  13. case CommandPut:
  14. result.Success, result.Error = this.add(keyCache, valueCache)
  15. case CommandDelete:
  16. result.Success, result.Error = this.delete(keyCache)
  17. case CommandGet:
  18. result.Value, result.Error = this.get(keyCache)
  19. case CommandInc:
  20. result.Value, result.Error = this.inc(keyCache, cmd.Value)
  21. }
  22. return result
  23. }

输入给Raft状态的命令实际上都是序列化好的, Raft状态机会自己把命令保存到Storage里面(可以是内存, 也可以是磁盘/DB等). 所以Apply命令的时候, 先对raft log进行解码, 然后switch去处理.

这边再看看例如inc的处理:

  1. func (this *FSM) inc(key []byte, add int64) (int64, error) {
  2. var value int64 = 0
  3. err := this.db.Update(func(tx *bbolt.Tx) error {
  4. b, err := tx.CreateBucketIfNotExists(BBoltBucket)
  5. if err != nil {
  6. return err
  7. }
  8. valueBytes := b.Get(key)
  9. if len(valueBytes) != 8 {
  10. logging.Errorf("FSM.inc, key:%d, value length:%d, Reset",
  11. int64(binary.LittleEndian.Uint64(key)), len(valueBytes))
  12. valueBytes = make([]byte, 8)
  13. }
  14. value = int64(binary.LittleEndian.Uint64(valueBytes))
  15. value += add
  16. binary.LittleEndian.PutUint64(valueBytes, uint64(value))
  17. err = b.Put(key, valueBytes)
  18. return err
  19. })
  20. if err != nil {
  21. return -1, err
  22. }
  23. return value, err
  24. }

这个指令稍微复杂一点, 需要先到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库打造自己的分布式系统的更多相关文章

  1. 结合consul raft库理解raft

    一 入口 github.com/hashicorp/consul/agent/consul/server.go func (s *Server) setupRaft() error { 状态机,用于c ...

  2. Go多组Raft库

    Go多组Raft库 https://github.com/lni/dragonboat/blob/master/README.CHS.md 使用用例 https://github.com/lni/dr ...

  3. Dapr实现分布式有状态服务的细节

    Dapr是为云上环境设计的跨语言, 事件驱动, 可以便捷的构建微服务的系统. balabala一堆, 有兴趣的小伙伴可以去了解一下. Dapr提供有状态和无状态的微服务. 大部分人都是做无状态服务(微 ...

  4. 分布式系统一致性问题与Raft算法(下)

    上一篇讲述了什么是分布式一致性问题,以及它难在哪里,liveness和satefy问题,和FLP impossibility定理.有兴趣的童鞋可以看看分布式系统一致性问题与Raft算法(上). 这一节 ...

  5. 如何快速为团队打造自己的组件库(下)—— 基于 element-ui 为团队打造自己的组件库

    文章已收录到 github,欢迎 Watch 和 Star. 简介 在了解 Element 源码架构 的基础上,接下来我们基于 element-ui 为团队打造自己的组件库. 主题配置 基础组件库在 ...

  6. Consul文档简要整理

    什么是Consul? Consul是一个用来实现分布式系统的服务发现与配置的开源工具.他主要由多个组成部分: 服务发现:客户端通过Consul提供服务,类似于API,MySQL,或者其他客户端可以使用 ...

  7. 基于hashicorp/raft的分布式一致性实战教学

    本文由云+社区发表 作者:Super 导语:hashicorp/raft是raft算法的一种比较流行的golang实现,基于它能够比较方便的构建具有强一致性的分布式系统.本文通过实现一个简单的分布式缓 ...

  8. 什么是Consul

    什么是Consul Consul文档简要整理 什么是Consul? Consul是一个用来实现分布式系统的服务发现与配置的开源工具.他主要由多个组成部分: 服务发现:客户端通过Consul提供服务,类 ...

  9. consul(一)什么是consul

    1. consul的基本介绍 在分布式架构中,服务治理是一个重要的问题.在没有服务治理的分布式集群中,各个服务之间通过手工或者配置的方式进行服务关系管理,遇到服务关系变化或者增加服务的时候,人肉配置极 ...

随机推荐

  1. vue自定义指令长按事件

    Vue.directive('longpress', {     bind: function (el, binding, vNode) {         // Make sure expressi ...

  2. 20202405李昕亮《BASE64编码》

    BASE64编码 20202405李昕亮 参考网址: 1.https://baike.baidu.com/item/base64/8545775?fr=aladdin 2.https://blog.c ...

  3. SQL Server双机热备之发布、订阅实现实时同步

    一.复制的功能概述 SQL Server 复制功能实现了主从库的分离,从而将主库的压力分解掉,主库就主要负责数据的更改等,而主库主要负责查询ji.另外,有了主.从库,则从另一个方面,也了一层安全性,即 ...

  4. FloodFill算法详解及应用

    啥是 FloodFill 算法呢,最直接的一个应用就是「颜色填充」,就是 Windows 绘画本中那个小油漆桶的标志,可以把一块被圈起来的区域全部染色. 这种算法思想还在许多其他地方有应用.比如说扫雷 ...

  5. 写文档太麻烦,试试这款 IDEA 插件吧!

    前言 每次开发完新项目或者新接口功能等,第一件事就是提供接口文档.说到接口文档,当然是用 Markdown 了.各种复制粘贴字段,必填非必填,字段备注,请求返回示例等等.简直是浪费时间哇.所以想到了开 ...

  6. How to: Debug X++ Code Running in .NET Business Connector [AX 2012]

    This topic has not yet been rated - Rate this topic  http://msdn.microsoft.com/EN-US/library/bb19006 ...

  7. 系统解析Apache Hive

    Apache Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供一种HQL语言进行查询,具有扩展性好.延展性好.高容错等特点,多应用于离线数仓建设. 1. ...

  8. 14 RPC

    14 RPC RPC(Remote Procedure Call Protocol)--远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.RPC协议假定某些 ...

  9. 释放至强平台 AI 加速潜能 汇医慧影打造全周期 AI 医学影像解决方案

    基于英特尔架构实现软硬协同加速,显著提升新冠肺炎.乳腺癌等疾病的检测和筛查效率,并帮助医疗科研平台预防"维度灾难"问题 <PAGE 1 LEFT COLUMN: CUSTOM ...

  10. 使用pipenv管理python虚拟环境

    前言 近期的项目中,我开始尝试着从virtualenv管理python虚拟环境,切换到用pipenv来管理. 经过一段时间的使用,着实觉得pipenv使用的更加顺手,更加的便捷.这当然也延续了 Ken ...