微服务注册中心注册表与hashcode实现golang版

2023-05-16

背景

基于负载均衡的服务调用

基于负载均衡的服务相互调用指的是通过基于Lvs、Haproxy、Nginx等负载均衡软件来构建一个负载均衡服务,所有的服务调用都通过负载均衡器

从负载均衡的这种模式下其实有两个主要的问题: 一是中心化,整个系统都基于负载均衡器,负载均衡就相当于整个业务的中心,虽然我们可以通过一些高可用手段来保证,但其实内部流量通常是巨大的,很容易出现性能瓶颈 二是增加了一次TCP交互

当然也有很多好处,比如可以做一些负载均衡、长链接维护、分布式跟踪等,这不是本文重点

基于注册中心的服务调用

所有的服务都启动后都通过注册中心来注册自己,同时把注册中心里面的服务信息拉回本地,后续调用,就直接检查本地的服务和节点信息来进行服务节点的调用

注册中心中的注册表

每个服务节点都会来注册中心进行服务注册,那数据如何在服务端进行保存呢,其实就是注册表,其实等同于windows 里面的注册表,每个服务都来注册,把自己的信息上报上来,然后注册中心吧注册表,返回给client端,那服务之间就知道要调用服务的节点啦

注册中心事件队列

微服务注册注册中心通常会大量的服务注册, 那不能每次客户端来请求的时候,服务端都返回全量的数据,在数据传输的设计中,通常会有一种增量同步,其实在注册中心中也类似 注册中心通过将最近的服务变更事件保存在一个 事件队列中,后续每次客户端拉取只返回增量数据,这样服务端的忘了压力就会小很多

注册中心hashcode

增量数据有一个问题就是,如果客户端错过啦某些事件,比如事件队列满了,则客户端与注册中心的注册表就会不一致, 所以eureka里面引入了一个hashcode的概念,通过比对hashcode是否相同, 如果不同则客户端需要重新全量拉取

代码实现

系统架构

系统整体上分为两个端:客户端(Client)和注册中心(Server) Server: 提供服务注册和获取注册表的接口, 同时本地把保存服务和节点的对应信息,变更事件写入eventQueue Client: 调用server接口进行服务注册, 同时调用注册表拉取接口进行注册表拉取,保存懂啊LocalRegistry

应用与节点信息

Server端的服务注册表里面的服务和节点的信息,我通过Application和lease来维护 Application: 代表一个应用,里面会包含服务对应的节点信息 Lease: 维护一个节点的信息,比如心跳信息

服务端注册表

注册表结构体

服务端注册表结构体Registry主要包含三部分信息: lock(读写锁)、apps(应用对应信息)、eventQueue(事件队列) Lock: 注册中心是典型的读多写少的应用,server端注册表可能同时提供给N个服务进行读取,所以这里采用读写锁 apps: 保存应用对应的信息, 其实后面写完发现,没必要使用,只使用基础的map就可以搞定 eventQueue: 每次注册表变更都写入事件到里面

// Registry 注册表
type Registry struct {
	lock       sync.RWMutex
	apps       sync.Map
	duration   time.Duration
	eventQueue *EventQueue
}
复制代码

注册表服务注册

注册流程主要分为下面几部分:

  1. 从注册表获取对应的应用Application
  2. 调用Application的add接口添加节点
  3. 为节点创建一个Lease
  4. 保存节点信息到Application.Node里
  5. 将事件写入到eventQueue
// Registr 注册服务
func (r *Registry) Registr(name, node string) bool {
	r.lock.Lock()
	defer r.lock.Unlock()
	app := r.getApp(name)
	if app == nil {
		app = NewApplication(name)
		r.apps.Store(name, app)
	}

	if lease, ok := app.add(node, r.duration); ok {
		r.eventQueue.Push(&Event{lease: lease, action: ADD})
		return true
	}
	return false
}
复制代码

注册表拉取

全量拉取通过all接口拉取全量的返回的是服务对应的节点切片 增量拉取通过details接口返回增量的变更事件和服务端注册表的hashcode

