个人整理的消息队列的几个函数

本文详细介绍了消息队列的结构及核心函数,包括msgget、msgctl、msgsnd和msgrcv的功能与使用方法,并概述了Linux系统对消息队列的限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


   struct msqid_ds
    {
      struct ipc_perm msg_perm;     //structure describing operation permission
      time_t msg_stime;             //发送到队列的最后一个消息的时间戳
      time_t msg_rtime;             //最后一次接收数据时间
      time_t msg_ctime;             //对队列进行最后一次变动的时间戳
      unsigned long int __msg_cbytes; // 当前队列驻留的字节数
      msgqnum_t msg_qnum;             //当前处于队列的消息数
      msglen_t msg_qbytes;             //队列中所能容纳的字节的最大数目
      pid_t msg_lspid;                 //发送最后一个消息进程的PID
      pid_t msg_lrpid;                 //最后一次接受消息进程的PID
    };
   
    struct ipc_perm {
       key_t       key;          //函数msgget()使用的键值
       uid_t      uid;        // owner’s effective user id
       gid_t      gui;        // owner’s effective group id
       uid_t      cuid;       //creator’s effective user id
       gid_t      cgid;       // creator’s effective group id
       mode_t   mode;           // access mode
       …..
    };


1.  主要函数的介绍( #include <sys/ipc.h>

   1) int msgget(key_t key, int flag);  

     参数:        key: 消息队列的key值,通过ftok获得。

                  flag: 标志位

   返回 :    成功返回消息队列描述字,否则返回-1。
     注:该函数初始化结构msqid_ds 成员:  

        * ipc_perm中的mode成员按flag进行设置.
            * msg_qnum, msg_lspid, msg_lrpid, msg_stime和msg_rtime都设置为0.
            * msg_ctime设置为当前时间.
            * msg_qbytes设置为系统限制值.
  参数key设置成常数IPC_PRIVATE并不意味着其他进程不能访问该消息队列,只意味着即将创建新的消息队列。

 

   2)    int msgctl(int msqid, int cmd, struct msgqid_ds *buf);    

   参数:     cmd参数说明对msqid指定的队列要执行的命令
               IPC_STAT: 取此队列的msqid_ds结构, 并将它存放在buf指向的结构中.

               IPC_SET: 按由buf指向结构中的值, 设置与此队列相关结构中的msg_perm.uid, msg_perm.gid,   msg_perm.mode和msg_qbytes. 该命令只有下列两种进程可以执行:

                        o 有效用户ID等于msg_perm.cuid或msg_per.uid.
                        o 具有超级用户特权的进程.
          IPC_RMID: 从系统中删除该消息队列以及仍在该队列中的所有数据. 执行权限同上.

   返回: 成功返回0,否则返回-1。


  3 )    int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg)

  参数:     msgid,是由msgget函数所返回的消息队列标识符
          msg_ptr,是一个指向要发送消息的指针,正如前面所描述的,这个消息必须以long int类型开始。
            msg_sz,是由msg_ptr所指向的消息的尺寸。这个尺寸必须不包含long int消息类型
            msgflg,控制如果当前消息队列已满或是达到了队列消息的系统限制时如何处理
                    如果msgflg标记设置了IPC_NOWAIT,函数就会立即返回而不发送消息,并且返回值为-1。
                    如果msgflg标记清除了IPC_NOWAIT标记,发送进程就会被挂起,等待队列中有可用的空间
      返回:   如果成功,函数会返回0,如果失败,则会返回-1。如果调用成功,系统就会复制一份消息数据并将其放入消息队列中。


  4 )    int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);

  参数:  msqid,是由msgget函数所返回的消息队列标记符
           msg_ptr,是一个指向将要接收消息的指针,这个消息必须以long int类型开始。
           msg_sz,是由msg_ptr所指向的消息的尺寸,并不包含long int消息类型
           msgtype,是一个long int类型,允许一个接收优先级形式的实现。

         如果msgtype的值为0,队列中第一个可用的消息就会被接收。
                    如果其值大于0,具有相同消息类型的第一个消息就会被接收。

         如果其值小于0,第一个具有相同类型或是小于msgtype绝对值的消息就会被接收。
                    (注:如果我们只是简单的希望以其发送的顺序来接收消息,我们可以将msgtype设置为0。
                       如果我们希望接收特殊消息类型的消息,我们可以将msgtype设置为等于这个值。
                       如果我们希望接收消息类型为n或是小于n的值,我们可以将msgtype设置为-n。)
         msgflg,控制当没有合适类型的消息正在等待被接收时如何处理。如果在msgflg中设置了IPC_NOWAIT位,调用就会立即返回,而返回值为-1。
                如果msgflg标记中消除了IPC_NOWAIT位,进程就会被挂起,等待一个合适类型的消息到来。
      返回:         如果成功,msgrcv会返回放入接收缓冲区中的字节数,消息会被拷贝到由msg_ptr所指向的用户分配缓冲区中,而数据就会由消息队列中删除。如果失败则会返回-1。


    * linux对消息队列的限制
    #define MSGMNI 16    //系统中最大的消息队列数
    #define MSGMAX 8192 //可发送最长的消息字节数
    #define MSGMNB 16384 //一个特定队列中最大的字节数(队列中所有消息之和)

 

