一、HTTP/2 核心概念
1.1 HTTP/2 帧结构
java
复制
下载
// HTTP/2 帧基础结构(RFC 7540)
public class Http2Frame {
// 9字节的帧头
private int length; // 24位:负载长度 (0-2^24-1)
private byte type; // 8位:帧类型
private byte flags; // 8位:标志位
private int streamId; // 31位:流ID (0-2^31-1)
// 帧负载 (0-16383字节)
private byte[] payload;
}
// 帧类型枚举
public enum FrameType {
DATA(0x0), // 数据帧
HEADERS(0x1), // 头部帧
PRIORITY(0x2), // 优先级帧
RST_STREAM(0x3), // 流终止帧
SETTINGS(0x4), // 设置帧
PUSH_PROMISE(0x5), // 推送承诺帧
PING(0x6), // PING帧
GOAWAY(0x7), // GOAWAY帧
WINDOW_UPDATE(0x8), // 窗口更新帧
CONTINUATION(0x9); // 延续帧
}
1.2 HTTP/2 连接建立
java
复制
下载
// HTTP/2 连接建立过程
public class Http2Connection {
// 必须发送的连接前言(24字节)
private static final byte[] CONNECTION_PREFACE =
"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes();
public void establishConnection(Socket socket) throws IOException {
// 1. TLS握手(如果使用HTTPS)
SSLSocket sslSocket = (SSLSocket) socket;
sslSocket.startHandshake();
// 2. 发送连接前言
socket.getOutputStream().write(CONNECTION_PREFACE);
// 3. 发送SETTINGS帧
sendSettingsFrame(socket);
// 4. 等待对方SETTINGS帧
readSettingsFrame(socket);
// 5. 发送SETTINGS ACK
sendSettingsAck(socket);
}
private void sendSettingsFrame(Socket socket) throws IOException {
// 构建SETTINGS帧
ByteBuffer buffer = ByteBuffer.allocate(9 + 6); // 帧头 + 一个设置项
// 帧头
buffer.putShort((short) 6); // 长度
buffer.put((byte) 0x4); // 类型: SETTINGS
buffer.put((byte) 0x0); // 标志位
buffer.putInt(0x0); // 流ID: 0(连接级别)
// 负载:设置初始窗口大小
buffer.putShort((short) 0x4); // SETTINGS_INITIAL_WINDOW_SIZE
buffer.putInt(65535); // 初始窗口大小
socket.getOutputStream().write(buffer.array());
}
}
二、HTTP/2 流控制实现
2.1 流控制核心算法
java
复制
下载
// HTTP/2 流控制实现
public class Http2FlowControl {
// 流控制窗口状态
private final Map<Integer, StreamWindow> streamWindows = new ConcurrentHashMap<>();
private final AtomicInteger connectionWindow = new AtomicInteger(65535);
// 流窗口状态
static class StreamWindow {
private final int streamId;
private volatile int windowSize; // 当前窗口大小
private volatile int bytesSent; // 已发送字节数
private volatile int bytesReceived; // 已接收字节数
private volatile boolean blocked; // 是否阻塞
// 窗口更新等待队列
private final Queue<Runnable> pendingWrites = new ConcurrentLinkedQueue<>();
public StreamWindow(int streamId, int initialWindowSize) {
this.streamId = streamId;
this.windowSize = initialWindowSize;
}
}
/**
* 发送数据前的流控制检查
*/
public synchronized boolean canSend(int streamId, int dataLength) {
// 1. 检查连接级别窗口
if (connectionWindow.get() < dataLength) {
return false;
}
// 2. 检查流级别窗口
StreamWindow window = streamWindows.get(streamId);
if (window == null) {
window = new StreamWindow(streamId, 65535);
streamWindows.put(streamId, window);
}
if (window.windowSize < dataLength) {
window.blocked = true;
return false;
}
// 3. 更新窗口计数
connectionWindow.addAndGet(-dataLength);
window.windowSize -= dataLength;
window.bytesSent += dataLength;
return true;
}
/**
* 接收数据后的窗口更新
*/
public synchronized void onDataReceived(int streamId, int dataLength) {
// 1. 更新流窗口
StreamWindow window = streamWindows.get(streamId);
if (window != null) {
window.bytesReceived += dataLength;
// 当数据被消费后,发送WINDOW_UPDATE
}
// 2. 更新连接窗口
connectionWindow.addAndGet(-dataLength);
// 3. 如果窗口太小,发送WINDOW_UPDATE帧
if (connectionWindow.get() < 16384) { // 低于阈值
sendWindowUpdate(0, 32768); // 连接级别更新
}
}
/**
* 消费数据后的窗口更新
*/
public synchronized void onDataConsumed(int streamId, int dataLength) {
StreamWindow window = streamWindows.get(streamId);
if (window != null) {
window.windowSize += dataLength;
// 发送WINDOW_UPDATE帧
sendWindowUpdate(streamId, dataLength);
// 尝试处理阻塞的写入
processPendingWrites(window);
}
}
/**
* 发送WINDOW_UPDATE帧
*/
private void sendWindowUpdate(int streamId, int windowSizeIncrement) {
ByteBuffer buffer = ByteBuffer.allocate(13); // 9字节帧头 + 4字节增量
// 帧头
buffer.putInt(4); // 长度: 4字节
buffer.put((byte) 0x8); // 类型: WINDOW_UPDATE
buffer.put((byte) 0x0); // 标志位
buffer.putInt(streamId); // 流ID
// 负载:窗口增量(必须>0)
buffer.putInt(windowSizeIncrement);
// 发送帧
sendFrame(buffer.array());
}
/**
* 处理被阻塞的写入
*/
private void processPendingWrites(StreamWindow window) {
while (!window.pendingWrites.isEmpty()) {
Runnable write = window.pendingWrites.peek();
// 重新尝试执行
try {
write.run();
window.pendingWrites.poll();
} catch (WindowBlockedException e) {
// 仍然阻塞,等待下次窗口更新
break;
}
}
if (window.pendingWrites.isEmpty()) {
window.blocked = false;
}
}
}
2.2 零窗口与阻塞恢复
java
复制
下载
// 零窗口探测与恢复
public class ZeroWindowProbe {
private final ScheduledExecutorService scheduler;
private final Map<Integer, ProbeState> probeStates;
public ZeroWindowProbe() {
this.scheduler = Executors.newSingleThreadScheduledExecutor();
this.probeStates = new ConcurrentHashMap<>();
}
/**
* 当窗口变为0时,启动探测机制
*/
public void onZeroWindow(int streamId) {
ProbeState state = new ProbeState(streamId);
probeStates.put(streamId, state);
// 启动探测定时任务
scheduler.scheduleAtFixedRate(
() -> probeZeroWindow(streamId),
1000, // 初始延迟1秒
1000, // 间隔1秒
TimeUnit.MILLISECONDS
);
}
/**
* 探测零窗口
*/
private void probeZeroWindow(int streamId) {
// 发送1字节的空DATA帧(标志位: END_STREAM = false)
byte[] probeFrame = createDataFrame(streamId, new byte[1], false);
sendFrame(probeFrame);
ProbeState state = probeStates.get(streamId);
if (state != null) {
state.probeCount++;
// 如果探测超过阈值,关闭流
if (state.probeCount > 30) { // 30次探测失败
resetStream(streamId, ErrorCode.FLOW_CONTROL_ERROR);
probeStates.remove(streamId);
}
}
}
/**
* 窗口恢复,停止探测
*/
public void onWindowUpdate(int streamId, int increment) {
ProbeState state = probeStates.get(streamId);
if (state != null && increment > 0) {
state.probeTask.cancel(false);
probeStates.remove(streamId);
}
}
static class ProbeState {
final int streamId;
int probeCount;
ScheduledFuture<?> probeTask;
ProbeState(int streamId) {
this.streamId = streamId;
this.probeCount = 0;
}
}
/**
* 创建DATA帧
*/
private byte[] createDataFrame(int streamId, byte[] data, boolean endStream) {
ByteBuffer buffer = ByteBuffer.allocate(9 + data.length);
// 帧头
buffer.putShort((short) data.length); // 长度
buffer.put((byte) 0x0); // 类型: DATA
buffer.put((byte) (endStream ? 0x1 : 0x0)); // END_STREAM标志位
buffer.putInt(streamId); // 流ID
// 负载
buffer.put(data);
return buffer.array();
}
}
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
三、gRPC 消息分帧实现
3.1 Protobuf 消息序列化与分帧
java
复制
下载
// gRPC消息分帧处理器
public class GrpcMessageFramer {
// gRPC帧头:5字节
static class FrameHeader {
byte compressedFlag; // 压缩标志 (1=压缩,0=未压缩)
int length; // 消息长度 (4字节,大端序)
}
// 消息分帧
public List<byte[]> frameMessage(byte[] protobufData, boolean compressed) {
List<byte[]> frames = new ArrayList<>();
// 如果消息太大,需要分片
int maxFrameSize = 16384; // 16KB,HTTP/2默认最大帧大小
int offset = 0;
while (offset < protobufData.length) {
int chunkSize = Math.min(maxFrameSize, protobufData.length - offset);
byte[] chunk = new byte[chunkSize];
System.arraycopy(protobufData, offset, chunk, 0, chunkSize);
// 创建帧
byte[] frame = createFrame(chunk, compressed, offset == 0);
frames.add(frame);
offset += chunkSize;
}
return frames;
}
// 创建单个帧
private byte[] createFrame(byte[] data, boolean compressed, boolean isFirst) {
ByteBuffer buffer = ByteBuffer.allocate(5 + data.length);
// 帧头
buffer.put(compressed ? (byte) 1 : (byte) 0); // 压缩标志
buffer.putInt(data.length); // 数据长度
// 数据负载
buffer.put(data);
return buffer.array();
}
// 消息重组
public byte[] deframeMessages(List<byte[]> frames) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
for (byte[] frame : frames) {
ByteBuffer buffer = ByteBuffer.wrap(frame);
// 解析帧头
byte compressedFlag = buffer.get();
int length = buffer.getInt();
// 读取数据
byte[] data = new byte[length];
buffer.get(data);
// 处理压缩
if (compressedFlag == 1) {
data = decompress(data);
}
output.write(data);
}
return output.toByteArray();
}
// Protobuf消息包装器
public static class GrpcMessage<T> {
private final T protobufMessage;
private final Metadata headers;
public GrpcMessage(T message, Metadata headers) {
this.protobufMessage = message;
this.headers = headers;
}
public byte[] serialize() throws IOException {
// 1. 序列化Protobuf消息
ByteArrayOutputStream protoStream = new ByteArrayOutputStream();
protobufMessage.writeTo(protoStream);
byte[] protoData = protoStream.toByteArray();
// 2. 添加gRPC帧头
ByteBuffer buffer = ByteBuffer.allocate(5 + protoData.length);
buffer.put((byte) 0); // 未压缩
buffer.putInt(protoData.length);
buffer.put(protoData);
return buffer.array();
}
public static <T extends Message> GrpcMessage<T> deserialize(
byte[] framedData,
Class<T> clazz) throws IOException {
ByteBuffer buffer = ByteBuffer.wrap(framedData);
// 解析帧头
byte compressedFlag = buffer.get();
int length = buffer.getInt();
// 读取数据
byte[] protoData = new byte[length];
buffer.get(protoData);
// 反序列化Protobuf
T message = parseProtobuf(protoData, clazz);
return new GrpcMessage<>(message, new Metadata());
}
private static <T extends Message> T parseProtobuf(
byte[] data,
Class<T> clazz) throws IOException {
Method parseMethod = clazz.getMethod("parseFrom", byte[].class);
return (T) parseMethod.invoke(null, data);
}
}
}
3.2 HTTP/2 头部帧与数据帧组装
java
复制
下载
// gRPC over HTTP/2 帧组装器
public class GrpcHttp2Framer {
// gRPC特定的HTTP/2头部
private static final List<Header> GRPC_HEADERS = Arrays.asList(
new Header("content-type", "application/grpc"),
new Header("te", "trailers") // 必须的,用于支持尾部头部
);
/**
* 构建gRPC请求帧序列
*/
public List<byte[]> buildRequestFrames(
int streamId,
String methodPath,
byte[] messageData,
Metadata headers) {
List<byte[]> frames = new ArrayList<>();
// 1. 构建HEADERS帧(包含gRPC特定头部)
byte[] headersFrame = buildHeadersFrame(streamId, methodPath, headers);
frames.add(headersFrame);
// 2. 构建DATA帧序列
List<byte[]> dataFrames = buildDataFrames(streamId, messageData);
frames.addAll(dataFrames);
return frames;
}
/**
* 构建HEADERS帧
*/
private byte[] buildHeadersFrame(
int streamId,
String path,
Metadata metadata) {
List<Header> allHeaders = new ArrayList<>();
// 必需头部
allHeaders.add(new Header(":method", "POST"));
allHeaders.add(new Header(":scheme", "http"));
allHeaders.add(new Header(":path", path));
allHeaders.add(new Header(":authority", "api.example.com"));
// gRPC特定头部
allHeaders.addAll(GRPC_HEADERS);
// 自定义元数据头部(需要编码)
for (String key : metadata.keys()) {
String value = metadata.get(key);
allHeaders.add(new Header(key, value));
}
// HPACK压缩头部
byte[] compressedHeaders = compressHeaders(allHeaders);
// 构建帧
return buildFrame(FrameType.HEADERS, streamId,
(byte) 0x4, // END_HEADERS标志
compressedHeaders);
}
/**
* 构建DATA帧序列
*/
private List<byte[]> buildDataFrames(int streamId, byte[] messageData) {
List<byte[]> frames = new ArrayList<>();
// gRPC帧头
byte[] grpcFrameHeader = new byte[5];
ByteBuffer headerBuffer = ByteBuffer.wrap(grpcFrameHeader);
headerBuffer.put((byte) 0); // 未压缩
headerBuffer.putInt(messageData.length);
// 组合完整消息
byte[] fullMessage = new byte[grpcFrameHeader.length + messageData.length];
System.arraycopy(grpcFrameHeader, 0, fullMessage, 0, grpcFrameHeader.length);
System.arraycopy(messageData, 0, fullMessage, grpcFrameHeader.length, messageData.length);
// 分片(如果需要)
int maxFrameSize = 16384; // HTTP/2默认最大帧
int offset = 0;
while (offset < fullMessage.length) {
int chunkSize = Math.min(maxFrameSize, fullMessage.length - offset);
byte[] chunk = new byte[chunkSize];
System.arraycopy(fullMessage, offset, chunk, 0, chunkSize);
byte flags = 0;
if (offset + chunkSize == fullMessage.length) {
flags = 0x1; // END_STREAM标志
}
byte[] frame = buildFrame(FrameType.DATA, streamId, flags, chunk);
frames.add(frame);
offset += chunkSize;
}
return frames;
}
/**
* 构建通用HTTP/2帧
*/
private byte[] buildFrame(FrameType type, int streamId, byte flags, byte[] payload) {
ByteBuffer buffer = ByteBuffer.allocate(9 + payload.length);
// 帧头
buffer.putShort((short) payload.length); // 24位长度的高16位
buffer.put((byte) 0); // 24位长度的低8位
buffer.put((byte) type.getValue());
buffer.put(flags);
buffer.putInt(streamId);
// 负载
buffer.put(payload);
return buffer.array();
}
/**
* HPACK头部压缩
*/
private byte[] compressHeaders(List<Header> headers) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
for (Header header : headers) {
// 简化的HPACK编码
if (isIndexedHeader(header)) {
// 使用索引表示
int index = getHeaderIndex(header);
output.write(0x80 | index); // 最高位为1表示索引
} else {
// 字面表示
output.write(0x40); // 字面头部,不索引
encodeString(output, header.name);
encodeString(output, header.value);
}
}
return output.toByteArray();
}
private void encodeString(ByteArrayOutputStream output, String str) {
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
if (bytes.length < 127) {
output.write(bytes.length);
} else {
output.write(0x80 | (bytes.length & 0x7F));
output.write(bytes.length >> 7);
}
output.write(bytes);
}
}
四、gRPC 流式通信实现
4.1 服务器端流实现
java
复制
下载
// 服务器端流处理器
public class ServerStreamHandler {
private final Http2Connection connection;
private final Map<Integer, StreamContext> activeStreams;
public ServerStreamHandler(Http2Connection connection) {
this.connection = connection;
this.activeStreams = new ConcurrentHashMap<>();
}
/**
* 处理客户端请求并返回服务器端流
*/
public void handleServerStream(int streamId,
MethodDescriptor method,
StreamObserver<Message> responseObserver) {
StreamContext context = new StreamContext(streamId, method, responseObserver);
activeStreams.put(streamId, context);
// 开始流式响应
sendInitialHeaders(streamId);
// 在后台生成流数据
Executors.newSingleThreadExecutor().submit(() -> {
try {
generateStreamData(context);
} catch (Exception e) {
sendError(streamId, e);
} finally {
completeStream(streamId);
}
});
}
/**
* 生成流数据
*/
private void generateStreamData(StreamContext context) {
int count = 0;
while (count < 100 && !context.isCancelled()) {
// 生成响应消息
Message response = context.method.invoke(context.request);
// 发送消息帧
sendMessageFrame(context.streamId, response, false);
count++;
// 流控制检查
if (!checkFlowControl(context.streamId)) {
// 等待窗口更新
waitForWindowUpdate(context.streamId);
}
// 模拟处理延迟
Thread.sleep(100);
}
// 发送结束标志
sendTrailers(context.streamId, Status.OK);
}
/**
* 发送消息帧
*/
private void sendMessageFrame(int streamId, Message message, boolean endStream) {
try {
// 序列化消息
byte[] serialized = message.toByteArray();
// 构建gRPC帧
byte[] grpcFrame = buildGrpcFrame(serialized, false);
// 构建HTTP/2 DATA帧
byte[] http2Frame = buildDataFrame(streamId, grpcFrame, endStream);
// 发送帧
connection.sendFrame(http2Frame);
// 更新流控制状态
updateFlowControl(streamId, grpcFrame.length);
} catch (IOException e) {
throw new RuntimeException("Failed to send message", e);
}
}
/**
* 流控制检查
*/
private boolean checkFlowControl(int streamId) {
StreamContext context = activeStreams.get(streamId);
if (context == null) {
return false;
}
// 检查窗口大小
return connection.getStreamWindowSize(streamId) > 0;
}
/**
* 等待窗口更新
*/
private void waitForWindowUpdate(int streamId) {
StreamContext context = activeStreams.get(streamId);
if (context == null) {
return;
}
synchronized (context) {
try {
// 等待窗口更新通知
context.wait(5000); // 5秒超时
// 检查是否被取消
if (context.isCancelled()) {
throw new CancellationException();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Stream interrupted", e);
}
}
}
/**
* 发送尾部头部(包含gRPC状态)
*/
private void sendTrailers(int streamId, Status status) {
Metadata trailers = new Metadata();
trailers.put(GRPC_STATUS_KEY, status.getCode().value());
trailers.put(GRPC_MESSAGE_KEY, status.getDescription());
byte[] trailersFrame = buildHeadersFrame(streamId, trailers, true);
connection.sendFrame(trailersFrame);
// 标记流结束
activeStreams.remove(streamId);
}
static class StreamContext {
final int streamId;
final MethodDescriptor method;
final StreamObserver<Message> responseObserver;
volatile boolean cancelled = false;
volatile Message request;
StreamContext(int streamId, MethodDescriptor method,
StreamObserver<Message> responseObserver) {
this.streamId = streamId;
this.method = method;
this.responseObserver = responseObserver;
}
synchronized void cancel() {
cancelled = true;
notifyAll();
}
boolean isCancelled() {
return cancelled;
}
}
}
4.2 客户端流实现
java
复制
下载
// 客户端流处理器
public class ClientStreamHandler {
private final Http2Connection connection;
private final int streamId;
private final StreamObserver<Message> requestObserver;
private final StreamObserver<Message> responseObserver;
private volatile boolean completed = false;
public ClientStreamHandler(Http2Connection connection,
MethodDescriptor method,
StreamObserver<Message> responseObserver) {
this.connection = connection;
this.streamId = connection.createStream();
this.responseObserver = responseObserver;
// 发送初始头部
sendInitialHeaders(method);
// 创建请求观察者
this.requestObserver = new StreamObserver<Message>() {
@Override
public void onNext(Message message) {
sendMessage(message, false);
}
@Override
public void onError(Throwable t) {
sendError(t);
completed = true;
}
@Override
public void onCompleted() {
sendComplete();
completed = true;
}
};
}
/**
* 发送消息到服务器
*/
private void sendMessage(Message message, boolean endStream) {
if (completed) {
throw new IllegalStateException("Stream already completed");
}
try {
// 检查流控制
while (!canSendMessage()) {
Thread.sleep(10);
}
// 序列化消息
byte[] serialized = message.toByteArray();
// 构建并发送帧
byte[] grpcFrame = buildGrpcFrame(serialized, false);
byte[] http2Frame = buildDataFrame(streamId, grpcFrame, endStream);
connection.sendFrame(http2Frame);
// 更新流控制
connection.updateSentBytes(streamId, grpcFrame.length);
} catch (Exception e) {
throw new RuntimeException("Failed to send message", e);
}
}
/**
* 检查是否可以发送消息
*/
private boolean canSendMessage() {
// 检查连接级别窗口
int connectionWindow = connection.getConnectionWindowSize();
if (connectionWindow <= 0) {
return false;
}
// 检查流级别窗口
int streamWindow = connection.getStreamWindowSize(streamId);
return streamWindow > 0;
}
/**
* 处理服务器响应
*/
public void onHeadersReceived(Map<String, String> headers) {
// 处理响应头部
String status = headers.get(":status");
if (!"200".equals(status)) {
responseObserver.onError(new RuntimeException("HTTP error: " + status));
}
}
public void onDataReceived(byte[] data) {
try {
// 解析gRPC帧
GrpcFrame frame = parseGrpcFrame(data);
// 如果是压缩的,解压缩
byte[] messageData = frame.compressed ?
decompress(frame.payload) : frame.payload;
// 反序列化消息
Message message = parseMessage(messageData);
// 传递给观察者
responseObserver.onNext(message);
// 发送窗口更新
connection.sendWindowUpdate(streamId, data.length);
} catch (Exception e) {
responseObserver.onError(e);
}
}
public void onTrailersReceived(Map<String, String> trailers) {
// 检查gRPC状态
String grpcStatus = trailers.get("grpc-status");
if (!"0".equals(grpcStatus)) {
String grpcMessage = trailers.get("grpc-message");
responseObserver.onError(new RuntimeException(
"gRPC error: " + grpcStatus + " - " + grpcMessage));
} else {
responseObserver.onCompleted();
}
completed = true;
}
}
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
五、流量整形与优先级
5.1 HTTP/2 优先级处理
java
复制
下载
// HTTP/2 优先级调度器
public class Http2PriorityScheduler {
// 优先级树节点
static class PriorityNode {
final int streamId;
final int weight; // 权重 (1-256)
int dependency; // 依赖的流ID
boolean exclusive; // 是否独占依赖
int allocatedBytes; // 已分配字节数
PriorityNode parent;
List<PriorityNode> children = new ArrayList<>();
PriorityNode(int streamId, int weight, int dependency, boolean exclusive) {
this.streamId = streamId;
this.weight = weight;
this.dependency = dependency;
this.exclusive = exclusive;
}
int getEffectiveWeight() {
if (parent == null) {
return weight;
}
return weight * parent.getEffectiveWeight() / 100;
}
}
private final Map<Integer, PriorityNode> nodes = new HashMap<>();
private final PriorityQueue<PriorityNode> readyQueue;
public Http2PriorityScheduler() {
// 初始化根节点 (stream 0)
PriorityNode root = new PriorityNode(0, 256, 0, false);
nodes.put(0, root);
// 基于有效权重的优先级队列
readyQueue = new PriorityQueue<>(
Comparator.comparingInt(PriorityNode::getEffectiveWeight).reversed()
);
}
/**
* 处理PRIORITY帧
*/
public void processPriorityFrame(int streamId, int dependency,
int weight, boolean exclusive) {
PriorityNode node = nodes.get(streamId);
if (node == null) {
node = new PriorityNode(streamId, weight, dependency, exclusive);
nodes.put(streamId, node);
} else {
node.weight = weight;
node.dependency = dependency;
node.exclusive = exclusive;
}
// 更新依赖关系
updateDependency(node);
// 重新计算优先级
recalculatePriorities();
}
/**
* 更新依赖关系
*/
private void updateDependency(PriorityNode node) {
// 从原父节点移除
if (node.parent != null) {
node.parent.children.remove(node);
}
// 设置新父节点
PriorityNode parent = nodes.get(node.dependency);
if (parent == null) {
parent = nodes.get(0); // 默认依赖根节点
}
if (node.exclusive) {
// 独占依赖:新节点成为父节点的唯一子节点
List<PriorityNode> oldChildren = new ArrayList<>(parent.children);
parent.children.clear();
parent.children.add(node);
node.children.addAll(oldChildren);
} else {
// 共享依赖:添加到子节点列表
parent.children.add(node);
}
node.parent = parent;
}
/**
* 调度发送数据
*/
public List<Integer> scheduleSend(int availableWindow) {
List<Integer> scheduledStreams = new ArrayList<>();
int remaining = availableWindow;
// 填充就绪队列
fillReadyQueue();
while (remaining > 0 && !readyQueue.isEmpty()) {
PriorityNode node = readyQueue.poll();
// 计算分配量(基于权重)
int allocation = calculateAllocation(node, remaining);
if (allocation > 0) {
node.allocatedBytes += allocation;
scheduledStreams.add(node.streamId);
remaining -= allocation;
// 如果还有剩余额度,重新加入队列
if (allocation < remaining) {
readyQueue.add(node);
}
}
}
return scheduledStreams;
}
/**
* 计算分配量
*/
private int calculateAllocation(PriorityNode node, int totalAvailable) {
// 基于权重和父节点已分配量的比例
double weightRatio = (double) node.weight /
node.parent.children.stream().mapToInt(n -> n.weight).sum();
int parentAllocated = node.parent.allocatedBytes;
int fairShare = (int) (parentAllocated * weightRatio);
// 减去已经分配的量
int allocation = Math.max(0, fairShare - node.allocatedBytes);
// 不超过可用窗口
allocation = Math.min(allocation, totalAvailable);
return allocation;
}
/**
* 填充就绪队列
*/
private void fillReadyQueue() {
readyQueue.clear();
// 遍历所有有数据的流
for (PriorityNode node : nodes.values()) {
if (node.streamId != 0 && hasDataToSend(node.streamId)) {
readyQueue.add(node);
}
}
}
}
5.2 gRPC 特定流量控制
java
复制
下载
// gRPC特定的流量控制器
public class GrpcFlowController {
// gRPC流控制设置
private static final int DEFAULT_INITIAL_WINDOW_SIZE = 65535;
private static final int DEFAULT_MAX_CONCURRENT_STREAMS = 100;
private static final int DEFAULT_MAX_FRAME_SIZE = 16384;
private final Http2FlowControl http2FlowControl;
private final Map<Integer, GrpcStreamContext> grpcStreams;
private final int initialWindowSize;
public GrpcFlowController() {
this(DEFAULT_INITIAL_WINDOW_SIZE);
}
public GrpcFlowController(int initialWindowSize) {
this.initialWindowSize = initialWindowSize;
this.http2FlowControl = new Http2FlowControl();
this.grpcStreams = new ConcurrentHashMap<>();
}
/**
* gRPC特定的流创建
*/
public int createGrpcStream(String methodName, Metadata headers) {
// 创建HTTP/2流
int streamId = http2FlowControl.createStream();
// 设置gRPC特定的初始窗口
http2FlowControl.setInitialWindowSize(streamId, initialWindowSize);
// 创建gRPC流上下文
GrpcStreamContext context = new GrpcStreamContext(streamId, methodName);
grpcStreams.put(streamId, context);
// 应用方法特定的流控制策略
applyMethodSpecificFlowControl(context, methodName);
return streamId;
}
/**
* 发送gRPC消息(考虑消息分帧)
*/
public void sendGrpcMessage(int streamId, byte[] messageData)
throws FlowControlException {
GrpcStreamContext context = grpcStreams.get(streamId);
if (context == null) {
throw new IllegalArgumentException("Stream not found: " + streamId);
}
// 1. 添加gRPC帧头
byte[] framedMessage = frameGrpcMessage(messageData, false);
// 2. 检查流控制
if (!canSendGrpcMessage(streamId, framedMessage.length)) {
throw new FlowControlException("Flow control limit exceeded");
}
// 3. 分帧发送(如果超过HTTP/2最大帧大小)
List<byte[]> frames = splitIntoFrames(framedMessage);
for (byte[] frame : frames) {
sendDataFrame(streamId, frame, false);
}
// 4. 更新统计
context.sentMessages++;
context.sentBytes += framedMessage.length;
}
/**
* 接收gRPC消息
*/
public byte[] receiveGrpcMessage(int streamId, List<byte[]> frames)
throws IOException {
// 1. 重组帧
byte[] completeMessage = reassembleFrames(frames);
// 2. 解析gRPC帧头
GrpcFrameHeader header = parseGrpcFrameHeader(completeMessage);
// 3. 提取消息数据
byte[] messageData = extractMessageData(completeMessage, header);
// 4. 如果是压缩的,解压缩
if (header.isCompressed()) {
messageData = decompress(messageData);
}
// 5. 更新流控制窗口
updateWindowAfterReceiving(streamId, completeMessage.length);
// 6. 更新统计
GrpcStreamContext context = grpcStreams.get(streamId);
if (context != null) {
context.receivedMessages++;
context.receivedBytes += completeMessage.length;
}
return messageData;
}
/**
* 动态调整窗口大小
*/
public void adjustWindowDynamically(int streamId) {
GrpcStreamContext context = grpcStreams.get(streamId);
if (context == null) {
return;
}
// 基于历史流量模式调整窗口
double messagesPerSecond = calculateMessageRate(context);
int avgMessageSize = calculateAverageMessageSize(context);
// 计算理想窗口大小(2秒的流量)
int idealWindowSize = (int) (messagesPerSecond * avgMessageSize * 2);
// 限制在合理范围内
idealWindowSize = Math.max(initialWindowSize,
Math.min(idealWindowSize, initialWindowSize * 10));
// 如果调整幅度超过10%,发送窗口更新
int currentWindow = http2FlowControl.getStreamWindowSize(streamId);
double changeRatio = (double) Math.abs(idealWindowSize - currentWindow) / currentWindow;
if (changeRatio > 0.1) {
int increment = idealWindowSize - currentWindow;
http2FlowControl.sendWindowUpdate(streamId, increment);
}
}
/**
* 应用方法特定的流控制策略
*/
private void applyMethodSpecificFlowControl(GrpcStreamContext context,
String methodName) {
// 根据方法类型设置不同的流控制策略
if (methodName.contains("Upload") || methodName.contains("Stream")) {
// 上传或流式方法:使用较小的初始窗口,逐步增加
context.flowControlStrategy = FlowControlStrategy.GRADUAL;
context.maxWindowSize = initialWindowSize * 2;
} else if (methodName.contains("Download") || methodName.contains("Get")) {
// 下载方法:使用较大的窗口
context.flowControlStrategy = FlowControlStrategy.AGGRESSIVE;
context.maxWindowSize = initialWindowSize * 5;
} else {
// 普通RPC:使用默认设置
context.flowControlStrategy = FlowControlStrategy.CONSERVATIVE;
context.maxWindowSize = initialWindowSize;
}
}
static class GrpcStreamContext {
final int streamId;
final String methodName;
FlowControlStrategy flowControlStrategy;
int maxWindowSize;
int sentMessages;
int sentBytes;
int receivedMessages;
int receivedBytes;
long createdTime;
GrpcStreamContext(int streamId, String methodName) {
this.streamId = streamId;
this.methodName = methodName;
this.createdTime = System.currentTimeMillis();
}
}
enum FlowControlStrategy {
CONSERVATIVE, // 保守:小窗口,慢启动
GRADUAL, // 渐进:中等窗口,线性增长
AGGRESSIVE // 激进:大窗口,快速响应
}
}
六、错误处理与重试
6.1 gRPC 错误码映射
java
复制
下载
// gRPC错误处理
public class GrpcErrorHandler {
// gRPC状态码到HTTP/2错误码的映射
private static final Map<Status.Code, ErrorCode> GRPC_TO_HTTP2_ERROR_MAP =
new EnumMap<>(Status.Code.class);
static {
GRPC_TO_HTTP2_ERROR_MAP.put(Status.Code.CANCELLED, ErrorCode.CANCEL);
GRPC_TO_HTTP2_ERROR_MAP.put(Status.Code.DEADLINE_EXCEEDED, ErrorCode.CANCEL);
GRPC_TO_HTTP2_ERROR_MAP.put(Status.Code.INVALID_ARGUMENT, ErrorCode.PROTOCOL_ERROR);
GRPC_TO_HTTP2_ERROR_MAP.put(Status.Code.NOT_FOUND, ErrorCode.REFUSED_STREAM);
GRPC_TO_HTTP2_ERROR_MAP.put(Status.Code.ALREADY_EXISTS, ErrorCode.REFUSED_STREAM);
GRPC_TO_HTTP2_ERROR_MAP.put(Status.Code.PERMISSION_DENIED, ErrorCode.REFUSED_STREAM);
GRPC_TO_HTTP2_ERROR_MAP.put(Status.Code.RESOURCE_EXHAUSTED, ErrorCode.ENHANCE_YOUR_CALM);
GRPC_TO_HTTP2_ERROR_MAP.put(Status.Code.FAILED_PRECONDITION, ErrorCode.REFUSED_STREAM);
GRPC_TO_HTTP2_ERROR_MAP.put(Status.Code.ABORTED, ErrorCode.INTERNAL_ERROR);
GRPC_TO_HTTP2_ERROR_MAP.put(Status.Code.OUT_OF_RANGE, ErrorCode.PROTOCOL_ERROR);
GRPC_TO_HTTP2_ERROR_MAP.put(Status.Code.UNIMPLEMENTED, ErrorCode.PROTOCOL_ERROR);
GRPC_TO_HTTP2_ERROR_MAP.put(Status.Code.INTERNAL, ErrorCode.INTERNAL_ERROR);
GRPC_TO_HTTP2_ERROR_MAP.put(Status.Code.UNAVAILABLE, ErrorCode.REFUSED_STREAM);
GRPC_TO_HTTP2_ERROR_MAP.put(Status.Code.DATA_LOSS, ErrorCode.INTERNAL_ERROR);
GRPC_TO_HTTP2_ERROR_MAP.put(Status.Code.UNAUTHENTICATED, ErrorCode.REFUSED_STREAM);
}
/**
* 处理gRPC错误
*/
public void handleGrpcError(int streamId, Status status, Metadata trailers) {
// 1. 发送包含错误状态的尾部头部
sendErrorTrailers(streamId, status, trailers);
// 2. 如果错误严重,发送RST_STREAM帧
if (shouldResetStream(status)) {
ErrorCode errorCode = mapGrpcToHttp2Error(status.getCode());
sendRstStream(streamId, errorCode);
}
// 3. 记录错误统计
recordErrorStatistics(status);
}
/**
* 发送错误尾部头部
*/
private void sendErrorTrailers(int streamId, Status status, Metadata trailers) {
// 添加gRPC状态头部
Metadata errorTrailers = new Metadata();
errorTrailers.merge(trailers);
errorTrailers.put(GrpcUtil.STATUS_KEY, status.getCode().value());
errorTrailers.put(GrpcUtil.MESSAGE_KEY, status.getDescription());
// 发送HEADERS帧(END_STREAM标志)
sendHeadersFrame(streamId, errorTrailers, true);
}
/**
* 发送RST_STREAM帧
*/
private void sendRstStream(int streamId, ErrorCode errorCode) {
ByteBuffer buffer = ByteBuffer.allocate(13); // 9字节帧头 + 4字节错误码
// 帧头
buffer.putInt(4); // 长度: 4字节
buffer.put((byte) 0x3); // 类型: RST_STREAM
buffer.put((byte) 0x0); // 标志位
buffer.putInt(streamId); // 流ID
// 错误码
buffer.putInt(errorCode.getCode());
sendFrame(buffer.array());
}
/**
* 判断是否需要重置流
*/
private boolean shouldResetStream(Status status) {
switch (status.getCode()) {
case CANCELLED:
case DEADLINE_EXCEEDED:
case RESOURCE_EXHAUSTED:
case UNAVAILABLE:
return true;
default:
return false;
}
}
/**
* gRPC错误码到HTTP/2错误码的映射
*/
private ErrorCode mapGrpcToHttp2Error(Status.Code grpcCode) {
return GRPC_TO_HTTP2_ERROR_MAP.getOrDefault(grpcCode, ErrorCode.INTERNAL_ERROR);
}
/**
* 流控制错误处理
*/
public void handleFlowControlError(int streamId, FlowControlException e) {
// 1. 发送WINDOW_UPDATE帧来恢复
sendWindowUpdate(streamId, 65536); // 增加64KB窗口
// 2. 如果是持续错误,减少窗口
if (isPersistentFlowControlError(streamId)) {
adjustFlowControlWindow(streamId, -0.5); // 减少50%
}
// 3. 记录日志
logFlowControlError(streamId, e);
}
}
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
七、性能监控与调优
7.1 gRPC 性能监控
java
复制
下载
// gRPC性能监控器
@RestController
public class GrpcPerformanceMonitor {
@Autowired
private GrpcMetricsCollector metricsCollector;
@GetMapping("/api/grpc/metrics")
public GrpcMetrics getMetrics() {
return metricsCollector.collectAllMetrics();
}
@GetMapping("/api/grpc/streams/{id}")
public StreamMetrics getStreamMetrics(@PathVariable int streamId) {
return metricsCollector.getStreamMetrics(streamId);
}
@GetMapping("/api/grpc/flow-control")
public FlowControlMetrics getFlowControlMetrics() {
return metricsCollector.getFlowControlMetrics();
}
}
// gRPC指标收集器
@Component
public class GrpcMetricsCollector {
// 连接级别指标
private final AtomicInteger activeStreams = new AtomicInteger(0);
private final AtomicLong totalRequests = new AtomicLong(0);
private final AtomicLong totalBytesSent = new AtomicLong(0);
private final AtomicLong totalBytesReceived = new AtomicLong(0);
// 流级别指标
private final Map<Integer, StreamMetrics> streamMetrics =
new ConcurrentHashMap<>();
// 延迟直方图
private final Histogram latencyHistogram = new Histogram(
TimeUnit.MILLISECONDS.toNanos(1), // 1ms
TimeUnit.SECONDS.toNanos(10), // 10s
5 // 精度
);
/**
* 记录请求开始
*/
public void recordRequestStart(int streamId, String method) {
activeStreams.incrementAndGet();
totalRequests.incrementAndGet();
StreamMetrics metrics = new StreamMetrics(streamId, method);
metrics.startTime = System.nanoTime();
streamMetrics.put(streamId, metrics);
}
/**
* 记录请求完成
*/
public void recordRequestEnd(int streamId, Status status) {
activeStreams.decrementAndGet();
StreamMetrics metrics = streamMetrics.get(streamId);
if (metrics != null) {
metrics.endTime = System.nanoTime();
metrics.duration = metrics.endTime - metrics.startTime;
metrics.status = status;
// 记录延迟
latencyHistogram.record(metrics.duration);
// 保留一段时间后清理
scheduleCleanup(streamId);
}
}
/**
* 记录流量数据
*/
public void recordTraffic(int streamId, int bytesSent, int bytesReceived) {
totalBytesSent.addAndGet(bytesSent);
totalBytesReceived.addAndGet(bytesReceived);
StreamMetrics metrics = streamMetrics.get(streamId);
if (metrics != null) {
metrics.bytesSent += bytesSent;
metrics.bytesReceived += bytesReceived;
}
}
/**
* 收集所有指标
*/
public GrpcMetrics collectAllMetrics() {
GrpcMetrics metrics = new GrpcMetrics();
metrics.setActiveStreams(activeStreams.get());
metrics.setTotalRequests(totalRequests.get());
metrics.setTotalBytesSent(totalBytesSent.get());
metrics.setTotalBytesReceived(totalBytesReceived.get());
metrics.setAverageLatency(latencyHistogram.getMean());
metrics.setP50Latency(latencyHistogram.getValue(0.5));
metrics.setP95Latency(latencyHistogram.getValue(0.95));
metrics.setP99Latency(latencyHistogram.getValue(0.99));
// 计算成功率
long successfulStreams = streamMetrics.values().stream()
.filter(m -> m.status != null && m.status.isOk())
.count();
double successRate = streamMetrics.isEmpty() ? 0 :
(double) successfulStreams / streamMetrics.size();
metrics.setSuccessRate(successRate);
return metrics;
}
/**
* 流控制指标
*/
public FlowControlMetrics getFlowControlMetrics() {
FlowControlMetrics metrics = new FlowControlMetrics();
// 计算平均窗口使用率
double avgWindowUsage = streamMetrics.values().stream()
.mapToDouble(m -> m.getWindowUsage())
.average()
.orElse(0);
metrics.setAverageWindowUsage(avgWindowUsage);
// 识别热点流
List<Integer> hotStreams = streamMetrics.entrySet().stream()
.filter(e -> e.getValue().bytesSent > 1024 * 1024) // 超过1MB
.map(Map.Entry::getKey)
.collect(Collectors.toList());
metrics.setHotStreams(hotStreams);
return metrics;
}
static class StreamMetrics {
final int streamId;
final String method;
long startTime;
long endTime;
long duration;
Status status;
long bytesSent;
long bytesReceived;
int windowSize;
StreamMetrics(int streamId, String method) {
this.streamId = streamId;
this.method = method;
}
double getWindowUsage() {
return windowSize == 0 ? 0 : (double) bytesSent / windowSize;
}
}
}
八、总结
8.1 核心要点总结
-
HTTP/2 流控制:
-
基于信用的流控制机制
-
连接级别和流级别窗口控制
-
WINDOW_UPDATE帧动态调整窗口
-
-
gRPC 消息分帧:
-
5字节gRPC帧头(压缩标志 + 长度)
-
支持消息压缩(gzip)
-
自动分片以适应HTTP/2帧大小限制
-
-
性能优化:
-
优先级调度确保关键流量
-
动态窗口调整适应网络条件
-
连接复用减少握手开销
-
-
错误处理:
-
gRPC状态码到HTTP/2错误码的映射
-
优雅的连接终止(GOAWAY)
-
智能重试机制
-
8.2 最佳实践
-
窗口大小调优:
java
复制
下载
// 根据网络延迟调整初始窗口 int rtt = measureRTT(); int initialWindow = Math.max(65535, rtt * bandwidth / 8); -
消息大小控制:
java
复制
下载
// 大消息自动分块 if (messageSize > 1_000_000) { // 1MB enableCompression(); useStreamingRPC(); } -
监控告警:
java
复制
下载
// 关键指标监控 if (windowUsage > 0.9) { alert("Flow control window nearly exhausted"); } if (errorRate > 0.01) { alert("High error rate detected"); }
gRPC的HTTP/2实现提供了高效的流控制和消息分帧机制,通过理解这些底层原理,可以更好地优化gRPC应用的性能和可靠性。