Redis使用总结(四、处理延时任务)

2023-11-18

引言

 

在开发中,往往会遇到一些关于延时任务的需求。例如

 

  • 生成订单30分钟未支付,则自动取消

  • 生成订单60秒后,给用户发短信

 

对上述的任务,我们给一个专业的名字来形容,那就是延时任务。那么这里就会产生一个问题,这个延时任务和定时任务的区别究竟在哪里呢?一共有如下几点区别

 

  1. 定时任务有明确的触发时间,延时任务没有

  2. 定时任务有执行周期,而延时任务在某事件触发后一段时间内执行,没有执行周期

  3. 定时任务一般执行的是批处理操作是多个任务,而延时任务一般是单个任务

 

下面,我们以判断订单是否超时为例,进行方案分析

 

方案分析

 

(1)数据库轮询

 

思路

 

该方案通常是在小型项目中使用,即通过一个线程定时的去扫描数据库,通过订单时间来判断是否有超时的订单,然后进行update或delete等操作

 

实现

 

博主当年早期是用quartz来实现的(实习那会的事),简单介绍一下


maven项目引入一个依赖如下所示

 

    <dependency>

        <groupId>org.quartz-scheduler</groupId>

        <artifactId>quartz</artifactId>

        <version>2.2.2</version>

    </dependency>

 

调用Demo类MyJob如下所示

 

package com.rjzheng.delay1;

 

import org.quartz.JobBuilder;

import org.quartz.JobDetail;

import org.quartz.Scheduler;

import org.quartz.SchedulerException;

import org.quartz.SchedulerFactory;

import org.quartz.SimpleScheduleBuilder;

import org.quartz.Trigger;

import org.quartz.TriggerBuilder;

import org.quartz.impl.StdSchedulerFactory;

import org.quartz.Job;

import org.quartz.JobExecutionContext;

import org.quartz.JobExecutionException;

 

public class MyJob implements Job {

    public void execute(JobExecutionContext context)

            throws JobExecutionException {

        System.out.println("要去数据库扫描啦。。。");

    }

 

    public static void main(String[] args) throws Exception {

        // 创建任务

        JobDetail jobDetail = JobBuilder.newJob(MyJob.class)

                .withIdentity("job1", "group1").build();

        // 创建触发器 每3秒钟执行一次

        Trigger trigger = TriggerBuilder

                .newTrigger()

                .withIdentity("trigger1", "group3")

                .withSchedule(

                        SimpleScheduleBuilder.simpleSchedule()

                                .withIntervalInSeconds(3).repeatForever())

                .build();

        Scheduler scheduler = new StdSchedulerFactory().getScheduler();

        // 将任务及其触发器放入调度器

        scheduler.scheduleJob(jobDetail, trigger);

        // 调度器开始调度任务

        scheduler.start();

    }

}

 

运行代码,可发现每隔3秒,输出如下

 

要去数据库扫描啦。。。

 

优缺点

 

优点:简单易行,支持集群操作

 

缺点:(1)对服务器内存消耗大

(2)存在延迟,比如你每隔3分钟扫描一次,那最坏的延迟时间就是3分钟

(3)假设你的订单有几千万条,每隔几分钟这样扫描一次,数据库损耗极大

 

(2)JDK的延迟队列

 

思路

 

该方案是利用JDK自带的DelayQueue来实现,这是一个无界阻塞队列,该队列只有在延迟期满的时候才能从中获取元素,放入DelayQueue中的对象,是必须实现Delayed接口的。


DelayedQueue实现工作流程如下图所示

 

 

其中Poll():获取并移除队列的超时元素,没有则返回空


take():获取并移除队列的超时元素,如果没有则wait当前线程,直到有元素满足超时条件,返回结果。

 

实现

 

定义一个类OrderDelay实现Delayed,代码如下

 

package com.rjzheng.delay2;

 

import java.util.concurrent.Delayed;

import java.util.concurrent.TimeUnit;

 

public class OrderDelay implements Delayed {

    

    private String orderId;

    private long timeout;

 

    OrderDelay(String orderId, long timeout) {

        this.orderId = orderId;

        this.timeout = timeout + System.nanoTime();

    }

 

