当前位置: 首页 > news >正文

Redis实现服务注册与服务发现源码阅读(Go语言)

Redis实现服务注册与服务发现源码阅读

背景

近期在看开源项目CloudWeGo中看到目前GoLang微服务框架Hertz中支持通过Redis实现服务注册与服务发现功能。便想着阅读下源码

源码阅读

gut clone了hertz-contrib后看到在一级目录下有目前各种主流的服务注册与发现的实现方案。为了便于学习选择阅读redis
在这里插入图片描述

服务注册源码分析

看到redis/example/server/main.go中有服务注册的实现示例

func main() {r := redis.NewRedisRegistry("127.0.0.1:6379")addr := "127.0.0.1:8888"h := server.Default(server.WithHostPorts(addr),server.WithRegistry(r, &registry.Info{ServiceName: "hertz.test.demo",Addr:        utils.NewNetAddr("tcp", addr),Weight:      10,Tags:        nil,}),)h.GET("/ping", func(_ context.Context, ctx *app.RequestContext) {ctx.JSON(consts.StatusOK, utils.H{"ping": "pong"})})h.Spin()
}

代码主要逻辑是实现一个简单的webservice,其中用到了服务注册机制。可以看到,在hertz中服务注册可以通过配置engine的形式在webservice初始化时定义,其中

r := redis.NewRedisRegistry("127.0.0.1:6379")

定义了一个服务注册的地址,即要把这个微服务注册到哪个主机中。而server.WithRegistry()使得服务初始化时引入了这个服务注册。Info即是服务注册的相关信息
进入redis/registry.go查看服务注册的定义,可以看到redis服务注册是实现的registry.Registry接口

var _ registry.Registry = (*redisRegistry)(nil)type redisRegistry struct {client *redis.Clientrctx   *registryContextmu     sync.Mutexwg     sync.WaitGroup
}type registryContext struct {ctx    context.Contextcancel context.CancelFunc
}// Registry is extension interface of service registry.
type Registry interface {Register(info *Info) errorDeregister(info *Info) error
}// Info is used for registry.
// The fields are just suggested, which is used depends on design.
type Info struct {// ServiceName will be set in hertz by defaultServiceName string// Addr will be set in hertz by defaultAddr net.Addr// Weight will be set in hertz by defaultWeight int// extend other infos with Tags.Tags map[string]string
}

registry.Registry通过Register(info *Info)和Deregister(info *Info)描述服务注册与服务发现
接下来看如何创建一个redis服务注册

// NewRedisRegistry creates a redis registry
func NewRedisRegistry(addr string, opts ...Option) registry.Registry {redisOpts := &redis.Options{Addr:     addr,Password: "",DB:       0,}for _, opt := range opts {opt(redisOpts)}rdb := redis.NewClient(redisOpts)return &redisRegistry{client: rdb,}
}

我们已经可以猜到了,配置redis客户端连接User Server的redis,用redis来存储服务映射关系,实现服务注册中心,那么是不是这样呢,我们接着往下看服务注册的实现源码

func (r *redisRegistry) Register(info *registry.Info) error {// 校验配置信息if err := validateRegistryInfo(info); err != nil {return err}rctx := registryContext{}rctx.ctx, rctx.cancel = context.WithCancel(context.Background())m := newMentor()r.wg.Add(1)// 并发监控redisgo m.subscribe(rctx.ctx, info, r)r.wg.Wait()rdb := r.client// 将注册信息hash化hash, err := prepareRegistryHash(info)if err != nil {return err}// 上锁r.mu.Lock()r.rctx = &rctx// 注册信息写入到redis,即我们的服务注册中心rdb.HSet(rctx.ctx, hash.key, hash.field, hash.value)rdb.Expire(rctx.ctx, hash.key, defaultExpireTime)// 生成服务相关信息和发送rdb.Publish(rctx.ctx, hash.key, generateMsg(register, info.ServiceName, info.Addr.String()))// 写完,解锁r.mu.Unlock()go m.monitorTTL(rctx.ctx, hash, info, r)// 保持长连接go keepAlive(rctx.ctx, hash, r)return nil
}

