高效利用资源:分布式有状态服务的高可靠性设计

news2024/12/12 14:32:30

在分布式系统设计中,实现有状态服务的高可靠性通常采用主备切换的方式。当主服务停止工作时,备服务接管任务,例如通过Keepalive实现VIP的切换以保证可用性。然而,这种方式存在资源浪费的问题,因为备服务始终处于空转状态,未能充分利用系统资源。

程序的本质与分解

程序本质上由函数、对象(在面向对象语言中)和数据组合而成,通过特定的逻辑完成特定的业务目标。为了提升有状态服务的高可靠性和高可用性,我们可以从数据、函数和对象三个方面对程序进行分解:

  1. 数据持久化:在主备切换时,确保程序中的数据不丢失。通过数据持久化,可以在备服务接管后,基于持久化的数据重新运算,恢复服务状态。
  2. 纯函数设计:如果函数在相同条件下对相同输入总是产生相同输出,并且只完成其固有功能,具备单一职责,这类函数被称为纯函数。设计成纯函数后,可以基于持久化的数据进行重新计算,增强系统的可恢复性。
  3. 无状态对象:对象由数据和方法组成。如果对象的方法输出仅依赖于自身的状态,而不依赖外部状态,则该对象为无状态对象。无状态对象可以通过数据持久化进行重建,提升系统的灵活性和可扩展性。

横向扩展有状态服务的方法

为了实现有状态服务的横向扩展,并充分利用所有硬件资源,我们需要针对不同情况采用不同的方案。本文以Redis作为数据持久化方案为例,阐述如何实现有状态服务的横向扩展。

  • 有状态对象:通过分布式锁确保同一时间仅有一个实例运行,保证控制逻辑的一致性。
  • 纯函数/无状态对象:通过分布式任务调度器将任务分发到多个节点并行运行。
    在这里插入图片描述

1. 管理有状态对象

如果程序中的对象为有状态对象,即对象的行为不仅依赖自身状态,还依赖外部状态。为了保证状态的一致性与正确性,此类对象只能在一个服务实例中运行。可以通过Redis构建分布式锁来实现这一点。当多个实例同时运行时,只有一个实例能够获取分布式锁并执行计算任务。
在这里插入图片描述

Redisson 示例:

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class DistributedLockExample {
    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        RedissonClient redisson = Redisson.create(config);

        RLock lock = redisson.getLock("myLock");
        lock.lock();
        try {
            // 执行业务逻辑
            System.out.println("Lock acquired, executing business logic.");
        } finally {
            lock.unlock();
            redisson.shutdown();
        }
    }
}

2. 处理纯函数与无状态对象

如果函数是纯函数或对象是无状态的,这意味着函数的执行无副作用,可以在多个实例上并行运行。在这种情况下,尽管多个实例同时处理请求,但由于函数和对象的无状态特性,可以确保结果的正确性。

然而,纯函数和无状态对象的并行计算无法实现运算负载的均衡。为此,可以通过Redisson Executor Service构建分布式执行引擎,将函数和对象分布到不同的节点上计算,以充分利用系统资源。

Redisson 分布式执行引擎示例:
在这里插入图片描述

Redisson Executor Service 分布式执行引擎示例:

import org.redisson.Redisson;
import org.redisson.api.RExecutorService;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.ExecutorOptions;
import org.redisson.api.executor.TaskSuccessListener;
import org.redisson.api.executor.TaskFailureListener;

