实战:Spring Cloud Stream集成兼容多消息中间件kafka、rabbitmq

news2024/11/22 11:08:25

文章目录

    • 前言
    • 实战要点
    • 技术积累
      • Spring Cloud Stream简介
      • 集成kafka要点
      • 集成rabbitmq要点
    • 实战演示
      • Maven依赖版本号选择
      • Spring及MQ主要配置
      • 基础信道
      • 绑定信道消息发送
      • 集成兼容多mq演示
        • Rabbitmq演示
        • Kafka演示
    • 写在最后

前言

前面的博文我们介绍并实战演示了Spring Cloud Stream整合rabbitmq,其中主要介绍了如何使用和配置完成消息中间件的集成。但是,在实际的生产环境中可能会用到多个消息中间件,又或者是由于业务改变需要更换消息中间件,在这些情况下我们的Spring Cloud Stream框架可以完全兼容多个消息中间件和多种消息中间件的替换。今天,我们就在一个项目中用Spring Cloud Stream 集成两个消息中间件kafka和rabbitmq。

实战要点

1、完美集成并兼容kafka和rabbitmq
2、增加消费组概念,直接保证消息唯一消费
3、增加重试机制,重试条件满足后自动加入死信
4、增加死信消费者,可以直接移植生产
5、消费者手动ack、offset
6、rabbitmq、kafka配置,保证消息不丢失

技术积累

Spring Cloud Stream简介

Spring Cloud Stream是用于构建微服务具有消息驱动能力的框架,应用程序通过inputs、outputs通道与binder进行交互,binder与消息中间件进行通信。

binder的作用是将消息中间件进行粘合,相当于对第三方中间件进行封装整合,让开发人员不用关心底层消息中间件如何运行。
在这里插入图片描述

inputs是消息输入通道,类似于消息中间件的consumer消费者;outputs是消息输出通道,类似于消息中间件的producer生产者。应用程序收发消息不再直接调用消息中间件的接口或者逻辑代码,直接使用Spring Cloud Stream 的OUTPUT与INPUT通道进行处理。

可以通过binder绑定选用各种消息中间件,用binding进行中间件的相关参数配置,让应用程序达到灵活配置和切换消息中间件的目的。

集成kafka要点

1、修改server.properties文件,将#listeners=PLAINTEXT://:9092这一句注释放开,改为listeners=PLAINTEXT://kafka服务器ip:9092
如果此处不改SpringBoot在启动时会报错:
Error connecting to node devops-01:9092 (id: 0 rack: null)
2、kafka 2.8版本开始自带zk,建议使用2.8版本以上的版本不用安装zk
3、spring-boot-starter-paren与spring-cloud-starter-stream-kafk版本号一定要对应上,特别是springboot2之后的版本。如果没有特殊要求,需严格按照本文的版本号进行配置和实战
4、kafka本身、生产者、消费者保证消息不丢失,注意必须使用kafka HA配合修改配置

集成rabbitmq要点

1、rabbitmq比kafka的限制条件就少很多,基本上不用考虑spring版本号兼容
2、rabbimq本身、生产者、消费者保证消息不丢失,注意必须使用rabbitmq HA

实战演示

本次实战直接采用从0到1的策略进行演示,适合小白直接入手,可直接接入生产
本次实战MQ组件全部采用单机进行测试,生产环境请更换为HA

本次实战提供:
1、Kafka、Rabbitmq消息中间件信道注册
2、Kafka、Rabbitm消息中间件消息发送、接收消息监听、死信消息监听

Maven依赖版本号选择

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.12.RELEASE</version>
    <relativePath/>
</parent>
<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Hoxton.SR10</spring-cloud.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-stream-kafka</artifactId>
        <version>3.0.3.RELEASE</version>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Spring及MQ主要配置

server:
  port: 9999