// all 全量拉取
func (r *Registry) all() map[string][]string {
	r.lock.RLock()
	defer r.lock.RUnlock()
	apps := make(map[string][]string)
	r.apps.Range(func(k, v interface{}) bool {
		name, app := k.(string), v.(*Application)
		nodes := []string{}
		for key := range app.Node {
			nodes = append(nodes, key)
		}
		apps[name] = nodes
		return true
	})
	return apps
}
// details 增量拉取
func (r *Registry) details() []*Event {
	r.lock.RLock()
	defer r.lock.RUnlock()
	events := []*Event{}
	for {
		event := r.eventQueue.Pop()
		if event == nil {
			break
		}
		events = append(events, event)
	}
	return events
}
复制代码

hashcode

hashcode是一个一致性的保证,eureka里面主要是通过拼接所有的服务名称和节点的个数来生成的一个字符串,这里我们也采用这种方式,

func (r *Registry) hashCode() string {
	r.lock.RLock()
	defer r.lock.RUnlock()
	hashCodes := []string{}
	r.apps.Range(func(_, value interface{}) bool {
		app := value.(*Application)
		hashCodes = append(hashCodes, app.HashCode())
		return true
	})
	sort.Sort(sort.StringSlice(hashCodes))
	return strings.Join(hashCodes, "|")
}
复制代码

客户端注册表

数据结构

客户端本地注册表其实就比较简单了,只需要存储服务和节点的对应信息即可

// LocalRegistry 本地注册表
type LocalRegistry struct {
	lock sync.RWMutex
	apps map[string][]string
}
复制代码

客户端逻辑架构

  • 启动流程: 启动时客户端首先调用注册接口进行自我注册,然后调用poll拉取全量注册表
func (c *Client) start() {
	c.wg.Add(1)
	c.registr()
	c.poll()
	go c.loop()
}
复制代码
  • 主循环
func (c *Client) loop() {
	timer := time.NewTimer(time.Second)
	for {
            // 从服务的拉取增量事件,details内部会直接应用,然后返回服务端返回的注册表的hashcode
		respHashCode := c.details()
		localHashCode := c.registry.hashCode()

            // 如果发现本地和服务的的注册表的hashcode不同,则全量拉取
		if respHashCode != localHashCode {
			fmt.Printf("client app %s node %s poll hashcode: %s\n", c.App, c.Name, respHashCode)
			c.poll()
		}
		select {
		case <-timer.C:
			timer.Reset(time.Second)
		case <-c.done:
			c.wg.Done()
			return
		}
	}
}
复制代码

验证逻辑

func main() {
        // 生成服务端
	server := NewServer("aliyun", time.Second)

        // 注册两个test服务的节点
	clientOne := NewClient("test", "1.1.1.1:9090", server)
	clientOne.start()
	clientTwo := NewClient("test", "1.1.1.2:9090", server)
	clientTwo.start()

    // 注册两个hello服务的节点
	clientThree := NewClient("hello", "1.1.1.3:9090", server)
	clientThree.start()
	clientFour := NewClient("hello", "1.1.1.4:9090", server)
	clientFour.start()

	time.Sleep(time.Second * 3)
        // 验证每个服务节点的注册表的hashcode是否一致
	println(clientOne.details())
	println(clientTwo.details())
	println(clientThree.details())
	println(clientFour.details())
	println(clientTwo.details() == clientOne.details())
	println(clientThree.details() == clientFour.details())
	println(clientOne.details() == clientFour.details())

	clientOne.stop()
	clientTwo.stop()
	clientThree.stop()
	clientFour.stop()
}
复制代码

通过结果我们可以看出,节点增量拉取了注册表,同时如果发现与本地的hashcode不同就进行全量拉取,并最终达成一致