Register方法已经对服务注册的主要流程进行了描述,下面来看一些细节

func validateRegistryInfo(info *registry.Info) error {if info == nil {return fmt.Errorf("registry.Info can not be empty")}if info.ServiceName == "" {return fmt.Errorf("registry.Info ServiceName can not be empty")}if info.Addr == nil {return fmt.Errorf("registry.Info Addr can not be empty")}return nil
}

校验服务注册时并不会对客户端是否连接上进行校验,只会校验参数和结构体是否为空

func prepareRegistryHash(info *registry.Info) (*registryHash, error) {meta, err := json.Marshal(convertInfo(info))if err != nil {return nil, err}return &registryHash{key:   generateKey(info.ServiceName, server),field: info.Addr.String(),value: string(meta),}, nil
}

服务注册信息hash即生成key-velue,方便写入到redis中

func keepAlive(ctx context.Context, hash *registryHash, r *redisRegistry) {ticker := time.NewTicker(defaultTickerTime)defer ticker.Stop()for {select {case <-ticker.C:r.client.Expire(ctx, hash.key, defaultKeepAliveTime)case <-ctx.Done():break}}
}

最后再起一个协程在生命期内监听保持长连接,这里用到的是多路复用

func keepAlive(ctx context.Context, hash *registryHash, r *redisRegistry) {ticker := time.NewTicker(defaultTickerTime)defer ticker.Stop()for {select {case <-ticker.C:r.client.Expire(ctx, hash.key, defaultKeepAliveTime)case <-ctx.Done():break}}
}

再来看服务注册退出:

func (r *redisRegistry) Deregister(info *registry.Info) error {if err := validateRegistryInfo(info); err != nil {return err}rctx := r.rctxrdb := r.clienthash, err := prepareRegistryHash(info)if err != nil {return err}r.mu.Lock()// 删除redis中的注册信息rdb.HDel(rctx.ctx, hash.key, hash.field)rdb.Publish(rctx.ctx, hash.key, generateMsg(deregister, info.ServiceName, info.Addr.String()))rctx.cancel()r.mu.Unlock()return nil
}

整体逻辑和服务注册相似,只是最后把注册信息删掉

服务发现源码分析

看到redis/example/client/main.go中有服务注册的实现示例

func main() {cli, err := client.NewClient()if err != nil {panic(err)}r := redis.NewRedisResolver("127.0.0.1:6379")cli.Use(sd.Discovery(r))for i := 0; i < 10; i++ {status, body, err := cli.Get(context.Background(), nil, "http://hertz.test.demo/ping", config.WithSD(true))if err != nil {hlog.Fatal(err)}hlog.Infof("HERTZ: code=%d,body=%s", status, string(body))}
}

config.WithSD(true)即通过中间件形式,使得客户端发送请求时,并非直接请求服务器,而是请求注册中心,通过服务发现再进一步转到服务器上
接前文中在redis里进行了服务注册,这里客户端想要进行服务发现找到自己请求的微服务。这里服务发现还是通过复用接口实现的

var _ discovery.Resolver = (*redisResolver)(nil)type redisResolver struct {client *redis.Client
}// NewRedisResolver creates a redis resolver
func NewRedisResolver(addr string, opts ...Option) discovery.Resolver {redisOpts := &redis.Options{Addr: addr}for _, opt := range opts {opt(redisOpts)}rdb := redis.NewClient(redisOpts)return &redisResolver{client: rdb,}
}

服务发现开始和服务注册一样,需要先连接上redis

func (r *redisResolver) Target(_ context.Context, target *discovery.TargetInfo) string {return target.Host
}func (r *redisResolver) Resolve(ctx context.Context, desc string) (discovery.Result, error) {rdb := r.client// 查询服务列表fvs := rdb.HGetAll(ctx, generateKey(desc, server)).Val()var its []discovery.Instancefor f, v := range fvs {// 反序列化获取服务信息var ri registryInfoerr := json.Unmarshal([]byte(v), &ri)if err != nil {hlog.Warnf("HERTZ: fail to unmarshal with err: %v, ignore instance Addr: %v", err, f)continue}// 负载均衡参数weight := ri.Weightif weight <= 0 {weight = defaultWeight}its = append(its, discovery.NewInstance(tcp, ri.Addr, weight, ri.Tags))}return discovery.Result{// 服务发现的结果CacheKey:  desc,//redis表中的keyInstances: its,//服务表}, nil
}func (r *redisResolver) Name() string {return Redis
}

