微服务之服务注册和服务发现篇

Redis实现服务注册与服务发现源码阅读(Go语言) 服务注册与服务发现redis方案源码解读 阅读详情

有了服务注册和发现机制,消费者不需要知道具体服务提供者的真实物理地址就可以进行调用,也无须知道具体有多少个服务者可用;而服务提供者只需要注册到注册中心,就可以对外提供服务,在对外服务时不需要知道具体是哪些服务调用了自己。

RPC 配置

Etcd:
  Hosts:
  - 127.0.0.1:2379
  Key: user.rpc

这里分析go-zero 的etcd 部分源码, 源码引用https://github.com/zeromicro/go-zero-demo/tree/master/mall

被调方-服务注册

  • mall/user/rpc/user.go 源码如下
package main

import (
    "flag"
    "fmt"

    "go-zero-demo-rpc/mall/user/rpc/internal/config"
    "go-zero-demo-rpc/mall/user/rpc/internal/server"
    "go-zero-demo-rpc/mall/user/rpc/internal/svc"
    "go-zero-demo-rpc/mall/user/rpc/types/user"

    "github.com/zeromicro/go-zero/core/conf"
    "github.com/zeromicro/go-zero/core/service"
    "github.com/zeromicro/go-zero/zrpc"
    "google.golang.org/grpc"
    "google.golang.org/grpc/reflection"
)

var configFile = flag.String("f", "etc/user.yaml", "the config file")

func main() {
    flag.Parse()
    
    var c config.Config
    conf.MustLoad(*configFile, &c)
    ctx := svc.NewServiceContext(c)
    svr := server.NewUserServer(ctx)
    
    s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
        user.RegisterUserServer(grpcServer, svr)
    
        if c.Mode == service.DevMode || c.Mode == service.TestMode {
            reflection.Register(grpcServer)
        }
    })
    defer s.Stop()
    
    fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
    s.Start()
}
  • MustNewServer 内部实现调用了NewServer 方法, 这里我们关注NewServer 通过internal.NewRpcPubServer 方法实例化了internal.Server
if c.HasEtcd() {
    server, err = internal.NewRpcPubServer(c.Etcd, c.ListenOn, serverOptions...)
    if err != nil {
        return nil, err
    }
}
  • internal.NewRpcPubServer 中的registerEtcd 会调用Publisher.KeepAlive 方法
// KeepAlive keeps key:value alive.
func (p *Publisher) KeepAlive() error {
    // 这里获取 etcd 的连接
    cli, err := internal.GetRegistry().GetConn(p.endpoints)
    if err != nil {
        return err
    }
    
    p.lease, err = p.register(cli)
    if err != nil {
        return err
    }
    
    proc.AddWrapUpListener(func() {
        p.Stop()
    })
    
    return p.keepAliveAsync(cli)
}
  • p.register 这里把自己注册到服务中
func (p *Publisher) register(client internal.EtcdClient) (clientv3.LeaseID, error) {

    // 这里新建一个租约
    resp, err := client.Grant(client.Ctx(), TimeToLive)
    if err != nil {
        return clientv3.NoLease, err
    }
    
    // 得到租约的 ID
    lease := resp.ID
    
    // 这里拼接出实际存储的 key
    if p.id > 0 {
        p.fullKey = makeEtcdKey(p.key, p.id)
    } else {
        p.fullKey = makeEtcdKey(p.key, int64(lease))
    }

    // p.value 是前面的 figureOutListenOn 方法获取到自己的地址
    _, err = client.Put(client.Ctx(), p.fullKey, p.value, clientv3.WithLease(lease))

    return lease, err
}
  • 注册完之后,keepAliveAsync 开了一个协程保活这个服务
  • 当这个服务意外宕机时, 就不会再向etcd 保活,etcd 就会删除这个key
  • 注册好的服务如图

1.png

调用方-服务发现

  • order/api/order.go 源码如下
package main

import (
  "flag"
  "fmt"

  "go-zero-demo-rpc/order/api/internal/config"
  "go-zero-demo-rpc/order/api/internal/handler"
  "go-zero-demo-rpc/order/api/internal/svc"

  "github.com/zeromicro/go-zero/core/conf"
  "github.com/zeromicro/go-zero/rest"
)

var configFile = flag.String("f", "etc/order.yaml", "the config file")

func main() {
  flag.Parse()

  var c config.Config
  conf.MustLoad(*configFile, &c)

  server := rest.MustNewServer(c.RestConf)
  defer server.Stop()

  ctx := svc.NewServiceContext(c)
  handler.RegisterHandlers(server, ctx)

  fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
  server.Start()
}
  • 在svc.NewServiceContext 方法内部又调用了zrpc.MustNewClient ,zrpc.MustNewClient 主要实现在zrpc.NewClient
