制作一个rabbitmq-sdk以及rabbitmq消费者实现定时上下线功能

news2024/11/16 11:51:11
目录结构

在这里插入图片描述

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.aasee</groupId>
        <artifactId>sms-modules</artifactId>
        <version>3.6.3</version>
    </parent>

    <artifactId>sms-rabbitmq-starter</artifactId>

    <name>sms-rabbitmq-starter</name>

    <description>
        rabbitmq-sdk
    </description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- SpringBoot Boot rabbitmq -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- skywalking -->
        <dependency>
            <groupId>org.apache.skywalking</groupId>
            <artifactId>apm-toolkit-logback-1.x</artifactId>
            <version>9.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.skywalking</groupId>
            <artifactId>apm-toolkit-trace</artifactId>
            <version>9.3.0</version>
        </dependency>
    </dependencies>
</project>

com.aasee.rabbitmq.configure.Callback
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
@Slf4j
public class Callback implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {

    /**
     *
     * @param correlationData correlation data for the callback. 相关配置信息
     * @param ack true for ack, false for nack exchange交换机 是否成功收到了消息。true代表成功,false代表失败
     * @param cause An optional cause, for nack, when available, otherwise null.  失败原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        log.info("confirm方法被执行了!!!!! correlationData:{}",correlationData);
        if (ack){
            // 接受成功
            log.info("接受消息成功! correlationDataId: {} ,cause: {} " ,correlationData.getId(), cause);
        }else {
            // 接受失败
            log.error("接受消息失败! correlationDataId: {} ,cause: {} " ,correlationData.getId(), cause);
        }
    }

    /**
     * 回退模式:当消息发送给Exchange后, Exchange路由到Queue失败时 才会执行 ReturnCallBack
     * 步骤:
     *  1. 开启回退模式: publisher-returns: true #是否开启发送端消息抵达队列的确认
     *  2. 设置ReturnCallBack
     *  3. 设置Exchange处理消息的模式:
     *      1. 如果消息没有路由到Queue,则丢弃信息(默认)
     *      2. 如果消息没有路由到Queue,返回给消息发送方ReturnCallBack  设置mandatory为true
     *
     */

    /**
     *
     * @param returned the returned message and metadata.
     */
    @Override
    public void returnedMessage(ReturnedMessage returned) {
        log.info("return机制方法被执行了。。。。。");
        log.info("消息(message):" + returned.getMessage());
        log.info("退回原因代码(replyCode):" + returned.getReplyCode());
        log.info("退回原因(replyText):" + returned.getReplyText());
        log.info("交换机(exchange):" + returned.getExchange());
        log.info("路由Key(routingKey):" + returned.getRoutingKey());

        // TODO 处理未到Queue的数据(或者使用备份交换机)
    }
}
com.aasee.rabbitmq.configure.CustomMessageInterceptor
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.stereotype.Component;

/**
 * 自定义消息拦截器
 *
 * @author Aasee
 * @date 2024-03-11
 */
@Component
public class CustomMessageInterceptor implements MessagePostProcessor {

    @Override
    public Message postProcessMessage(Message message) {
        // 获取原始消息属性
        MessageProperties properties = message.getMessageProperties();

        // 设置新的消息头部信息(如果有需求)
//        properties.setHeader("loginUser", SecurityUtils.getLoginUser());
        return new Message(message.getBody(), properties);
    }
}
com.aasee.rabbitmq.configure.RabbitMQConfig
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * RabbitMQ配置
 *
 * @author Aasee
 * @date 2024-02-24
 */
@Configuration
public class RabbitMQConfig {
    /**
     * rabbitmq Redis 防止重复消费键
     */
    public final static String RABBITMQ_REDIS_KEY="RabbitMq_Send_Sms:";
    public final static String RABBITMQ_REISSUE_KEY="RabbitMq_Reissue_Sms:";
    public final static String RABBITMQ_CALLBACK_KEY="RabbitMq_Callback_Sms:";

    //--------------交换机名称-----------------------------------
    public static final String EXCHANGE_NAME = "aasee_topic_exchange";

    //---------------队列名称------------------------------------
    public static final String QUEUE_NAME = "aasee_queue";
    public static final String SMS_QUEUE_NAME = "cloud_sms_queue";
    public static final String REISSUE_QUEUE_NAME = "sms_reissue_queue";
    public static final String CALLBACK_QUEUE_NAME = "callback_sms_queue";