import java.io.Serializable;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class DistributedExecutionExample {
    public static void main(String[] args) throws Exception {
        // 配置Redisson客户端
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        RedissonClient redisson = Redisson.create(config);

        // 定义ExecutorOptions
        ExecutorOptions options = ExecutorOptions.defaults()
            .taskRetryInterval(10, TimeUnit.MINUTES);

        // 获取RExecutorService实例
        RExecutorService executorService = redisson.getExecutorService("myExecutor", options);

        // 提交Runnable任务
        executorService.submit(new RunnableTask(123));

        // 提交Callable任务并获取结果
        Future<Long> future = executorService.submit(new CallableTask());
        Long result = future.get();
        System.out.println("Callable任务结果: " + result);

        // 提交Lambda任务
        Future<Long> lambdaFuture = executorService.submit((Callable<Long> & Serializable) () -> {
            System.out.println("Lambda任务已执行!");
            return 100L;
        });
        Long lambdaResult = lambdaFuture.get();
        System.out.println("Lambda任务结果: " + lambdaResult);

        redisson.shutdown();
    }

    // 定义Callable任务
    public static class CallableTask implements Callable<Long>, Serializable {
        private static final long serialVersionUID = 1L;

        @org.redisson.api.annotation.RInject
        private transient RedissonClient redissonClient;

        @org.redisson.api.annotation.RInject
        private transient String taskId;

        @Override
        public Long call() throws Exception {
            RMap<String, Integer> map = redissonClient.getMap("myMap");
            Long result = 0L;
            for (Integer value : map.values()) {
                result += value;
            }
            return result;
        }
    }

    // 定义Runnable任务
    public static class RunnableTask implements Runnable, Serializable {
        private static final long serialVersionUID = 1L;

        @org.redisson.api.annotation.RInject
        private transient RedissonClient redissonClient;

        @org.redisson.api.annotation.RInject
        private transient String taskId;

        private long param;

        public RunnableTask() {
        }

        public RunnableTask(long param) {
            this.param = param;
        }

        @Override
        public void run() {
            RAtomicLong atomic = redissonClient.getAtomicLong("myAtomic");
            atomic.addAndGet(param);
            System.out.println("Runnable任务已执行,参数:" + param);
        }
    }
}

Worker 注册示例:

import org.redisson.Redisson;
import org.redisson.api.RExecutorService;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.api.executor.WorkerOptions;

public class WorkerRegistrationExample {
    public static void main(String[] args) {
        // 配置Redisson客户端
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        RedissonClient redisson = Redisson.create(config);

        // 定义WorkerOptions
        WorkerOptions options = WorkerOptions.defaults()
            .setWorkers(2) // 定义使用的Worker数量
            .setTaskTimeout(60, TimeUnit.SECONDS) // 设置任务超时时间
            .addListener(new TaskSuccessListener<Long>() {
                @Override
                public void onSucceeded(String taskId, Long result) {
                    System.out.println("任务 " + taskId + " 成功完成,结果: " + result);
                }
            })
            .addListener(new TaskFailureListener() {
                @Override
                public void onFailed(String taskId, Throwable exception) {
                    System.out.println("任务 " + taskId + " 失败,异常: " + exception.getMessage());
                }
            });

        // 获取RExecutorService实例并注册Workers
        RExecutorService executor = redisson.getExecutorService("myExecutor");
        executor.registerWorkers(options);

        // Redisson节点无需包含任务类,任务类由Redisson节点的ClassLoader自动加载

        redisson.shutdown();
    }
}

性能与限制

Redisson Executor Service 的性能表现

Redisson Executor Service 通过利用 Redis 作为任务队列,实现了分布式任务的调度与执行。这种架构在以下场景下表现出色:

  1. 高并发任务处理

    • 优势:Redis 的高吞吐量使得 Redisson Executor Service 能够快速地提交和分发任务,适用于大量短时间内需要处理的任务。
    • 优化建议:确保 Redis 服务器具备足够的资源(如内存和网络带宽),以应对高并发需求。使用 Redis 集群来分担负载,提升系统的整体吞吐量。
  2. 延迟敏感型任务

    • 优势:由于任务调度和执行的低延迟特性,适用于需要快速响应的应用场景,如实时数据处理和即时反馈系统。
    • 优化建议:优化网络延迟,部署 Redis 服务器尽量靠近应用服务器。同时,合理配置 Redisson 的线程池,确保任务能够及时被处理。
  3. 长时间运行的任务

    • 限制:Redisson Executor Service 更适用于短时间内完成的任务。对于长时间运行的任务,可能会占用大量资源,导致其他任务的延迟增加。
    • 优化建议:将长时间运行的任务拆分为多个子任务,通过批处理或工作流管理来处理。同时,监控任务的执行时间,设置合理的任务超时时间(taskTimeout),避免资源被单个任务长时间占用。
  4. 任务依赖与复杂流程

    • 限制:Redisson Executor Service 主要适用于独立的任务执行,对于有复杂依赖关系的任务流程管理能力有限。
    • 优化建议:结合其他工作流引擎(如 Apache Airflow 或 Spring Batch)来管理复杂的任务依赖关系,同时使用 Redisson Executor Service 处理独立的子任务。