func NewServiceContext(c config.Config) *ServiceContext {
  return &ServiceContext{
    Config:  c,
    UserRpc: user.NewUser(zrpc.MustNewClient(c.UserRpc)),
  }
}
  • 最后实际调用了internal.NewClient 去实例化rpc client
func NewClient(c RpcClientConf, options ...ClientOption) (Client, error) {
  var opts []ClientOption
  if c.HasCredential() {
    opts = append(opts, WithDialOption(grpc.WithPerRPCCredentials(&auth.Credential{
      App:   c.App,
      Token: c.Token,
    })))
  }
  if c.NonBlock {
    opts = append(opts, WithNonBlock())
  }
  if c.Timeout > 0 {
    opts = append(opts, WithTimeout(time.Duration(c.Timeout)*time.Millisecond))
  }

  opts = append(opts, options...)

  target, err := c.BuildTarget()
  if err != nil {
    return nil, err
  }

  client, err := internal.NewClient(target, opts...)
  if err != nil {
    return nil, err
  }

  return &RpcClient{
    client: client,
  }, nil
}
  • 在zrpc/internal/client.go 文件里, 包含一个init 方法, 这里就是实际发现服务的地方, 在这里注册服务发现者
func init() {
  resolver.Register()
}
  • resolver.Register 方法实现
package resolver

import (
  "github.com/zeromicro/go-zero/zrpc/resolver/internal"
)

// Register registers schemes defined zrpc.
// Keep it in a separated package to let third party register manually.
func Register() {
  internal.RegisterResolver()
}
  • 最后又回到interval 包的internal.RegisterResolver 方法, 这里我们关注etcdResolverBuilder
func RegisterResolver() {
  resolver.Register(&directResolverBuilder)
  resolver.Register(&discovResolverBuilder)
  resolver.Register(&etcdResolverBuilder)
  resolver.Register(&k8sResolverBuilder)
}
  • etcdBuilder 的内嵌了discovBuilder 结构体,
  • Build 方法调用过程:
  • 实例化服务端:internal.NewClient ->client.dial ->grpc.DialContext
  • 由于etcd 是resolver.BuildDiscovTarget 生成的taget 所以是类似这样子的:discov://127.0.0.1:2379/user.rpc
  • 解析服务发现:ClientConn.parseTargetAndFindResolver ->grpc.parseTarget ->ClientConn.getResolver
  • 然后在grpc.newCCResolverWrapper 调用resolver.Builder.Build 方法去发现服务
  • 我们着重关注discovBuilder.Build 方法
type etcdBuilder struct {
  discovBuilder
}


type discovBuilder struct{}

func (b *discovBuilder) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (
  resolver.Resolver, error) {
  hosts := strings.FieldsFunc(targets.GetAuthority(target), func(r rune) bool {
    return r == EndpointSepChar
  })
  sub, err := discov.NewSubscriber(hosts, targets.GetEndpoints(target))
  if err != nil {
    return nil, err
  }

  update := func() {
    var addrs []resolver.Address
    for _, val := range subset(sub.Values(), subsetSize) {
      addrs = append(addrs, resolver.Address{
        Addr: val,
      })
    }
    if err := cc.UpdateState(resolver.State{
      Addresses: addrs,
    }); err != nil {
      logx.Error(err)
    }
  }
  sub.AddListener(update)
  update()

  return &nopResolver{cc: cc}, nil
}

func (b *discovBuilder) Scheme() string {
  return DiscovScheme
}
  • discov.NewSubscriber 方法调用internal.GetRegistry().Monitor 最后调用Registry.monitor 方法进行监视

cluster.getClient 拿到etcd 连接

cluster.load 作为第一次载入数据

cluster.watch 去watch 监听etcd 前缀key 的改动

func (c *cluster) monitor(key string, l UpdateListener) error {
  c.lock.Lock()
  c.listeners[key] = append(c.listeners[key], l)
  c.lock.Unlock()

  cli, err := c.getClient()
  if err != nil {
    return err
  }

  c.load(cli, key)
  c.watchGroup.Run(func() {
    c.watch(cli, key)
  })

  return nil
}
  • 如下图是cluster.load 的实现, 就是根据前缀拿到user.prc 服务注册的所有地址

2.png

Q

为什么不用Redis 做注册中心(反正只是把被调方的地址存储, 过期Redis 也能胜任), 找了很久找到这个说法

简单从以下几个方面说一下瑞迪斯为啥在微服务中不能取代 etcd:

1、redis 没有版本的概念,历史版本数据在大规模微服务中非常有必要,对于状态回滚和故障排查,甚至定锅都很重要

2、redis 的注册和发现目前只能通过 pub 和 sub 来实现,这两个命令完全不能满足生产环境的要求,具体原因可以 gg 或看源码实现