spring:
  rabbitmq:
    host: 10.10.22.187
    port: 5672
    username: admin
    password: admin
    virtual-host: /
  kafka:
    bootstrap-servers: 10.10.22.174:9092
  cloud:
    stream:
      default-binder: myRabbit #默认绑定的mq
      binders: #stream框架粘接的mq
        myRabbit: #自定义个人mq名称
          type: rabbit
          environment:
            spring: ${spring.rabbitmq}
        myKafka:
          type: kafka
          environment:
            spring:
              cloud:
                stream:
                  kafka: ${spring.cloud.stream.kafka.binder}
      bindings: #stream绑定信道
        output_channel: #自定义发送信道名称
          destination: assExchange #目的地 交换机/主题
          content-type: application/json
          binder: myRabbit #粘接到的mq
        input_channel: #自定义接收信道
          destination: assExchange #目的地 交换机/主题
          content-type: application/json
          binder: myRabbit #粘接到的mq
          group: assGroup
          consumer:
            maxAttempts: 3 # 尝试消费该消息的最大次数(消息消费失败后,发布者会重新投递)。默认3
            backOffInitialInterval: 1000 # 重试消费消息的初始化间隔时间。默认1s,即第一次重试消费会在1s后进行
            backOffMultiplier: 2 # 相邻两次重试之间的间隔时间的倍数。默认2
            backOffMaxInterval: 10000 # 下一次尝试重试的最大时间间隔,默认为10000ms,即10s
        output_kafka_channel: #自定义发送信道名称
          destination: assTopic #目的地 交换机/主题
          content-type: text/plain
          binder: myKafka #粘接到的mq
          producer:
            partition-count: 2 #分区数目
        input_kafka_channel: #自定义接收信道
          destination: assTopic #目的地 交换机/主题
          content-type: text/plain
          binder: myKafka #粘接到的mq
          group: assGroup
          consumer:
            maxAttempts: 3 # 尝试消费该消息的最大次数(消息消费失败后,发布者会重新投递)。默认3
            backOffInitialInterval: 1000 # 重试消费消息的初始化间隔时间。默认1s,即第一次重试消费会在1s后进行
            backOffMultiplier: 2 # 相邻两次重试之间的间隔时间的倍数。默认2
            backOffMaxInterval: 10000 # 下一次尝试重试的最大时间间隔,默认为10000ms,即10s
      rabbit: #stream mq配置
        bindings:
          out_channel:
            producer:
              delivery-mode: persistent   #消息持久化 non-persistent
              useConfirmHeader: true #Future<Confirm>获取异常投递,与confirmAckChannel互斥
          input_channel:
            consumer:
              concurrency: 1 #消费者数量
              max-concurrency: 5 #最大消费者数量
              durable-subscription: true  #持久化队列
              recovery-interval: 3000  #3s 重连
              acknowledge-mode: MANUAL  #手动
              requeue-rejected: false #是否重新放入队列
              auto-bind-dlq: true #开启死信队列
              requeueRejected: true #异常放入死信
      kafka:
        binder:
          brokers: ${spring.kafka.bootstrap-servers}
          auto-add-partitions: true #自动分区
          auto-create-topics: true #自动创建主题
          replication-factor: 1 #两个副本
          min-partition-count: 1 #最小分区
        bindings:
          out_kafka_channel:
            producer:
              # 无限制重发不产生消息丢失
              retries: Integer.MAX_VALUE
              #acks =0:producer不等待broker的ack,broker一接收到还没有写入磁盘就已经返回,可靠性最低
              #acks =1:producer等待broker的ack,partition的leader刷盘成功后返回ack,如果在follower同步成功之前leader故障,那么将会丢失数据,可靠性中
              #acks = all 、 -1:producer等待broker的ack,partition的leader和follower全部落盘成功后才返回ack,可靠性高,但延迟时间长
              #可以设置的值为:all, -1, 0, 1
              acks: all
              min:
                insync:
                  replicas: 1 #感知副本数
          input_kafka_channel:
            consumer:
              concurrency: 1 #消费者数量
              max-concurrency: 5 #最大消费者数量
              recovery-interval: 3000  #3s 重连
              auto-rebalance-enabled: true  #主题分区消费者组成员自动平衡
              auto-commit-offset: false   #手动提交偏移量
              enable-dlq: true  # 开启 dlq队列
              dlq-name: assTopic.dlq
              deserializationExceptionHandler: sendToDlq #异常加入死信

基础信道

/**
 * MqChannel
 * @author senfel
 * @version 1.0
 * @date 2023/6/2 15:46
 */
public interface MqChannel {