    public int compareTo(Delayed other) {

        if (other == this)

            return 0;

        OrderDelay t = (OrderDelay) other;

        long d = (getDelay(TimeUnit.NANOSECONDS) - t

                .getDelay(TimeUnit.NANOSECONDS));

        return (d == 0) ? 0 : ((d < 0) ? -1 : 1);

    }

 

    // 返回距离你自定义的超时时间还有多少

    public long getDelay(TimeUnit unit) {

        return unit.convert(timeout - System.nanoTime(),TimeUnit.NANOSECONDS);

    }

 

    void print() {

        System.out.println(orderId+"编号的订单要删除啦。。。。");

    }

}

 

运行的测试Demo为,我们设定延迟时间为3秒

 

package com.rjzheng.delay2;

 

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.DelayQueue;

import java.util.concurrent.TimeUnit;

 

public class DelayQueueDemo {

     public static void main(String[] args) {  

            // TODO Auto-generated method stub  

            List<String> list = new ArrayList<String>();  

            list.add("00000001");  

            list.add("00000002");  

            list.add("00000003");  

            list.add("00000004");  

            list.add("00000005");  

            DelayQueue<OrderDelay> queue = newDelayQueue<OrderDelay>();  

            long start = System.currentTimeMillis();  

            for(int i = 0;i<5;i++){  

                //延迟三秒取出

                queue.put(new OrderDelay(list.get(i),  

                        TimeUnit.NANOSECONDS.convert(3,TimeUnit.SECONDS)));  

                    try {  

                         queue.take().print();  

                         System.out.println("After " +  

                                 (System.currentTimeMillis()-start) + " MilliSeconds");  

                } catch (InterruptedException e) {  

                    // TODO Auto-generated catch block  

                    e.printStackTrace();  

                }  

            }  

        }  

    

}

 

输出如下

 

00000001编号的订单要删除啦。。。。

After 3003 MilliSeconds

00000002编号的订单要删除啦。。。。

After 6006 MilliSeconds

00000003编号的订单要删除啦。。。。

After 9006 MilliSeconds

00000004编号的订单要删除啦。。。。

After 12008 MilliSeconds

00000005编号的订单要删除啦。。。。

After 15009 MilliSeconds

 

可以看到都是延迟3秒,订单被删除

 

优缺点

 

优点:效率高,任务触发时间延迟低。


缺点:(1)服务器重启后,数据全部消失,怕宕机
(2)集群扩展相当麻烦
(3)因为内存条件限制的原因,比如下单未付款的订单数太多,那么很容易就出现OOM异常
(4)代码复杂度较高

(3)时间轮算法

 

思路

 

先上一张时间轮的图(这图到处都是啦)

 

 

时间轮算法可以类比于时钟,如上图箭头(指针)按某一个方向按固定频率轮动,每一次跳动称为一个 tick。这样可以看出定时轮由个3个重要的属性参数,ticksPerWheel(一轮的tick数),tickDuration(一个tick的持续时间)以及 timeUnit(时间单位),例如当ticksPerWheel=60,tickDuration=1,timeUnit=秒,这就和现实中的始终的秒针走动完全类似了。

 

如果当前指针指在1上面,我有一个任务需要4秒以后执行,那么这个执行的线程回调或者消息将会被放在5上。那如果需要在20秒之后执行怎么办,由于这个环形结构槽数只到8,如果要20秒,指针需要多转2圈。位置是在2圈之后的5上面(20 % 8 + 1)

 

实现

 

我们用Netty的HashedWheelTimer来实现


给Pom加上下面的依赖

 

        <dependency>

            <groupId>io.netty</groupId>

            <artifactId>netty-all</artifactId>

            <version>4.1.24.Final</version>

        </dependency>

 

测试代码HashedWheelTimerTest如下所示

 

package com.rjzheng.delay3;

 

import io.netty.util.HashedWheelTimer;

import io.netty.util.Timeout;

import io.netty.util.Timer;

import io.netty.util.TimerTask;

 

import java.util.concurrent.TimeUnit;

 

public class HashedWheelTimerTest {

    static class MyTimerTask implements TimerTask{

        boolean flag;

        public MyTimerTask(boolean flag){

            this.flag = flag;

        }

        public void run(Timeout timeout) throws Exception {

            // TODO Auto-generated method stub

             System.out.println("要去数据库删除订单了。。。。");

             this.flag =false;

        }

    }