3、etcd 在 2.+版本时,watch 到数据官方文档均建议再 get 一次,因为会存在数据延迟,3.+版本不再需要,可想 redis 的 pub 和 sub 能否达到此种低延迟的要求

4、楼主看到的微服务架构应该都是将 etcd 直接暴露给 client 和 server 的,etcd 的性能摆在那,能够承受多少的 c/s 直连呢,更好的做法应该是对 etcd 做一层保护,当然这种做法会损失一些功能

5、redis 和 etcd 的集群实现方案是不一致的,etcd 采用的是 raft 协议,一主多从,只能写主,底层采用 boltdb 作为 k/v 存储,直接落盘

6、redis 的持久化方案有 aof 和 rdb,这两种方案在宕机的时候都或多或少的会丢失数据

二维TE波ADI-FDTD程序包使用指南:解压前的三件事 时域有限差分(FDTD)是电磁仿真的核心方法,但传统显式格式受Courant稳定条件约束,网格细化后时间步长被迫缩小,计算成本剧增。交替方向隐式时域有限差分(ADI-FDTD)通过将每个时间步分裂为两个半步,以隐式更新换取对稳定条件的突破,在保持精度的同时显著放大时间步长。针对二维TE波(仅含Ex、Ey、Hz分量),ADI-FDTD只需交替求解两个方向的三对角方程,非常适合波导、光子晶体、微带电路等纵向均匀结构的建模。然而,“无条件稳定”不等于无条件准确,程序落地仍需验证场分量类型、时间步放大倍数及PML吸 阅读详情

相关推荐

clerq:基于Redis的极简服务注册表和服务发现

克莱尔 基于Redis的极简服务注册表和服务发现 安装 npm i --save clerq 您也可以克隆此存储库并自己使用它。 git clone https://github.com/Dvs-Bilisim/clerq.git cd clerq npm i npm test 在运行测试之前,请确保本地主机上有Redis。 如果您暂时不知道该怎么做,请使用以下命令通过docker运行它。 docker run -p 6379:6379 --name clerq_redis redis:4-alpine 配置 cache:服务缓存的到期值(以毫秒为单位)。 默认情况下处于禁用状态。 delimiter:前缀和服务名称之间的分隔符。 expire:服务注册表记录的过期时间。 默认情况下处于禁用状态。 iface:可选。 从中获取外部ip的网络接口的名称 pino:用于pino记

【手写RPC框架(四)】使用Redis实现服务注册

文章目录一、Redis1.1 Redis安装1.2 Redis可视化工具1.3 Redis使用二、使用Redis实现简单服务注册2.1 项目使用的服务(service)2.1.1 接口2.1.2 实现类2.2 utils工具类2.2.1 注解2.2.2 包扫描器2.2.3 Redis管理工具类2.2.4 序列化2.3 服务端2.4 客户端 一、Redis 1.1 Redis安装 redis 1.2 Redis可视化工具 RedisDesktopManager 1.3 Redis使用 Redis教程 二、使用

xpl_1620的博客 2184

全栈开发指南:从前端到后端的全面掌握

全栈开发人员(Full Stack Developer)是指能够处理前端和后端开发工作的开发人员。他们需要掌握各种技术和工具,能够独立完成从需求分析、设计、开发到部署的整个流程。这篇博客将详细介绍全栈开发涉及的各个方面,为您提供从入门到进阶的指导。

Where there is a will, there is a way ! 3455

基于 Redis 发布订阅实现服务注册与发现

Redis发布订阅的详解并基于发布订阅进行服务注册与发现

面向生活编程 1831

mpu6050原理图_STM32控制 MPU6050 六轴陀螺仪资料汇总

一般使用MPU6050的步骤是先驱动I2C总线,然后初始化MPU6050,从MPU6050读取数据,最后是数据的处理。本文档详细介绍有关MPU6050 三维角度传感器电子陀螺仪使用说明。附件内容提供该 MPU6050模块原理图,配套STM32代码,相关数据手册及参考文档。实物连接图:MPU6050 六轴陀螺仪特点:使用芯片:MPU-6050供电电源:3-5v(内部低压差稳压)通信方式:标...

weixin_39911056的博客 4760

直流电源EMI滤波器设计实战:从器件选型到PCB布局的完整指南

本文提供了一份直流电源EMI滤波器的完整实战设计指南。针对EMI噪声超标问题,文章深入剖析了差模与共模噪声的抑制原理,详细讲解了从电路拓扑、核心器件(如X/Y电容、共模电感)的选型计算,到决定性能成败的PCB布局艺术。重点强调了低阻抗路径、干净接地及输入输出隔离等关键布局原则,并分享了常见的调试坑点与排查方法,旨在帮助硬件工程师设计出真正有效的滤波器,确保电源系统通过EMC测试。

q9r0s的博客 644

redis 学习笔记(一)

