redis实现分布式延时队列

文章目录

  • 延时队列简介
  • 应用场景
  • 案例:
  • 考虑:
  • 实现:
    • 整体思路:
    • 具体实现
      • 生产者
      • 消费者
    • 运行结果
  • redis分布式延时队列优势
  • redis分布式延时队列劣势

延时队列简介

延时队列是一种特殊的消息队列,它允许将消息在一定的延迟时间后再进行消费。延时队列的主要特点是可以延迟消息的处理时间,以满足定时任务或者定时事件的需求。

总之,延时队列通过延迟消息的消费时间,提供了一种方便、可靠的方式来处理定时任务和定时事件。它在分布式系统中具有重要的作用,能够提高系统的可靠性和性能。

延时队列的实现方式可以有多种,本文介绍一种redis实现的分布式延时队列。

应用场景

  • 定时任务:可以将需要在特定时间执行的任务封装为延时消息,通过延时队列来触发任务的执行。

  • 订单超时处理:可以将订单消息发送到延时队列中,并设置订单的超时时间,超过时间后,消费者从队列中获取到超时的订单消息,进行相应的处理。

  • 消息重试机制:当某个消息处理失败时,可以将该消息发送到延时队列中,并设置一定的重试时间,超过时间后再次尝试处理。

案例:

12306火车票购买,抢了订单后,45分钟没有支付,自动取消订单

考虑:

数据持久化:redis是支持的,可以使用rdb,也可以使用aof

有序存储:因为只要最小的没过期,后面的肯定就没过期,这样的话检查最小的节点就行了,考虑使用redis中的zset结构

高可用:考虑哨兵或者cluster

高伸缩:因为12306用户量非常大,可能导致redis中存储的任务空间非常大,所以考虑扩展节点,从这个角度来说,使用cluster集群模式,哨兵只有一个节点即主节点写数据。

实现:

整体思路:

  • 生产消费者模型:因为12306的用户量非常大,所以考虑生产者和消费者有多个节点;
  • 采用cluster模式实现高可用以及高伸缩性
  • 采用zset存储延时任务(zadd key score member,score表示时间);
  • 为了让数据均匀分布在cluster集群中的多个主节点中:构建多个zset,每个zset对应一个消费者,生产者随机向某个zset中生产数据。

具体实现

生产者

需要安装hiredis-cluster集群,安装编译如下:

git clone https://github.com/Nordix/hiredis-cluster.git
cd hiredis-cluster
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -
DENABLE_SSL=ON ..
make
sudo make install
sudo ldconfig

需要安装libevent库,最后编译时执行gcc producer.c -o producer -levent -lhiredis_cluster -lhiredis -lhiredis_ssl编译生产者可执行程序

#include <hiredis_cluster/adapters/libevent.h>
#include <hiredis_cluster/hircluster.h>
#include <event.h>
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/time.h>

int64_t g_taskid = 0;

#define MAX_KEY 10

static int64_t hi_msec_now() {
    int64_t msec;
    struct timeval now;
    int status;
    status = gettimeofday(&now, NULL);
    if (status < 0) {
        return -1;
    }
    msec = (int64_t)now.tv_sec * 1000LL + (int64_t)(now.tv_usec / 1000LL);
    return msec;
}

static int _vscnprintf(char *buf, size_t size, const char *fmt, va_list args) {
    int n;
    n = vsnprintf(buf, size, fmt, args);
    if (n <= 0) {
        return 0;
    }
    if (n <= (int)size) {
        return n;
    }
    return (int)(size-1);
}

static int _scnprintf(char *buf, size_t size, const char *fmt, ...) {
    va_list args;
    int n;
    va_start(args, fmt);
    n = _vscnprintf(buf, size, fmt, args);
    va_end(args);
    return n;
}

void connectCallback(const redisAsyncContext *ac, int status) {
    if (status != REDIS_OK) {
        printf("Error: %s\n", ac->errstr);
        return;
    }
    printf("Connected to %s:%d\n", ac->c.tcp.host, ac->c.tcp.port);
}

void disconnectCallback(const redisAsyncContext *ac, int status) {
    if (status != REDIS_OK) {
        printf("Error: %s\n", ac->errstr);
        return;
    }
    printf("Disconnected from %s:%d\n", ac->c.tcp.host, ac->c.tcp.port);
}

void addTaskCallback(redisClusterAsyncContext *cc, void *r, void *privdata) {
    redisReply *reply = (redisReply *)r;
    if (reply == NULL) {
        if (cc->errstr) {
            printf("errstr: %s\n", cc->errstr);
        }
        return;
    }

    int64_t now = hi_msec_now() / 10;
    printf("add task success reply: %lld now=%ld\n", reply->integer, now);
}