    public static void main(String[] argv) {

        MyTimerTask timerTask = new MyTimerTask(true);

        Timer timer = new HashedWheelTimer();

        timer.newTimeout(timerTask, 5, TimeUnit.SECONDS);

        int i = 1;

        while(timerTask.flag){

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                // TODO Auto-generated catch block

                e.printStackTrace();

            }

            System.out.println(i+"秒过去了");

            i++;

        }

    }

}

 

输出如下

 

1秒过去了

2秒过去了

3秒过去了

4秒过去了

5秒过去了

要去数据库删除订单了。。。。

6秒过去了

 

优缺点

 

优点:效率高,任务触发时间延迟时间比delayQueue低,代码复杂度比delayQueue低。

 

缺点:(1)服务器重启后,数据全部消失,怕宕机

(2)集群扩展相当麻烦

(3)因为内存条件限制的原因,比如下单未付款的订单数太多,那么很容易就出现OOM异常

 

(4)redis缓存

 

- 思路一

 

利用redis的zset,zset是一个有序集合,每一个元素(member)都关联了一个score,通过score排序来取集合中的值

 

添加元素:ZADD key score member [[score member] [score member] …]

按顺序查询元素:ZRANGE key start stop [WITHSCORES]

查询元素score:ZSCORE key member

移除元素:ZREM key member [member …]

 

测试如下

 

# 添加单个元素

 

redis> ZADD page_rank 10 google.com

(integer) 1

 

 

# 添加多个元素

 

redis> ZADD page_rank 9 baidu.com 8 bing.com

(integer) 2

 

redis> ZRANGE page_rank 0 -1 WITHSCORES

1) "bing.com"

2) "8"

3) "baidu.com"

4) "9"

5) "google.com"

6) "10"

 

# 查询元素的score值

redis> ZSCORE page_rank bing.com

"8"

 

# 移除单个元素

 

redis> ZREM page_rank google.com

(integer) 1

 

redis> ZRANGE page_rank 0 -1 WITHSCORES

1) "bing.com"

2) "8"

3) "baidu.com"

4) "9"

 

那么如何实现呢?我们将订单超时时间戳与订单号分别设置为score和member,系统扫描第一个元素判断是否超时,具体如下图所示

 

 

实现一

 

package com.rjzheng.delay4;

 

import java.util.Calendar;

import java.util.Set;

 

import redis.clients.jedis.Jedis;

import redis.clients.jedis.JedisPool;

import redis.clients.jedis.Tuple;

 

public class AppTest {

    private static final String ADDR = "127.0.0.1";

    private static final int PORT = 6379;

    private static JedisPool jedisPool = new JedisPool(ADDR, PORT);

    

    public static Jedis getJedis() {

       return jedisPool.getResource();

    }

    

    //生产者,生成5个订单放进去

    public void productionDelayMessage(){

        for(int i=0;i<5;i++){

            //延迟3秒

            Calendar cal1 = Calendar.getInstance();

            cal1.add(Calendar.SECOND, 3);

            int second3later = (int) (cal1.getTimeInMillis() / 1000);

            AppTest.getJedis().zadd("OrderId",second3later,"OID0000001"+i);

            System.out.println(System.currentTimeMillis()+"ms:redis生成了一个订单任务:订单ID为"+"OID0000001"+i);

        }

    }

    

    //消费者,取订单

    public void consumerDelayMessage(){

        Jedis jedis = AppTest.getJedis();

        while(true){

            Set<Tuple> items = jedis.zrangeWithScores("OrderId", 0, 1);

            if(items == null || items.isEmpty()){

                System.out.println("当前没有等待的任务");

                try {

                    Thread.sleep(500);

                } catch (InterruptedException e) {

                    // TODO Auto-generated catch block

                    e.printStackTrace();

                }

                continue;

            }

            int  score = (int) ((Tuple)items.toArray()[0]).getScore();

            Calendar cal = Calendar.getInstance();

            int nowSecond = (int) (cal.getTimeInMillis() / 1000);

            if(nowSecond >= score){

                String orderId = ((Tuple)items.toArray()[0]).getElement();

                jedis.zrem("OrderId", orderId);

                System.out.println(System.currentTimeMillis() +"ms:redis消费了一个任务:消费的订单OrderId为"+orderId);

            }

        }

    }

    

    public static void main(String[] args) {

        AppTest appTest =new AppTest();

        appTest.productionDelayMessage();

        appTest.consumerDelayMessage();

    }

    

}

 

