* Add comprehensive in-process test framework Add unit tests for exchange layer, E2E integration tests, security tests (race + fuzz), and Go benchmark tests replacing the old shell-script-based bench programs. All tests run in-process without requiring an external frontier process. Suppress klog and armorigo log noise in all test files. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Update build configs, Dockerfiles and dependencies Update Makefile with new targets, consolidate frontier_all.yaml config, bump base image versions in Dockerfiles, and update go.mod/go.sum. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Revert etc/frontier_all.yaml to previous version Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
61 KiB
Frontier 技术原理
目录
客户端认证
概述
Frontier 支持两种类型的客户端连接:
- Service(服务端):后端服务,负责处理业务逻辑
- Edge(边缘端):客户端,通常是终端设备或用户应用
Service 客户端认证
Service 客户端在连接时通过 Meta 结构体传递认证信息:
type Meta struct {
Service string `json:"service"` // 服务名称
Topics []string `json:"topics"` // 订阅的主题列表
}
连接流程:
- Service 客户端建立 TCP 连接
- 通过 Geminio 协议握手,在
Meta中携带服务名称和订阅的 Topics - Frontier 解析
Meta并分配ServiceID - 注册服务到内存缓存和数据库
- 注册服务声明的 Topics 和 RPCs
关键代码位置:
pkg/frontier/servicebound/service_manager.go:handleConn()- 处理连接pkg/frontier/servicebound/service_manager.go:online()- 上线处理pkg/frontier/servicebound/service_onoff.go:GetClientID()- ID 分配
Edge 客户端认证
Edge 客户端连接时通过 Meta 传递元数据(通常是用户标识等信息):
连接流程:
- Edge 客户端建立 TCP 连接
- 通过 Geminio 协议握手,携带
Meta信息 - Frontier 通过 Exchange 向 Service 请求分配
EdgeID- 如果 Service 不在线,根据配置决定是否自动分配 ID
- 注册 Edge 到内存缓存和数据库
EdgeID 分配机制:
func (em *edgeManager) GetClientID(_ uint64, meta []byte) (uint64, error) {
// 优先从 Exchange 获取 EdgeID(通过 Service)
if em.exchange != nil {
edgeID, err := em.exchange.GetEdgeID(meta)
if err == nil {
return edgeID, nil
}
}
// 如果 Service 不在线,根据配置决定是否自动分配
if em.conf.Edgebound.EdgeIDAllocWhenNoIDServiceOn {
return em.idFactory.GetID(), nil
}
return 0, err
}
关键代码位置:
pkg/frontier/edgebound/edge_manager.go:handleConn()- 处理连接pkg/frontier/edgebound/edge_manager.go:online()- 上线处理pkg/frontier/edgebound/edge_onoff.go:GetClientID()- ID 分配
认证特点
- 基于 Geminio 协议:使用 Geminio 作为底层通信协议
- Meta 信息传递:通过连接时的 Meta 字段传递认证和配置信息
- ID 分配策略:
- Service:支持指定 ID 或自动分配
- Edge:优先从 Service 获取,支持降级自动分配
- 连接复用:同一 ServiceID/EdgeID 的旧连接会被新连接踢下线
客户端上下线
Service 上线流程
时序图:
Service Client Servicebound Manager
| |
|---- TCP Connect -------->|
| |
|---- Geminio Handshake -->|
| |
|<--- Parse Meta ----------|
| |
|---- Allocate ServiceID ->|
| |
|---- Register Service -->|
| |
|---- Register Topics ----->|
| |
|---- Register RPCs ------>|
| |
|---- Add to MQM --------->|
| |
|---- ConnOnline Event --->|
| |
|---- Forward Setup ------->|
详细步骤:
-
连接建立 (
handleConn)- 接受 TCP 连接
- 创建 Geminio End
- 解析 Meta 信息
-
注册 Topics (
remoteReceiveClaim)- 将服务声明的 Topics 注册到数据库
- 创建
ServiceTopic记录
-
添加到 MQM (Message Queue Manager)
- 将 End 添加到消息队列管理器
- 用于后续消息路由
-
上线处理 (
online)- 检查是否存在旧连接,如果存在则关闭
- 添加到内存缓存
services[serviceID] = end - 创建数据库记录
Service - 触发
ConnOnline事件
-
设置转发 (
forward)- 调用 Exchange 设置 Service -> Edge 的转发
关键代码:
func (sm *serviceManager) handleConn(conn net.Conn) error {
// 创建 Geminio End
end, err := server.NewEndWithConn(conn, opt)
// 解析 Meta
meta := &apis.Meta{}
json.Unmarshal(end.Meta(), meta)
// 注册 Topics
sm.remoteReceiveClaim(end.ClientID(), meta.Topics)
// 添加到 MQM
sm.mqm.AddMQByEnd(meta.Topics, end)
// 上线处理
sm.online(end, meta)
// 设置转发
sm.forward(meta, end)
}
Service 下线流程
触发时机:
- 客户端主动关闭连接
- 网络断开
- 连接超时
处理步骤:
-
ConnOffline 事件
- Geminio 协议层检测到连接断开
- 触发
ConnOffline回调
-
清理缓存 (
offline)- 从内存缓存
services中删除 - 验证地址匹配,避免误删
- 从内存缓存
-
清理数据库
- 删除
Service记录 - 删除
ServiceRPCs记录 - 删除
ServiceTopics记录
- 删除
-
清理 MQM
- 从消息队列管理器中移除
-
通知其他组件
- 触发
ServiceOffline事件 - 通知 Informer
- 触发
关键代码:
func (sm *serviceManager) ConnOffline(d delegate.ConnDescriber) error {
serviceID := d.ClientID()
addr := d.RemoteAddr()
// 清理缓存和数据库
err := sm.offline(serviceID, addr)
// 通知其他组件
if sm.informer != nil {
sm.informer.ServiceOffline(serviceID, meta, addr)
}
return nil
}
Edge 上线流程
时序图:
Edge Client Edgebound Manager Exchange Service
| | | |
|---- TCP Connect ---->| | |
| | | |
|---- Geminio -------->| | |
| Handshake | | |
| | | |
|<--- Get EdgeID ------| | |
| |---- GetEdgeID ------->| |
| | |---- RPC ------->|
| | |<--- EdgeID -----|
| |<--- EdgeID ----------| |
|<--- EdgeID ----------| | |
| | | |
| |---- Register Edge --->| |
| |---- ConnOnline ------>| |
| |---- Forward Setup --->| |
详细步骤:
-
连接建立 (
handleConn)- 接受 TCP 连接
- 创建 Geminio End
-
EdgeID 分配 (
GetClientID)- 优先通过 Exchange 向 Service 请求 EdgeID
- 如果 Service 不在线,根据配置决定是否自动分配
-
上线处理 (
online)- 检查是否存在旧连接,如果存在则关闭
- 添加到内存缓存
edges[edgeID] = end - 创建数据库记录
Edge - 触发
ConnOnline事件 - 通知 Exchange Edge 上线
-
设置转发 (
forward)- 调用 Exchange 设置 Edge -> Service 的转发
关键代码:
func (em *edgeManager) handleConn(conn net.Conn) error {
// 创建 Geminio End
end, err := server.NewEndWithConn(conn, opt)
// 上线处理(内部会分配 EdgeID)
em.online(end)
// 设置转发
em.forward(end)
}
Edge 下线流程
处理步骤:
-
ConnOffline 事件
- Geminio 协议层检测到连接断开
-
清理缓存 (
offline)- 从内存缓存
edges中删除 - 验证地址匹配
- 从内存缓存
-
清理数据库
- 删除
Edge记录 - 删除
EdgeRPCs记录
- 删除
-
通知其他组件
- 触发
EdgeOffline事件 - 通知 Exchange Edge 下线
- 通知 Informer
- 触发
关键代码:
func (em *edgeManager) ConnOffline(d delegate.ConnDescriber) error {
edgeID := d.ClientID()
meta := d.Meta()
addr := d.RemoteAddr()
// 清理缓存和数据库
err := em.offline(edgeID, meta, addr)
return nil
}
上下线特性
-
并发安全
- 使用
sync.RWMutex保护内存缓存 - 使用
synchub.SyncHub同步旧连接清理
- 使用
-
幂等性
- 通过地址匹配避免误删
- 支持同一 ID 的重复连接(会踢掉旧连接)
-
数据一致性
- 内存缓存和数据库同步更新
- 使用事务保证数据一致性(TODO)
-
事件通知
- 通过 Informer 通知其他组件
- 支持自定义事件处理
Exchange 原理
概述
Exchange 是 Frontier 的核心组件,负责在 Service 和 Edge 之间转发消息、RPC 调用和 Stream。它实现了 Service 和 Edge 的解耦,使得两者可以独立扩展。
架构设计
Exchange
|
+--------------+--------------+
| |
Edgebound Servicebound
| |
Edge Clients Service Clients
核心组件:
Edgebound: 管理所有 Edge 连接Servicebound: 管理所有 Service 连接MQM: 消息队列管理器,负责消息路由Exchange: 转发引擎
Service -> Edge 转发
消息转发 (Message Forwarding)
流程:
- Service 发送消息,在
Custom字段末尾携带目标EdgeID(8字节) - Exchange 截取
EdgeID并查找对应的 Edge - 如果 Edge 在线,将消息转发给 Edge
- 如果 Edge 不在线,返回错误
时序图:
Service Exchange Edgebound Edge
| | | |
|--Publish------->| | |
| | | |
| |--GetEdgeByID--->| |
| | | |
| |<--Edge End------| |
| | | |
| |-----------------|--Publish---->|
| | | |
| |<----------------|--Done--------|
| | | |
|<--Done----------| | |
| | | |
说明: Publish Message 携带 Custom+EdgeID
Exchange 提取 EdgeID 后查找 Edge
如果 Edge 在线,转发消息并返回 Done
如果 Edge 不在线,返回 Error(ErrEdgeNotOnline)
或者(Edge不在线):
Service Exchange Edgebound Edge
| | | |
|--Publish------->| | |
| | | |
| |--GetEdgeByID--->| |
| | | |
| |<--nil-----------| |
| | | |
|<--Error----------| | |
关键代码:
func (ex *exchange) forwardMessageToEdge(end geminio.End) {
serviceID := end.ClientID()
go func() {
for {
msg, err := end.Receive(context.TODO())
if err != nil {
return
}
// 从 Custom 末尾提取 EdgeID
custom := msg.Custom()
edgeID := binary.BigEndian.Uint64(custom[len(custom)-8:])
msg.SetCustom(custom[:len(custom)-8])
// 查找 Edge
edge := ex.Edgebound.GetEdgeByID(edgeID)
if edge == nil {
msg.Error(apis.ErrEdgeNotOnline)
return
}
// 转发消息
mopt := options.NewMessage()
mopt.SetCustom(msg.Custom())
mopt.SetTopic(msg.Topic())
newmsg := edge.NewMessage(msg.Data(), mopt)
edge.Publish(context.TODO(), newmsg, popt)
msg.Done()
}
}()
}
RPC 转发 (RPC Forwarding)
流程:
- Service 发起 RPC 调用,在
Custom字段末尾携带目标EdgeID - Exchange 拦截 RPC(通过 Hijack)
- 提取
EdgeID并查找 Edge - 转发 RPC 调用到 Edge
- 将响应返回给 Service,并在
Custom中携带EdgeID
时序图:
Service Exchange Edgebound Edge
| | | |
|--Call RPC------>| | |
| | | |
| |--GetEdgeByID--->| |
| | | |
| |<--Edge End------| |
| | | |
| |-----------------|--Call RPC-->|
| | | |
| |<----------------|--Response----|
| | | |
|<--Response-------| | |
| | | |
说明: Call RPC 携带 Custom+EdgeID
Exchange Hijack 拦截并提取 EdgeID
如果 Edge 在线,转发 RPC 并返回 Response(Data+Custom+EdgeID)
如果 Edge 不在线,返回 Error(ErrEdgeNotOnline)
或者(Edge不在线):
Service Exchange Edgebound Edge
| | | |
|--Call RPC------>| | |
| | | |
| |--GetEdgeByID--->| |
| | | |
| |<--nil-----------| |
| | | |
|<--Error----------| | |
关键代码:
func (ex *exchange) forwardRPCToEdge(end geminio.End) {
end.Hijack(func(ctx context.Context, method string, r1 geminio.Request, r2 geminio.Response) {
serviceID := end.ClientID()
// 提取 EdgeID
custom := r1.Custom()
edgeID := binary.BigEndian.Uint64(custom[len(custom)-8:])
r1.SetCustom(custom[:len(custom)-8])
// 查找 Edge
edge := ex.Edgebound.GetEdgeByID(edgeID)
if edge == nil {
r2.SetError(apis.ErrEdgeNotOnline)
return
}
// 转发 RPC
r3 := edge.NewRequest(r1.Data(), ropt)
r4, err := edge.Call(ctx, method, r3, copt)
// 返回响应,携带 EdgeID
tail := make([]byte, 8)
binary.BigEndian.PutUint64(tail, edgeID)
r2.SetCustom(append(r4.Custom(), tail...))
r2.SetData(r4.Data())
})
}
Edge -> Service 转发
消息转发 (Message Forwarding)
流程:
- Edge 发送消息到指定 Topic
- Exchange 接收消息
- 通过 MQM 将消息投递到订阅该 Topic 的 Service
- MQM 负责消息路由和负载均衡
时序图:
Edge Exchange MQM Servicebound Service
| | | | |
|--Publish---->| | | |
| | | | |
| |--Produce---->| | |
| | | | |
| | |--GetServices>| |
| | | | |
| | |<--ServiceList| |
| | | | |
| | |--Deliver---->| |
| | | | |
| | |<--Done-------| |
| | | | |
| |<--Success----| | |
| | | | |
|<--Done-------| | | |
说明: Edge 发送消息到指定 Topic
Exchange 通过 MQM 投递消息
MQM 查找订阅 Topic 的 Services 并负载均衡投递
关键代码:
func (ex *exchange) forwardMessageToService(end geminio.End) {
edgeID := end.ClientID()
go func() {
for {
msg, err := end.Receive(context.TODO())
if err != nil {
return
}
topic := msg.Topic()
// 通过 MQM 投递消息
err = ex.MQM.Produce(topic, msg.Data(),
apis.WithOrigin(msg),
apis.WithEdgeID(edgeID),
apis.WithAddr(end.RemoteAddr()))
if err != nil {
msg.Error(err)
continue
}
msg.Done()
}
}()
}
RPC 转发 (RPC Forwarding)
流程:
- Edge 发起 RPC 调用,指定 RPC 方法名
- Exchange 拦截 RPC
- 根据 RPC 方法名查找提供该方法的 Service(可能有多个)
- 使用哈希算法选择目标 Service(负载均衡)
- 转发 RPC 调用,在
Custom中携带EdgeID - 将响应返回给 Edge
时序图:
Edge Exchange Servicebound Service
| | | |
|--Call RPC-->| | |
| | | |
| |--GetServicesByRPC| |
| | | |
| |<--ServiceList----| |
| | | |
| |------------------|--Call RPC-->|
| | | |
| |<-----------------|--Response---|
| | | |
|<--Response----| | |
| | | |
说明: Edge 发起 RPC 调用指定 method
Exchange Hijack 拦截并查找提供该 RPC 的 Services
使用哈希算法 Hash(edgeID, addr) 选择 Service
在 Custom 中追加 EdgeID 后转发 RPC
返回 Response(Data+Custom)
负载均衡策略:
// 使用哈希算法选择 Service
index := misc.Hash(ex.conf.Exchange.HashBy, len(svcs), edgeID, addr)
svc := svcs[index]
关键代码:
func (ex *exchange) forwardRPCToService(end geminio.End) {
edgeID := end.ClientID()
addr := end.RemoteAddr()
end.Hijack(func(ctx context.Context, method string, r1 geminio.Request, r2 geminio.Response) {
// 查找提供该 RPC 的 Services
svcs, err := ex.Servicebound.GetServicesByRPC(method)
if err != nil {
r2.SetError(err)
return
}
// 负载均衡选择 Service
index := misc.Hash(ex.conf.Exchange.HashBy, len(svcs), edgeID, addr)
svc := svcs[index]
// 在 Custom 中携带 EdgeID
tail := make([]byte, 8)
binary.BigEndian.PutUint64(tail, edgeID)
custom := append(r1.Custom(), tail...)
// 转发 RPC
r3 := svc.NewRequest(r1.Data(), ropt)
r4, err := svc.Call(ctx, method, r3, copt)
r2.SetData(r4.Data())
r2.SetCustom(r4.Custom())
})
}
Stream 转发
Stream 用于建立 Service 和 Edge 之间的双向数据流。
Service -> Edge Stream
流程:
- Service 创建 Stream,在
Peer字段中指定目标EdgeID - Exchange 解析
EdgeID - 查找对应的 Edge
- 在 Edge 端创建对应的 Stream
- 双向转发 Stream 数据(Raw、Message、RPC)
时序图:
Service Exchange Edgebound Edge
| | | |
|--OpenStream---->| | |
| | | |
| |--GetEdgeByID--->| |
| | | |
| |<--Edge End------| |
| | | |
| |-----------------|--OpenStream->|
| | | |
| |<----------------|--EdgeStream--|
| | | |
|<--Connected-----| | |
| | | |
|<==============Bidirectional Data==========>|
| | | |
说明: Service 创建 Stream,Peer=EdgeID
Exchange 解析 EdgeID 并查找 Edge
如果 Edge 在线,建立双向 Stream 并转发数据
如果 Edge 不在线,关闭 Stream
或者(Edge不在线):
Service Exchange Edgebound Edge
| | | |
|--OpenStream---->| | |
| | | |
| |--GetEdgeByID--->| |
| | | |
| |<--nil-----------| |
| | | |
|<--Close Stream--| | |
关键代码:
func (ex *exchange) StreamToEdge(serviceStream geminio.Stream) {
// 从 Peer 中解析 EdgeID
peer := serviceStream.Peer()
edgeID, err := strconv.ParseUint(peer, 10, 64)
// 查找 Edge
edge := ex.Edgebound.GetEdgeByID(edgeID)
if edge == nil {
serviceStream.Close()
return
}
// 创建 Edge Stream
edgeStream, err := edge.OpenStream()
// 双向转发
ex.streamForward(serviceStream, edgeStream)
}
Edge -> Service Stream
流程:
- Edge 创建 Stream,在
Peer字段中指定目标 Service 名称 - Exchange 根据 Service 名称查找 Service
- 在 Service 端创建对应的 Stream
- 双向转发 Stream 数据
时序图:
Edge Exchange Servicebound Service
| | | |
|--OpenStream->| | |
| | | |
| |--GetServiceByName| |
| | | |
| |<--Service End----| |
| | | |
| |------------------|--OpenStream->|
| | | |
| |<-----------------|--ServiceStream|
| | | |
|<--Connected---| | |
| | | |
|<============Bidirectional Data==========>|
| | | |
说明: Edge 创建 Stream,Peer=ServiceName
Exchange 解析 Service 名称并查找 Service
如果 Service 在线,建立双向 Stream 并转发数据
如果 Service 不在线,关闭 Stream
或者(Service不在线):
Edge Exchange Servicebound Service
| | | |
|--OpenStream->| | |
| | | |
| |--GetServiceByName| |
| | | |
| |<--Error-----------| |
| | | |
|<--Close Stream| | |
关键代码:
func (ex *exchange) StreamToService(edgeStream geminio.Stream) {
// 从 Peer 中解析 Service 名称
peer := edgeStream.Peer()
svc, err := ex.Servicebound.GetServiceByName(peer)
// 创建 Service Stream
serviceStream, err := svc.OpenStream()
// 双向转发
ex.streamForward(edgeStream, serviceStream)
}
Stream 数据转发
Stream 支持三种数据类型的转发:
- Raw 数据:原始字节流双向转发
- Message:消息双向转发
- RPC:RPC 调用双向转发
时序图:
Stream A Exchange Stream B
| | |
|--Raw Data---->| |
| | |
| |--Raw Data---->|
| | |
|<--Raw Data----| |
| | |
| |<--Raw Data----|
| | |
|--Message----->| |
| | |
| |--Publish Msg->|
| | |
|<--Message-----| |
| | |
| |<--Message------|
| | |
|--RPC Request->| |
| | |
| |--Call RPC----->|
| | |
| |<--RPC Response-|
| | |
|<--RPC Response| |
说明: Stream 支持三种数据类型的双向转发
- Raw 数据: 原始字节流双向转发
- Message: 消息双向转发
- RPC: RPC 调用双向转发
关键代码:
func (ex *exchange) streamForward(left, right geminio.Stream) {
// Raw 数据转发
ex.streamForwardRaw(left, right)
// Message 转发
ex.streamForwardMessage(left, right)
// RPC 转发
ex.streamForwardRPC(left, right)
}
Exchange 特性
-
透明转发
- Service 和 Edge 无需知道对方的具体位置
- 通过 ID 或名称进行路由
-
负载均衡
- RPC 调用支持多 Service 负载均衡
- 使用哈希算法保证相同 Edge 的请求路由到同一 Service
-
错误处理
- 目标不在线时返回明确错误
- 支持超时控制(默认 30 秒)
-
Custom 字段传递
- 通过 Custom 字段传递路由信息(EdgeID)
- 保持原始 Custom 数据,仅在末尾追加路由信息
-
异步处理
- 消息转发使用 goroutine 异步处理
- RPC 转发同步等待响应
数据流向总结
Service -> Edge:
- Message: Service 指定 EdgeID -> Exchange 转发 -> Edge
- RPC: Service 指定 EdgeID -> Exchange 转发 -> Edge -> 响应返回
- Stream: Service 指定 EdgeID -> Exchange 建立双向流
Edge -> Service:
- Message: Edge 指定 Topic -> Exchange -> MQM -> 订阅的 Service
- RPC: Edge 指定方法名 -> Exchange 负载均衡 -> Service -> 响应返回
- Stream: Edge 指定 Service 名称 -> Exchange 建立双向流
Frontier + Frontlas 集群模式
概述
Frontier 集群模式通过引入 Frontlas(Frontier Atlas)组件实现多 Frontier 实例的协调管理。Frontlas 是一个无状态的集群管理组件,使用 Redis 存储 Frontier、Service 和 Edge 的元数据信息。
架构图:
Frontlas (集群管理)
|
+------------+------------+
| |
Redis (元数据存储) gRPC/REST API
| |
+-------+-------+ +-------+-------+
| | | |
Frontier-1 Frontier-2 Service-1 Service-2
| | | |
Edge-1 Edge-2 | |
... ...
核心组件:
- Frontier: 无状态的数据平面组件,可以水平扩展
- Frontlas: 无状态的集群管理组件,使用 Redis 存储元数据
- Redis: 存储 Frontier、Service、Edge 的元数据和存活信息
多 Frontier 下的连接管理
Service 连接管理
在集群模式下,Service 通过 clusterServiceEnd 管理多个 Frontier 连接。
连接池管理:
type clusterServiceEnd struct {
cc clusterv1.ClusterServiceClient // gRPC 客户端,连接 Frontlas
edgefrontiers *mapmap.BiMap // EdgeID <-> FrontierID 双向映射
frontiers sync.Map // FrontierID -> frontierNend 连接池
}
关键机制:
-
定期更新 Frontier 列表
- 每 10 秒通过 gRPC 调用
ListFrontiers获取最新的 Frontier 列表 - 对比当前连接池,识别新增和删除的 Frontier
- 每 10 秒通过 gRPC 调用
-
动态连接管理
- 新增 Frontier: 自动创建连接并加入连接池
- 删除 Frontier: 关闭连接并从连接池移除
- 连接漂移: 当 Frontier 地址变化时,关闭旧连接,创建新连接
时序图:
Service Frontlas Frontier-1 Frontier-2
| | | |
|--ListFrontiers>| | |
| | | |
|<--FrontierList-| | |
| | | |
|--Compare Pool->| | |
| | | |
|--New Frontier->| | |
| | | |
|----------------|-----------------|--Connect---->|
| | | |
|<----------------|-----------------|--Connected---|
| | | |
|--Old Frontier->| | |
| | | |
|----------------|-----------------|--Close------>|
| | | |
流程:
- Service 启动时连接 Frontlas(gRPC)
- 调用
ListFrontiers获取所有 Frontier 列表 - 为每个 Frontier 创建
serviceEnd连接 - 定期(10秒)更新 Frontier 列表
- 对比差异:
- 新增: 创建新连接
- 删除: 关闭旧连接,清理 EdgeID 映射
- 变更: 关闭旧连接,创建新连接
关键代码:
func (end *clusterServiceEnd) update() error {
// 获取最新 Frontier 列表
rsp, err := end.cc.ListFrontiers(context.TODO(), &clusterv1.ListFrontiersRequest{})
// 对比当前连接池
keeps := []string{}
removes := []*frontierNend{}
news := []*clusterv1.Frontier{}
// 识别需要删除的 Frontier
end.frontiers.Range(func(key, value interface{}) bool {
// 如果不在新列表中,标记为删除
if !foundInNewList {
removes = append(removes, frontierNend)
}
return true
})
// 识别新增的 Frontier
for _, frontier := range rsp.Frontiers {
if !foundInKeeps {
news = append(news, frontier)
}
}
// 异步处理连接变更
go func() {
// 关闭旧连接
for _, remove := range removes {
remove.end.Close()
end.edgefrontiers.DelValue(remove.frontier.FrontierId)
}
// 创建新连接
for _, new := range news {
serviceEnd, err := end.newServiceEnd(new.AdvertisedSbAddr)
end.frontiers.Swap(new.FrontierId, &frontierNend{
frontier: new,
end: serviceEnd,
})
}
}()
}
Edge 路由查找
当 Service 需要与特定 Edge 通信时,需要查找该 Edge 所在的 Frontier。
查找机制:
- 缓存查找: 首先从
edgefrontiers双向映射中查找 EdgeID 对应的 FrontierID - Frontlas 查询: 如果缓存未命中,调用
GetFrontierByEdge查询 Frontlas - 连接获取: 从连接池中获取对应的 Frontier 连接
- 连接创建: 如果连接池中没有,动态创建连接
时序图:
Service Frontlas Redis Frontier
| | | |
|--Publish(edgeID)>| | |
| | | |
|--Check Cache-->| | |
| | | |
|<--Cache Miss----| | |
| | | |
|--GetFrontierByEdge>| | |
| | | |
| |--GetEdge------->| |
| | | |
| |<--Edge Info----| |
| | | |
| |--GetFrontier-->| |
| | | |
| |<--Frontier Info| |
| | | |
|<--Frontier Info-| | |
| | | |
|--Get Connection>| | |
| | | |
|----------------|----------------|--Publish---->|
| | | |
关键代码:
func (end *clusterServiceEnd) lookup(edgeID uint64) (string, *serviceEnd, error) {
// 1. 从缓存查找
frontierID, ok := end.edgefrontiers.GetValue(edgeID)
if !ok {
// 2. 从 Frontlas 查询
rsp, err := end.cc.GetFrontierByEdge(context.TODO(), &clusterv1.GetFrontierByEdgeIDRequest{
EdgeId: edgeID,
})
frontierID = rsp.Fontier.FrontierId
// 3. 更新缓存
end.edgefrontiers.Set(edgeID, frontierID)
}
// 4. 从连接池获取连接
fe, ok := end.frontiers.Load(frontierID)
if !ok {
// 5. 动态创建连接
serviceEnd, err := end.newServiceEnd(frontier.AdvertisedSbAddr)
end.frontiers.Swap(frontierID, &frontierNend{
frontier: frontier,
end: serviceEnd,
})
}
return frontierID, serviceEnd, nil
}
连接漂移处理
场景:
- Frontier 实例重启: IP 地址可能变化(K8s Pod)
- Frontier 实例迁移: 节点故障导致 Pod 迁移
- Frontier 配置变更: 端口或地址配置变化
处理机制:
- 定期更新检测: 每 10 秒更新 Frontier 列表,检测地址变化
- 连接对比: 通过
frontierEqual比较 FrontierID 和地址 - 优雅切换:
- 先创建新连接
- 再关闭旧连接
- 使用
Swap保证原子性
关键代码:
func frontierEqual(a, b *clusterv1.Frontier) bool {
return a.AdvertisedSbAddr == b.AdvertisedSbAddr &&
a.FrontierId == b.FrontierId
}
// 在 update() 中
prev, ok := end.frontiers.Swap(new.FrontierId, &frontierNend{
frontier: new,
end: serviceEnd,
})
if ok {
// 关闭旧连接
prev.(*frontierNend).end.Close()
}
Edge 连接管理
在集群模式下,Edge 直接连接到 Frontier 实例。Edge 可以连接到任意 Frontier,当连接失败时可以重试或切换到其他 Frontier。
连接流程:
-
Edge 初始连接
- Edge 通过 Dialer 连接到指定的 Frontier 地址
- Frontier 接受连接并分配 EdgeID
- Edge 上线处理
-
Edge 上线通知
- Frontier 在本地注册 Edge
- Frontier 通过 Exchange 通知 Service(如果 Service 在线)
- Frontier 向 Frontlas 报告 Edge 上线
-
心跳续期
- Edge 每 30 秒向 Frontier 发送心跳
- Frontier 转发心跳到 Frontlas
- Frontlas 续期 Edge 的存活标记
-
连接失败处理
- Edge 连接失败时,根据配置决定是否重试
- 使用
NewRetryEdge时,会自动重连 - 可以配置连接到不同的 Frontier 地址
时序图:
Edge Frontier-1 Exchange Frontlas Redis
| | | | |
|--Connect------->| | | |
| | | | |
| |--Allocate EdgeID>| | |
| | | | |
| |<--EdgeID--------| | |
| | | | |
|<--Connected-----| | | |
| | | | |
| |--EdgeOnline---->| | |
| | | | |
| | |--EdgeOnline---->| |
| | | | |
| | | |--SetEdgeAndAlive>|
| | | | |
| | | |<--Success-----|
| | | | |
| | |<--Success------| |
| | | | |
| |<--Success-------| | |
| | | | |
|--Heartbeat------| | | |
| | | | |
| |--EdgeHeartbeat->| | |
| | | | |
| | |--EdgeHeartbeat>| |
| | | | |
| | | |--ExpireEdge-->|
| | | | |
| | | |<--Success-----|
| | | | |
| | |<--Success------| |
| | | | |
| |<--Success-------| | |
| | | | |
Edge 重连场景:
当 Edge 连接失败或 Frontier 故障时,Edge 可以重连到其他 Frontier。
时序图:
Edge Frontier-1 Frontier-2 Frontlas Redis
| | | | |
|--Connect------->| | | |
| | | | |
| |<--Connection Failed| | |
| | | | |
|--Retry Connect->| | | |
| | | | |
| |<--Connection Failed| | |
| | | | |
|--Connect--------|---------------->| | |
| | | | |
| | |--Allocate EdgeID>| |
| | | | |
| | |<--EdgeID--------| |
| | | | |
|<--Connected-----| | | |
| | | | |
| | |--EdgeOnline---->| |
| | | | |
| | | |--SetEdgeAndAlive>|
| | | | |
| | | |<--Success-----|
| | | | |
| | |<--Success------| |
| | | | |
| | |--Delete Old Edge>| |
| | | | |
| | | |--DeleteEdge-->|
| | | | |
| | | |<--Success-----|
| | | | |
| | |<--Success------| |
Edge 下线流程:
时序图:
Edge Frontier Exchange Frontlas Redis
| | | | |
|--Disconnect---->| | | |
| | | | |
| |--EdgeOffline-->| | |
| | | | |
| | |--EdgeOffline-->| |
| | | | |
| | | |--DeleteEdge->|
| | | | |
| | | |<--Success-----|
| | | | |
| | |<--Success------| |
| | | | |
| |<--Success-------| | |
| | | | |
| |--Clean Local---->| | |
| | | | |
关键机制:
-
EdgeID 分配
- Edge 连接时,Frontier 通过 Exchange 向 Service 请求 EdgeID
- 如果 Service 不在线,根据配置决定是否自动分配 EdgeID
- EdgeID 在集群中全局唯一
-
连接选择
- Edge 可以连接到任意 Frontier 实例
- 通常通过负载均衡器或 DNS 选择 Frontier
- 支持配置多个 Frontier 地址进行重试
-
状态同步
- Edge 上线/下线时,Frontier 同步状态到 Frontlas
- Frontlas 更新 Redis 中的 Edge 元数据
- Service 通过 Frontlas 查询 Edge 所在的 Frontier
-
心跳机制
- Edge 每 30 秒发送心跳到 Frontier
- Frontier 转发心跳到 Frontlas
- Frontlas 续期 Redis 中的存活标记(TTL 30秒)
关键代码:
// Edge 上线处理
func (em *edgeManager) online(end geminio.End) error {
// 1. 检查是否存在旧连接
old, ok := em.edges[end.ClientID()]
if ok {
oldend.Close() // 关闭旧连接
}
// 2. 添加到本地缓存
em.edges[end.ClientID()] = end
// 3. 创建数据库记录
edge := &model.Edge{
EdgeID: end.ClientID(),
Meta: string(end.Meta()),
Addr: end.RemoteAddr().String(),
}
em.repo.CreateEdge(edge)
// 4. 通知 Exchange
if em.exchange != nil {
em.exchange.EdgeOnline(end.ClientID(), end.Meta(), end.RemoteAddr())
}
}
// Edge 上线通知 Frontlas
func (fm *FrontierManager) EdgeOnline(ctx context.Context, req geminio.Request, rsp geminio.Response) {
edgeOnline := &gapis.EdgeOnline{}
json.Unmarshal(req.Data(), edgeOnline)
// 更新 Redis
fm.repo.SetEdgeAndAlive(edgeOnline.EdgeID, &repo.Edge{
FrontierID: edgeOnline.FrontierID,
Addr: edgeOnline.Addr,
}, edgeHeartbeatInterval)
}
水平扩展原理
Frontier 无状态设计
Frontier 实例是无状态的,所有状态信息存储在:
- 内存: 当前连接的 Service 和 Edge 信息(重启后丢失)
- Redis: 通过 Frontlas 持久化的元数据
无状态特性:
- 无本地存储: Frontier 不存储任何持久化数据
- 无会话绑定: Service 和 Edge 可以连接到任意 Frontier 实例
- 动态路由: 通过 Frontlas 查询 Edge 所在的 Frontier
水平扩展流程
扩展步骤:
-
添加 Frontier 实例
- 新 Frontier 启动并连接 Frontlas
- 在 Redis 中注册 Frontier 信息(FrontierID、地址等)
- 设置存活标记(TTL 30秒)
-
Service 发现新 Frontier
- Service 定期调用
ListFrontiers获取最新列表 - 检测到新 Frontier,自动创建连接
- 新连接加入连接池
- Service 定期调用
-
Edge 连接分配
- 新 Edge 可以连接到任意 Frontier 实例
- 通过负载均衡或随机选择
- Edge 信息记录到 Redis(关联 FrontierID)
-
流量自动分配
- 新 Edge 的请求自动路由到对应的 Frontier
- Service 通过
lookup查找 Edge 所在的 Frontier - 实现负载均衡
时序图:
NewFrontier Frontlas Redis Service
| | | |
|--Connect------>| | |
| (geminio) | | |
| | | |
|--ConnOnline--->| | |
| (FrontierID, | | |
| Addr) | | |
| | | |
| |--SetFrontierAndAlive>| |
| | (Hash + TTL) | |
| | | |
| |<--Success------| |
| | | |
|<--Registered---| | |
| | | |
| |<--ListFrontiers| |
| | (gRPC) | |
| | | |
| |--GetAllFrontiers>| |
| | | |
| |<--FrontierList-| |
| | | |
| |--FrontierList->| |
| | | |
| | |--Compare Pool>|
| | | |
| | |--New Frontier>|
| | | |
| | |--Connect----->|
| | | (geminio) |
| | | |
| | |<--Connected---|
负载均衡策略
Edge 连接分配:
- 随机分配: Edge 随机选择 Frontier 连接
- 负载均衡: 根据 Frontier 的 Edge 数量分配(需要额外实现)
Service 请求路由:
- 精确路由: 通过 EdgeID 查找对应的 Frontier
- 缓存优化: EdgeID -> FrontierID 映射缓存,减少 Frontlas 查询
高可用性
Frontier 故障处理:
- 心跳检测: Frontier 每 30 秒向 Frontlas 发送心跳
- TTL 过期: Redis 中的存活标记过期后,Frontier 被视为离线
- 自动清理: Frontlas 清理过期的 Frontier 信息
- 连接重建: Service 检测到 Frontier 离线,关闭连接并清理缓存
Frontlas 高可用:
- 无状态设计: Frontlas 实例无状态,可以部署多个
- Redis 高可用: 使用 Redis Sentinel 或 Cluster 模式
- 负载均衡: 多个 Frontlas 实例通过负载均衡器提供服务
Redis 数据模型
Frontlas 使用 Redis 存储 Frontier、Service 和 Edge 的元数据和存活信息。所有数据通过 TTL(Time To Live)机制管理生命周期。
Frontier 数据模型
存储结构:
-
元数据(Hash)
- Key:
frontlas:frontiers:{frontierID} - Type: Hash
- Fields:
advertised_sb_addr: Servicebound 地址advertised_eb_addr: Edgebound 地址edge_count: Edge 数量service_count: Service 数量
- TTL: 由配置的
service_meta决定(默认 30 秒)
- Key:
-
存活标记(String)
- Key:
frontlas:alive:frontiers:{frontierID} - Type: String
- Value:
1 - TTL: 30 秒(通过心跳续期)
- Key:
示例:
# Frontier 元数据
frontlas:frontiers:frontier01
advertised_sb_addr: "192.168.1.10:30011"
advertised_eb_addr: "192.168.1.10:30012"
edge_count: "100"
service_count: "5"
# Frontier 存活标记
frontlas:alive:frontiers:frontier01 = "1" (TTL: 30s)
操作:
- 创建:
SetFrontierAndAlive()- 使用 Lua 脚本原子性创建元数据和存活标记 - 更新:
ExpireFrontier()- 更新存活标记的 TTL - 删除:
DeleteFrontier()- 删除存活标记,保留元数据(edge_count 设为 0)
Service 数据模型
存储结构:
-
元数据(String/JSON)
- Key:
frontlas:services:{serviceID} - Type: String
- Value: JSON 格式
{ "service": "user-service", "frontier_id": "frontier01", "addr": "192.168.1.20:54321", "update_time": 1234567890 } - TTL: 由配置的
service_meta决定(默认 30 秒)
- Key:
-
存活标记(String)
- Key:
frontlas:alive:services:{serviceID} - Type: String
- Value:
1 - TTL: 30 秒(通过心跳续期)
- Key:
示例:
# Service 元数据
frontlas:services:12345 = '{"service":"user-service","frontier_id":"frontier01","addr":"192.168.1.20:54321","update_time":1234567890}' (TTL: 30s)
# Service 存活标记
frontlas:alive:services:12345 = "1" (TTL: 30s)
操作:
- 创建:
SetServiceAndAlive()- 使用 Lua 脚本原子性创建元数据和存活标记,并更新 Frontier 的 service_count - 更新:
ExpireService()- 更新元数据和存活标记的 TTL - 删除:
DeleteService()- 使用 Lua 脚本删除存活标记,更新 Frontier 的 service_count
Edge 数据模型
存储结构:
-
元数据(String/JSON)
- Key:
frontlas:edges:{edgeID} - Type: String
- Value: JSON 格式
{ "frontier_id": "frontier01", "addr": "192.168.1.30:54322", "update_time": 1234567890 } - TTL: 由配置的
edge_meta决定(默认 30 秒)
- Key:
-
存活标记(String)
- Key:
frontlas:alive:edges:{edgeID} - Type: String
- Value:
1 - TTL: 30 秒(通过心跳续期)
- Key:
示例:
# Edge 元数据
frontlas:edges:67890 = '{"frontier_id":"frontier01","addr":"192.168.1.30:54322","update_time":1234567890}' (TTL: 30s)
# Edge 存活标记
frontlas:alive:edges:67890 = "1" (TTL: 30s)
操作:
- 创建:
SetEdgeAndAlive()- 使用 Pipeline 原子性创建元数据和存活标记,并更新 Frontier 的 edge_count - 更新:
ExpireEdge()- 更新元数据和存活标记的 TTL - 删除:
DeleteEdge()- 使用 Lua 脚本删除存活标记,更新 Frontier 的 edge_count
数据关系
Frontier <-> Service 关系:
- Service 元数据中包含
frontier_id字段 - Frontier 的 Hash 中包含
service_count字段 - 通过
frontier_id可以查找 Service 所在的 Frontier
Frontier <-> Edge 关系:
- Edge 元数据中包含
frontier_id字段 - Frontier 的 Hash 中包含
edge_count字段 - 通过
frontier_id可以查找 Edge 所在的 Frontier
查询路径:
EdgeID -> frontlas:edges:{edgeID} -> frontier_id -> frontlas:frontiers:{frontierID} -> advertised_sb_addr
TTL 和心跳机制
TTL 策略:
- 元数据 TTL: 配置项控制(默认 30 秒),用于清理长期不活跃的数据
- 存活标记 TTL: 固定 30 秒,通过心跳续期
心跳机制:
- Frontier 心跳: 每 30 秒向 Frontlas 发送心跳,续期
frontlas:alive:frontiers:{frontierID} - Service 心跳: 每 30 秒向 Frontier 发送心跳,Frontier 转发给 Frontlas,续期
frontlas:alive:services:{serviceID} - Edge 心跳: 每 30 秒向 Frontier 发送心跳,Frontier 转发给 Frontlas,续期
frontlas:alive:edges:{edgeID}
过期处理:
- 当存活标记过期时,对应的资源被视为离线
- Frontlas 会清理过期的存活标记
- Service 通过定期查询检测到 Frontier 离线,自动清理连接
数据一致性
原子性操作:
- 使用 Redis Lua 脚本保证创建/删除操作的原子性
- 使用 Pipeline 批量操作保证一致性
关键 Lua 脚本:
- frontier_create.lua: 创建 Frontier 元数据和存活标记
- service_create.lua: 创建 Service 元数据、存活标记,并更新 Frontier 计数
- service_delete.lua: 删除 Service 存活标记,并更新 Frontier 计数
- edge_delete.lua: 删除 Edge 存活标记,并更新 Frontier 计数
CRD 部分原理
CRD 定义
Frontier 使用 Kubernetes Operator 模式,通过 CRD(Custom Resource Definition)定义集群配置。
CRD 结构:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: frontierclusters.frontier.singchia.io
spec:
group: frontier.singchia.io
names:
kind: FrontierCluster
plural: frontierclusters
scope: Namespaced
versions:
- name: v1alpha1
CRD Spec 定义:
type FrontierClusterSpec struct {
Frontier Frontier `json:"frontier"`
Frontlas Frontlas `json:"frontlas"`
}
type Frontier struct {
Replicas int `json:"replicas,omitempty"`
Servicebound Servicebound `json:"servicebound"`
Edgebound Edgebound `json:"edgebound"`
Image string `json:"image,omitempty"`
NodeAffinity corev1.NodeAffinity `json:"nodeAffinity,omitempty"`
}
type Frontlas struct {
Replicas int `json:"replicas,omitempty"`
ControlPlane ControlPlane `json:"controlplane,omitempty"`
Redis Redis `json:"redis"`
Image string `json:"image,omitempty"`
}
Controller 工作原理
Reconcile 循环:
Controller 通过 Reconcile 函数实现声明式配置管理。
工作流程:
- 监听 CRD 变更: Controller 监听
FrontierCluster资源的创建、更新、删除 - Reconcile 触发: 当 CRD 变更时,触发 Reconcile 函数
- 状态对比: 对比期望状态(Spec)和实际状态(Status)
- 资源创建/更新: 创建或更新 Deployment、Service 等资源
- 状态更新: 更新 CRD 的 Status 字段
时序图:
用户 K8s API Controller Deployment Service
| | | | |
|--Apply CRD---->| | | |
| | | | |
| |--Event-------->| | |
| | | | |
| | |--Reconcile-->| |
| | | | |
| | |--Ensure Service>| |
| | | | |
| | |--Create/Update>| |
| | | | |
| |<--Create Service| | |
| | | | |
| | |--Ensure Deployment>| |
| | | | |
| | |--Create/Update>| |
| | | | |
| |<--Create Deployment| | |
| | | | |
| | |--Check Ready->| |
| | | | |
| | |<--Ready--------| |
| | | | |
| | |--Update Status>| |
| | | | |
| |<--Status Update| | |
关键代码:
func (r *FrontierClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// 1. 获取 CRD
frontiercluster := frontierv1alpha1.FrontierCluster{}
r.Get(ctx, req.NamespacedName, &frontiercluster)
// 2. 确保 Service 存在
r.ensureService(ctx, frontiercluster)
// 3. 确保 TLS Secret 存在
r.ensureTLS(ctx, frontiercluster)
// 4. 确保 Deployment 存在
ready, err := r.ensureDeployment(ctx, frontiercluster)
// 5. 更新状态
status.Update(ctx, r.client.Status(), &frontiercluster, statusOptions().
withMessage(Info, "Good to go!").
withRunningPhase())
}
资源管理
Deployment 创建:
Controller 根据 CRD Spec 创建 Frontier 和 Frontlas 的 Deployment。
关键配置:
-
环境变量注入:
- Frontier 地址和端口
- Frontlas 地址
- Redis 配置
-
Pod 反亲和性:
- 确保 Frontier Pod 分布在不同节点
- 提高可用性
-
资源限制:
- 可配置 CPU 和内存限制
关键代码:
func (r *FrontierClusterReconciler) ensureFrontierDeployment(ctx context.Context, fc v1alpha1.FrontierCluster) error {
// 构建容器配置
container := container.Builder().
SetName("frontier").
SetImage(image).
SetEnvs([]corev1.EnvVar{
{Name: FrontierServiceboundPortEnv, Value: strconv.Itoa(sbport)},
{Name: FrontierEdgeboundPortEnv, Value: strconv.Itoa(ebport)},
{Name: FrontlasAddrEnv, Value: frontlasAddr},
})
// 构建 Pod 模板
podTemplateSpec := podtemplatespec.Builder().
SetPodAntiAffinity(&corev1.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
{TopologyKey: "kubernetes.io/hostname"},
},
})
// 创建 Deployment
deploy := deployment.Builder().
SetReplicas(fc.FrontierReplicas()).
SetPodTemplateSpec(podTemplateSpec).
Build()
deployment.CreateOrUpdate(ctx, r.client, deploy)
}
自动扩缩容
水平扩缩容:
- 修改 Replicas: 用户修改 CRD 中的
replicas字段 - Controller 检测: Controller 检测到 Spec 变更
- 更新 Deployment: 更新 Deployment 的 Replicas
- K8s 调度: Kubernetes 自动创建或删除 Pod
- 状态同步: Controller 更新 CRD Status
垂直扩缩容:
- 通过修改 Deployment 的资源限制实现
- 需要重启 Pod,影响较大
CRD Status 管理
Status 字段:
type FrontierClusterStatus struct {
Phase Phase `json:"phase"` // Running, Failed, Pending
Message string `json:"message"` // 状态描述
}
状态转换:
- Pending: Deployment 未就绪
- Running: 所有 Deployment 就绪
- Failed: 创建或更新资源失败
关键代码:
func (r *FrontierClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// 检查 Deployment 就绪状态
frontierIsReady := deployment.IsReady(currentFrontierDeployment, fc.FrontierReplicas())
frontlasIsReady := deployment.IsReady(currentFrontlasDeployment, fc.FrontlasReplicas())
if !frontierIsReady || !frontlasIsReady {
// 更新为 Pending 状态
status.Update(ctx, r.client.Status(), &frontiercluster, statusOptions().
withMessage(Info, "Deployment is not yet ready").
withPendingPhase(10))
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
}
// 更新为 Running 状态
status.Update(ctx, r.client.Status(), &frontiercluster, statusOptions().
withMessage(Info, "Good to go!").
withRunningPhase())
}
集群模式特性总结
-
无状态设计
- Frontier 和 Frontlas 都是无状态的
- 状态信息存储在 Redis 中
- 支持水平扩展
-
自动发现和路由
- Service 自动发现所有 Frontier 实例
- 通过 Frontlas 查询 Edge 所在的 Frontier
- 支持连接漂移和自动重连
-
高可用性
- 多 Frontier 实例提供冗余
- Frontlas 支持多实例部署
- Redis 支持高可用模式
-
声明式管理
- 通过 CRD 声明集群配置
- Controller 自动管理资源
- 支持自动扩缩容
总结
Frontier 通过以下机制实现了高效的客户端管理和消息转发:
- 灵活的认证机制:支持 Meta 信息传递和灵活的 ID 分配策略
- 可靠的上下线管理:保证数据一致性和并发安全
- 高效的 Exchange 转发:实现 Service 和 Edge 的解耦,支持消息、RPC 和 Stream 的透明转发
- 集群模式支持:通过 Frontlas 实现多 Frontier 实例的协调管理,支持水平扩展和高可用
这些设计使得 Frontier 能够支持大规模、高并发的边缘计算场景。