用 RSocket 解决响应式服务之间的通讯-Part 2:负载均衡和可恢复性[译]

作者:Rafał Kowalski ,原文:Reactive Service-To-Service Communication With RSocket (Part 2): Load Balancing and Resumability

本文是《用 RSocket 解决响应式服务之间的通讯》微型系列的第二篇文章,它将帮助你熟悉 RSocket——一种可能会彻底改变机器之间通讯的新二进制协议。在以下段落中,我们将讨论在云环境中的负载平衡问题以及介绍可恢复性能力,可恢复性能力有助于解决网络问题,尤其是在 IOT 系统中。

如果您不了解 RSocket 基础知识,请查看该系列之前的文章。

高可用性和负载平衡是企业级系统的重要能力

类似银行和保险等许多业务领域,可用性和可靠性是应用程序的核心能力。在这些要求苛刻的行业中,即使在高流量、网络问题导致延迟增加或自然灾害期间,服务也必须 24*7 全天候提供服务。为了确保该软件始终能够提供用户使用,通常会在多个可用区域中对其进行冗余部署。

在这种情况下,每个微服务的至少两个实例部署在至少两个可用性区域中。多个可用区内冗余部署让系统具有弹性且增加了容量(更多个实例能够处理更高的负载)。那么这样会带来什么问题呢?冗余部署引入了额外的复杂性。作为研发人员,我们必须确保进入的的流量分布在所有可用实例中(如果流量打到不可用的实例上,会导致请求失败)。

有两种主要的方法可解决这个问题:服务端负载平衡和客户端负载平衡

第一种方法(服务端负载平衡)基于以下假设,请求者不知道响应者的 IP 地址。取而代之的是,请求者与负载均衡器进行通信,负载均衡器负责在后端的微服务之间分配进入的请求,类似产品有 Ngnix。这种设计在云时代很容易采用。 IaaS 提供商通常具有内置的可靠解决方案,例如 Amazon Web Services 中提供的 Elastic Load Balancer。此外,这种设计有助于开发比普通轮询式负载均衡更复杂的路由策略(例如,自适应负载平衡或链式故障转移)。该服务端负载平衡的主要缺点是必须配置和部署额外的资源,如果我们的系统包含数百个微服务,这可能会很痛苦。此外,它可能会影响延迟-每个请求在负载均衡器上会增加了网络一跳。

第二种方法(客户端负载平衡)与第一种方法有些相反。这里请求者知道给定微服务的每个实例的 IP 地址,而不是通过连接到响应者的中心点来进行负载均衡。有了这些微服务的实例 IP 地址,客户端就可以自己选择响应者实例,然后向其发送请求。该方法好处是不需要任何额外资源,但是必须确保请求者具有响应者所有实例的 IP 地址(请参阅如何使用服务发现模式来处理它)。客户端负载平衡模式的主要优点是其性能(可以减少一个额外的“网络跃点”),进而可以显着减少延迟。这也是 RSocket 实现客户端负载平衡模式的关键原因之一。

Load Balance

RSocket 中的客户端负载平衡

在代码实现方面,RSocket 中客户端负载平衡的实现非常简单。该机制依赖于LoadBalancedRSocketMono对象,根据该对象会从一组可用的 RSocket 实例中选择合适的 RSocket 实例。要访问 RSocket,我们必须订阅LoadBalancedRSocketMonoonNext该方法接收所有 RSocket 实例。而且,它为每个 RSocket 计算统计信息,以便能够估计每个实例的负载,并在特定时间点根据该负载选择性能最佳的实例。

该算法考虑了多个参数,例如延迟,保持的连接数以及未处理的请求数。每个 RSocket 的运行状况都由可用性参数反映出来,该参数的值从零到一,其中零表示给定实例无法处理任何请求,而一则表示分配的请求完全处理完成的 RSocket。下面的代码片段显示了 RSocket 负载平衡 的最基本示例,该示例连接到响应器的三个不同实例并执行 100 个请求。每次它从LoadBalancedRSocketMono对象获取 RSocket 。

@Slf4j
public class LoadBalancedClient {
    static final int[] PORTS = new int[]{7000, 7001, 7002};
    public static void main(String[] args) {
        List rsocketSuppliers = Arrays.stream(PORTS)
                .mapToObj(port -> new RSocketSupplier(() -> RSocketFactory.connect()
                        .transport(TcpClientTransport.create(HOST, port))
                        .start()))
                .collect(Collectors.toList());
        LoadBalancedRSocketMono balancer = LoadBalancedRSocketMono.create((Publisher>) s -> {
            s.onNext(rsocketSuppliers);
            s.onComplete();
        });
        Flux.range(0, 100)
                .flatMap(i -> balancer)
                .doOnNext(rSocket -> rSocket.requestResponse(DefaultPayload.create("test-request")).block())
                .blockLast();
    }
}