此时对应输出如下

 

 

可以看到,几乎都是3秒之后,消费订单。

 

然而,这一版存在一个致命的硬伤,在高并发条件下,多消费者会取到同一个订单号,我们上测试代码ThreadTest

 

package com.rjzheng.delay4;

 

import java.util.concurrent.CountDownLatch;

 

public class ThreadTest {

    private static final int threadNum = 10;

    private static CountDownLatch cdl = newCountDownLatch(threadNum);

    static class DelayMessage implements Runnable{

        public void run() {

            try {

                cdl.await();

            } catch (InterruptedException e) {

                // TODO Auto-generated catch block

                e.printStackTrace();

            }

            AppTest appTest =new AppTest();

            appTest.consumerDelayMessage();

        }

    }

    public static void main(String[] args) {

        AppTest appTest =new AppTest();

        appTest.productionDelayMessage();

        for(int i=0;i<threadNum;i++){

            new Thread(new DelayMessage()).start();

            cdl.countDown();

        }

    }

}

 

输出如下所示

 

 

显然,出现了多个线程消费同一个资源的情况。

 

解决方案

 

(1)用分布式锁,但是用分布式锁,性能下降了,该方案不细说。

 

(2)对ZREM的返回值进行判断,只有大于0的时候,才消费数据,于是将consumerDelayMessage()方法里的

 

if(nowSecond >= score){

    String orderId = ((Tuple)items.toArray()[0]).getElement();

    jedis.zrem("OrderId", orderId);

    System.out.println(System.currentTimeMillis()+"ms:redis消费了一个任务:消费的订单OrderId为"+orderId);

}

 

修改为

 

if(nowSecond >= score){

    String orderId = ((Tuple)items.toArray()[0]).getElement();

    Long num = jedis.zrem("OrderId", orderId);

    if( num != null && num>0){

        System.out.println(System.currentTimeMillis()+"ms:redis消费了一个任务:消费的订单OrderId为"+orderId);

    }

}

 

在这种修改后,重新运行ThreadTest类,发现输出正常了

 

- 思路二

 

该方案使用redis的Keyspace Notifications,中文翻译就是键空间机制,就是利用该机制可以在key失效之后,提供一个回调,实际上是redis会给客户端发送一个消息。是需要redis版本2.8以上。

 

实现二

 

在redis.conf中,加入一条配置

 

notify-keyspace-events Ex

 

运行代码如下

 

package com.rjzheng.delay5;

 

import redis.clients.jedis.Jedis;

import redis.clients.jedis.JedisPool;

import redis.clients.jedis.JedisPubSub;

 

public class RedisTest {

    private static final String ADDR = "127.0.0.1";

    private static final int PORT = 6379;

    private static JedisPool jedis = new JedisPool(ADDR, PORT);

    private static RedisSub sub = new RedisSub();

 

    public static void init() {

        new Thread(new Runnable() {

            public void run() {

                jedis.getResource().subscribe(sub, "__keyevent@0__:expired");

            }

        }).start();

    }

 

    public static void main(String[] args) throws InterruptedException {

        init();

        for(int i =0;i<10;i++){

            String orderId = "OID000000"+i;

            jedis.getResource().setex(orderId, 3, orderId);

            System.out.println(System.currentTimeMillis()+"ms:"+orderId+"订单生成");

        }

    }

    

    static class RedisSub extends JedisPubSub {

        <ahref='http://www.jobbole.com/members/wx610506454'>@Override</a>

        public void onMessage(String channel, String message) {

            System.out.println(System.currentTimeMillis()+"ms:"+message+"订单取消");

        }

    }

}

 

输出如下

 

 

可以明显看到3秒过后,订单取消了


ps:redis的pub/sub机制存在一个硬伤,官网内容如下

 

原:Because Redis Pub/Sub is fire and forget currently there is no way to use this feature if your application demands reliable notification of events, that is, if your Pub/Sub client disconnects, and reconnects later, all the events delivered during the time the client was disconnected are lost.


翻: Redis的发布/订阅目前是即发即弃(fire and forget)模式的,因此无法实现事件的可靠通知。也就是说,如果发布/订阅的客户端断链之后又重连,则在客户端断链期间的所有事件都丢失了。
因此,方案二不是太推荐。当然,如果你对可靠性要求不高,可以使用。

 

优缺点

 