    //--------------路由键名称-----------------------------------
    public static final String ROUTING_KEY = "aasee.#";
    public static final String SMS_ROUTING_KEY = "sms.#";
    public static final String REISSUE_ROUTING_KEY = "reissue.#";
    public static final String CALLBACK_ROUTING_KEY = "callback.#";
    //------------------------交换机--------------------------
	// 交换机
    @Bean(value = "aaseeExchange")
    public Exchange bootExchange(){
        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
    }

    //------------------------队列--------------------------
    // 队列
    @Bean("aaseeQueue")
    public Queue bootQueue(){
        return QueueBuilder.durable(QUEUE_NAME).build();
    }
    // SMS 队列
    @Bean("cloudSmsQueue")
    public Queue smsQueue(){
        return QueueBuilder.durable(SMS_QUEUE_NAME).build();
    }
    // SMS 补发 队列
    @Bean("smsReissueQueue")
    public Queue smsReissueQueue(){
        return QueueBuilder.durable(REISSUE_QUEUE_NAME).build();
    }
    // 短信回调 队列
    @Bean("callbackSmsQueue")
    public Queue callbackSmsQueue(){
        return QueueBuilder.durable(CALLBACK_QUEUE_NAME).build();
    }
    //-------------------路由绑定-------------------------
    // 队列和交换机绑定关系 Binding
    /*
        1. 知道哪个队列
        2. 知道哪个交换机
        3. routing key
     */
    // 路由绑定
    @Bean
    public Binding bingQueueExchange(@Qualifier("aaseeQueue") Queue queue, @Qualifier("aaseeExchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY).noargs();
    }
    // SMS 路由绑定
    @Bean
    public Binding bingSmsQueueExchange(@Qualifier("cloudSmsQueue") Queue queue, @Qualifier("aaseeExchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(SMS_ROUTING_KEY).noargs();
    }
    // 补发 路由绑定
    @Bean
    public Binding bingSmsReissueQueueExchange(@Qualifier("smsReissueQueue") Queue queue, @Qualifier("aaseeExchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(REISSUE_ROUTING_KEY).noargs();
    }
    // 短信回调 路由绑定
    @Bean
    public Binding bingCallbackSmsQueueExchange(@Qualifier("callbackSmsQueue") Queue queue, @Qualifier("aaseeExchange") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with(CALLBACK_ROUTING_KEY).noargs();
    }


    // Mq模板类
    @Bean
    //设置rabbitTemplate的scope为:prototype
//    @Scope("prototype")
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setBeforePublishPostProcessors(new CustomMessageInterceptor());
        //成功回调
        template.setConfirmCallback(new Callback());
        // 开启mandatory模式(开启失败回调)
        template.setMandatory(true);
        //失败回调
        template.setReturnsCallback(new Callback());
        return template;
    }



}
com.aasee.rabbitmq.service.RabbitMqService
import com.alibaba.fastjson2.JSON;
import com.aasee.rabbitmq.configure.RabbitMQConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.UUID;


/**
 * Rabbit MQ 工具类
 *
 * @author Aasee
 * @date 2024-03-11
 */
//@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
@Slf4j
public class RabbitMqService {
    @Autowired
    public RabbitTemplate rabbitTemplate;


    /**
     * 发送 MQ 消息
     *
     * @param exchange             交换机
     * @param routingKey           路由键
     * @param value                放入mq中的消息体(需要对象)
     * @param messagePostProcessor 消息后处理器(自定义处理)
     * @param correlationData      相关数据(用于传递唯一标识,跟踪绑定数据信息)
     */
    public <T> void sendMqMessage(String exchange, String routingKey,
                                  T value, MessagePostProcessor messagePostProcessor,CorrelationData correlationData) {
        // 推送到Mq中
        rabbitTemplate.convertAndSend(exchange, routingKey, JSON.toJSONString(value), messagePostProcessor,correlationData);
    }


    /**
     * 发送 MQ 消息
     *
     * @param exchange             交换机
     * @param routingKey           路由键
     * @param value                放入mq中的消息体(需要对象)
     * @param messagePostProcessor 消息后处理器(自定义处理)
     */
    public <T> void sendMqMessage(String exchange, String routingKey,
                                  T value, MessagePostProcessor messagePostProcessor) {
        // 唯一标识用于判断消息身份和内容
        String messageId = UUID.randomUUID().toString();
        CorrelationData correlationData = new CorrelationData(messageId);
        // 推送到Mq中
        String jsonString = JSON.toJSONString(value);
        log.info("发送 MQ 消息! exchange: {} , routingKey: {} , messageId: {} , jsonString: {}",exchange,routingKey,messageId,jsonString);
        rabbitTemplate.convertAndSend(exchange, routingKey, jsonString, messagePostProcessor,correlationData);
    }


    /**
     * 发送短信信息
     *
     * @param value                短信信息
     * @param messagePostProcessor 消息后处理器(自定义处理)
     */
    public <T> void sendSmsMessage(T value, MessagePostProcessor messagePostProcessor) {
        // 推送到Mq中
        rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, RabbitMQConfig.SMS_ROUTING_KEY, JSON.toJSONString(value), messagePostProcessor);
    }
}
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.aasee.rabbitmq.configure.RabbitMQConfig, \
  com.aasee.rabbitmq.service.RabbitMqService, \
  com.aasee.rabbitmq.configure.CustomMessageInterceptor

以上就是sdk的所有配置内容,使用bean的自动装配原理让这个sdk被引入时可以自动被spring托管配置。

接下来就是展示如何使用sdk的,同时展示nacos配置或者说application.yml配置

引入SDK
<dependency>
    <groupId>com.aasee</groupId>
    <artifactId>sms-rabbitmq-starter</artifactId>
    <version>3.6.3</version>
</dependency>

可以把这个sdk放到阿里云制品仓库,或者自建私服,又或是直接托管到maven中央仓库,这样你的小伙伴们就能直接引入你的sdk

application.yml
# rabbitmq 配置
  rabbitmq:
    host: xx.xx.xxx.xxx
    virtual-host: /cloudsmsTest
    username: root
    password: xxxxxx
    port: 5672
    publisher-confirm-type: correlated
    publisher-returns: true #是否开启发送端消息抵达队列的确认
    template:
      mandatory: true # 只要消息没有正确抵达队列,以异步方式优先执行我们自己设置的回调,设置交换机处理失败消息的模式
    listener:
      simple:
        acknowledge-mode: manual
        prefetch: 1 #更改为每次读取1条消息,在消费者未回执确认之前,不在进行下一条消息的投送
定时上下线

在开发中我发现了一个有趣的需求,定时上线消费,定时下线停止消费,生产者可以持续往队列里发送消息,但是消费者则可以在指定时间,或者通过手动的方式上下线,以下是具体实现方法

com.aasee.smsconsumer.scheduler.ConsumerScheduler
import com.aasee.common.core.constant.ChannelConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
@Slf4j
public class ConsumerScheduler implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private RabbitListenerEndpointRegistry registry;

