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

微服务中的服务发现

微服务中的服务发现

什么是服务发现

服务发现是微服务架构中的关键机制,用于确定各个微服务的地址。例如,在一个 API Server 服务中,我们可能需要调用 User 服务来处理用户注册、登录和信息查询,也可能需要 Product 服务来获取商品相关信息。那么,如何发现并访问这些服务呢?

传统服务发现方法

最简单的方式是使用数据库存储服务名称及其对应的地址。每当有新的服务实例启动时,我们将其地址注册到数据库中。客户端在访问该服务时,可以从数据库中查询到对应的地址并发起请求。

Kitex 框架中,我们通常使用 ETCD 作为服务注册与发现的数据库,下面通过一个示例演示如何实现。

在 ETCD 中注册服务

在服务启动前,我们需要将其注册到 ETCD 中,以便其他微服务能够发现并访问它:

func kitexInit() (opts []server.Option) {
	cfg := config.GetConfig()
    // RegistryAddress为etcd数据库地址
	r, _ := etcd.NewEtcdRegistry(cfg.Registry.RegistryAddress)

    // address为user服务的地址
	address := cfg.KitexConfig.Address + cfg.KitexConfig.Port
	addr, _:= net.ResolveTCPAddr("tcp", address)

    // 保存server的配置信息。
	opts = append(opts,
		server.WithServiceAddr(addr),
		server.WithRegistry(r),
		server.WithServerBasicInfo(
			&rpcinfo.EndpointBasicInfo{
				ServiceName: cfg.KitexConfig.Service,
			},
		),
	)
	return
}

func main() {
	config.InitConfig()   // 初始化配置文件
	db.InitDB()           // 初始化db
	opts := kitexInit()   // 初始化服务配置信息

	svr := user.NewServer(new(UserServiceImpl), opts...) //创建newserver

	err := svr.Run() // 开启server服务,并实时注册到etcd数据库中

	if err != nil {
		log.Println(err.Error())
	}
}

这样,我们就成功开启了一个服务,并且成功将其注册到etcd数据库中。使其他服务可以通过 ETCD 进行发现和访问。

通过客户端调用服务

在微服务架构中,我们需要使用客户端来调用 User 服务,并通过 ETCD 进行服务发现:

func initUserClient() userservice.Client {
	// 1. 创建 etcd 服务解析器(连接注册中心)
	r, err := etcd.NewEtcdResolver(registryAddr)
	if err != nil {
		log.Fatalln(r)
	}
	// 2. 配置客户端选项:声明使用 etcd 作为服务发现源
	opts := []client.Option{
		client.WithResolver(r),
	}
	// 生命etcd进行服务发现。
	userClient, err := userservice.NewClient("douyinec.user", opts...)
	if err != nil {
		log.Fatalln(err)
	}
	return userClient
}

处理多个服务实例的情况

每次调用userClient客户端时,client会向etcd查询,微服务地址,并发送请求。如果 etcd 里存储了多个 user service 的地址(即多个实例部署了 user service),那么 client 会从 etcd 获取所有可用的 user service 地址,并根据负载均衡策略选择一个进行调用。

Kitex 默认使用随机负载均衡,但可以通过 client.WithLoadBalancer() 设定不同的策略,比如轮询`(Round Robin)、最小连接数(Least Connection)等。

处理服务变更(崩溃或重启)

如果 User 服务实例不可用了,会发生什么?

  1. User 服务启动后,会通过etcd.NewEtcdRegistry(...)注册自身地址到 ETCD
  2. 在运行期间,Kitex 定期向 ETCD 发送心跳,用于维持服务的可用状态。
  3. 如果 User 服务崩溃或手动关闭:
  • 它将停止发送心跳信号
  • ETCD 发现心跳超时(例如 10s 内未收到心跳)
  • ETCD 自动将该 User 服务实例从注册列表中移除

如果 User 服务重新启动到了新的地址,客户端还能找到它吗?

客户端每次请求时都会向 ETCD 查询最新的 User 服务地址,不会缓存旧的地址。

User 服务重新启动并注册到新的地址时,客户端会自动获取最新的服务位置。

这样,无论 User 服务的实例数量如何变化,客户端始终能够找到可用的服务实例,实现了动态的服务发现和负载均衡。

k8s中服务发现的方法

Kubernetes(k8s)中,每个应用服务对应一个 Servicek8s通过service进行服务发现,它充当了访问 Pod 的稳定入口。Service 通过 Endpoint 关联到具体的 Pod,并按照一定的负载均衡策略将请求路由到后端的 Pod。

Service、Endpoint 与 Pod 的关系

在 Kubernetes 中,

  • Pod 是运行应用程序的最小单位,每个 Pod 可能包含一个或多个容器。
  • Service 提供了一个稳定的访问入口,它会自动发现符合标签选择器(selector)的 Pod,并将请求负载均衡地转发给它们。
  • Endpoint 记录了 Service 关联的 Pod 的实际 IP 和端口。

整个流程如下:

  • Service 负责管理和暴露一组 Pod。
  • Endpoint 维护了当前 Service 关联的 Pod 列表。
  • kube-proxy 监听 Service 变更,并基于 Endpoint 配置负载均衡规则。
  • 客户端访问 Service 时,kube-proxy 负责将请求转发给 Endpoint 中的 Pod。

查看当前 Kubernetes 集群中的 Service

我们可以通过kubectl get service -o wide查看当前存在的所有service。实例输出:

kubectl get service -o wide
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)                       AGE    SELECTOR
kubernetes     ClusterIP      10.43.0.1       <none>           443/TCP                       297d   <none>
manager        NodePort       10.43.196.96    <none>           8090:32545/TCP                191d   app.oam.dev/component=manager
localstorage   NodePort       10.43.183.49    <none>           8189:30621/TCP                191d   app.oam.dev/component=localstorage
scheduler      NodePort       10.43.8.18      <none>           5525:31965/TCP                191d   app.oam.dev/component=scheduler
redis          NodePort       10.43.167.133   <none>           6379:32155/TCP                191d   app.oam.dev/component=redis

其中 redis 服务的 Cluster-IP 10.43.167.133,我们可以继续查询其 Endpoint。

查看 Service 关联的 Endpoints

使用以下命令查看 kubectl get endpoints redis -o wide。实例输出:

 kubectl get endpoints redis -o wide
NAME    ENDPOINTS         AGE
redis   10.42.0.18:6379   191d

从这里可以看到,redis 服务对应的 Pod 运行在 10.42.0.18:6379

查看 Service 详细信息

之后通过kubectl describe service redis查看详情。实例输出:

IP Families:              IPv4
IP:                       10.43.167.133
IPs:                      10.43.167.133
Port:                     redis  6379/TCP
TargetPort:               6379/TCP
NodePort:                 redis  32155/TCP
Endpoints:                10.42.0.18:6379
Session Affinity:         None
External Traffic Policy:  Local```,

