聊聊10种延迟任务

盘点JAVA中延时任务的几种实现方式 (1)基于内存,应用重启(或宕机)会导致任务丢失 (2)基于内存存放队列,不支持集群 (3)依据compareTo方法排列队列,调用take阻塞式的取出第一个任务(不调用则不取出),比较不灵活,会影响时间的准确性。(1)基于内存,应用重启(或宕机)会导致任务丢失 (2)基于内存挂起线程实现延时,不支持集群 (3)代码耦合性大,不易维护 (4)一个任务就要新建一个线程绑定任务的执行,容易造成资源浪费。(1)基于内存,应用重启(或宕机)会导致任务丢失 (2)基于内存存放任务,不支持集群。 阅读详情

什么是延迟任务?

顾明思议,我们 把需要延迟执行的任务叫做延迟任务 。

延迟任务的 使用场景 有以下这些:

  1. 红包 24 小时未被查收,需要延迟执退还业务;

  2. 每个月账单日,需要给用户发送当月的对账单;

  3. 订单下单之后 30 分钟后,用户如果没有付钱,系统需要自动取消订单。

等事件都需要使用延迟任务。

延迟任务实现思路分析

延迟任务 实现的关键是在某个时间节点执行某个任务 。基于这个信息我们可以想到 实现延迟任务的手段 有以下两个:

  1. 自己手写一个“死循环”一直判断当前时间节点有没有要执行的任务;

  2. 借助 JDK 或者第三方提供的工具类来实现延迟任务。

而通过 JDK 实现延迟任务我们能想到的关键词是:DelayQueue、ScheduledExecutorService,而第三方提供的延迟任务执行方法就有很多了,例如:Redis、Netty、MQ 等手段。

延迟任务实现

下面我们将结合代码来讲解每种延迟任务的具体实现。

1.无限循环实现延迟任务

此方式我们需要开启一个无限循环一直扫描任务,然后使用一个 Map 集合用来存储任务和延迟执行的时间,实现代码如下:

import java.time.Instant;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 延迟任务执行方法汇总
 */
public class DelayTaskExample {
    // 存放定时任务
    private static Map<String, Long> _TaskMap = new HashMap<>();

    public static void main(String[] args) {
        System.out.println("程序启动时间:" + LocalDateTime.now());
        // 添加定时任务
        _TaskMap.put("task-1", Instant.now().plusSeconds(3).toEpochMilli()); // 延迟 3s

        // 调用无限循环实现延迟任务
        loopTask();
    }

    /**
     * 无限循环实现延迟任务
     */
    public static void loopTask() {
        Long itemLong = 0L;
        while (true) {
            Iterator it = _TaskMap.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry entry = (Map.Entry) it.next();
                itemLong = (Long) entry.getValue();
                // 有任务需要执行
                if (Instant.now().toEpochMilli() >= itemLong) {
                    // 延迟任务,业务逻辑执行
                    System.out.println("执行任务:" + entry.getKey() +
                            " ,执行时间:" + LocalDateTime.now());
                    // 删除任务
                    _TaskMap.remove(entry.getKey());
                }
            }
        }
    }
}

以上程序执行的结果为:

程序启动时间:2020-04-12T18:51:28.188

执行任务:task-1 ,执行时间:2020-04-12T18:51:31.189

可以看出任务延迟了 3s 钟执行了,符合我们的预期。

2.Java API 实现延迟任务

Java API 提供了 两种实现延迟任务的方法:DelayQueue 和 ScheduledExecutorService。

① ScheduledExecutorService 实现延迟任务

我们可以使用 ScheduledExecutorService 来以固定的频率一直执行任务,实现代码如下:

public class DelayTaskExample {
    public static void main(String[] args) {
        System.out.println("程序启动时间:" + LocalDateTime.now());
        scheduledExecutorServiceTask();
    }

    /**
     * ScheduledExecutorService 实现固定频率一直循环执行任务
     */
    public static void scheduledExecutorServiceTask() {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.scheduleWithFixedDelay(
                new Runnable() {
                    @Override
                    public void run() {
                        // 执行任务的业务代码
                        System.out.println("执行任务" +
                                " ,执行时间:" + LocalDateTime.now());
                    }
                },
                2, // 初次执行间隔
                2, // 2s 执行一次
                TimeUnit.SECONDS);
    }
}