文章目录redis存储的数据类型redis 关键技术事件循环逐出过期持久化主从复制(异步操作:写入成功即成功)pipelinemgetredis集群:一致性 hash & redis clusterredis 客户端: 服务发现什么是服务发现redis 客户端: failoverredis 延迟删除&双机房删除redis 分布式锁 本笔记主要涉及学习 redis 过程中做的一些笔记,包含最初比较浅显的了解(所以会有一些比较简单的笔记描述),到后面对其应用的逐渐学习和补齐。其中主要参考书籍为

LynnWonder 634

【Ultrascale+ MPSOC学习记录】10、PS端以太网使用lwip

还是参考ALINX教程,学习PS端以太网如何使用,从教程中可以看到,开发板使用了一个xilinx默认不支持的phy JL2121,所以需要修改lwip的库源码。

ryo891012的博客 883

微服务架构 SpringCloud(二)Eureka(服务注册和服务发现基础篇)

一:Eureka简介 Eureka是Spring Cloud Netflix的一个子模块,也是核心模块之一。用于云端服务发现,一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移。服务注册与发现对于微服务系统来说非常重要。有了服务发现与注册,你就不需要整天改服务调用的配置文件了,你只需要使用服务的标识符,就可以访问到服务。他的功能类似于dubbo的注册中心(register...

weixin_34072637的博客 113

Hierarchical Flow Partition(Top-down&&Bottom-up)

hierarchy flow是将设计切分成多个block,分别处理单个block,然后完成后,将它们进行拼接以完成最终的chip。一般划分为三个阶段:chip planning;physical budget就是pin assignmenthierarchy flow实现的方法有两种,一种是top-down,另一种是bottom-up,无论是哪种,都是都要通过flatten的方法分别全局范围进行优化实现。

qq_39493558的博客 3074

Unity游戏开发:从零开始适配Nintendo Switch的完整指南

本文详细介绍了如何使用Unity进行Nintendo Switch游戏开发的完整流程,包括开发环境搭建、Unity工程适配、手柄与硬件特性开发、存储与性能优化等关键步骤。特别强调了Switch SDK的配置、输入系统改造和内存优化技巧,帮助开发者高效完成跨平台适配。

vodka的博客 1072

图灵学院:【微服务架构】SpringCloud之Eureka(服务注册和服务发现基础篇)(二)

一:Eureka简介   Eureka是Spring Cloud Netflix的一个子模块,也是核心模块之一。用于云端服务发现,一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移。服务注册与发现对于微服务系统来说非常重要。有了服务发现与注册,你就不需要整天改服务调用的配置文件了,你只需要使用服务的标识符,就可以访问到服务。他的功能类似于dubbo的注册中心(register...

JAVA这点事 898

【DaVinci Configurator专题】-5-通过Data Mapping自动创建CAN信号关联的Port并添加到APP SWC

目录 1 导入CAN Matrix变更后的Arxml文件 2 自动创建CAN信号关联的APP Port 3 CAN信号与App SWC的接口映射/连接 4 结尾 <--返回「实战篇」博文-总目录--> <--返回「Autosar从入门到精通-实战篇」专栏主页--> 关键词: DaVinci Developer DaVinci Configurator Pro ARXML AUTOSAR System Description Network Explorer 1

「汽车电子助手」的博客 5130

【微服务架构】SpringCloud之Eureka(服务注册和服务发现基础篇)(二)

【微服务架构】SpringCloud之Eureka(服务注册和服务发现基础篇)一:Eureka简介二:Eureka服务发现和注册(创建注册中心)三:Eureka注册中心加入权限四:注册一个服务 原文链接 上篇文章讲解了SpringCloud组件和概念介绍,接下来讲解一下SpringCloud组件相关组件使用、原理和每个组件的作用的,它主要提供的模块包括:服务发现(Eureka),断路器(Hystr...

CodeMeiYang的博客 211

南 京 农 业 大 学 试 题 纸 附表(rape.csv)是25个油菜材料,测得每个材料的千粒重、亚油酸等的含量. (1) 对物质含量的八个指标作主成分分析,画出碎石图和主成分得分图; (2) 选

(1)请用n1

Mrrunsen的博客 498

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

微服务架构 SpringCloud(二)Eureka(服务注册和服务发现基础篇) ...

一:Eureka简介 Eureka是Spring Cloud Netflix的一个子模块,也是核心模块之一。用于云端服务发现,一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移。 服务注册与发现对于微服务系统来说非常重要。有了服务发现与注册,你就不需要整天改服务调用的配置文件了,你只需要使用服务的标识符,就可以访问到服务。他的功...

weixin_34247155的博客 89

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

上一篇: 后端思维篇:如何抽一个观察者模板
下一篇: 聊聊如何实现 LRU 缓存算法
JavaMonsterr
博客等级 码龄4年 211粉丝 481原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值