这里,这里的 Endpoints 字段表明,该 Service 的流量被转发到了 10.42.0.18:6379 上运行的 Pod。其中IP字段标识ServiceClusterIPPort: redis 6379/TCP定义Service 的端口映射。TargetPort标识Service 关联的 Pod 实际监听的端口,流量会被转发到该端口。

Service 的服务发现机制

Kubernetes 主要通过两种方式实现服务发现:

  1. 环境变量
  • KubernetesPod 启动时,会为其关联的 Service 自动创建一组环境变量(处于同一namespaceService),例如 REDIS_SERVICE_HOST=10.43.167.133REDIS_SERVICE_PORT=6379,在同一命名空间中的pod可以通过这些环境变量连接 Service
  1. DNS 解析方式
  • Kubernetes 内置的 CoreDNS 服务会为 Service 自动创建 DNS 记录。例如,redis 服务的DNS 记录是 redis.default.svc.cluster.local,集群内部的 Pod 直接访问 redis:6379 即可连接。其中default是服务所在的命名空间

我们运行ping redis.default.svc.cluster.local,发现返回ip地址10.42.0.18,即redis service的地址。

redis.default.svc.cluster.local被称为 FQDN(Fully Qualified Domain Name,全限定域名)。

全限定域名信息存储在 Kubernetes 内部的 CoreDNS 组件中。其中Kubernetes API Server 监听 Service 资源的创建或删除。CoreDNS 通过 kube-dns 插件 监听这些变化,并在内部的 DNS 服务器 里维护这些 Service 的解析记录。当 Pod 需要访问 Service,会向 CoreDNS 查询 xxx.namespace.svc.cluster.localCoreDNS 解析出 ClusterIP 并返回给 Pod,这样 Pod 就能访问 Service 了。

Service 的负载均衡原理

当多个 Pod 运行同一个 Service 时,Kubernetes 通过 kube-proxy 进行负载均衡,主要有以下几种模式:

  • iptables(默认)kube-proxy 使用 iptables 规则将流量随机转发到 Endpoints

  • IPVS(更高效):使用 IP Virtual Server 进行流量分发,支持更多的负载均衡策略(如 rr 轮询、wrr 权重轮询、lc 最小连接数等)。


http://www.kler.cn/a/598279.html

相关文章:

  • 中间件漏洞-Tomcat篇
  • HashRouter和BrowserRouter对比
  • uni-app jyf-parser将字符串转化为html 和 rich-text
  • 数据分析处理库-Pandas
  • 理解操作系统(一)冯诺依曼结构和什么是操作系统
  • JavaSE1.0(基础语法之运算符)
  • 【spring对bean Singleton和Prototype的管理流程】
  • Java面试黄金宝典12
  • PyTorch 面试题及参考答案(精选100道)
  • 学习Flutter:搭建第一个 Flutter 应用
  • OpenCV图像拼接项目指南
  • AI:如何用 MeloSpyGUI 和 MeloSpySuite 生成爵士音乐文件
  • Android项目实战搭建 MVVM架构
  • 知识库已上线
  • 算法-动态规划总结
  • 20届智能车赛规则
  • 从零开始学习 Go 语言
  • go常用标准库学习笔记
  • VMWare虚拟机磁盘扩容
  • LabVIEW液压传动系统教学仿真平台