微服务架构是一种设计方法,将应用程序划分为一组小型服务,每个服务在独立的进程中运行,通常根据业务能力进行组织。这些服务通过多种通信方式交互,以实现整个应用的功能。今天我们着重介绍同步通信,关于异步通信和消息队列(MQ)等内容将在后续讲解。
这里所指的通信,是指我们在客户端内部进行的服务间通信,而非通过调用外部的 Web 服务进行访问。好的,让我们开始。
负载均衡
服务端负载均衡
在深入探讨客户端负载均衡之前,我们首先应该对服务端负载均衡有一个更加深入的了解,例如 nginx 组件。现在让我们来仔细看一下:
nginx 通常用来对我们服务器内部进行负载转发请求,而客户端则是在各个服务内部进行负载分担。
客户端负载均衡
在 Spring Cloud 中,例如使用 Ribbon 时,客户端会维护一个服务器地址列表,在发送请求之前通过负载均衡算法选择一个服务器进行访问。这种方式被称为客户端负载均衡,因为它在客户端内部就完成了负载均衡算法的分配工作。
同步通信
一般情况下,我们在进行 HTTP 请求时常会借助各种工具类。我相信大家之前可能经常自行封装 httputils 等类,或者使用其他官方提供的工具。比如,今天我们来讲解一下 RestTemplate 工具类。它其实就是一个用来发送 HTTP 请求的工具,只不过它在此基础上多做了一些额外的工作。接下来我们先看一下它的基本用法。
RestTemplate
RestTemplate 是由 Spring 框架提供的一个功能强大的类,专门用于进行同步客户端的 HTTP 访问。它的设计旨在简化使用 HTTP 客户端进行 REST 调用的复杂流程,并提供了丰富的方法和功能来处理各种 HTTP 请求和响应。
创建 RestTemplate 实例
首先,你需要创建一个 RestTemplate 的实例。这可以通过直接实例化或使用 Spring 的自动装配来完成。
import org.springframework.web.client.RestTemplate;
RestTemplate restTemplate = new RestTemplate();
发送请求
使用 RestTemplate 发送一个 GET 请求并获取响应体。
String url = "http://example.com/api/resource";
String result = restTemplate.getForObject(url, String.class);
System.out.println(result);
发送一个 POST 请求,通常包含请求体。
String url = "http://example.com/api/resource";Map<String, Object> requestMap = new HashMap<>();
requestMap.put("key1", "value1");
requestMap.put("key2", "value2");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestMap, headers);
String response = restTemplate.postForObject(url, entity, String.class);
System.out.println(response);
看到这里,你可能会想到,这与微服务间的通信有什么关系呢?难道不只是简单的 HTTP 调用吗?让我们继续深入探讨。
LoadBalanced 注解
微服务架构中通常会涉及到注册中心。今天我们专注讨论微服务间的通信,而不深入讲解注册中心,例如 Nacos 是如何管理所有微服务的注册以及如何使一个服务节点发现其他服务节点的。假设我们已经获得了其他服务节点的 IP 地址,你可能会想直接将上述示例中的域名替换为 IP 地址,但是在面对保证高可用的多节点微服务时,直接在代码中写死 IP 地址将会带来灾难性的后果。所有的 IP 地址应当由一个统一组件进行管理和选择。
因此,Ribbon 应运而生。一般情况下,如果在项目的 pom 文件中集成了 Nacos 依赖,通常会默认包含 Ribbon 组件,因此不需要单独配置 pom 文件来引入 Ribbon 依赖。
基本用法
如前所述,一种方法是直接实例化对象,另一种则是通过 Spring 容器进行管理和注入。
@Configuration
public class RestConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
这样一来,当需要使用时,就可以轻松地进行服务调用操作。
@RestController@RequestMapping("/user")
public class UserController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/findOrderByUserId/{id}")
public R findOrderByUserId(@PathVariable("id") Integer id) {
String url = "http://mall-order/order/findOrderByUserId/"+id;
R result = restTemplate.getForObject(url,R.class);
return result;
}
}
注意,mall-order 可不是我们说的网站域名,而是我们配置在 nacos 等注册中心的服务名。
添加了 LoadBalanced 注解后,RestTemplate 会自动注入所需的依赖项,具体的实现可以通过查看源代码来了解。
源码分析
关于 Spring 的自动配置,我之前在讲解 Spring 时提到过很多次,这里就不再详细展开了。我们可以看到它实现了 SmartInitializingSingleton 接口,因此,想要使用负载均衡功能,必须等到所有的 bean 都加载完毕才能进行。
好的,我们可以看到,在这里他向 RestTemplate 类添加了一个拦截器。接下来,我们可以探究一下这个拦截器具体做了什么。我选择不逐步展示整个过程是因为这并不必要。首先,这样做记不住,其次,就像处理业务一样,我们只关注最终走到了哪一个数据表。我们只需记住他一定会经过的那个十字路口即可,这样可以减轻大脑的负担。
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
其实,看到这一点,不用猜也能明白。一旦获取了 serviceName,也就是我们之前定义的微服务名,它会在 execute 方法中被替换为真正的 IP 地址,然后最终调用 HTTP 请求完成整个过程。让我们来仔细看一下源代码吧。
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
所有的负载均衡规则都实现在 getServer 方法中,我们不再深入追踪了。在这一步,我们已经找到了要调用的微服务。如果你还有疑问,我们可以看一下 Server 类的具体实现,就能明白了。
最终就交给 HTTP 调用即可。然而,仅仅这样还不够。难道你会愿意在每个服务中编写这种无关紧要的服务调用代码吗?这会非常繁琐,不仅增加了业务逻辑的复杂性,还让人感到不便。幸运的是,有了 Spring Cloud OpenFeign,这一切变得轻松许多。OpenFeign 的出现正是为了解决这种服务间通信的问题,它将这些繁琐的细节封装起来。
然而,要注意,这种封装并不影响通信的实质。下次我们将详细讨论 Spring Cloud OpenFeign 与 Dubbo 调用组件的区别与使用方法。
总结
今天我们专注于微服务之间的网络通信。可以清楚地看到,框架的最终目标是使程序员能够更专注于业务逻辑,而不是被迫写各种无关紧要的代码。总结一下,尽管我们使用了框架和各种抽象,但最终仍然是通过 HTTP 来进行调用。不同的是,在实际调用之前,我们引入了一个拦截器来实现微服务的负载均衡。这个拦截器中实现了各种均衡算法,最终确定真实的 IP 地址和端口,以便进行访问并获取所需的数据。
转载请注明:可思数据 » 深入探讨微服务架构中的同步通信机制