优点:(1)由于使用Redis作为消息通道,消息都存储在Redis中。如果发送程序或者任务处理程序挂了,重启之后,还有重新处理数据的可能性。
(2)做集群扩展相当方便
(3)时间准确度高
 

缺点:(1)需要额外进行redis维护

(5)使用消息队列

 

我们可以采用rabbitMQ的延时队列。RabbitMQ具有以下两个特性,可以实现延迟队列

 

  • RabbitMQ可以针对Queue和Message设置 x-message-tt,来控制消息的生存时间,如果超时,则消息变为dead letter

  • lRabbitMQ的Queue可以配置x-dead-letter-exchange 和x-dead-letter-routing-key(可选)两个参数,用来控制队列内出现了deadletter,则按照这两个参数重新路由。
    结合以上两个特性,就可以模拟出延迟消息的功能,具体的,我改天再写一篇文章,这里再讲下去,篇幅太长。

 

优缺点

 

优点: 高效,可以利用rabbitmq的分布式特性轻易的进行横向扩展,消息支持持久化增加了可靠性。


缺点:本身的易用度要依赖于rabbitMq的运维.因为要引用rabbitMq,所以复杂度和成本变高

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Redis使用总结(四、处理延时任务) 的相关文章

  • 拿下60亿流量的《惊雷》都是哪些人在听?python帮你统计出来

    前言 本文的文字及图片来源于网络 仅供学习 交流使用 不具有任何商业用途 版权归原作者所有 如有问题请及时联系我们以作处理 作者 听不来喊麦的C君 PS 如果你处于想学Python或者正在学习Python Python的教程不少了吧 但是是
  • phpstudy安装及简单使用

    前言 我们都知道如果要搭建网站 就需要借助Apache或者Nginx的帮助 当然也少不了数据库的使用 我们自行下载Apache和MySQL等数据库 需要我们进行配置才能使用 这对于一些小白非常的不方便 而且出现一些错误不会解决可能会耽搁很长
  • MySQL执行顺序

    mysql执行顺序如下 1 from 阶段 2 where 阶段 3 group by 阶段 4 having 阶段 5 select 阶段 6 order by 阶段 7 limit 阶段 问题 为什么别名不可以使用在where中 但可以
  • 队列以及Java实现

    了解队列 在我们的现实生活中会经常看见队列的出现 如 排队买奶茶 叫号服务和餐厅的排号 我们去餐厅准备吃饭时 由于人多只能排队等待就餐或换一家人少的餐厅 餐厅为了让服务更加的好 就会使用排队系统 排队系统完全模拟了人群排队全过程 通过取票进

