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

有意思的网站推广计划表

有意思的网站,推广计划表,互联网小程序开发,广东移动手机营业厅网站Redis实现服务注册与服务发现源码阅读 背景 近期在看开源项目CloudWeGo中看到目前GoLang微服务框架Hertz中支持通过Redis实现服务注册与服务发现功能。便想着阅读下源码 源码阅读 gut clone了hertz-contrib后看到在一级目录下有目前各种主流的服务注册与发现的实现方案。为…

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中解析出服务地址

http://www.yayakq.cn/news/889427/

相关文章:

  • 品牌全案北京官网优化公司
  • 6免费网站建站应用中心下载
  • 网站建设项目报告总结统一管理网站系统
  • 官方网站平台下载在网上怎样卖货
  • 专门做特医食品的网站网站开发流程怎么写
  • 手机论坛网站模板网站建设与管理试卷_
  • 企业网站开发用什么语言写织梦网站模板陶瓷
  • 自己想建设一个网站自媒体时代做网站有前途吗
  • 中国建设银行陕西分行官方网站专业做网站套餐
  • 做网站需要自己上传产品吗全部列表支持安卓浏览器软件下载
  • 专门做优惠券的网站热搜榜排名前十
  • 建好网站后如何向里面加东西汕头建站费用
  • 2017年用什么语言做网站wordpress 作者简介
  • 建站至尊wordpress页面显示分类目录
  • 网站建设大连建模师培训机构有哪些
  • 南京网站设计外包怎么建设幸运28网站
  • 专做视频和ppt的网站visual composer for wordpress
  • 调研园区网站建设工作总结如何制作qq小程序
  • 制作网站 优帮云有没有网站可以学做床上用品
  • 织梦 网站地图 样式住建房官网查询
  • 做我的世界的mod的网站浙江省住房和城乡建设厅官网
  • 哈尔滨专业网站建设公司郑州网站制作工作室
  • 建设网站需要什么软件下载.net 网站源码下载
  • 天津低价网站建设河北网站建设
  • 南京网站制作系统支付宝小程序推广
  • 做网站设计用什么软件开发软件用什么编程软件
  • 设一个网站链接为安全怎么做亚马逊网站建设案例
  • app网站有哪些机械加工平台
  • 常宁市城乡和住房建设网站用手机怎么申请免费自助网站
  • 深圳地铁公司网站网页素材大宝库