以上程序执行的结果为:

程序启动时间:2020-04-12T21:28:10.416

执行任务 ,执行时间:2020-04-12T21:28:12.421

执行任务 ,执行时间:2020-04-12T21:28:14.422

......

可以看出使用 ScheduledExecutorService#scheduleWithFixedDelay(...) 方法之后,会以某个频率一直循环执行延迟任务。

② DelayQueue 实现延迟任务

DelayQueue 是一个支持延时获取元素的无界阻塞队列,队列中的元素必须实现 Delayed 接口,并重写 getDelay(TimeUnit) 和 compareTo(Delayed) 方法, DelayQueue 实现延迟队列的完整代码 如下:

public class DelayTest {
    public static void main(String[] args) throws InterruptedException {
        DelayQueue delayQueue = new DelayQueue();
        // 添加延迟任务
        delayQueue.put(new DelayElement(1000));
        delayQueue.put(new DelayElement(3000));
        delayQueue.put(new DelayElement(5000));
        System.out.println("开始时间:" +  DateFormat.getDateTimeInstance().format(new Date()));
        while (!delayQueue.isEmpty()){
            // 执行延迟任务
            System.out.println(delayQueue.take());
        }
        System.out.println("结束时间:" +  DateFormat.getDateTimeInstance().format(new Date()));
    }

    static class DelayElement implements Delayed {
        // 延迟截止时间(单面:毫秒)
        long delayTime = System.currentTimeMillis();
        public DelayElement(long delayTime) {
            this.delayTime = (this.delayTime + delayTime);
        }
        @Override
        // 获取剩余时间
        public long getDelay(TimeUnit unit) {
            return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }
        @Override
        // 队列里元素的排序依据
        public int compareTo(Delayed o) {
            if (this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) {
                return 1;
            } else if (this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS)) {
                return -1;
            } else {
                return 0;
            }
        }
        @Override
        public String toString() {
            return DateFormat.getDateTimeInstance().format(new Date(delayTime));
        }
    }
}

以上程序执行的结果为:

开始时间:2020-4-12 20:40:38

2020-4-12 20:40:39

2020-4-12 20:40:41

2020-4-12 20:40:43

结束时间:2020-4-12 20:40:43

3.Redis 实现延迟任务

使用 Redis 实现延迟任务的方法大体可分为两类:通过 zset 数据判断的方式,和通过键空间通知的方式 。

① 通过数据判断的方式

我们借助 zset 数据类型,把延迟任务存储在此数据集合中,然后在开启一个无线循环查询当前时间的所有任务进行消费,实现代码如下(需要借助 Jedis 框架):

import redis.clients.jedis.Jedis;
import utils.JedisUtils;
import java.time.Instant;
import java.util.Set;

public class DelayQueueExample {
    // zset key
    private static final String _KEY = "myDelayQueue";
    
    public static void main(String[] args) throws InterruptedException {
        Jedis jedis = JedisUtils.getJedis();
        // 延迟 30s 执行(30s 后的时间)
        long delayTime = Instant.now().plusSeconds(30).getEpochSecond();
        jedis.zadd(_KEY, delayTime, "order_1");
        // 继续添加测试数据
        jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_2");
        jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_3");
        jedis.zadd(_KEY, Instant.now().plusSeconds(7).getEpochSecond(), "order_4");
        jedis.zadd(_KEY, Instant.now().plusSeconds(10).getEpochSecond(), "order_5");
        // 开启延迟队列
        doDelayQueue(jedis);
    }

    /**
     * 延迟队列消费
     * @param jedis Redis 客户端
     */
    public static void doDelayQueue(Jedis jedis) throws InterruptedException {
        while (true) {
            // 当前时间
            Instant nowInstant = Instant.now();
            long lastSecond = nowInstant.plusSeconds(-1).getEpochSecond(); // 上一秒时间
            long nowSecond = nowInstant.getEpochSecond();
            // 查询当前时间的所有任务
            Set<String> data = jedis.zrangeByScore(_KEY, lastSecond, nowSecond);
            for (String item : data) {
                // 消费任务
                System.out.println("消费:" + item);
            }
            // 删除已经执行的任务
            jedis.zremrangeByScore(_KEY, lastSecond, nowSecond);
            Thread.sleep(1000); // 每秒轮询一次
        }
    }
}