潜在限制

  1. 单点瓶颈

    • 问题:虽然 Redis 本身支持高性能,但在极端高负载情况下,单个 Redis 节点可能成为性能瓶颈。
    • 解决方案:采用 Redis 集群架构,将数据和任务负载分散到多个 Redis 节点,以提升整体性能和可用性。
  2. 任务持久性与可靠性

    • 问题:如果 Redis 实例发生故障,尚未处理的任务可能会丢失,影响系统的可靠性。
    • 解决方案:启用 Redis 持久化(RDB 或 AOF),并配置 Redis 主从复制,确保数据在节点故障时可以快速恢复。此外,结合 Redisson 的任务重试机制,确保任务不因临时故障而丢失。
  3. 网络依赖性

    • 问题:Redisson Executor Service 依赖于网络连接 Redis,如果网络不稳定,可能导致任务提交失败或执行延迟。
    • 解决方案:部署 Redis 服务器和应用服务器在同一数据中心或局域网内,减少网络延迟和抖动。使用高可用的网络架构,确保网络的稳定性和可靠性。
  4. 任务序列化开销

    • 问题:任务和结果对象需要序列化和反序列化,可能带来性能开销,特别是在任务数据量较大时。
    • 解决方案:优化任务对象的序列化方式,选择高效的序列化协议(如 Kryo 或 Protostuff),减少序列化和反序列化的时间开销。

容错与可靠性

故障恢复机制

  1. 任务重试机制

    • 实现方式:Redisson Executor Service 支持任务的自动重试,当任务执行失败或超时后,可以根据配置的重试间隔(taskRetryInterval)自动重新提交任务。
    • 优化建议:根据任务的重要性和执行环境,合理配置重试次数和间隔时间,避免过度重试导致系统负载过高。同时,结合幂等性设计,确保任务在多次重试后仍能保证数据一致性。
  2. 任务确认机制

    • 实现方式:通过任务监听器(如 TaskSuccessListenerTaskFailureListener),可以实时监控任务的执行状态,及时处理执行成功或失败的任务。
    • 优化建议:在任务失败时,结合监控系统(如 Prometheus 和 Grafana)触发告警,快速响应并修复潜在问题。同时,可以根据失败原因,动态调整任务的执行策略。
  3. 数据备份与持久化

    • 实现方式:启用 Redis 的持久化机制(RDB 或 AOF),确保任务队列的数据在 Redis 意外重启或宕机时能够快速恢复。
    • 优化建议:结合 Redis 集群和主从复制,部署多副本以提升数据的持久性和可用性。同时,定期备份 Redis 数据,防止数据丢失。

容错机制

  1. 高可用 Redis 部署

    • 实现方式:通过 Redis Sentinel 或 Redis 集群,实现 Redis 的自动故障转移和主从切换,确保 Redis 服务的高可用性。
    • 优化建议:合理配置 Sentinel 或 Redis 集群,设置合适的故障检测和自动恢复策略,确保在 Redis 节点故障时能够迅速切换到健康节点,最小化服务中断时间。
  2. 分布式锁的健壮性

    • 实现方式:在管理有状态对象时,使用 Redisson 提供的分布式锁机制,确保在多个实例中只有一个实例能够持有锁并执行任务。
    • 优化建议:合理设置锁的自动释放时间,防止因实例故障导致锁无法释放。同时,结合锁续租机制,确保在长时间任务执行时锁不会意外过期。
  3. 冗余服务实例

    • 实现方式:部署多个服务实例,并通过负载均衡器(如 Nginx 或 HAProxy)进行流量分配,避免单点故障导致服务不可用。
    • 优化建议:结合自动伸缩(Auto Scaling)策略,根据系统负载动态调整服务实例的数量,确保在高负载时仍能保持高可用性。同时,定期健康检查服务实例,及时处理故障实例。
  4. 监控与报警

    • 实现方式:部署全面的监控系统,实时监控 Redis 和 Redisson Executor Service 的性能指标(如任务队列长度、任务执行时间、错误率等),并配置告警机制。
    • 优化建议:使用 Prometheus 收集指标数据,并通过 Grafana 可视化展示。同时,设置多渠道告警(如邮件、短信、钉钉等),确保在出现异常时能够快速响应和处理。