    /**
     * 消息目的地
     * RabbitMQ中为交换机名称
     * kafka topic
     */
    String DESTINATION = "assExchange";
    String DESTINATIONBYGROUP = "assGroup";
    String DESTINATIONBYTOPIC = "assTopic";
    /**
     * 输出信道
     */
    String OUTPUT_CHANNEL = "output_channel";
    String OUTPUT_KAFKA_CHANNEL = "output_kafka_channel";
    /**
     * 输入信道
     */
    String INPUT_CHANNEL = "input_channel";
    String INPUT_KAFKA_CHANNEL = "input_kafka_channel";
    String INPUT_KAFKA_CHANNEL_ERROR = "assTopic.dlq";
    /**
     * 死信队列
     */
    String INPUT_CHANNEL_DLQ = "assExchange.assGroup.dlq";


    @Output(MqChannel.OUTPUT_CHANNEL)
    MessageChannel output();

    @Output(MqChannel.OUTPUT_KAFKA_CHANNEL)
    MessageChannel outputByKafka();

    @Input(MqChannel.INPUT_CHANNEL)
    SubscribableChannel input();

    @Input(MqChannel.INPUT_KAFKA_CHANNEL)
    SubscribableChannel inputByKafka();

    @Input(MqChannel.INPUT_KAFKA_CHANNEL_ERROR)
    SubscribableChannel inputByKafkaError();
}

绑定信道消息发送

提供绑定信道,增加rabbitmq、kafka发消息逻辑

1、启动类增加绑定mq注解@EnableBinding(MqChannel.class)

@SpringBootApplication
@EnableBinding(MqChannel.class)
public class TestDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestDemoApplication.class, args);
    }

}

2、增加发送消息接口

/**
 * TestMQService
 * @author senfel
 * @version 1.0
 * @date 2023/6/2 15:47
 */
public interface TestMQService {

    /**
     * rabbitmq发送消息
     */
    void send(String str);

    /**
     * kafka发送消息
     */
    void sendByKafka(String str);


}

3、实现发送消息接口

/**
 * TestMQServiceImpl
 * @author senfel
 * @version 1.0
 * @date 2023/6/2 15:49
 */
@Service
@Slf4j
public class TestMQServiceImpl implements TestMQService {

    @Resource
    private MqChannel mqChannel;

    @Override
    public void send(String str) {
        mqChannel.output().send(MessageBuilder.withPayload("rabbitmq测试:"+str).build());
    }

    @Override
    public void sendByKafka(String str) {
        mqChannel.outputByKafka().send(MessageBuilder.withPayload("kafka测试:"+str).build());
    }
}

4、提供接口层

/**
 * @author senfel
 * @version 1.0
 * @date 2023/6/2 17:27
 */
@RestController
public class TestController{
    @Resource
    private TestMQService testMQService;


    /**
     * testRabbitmq
     * @param str
     * @author senfel
     * @date 2023/6/8 11:27 
     * @return java.lang.String
     */
    @GetMapping("/test")
    public String testMq(String str){
        testMQService.send(str);
        return str;
    }

    /**
     * testKafka
     * @param str
     * @author senfel
     * @date 2023/6/8 11:27 
     * @return java.lang.String
     */
    @GetMapping("/testKafka")
    public String testKafka(String str){
        testMQService.sendByKafka(str);
        return str;
    }
}

集成兼容多mq演示

Rabbitmq演示

1、TestMQServiceImpl增加mq消息监听和私信监听

 /**
 * 接收消息监听
 * @param message 消息体
 * @param channel 信道
 * @param tag 标签
 * @author senfel
 * @date 2023/6/5 9:25
 * @return void
 */