② 通过键空间通知

默认情况下 Redis 服务器端是不开启键空间通知的,需要我们通过 config set notify-keyspace-events Ex 的命令手动开启,开启键空间通知后,我们就可以拿到每个键值过期的事件,我们利用这个机制实现了给每个人开启一个定时任务的功能,实现代码如下:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import utils.JedisUtils;

public class TaskExample {
    public static final String _TOPIC = "__keyevent@0__:expired"; // 订阅频道名称
    public static void main(String[] args) {
        Jedis jedis = JedisUtils.getJedis();
        // 执行定时任务
        doTask(jedis);
    }

    /**
     * 订阅过期消息,执行定时任务
     * @param jedis Redis 客户端
     */
    public static void doTask(Jedis jedis) {
        // 订阅过期消息
        jedis.psubscribe(new JedisPubSub() {
            @Override
            public void onPMessage(String pattern, String channel, String message) {
                // 接收到消息,执行定时任务
                System.out.println("收到消息:" + message);
            }
        }, _TOPIC);
    }
}

4.Netty 实现延迟任务

Netty 是由 JBOSS 提供的一个 Java 开源框架,它是一个基于 NIO 的客户、服务器端的编程框架,使用 Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty 相当于简化和流线化了网络应用的编程开发过程,例如:基于 TCP 和 UDP 的 socket 服务开发。

可以使用 Netty 提供的工具类 HashedWheelTimer 来实现延迟任务,实现代码如下。

首先在项目中添加 Netty 引用,配置如下:

<!-- https://mvnrepository.com/artifact/io.netty/netty-common -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-common</artifactId>
    <version>4.1.48.Final</version>
</dependency>

Netty 实现的完整代码如下:

public class DelayTaskExample {
    public static void main(String[] args) {
        System.out.println("程序启动时间:" + LocalDateTime.now());
        NettyTask();
    }

    /**
     * 基于 Netty 的延迟任务
     */
    private static void NettyTask() {
        // 创建延迟任务实例
        HashedWheelTimer timer = new HashedWheelTimer(3, // 时间间隔
                TimeUnit.SECONDS,
                100); // 时间轮中的槽数
        // 创建一个任务
        TimerTask task = new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                System.out.println("执行任务" +
                        " ,执行时间:" + LocalDateTime.now());
            }
        };
        // 将任务添加到延迟队列中
        timer.newTimeout(task, 0, TimeUnit.SECONDS);

    }
}

以上程序执行的结果为:

程序启动时间:2020-04-13T10:16:23.033

执行任务 ,执行时间:2020-04-13T10:16:26.118

HashedWheelTimer 是使用定时轮实现的,定时轮其实就是一种环型的数据结构,可以把它想象成一个时钟,分成了许多格子,每个格子代表一定的时间,在这个格子上用一个链表来保存要执行的超时任务,同时有一个指针一格一格的走,走到那个格子时就执行格子对应的延迟任务,如下图所示:

(图片来源于网络)

以上的图片可以理解为,时间轮大小为 8,某个时间转一格(例如 1s),每格指向一个链表,保存着待执行的任务。

5.MQ 实现延迟任务

如果专门开启一个 MQ 中间件来执行延迟任务,就有点杀鸡用宰牛刀般的奢侈了,不过已经有了 MQ 环境的话,用它来实现延迟任务的话,还是可取的。

几乎所有的 MQ 中间件都可以实现延迟任务,在这里更准确的叫法应该叫延队列。本文就使用 RabbitMQ 为例,来看它是如何实现延迟任务的。

RabbitMQ 实现延迟队列的方式有两种:

  • 通过消息过期后进入死信交换器,再由交换器转发到延迟消费队列,实现延迟功能;

  • 使用 rabbitmq-delayed-message-exchange 插件实现延迟功能。

注意:延迟插件 rabbitmq-delayed-message-exchange 是在 RabbitMQ 3.5.7 及以上的版本才支持的,依赖 Erlang/OPT 18.0 及以上运行环境。