灾难恢复策略

  1. 跨数据中心冗余

    • 实现方式:将 Redis 集群部署在多个数据中心,确保在某个数据中心发生灾难性故障时,其他数据中心能够继续提供服务。
    • 优化建议:配置异地复制(如 Redis 的跨集群复制),确保数据在不同数据中心同步更新。同时,定期进行跨数据中心的故障演练,确保灾难恢复方案的有效性。
  2. 备份与恢复测试

    • 实现方式:定期备份 Redis 数据,并在需要时进行恢复测试,确保备份数据的完整性和可用性。
    • 优化建议:采用增量备份和全量备份相结合的策略,减少备份时间和存储空间。同时,自动化恢复流程,确保在需要恢复时能够快速执行,最小化系统的停机时间。

通过以上的性能优化和容错机制设计,可以大幅提升 Redisson Executor Service 在分布式有状态服务中的表现和可靠性,确保系统在各种场景下都能稳定、高效地运行。

结论

为了避免传统主备切换中的资源浪费,可以将有状态服务拆分为数据、纯函数和对象,通过数据持久化和分布式计算实现高效扩展。利用 Redis 提供分布式锁和持久化支持,并借助 Redisson 实现分布式任务调度和并行计算,有状态的任务通过加锁在单实例上运行,无状态的任务则在多个实例间并行分发。此策略不仅提升了资源利用率,还增强了系统的可靠性和可扩展性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2258284.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

重生之我在异世界学智力题(2)

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言智力题&#xff1a;逃离孤岛智力题&a…

论文浅尝 | SAC-KG:利用大语言模型作为领域知识图谱熟练的自动化构造器(ACL2024)...

笔记整理&#xff1a;杜超超&#xff0c;天津大学硕士&#xff0c;研究方向为自然语言处理、大语言模型 论文链接&#xff1a;https://aclanthology.org/2024.acl-long.238/ 发表会议&#xff1a;ACL 2024 1. 动机 知识图谱&#xff08;KG&#xff09;在各个专业领域的知识密集…

Python机器视觉的学习

一、二值化 1.1 二值化图 二值化图&#xff1a;就是将图像中的像素改成只有两种值&#xff0c;其操作的图像必须是灰度图。 1.2 阈值法 阈值法&#xff08;Thresholding&#xff09;是一种图像分割技术&#xff0c;旨在根据像素的灰度值或颜色值将图像分成不同的区域。该方法…

Elasticsearch高性能实践

前言 本方案主要从运维层面分析es是实际生产使用过程中的参数优化&#xff0c;深入理解es各个名词及含义&#xff0c;深入分析es的使用过程中应注意的点&#xff0c;详细解释参数设置的原因以及目的&#xff0c;主要包括系统层面&#xff0c;参数层面。除此之外&#xff0c;优…

笔记本外接显示屏没声音

1、笔记本正常有声音&#xff0c;但是外接显示屏后没有声音了怎么回事呢&#xff1f;原来外接显示屏后笔记本的声音输出会自动选择显示屏的音频输出&#xff0c;但是显示屏可能没有声音输出所以导致笔记本没有声音。 2、解决办法&#xff1a;打开笔记本设置&#xff0c;选择声…

JavaWeb学习(4)(四大域、HttpSession原理(面试)、SessionAPI、Session实现验证码功能)

目录 一、web四大域。 &#xff08;1&#xff09;基本介绍。 &#xff08;2&#xff09;RequestScope。(请求域) &#xff08;3&#xff09;SessionScope。(会话域) &#xff08;4&#xff09;ApplicationScope。(应用域) &#xff08;5&#xff09;PageScope。(页面域) 二、Ht…

用人话讲计算机:Python篇!(十二)正则运算+re模块

目录 一、正则表达式 &#xff08;1&#xff09;什么是正则表达式 &#xff08;2&#xff09;它的结构及使用 示例&#xff1a; 1.字符 . &#xff08;←这里有个小点哦&#xff09; 2.字符 | 3.字符 [ ] 4.字符^ 5.字符\d &#xff08;3&#xff09;补充&#xff…

力扣打卡12:复原IP地址

链接&#xff1a;93. 复原 IP 地址 - 力扣&#xff08;LeetCode&#xff09; 这道题需要对字符串进行操作&#xff0c;我选择了三层循环&#xff0c;其实还可以递归。 我在循环时进行了剪枝&#xff0c;比如一些情况直接跳出循环。 我的代码&#xff1a; class Solution { p…

说下JVM中一次完整的GC流程?

大家好&#xff0c;我是锋哥。今天分享关于【说下JVM中一次完整的GC流程&#xff1f;】面试题。希望对大家有帮助&#xff1b; 说下JVM中一次完整的GC流程&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在JVM中&#xff0c;垃圾回收&#xff08;GC&am…