值得提到的是,RSocket 中的客户端负载均衡器还会处理无效连接。如果在LoadBalancedRSocketMono中注册的任何 RSocket 实例停止响应,RSocket 将会自动尝试重新连接。默认情况下,它将在 25 秒内执行五次尝试。如果未成功,则将从可用连接池中删除给定的 RSocket。这种设计结合了服务器端负载平衡和低延迟的优点,并减少了客户端负载平衡的“网络跳数”。

无效连接和恢复机制

在云环境中,进行机器之间通信,实时流数据交互一般不会出现什么网络问题,但是试想一下,如果我们将物联网设备放置在无法稳定、可靠地通过网络连接访问的区域中,问题就比较复杂了。很容易想到,在这样的环境中可能面临的两个主要问题:“网络延迟”“连接稳定性”。从软件的角度来看,我们对“网络延迟”可能没有太好的办法,但是对于“连接稳定性”我们还能够做些工作的。

我们来试试用 RSocket 解决问题,先来选择合适的交互模型开始。在这种情况下,最合适的是“请求流”方法,其中部署在云环境中的微服务是请求者,而温度传感器是响应者。选择好交互模型后,我们再应用可恢复性策略。在 RSocket 中,我们通过在RSocketFactory上调用的resume()方法来实现,如下例所示:

@Slf4j
public class ResumableRequester {
    private static final int CLIENT_PORT = 7001;
    public static void main(String[] args) {
        RSocket socket = RSocketFactory.connect()
                .resume()
                .resumeSessionDuration(RESUME_SESSION_DURATION)
                .transport(TcpClientTransport.create(HOST, CLIENT_PORT))
                .start()
                .block();
        socket.requestStream(DefaultPayload.create("dummy"))
                .map(payload -> {
                    log.info("Received data: [{}]", payload.getDataUtf8());
                    return payload;
                })
                .blockLast();
    }
}
@Slf4j
public class ResumableResponder {
    private static final int SERVER_PORT = 7000;
    static final String HOST = "localhost";
    static final Duration RESUME_SESSION_DURATION = Duration.ofSeconds(60);
    public static void main(String[] args) throws InterruptedException {
        RSocketFactory.receive()
                .resume()
                .resumeSessionDuration(RESUME_SESSION_DURATION)
                .acceptor((setup, sendingSocket) -> Mono.just(new AbstractRSocket() {
                    @Override
                    public Flux requestStream(Payload payload) {
                        log.info("Received 'requestStream' request with payload: [{}]", payload.getDataUtf8());
                        return Flux.interval(Duration.ofMillis(1000))
                                .map(t -> DefaultPayload.create(t.toString()));
                    }
                }))
                .transport(TcpServerTransport.create(HOST, SERVER_PORT))
                .start()
                .subscribe();
        log.info("Server running");
        Thread.currentThread().join();
    }
}

请注意,要运行提供的示例,您需要在计算机上安装socat,请参阅README文件以了解更多详细信息

请求方和响应方的机制类似,它基于一些组件。首先,有一个ResumableFramesStore,它用作帧的缓冲区。按照默认实现,它会将它们存储在内存中,但是我们可以通过实现ResumableFramesStore接口(例如,将帧存储在分布式缓存中,如 Redis)来轻松调整以满足业务的需求。这个缓冲区是用来保存在“keep-alive 帧”之间发出的数据,“keep-alive 帧”是定期来回发送,能够探测出交互双方之间的连接是否稳定;另外,“keep-alive 帧(保活帧)”还包含令牌,这个令牌是为了确定请求者和响应者的最后接收位置。当交互双方需要要恢复连接时,它将发送带有“隐含位置”的“resume 帧(恢复帧)”。隐含位置是根据上次接收到的位置(与“保活帧”中的值相同)加上该时刻接收到的帧的长度计算得出的。此算法适用于通信的双方,在恢复帧中会含有“最后接收的服务器位置”“第一个客户端可用位置”信息的令牌。下图显示了恢复操作的整个流程:

Resumabilty Mechanism

通过采用 RSocket 协议中内置的可恢复性机制,我们可以用相对较少的精力来减少网络问题的影响。就像上面的示例所示,可恢复性在数据流应用程序中可能非常有用,尤其是在 IOT 设备与云环境中的服务通信的场景下。

总结

在本文中,我们讨论了 RSocket 协议的更多高级功能,这些功能有助于减少网络对系统可操作性的影响。我们介绍了客户端负载平衡模式和可恢复性机制的实现。这些功能与健壮的交互模型相结合,构成了协议的核心。

在本微型系列的最后一篇文章中,我们将介绍 RSocket 之上的构建可用抽象层。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容