Target、Name、Resolve即为实现自方法的接口,其中target和Name分别解出redis的地址和Name,Resolve方法用来在Redis中发现服务
我们还可以细扣一下,服务发现中间件进一步是怎么实现的?
/pkg/mod/github.com/cloudwego/hertz@v0.6.0/pkg/common/config/request_option.go:58中WithSD如下:

// WithSD set isSD in RequestOptions.
func WithSD(b bool) RequestOption {return RequestOption{F: func(o *RequestOptions) {o.isSD = b}}
}

可见这里是用来高速请求,这个请求是有服务发现机制的。循着client.Get()方法一路往下找,这项配置写入到了req中:

func GetURL(ctx context.Context, dst []byte, url string, c Doer, requestOptions ...config.RequestOption) (statusCode int, body []byte, err error) {req := protocol.AcquireRequest()req.SetOptions(requestOptions...)statusCode, body, err = doRequestFollowRedirectsBuffer(ctx, req, dst, url, c)protocol.ReleaseRequest(req)return statusCode, body, err
}

在hertz中的Request定义中其实是包含有config定义,里面就有sd的flag

type Request struct {noCopy nocopy.NoCopy //lint:ignore U1000 until noCopy is usedHeader RequestHeaderuri      URIpostArgs ArgsbodyStream      io.Readerw               requestBodyWriterbody            *bytebufferpool.ByteBufferbodyRaw         []bytemaxKeepBodySize intmultipartForm         *multipart.FormmultipartFormBoundary string// Group bool members in order to reduce Request object size.parsedURI      boolparsedPostArgs boolisTLS boolmultipartFiles  []*FilemultipartFields []*MultipartField// Request level options, service discovery options etc.options *config.RequestOptions
}

也就是会从req中解析出服务地址

相关文章:

Redis实现服务注册与服务发现源码阅读(Go语言)

Redis实现服务注册与服务发现源码阅读 背景 近期在看开源项目CloudWeGo中看到目前GoLang微服务框架Hertz中支持通过Redis实现服务注册与服务发现功能。便想着阅读下源码 源码阅读 gut clone了hertz-contrib后看到在一级目录下有目前各种主流的服务注册与发现的实现方案。为…...

论文复现-3

模型构建中的运算 数据集是CONLL03 这个数据集共有4种实体类型&#xff0c;所以&#xff0c;在做实体描述的embedding时&#xff0c;得到的语义表示的Tensor大小为 &#xff1a; 4*max_len, 具体指的是&#xff1a; type_input_ids: torch.LongTensor None, type_attention_m…...

667知识点 | 经过三年实战检验的667知识清单

文章目录 前言第一章 信息与信息资源第二章 信息社会第三章 信息交流第四章 信息技术第五章 信息组织第六章 信息管理活动第七章 信息资源人文管理第八章 信息资源经济管理第九章 信息资源系统管理第十章 信息资源管理专门化前言 参考书目:《信息管理导论(第三版)》党跃武推…...

后端快速上手前端三剑客 HtmlCSSJavaScript

文章目录前言HTML1.基础标签2.多媒体标签&#xff1a;3.表格&列表&布局4.表单CSS1.简介2.导入方式3.选择器JavaScript1.简介2.引入方式3.基本语法4.对象(1) 基本对象(2) BOM对象(3) DOM对象5.事件前言 结构&#xff1a;HTML 表现&#xff1a;CSS 行为&#xff1a;Java…...

Cdiscount、Allegro如何利用测评补单自养号提升店铺权重和流量

Allegro成立于 1999 年是在波兰最受欢迎的电商平台&#xff0c;75%的波兰人都知道该网站&#xff0c;Allegro的品牌认知度在波兰高达98%。Allegro平台卖家的数量目前还是比较少的约为13万&#xff0c;最重要的就是中国卖家占比少&#xff0c;所以竞争也比较低&#xff0c;像是美…...

