跳到主要内容

动态绑定逻辑服

介绍

动态绑定逻辑服,指的是用户与逻辑服绑定后,之后的请求都由该逻辑服来处理。 可以实现类似 LOL、王者荣耀匹配后动态分配房间的效果。

解决的问题

动态绑定逻辑服可以解决用户增量的问题,我们都知道一台机器所能承载的运算是有上限的。 当上限达到时,就需要增加新机器来分摊请求量。 如果你开发的是有状态的,那么你如何解决请求分配的问题呢? 在比如让你做一个类似 LOL、王者荣耀的匹配,将匹配好的用户分配到一个房间中, 之后这些用户的请求都能在同一个逻辑服上处理,这种业务你该如何实现呢?

使用框架提供的动态绑定逻辑服节点可以轻松解决此类问题,而且还可以根据业务规则, 计算出当前空闲最多的逻辑服,并将此逻辑服与用户做绑定, 从而做到均衡的利用机器资源,来防止请求倾斜的问题。

场景举例

什么意思呢?这里用匹配与象棋的场景举例。

假设我们部署了 3 台象棋逻辑服,在用户开始游戏之前,我们可以在匹配服中进行匹配。

当匹配逻辑服把 A、B 两个用户匹配到一起后, 我们可以通过 request/multiple_response 通信模型来得到象棋房间数最少的象棋逻辑服。

这里假设是房间数最少的象棋逻辑服是 ChessLogicServer-2,并将其的逻辑服 id 绑定到 A、B 两个用户身上。 之后与象棋相关的操作请求都会由 ChessLogicServer-2 这个逻辑服来处理,比如开始游戏、下棋、吃棋、和棋等。

也可以简单点把这理解成,类似 LOL、王者荣耀的匹配机制。 在匹配服匹配到用户后,把匹配结果中的所有用户分配到一个房间(节点)里面, 这是一种动态分配资源最少的节点(逻辑服)的用法之一。


匹配简图

  • Player-1、Player-2,表示用户1、用户2,后续简称为 A、B 用户。
  • ExternalServer-1、ExternalServer-2,表示对外服。
  • ChessLogicServer,是象棋逻辑服。
  • MatchLogicServer,是匹配逻辑服。
  • match request,匹配请求。

图中,用户1用户2 可以在不同的对外服上,两个用户发起一个匹配请求。 由匹配逻辑服来处理,假设是房间数最少的象棋逻辑服是 ChessLogicServer-2,那么将其的逻辑服 id 绑定到 A、B 两个用户身上。

An

操作简图

  • chess request,象棋相关的操作请求。

当用户与 ChessLogicServer-2 绑定之后, 与象棋相关的操作请求都会由 ChessLogicServer-2 这个逻辑服来处理,比如开始游戏、下棋、吃棋、和棋等。

也可以把这理解成类似 LOL、王者荣耀的匹配机制。 在匹配服匹配到用户后,把匹配结果中的所有用户分配到一个房间里面。

An

Example Source Code


示例内容如下

  • 匹配服逻辑服 (提供两个 action)
    • 登录验证请求
    • 匹配请求
  • 房间逻辑服 (提供两个 action)
    • 统计当前房间请求
    • 接收操作请求
  • 一个模拟的客户端(提供3个请求)
    • 登录请求
    • 匹配请求
    • 循环给房间逻辑服发送操作请求

在服务器中,我们会启动如下内容

  • 启动 2 个房间逻辑服的实例
  • 启动 1 个匹配逻辑服
  • 启动 1 个对外服

模拟客户端中,用户登录后会发起申请匹配。

匹配逻辑服匹配到用户后,通过使用 request/multiple_response 通信模型来得到房间逻辑服中房间数最少的逻辑服。 将其的逻辑服 id 绑定到 A、B 两个用户身上,之后的与房间相关的请求都由该房间逻辑服来处理。

之后该用户会每隔几秒向服务器发送一个操作的请求, 请求会进入房间逻辑服的 DemoEndPointRoomAction.operation 方法。

关键代码说明

动态绑定逻辑服相关代码

  • code 4~7: bindingPrint,打印用户所绑定的逻辑服
  • code 10~22: bindingCover,覆盖绑定
  • code 25~37: bindingAppend,追加绑定
  • code 40~51: bindingRemove,移除绑定
  • code 54~64: bindingClear,清除所有绑定