int addTask(redisClusterAsyncContext *cc, char *desc) {
    /* 转化为厘米秒 */
    int64_t now = hi_msec_now() / 10;
    g_taskid++;
    
    /* key */
    char key[256] = {0};
	// 为了让数据均匀分布在cluster集群中的多个主节点中:// 构建多个zset,每个zset对应一个消费者,生产者随机向某个zset中生产数据,
	// 生产者可以有很多个,只需要保证向task_group:0-task_group:9中均匀的生产数据即可
    int len = _scnprintf(key, 255, "task_group:%ld", g_taskid % MAX_KEY);
    key[len] = '\0';
    
    /* member */
    char mem[1024] = {0};
    len = _scnprintf(mem, 1023, "task:%ld:%s", g_taskid, desc);
    mem[len] = '\0';
    
    int status;
	// 为每一个任务延时5秒中去处理
    status = redisClusterAsyncCommand(cc, addTaskCallback, "",
                                      "zadd %s %ld %s", key, now+500, mem);

    printf("redisClusterAsyncCommand:zadd %s %ld %s\n", key, now+500, mem);
    if (status != REDIS_OK) {
        printf("error: err=%d errstr=%s\n", cc->err, cc->errstr);
    }
    return 0;
}

void stdio_callback(struct bufferevent *bev, void *arg) {
    redisClusterAsyncContext *cc = (redisClusterAsyncContext *)arg;
    struct evbuffer *evbuf = bufferevent_get_input(bev);
    char *msg = evbuffer_readln(evbuf, NULL, EVBUFFER_EOL_LF);
    if (!msg) return;

    if (strcmp(msg, "quit") == 0) {
        printf("safe exit!!!\n");
        exit(0);
        return;
    }
    if (strlen(msg) > 1024-5-13-1) {
        printf("[err]msg is too long, try again...\n");
        return;
    }

    addTask(cc, msg);
    printf("stdio read the data: %s\n", msg);
}

int main(int argc, char **argv) {
    printf("Connecting...\n");
	// 连接cluster集群,可以从cluster集群中任意一个节点出发连接集群
    redisClusterAsyncContext *cc =
        redisClusterAsyncConnect("127.0.0.1:7006", HIRCLUSTER_FLAG_NULL);
    printf("redisClusterAsyncContext...\n");
    if (cc && cc->err) {
        printf("Error: %s\n", cc->errstr);
        return 1;
    }

    struct event_base *base = event_base_new();
    redisClusterLibeventAttach(cc, base);
    redisClusterAsyncSetConnectCallback(cc, connectCallback);
    redisClusterAsyncSetDisconnectCallback(cc, disconnectCallback);

    // nodeIterator ni;
    // initNodeIterator(&ni, cc->cc);
    // cluster_node *node;
    // while ((node = nodeNext(&ni)) != NULL) {
    //     printf("node %s:%d role:%d pad:%d\n", node->host, node->port, node->role, node->pad);
    // }
    struct bufferevent *ioev = bufferevent_socket_new(base, 0, BEV_OPT_CLOSE_ON_FREE);
    bufferevent_setcb(ioev, stdio_callback, NULL, NULL, cc);
    bufferevent_enable(ioev, EV_READ | EV_PERSIST);

    printf("Dispatch..\n");
    event_base_dispatch(base);

    printf("Done..\n");
    redisClusterAsyncFree(cc);
    event_base_free(base);
    return 0;
}

// 需要安装 hiredis-cluster libevent
// gcc producer.c -o producer -levent -lhiredis_cluster -lhiredis -lhiredis_ssl

说明:

这里构建了10个zset,分别是task_group:0,task_group:1,…,task_group:9作为10个zset的key,zset的数据其实就代表着消费者的数量,通常消费者的功能是一摸一样的,生产者就不管你有多少个了,只需要将任务均匀的打散在不同的zset中就行了(具体实现可以搞一个全局的id,每一次添加任务时id++,然后再对zset个数10取模,最终可以得到0-9之间的一个数,然后再与task_group拼接,这样就可以将任务均匀的打散在不同的zset中)。

消费者

消费者是采用skynet+lua脚本实现的,每个消费者会不断的去检查redis中的任务有没有过期,如果过期,就取出来删除(这里只是demo,只是打印之后删除任务)

local skynet = require "skynet"

local function table_dump( object )
    if type(object) == 'table' then
        local s = '{ '
        for k,v in pairs(object) do
            if type(k) ~= 'number' then k = string.format("%q", k) end
            s = s .. '['..k..'] = ' .. table_dump(v) .. ','
        end
        return s .. '} '
    elseif type(object) == 'function' then
        return tostring(object)
    elseif type(object) == 'string' then
        return string.format("%q", object)
    else
        return tostring(object)
    end
end