vs配置c++标准

在 vcxproj 文件中添加 <LanguageStandard>stdcpp17</LanguageStandard> 和在 Visual Studio 属性页面中设置 “C语言标准” 为 “ISO C17 标准 (/std:c17)” 是完全等价的。 它们的对应关系是&#xff1a; VS属性页面中的设置&#xff1a; 项目 -> 属性 ->…

TcpServer 服务器优化之后,加了多线程,对心跳包进行优化

TcpServer 服务器优化之后&#xff0c;加了多线程&#xff0c;对心跳包进行优化 TcpServer.h #ifndef TCPSERVER_H #define TCPSERVER_H#include <iostream> #include <winsock2.h> #include <ws2tcpip.h> #include <vector> #include <map> #…

风控大讲堂|游戏黑产情报挖掘与治理

您的产品有没有遇到过被薅羊毛了&#xff1f;网络游戏行业的繁荣&#xff0c;催生了一批围绕游戏而生的职业玩家&#xff0c;他们利用多开、修改器等手段&#xff0c;疯狂薅游戏资源&#xff0c;破坏游戏经济平衡&#xff0c;给游戏公司带来了难以估量的巨大损失。那么针对此类…

最近邻搜索 - 经典树型结构 M-Tree

前言 如果你对这篇文章感兴趣&#xff0c;可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」&#xff0c;查看完整博客分类与对应链接。 最近邻搜索的目标是从 N N N 个对象中&#xff0c;快速找到距离查询点最近的对象。根据需求的不同&#xff0c;该任务又分…

Jmeter进阶篇(30)深入探索 JMeter 监听器

前言 在性能测试领域里,Apache JMeter 是一款经典而强大的工具,而其中的监听器(Listeners)组件更是发挥着不可或缺的关键作用。 监听器就像敏锐的观察者,默默记录测试执行过程中的各种数据,作为系统性能分析的数据依据。 本文将带你全方位走进 JMeter 监听器的奇妙世界,…

uni-app 个人课程表页面

uni-app 个人课程表页面 插件参考地址 大部分代码都是参考了上述代码&#xff0c;只对代码做出了优化 1. 页面模板 在 schedule.vue 文件中&#xff0c;编写页面结构&#xff1a; <template><view><u-navbar title"个人中心"><view class&q…

ElementEye,网页分析器

介绍 我们经常使用Python写爬虫&#xff0c;爬到网页数据之后&#xff0c;就需要用beautifulSoup进行解析。因为写爬虫并不是我的主营工作&#xff0c;大多数只是用来分析一下想要的数据而已&#xff0c;所以经常会忘记beautifulSoup的用法。 同时&#xff0c;我们总是分析页面…

【Compose multiplatform教程】01 创建你的多平台项目 <官网搬运>

这是 “创建带有共享逻辑和用户界面的 Compose 多平台应用” 教程的第一部分。 第一步&#xff1a;创建你的多平台项目 第二步&#xff1a;探究可组合代码 第三步&#xff1a;修改项目 第四步&#xff1a;创建你自己的应用程序 在这里&#xff0c;你将学习如何使用 Kotlin 多平…

使用OpenTK展示3D点云图像(C#)

最近在研究3D显示&#xff0c;找到一款在winform上展示3D点云的控件&#xff0c;并且实现了点线面的展示&#xff0c;及光照渲染纹理贴图等功能&#xff0c;如下面几张图所展示。 一些基础知识可以在LearnOpenTK - OpenTK 这个网站上学习到。 我这边使用的是openTK3.3.3版本&a…

李宏毅机器学习-批次 (batch)和动量(momentum)

一.batch&#xff08;批次&#xff09; 在计算微分时&#xff0c;不是对所有的数据算出来的Loss值做微分&#xff0c;而是将所有的数据分成一个一个的batch。一个batch是一个B&#xff0c;在更新参数时&#xff0c;拿B的资料计算Loss&#xff0c;计算gradient&#xff0c;再更新…

洗鞋小程序(源码+文档+部署+讲解)

本文将深入解析“洗鞋小程序”的项目&#xff0c;探究其架构、功能以及技术栈&#xff0c;并分享获取完整源码的途径。 系统概述 为洗鞋提供服务&#xff0c;包含小程序和管理端。 本项目名称为洗鞋小程序&#xff0c;是一个基于小程序的在线洗鞋平台。该系统提供下单、订单管…