@ActionController(BindingCmd.cmd)
public class BindingAction {
@ActionMethod(BindingCmd.bindingPrint)
private List<Integer> bindingPrint(FlowContext flowContext) {
int[] bindingLogicServerIds = flowContext.getRequest().getBindingLogicServerIds();
return ArrayKit.toList(bindingLogicServerIds);
}

@ActionMethod(BindingCmd.bindingCover)
private boolean bindingCover(FlowContext flowContext) {
// Cover binding
long[] userIds = {1L, flowContext.getUserId()};
int[] logicServerIds = {1, 2, 3};

var message = BindingLogicServerMessage.builder(BindingEnum.COVER)
.addLogicServerId(logicServerIds)
.addUserId(userIds)
.build();

var response = communication().bindingLogicServer(message);
return response.isSuccess();
}

@ActionMethod(BindingCmd.bindingAppend)
private boolean bindingAppend(FlowContext flowContext) {
// Append binding
long[] userIds = {1L, flowContext.getUserId()};

int[] logicServerIds = {4};
var message = BindingLogicServerMessage.builder(BindingEnum.APPEND)
.addLogicServerId(logicServerIds)
.addUserId(userIds)
.build();

var response = communication().bindingLogicServer(message);
return response.isSuccess();
}

@ActionMethod(BindingCmd.bindingRemove)
private boolean bindingRemove(FlowContext flowContext) {
// Remove binding
long[] userIds = {1L, flowContext.getUserId()};
int[] logicServerIds = {1};
var message = BindingLogicServerMessage.builder(BindingEnum.REMOVE)
.addLogicServerId(logicServerIds)
.addUserId(userIds)
.build();

var response = communication().bindingLogicServer(message);
return response.isSuccess();
}

@ActionMethod(BindingCmd.bindingClear)
private boolean bindingClear(FlowContext flowContext) {
// Clear all bindings
long[] userIds = {1L, flowContext.getUserId()};

var message = BindingLogicServerMessage.builder(BindingEnum.CLEAR)
.addUserId(userIds)
.build();

var response = communication().bindingLogicServer(message);
return response.isSuccess();
}

private Communication communication() {
return CommunicationKit.getCommunication();
}
}

FlowContext 内置了便捷的方法,仅对当前用户生效。

  • code 4~9: bindingCoverMe,覆盖绑定
  • code 12~17: bindingAppendMe,追加绑定
  • code 20~25: bindingRemoveMe,移除绑定
  • code 28~32: bindingClearMe,清除所有绑定
@ActionController(BindingCmd.cmd)
public class BindingAction {
@ActionMethod(BindingCmd.bindingCoverMe)
private boolean bindingCoverMe(FlowContext flowContext) {
// Cover binding
int[] logicServerIds = {1, 2, 3};
var response = flowContext.bindingLogicServer(BindingEnum.COVER, logicServerIds);
return response.isSuccess();
}

@ActionMethod(BindingCmd.bindingAppendMe)
private boolean bindingAppendMe(FlowContext flowContext) {
// Append binding
int[] logicServerIds = {4};
var response = flowContext.bindingLogicServer(BindingEnum.APPEND, logicServerIds);
return response.isSuccess();
}

@ActionMethod(BindingCmd.bindingRemoveMe)
private boolean bindingRemoveMe(FlowContext flowContext) {
// 移除绑定 Remove binding
int[] logicServerIds = {1};
var response = flowContext.bindingLogicServer(BindingEnum.REMOVE, logicServerIds);
return response.isSuccess();
}

@ActionMethod(BindingCmd.bindingClearMe)
private boolean bindingClearMe(FlowContext flowContext) {
// 清除所有绑定 Clear all bindings
var response = flowContext.clearBindingLogicServer();
return response.isSuccess();
}

BindingEnum

在绑定辑服时,有一个参数比较的重要

BindingLogicServerMessage.builder(BindingEnum.xxx);

是用于绑定的业务类型的枚举类,内容如下


APPEND : 追加绑定逻辑服。

举例 : 在追加之前,如果用户已经绑定了 [1-1] serverId。

当添加 [2-2、3-1] serverId, 此时用户所绑定的逻辑服数据为 [1-1、2-2、3-1],一共 3 条数据。


COVER : 覆盖绑定逻辑服。

举例 : 在覆盖之前,如果用户已经绑定了 [1-1] serverId。

当添加 [2-2、3-1] serverId, 此时用户所绑定的逻辑服数据为 [2-2、3-1],一共 2 条数据,新的覆盖旧的。

无论之前有没有绑定的数据,都将使用当前设置的值。


REMOVE : 移除绑定的逻辑服。

举例 : 在移除之前,如果用户已经绑定了 [1-1、2-2、3-1] serverId。

当移除 [2-2、3-1] serverId, 此时用户所绑定的逻辑服数据为 [1-1],一共 1 条数据,被移除了 2 条数据。


CLEAR : 清除所有绑定的逻辑服。

小结

本篇我们介绍了如何动态绑定逻辑服,在使用上来说是比较简单的,只有几句代码。

构建动态绑定信息,执行绑定。

long[] userIds = {1L, flowContext.getUserId()};
int[] logicServerIds = {1, 2, 3};

var message = BindingLogicServerMessage.builder(BindingEnum.COVER)
.addLogicServerId(logicServerIds)
.addUserId(userIds)
.build();

var response = communication().bindingLogicServer(message);

注意事项

注意

该功能为企业级功能


事项1

如果用户断线了,在重新登录后需要重新绑定一下这个逻辑服。 因为绑定的数据与元信息-附加信息一样, 是保存在对外服的,断线后对外服会移除用户的 channel 并清除相关数据。

简单的说,需要开发者来维护这个信息,框架只提供赋值功能。


事项2

用户在绑定逻辑服时,框架不会检测需要绑定的逻辑服是否存在。