    // 每天早上8点启动消费者
    // todo 创建接口用于手动启动
    @Scheduled(cron = "0 0 8 * * ?")
    public void startListening() {
//        RabbitListenerEndpointRegistry registry = applicationContext.getBean(RabbitListenerConfigUtils.RABBIT_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME, RabbitListenerEndpointRegistry.class);
        startConsumer();
    }

    public void startConsumer() {
        if (!registry.getListenerContainer(ChannelConstant.SMSCONSUMER).isRunning()) {
            registry.getListenerContainer(ChannelConstant.SMSCONSUMER).start();
            log.info("smsConsumer 开启监听");
        }
        if (!registry.getListenerContainer(ChannelConstant.REISSUECONSUMER).isRunning()) {
            registry.getListenerContainer(ChannelConstant.REISSUECONSUMER).start();
            log.info("reissueConsumer 开启监听");
        }
    }

    // 每天晚上7点停止消费者
    @Scheduled(cron = "0 0 19 * * ?")
    public void stopListening() {
//        RabbitListenerEndpointRegistry registry = applicationContext.getBean(RabbitListenerConfigUtils.RABBIT_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME, RabbitListenerEndpointRegistry.class);
        if (registry.getListenerContainer(ChannelConstant.SMSCONSUMER).isRunning()) {
            registry.getListenerContainer(ChannelConstant.SMSCONSUMER).stop();
            log.info("smsConsumer 停止监听");
        }
        if (registry.getListenerContainer(ChannelConstant.REISSUECONSUMER).isRunning()) {
            registry.getListenerContainer(ChannelConstant.REISSUECONSUMER).stop();
            log.info("reissueConsumer 停止监听");
        }
    }

