微服务中的服务调用至少需要考虑以下几个方面:
- 服务注册:动态存储服务的访问地址
- 服务发现:动态获取服务的访问地址
- 负载均衡:按照一定的规则获取同一服务的不同实例
它们在SpringCloud和K8S中有着不同的实现方式:
SpringCloud | K8S | |
---|---|---|
服务注册与服务发现 | 注册中心(Erueka、Nacos等) | Service(kube-dns) |
负载均衡 | 负载均衡组件(Ribbon、SpringCloudLoadBalancer等) | Service(kube-proxy等) |
下面将结合代码分别介绍SpinrgCloud与K8S中服务注册、服务发现、负载均衡的过程。
一、SpringCloud中的服务注册与服务发现
(一)SpringCloud服务注册与服务发现编程模型
SpringCloud在SpringCloudCommons模块中定义了服务注册与服务发现的抽象层,如下:
- 服务注册接口
ServiceRegistry
- 服务发现接口
DiscoveryClient
在maven依赖中引入Eureka或Nacos后,将会基于SpringBoot的工厂加载机制自动加载上述接口的实现:
- 服务注册接口
ServiceRegistry
的实现:EurekaServiceRegistry
、NacosServiceRegistry
- 服务发现接口
DiscoveryClient
的实现:NacosDiscoveryClient
、NacosDiscoveryClient
、CompositeDiscoveryClient
、SimpleDiscoveryClient
由于组件注入时使用的是ServiceRegistry
与DiscoveryClient
这两个抽象接口,因此,无须修改代码即可实现不同注册中心的无缝切换,只需要替换maven依赖与对应的注册中心的配置即可。
下面将基于Nacos演示一下服务注册与服务发现的用法(Eureka的用法完全一致)。
(二)Nacos注册中心服务
根据Nacos官网步骤启动Nacos注册中心。
访问http://127.0.0.1:8848
进入Nacos首页。
(三)Nacos服务提供者
在pom.xml中引入nacos-discovery依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
在application.properties(或application.yml)中引入nacos相关配置:
server.port=8081
spring.application.name=nacos-provider
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
spring.cloud.nacos.discovery.server-addr=localhost:8848
spring.cloud.nacos.discovery.namespace=public
定义应用入口并暴露一个服务:
@SpringBootApplication
public class NacosProviderApplication {
public static void main(String[] args) {
SpringApplication.run(NacosProviderApplication.class, args);
}
@RestController
class EchoController {
@GetMapping("/echo")
public String echo(HttpServletRequest request) {
return "echo:" + request.getParameter("name");
}
}
}
上述代码详见github
启动应用后,该服务将自动注册到Nacos注册中心。查看注册中心中该服务的状态:
curl -X GET 'http://127.0.0.1:8848/nacos/v1/ns/instance/list?serviceName=nacos-provider'
正常返回如下:
{"name":"DEFAULT_GROUP@@nacos-provider","groupName":"DEFAULT_GROUP","clusters":"","cacheMillis":10000,"hosts":[{"ip":"172.26.2.233","port":8081,"weight":1.0,"healthy":true,"enabled":true,"ephemeral":true,"clusterName":"DEFAULT","serviceName":"DEFAULT_GROUP@@nacos-provider","metadata":{"preserved.register.source":"SPRING_CLOUD","IPv6":"[fc00:f853:ccd:e793:0:0:0:1]"},"instanceHeartBeatInterval":5000,"instanceHeartBeatTimeOut":15000,"ipDeleteTimeout":30000}],"lastRefTime":1683528099856,"checksum":"","allIPs":false,"reachProtectionThreshold":false,"valid":true}
(四)Nacos服务消费者
在pom.xml中引入与Nacos服务提供者一样的nacos-discovery依赖。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
在application.properties(或application.yml)中引入nacos相关配置。除了端口和服务名外,其它配置与Nacos服务提供者完全一致。
server.port=8080
spring.application.name=nacos-consumer
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
spring.cloud.nacos.discovery.server-addr=localhost:8848
spring.cloud.nacos.discovery.namespace=public
在代码中通过DiscoveryClient获取Nacos服务提供者的实例地址,通过RestTemplate访问。并使用@EnableDiscoveryClient(autoRegister = false)
以避免将服务消费者注册到注册中心。
@SpringBootApplication
@EnableDiscoveryClient(autoRegister = false)
public class NacosConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(NacosConsumerApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@RestController
class HelloController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
private String serviceName = "nacos-provider";
@GetMapping("/services")
public String info() {
String serviceInfo = discoveryClient.getServices().toString();
String instanceInfo = discoveryClient.getInstances(serviceName).stream().map(instance -> "InstanceId: " + instance.getInstanceId() + "<br>ServiceId: " + instance.getServiceId() + "<br>Host: " + instance.getHost() + "<br>Port: " + instance.getPort() + "<br>").collect(Collectors.joining());
return serviceInfo + "<br><br>" + instanceInfo;
}
@GetMapping("/hello")
public String hello() {
List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
ServiceInstance serviceInstance = instances.stream().findAny().orElseThrow(() -> new IllegalStateException("no " + serviceName + " instance available"));
return restTemplate.getForObject("http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/echo?name=nacos",String.class);
}
}
}
上述代码详见github
启动应用后,访问对应服务:
curl -X GET 'http://127.0.0.1:8080/hello?name=nacos'
正常返回如下:
echo:nacos
二、SpringCloud中的负载均衡
(一)SpringCloud负载均衡编程模型
SpringCloud在SpringCloudCommons模块中定义了负载均衡的抽象层:LoadBalancerClient
与LoadBalanced
。
LoadBalancerClient
的实现有:基于Ribbon的RibbonLoadBalancerClient
、基于SpringCloudLoadBalancer的BlockingLoadBalancerClient
。
(二)基于@LoadBalanced注解与Feign组件实现客户端负载均衡
在pom.xml中引入Feign、服务发现与负载均衡的依赖。
<!-- OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 服务发现组件 -->
<!-- 该组件依赖了SpringCloudLoadBalancer组件(老版本为Ribbon)来实现负载均衡 -->
<!-- 如果将负载均衡交给K8S,就没有必要引入客户端负载均衡组件了,直接将@FeignClient的url指向K8S的Service地址即可 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 负载均衡组件(Feign需要) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
在application.properties(或application.yml)中引入nacos相关配置。该配置与Nacos服务消费者完全一致。
server.port=8080
spring.application.name=open-feign
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
spring.cloud.nacos.discovery.server-addr=localhost:8848
spring.cloud.nacos.discovery.namespace=public
定义Feign服务。
@FeignClient(name = "nacos-provider") // url = "http://localhost:8081"
interface EchoApi {
@GetMapping("/echo")
String echo(@RequestParam("name") String name);
}
定义应用入口。
@EnableFeignClients
@SpringBootApplication
public class OpenFeignApplication {
public static void main(String[] args) {
SpringApplication.run(OpenFeignApplication.class, args);
}
@RestController
class HelloController {
@Autowired
EchoApi echoApi;
@GetMapping("/hello")
public String hello() {
return echoApi.echo("nacos");
}
}
}
上述代码详见github
停止Nacos服务提供者与Nacos服务消费者两个应用。随后启动三个Nacos服务提供者应用(修改server.port环境变量),再启动当前应用,验证负载均衡效果。
curl -X GET 'http://127.0.0.1:8080/hello?name=nacos'
正常返回如下:
echo:nacos
查看三个Nacos服务提供者的控制台日志,多次调用会在不同的应用控制台显示相关信息。
三、K8S中的服务注册、服务发现与负载均衡
K8S通过Service对象定义一个服务,一旦Service定义成功,就自动支持了服务注册、服务发现与负载均衡。Service可以基于Pod、Deployment、另外一个Service等对象对外暴露服务,下面将以Deployment为例创建Service。
创建并执行deployment.yml
文件:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
kubectl apply -f deployment.yml
创建并执行service.yml
文件:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 8080
targetPort: 80
kubectl apply -f service.yml
上述Service的创建过程与下述命令等效:
kubectl expose deployment nginx-deployment --name nginx-service --port=8080 --target-port=80
至此,nginx-service服务创建成功,其自动支持了以下功能:
- 服务注册:在DNS中写入了nginx-service的记录
- 服务发现:其它服务调用nginx-service时,会从DNS中获取到对应的Service记录,并根据Service找到Pod的ip
- 负载均衡:一个Service可以对应多个Pod,访问Service会自动转发到其中某一个Pod
为了验证上述功能,可以进入容器访问对应服务:
kubectl get pod # 获取pod name
kubectl exec -it nginx-deployment-6b7f675859-dn9jj bash # 进入对应的pod容器
curl nginx-service:8080 # 访问nginx-service服务
正常将会返回nginx首页的html文本。
如果使用minikube,可以使用minikube service
命令暴露服务,如下所示:
minikube service nginx-service
随后浏览器会自动打开nginx首页。
四、总结
SpringCloud通过Erueka、Nacos等注册中心实现服务注册与服务发现,通过Ribbon、SpringCloudLoadBalancer等负载均衡组件实现客户端的负载均衡。
K8S通过Service等对象实现服务注册、服务发现以及服务端的负载均衡。
可以看到,SpringCloud与K8S中的服务注册、服务发现与负载均衡的角度不同:SpringCloud偏向于开发,而K8S则更偏向于运维。纯K8S、纯SpringCloud、以及SpringCloud与K8S混用这三种场景都可能存在,不能说哪个一定更好,只能视情况而定。比如说:技术栈是Java,并且没有专职运维或熟悉K8S的人员,那么SpringCloud就是很好的选择;如果技术栈为Go等语言,或者有容器化部署的需求与能力,则可以考虑更通用的K8S。
同时,除了服务注册、服务发现以及负载均衡外,SpringCloud与K8S还有其它技术重叠的地方,如:配置管理、服务路由(网关)等等,后续也将继续分析SpringCloud与K8S在微服务中的其它不同用法。
参考文档
- 《深入理解SpringCloud与实战 方剑 著》
- SpringCloud官方网站
- 黑马程序员SpringCloud微服务技术栈教程
- K8S官方网站