随机推荐

  • el-cascader 懒加载回显问题解决

    解决思路 在页面初始化技术后根据组件的ref 直接给组件的inputValue和inputTerxt赋label的值 前提是你之前已经保存过这个值 ref的部分属性 代码
  • 靠Python做副业一年买了房:未来五年的风口行业 ,90%的人都不知道

    程序开发领域有这样一句话 人生苦短 我用Python 这本是开发者大佬Bruce Eckel的金句 Life is short you need Python 有趣的是 很多人并非专职程序员 但却把这句话奉为神谕 所以Python究竟有什么
  • Python中MNE库的脑电地形图绘制

    脑电地形图在进行和 源 相关的分析时很有用 可以直观的看出各个电极的激活情况以及其随时间的变化 在标准的脑电数据中都是有电极的坐标位置的 会用EEGLab的可能对这块比较熟悉了 实际MNE库中也有相关的定义和实现 可以导入外部的电极数据 或
  • top 内存耗用:VSS/RSS/PSS/USS

    转载 http blog csdn net adaptiver article details 7084364 Terms VSS Virtual Set Size 虚拟耗用内存 包含共享库占用的内存 RSS Resident Set Si
  • Python-OpenCv读取,输出图像

    读取图像 调用imread函数生成像素点矩阵 img cv2 imread lll jpg 读图片 print函数查看矩阵 print img 输出图像 调用imshow函数 新建一个show py文件写输出函数分别调用 resize函数设
  • C#中Ilist与list的区别小结

    常见问题 Ilist lt gt 本身只是一个泛型接口 既然是接口当然不能实例化 只能用如下方法 IList
  • python的print打印颜色设置

    1 顺序 显示方式 前景颜色 背景颜色 2 顺序非固定 但尽量按默认书写方式 3 也可以在input中输出使用 4 格式 print 033 显示方式 前景颜色 背景颜色m 033 0m 显示方式 意义 显示方式 默认 0 高亮显示 1 下
  • 读书笔记:Bi-Directional Cascade Network for Perceptual Edge Detection

    目录 摘要 1 介绍 2 相关工作 3 方法 3 1公式 3 2BDCN的架构 3 3网络训练 4 实验 4 1数据集 摘要 利用多尺度表示对于提高不同尺度对象的边缘检测至关重要 为了利用多尺度提取边缘 我们提出了一种全向级联网络 BDCN
  • 仿网页列表的实现

    序言 实现思路 代码实现 效果展现 总结 序言 最近遇到了一项需求 用Android实现网页中的列表效果 首先我便想到了用ListView的方式来实现 由于网页的表格数据通常都是以一行的形式展现的 因此 我把需要展现的数据放在了一行 可是需
  • python中的tcp/ip连接

    参考博客 https blog csdn net c123 sensing article details 81563702 服务端的代码 usr bin python coding UTF 8 import socket sock soc
  • Android很好看的登陆界面(包含详细代码)

    一 这是我自己在做一个小项目的时候做的界面 在这里分享给大家 其实没什么东西 主要是利用了Material Design设计风格 1 在这里给大家安利一下Material Design设计风格 这个组件库的里面的组件和Android原生的组
  • 【SSL证书安全】

    SSL证书介绍 一种数字证书 也被称为 https证书 CA证书 安全证书 服务器证书 或 SSL证书 一般新申请的证书中 由三部分组成 分别为 CA证书 公钥 私钥 术语定义 发送内容 私钥加密过的内容 数字签名 确认发送内容的完整性 h
  • Cobalt Strike(学习笔记)

    Cobalt Strike简介 Cobalt Strike 是一款GUI的框架式渗透工具 集成了端口转发 服务扫描 自动化溢出 多模式端口监听 win exe木马生成 win dll木马生成 java木马生成 office宏病毒生成 木马捆
  • Linux实用工具

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 1 Windows下同步Linux文件 Linux安装Samba和配置 场景需求 安装了Ubuntu在虚拟机上 但是代码编辑或者其它更多的操作的时候 还是习惯在window
  • @Autowired注解的实现原理

    Autowired注解用法 在分析这个注解的实现原理之前 我们不妨先来回顾一下 Autowired注解的用法 将 Autowired注解应用于构造函数 如以下示例所示 public class MovieRecommender privat
  • 3dmax2014卸载/安装失败/如何彻底卸载清除干净3dmax2014注册表和文件的方法

    3dmax2014提示安装未完成 某些产品无法安装该怎样解决呢 一些朋友在win7或者win10系统下安装3dmax2014失败提示3dmax2014安装未完成 某些产品无法安装 也有时候想重新安装3dmax2014的时候会出现本电脑win
  • 【电子电路】逻辑章

    人不能 至少不应该 一天速成电子电路 不全 漏掉的那部分是常识或者 真的是眼睛不好了 BCD码 即8421码 2421码 余3码 平常的二进制就是8421码 其余以此类推 符号位 1100 正 1101 负 可靠性编码 奇偶校验码 奇校验
  • H5页面与vue的客户端交互

    工作中经常遇到一些奇怪的东西 我有个这样的需求 就是我写的vue项目被嵌套在别的h5项目页面下 so进入我的页面前需要判断他的h5页面有没有登录 这时候就需要我的客户端页面调用h5页面的登录方法 客户端页面 ios 安卓 两种都得交互h5页
  • 海康威视系统未连接服务器,ivms-4200客户端登入不了云服务器

    ivms 4200客户端登入不了云服务器 内容精选 换一换 本章节为您介绍以下内容 准备弹性云服务器作为GDS服务器在使用GDS导入导出数据之前 需要准备一台或多台与GaussDB DWS 集群在相同VPC内的Linux弹性云服务器 简称E
  • Redis使用总结(四、处理延时任务)

    引言 在开发中 往往会遇到一些关于延时任务的需求 例如 生成订单30分钟未支付 则自动取消 生成订单60秒后 给用户发短信 对上述的任务 我们给一个专业的名字来形容 那就是延时任务 那么这里就会产生一个问题 这个延时任务和定时任务的区别究竟