微服务架构:进程间通信、服务发现、负载均衡

微服务架构,说白了就是把一个大系统拆成一堆小服务。每个服务独立部署、独立运行。但问题来了——这些服务之间怎么说话?怎么找到对方?怎么分摊压力?

我刚开始接触微服务时,觉得这不就是分布式系统那套东西吗?后来踩了不少坑才明白,微服务之间的通信、发现、负载均衡,这三件事环环相扣,任何一个环节出问题,整个系统都可能崩掉。

今天咱们就聊聊这三个核心话题。我会结合自己项目中的实际经验,把关键点讲透。

进程间通信:服务之间怎么说话?

微服务之间通信,主流方式就两种:同步通信异步通信

同步通信最常见的就是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;
}

运行结果会显示:权重高的服务器被分配了更多请求。这就是加权轮询的核心思想。

核心要点:负载均衡不是简单的"一人一个"。你得考虑服务器的实际处理能力。权重设置不合理,反而会拖慢整体性能。

三者如何协同工作?

通信、发现、负载均衡,这三者不是孤立的。它们共同构成了微服务架构的"骨架"。

我画了一张图,帮你理清它们的关系:

微服务架构核心流程 服务消费者 注册中心 负载均衡器 服务实例1 服务实例2 服务实例3 ① 获取服务列表 ② 返回实例列表 ③ 发起请求 ④ 转发到选定实例 注册/心跳

流程是这样的:

  1. 服务提供者启动时,向注册中心注册自己,并定期发送心跳。
  2. 服务消费者从注册中心拉取可用的服务实例列表。
  3. 消费者把请求发给负载均衡器。
  4. 负载均衡器根据策略,选择一个健康的实例,转发请求。

这个流程看起来简单,但实际落地时有很多细节。比如注册中心挂了怎么办?我建议做本地缓存——即使注册中心不可用,也能用缓存的服务列表继续工作一段时间。

避坑指南:我曾经在项目中遇到一个问题——服务实例已经挂了,但注册中心还没把它摘掉。原因是健康检查的间隔设得太长。后来我把检查间隔从30秒缩短到5秒,问题就解决了。健康检查的频率要跟业务容忍度匹配。

总结

微服务架构的通信、发现、负载均衡,说白了就是三个字:通、找、分

  • :选同步还是异步,看业务场景。
  • :用注册中心动态管理服务地址。
  • :用负载均衡策略合理分配请求。

这三件事做好了,微服务架构的"骨架"就稳了。剩下的就是业务逻辑的填充和持续优化。嗯,今天就聊到这儿。如果你在实际项目中遇到什么问题,欢迎交流。


公众号:蓝海资料掘金营,微信deep3321