local mode, key = ...
if mode == "slave" then
    local rediscluster = require "skynet.db.redis.cluster"
    local function onmessage(data,channel,pchannel)
        print("onmessage",data,channel,pchannel)
    end
    skynet.start(function ()
        local db = rediscluster.new({
                {host="127.0.0.1",port=7001},
            },
            {read_slave=true,auth=nil,db=0,},
            onmessage
        )
        assert(db, "redis-cluster startup error")
        skynet.fork(function ()
            while true do
                local res = db:zrange(key, 0, 0, "withscores")
                if not next(res) then
                    skynet.sleep(50)
                else
                    local expire = tonumber(res[2])
                    local now = skynet.time()*100
                    if now >= expire then
                        print(("%s is comsumed:expire_time:%d"):format(res[1], expire))
                        db:zrem(key, res[1])
                    else
                        skynet.sleep(10)
                    end
                end
            end
        end)
    end)

else
    skynet.start(function ()	-- // 启动10个程序,并把"slave"传入mode,task_group:i传入到key中,即每个程序只消费一个
        for i=0,9 do
            skynet.newservice(SERVICE_NAME, "slave", "task_group:"..i)

运行结果

在这里插入图片描述

redis分布式延时队列优势

1.Redis zset支持高性能的 score 排序。

2.Redis是在内存上进行操作的,速度非常快。

3.Redis可以搭建集群,当消息很多时候,我们可以用集群来提高消息处理的速度,提高可用性。

4.Redis具有持久化机制,当出现故障的时候,可以通过AOF和RDB方式来对数据进行恢复,保证了数据的可靠性

redis分布式延时队列劣势

使用 Redis 实现的延时消息队列也存在数据持久化, 消息可靠性的问题:

  • 没有重试机制 - 处理消息出现异常没有重试机制, 这些需要自己去实现, 包括重试次数的实现等;
  • 没有 ACK 机制 - 例如在获取消息并已经删除了消息情况下, 正在处理消息的时候客户端崩溃了, 这条正在处理的这些消息就会丢失, MQ 是需要明确的返回一个值给 MQ 才会认为这个消息是被正确的消费了。

总结:如果对消息可靠性要求较高, 推荐使用 MQ 来实现

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

②CPU - 运算器、控制器 【软考-软件设计师考点】

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ ②CPU - 运算器、控制器 【软考-软件设计师考点…

网络工程综合试题(三)

1. BGPMLS的作用是什么&#xff1f; BGPMLS&#xff08;BGP Monitoring Protocol and Label Switching&#xff09;是一种用于监控和管理BGP&#xff08;Border Gateway Protocol&#xff09;网络的协议和技术。它结合了BGP和MPLS&#xff08;Multiprotocol Label Switching&am…

RabbitMQ初入门

1、RabbitMQ是什么 RabbitMQ是“实现了高级消息队列协议&#xff08;AMQP&#xff09;的开源消息代理软件&#xff08;亦称面向消息的中间件&#xff09;。RabbitMQ服务器是用Erlang语言编写的&#xff0c;而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均…

LeetCode热题100 48.旋转图像

题目描述 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,5,6],[7,8,9…

vue 内置指令-v-pre/v-memo

一、v-pre 使用了该指令的元素和子元素会被编译忽略&#xff0c;也就是不进行编译&#xff0c;其中包含的所有vue模版语法都会原样显示&#xff0c;作用加快vue的编译 例子&#xff1a; <p v-pre>{{不会被编译}}<span v-text"msg"></span></p&…

部署K8S

防火强的初始化&#xff1a; [rootk8s-node-12 ~]# systemctl stop firewalld NetworkManager [rootk8s-node-12 ~]# systemctl disable firewalld NetworkManager Removed symlink /etc/systemd/system/multi-user.target.wants/NetworkManager.service. Removed symlink /et…

Flask 路由机制分析之一

一、前言 《Flask Run运行机制剖析》这篇我们讲了应用启动的内部机制&#xff0c;启动后就开始监听Http请求了&#xff0c;请求过来如何跳到对应的函数执行&#xff0c;这就是路由机制。我们沿用上一篇例子&#xff0c;来探究一下app.route("/")内部干了些什么事。 …

力扣 三数之和 双指针 java

Problem: 15. 三数之和 时间复杂度: O ( n 2 ) O(n^2) O(n2) &#x1f351; AC code class Solution {public List<List<Integer>> threeSum(int[] nums) {List<List<Integer>> res new ArrayList<>();int len nums.length; if(len < 3…

【ARM Trace32(劳特巴赫) 使用介绍 2 -- Trace32 cmm 脚本基本语法及常用命令】

文章目录 Trace32 CMM 概述1.1 Trace32 系统命令 SYStem1.1.1 Trace32 SYStem.CONFIG1.1.2 SYStem.MemAccess1.1.3 SYStem.Mode1.1.3.1 TRST-Resets the JTAG TAP controller and the CPU internal debug logic1.1.3.2 SRST- Resets the CPU core and peripherals 1.2 Trace32 …

PostgreSQL InvalidMessage Cache 同步机制

文章目录 背景InvalidMessages 基本类型InvalidMessages 数据结构概览共享内存 的 "ring-buffer" 结构Backend 本地的 InvalidMessages管理SharedInvalCatalogMsgSharedInvalCatcacheMsgSharedInvalRelcacheMsgSharedInvalSnapshotMsgSharedInvalSmgrMsgSharedInvalR…

【软考】14.3 设计模式

《设计模式》 有下划线&#xff1a;类模式 / 对象模式无下划线&#xff1a;对象模式 创建型 设计模式 创建对象 构建器&#xff08;Builder&#xff09;&#xff1a;类和构造分离抽象工厂&#xff08;Abstract Factory&#xff09;&#xff1a;抽象接口工厂&#xff08;Factor…

ChatGPT 驱动软件开发:AI 在软件研发全流程中的革新与实践

目录 内容简介作者简介专家推荐读者对象目录直播预告 计算机技术的发展和互联网的普及&#xff0c;使信息处理和传输变得更加高效&#xff0c;极大地改变了金融、商业、教育、娱乐等领域的运作方式。数据分析、人工智能和云计算等新兴技术&#xff0c;也在不断地影响和改变着各…

怎么搭建一个蛋糕店小程序?

在当今的移动互联网时代&#xff0c;很多企业纷纷选择了小程序作为推广和销售的利器。对于蛋糕店来说&#xff0c;创建一个小程序可以提高品牌知名度&#xff0c;增加销售渠道。下面&#xff0c;我们以【乔拓云】第三方平台为例&#xff0c;来介绍一个完整蛋糕店小程序的制作流…

TiDB x 汉口银行丨分布式数据库应用实践

汉口银行是一家城市商业银行&#xff0c;近年来专注科技金融、民生金融等领域。在数据库国产化改造中&#xff0c;汉口银行引入了 TiDB 数据库&#xff0c;并将其应用在重要业务系统&#xff1a;头寸系统中&#xff0c;实现了一栈式的数据服务&#xff0c;同时满足了高并发、低…

【0基础学Java第四课】-- 逻辑控制

4. 逻辑控制 4.1 顺序结构4.2 分支结构4.2.1 if语句判断一个数字是奇数还是偶数判断一个数字是正数&#xff0c;负数&#xff0c;还是零判断一个年份是否为闰年 4.2.2 switch 语句 4.3 while循环打印 1 - 10 的数字计算 1 - 100 的和计算 5 的阶乘计算1&#xff01;2&#xff0…

如何查看多开的逍遥模拟器的adb连接端口号

逍遥模拟器默认端口号为&#xff1a;21503。 不过&#xff0c;使用多开器多开的时候&#xff0c;端口就不一定是21503了。 如何查看&#xff1f; 进入G:\xiaoyao\Microvirt\MEmu\MemuHyperv VMs路径中 每多开一个模拟器&#xff0c;就会多出一个文件夹。 进入你要查找端口号…

MATLAB R2018b详细安装教程(附资源)

云盘链接&#xff1a; pan.baidu.com/s/1SsfNtlG96umfXdhaEOPT1g 提取码&#xff1a;1024 大小&#xff1a;11.77GB 安装环境&#xff1a;Win10/Win8/Win7 安装步骤&#xff1a; 1.鼠标右击【R2018b(64bit)】压缩包选择【解压到 R2018b(64bit)】 2.打开解压后的文件夹中的…

leetcode:1207. 独一无二的出现次数(python3解法)

难度&#xff1a;简单 给你一个整数数组 arr&#xff0c;请你帮忙统计数组中每个数的出现次数。 如果每个数的出现次数都是独一无二的&#xff0c;就返回 true&#xff1b;否则返回 false。 示例 1&#xff1a; 输入&#xff1a;arr [1,2,2,1,1,3] 输出&#xff1a;true 解释&…

RabbitMQ消息中间件

一、初始MQ 首先了解一下微服务间通讯有同步和异步两种方式&#xff1a;- 同步通讯&#xff1a;是指两个或多个系统在进行信息交换时&#xff0c;必须在同一时刻进行操作 - 异步通讯&#xff1a;是指两个或多个系统之间的通讯方式&#xff0c;其中发送方和接收方不是在同一时刻…

Hadoop学习总结(搭建Hadoop集群(伪分布式模式))

如果前面有搭建过Hadoop集群完全分布式模式&#xff0c;现在搭建Hadoop伪分布式模式可以选择直接克隆完全分布式模式中的主节点(hadoop001)。以下是在搭建过完全分布式模式下的Hadoop集群的情况进行 伪分布式模式下的Hadoop功能与完全分布式模式下的Hadoop功能相同。 一、克隆…