由于使用死信交换器比较麻烦,所以推荐使用第二种实现方式 rabbitmq-delayed-message-exchange 插件的方式实现延迟队列的功能。

首先,我们需要下载并安装 rabbitmq-delayed-message-exchange 插件,下载地址:http://www.rabbitmq.com/community-plugins.html

选择相应的对应的版本进行下载,然后拷贝到 RabbitMQ 服务器目录,使用命令 rabbitmq-plugins enable rabbitmq_delayed_message_exchange 开启插件,在使用命令 rabbitmq-plugins list 查询安装的所有插件,安装成功如下图所示:

最后重启 RabbitMQ 服务,使插件生效。

首先,我们先要配置消息队列,实现代码如下:

import com.example.rabbitmq.mq.DirectConfig;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DelayedConfig {
    final static String QUEUE_NAME = "delayed.goods.order";
    final static String EXCHANGE_NAME = "delayedec";
    @Bean
    public Queue queue() {
        return new Queue(DelayedConfig.QUEUE_NAME);
    }

    // 配置默认的交换机
    @Bean
    CustomExchange customExchange() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct");
        //参数二为类型:必须是x-delayed-message
        return new CustomExchange(DelayedConfig.EXCHANGE_NAME, "x-delayed-message", true, false, args);
    }
    // 绑定队列到交换器
    @Bean
    Binding binding(Queue queue, CustomExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(DelayedConfig.QUEUE_NAME).noargs();
    }
}

然后添加增加消息的代码,具体实现如下:

import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;

@Component
public class DelayedSender {
    @Autowired
    private AmqpTemplate rabbitTemplate;

    public void send(String msg) {
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("发送时间:" + sf.format(new Date()));

        rabbitTemplate.convertAndSend(DelayedConfig.EXCHANGE_NAME, DelayedConfig.QUEUE_NAME, msg, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setHeader("x-delay", 3000);
                return message;
            }
        });
    }
}

再添加消费消息的代码:

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;

@Component
@RabbitListener(queues = "delayed.goods.order")
public class DelayedReceiver {
    @RabbitHandler
    public void process(String msg) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("接收时间:" + sdf.format(new Date()));
        System.out.println("消息内容:" + msg);
    }
}

最后,我们使用代码测试一下:

import com.example.rabbitmq.RabbitmqApplication;
import com.example.rabbitmq.mq.delayed.DelayedSender;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.text.SimpleDateFormat;
import java.util.Date;

@RunWith(SpringRunner.class)
@SpringBootTest
public class DelayedTest {

    @Autowired
    private DelayedSender sender;

    @Test
    public void Test() throws InterruptedException {
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
        sender.send("Hi Admin.");
        Thread.sleep(5 * 1000); //等待接收程序执行之后,再退出测试
    }
}

以上程序的执行结果如下:

发送时间:2020-04-13 20:47:51

接收时间:2020-04-13 20:47:54

消息内容:Hi Admin.

从结果可以看出,以上程序执行符合延迟任务的实现预期。

6.使用 Spring 定时任务

如果你使用的是 Spring 或 SpringBoot 的项目的话,可以使用借助 Scheduled 来实现,本文将使用 SpringBoot 项目来演示 Scheduled 的实现,实现我们需要声明开启 Scheduled,实现代码如下:

@SpringBootApplication
@EnableScheduling
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

然后添加延迟任务,实现代码如下:

@Component
public class ScheduleJobs {
    @Scheduled(fixedDelay = 2 * 1000)
    public void fixedDelayJob() throws InterruptedException {
        System.out.println("任务执行,时间:" + LocalDateTime.now());
    }
}

此时当我们启动项目之后就可以看到任务以延迟了 2s 的形式一直循环执行,结果如下:

任务执行,时间:2020-04-13T14:07:53.349

任务执行,时间:2020-04-13T14:07:55.350

任务执行,时间:2020-04-13T14:07:57.351

...

我们也可以使用 Corn 表达式来定义任务执行的频率,例如使用 @Scheduled(cron = "0/4 * * * * ?") 。

7.Quartz 实现延迟任务

Quartz 是一款功能强大的任务调度器,可以实现较为复杂的调度功能,它还支持分布式的任务调度。