第16天-性能压测:压力测试,性能监控,优化QPS,Nginx动静分离

1.性能监控 1.1.JVM架构 运行时数据区&#xff1a; 方法区&#xff1a;最重要的内存区域&#xff0c;多线程共享&#xff0c;保存了类的信息&#xff08;名称、成员、接口、父类&#xff09;&#xff0c;反射机制是重要的组成部分&#xff0c;动态进行类操作的实现&#xff1b;…...

【python 基础篇 十一】python的函数-------函数的偏函数 高阶函数 返回函数 匿名函数 闭包

目录1.偏函数2.高阶函数3.返回函数4.匿名函数5.闭包1.偏函数 概念 ​ 当我们写一个参数比较多的函数时&#xff0c;如果有些参数&#xff0c;大部分场景下都是某一个固定值&#xff0c;那么为了简化使用&#xff0c;就可以创建一个新函数&#xff0c;指定我们要使用的函数的某个…...

妇女节到了,祝福所有女神 Happy Women‘s Day!

在每年&#xff13;月&#xff18;日人们庆祝妇女节 &#xff37;omens Day is cllebrated on March 8 every year.国际妇女节(IWD)&#xff0c;中国内地称“三八”国际劳动妇女节或国际劳动妇女节。是在每年的3月8日为庆祝妇女在经济、政治和社会等领域作出的重要贡献和取得的…...

etcd集群通过 Leader 写入数据,为什么K8s HA集群中讲每个 kube-apiserver 只和本机的 ETCD 通信

写在前面 对这个我不太明白&#xff0c;所有在 stackOverflow 的请教了大佬这里分享给小伙伴理解不足小伙伴帮忙指正 对每个人而言&#xff0c;真正的职责只有一个&#xff1a;找到自我。然后在心中坚守其一生&#xff0c;全心全意&#xff0c;永不停息。所有其它的路都是不完整…...

HTML 表单

HTML 表单和输入 HTML 表单用于收集不同类型的用户输入。 在线实例 创建文本字段 (Text field) 本例演示如何在 HTML 页面创建文本域。用户可以在文本域中写入文本。 创建密码字段 本例演示如何创建 HTML 的密码域。 &#xff08;在本页底端可以找到更多实例。&#xff09; …...

HTML、CSS学习笔记5(移动端基础知识、Flex布局)

一、移动端基础知识 1.PC端和移动端区别 移动端&#xff1a;手机版网页&#xff0c;手机屏幕小&#xff0c;网页宽度多数为100%&#xff0c;没有版心 PC端&#xff1a;电脑版网页&#xff0c;屏幕大&#xff0c;网页固定版心 PC端和移动端不是同一个网页 2.如何在电脑里面…...

【Java学习笔记】2.Java 开发环境配置

Java 开发环境配置 在本章节中我们将为大家介绍如何搭建Java开发环境。 window系统安装java 下载JDK 首先我们需要下载 java 开发工具包 JDK&#xff0c;下载地址&#xff1a;https://www.oracle.com/java/technologies/downloads/&#xff0c;在下载页面中根据自己的系统选…...

MyBatis——进阶操作(2)

标签 if标签 当提交的表单中有些为非必填项&#xff0c;用户并没有上传这些属性的值&#xff0c;那么程序可以上传NUll&#xff0c;也可以用if标签判断用户有没有上传这个值 <if test"参数!null">操作 </if>其中test中填写一条语句&#xff0c;如果得…...

循环结构

循环结构循环结构一、课前问答二、while循环三、do-while循环四、for循环五、流程控制5.1 break5.2 continue循环结构 一、课前问答 1、switch支持的数据类型。 2、switch中break的作用。 3、多重if如果多个条件都成立&#xff0c;执行方式。 二、while循环 语法&#xff1a; …...

漫谈数据库表设计及索引设计