    // 这个是为了每次启动或者重启服务后马上开始消费,可以取消
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        log.info("服务启动开启消费!");
        startConsumer();
    }
}
注意

如何你们存在这种定时上下线的需求,需要在**@RabbitListener**注解上加多一个参数 autoStartup = “false” ,这样消费者就不会自动消费消息了

@RabbitListener(id = ChannelConstant.REISSUECONSUMER,queues = RabbitMQConfig.REISSUE_QUEUE_NAME,autoStartup = "false")

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

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

相关文章

力扣中等 33.搜索旋转排序数组

文章目录 题目介绍题解 题目介绍 题解 首先用 153. 寻找旋转排序数组中的最小值 的方法&#xff0c;找到 nums 的最小值的下标 i。 然后分类讨论&#xff1a; 如果 target>nums[n−1]&#xff0c;在 [0,i−1] 中二分查找 target。 如果 target≤nums[n−1]&#xff0c;那…

利士策分享,家庭内耗:隐形的风暴,无声的侵蚀

利士策分享&#xff0c;家庭内耗&#xff1a;隐形的风暴&#xff0c;无声的侵蚀 在温馨的灯光下&#xff0c;家本应是我们心灵的港湾&#xff0c;是疲惫时最坚实的依靠。 然而&#xff0c;当家庭内部出现裂痕&#xff0c;无形的内耗便如同冬日里的寒风&#xff0c;悄无声息地…

11年408考研真题解析-计算机网络

第一题&#xff1a; 解析&#xff1a;网络层虚电路服务和数据报服务 传输服务只有&#xff1a;有连接可靠和无连接不可靠两种&#xff0c;直接排除BC。 网络层指的是IP协议&#xff0c;由图二可知&#xff1a;运输层&#xff0c;网际层&#xff0c;网络接口层唯一有连接可靠的协…

Spark MLlib实践指南:从大数据推荐系统到客户流失预测的全流程建模

问题一 背景&#xff1a; 本题目基于用户数据&#xff0c;将据数据切分为训练集和验证集&#xff0c;供建模使用。训练集与测试集切分比例为8:2。 数据说明&#xff1a; capter5_2ml.csv中每列数据分别为userId , movieId , rating , timestamp。 数据&#xff1a; capte…

详解 Linux 系统下的进程(下)

目录 一.进程控制 1.进程创建 a.Linux 系统中&#xff0c;如何创建一个进程&#xff1f; b.进程创建成功后&#xff0c;Linux 底层会为其做些什么&#xff1f; 2.进程终止 a.什么是进程终止&#xff1f; b.进程终止的方法有哪些&#xff1f; c.exit 与 _exit的区别 3.…

通过logstash同步elasticsearch数据

1 概述 logstash是一个对数据进行抽取、转换、输出的工具&#xff0c;能对接多种数据源和目标数据。本文介绍通过它来同步elasticsearch的数据。 2 环境 实验仅仅需要一台logstash机器和两台elasticsearch机器&#xff08;elasticsearch v7.1.0&#xff09;。本文用docker来模…

NLP 序列标注任务核心梳理

句向量标注 用 bert 生成句向量用 lstm 或 bert 承接 bert 的输出&#xff0c;保证模型可以学习到内容的连续性。此时 lstm 输入形状为&#xff1a; pooled_output.unsqueeze(0) (1, num_sentence, vector_size) 应用场景 词性标注句法分析 文本加标点 相当于粗粒度的分词任…

实时同步 解决存储问题 sersync

目录 1.sersync服务 2.sersync同步整体架构 ​编辑 3.rsync服务准备 4.sersync部署使用 5.修改配置文件 6.启动sersync 7.接入nfs服务 8.联调测试 1.sersync服务 sersync服务其实就是由两个服务组成一个是inotify服务和rsync服务组成 inotify服务用来监控那个…

Linux 文件系统(上)

目录 一.预备阶段 1.认识文件 2.OS对内存文件的管理 3.C库函数和系统调用接口 a.C库函数——fopen b.系统调用接口——open 二.理解文件描述符 1.一张图&#xff0c;详解文件描述符的由来 2.fd的分配规则 3.从fd的角度理解FILE 三.重定向和缓冲区 1.前置知识——理解…