我们使用 Quartz 来实现一个延迟任务,首先定义一个执行任务代码如下:

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.time.LocalDateTime;

public class SampleJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext)
            throws JobExecutionException {
        System.out.println("任务执行,时间:" + LocalDateTime.now());
    }
}

在定义一个 JobDetail 和 Trigger 实现代码如下:

import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SampleScheduler {
    @Bean
    public JobDetail sampleJobDetail() {
        return JobBuilder.newJob(SampleJob.class).withIdentity("sampleJob")
                .storeDurably().build();
    }

    @Bean
    public Trigger sampleJobTrigger() {
        // 3s 后执行
        SimpleScheduleBuilder scheduleBuilder =
                SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).withRepeatCount(1);
        return TriggerBuilder.newTrigger().forJob(sampleJobDetail()).withIdentity("sampleTrigger")
                .withSchedule(scheduleBuilder).build();
    }
}

最后在 SpringBoot 项目启动之后开启延迟任务,实现代码如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

/**
 * SpringBoot 项目启动后执行
 */
public class MyStartupRunner implements CommandLineRunner {

    @Autowired
    private SchedulerFactoryBean schedulerFactoryBean;

    @Autowired
    private SampleScheduler sampleScheduler;

    @Override
    public void run(String... args) throws Exception {
        // 启动定时任务
        schedulerFactoryBean.getScheduler().scheduleJob(
                sampleScheduler.sampleJobTrigger());
    }
}

以上程序的执行结果如下:

2020-04-13 19:02:12.331  INFO 17768 --- [  restartedMain] com.example.demo.DemoApplication         : Started DemoApplication in 1.815 seconds (JVM running for 3.088)

任务执行,时间:2020-04-13T19:02:15.019

从结果可以看出在项目启动 3s 之后执行了延迟任务。

总结

本文讲了延迟任务的使用场景,以及延迟任务的 10 种实现方式:

  1. 手动无线循环;

  2. ScheduledExecutorService;

  3. DelayQueue;

  4. Redis zset 数据判断的方式;

  5. Redis 键空间通知的方式;

  6. Netty 提供的 HashedWheelTimer 工具类;

  7. RabbitMQ 死信队列;

  8. RabbitMQ 延迟消息插件 rabbitmq-delayed-message-exchange;

  9. Spring Scheduled;

  10. Quartz。

定时任务和延迟任务,Java中实现延迟任务 定时任务指的是在预定的时间或间隔内执行特定任务或代码的过程。它是一种常见的编程需求,用于执行周期性的操作、定时触发事件以及处理需要延迟执行的任务。在软件开发中,定时任务通常用于处理后台数据处理、系统维护、报表生成、数据备份等任务。 阅读详情

相关推荐

java实现延迟/超时/定时

方法获取任务的时候,会拿到队列头部的元素,也就是队列中最早需要被执行的任务,通过getDelay返回值判断任务是否需要被立刻执行,如果需要的话,就返回任务,如果不需要就会等待这个任务到延迟时间的剩余时间,当时间到了就会将任务返回。,list数据类型,被称为目标队列,这个里面存放的任务都是已经到了延迟时间的,可以被消费者获取的任务,所以上面demo中的RBlockingQueue的take方法是从这个目标队列中获取到任务的。这里面就有这么一个channel叫做。图中channel理解成MQ中的topic。

yuzheh521的博客 4679

【应用篇】---史上最全的延迟任务实现方式汇总!

不用谢我,送人玫瑰,手有余香。相信接下来的内容一定不会让你失望,因为它将是目前市面上最好的关于“延迟任务”的文章,这也一直是我写作追求的目标,让我的每一篇文章都比市面上的好那么一点点。 好了,话不多说,直接进入今天的主题,本文的主要内容如下图所示: 什么是延迟任务? 顾明思议,我们把需要延迟执行的任务叫做延迟任务。 延迟任务的使用场景有以下这些: 红包 24 小时未被查收,需要延...

没有伞的孩子必须努力奔跑!—小林 1397

说完了 xxl-job 的执行器原理,再来聊聊调度中心是如何调度任务的

