微服务架构:进程间通信、服务发现、负载均衡
微服务架构,说白了就是把一个大系统拆成一堆小服务。每个服务独立部署、独立运行。但问题来了——这些服务之间怎么说话?怎么找到对方?怎么分摊压力?
我刚开始接触微服务时,觉得这不就是分布式系统那套东西吗?后来踩了不少坑才明白,微服务之间的通信、发现、负载均衡,这三件事环环相扣,任何一个环节出问题,整个系统都可能崩掉。
今天咱们就聊聊这三个核心话题。我会结合自己项目中的实际经验,把关键点讲透。
进程间通信:服务之间怎么说话?
微服务之间通信,主流方式就两种:同步通信和异步通信。
同步通信最常见的就是HTTP RESTful API。服务A直接调用服务B的接口,等它返回结果。这种方式简单直观,调试也方便。我在项目中用RESTful做内部服务通信时,发现一个问题——如果服务B挂了,服务A就得一直等,直到超时。所以超时时间一定要设,别让调用方死等。
异步通信则通过消息队列来实现。服务A把消息扔到队列里,服务B自己去取。这样服务A不用等,系统吞吐量上去了。我曾在电商项目里用RabbitMQ处理订单和库存的同步,效果不错。但异步也有坑——消息可能丢失,也可能重复消费。所以幂等性设计是必须的。
来看一个简单的RESTful通信示例:
// 服务A调用服务B的接口
// 使用libcurl库实现HTTP GET请求
#include <stdio.h>
#include <curl/curl.h>
size_t write_callback(void *ptr, size_t size, size_t nmemb, void *stream) {
// 处理响应数据
return size * nmemb;
}
int call_service_b() {
CURL *curl = curl_easy_init();
if (!curl) return -1;
curl_easy_setopt(curl, CURLOPT_URL, "http://service-b:8080/api/data");
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L); // 5秒超时
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "请求失败: %s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
return -1;
}
curl_easy_cleanup(curl);
return 0;
}
我的经验:同步通信适合实时性要求高的场景,比如用户登录验证。异步通信适合那些不需要立即返回结果的操作,比如发邮件、生成报表。选哪种,得看业务需求。
服务发现:怎么找到对方?
微服务架构里,服务实例的IP和端口是动态变化的。扩容、缩容、故障转移,都会导致地址变化。你不能把地址写死在代码里——那太傻了。
服务发现就是解决这个问题的。它分两种模式:客户端发现和服务端发现。
客户端发现:服务启动时,把自己注册到注册中心(比如Consul、Etcd)。调用方从注册中心拉取服务列表,然后自己选一个实例去调用。这种方式灵活,但每个客户端都得实现发现逻辑。
服务端发现:通过一个负载均衡器(比如Nginx、HAProxy)来做中转。调用方只跟负载均衡器打交道,由它去转发请求。这种方式对客户端友好,但负载均衡器可能成为瓶颈。
我在项目中用过Consul做服务发现。每个服务启动时,调用Consul的API注册自己,并定期发送心跳。如果某个服务挂了,Consul会自动把它从列表里移除。
注册服务的代码大概长这样:
// 向Consul注册服务
// 使用libcurl发送HTTP PUT请求
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
int register_service(const char *service_name, int port) {
CURL *curl = curl_easy_init();
if (!curl) return -1;
char url[256];
snprintf(url, sizeof(url), "http://consul-server:8500/v1/agent/service/register");
char json[512];
snprintf(json, sizeof(json),
"{"
"\"Name\": \"%s\","
"\"Port\": %d,"
"\"Check\": {"
"\"HTTP\": \"http://localhost:%d/health\","
"\"Interval\": \"10s\""
"}"
"}",
service_name, port, port);
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "注册失败: %s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
return -1;
}
curl_easy_cleanup(curl);
return 0;
}
注意:服务注册后一定要做健康检查。我曾经遇到过服务进程还在,但已经无法处理请求的情况。如果没有健康检查,调用方会把请求发给一个"活死人"服务,导致大量超时。
负载均衡:怎么分摊压力?
服务找到了,但如果有多个实例,该调用哪个?这就是负载均衡要干的事。
常见的负载均衡策略有:
- 轮询:按顺序轮流分配。简单,但不考虑服务器负载。
- 最少连接:把请求发给当前连接数最少的实例。适合长连接场景。
- 加权轮询:给性能好的服务器分配更多请求。我常用这个。
- 一致性哈希:根据请求的某个特征(比如用户ID)做哈希,保证同一个请求总是落到同一台机器。适合有状态服务。
在C语言层面实现一个简单的加权轮询负载均衡器,可以这样写:
// 加权轮询负载均衡器
// 假设有3个服务实例,权重分别为3、2、1
#include <stdio.h>
typedef struct {
char ip[16];
int port;
int weight;
int current_weight;
} Server;
Server servers[] = {
{"192.168.1.1", 8080, 3, 0},
{"192.168.1.2", 8080, 2, 0},
{"192.168.1.3", 8080, 1, 0}
};
int server_count = 3;
int total_weight = 6;
Server* get_next_server() {
int best_index = -1;
int max_weight = -1;
for (int i = 0; i < server_count; i++) {
servers[i].current_weight += servers[i].weight;
if (servers[i].current_weight > max_weight) {
max_weight = servers[i].current_weight;
best_index = i;
}
}
if (best_index != -1) {
servers[best_index].current_weight -= total_weight;
return &servers[best_index];
}
return NULL;
}
int main() {
// 模拟10次请求
for (int i = 0; i < 10; i++) {
Server *s = get_next_server();
if (s) {
printf("请求%d -> %s:%d\n", i+1, s->ip, s->port);
}
}
return 0;
}
运行结果会显示:权重高的服务器被分配了更多请求。这就是加权轮询的核心思想。
核心要点:负载均衡不是简单的"一人一个"。你得考虑服务器的实际处理能力。权重设置不合理,反而会拖慢整体性能。
三者如何协同工作?
通信、发现、负载均衡,这三者不是孤立的。它们共同构成了微服务架构的"骨架"。
我画了一张图,帮你理清它们的关系:
流程是这样的:
- 服务提供者启动时,向注册中心注册自己,并定期发送心跳。
- 服务消费者从注册中心拉取可用的服务实例列表。
- 消费者把请求发给负载均衡器。
- 负载均衡器根据策略,选择一个健康的实例,转发请求。
这个流程看起来简单,但实际落地时有很多细节。比如注册中心挂了怎么办?我建议做本地缓存——即使注册中心不可用,也能用缓存的服务列表继续工作一段时间。
避坑指南:我曾经在项目中遇到一个问题——服务实例已经挂了,但注册中心还没把它摘掉。原因是健康检查的间隔设得太长。后来我把检查间隔从30秒缩短到5秒,问题就解决了。健康检查的频率要跟业务容忍度匹配。
总结
微服务架构的通信、发现、负载均衡,说白了就是三个字:通、找、分。
- 通:选同步还是异步,看业务场景。
- 找:用注册中心动态管理服务地址。
- 分:用负载均衡策略合理分配请求。
这三件事做好了,微服务架构的"骨架"就稳了。剩下的就是业务逻辑的填充和持续优化。嗯,今天就聊到这儿。如果你在实际项目中遇到什么问题,欢迎交流。
公众号:蓝海资料掘金营,微信deep3321