一.数据库表设计 在数据库表设计上有个很重要的设计准则&#xff0c;称为范式设计。 什么是范式设计&#xff1f; 范式来自英文Normal Form&#xff0c;简称NF。MySQL是关系型数据库&#xff0c;但是要想设计—个好的关系&#xff0c;必须使关系满足一定的约束条件&#xff0c…...

【JavaWeb】CSS基础知识:引入方式 + 选择器

CSS引入 CSS的引入有三种&#xff0c;三种的优缺点各不相同。 行内样式表 <!-- 行内样式表 --><!-- 相当于标签的一个属性 --><!-- 只对当前标签生效 --><!-- 优先级较高&#xff0c;会覆盖其他样式 --><p style"color: blue;">这是…...

02-前端-javaScript

文章目录JavaScript1&#xff0c;JavaScript简介2&#xff0c;JavaScript引入方式2.1 内部脚本2.2 外部脚本3&#xff0c;JavaScript基础语法3.1 书写语法3.2 输出语句3.3 变量3.3.1 全局变量var3.3.2 局部变量let3.3.3 常量const3.4 数据类型3.5 运算符3.5.1 \和区别 ▲3.5.2 …...

对链表学习的总结一

一,单链表结构定义 C/C++ 数组:一组具有相同类型数据的集合。结构体:不同类型数据的集合。 // Definition for singly-linked list. struct ListNode {int val;ListNode *next;ListNode(int x) : val(x), next...

toSring()还有个高级用法好用

Object.prototype.toString()能够很好的判断数据的类型及内置对象 typeof xxx:能判断出number,string,undefined,boolean,object,function(null是object)Object.prototype.toString.call(xxx):能判断出大部分类型Array.isArray(xxx):判断是否为数组var test= Object.…...

Linux--多线程(3)

目录1. POSIX信号量1.1 概念2. 基于环形队列的生产消费者模型2.1 环形队列的基本原理2.2 基本实现思想3. 多生产多消费1. POSIX信号量 1.1 概念 信号量本质是一个计数器&#xff0c;申请了信号量以后&#xff0c;可以达到预定临界资源的效果。 POSIX信号量和SystemV信号量相同…...

【spring】事务

概述 1、什么事务 事务是数据库操作最基本单元&#xff0c;逻辑上一组操作&#xff0c;要么都成功&#xff0c;如果有一个失败所有操 作都失败 2、事务四个特性&#xff08;ACID&#xff09; &#xff08;1&#xff09;原子性 &#xff08;2&#xff09;一致性 &#xff08;3…...

博通仍然是美股市场最好的芯片半导体股

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 博通(AVGO)是一家快速增长的半导体公司&#xff0c;并且有很高的股息分红&#xff0c;目前其股息收益率已经高出了平均水平3.2%&#xff0c;而且估值非常合理&#xff0c;仅为预期净利润的14倍。 虽然博通也受到了经济衰退影…...

java开发手册之异常日志

文章目录异常日志异常处理日志规约异常日志 异常处理 1.Java 类库中定义的一类 RuntimeException可以通过预先检查进行规避&#xff0c;而不应该通过 catch 来处理 比如&#xff1a;IndexOutOfBoundsException&#xff0c;NullPointerException 等等。 说明&#xff1a;无法通…...

P6专题:关于P6 EPPM和PPM的区别及选型

目录 引言 什么是 Primavera P6 项目管理&#xff1f; Primavera P6 PPM 关键点 Primavera P6 PPM 是独立工具还是企业工具&#xff1f; 什么是 Primavera P6 企业项目组合管理&#xff1f; 那么EPPM的windows-tool呢&#xff1f; P6 EPPM 适合谁&#xff1f; 更多对比…...

亿万级海量数据去重软方法

文章目录原理案例一需求&#xff1a;方法案例二需求&#xff1a;方法&#xff1a;参考原理 在大数据分布式计算框架生态下&#xff0c;提升计算效率的方法是尽可能的把计算分布式话、并行化&#xff0c;避免单节点计算过载&#xff0c;把计算分摊到各个节点。这样解释小白能够…...