前言 在上一篇 xxl-job 执行器原理分析 一文中,我们提到了 xxl-job 框架中包含了两个核心模块:调度中心 和 执行器, 其中调度中心主要负责 任务的调度 , 而执行器负责 任务的执行, 两者各司其职。 紧接着我们通过画图的方式对 执行器 的内部构造进行了分析,并且还对 Job 的执行流程进行了梳理。 本文我们继续围绕任务的调度流程对 调度中心 进行剖析, 内容依然参照 xx-job v2.x 版本的源码。 正文 再看一遍 xxl-job 架构图: 调度中心主要提供了两个功能: 系统管理 和

nimo10050的博客 4829

实战:10 种实现延迟任务的方法,附代码!

您好,我是路人,更多优质文章见个人博客:http://itsoku.com接下来的内容一定不会让你失望,因为它将是目前市面上最好的关于“延迟任务”的文章,好了,话不多说,直接进入今天的主题,本文的主要内容如下图所示:什么是延迟任务? 顾明思议,我们把需要延迟执行的任务叫做延迟任务。延迟任务的使用场景有以下这些:红包 24 小时未被查收,需要延迟执退还业务;每个月账单日,需要给用户发送当月的对账单;...

路人甲Java 2882

深入理解Modbus通信中的延迟机制

在工业自动化领域摸爬滚打多年,我发现很多工程师在使用Modbus通信时经常遇到一个令人困惑的问题:为什么有时候通信会不稳定?数据丢失?设备无响应?其实,这些问题往往都与"延迟"这个看似简单却内涵丰富的概念有关。今天,我想从实战角度出发,和大家聊聊Modbus通信中的延迟机制,希望能帮助大家写出更稳定、更高效的工业通信程序。Modbus通信中的延迟,看似简单的一个"等待",实际上涉及硬件特性、协议规范、设备性能、网络环境等多个层面。理解这些延迟的本质和机理,是写出稳定可靠工业通信程序的关键。

m0_53548081的博客 1453

聊聊C#中将子任务附加到父任务

如果你想创建一个父任务(父Task)来监控一组子任务(子Task),你可以使用 Task.WhenAll 或 Task.WhenAny 方法来等待子任务的完成,或者使用 TaskCreationOptions.AttachedToParent 选项在创建子任务时将其附加到父任务。在这个例子中,父任务使用 Task.Run 启动,并且它等待两个子任务通过 Task.WhenAll 完成。// 等待父任务完成(这也会等待所有子任务,因为子任务被附加到了父任务)// 等待父任务完成(这也会等待所有子任务)

java专栏 403

3招搞定!Redis如何高效实现延迟任务?

延迟任务(Delayed Task)是指在未来的某个时间点执行的任务。换句话说,延迟任务是一种计划任务,它被安排在特定的时间后执行,而不是立即执行。通过本文的详细介绍,我们学会了如何使用 Redis 实现延迟任务,包括使用过期键的事件通知、使用 ZSet 和使用 Redisson 内置的延时队列。希望这篇文章能帮助你在面试中轻松应对这类问题,让你的数据管理更加得心应手!😄如果你有任何问题或建议,欢迎在评论区留言交流!😊。

java专栏 1878

SystemRDL:从寄存器描述到自动化生成的完整指南

本文详细介绍了SystemRDL如何通过“一份描述,多处生成”的理念,彻底解决芯片与FPGA设计中手动维护寄存器带来的不一致与低效问题。文章提供了从语言快速上手、核心组件讲解到使用PeakRDL工具链实现自动化代码与文档生成的完整实战指南,帮助硬件工程师构建高效、可靠的寄存器开发流程。

weixin_29243279的博客 664

PowerJob时间轮实战:从Netty到Kafka,聊聊那些年我们用过的延迟任务调度方案

本文深入解析PowerJob时间轮架构,从设计哲学到场景化选型指南,详细探讨了延迟任务调度的技术演进与实现细节。通过对比Netty、Kafka等框架的时间轮方案,重点介绍了PowerJob独创的双层时间轮设计及其在工业级应用中的优化策略,为开发者提供高效的延迟任务调度解决方案。

weixin_30644369的博客 196

Python爬虫实战:用Selenium+ChromeDriver批量抓取政府网站政策文件(附完整代码)

