现在电脑上装的各种联网软件,比如 xx管家,xx卫士,它们都作为客户端(Client)需要跟服务端(Server)建立连接收发消息,此时都会用到应用层协议,在这种 Client/Server (C/S) 架构下,它们可以使用自家造的 RPC 协议,因为它只管连自己公司的服务器就 ok 了。
但有个软件不同,浏览器(Browser) ,不管是 Chrome 还是 IE,它们不仅要能访问自家公司的服务器(Server) ,还需要访问其他公司的网站服务器,因此它们需要有个统一的标准,不然大家没法交流。于是,HTTP 就是那个时代用于统一 Browser/Server (B/S) 的协议。
也就是说在多年以前,HTTP 主要用于 B/S 架构,而 RPC 更多用于 C/S 架构。但现在其实已经没分那么清了,B/S 和 C/S 在慢慢融合。很多软件同时支持多端,比如某度云盘,既要支持网页版,还要支持手机端和 PC 端,如果通信协议都用 HTTP 的话,那服务器只用同一套就够了。而 RPC 就开始退居幕后,一般用于公司内部集群里,各个微服务之间的通讯。
那这么说的话,都用 HTTP 得了,还用什么 RPC?
HTTP 和 RPC 有什么区别
RPC和HTTP并不是竞争关系,而是互补关系:
- RPC = 内部通信的利器,追求性能和开发效率
- HTTP = 对外服务的标准,追求通用性和兼容性
基于 guide-rpc-framework RPC框架的代码分析,从服务发现、底层连接方式、传输内容三个维度详细对比HTTP和RPC的区别:
1. 🔍 服务发现机制
RPC的服务发现
RPC框架通常有完整的服务注册发现机制:
@Override
public InetSocketAddress lookupService(RpcRequest rpcRequest) {
String rpcServiceName = rpcRequest.getRpcServiceName();
CuratorFramework zkClient = CuratorUtils.getZkClient();
List<String> serviceUrlList = CuratorUtils.getChildrenNodes(zkClient, rpcServiceName);
if (CollectionUtil.isEmpty(serviceUrlList)) {
throw new RpcException(RpcErrorMessageEnum.SERVICE_CAN_NOT_BE_FOUND, rpcServiceName);
}
// load balancing
String targetServiceUrl = loadBalance.selectServiceAddress(serviceUrlList, rpcRequest);
log.info("Successfully found the service address:[{}]", targetServiceUrl);
String[] socketAddressArray = targetServiceUrl.split(":");
String host = socketAddressArray[0];
int port = Integer.parseInt(socketAddressArray[1]);
return new InetSocketAddress(host, port);
}
RPC服务发现特点:
- 🏗️ 自动注册:服务启动时自动注册到注册中心(如Zookeeper)
- 🔍 动态发现:客户端从注册中心动态获取服务列表
- ⚖️ 负载均衡:内置负载均衡算法选择最优服务实例
- 💓 健康检查:通过临时节点实现服务健康监控
- 🔄 实时更新:服务列表变化时自动更新本地缓存
HTTP的服务发现
HTTP通常依赖外部机制:
# 传统方式:硬编码或配置文件
http://api.example.com:8080/user/getUserInfo
# 现代方式:通过服务网格或API网关
http://api-gateway:80/user-service/getUserInfo
HTTP服务发现特点:
- 📝 手动配置:通常需要手动配置服务地址
- 🌐 DNS解析:依赖DNS或负载均衡器进行服务发现
- 🔧 外部依赖:需要额外的组件(如Nginx、API Gateway)
- 📍 静态配置:服务地址相对固定,变更需要重新配置
2. 🔌 底层连接方式
RPC的连接管理
RPC框架通常维护长连接池:
public Channel get(InetSocketAddress inetSocketAddress) {
String key = inetSocketAddress.toString();
// determine if there is a connection for the corresponding address
if (channelMap.containsKey(key)) {
Channel channel = channelMap.get(key);
// if so, determine if the connection is available, and if so, get it directly
if (channel != null && channel.isActive()) {
return channel;
} else {
channelMap.remove(key);
}
}
return null;
}
RPC连接特点:
- 🔗 长连接:维护持久化的TCP连接
- 🏊♂️ 连接池:复用连接,避免频繁建连开销
- 💓 心跳保活:定期发送心跳包保持连接活跃
- ⚡ 高性能:基于NIO的异步非阻塞通信
// If no data is sent to the server within 15 seconds, a heartbeat request is sent
p.addLast(new IdleStateHandler(0, 5, 0, TimeUnit.SECONDS));
p.addLast(new RpcMessageEncoder());
p.addLast(new RpcMessageDecoder());
p.addLast(new NettyRpcClientHandler());
HTTP的连接管理
HTTP传统上是短连接模式:
// HTTP 1.0: 每次请求建立新连接
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
// 请求完成后连接关闭
// HTTP 1.1: 支持Keep-Alive
connection.setRequestProperty("Connection", "keep-alive");
// HTTP/2: 多路复用
// 一个连接可以并发处理多个请求
HTTP连接特点:
- 🔄 短连接:HTTP/1.0默认每次请求后关闭连接
- 🔗 Keep-Alive:HTTP/1.1支持连接复用
- 🚀 多路复用:HTTP/2支持单连接并发请求
- 🌐 无状态:每个请求都是独立的
3. 📦 传输内容格式
RPC的传输内容
RPC使用自定义的二进制协议:
@Builder
@ToString
public class RpcMessage {
/**
* rpc message type
*/
private byte messageType;
/**
* serialization type
*/
private byte codec;
/**
* compress type
*/
private byte compress;
/**
* request id
*/
private int requestId;
/**
* request data
*/
private Object data;
}
RPC协议格式:
+---------------------------------------------------------------+
| 魔数 4byte | 版本 1byte | 总长度 4byte | 消息类型 1byte | ... |
+---------------------------------------------------------------+
| 序列化类型 1byte | 压缩类型 1byte | 请求ID 4byte | 数据长度 4byte |
+---------------------------------------------------------------+
| 数据内容 |
+---------------------------------------------------------------+
/**
* Magic number. Verify RpcMessage
*/
public static final byte[] MAGIC_NUMBER = {(byte) 'g', (byte) 'r', (byte) 'p', (byte) 'c'};
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
//version information
public static final byte VERSION = 1;
public static final byte TOTAL_LENGTH = 16;
public static final byte REQUEST_TYPE = 1;
public static final byte RESPONSE_TYPE = 2;
RPC传输特点:
- 🔢 二进制协议:紧凑的二进制格式,传输效率高
- 🗜️ 数据压缩:支持GZIP等压缩算法
- ⚡ 高效序列化:使用Kryo、Protobuf等高性能序列化
- 🎯 强类型:编译时类型检查,类型安全
HTTP的传输内容
HTTP使用文本协议:
POST /api/user/create HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 45
{
"name": "张三",
"age": 25,
"email": "zhangsan@example.com"
}
HTTP传输特点:
- 📝 文本协议:可读性好,便于调试
- 🏷️ 丰富头部:支持各种元数据(缓存、认证等)
- 🔄 标准化:统一的状态码和方法
- 🌍 跨语言:任何语言都可以轻松实现
📊 对比总结表
| 维度 | RPC | HTTP |
|---|---|---|
| 服务发现 | 🔍 自动注册发现 ⚖️ 内置负载均衡 💓 健康检查 | 📝 手动配置 🌐 DNS解析 🔧 外部组件 |
| 连接方式 | 🔗 长连接池 💓 心跳保活 ⚡ NIO异步 | 🔄 短连接 🔗 Keep-Alive 🚀 HTTP/2多路复用 |
| 传输内容 | 🔢 二进制协议 🗜️ 数据压缩 ⚡ 高效序列化 | 📝 文本协议 🏷️ 丰富头部 🌍 标准化 |
🎯 选择建议
选择RPC的场景:
- 🏢 内部微服务通信
- ⚡ 对性能要求极高
- 🔒 强类型约束需求
- 🎯 服务治理完整性要求
选择HTTP的场景:
- 🌐 对外API接口
- 🔄 跨语言系统集成
- 📱 前后端分离架构
- 🛠️ 需要标准化协议
在实际项目中,两者往往结合使用:内部用RPC保证性能,对外用HTTP保证兼容性! 🚀