网络安全-CSRF

一、环境 DVWA网上找 二、简单介绍 这个漏洞很早之前了&#xff0c;但是为了避免大家在面试等等的时候被问到&#xff0c;这里给大家温习一下 CSRF全程是没有黑客参与的&#xff0c;全程都是用户自己在操作 三、环境演练 这个是DVWA的提交表单页面&#xff0c;我这里伪造…

【2020工业图像异常检测文献】PaDiM

PaDiM: a Patch Distribution Modeling Framework for Anomaly Detection and Localization 1、Background 在单类学习&#xff08;仅使用正常数据&#xff08;即“单一类”&#xff09;来训练模型&#xff09;环境中的异常检测和定位任务方法中&#xff0c;要么需要深度神经网…

结合HashMap与Java 8的Function和Optional消除ifelse判断

shigen坚持更新文章的博客写手&#xff0c;记录成长&#xff0c;分享认知&#xff0c;留住感动。个人IP&#xff1a;shigen 在文章的开头我们先从这些场景进入本期的问题&#xff1a; 业务代码中各种if-else有遇到过吗&#xff0c;有什么好的优化方式&#xff1b;java8出来这么…

鸿蒙开发(NEXT/API 12)【跨设备互通特性简介】协同服务

跨设备互通提供跨设备的相机、扫描、图库访问能力&#xff0c;平板或2in1设备可以调用手机的相机、扫描、图库等功能。 说明 本章节以拍照为例展开介绍&#xff0c;扫描、图库功能的使用与拍照类似。 用户在平板或2in1设备上使用富文本类编辑应用&#xff08;如&#xff1a;…

学习 git 命令行的简单操作, 能够将代码上传到 Gitee 上

首先登录自己的gitee并创建好仓库 将仓库与Linux终端做链接 比如说我这里已经创建好了一个我的Linux学习仓库 点开克隆/下载&#xff1a; 在你的终端中粘贴上图中1中的指令 此时他会让你输入你的用户名和密码&#xff0c;用户名就是上图中3中Username for ....中后面你的一个…

预付费计量系统实体模型

1. 预付费计量系统实体模型 A generic entity model for electricity payment metering systems is shown in Figure 2. Although it provides a limited perspective, it does serve to convey certain essential concepts. 关于电子式预付费电表系统的实体模型见图 2…

李宏毅结构化学习 03

文章目录 一、Sequence Labeling 问题概述二、Hidden Markov Model(HMM)三、Conditional Random Field(CRF)四、Structured Perceptron/SVM五、Towards Deep Learning 一、Sequence Labeling 问题概述 二、Hidden Markov Model(HMM) 上图 training data 中的黑色字为x&#xff…

如何备份SqlServer数据库

第一步&#xff1a;登录你要备份的服务器数据库ssms 第二步&#xff1a;选择你要备份的数据库 此处已PZ-SJCS 数据库为例 右键该数据库-->任务-->备份 第三步&#xff1a;选择你备份的类型备份组件等&#xff0c;目标磁盘 &#xff0c;点击添加选择将你备份的文件备份那…

全面详尽的 PHP 环境搭建教程

目录 目录 PHP 环境搭建概述 在 Windows 上搭建 PHP 环境 使用集成环境 XAMPP 安装步骤 配置和测试 常用配置 手动安装 Apache、PHP 和 MySQL 安装 Apache 安装 PHP 安装 MySQL 配置 PHP 连接 MySQL 在 Linux 上搭建 PHP 环境 使用 LAMP 方案 安装 Apache 安装 …

【25.6】C++智能交友系统

常见错误总结 const-1 如下代码会报错 原因如下&#xff1a; man是一个const修饰的对象&#xff0c;即man不能修改任何内容&#xff0c;但是man所调用的play函数只是一个普通的函数&#xff0c;所以出现了报错。我们需要在play函数中加上const修饰&#xff0c;或者删除man对…

《论分布式存储系统架构设计》写作框架,软考高级系统架构设计师

论文真题 分布式存储系统&#xff08;Distributed Storage System&#xff09;通常将数据分散存储在多台独立的设备上。传统的网络存储系统采用集中的存储服务器存放所有数据&#xff0c;存储服务器成为系统性能的瓶颈&#xff0c;也是可靠性和安全性的焦点&#xff0c;不能满…