本文详细介绍了使用Python的Selenium和ChromeDriver进行政府网站政策文件批量抓取的实战技巧。从环境配置、动态页面元素定位到分页机制破解和反爬虫策略,提供了完整的解决方案和代码示例,帮助开发者高效获取结构化政策数据。

weixin_42531710的博客 266

ORCAD原理图关联封装时出现ERROR(ORCIS-6245)、ERROR(ORCIS-6013)问题

1、当你进行如下操作时: 系统先是提示你出现ERROR(ORCIS-6245)然后再确定后再出现ERROR(ORCIS-6013)问题 是由于你的数据库的路径不对。 具体操作方法如下:1、先找到你的CAPTURE.INI文件。 2、用记事本打开CAPTURE.INI。找到下图路径 3、在你的Orcad capture CIS原理图软件中点击 Options------>CIS con...

weixin_42304393的博客 2万+

原创 | 聊聊延迟满足

今天是2021年第36篇原创,一起进步!今天给大家聊聊延迟满足这件事。我们经常都说遇事要看长远,不满足当下,要坚持长期价值,因为长期价值能让我们未来获得更好的收益,而不是短期收益。这背后...

涛哥聊Python 672

实战指南!基于STM32的智能环境监测系统(代码+硬件设计+云端控制)

本文详细介绍了基于STM32的智能环境监测系统的设计与实现,包括硬件选型、电路设计、软件架构和云端控制。系统通过DHT11温湿度传感器和MQ-2烟雾传感器实时监测环境数据,STM32F103C8T6主控芯片处理数据并通过ESP8266上传至中国移动物联网平台,支持手机APP远程监控。项目成本低、稳定性高,适合智能家居和环境监测应用。

weixin_29194817的博客 433

详解RabbitMQ延迟队列

延迟队列(Delay Queue)是一种特殊类型的队列,它的主要特点是可以让进入队列的元素在指定的延迟时间之后才被取出进行处理。

sosovo66的博客 973

QtQuick QML教程(2):qml语法介绍

​ qml一个普通的文本文件,后缀是.qml, 一个qml文件主要由两部分组成:(1) import语句 :这个就像C++中的头文件, 使用 import 导入qml需要的相关模块,可以查询Qt文档进行导入,注意每个版本的Qt模块声明可能都不同,相比于QWidget项目,QtQuick的变动是很大的;(2) QML对象树 :根对象是Window, 然后可以在Window中添加各种控件,例如按钮,文本,菜单等。

琅嬛福地 551

流体仿真软件:COMSOL Multiphysics二次开发_(11).COMSOL与外部软件的数据交换

通过上述方法,您可以实现 COMSOL Multiphysics 与外部软件的数据交换,从而更好地集成和分析仿真结果。无论是从文本文件导入数据,导出数据到 MATLAB,还是通过 API 和 COMSOL Server 实现实时通信,都能满足不同场景下的需求。希望这些内容对您在流体仿真领域的开发工作有所帮助。

kkchenjj的博客 1553

数据慢半拍,问题可能不在“数据”:聊聊数据传播延迟的那些坑

本文探讨了数据传播延迟问题的定位与优化策略。作者指出,延迟本质是数据从产生到使用的链路问题,而非单点故障。通过实际案例,文章总结了五大常见瓶颈:Kafka消费配置不当、Flink背压、Spark调度延迟、存储IO瓶颈和跨机房网络问题。作者强调应遵循"先定位、再拆解、最后优化"的原则,建议通过打时间戳量化延迟,重点关注P95/P99指标。最后提出,技术团队应建立延迟意识,从业务视角理解"可预期的延迟"比盲目追求零延迟更重要。解决问题的关键在于准确识别延迟环节,而非盲目调

Echo_Wish 655

OpenCV计算机视觉实战(3)——计算机图像处理基础

本文全面介绍了计算机视觉和数字图像处理的基础知识。从最基本的像素概念入手,详细讲解了图像在计算机中的表示方式,包括RGB、CMYK等色彩空间的工作原理及其应用场景。还系统分析了常见图像和视频文件格式的特点,梳理了计算机图像处理技术的发展历程,并重点介绍了OpenCV这一强大的开源计算机视觉库的功能与应用领域。

qq_30167691的博客 1741