记录--手摸手带你撸一个拖拽效果

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 前言 最近看见一个拖拽效果的视频(抖音&#xff1a;艾恩小灰灰)&#xff0c;看好多人评论说跟着敲也没效果&#xff0c;还有就是作者也不回复大家提出的一些疑问&#xff0c;本着知其然必要知其所以然…...

python高德地图+58租房网站平台源码

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;python地图 免费获取完整源码源文件说明文档配置教程等 在PyCharm中运行《高德地图58租房》即可进入如图1所示的高德地图网页。 具体的操作步骤如下&#xff1a; &#xff08;1&#xff09;打开地图网页后&#xff0c;在编…...

ubuntu 将jupyter-lab保存为桌面快捷方式和favourites

ubuntu: 将jupyter-lab保存为桌面快捷方式和favourites desktop shortcut 建立一个新的desktop文件 cd ~/Desktop touch Jupyter-lab.desktop将文件修改成如下&#xff1a; [Desktop Entry] Version1.0 NameJupyterlab CommentBack up your data with one click Exec/home/cjb/…...

Java 类和对象简介

类是对象的抽象&#xff0c;是一组具有相同特性&#xff08;属性&#xff0c;事物的状态信息&#xff09;和行为&#xff08;事物能做什么&#xff09;的事物的集合&#xff0c;可以看做一类事物的模板。 对象是类的实例化&#xff0c;是具体的事物。 比如&#xff1a;人类和…...

时间复杂度的计算

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【数据结构初阶&#xff08;C实现&#xff09;】 文章目录123456789时间复杂度&#xff08;就是一个函数&#xff09;的计算&#xff0c;…...

wordpress插件 数据列表/百度文库官网登录入口

使用目录查看更佳&#xff01; Redis学习 Redis Redis中文网教程&#xff1a;https://www.redis.net.cn/tutorial/3501.html 概念 redis是一款高性能的NOSQL系列的非关系型数据库 什么是NOSQL&#xff1f; NoSQL(NoSQL Not Only SQL)&#xff0c;意即“不仅仅是SQL”&a…...

建立一个自己的网站/关键词排名优化报价

一面 1 自我介绍和项目 2 Java的内存分区 3 Java对象的回收方式&#xff0c;回收算法。 4 CMS和G1了解么&#xff0c;CMS解决什么问题&#xff0c;说一下回收的过程。 5 CMS回收停顿了几次&#xff0c;为什么要停顿两次。 6 Java栈什么时候会发生内存溢出&#xff0c;Jav…...

集团网站方案策划书/百度免费咨询

Vue2项目的创建和启动 创建项目 这个方式安装的是vue2的 cd 项目所在文件夹 ZHR:VueProjects zc$ vue init webpack learn? Project name learn ? Project description 学习 ? Author zhangyin ? Vue build standalone ? Install vue-router? Yes ? Use ESLint to li…...

dw中怎样做网站二级页面/什么是seo优化?

RHEL7 或CentOS7配置共享存储--使用iSCSI 环境&#xff1a; RHEL 7.3 1.LVM创建 2.服务端配置 3.客户端配置 1.LVM创建 在RHEL7.3中&#xff0c;对于lvm操作部分与之前RHEL6版本没有区别,还是创建pv&#xff0c;vg&#xff0c;lv的方法&#xff0c;命令参考如…...

建个可以注册会员网站多少钱/b2b平台有哪些网站

一、光盘重装系统步骤1、启动计算机&#xff0c;屏幕上出现 Press Del to Enter BIOS Setup&#xff0c;按住“DEL”。2、出现BIOS设置窗口&#xff1a;点击“Advance BIOS Features”&#xff0c;点击“enter”。3、点击“First Boot Derive”&#xff0c;点击“Page Up”或“…...

搜索别人的网站是带logo的请问怎么做的/南京响应式网站建设

有 时当 使用 一个 包含 多 个 参 数 的 方 法 时 &#xff0c; 由 于参 数 过 多 会 导 致 可 读 性 严 重 下 降 &#xff0c; 如 &#xff1a; 有 时当 使用 一个 包含 多 个 参 数 的 方 法 时 &#xff0c; 由 于参 数 过 多 会 导 致 可 读 性 严 重 下 降 &#xff0c; …...