@StreamListener(MqChannel.INPUT_CHANNEL)
public void process(String message,
                    @Header(AmqpHeaders.CHANNEL) Channel channel,
                    @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {
    log.info("message : "+message);
    if(message.contains("9")){
        // 参数1为消息的tag  参数2为是否多条处理 参数3为是否重发
        //channel.basicNack(tag,false,false);
        System.err.println("--------------rabbitmq消费者消费异常--------------------------------------");
        System.err.println(message);
        throw new RuntimeException("消费异常");
    }else{
        System.err.println("--------------rabbitmq消费者--------------------------------------");
        System.err.println(message);
        channel.basicAck(tag,false);
    }

}



/**
 * 死信监听
 * @param message 消息体
 * @param channel 信道
 * @param tag 标签
 * @author senfel
 * @date 2023/6/5 14:30
 * @return void
 */
@RabbitListener(
        bindings = @QueueBinding(
                value = @Queue(MqChannel.INPUT_CHANNEL_DLQ)
                , exchange = @Exchange(MqChannel.DESTINATION)
        ),
        concurrency = "1-5"
)
public void processByDlq(String message,
                         @Header(AmqpHeaders.CHANNEL) Channel channel,
                         @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {

    log.info("message : "+message);
    System.err.println("---------------rabbitmq死信消费者------------------------------------");
    System.err.println(message);
}

2、测试正常消息投递

在这里插入图片描述

--------------rabbitmq消费者--------------------------------------
rabbitmq测试:777777777777777

3、测试异常消息投递,投递规则3次消费失败直接进入死信

在这里插入图片描述

--------------rabbitmq消费者消费异常--------------------------------------
rabbitmq测试:7777777777777779
--------------rabbitmq消费者消费异常--------------------------------------
rabbitmq测试:7777777777777779
--------------rabbitmq消费者消费异常--------------------------------------
rabbitmq测试:7777777777777779
---------------rabbitmq死信消费者------------------------------------
rabbitmq测试:7777777777777779

Kafka演示

1、TestMQServiceImpl增加mq消息监听和私信监听

   /**
     * kafka消费者
     * @param message 消息体
     * @param acknowledgment ack
     * @param receivedTopic topic
     * @param groupId 消费者group
     * @author senfel
     * @date 2023/6/7 15:59
     * @return void
     */
    @StreamListener(MqChannel.INPUT_KAFKA_CHANNEL)
    public void processByKafka(String message,
                               @Header(value = KafkaHeaders.ACKNOWLEDGMENT,required = false) Acknowledgment acknowledgment,
                               @Header(KafkaHeaders.RECEIVED_TOPIC) String receivedTopic,
                               @Header(value = KafkaHeaders.GROUP_ID,required = false) String groupId,
                               @Header(value = KafkaHeaders.PARTITION_ID,required = false) String partitionId) throws Exception {
        System.err.println("-------进入kafka消费者---------------");
        System.err.println(message);
        System.err.println(receivedTopic);
        if(message.contains("9")){
            log.error("kafka消费异常:{}",message);
            System.err.println("kafka1消费异常"+message);
            throw new RuntimeException("kafka消费异常");
        }
        System.err.println("kafka接受的数据为"+message);
        acknowledgment.acknowledge();
    }



    /**
     * kafka死信消费
     * @param message 消息体
     * @param receivedTopic  topic
     * @author senfel
     * @date 2023/6/7 15:58
     * @return void
     */
   @KafkaListener(topics = {MqChannel.INPUT_KAFKA_CHANNEL_ERROR},
                    groupId = MqChannel.DESTINATIONBYGROUP)
    public void processByKafkaError(String message,
                                    @Header(KafkaHeaders.RECEIVED_TOPIC) String receivedTopic) throws Exception {

        System.err.println("-------进入死信消费者---------------");
        System.err.println(message);
        System.err.println(receivedTopic);
        System.err.println("kafka死信接受的数据为"+message);
        System.err.println(message);
    }

2、测试正常消息投递

在这里插入图片描述

-------进入kafka消费者---------------
kafka测试:7777777777777777
assTopic
kafka接受的数据为kafka测试:7777777777777777

3、测试异常消息投递,投递规则3次消费失败直接进入死信

在这里插入图片描述

-------进入kafka消费者---------------
kafka测试:7777777777777779
assTopic
kafka1消费异常kafka测试:7777777777777779
-------进入kafka消费者---------------
kafka测试:7777777777777779
assTopic
kafka1消费异常kafka测试:7777777777777779
-------进入kafka消费者---------------
kafka测试:7777777777777779
assTopic
kafka1消费异常kafka测试:7777777777777779
-------进入死信消费者---------------
kafka测试:7777777777777779
assTopic.dlq
kafka死信接受的数据为kafka测试:7777777777777779
kafka测试:7777777777777779

写在最后

Spring Cloud Stream集成多消息中间件kafka、rabbitmq较为简单,直接省去了原生中间的的操作与处理,开发人员可以直接任意切换和混用多种消息中间件,大大增加架构的可用性与可移植性。本实战案例提供重试、私信、手动ack、消费者分组和负载等高可用方案,直接可接入生产使用。

⭐️路漫漫其修远兮,吾将上下而求索 🔍

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

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

相关文章

Java 使用 VisualVM 排查内存泄露

文章目录 1. 问题发生2. 排查过程2.1 初步排查2.2 Visual VM 内存分析2.3 代码分析 1. 问题发生 线上突发告警&#xff0c;笔者负责的一个服务老年代内存使用率到达 75% 阈值&#xff0c;于是立即登录监控系统查看数据。拉长时间周期&#xff0c;查看最近 7 天的 GC 和老年代内…

Transformer【ViT】

参考 导师&#xff01;博主的复现太细了。做个记录。 层神经网络学习小记录67——Pytorch版 Vision Transformer&#xff08;VIT&#xff09;模型的复现详解 计算机视觉中的transformer模型创新思路总结_Tom Hardy的博客-CSDN博 Vision Transformer详解 ViT 前处理 网络结…

2核4G轻量应用服务器性能测评(腾讯云PK阿里云)

阿里云轻量应用服务器2核4G4M带宽297.98元12个月&#xff0c;腾讯云轻量2核4G5M服务器168元一年&#xff0c;628元3年&#xff0c;2核4G轻量应用服务器阿里云和腾讯云怎么选择&#xff1f;哪个性能比较好&#xff1f;阿腾云分享轻量应用服务器2核4G配置阿里云和腾讯云CPU、带宽…

三种编码方式(费诺曼编码,霍夫曼编码,哈夫曼树编码)的简单解释和介绍

一. 费诺曼(Fano)编码是一种前缀编码&#xff0c;其基本原理是将出现频率较高的符号用短的编码表示&#xff0c;而出现频率较低的符号则用长的编码表示。通过这种方式进行编码&#xff0c;可以达到更好的压缩效果。 费诺曼编码的具体过程如下&#xff1a; 将要编码的符号按照…

书籍《银河帝国11:曙光中的机器人》观后感

这本书其实看完有段时间了&#xff0c;《银河帝国11:曙光中的机器人》是阿西莫夫写的《基地》系列第11本书&#xff0c;整个系列不是完全连贯的&#xff0c;本书是《银河帝国10》的后续。 先让我们来回忆一下前奏和背景吧&#xff0c;未来随着人类科技发展&#xff0c;遨游太空…

d2l学习——第一章Introduction

x.1 key components in ML 就和统计学习方法书中说的一样&#xff0c;机器学习也可以分为几个核心要义&#xff0c;Data, Models, Objective Functions, Optimization Algorithms&#xff0c; 其中&#xff1a; Data: 用来学习的数据Model: 如何转换/translate数据的模型Obje…

Vivado_除法器 IP核 使用详解

本文介绍使用Vivado中除法器Divider Generator&#xff08;5.1&#xff09;的使用方法。 参考资料&#xff1a;pg151 文章目录 Divider Generator仿真测试 Divider Generator Channel Settings选项卡 #Common Options&#xff1a; Algorithm Type: 一共有三种类型&#xff0c;…

操作系统 第三章 3.1 错题整理

装入时&#xff0c;相应的地址要修改 若存放的位置从150开始 则所有的地址都加150 加完后的地址是物理地址 不能改变 可变分区&#xff1a;产生的外部碎片需要紧凑 C D 都是执行时才进行地址的变换的 该共享段直接在内存中 不用调入调出 供多个进程共享 II 假设该进程很小 页面…

智能指针(1)

智能指针&#xff08;1&#xff09; 概念内存泄漏指针指针概念RAII使用裸指针存在的问题 智能指针使用分类unique&#xff08;唯一性智能指针&#xff09;介绍智能指针的仿写代码理解删除器 概念 内存泄漏 内存泄漏&#xff1a;程序中已动态分配的堆内存由于某些原因而未释放…

Vue.js 中的插槽是什么?如何使用插槽?

Vue.js 中的插槽是什么&#xff1f;如何使用插槽&#xff1f; 在 Vue.js 中&#xff0c;插槽是一种组件之间通信的机制&#xff0c;允许父组件向子组件传递内容&#xff0c;并在子组件中进行渲染。本文将介绍 Vue.js 中插槽的概念、优势以及如何使用插槽。 什么是插槽&#xf…

cas 6 单点登录登出管理

cas自带的登出是通过登出地址后面接的service地址进行跳转&#xff0c;但是对于service没有进行验证&#xff0c;这边我们网络渗透测试后说可能被钓鱼需要进行验证所以开始了以下操作。 1找资料 首先到cas官网找&#xff0c;发现项目有自带的是否跳转&#xff0c;跳转地址参数…

辞了外包,上岸字节测试岗我落泪了,400多个日夜没人知道我付出了多少....

前言&#xff1a; 没有绝对的天才&#xff0c;只有持续不断的付出。对于我们每一个平凡人来说&#xff0c;改变命运只能依靠努力幸运&#xff0c;但如果你不够幸运&#xff0c;那就只能拉高努力的占比。 2023年3月&#xff0c;我有幸成为了字节跳动的一名自动化测试工程师&am…

Qt导出pdf格式表格

预期目标如下&#xff1a; 头文件&#xff1a; #include #include #include #include #include #include #include private: QString m_html; 调用&#xff1a; QDateTime dateTime QDateTime::currentDateTime(); //当前日期和时间 QString ExportTime dateTime.t…

python带你获取小破站喜爱UP得所用内容

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 环境使用: Python 3.8 Pycharm 2022.3版本 ffmpeg <需要设置环境变量> 软件的使用 合成视频和音频 模块使用: 第三方模块&#xff0c;需要安装 import requests >>> pip install requests 内置模…

认识数据湖加速器(Data Lake Accelerator Goose FileSystem,GooseFS)

认识数据湖加速器Data Lake Accelerator Goose FileSystem&#xff0c;GooseFS 一、产品概述二、产品功能三、产品优势四、快速入门五、使用 GooseFS 预热 Table 中的数据六、使用 GooseFS 进行文件上传和下载操作七、使用 GooseFS 加速文件上传和下载操作八、关闭 GooseFS九、…

UE5 Chaos破碎系统学习2

本文继续从实用性的角度学习Chaos破碎系统&#xff0c;因为破碎的许多操作需要力场&#xff0c;比较麻烦&#xff0c;因此本文打算绕过力场实现一些效果&#xff1a; 1.显示材质效果 制作Chaos破碎效果时&#xff0c;会在编辑器下看不见材质&#xff0c;我们可以选择Geometr…

美创数据安全服务能力再获认可!

美创数据安全服务能力又一次获认可&#xff01; 近日&#xff0c;经全方位能力评估和专家评审&#xff0c;美创获得中国软件评测中心和中国计算机行业协会数据安全专业委员会联合颁发的《数据安全服务能力评定资格证书》&#xff0c;数据安全评估能力符合二级评定资格要求。 为…

生存压力下,Smartbi这套方案为企业降本增效带来新的希望

在如今的经济环境下&#xff0c;许多IT企业都面临着困境。经济的不景气导致市场竞争更加激烈&#xff0c;企业的盈利能力受到了严重的冲击&#xff1b;高昂的成本让企业喘不上气来。为了在这个艰难的时期生存下来&#xff0c;降本增效成为了企业的当务之急。 l实施项目利润低&a…

【C++】是内存管理,但C++ !! 模板初阶

目录 一&#xff0c;回望C语言内存 二&#xff0c; C 内存管理方式 1. 内置类型 2. 自定义类型 3. new & malloc 返回内容区别 4. operator new & operator delete 5. malloc/free和new/delete的区别总结 6. 定位new表达式(placement-new) &#xff08;了…

018+limou+C语言预处理

0.前言 您好&#xff0c;这里是limou3434的一篇博客&#xff0c;感兴趣您可以看看我的其他博文系列。本次我主要给您带来了C语言有关预处理的知识。 1.宏的深度理解与使用 1.1.数值宏常量 #define PI 3.1415926注意define和#之间是可以留有空格的 1.2.字符宏常量 #includ…