低延迟响应任务调度优先级

本文深入探讨嵌入式实时系统中任务调度优先级的设计原则,重点分析抢占式调度机制、中断与任务协同、上下文切换优化及优先级划分策略。通过FreeRTOS实例和实际项目问题,揭示如何实现微秒级响应并避免常见陷阱。

weixin_35238815的博客 456

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

说说RabbitMQ延迟队列实现原理?

使用 RabbitMQ 和 RocketMQ 的人是幸运的,因为这两个 MQ 自身提供了延迟队列的实现,不像用 Kafka 的同学那么苦逼,还要自己实现延迟队列。当然,这都是题外话,今天咱们重点来聊聊 RabbitMQ 延迟队列的实现原理,以及 RabbitMQ 实现延迟队列的优缺点有哪些? 很多人知道使用 RabbitMQ 是可以实现延迟队列的,但对于 RocketMQ 自身也提供了延迟队...

weixin_35744067的博客 251

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

聊聊并发编程的12种业务场景

前言 并发编程是一项非常重要的技术,无论在面试,还是工作中出现的频率非常高。 之前我发表的一篇《聊聊并发编程的10个坑》,在全网广受好评。说明了这类文章还是比较有价值的,接下来,打算继续聊聊并发编程这个话题。 并发编程说白了就是多线程编程,但多线程一定比单线程效率更高? 答:不一定,要看具体业务场景。 毕竟如果使用了多线程,那么线程之间的竞争和抢占cpu资源,线程的上下文切换,也是相对来说比较耗时的操作。 下面这几个问题在面试中,你必定遇到过: 你在哪来业务场景中使用过多线程? 怎么用的? 踩过哪些坑?

2024

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

【并发编程】聊聊定时任务ScheduledThreadPool的实现原理和源码解析

【并发编程】聊聊定时任务ScheduledThreadPool的实现原理和源码解析

芝兰生于深谷,不以无人而不芳 772

别让大数据任务“互相等着死” ——聊聊任务依赖与 DAG 设计的江湖规矩

摘要 大数据任务依赖管理与DAG设计是数据平台的核心挑战。本文剖析了常见问题:任务死锁、数据不一致、补数困难等,根源在于依赖管理不当。提出三大设计原则:1)数据分层天然支持DAG结构(ODS→DWD→DWS→ADS);2)任务原子化,多任务组合实现复杂逻辑;3)显式声明依赖关系。文章还揭示了时间窗口错位、依赖过深、血缘不清等典型陷阱,并给出五条落地建议:固定依赖、分层设计、原子任务、可补数性、透明血缘。最终强调DAG不仅是技术方案,更是工程治理的关键。

Echo_Wish 893

别等系统“凉了”才响铃:聊聊延迟敏感系统的监控与报警设计

摘要:延迟敏感系统(如实时风控、在线交易等)对响应时间极为敏感,但传统监控常忽视延迟问题。文章指出,仅监控CPU、内存等指标远远不够,需重点关注P50/P95/P99等分位数而非平均值,并拆解链路延迟(接口级、中间件、内部处理阶段)。报警设计应贴近业务、持续触发且分级处理,同时配备自动熔断/降级等自救机制。核心观点是:用户感知的延迟才是关键,监控系统需能提前暴露问题并减少人工干预,而非事后背锅。

Echo_Wish 624

大白话-设计RocketMQ延迟消息

说到这里,估计你也能猜到,为什么不支持自定义延迟时间了,核心原因还是性能问题。试想一下,如果设计成任意时间,那么就不可能使用18个队列了,更不可能使用无限个队列了,只可能使用单个队列。但是如果使用单个队列,按照先进先出的存放的话,那出现需要后进先出的消息怎么办?那只能对整个队列进行排序,如果消息量很大,每次有消息进来都需要排序,那CPU肯定会被玩爆。而且队列里的消息被消费后,都会记录偏移量,如果每次有消息进来都要排序,那偏移量则失去意义,增加了消息丢失的风险。

程序员半支烟,wechat:yclxiao 1270
上一篇: 史上最全的mysql数据类型汇总(下)
下一篇: 聊聊Redis内存优化的7个神技
JavaMonsterr
博客等级 码龄4年 211粉丝 481原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值