lr event add 1.1.1.3:9090 hello
lr event add 1.1.1.4:9090 hello
lr event add client app hello node 1.1.1.4:9090 poll hashcode: hello_2|test_2
1.1.1.1:9090 test
lr event add 1.1.1.2:9090 test
client app test node 1.1.1.1:9090 poll hashcode: hello_2|test_2
client app test node 1.1.1.2:9090 poll hashcode: hello_2|test_2
client app hello node 1.1.1.3:9090 poll hashcode: hello_2|test_2
hello_2|test_2
hello_2|test_2
hello_2|test_2
hello_2|test_2
true
true
true
复制代码

总结

微服务注册中心注册表的这种实现机制,到这基本上就明白了,注册中心 通过增量、全量、hashcode三种机制来保证客户端与注册中心的注册表的同步

其实一个工业级的注册中心还是很麻烦的,比如注册表中那个事件队列,我现在的实现只有一个节点能获取增量,其他的都会通过hashcode来触发全量拉取,后续文章里面会相信介绍下,这块缓存和定时器来实现增量数据的打包

其实在go里面大家注册中心都是基于etcd、consul直接watch去做的,基本上可以完成eureka服务的8/9十的功能,但是当需要与公司现有的java做集成,可能就需要eureaka这种注册中心了

未完待续 关注公共号: 布衣码农

更多精彩内容可以查看www.sreguide.com

转载于:https://juejin.im/post/5ce608cee51d45772a49ac8f

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

微服务注册中心注册表与hashcode实现golang版 的相关文章

