微服务——网络篇(对比4种不同解决方案)

124 阅读5分钟

微服务——网络篇(对比4种不同解决方案)

说到微服务的网络,主要是为了让服务能够对外(对外指服务本身之外,包括集群内部/外部)暴露自己,并解决服务多个实例的负载均衡、服务本身的横向扩展等问题。 这篇文章我将对比4种不同的解决方案,并会解释各种方案的适用场景,当然还会提供简单实践示例帮助理解。

consul

consul是微服务中用于服务注册和发现的中心化组件,类似的组件还有如Zookeeper。

image.webp

在consul中,维护了一个中心化的注册中心,这个注册中心用于跟踪服务和他们各自的ip地址。

这个注册中心,由图中的consul server和consul client组成。

  1. consul server:一般是独立部署的,并且由几个server来保证可用性。
  2. consul client:一般在每一个node中有一个consul client,负责跟consul server通信。(在k8s中,每个node一个consul client,通过 DaemonSet 进行部署的)

consul提供了Restful API和各语言实现的SDK(如Go SDK)来与consul交互并查询服务信息。

如我想要去使用ServiceA提供的服务,我可以先通过Restful API发现ServiceA的域名/IP:

curl http://127.0.0.1:8500/v1/catalog/service/ServiceA

这个请求将返回如下响应:

[
  {
    "ID": "service-instance-id-1",
    "Node": "node-name-1",
    "Address": "192.168.1.101",
    "Datacenter": "dc1",
    "TaggedAddresses": {
      "lan": "192.168.1.101",
      "wan": "203.0.113.1"
    },
    "NodeMeta": {
      "some-metadata-key": "some-metadata-value"
    },
    "ServiceKind": "",
    "ServiceID": "service-instance-id-1",
    "ServiceName": "ServiceA",
    "ServiceTags": ["primary", "v1"],
    "ServiceAddress": "",
    "ServicePort": 8080,
    "ServiceEnableTagOverride": false,
    "CreateIndex": 100,
    "ModifyIndex": 200
  },
  ...
]

其中,Address表示服务ip,ServicePort服务工作的端口。

同时,我也可以使用Go SDK来实现同样的效果:

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/hashicorp/consul/api"
)

func main() {
	// 1. 创建默认的 Consul 配置(默认连接本地 8500 端口)
	config := api.DefaultConfig()

	// 可选:如果 Consul 在远程服务器上
	// config.Address = "192.168.1.10:8500"

	// 2. 创建 Consul 客户端
	client, err := api.NewClient(config)
	if err != nil {
		panic(err)
	}

	// 3. 查询 ServiceA 的健康实例
	serviceName := "ServiceA"
	tag := ""           // 如果你想根据 tag 过滤,比如 "v1"
	passingOnly := true // 只获取健康的实例

	services, _, err := client.Health().Service(serviceName, tag, passingOnly)
	if err != nil {
		panic(err)
	}

	if len(services) == 0 {
		fmt.Printf("未找到服务 %s 的健康实例\n", serviceName)
		return
	}

	// 4. 获取第一个可用的服务地址和端口
	firstService := services[0].Service
	address := firstService.Address
	port := firstService.Port

	fmt.Printf("发现服务 %s 实例:%s:%d\n", serviceName, address, port)

	// 5. 构造请求并调用该服务的 API 接口(例如 /api/data)
	url := fmt.Sprintf("http://%s:%d/api/data", address, port)
	resp, err := http.Get(url)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)
	fmt.Printf("服务返回结果: %s\n", body)
}

如果你的服务并不是适用Kubernetes进行部署的,那么使用consul是一个非常好的选择。

Kubernetes Service

如果你的服务是通过Kubernetes部署的,那么你可能没有必要去使用consul,因为Kubernetes本身就提供了很完善的网络服务。

Kubernetes Service作用是给一组Pod实例提供一个稳定的对外暴露的出口。我们通过创建Pod提供服务,每个Pod在集群内部都有一个虚拟ip,客户端通过访问这个虚拟ip,可以请求到对应的Pod。但是,Pod的生命周期是短暂的,它可能因为服务更新、Pod漂移等原因而消亡,这时候可能导致通过Pod ip访问服务的方式不可用。

而Service解决了这个问题,Service在集群内部拥有稳定的ip和域名,service指向一组pod(服务多实例部署),并提供负载均衡(4层负载均衡)等功能。

下面这个例子,我创建了一个Service资产:

# nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80         # Service 暴露的端口
      targetPort: 80   # 容器监听的端口

这个Service指向了app=nginx的Pod。(默认是type=ClusterIP)

$ kubectl get svc
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
nginx-service   ClusterIP   10.96.123.45    <none>        80/TCP    1m

集群内部访问这个Service,可以采用如下方式:

curl http://nginx-service

Kubernetes ingress

如果外部客户端想要访问集群内部的服务时,单靠service就无法解决这个问题。Ingress提供从集群外部到集群内服务的 HTTP 和 HTTPS 路由。 流量路由由 Ingress 资源所定义的规则来控制。

想要使用ingress,你需要先创建一个ingress资产,并为这个ingress资产指定一个ingress class(ingress 控制器)。

下面这个例子中,创建了ingress资产,ingress class为nginx(它指定了ingress-nginx控制器)

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minimal-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx-example
  rules:
  - http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          service:
            name: test
            port:
              number: 80

假设你的集群已经有了一个域名example.com,你访问http://example.com/testpath/get_data,请求将被路由到你的服务test,并且在test内部看来,客户端的访问url是http://example.com/get_data

(阿里云)SLB

SLB(service load balance)指服务负载均衡,它的实现方案有很多种(4层负载均衡、7层负载均衡),这里以阿里云的SLB为例。

如果你使用了阿里云的ECS机器,那使用阿里云的SLB会更加方便。当你的服务部署了多个实例,可能会面临一个问题是我如何让外部访问我的服务,如何做到负载均衡,如何做到横向扩展。

阿里云的SLB提供了多种解决方案,包含了ALB(7层网络负载)和NLB(4层网络负载)。

以ALB为例,应用型负载均衡ALB(Application Load Balancer)是阿里云推出的专门面向HTTP、HTTPS和QUIC等应用层负载场景的负载均衡服务,具备超强弹性及大规模应用层流量处理能力。ALB具备处理复杂业务路由的能力,与云原生相关服务深度集成,是阿里云官方提供的云原生Ingress网关。

Xnip2025-06-29_20-58-47.jpg