<think>嗯,用户现在把问题转向了消息队列的另一核心痛点——重复消息处理。结合之前讨论的消息不丢失机制,这确实是分布式系统中并行的关键挑战。用户引用的三份资料很有价值:第一篇提到不同消息队列的重复消费特性,第二篇引入消息传递语义的理论框架,第三篇则给出系统性解决方案。看来用户需要的是从理论到实践的完整指导。先整理核心矛盾点:消息队列的“at-least-once”传递语义必然导致重复(如RabbitMQ确认重传、Kafka消费者重启偏移量回退),而业务侧需要的是“exactly-once”效果。这本质是个分布式系统的不可能三角问题——要在性能、可靠性和精确性间权衡。从工程落地角度,最佳实践应该是分层防御体系:-生产者端:植入唯一ID生成机制(雪花算法/时间戳+机器ID),相当于给消息带“身份证”-Broker端:对主流中间件差异化处理-Kafka:0.11版起支持事务和幂等,需配合enable.idempotence参数-RabbitMQ:死信队列+TTL实现延迟重试,规避无序重复-消费者端:这是主战场-关键在业务逻辑幂等设计,比如:状态机校验(订单状态跃迁检测)版本号乐观锁(Updatesetversion=version+1whereversion=old_version)唯一索引/去重表(金融交易常用)注意到用户可能更关注实战,需要强调几个魔鬼细节:1)消息ID的全局唯一性必须用纳秒级时间戳+空间标识保证,避免SnowFlake时钟回拨问题2)Redis去重时设置合理的TTL,防止历史数据无限膨胀3)数据库唯一索引在分库分表场景下失效的应对方案(如基因法分片)补充一点性能考量:去重必然增加I/O开销,可在内存用布隆过滤器做第一道过滤。实测显示当重复率<5%时,能降低90%数据库查询。用户上次问过消息不丢失机制,这次转向重复消费,后续可能会追问:-如何监控消息重复率?(展示Prometheus指标采集方法)-事务消息与普通消息的去重差异?(准备用Seata的AT模式举例)-业务无法幂等时的补偿方案?(预留回调接口设计案例)最后检查理论闭环:从CAP定理看,分布式消息队列选择CP模型时(如Kafka),必须通过业务层补偿来实现最终一致性——这也印证了分层防御的必要性。</think> 消息队列中处理重复消息的机制与方法需分层次构建防御体系,以下是结合Kafka、RabbitMQ等中间件的最佳实践和核心原理: --- ### 一、重复消息产生的根本原因 1. **生产者重试机制** - 网络闪断或Broker响应超时导致消息重复投递 - 生产者配置:如Kafka的 `retries > 0` 可能引发重复 2. **消费者处理失败** - 消费者崩溃或超时未提交偏移量(offset),Broker重新分发消息 - 自动提交偏移量(如Kafka `enable.auto.commit=true`)时进程异常退出 3. **Broker冗余机制** - 分布式场景下副本同步延迟(如Kafka ISR机制) - RabbitMQ的镜像队列切换可能触发重传 --- ### 二、三层防御机制(生产者→Broker→消费者) #### 🔹 第一层:生产者端去重 ```mermaid graph LR A[生产者] --> B{消息生成} B --> C[植入业务ID] C --> D[发送前本地缓存] D --> E[Broker接收] ``` - **业务唯一ID生成** - 雪花算法(Snowflake)生成全局唯一ID作为消息Key - 示例:订单ID `20230809123456001` - **本地去重缓存** - 使用内存数据库(如Redis)记录近期发送的ID,设置合理TTL - 伪代码实现: ```python if not redis.exists(message_id): send_to_mq(message) redis.setex(message_id, 3600) # 缓存1小时 ``` #### 🔹 第二层:Broker端去重 | 消息队列 | 去重机制 | 实现方式 | |----------|----------|----------| | **Kafka** | 幂等生产者 | 开启 `enable.idempotence=true`,基于序列号(PID/Sequence)过滤重复 | | **RabbitMQ** | 消息去重插件 | 安装 `rabbitmq-message-deduplication` 插件,基于Header去重 | | **RocketMQ** | 消息Key检查 | 发送时指定 `UNIQ_KEY`,Broker检查重复Key | > 💡 **Kafka幂等原理**: > 每个生产者实例分配PID(Producer ID)和分区级序列号,Broker维护三元组 `(PID, Partition, Sequence)`,序列号不连续则拒绝写入[^3]。 #### 🔹 第三层:消费者端幂等处理(终极防线) ```mermaid graph TD A[消费者] --> B{提取业务ID} B -->|查重| C[去重服务] C -->|存在| D[丢弃消息] C -->|不存在| E[执行业务逻辑] E --> F[落库+记录ID] ``` - **幂等核心设计** 1. **创建去重表** ```sql CREATE TABLE msg_dup_check ( id VARCHAR(64) PRIMARY KEY, -- 消息唯一ID biz_type VARCHAR(20), -- 业务类型 created_time TIMESTAMP -- 处理时间 ); ``` 2. **事务型写入** ```java // 伪代码(Spring事务) @Transactional void processMessage(Message msg) { if (!dupTable.exists(msg.id)) { saveToDupTable(msg.id); // 先插入去重记录 doBusinessLogic(msg); // 执行核心业务 } } ``` 3. **分布式锁**:对关键资源(如账户ID)加锁处理 > ⚠️ **注意事项**: > - 并发场景使用 `SELECT ... FOR UPDATE` 或Redis原子操作(SETNX) > - 数据库唯一索引是最后防线(如订单表的订单号唯一索引) --- ### 三、不同消息队列的语义保障 | 传递语义 | 特点 | 适用场景 | |----------|------|----------| | **At most once** | 可能丢失,但绝不重复 | 日志采集 | | **At least once** | 可能重复,但绝不丢失(默认) | 交易订单 | | **Exactly once** | 精确一次(需业务层配合) | 资金扣减 | > 📌 主流消息队列对Exactly once的支持: > - Kafka:通过 `isolation.level=read_committed` 实现事务消息 > - RocketMQ:事务消息+回查机制 > - RabbitMQ:需业务自己实现幂等[^1] --- ### 四、性能优化方案 1. **降低去重开销** - 布隆过滤器(Bloom Filter)前置拦截,误判率控制在0.1%以下 $$P_{fp} = (1 - e^{-k \cdot n / m})^k$$ 其中 $n$ 为元素数,$m$ 为二进制向量长度,$k$ 为哈希函数数 - 本地缓存热点数据(Guava Cache + LRU策略) 2. **异步落盘机制** - 消费者先快速响应Broker ACK,异步写入数据库 - 通过WAL(Write-Ahead Log)保障可靠性 --- ### 总结 > “三级防护体系”的综合策略: > 1. **生产者**:植入唯一ID + 本地缓存检查 > 2. **Broker**:开启内置幂等(如Kafka)或安装去重插件 > 3. **消费者**:唯一索引 + 事务锁 + 异步补偿 > > 根据业务容忍度选择组合方案,核心系统需实现 **至少两层防护**[^3]。 --- ### 相关问题 1. Kafka事务消息如何实现端到端的Exactly Once语义? 2. 在高并发场景下,布隆过滤器误判率如何动态调整? 3. RabbitMQ如何通过死信队列实现延迟重试去重? 4. 消费者端幂等处理时,数据库唯一索引与分布式锁如何取舍? 5. 在分布式事务中,消息队列与Seata等框架如何协同保障一致性?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值