随机推荐

  • ftp网络文件传输服务器,FTP文件传输服务

    FTP 文件传输协议 典型的 xff23 xff0f xff33 架构的应用层协议 xff0c 需要由服务端软件 xff0c 客户端软件两部分共同实现传输功能 需求描述 xff1b 采用FTP虚拟用户的方式 xff0c 添加三个用户 xff
  • 服务器主板上一个处理器性能如何,怎么判断一个CPU才算好

    我们作为一个电脑用户新手来说 xff0c 购买一个好的电脑配置能让我们体验更好的电脑 作为一个电脑核心CPU来说是非常重要的 xff0c 不过大家是新手却不知道如何判断一个CPU的好坏 所以学习啦小编这里就给大家上一堂关于如何选择一个好的C
  • linux添加fuji打印机,Ubuntu16.04下添加打印机FujiXerox CP116w

    今天要打印一份北马的成绩单 不想重启机器了 在Ubuntu下尝试添加打印机 最后成功了 记录一下 打印机型号是FujiXerox CP116w 通过WIFI连接的 在Ubuntu16 04下添加打印机 Add Printer gt Devi
  • 【非常详细】Ubuntu18.04安装显卡驱动和CUDA,CUDNN流程和踩坑记录

    1 预准备 在一个刚安装好的 全新的ubuntu18 04上 xff0c 按下Ctrl 43 Al 43 T打开终端 xff0c 依次输入以下三条指令 xff0c 安装好gcc和cmake gcc是GNU compiler collecti
  • java w3 xml格式化_xml代码在线格式化美化工具 - 代码工具 - W3Cschool

    XML 代码美化 xff1a 在上面的编辑器中输入你手中混乱 压缩或混淆的XML代码 xff0c 点击 格式化 代码 即可实现代码的格式化与美化功能 该编辑器还具备行数显示及语法高亮显示的功能 此外还提供了大量的附加选项来实现个性化的代码美
  • 计算机主板手工,教你DIY一台笔记本(伪),简单粗暴成本低

    教你DIY一台笔记本 伪 xff0c 简单粗暴成本低 2018 11 03 17 25 00 277点赞 561收藏 194评论 这是某个垃圾佬长久以来的执念 xff0c 灵感来自于一个不堪龟速笔记本折磨的夜晚 因为某些原因 xff0c 个
  • Openstack 安装部署指南翻译系列 之 Manila服务安装(Share Storage)

    上面左边是我的个人微信 xff0c 如需进一步沟通 xff0c 请加微信 右边是我的公众号 Openstack私有云 xff0c 如有兴趣 xff0c 请关注 1 1 1 1 Manila服务 安装 xff08 Share Storage
  • Clang与libc++abi库安装

    系统ubuntu64位 Clang4 0 参考 xff1a 1 https github com yangyangwithgnu use vim as ide 0 1 其中 第7章 工具链集成 2 http clang llvm org g
  • pandas处理较大数据量级的方法 - chunk,hdf,pkl

    前情提要 工作原因需要处理一批约30G左右的CSV数据 xff0c 数据量级不需要hadoop的使用 xff0c 同时由于办公的本本内存较低的缘故 xff0c 需要解读取数据时内存不足的原因 操作流程 xff1a 方法与方式 首先是读取数据
  • Objective-C --- UIView (基础运用)

    为什么80 的码农都做不了架构师 xff1f gt gt gt 1 创建 UIView view 61 UIView alloc init 2 位置 frame view frame 相对父视图的位置 view bounds 相对自己的位置
  • Debian8.2 xfce桌面设置双屏

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 最近想折腾 Xfce xff0c 而且 Fedora 的包都好老啊 xff0c 所以换了 Arch Linux 果然装完就有的折腾了 xff1a Xfce 4 0 居然不支
  • 【转】好东西!sqlite3中BLOB数据类型存储大对象运用示例

    1 常用接口 个人比较喜欢sqlite 使用最方便 xff0c 唯一的准备工作是下载250K的源 xff1b 而且作者很热心 xff0c 有问必答 以下演示一下使用sqlite的步骤 xff0c 先创建一个数据库 xff0c 然后查询其中的
  • Qt实现MFC的WM_IDLE机制.doc

    win32 有一个消息是 WM IDLE 而在MFC里面的也有 一个virtualBOOLOnIdle LONGlCount 的函数与之相对应 xff0c 而在MSDN中对该函数的解释是 xff1a Override this member
  • ERROR: Kernel configuration is invalid.

    最简单的linux hello的驱动源程序 span class hljs comment 下面是驱动源代码 span span class hljs preprocessor include lt linux init h gt span
  • i2c中start和restart的区别

    有的硬件芯片提供了一个个寄存器 xff0c 供我们很好的操作i2c xff0c 但是 xff0c 在用的时候 xff0c 我们是不知道他到地是怎么操作的 xff0c 下边 xff0c 我就探讨下i2c中的start和restart的区别 s
  • Linux——常见的N个问题

    一 如何建立多用户 提醒大家一句 xff0c 别一直使用root用户 xff0c 因为root用户在系统中有着至高无上的权力 xff0c 一不小心 就可能破坏系统 比如我们想删除 temp目录下的文件却将命令不小心输成 rm temp xf
  • animate css组合,Vue---CSS动画之animate.css库

    animation完成一个动画效果 代码基本结构搭建 使用与过渡动画相同的代码结构 hello world change var vm 61 new Vue el 39 root 39 data show true methods hand
  • P1661 扩散

    P1661 扩散 题目描述 一个点每过一个单位时间就会向四个方向扩散一个距离 xff0c 如图 两个点a b连通 xff0c 记作e a b 当且仅当a b的扩散区域有公共部分 连通块的定义是块内的任意两个点u v都必定存在路径e u a0
  • java 返回两个list_Java 获取两个List的交集和差集,以及应用场景操作

    背景介绍 在实际项目中 xff0c 特别是一些管理后台类的项目 xff0c 会遇到底层数据是按照一对多关系的数据表存储的管理界面 列表页是一对多关系中一对应的数据列表 xff0c 二级的详情页中是一对多关系中多对应的多条数据展示 通常二级页
  • 微服务注册中心注册表与hashcode实现golang版

    背景 基于负载均衡的服务调用 基于负载均衡的服务相互调用指的是通过基于Lvs Haproxy Nginx等负载均衡软件来构建一个负载均衡服务 xff0c 所有的服务调用都通过负载均衡器 从负载均衡的这种模式下其实有两个主要的问题 xff1a