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

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

话不多说,直接进入今天的主题,本文的主要内容如下图所示:

什么是延迟任务?

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

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

  • 红包 24 小时未被查收,需要延迟执退还业务;
  • 每个月账单日,需要给用户发送当月的对账单;
  • 订单下单之后 30 分钟后,用户如果没有付钱,系统需要自动取消订单。

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

延迟任务实现思路分析

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

  • 自己手写一个“死循环”一直判断当前时间节点有没有要执行的任务;
  • 借助 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 种实现方式:

  • 手动无线循环;
  • ScheduledExecutorService;
  • DelayQueue;
  • Redis zset 数据判断的方式;
  • Redis 键空间通知的方式;
  • Netty 提供的 HashedWheelTimer 工具类;
  • RabbitMQ 死信队列;
  • RabbitMQ 延迟消息插件 rabbitmq-delayed-message-exchange;
  • Spring Scheduled;
  • Quartz。
定时任务和延迟任务,Java中实现延迟任务 定时任务指的是在预定的时间或间隔内执行特定任务或代码的过程。它是一种常见的编程需求,用于执行周期性的操作、定时触发事件以及处理需要延迟执行的任务。在软件开发中,定时任务通常用于处理后台数据处理、系统维护、报表生成、数据备份等任务。 阅读详情

相关推荐

java实现延迟/超时/定时

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

yuzheh521的博客 4679

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

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

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

使用Redis如何实现延迟任务?

延迟任务(Delayed Task)是指在未来的某个时间点,执行相应的任务。也就是说,延迟任务是一种计划任务,它被安排在特定的时间后执行,而不是立即执行。延迟任务的常见使用场景有以下几个:1、定时发送通知或消息:发送定时短信、邮件或应用内消息,如注册确认、订单状态更新、促销活动通知等。定时推送新闻、天气预报、股票价格等实时信息。2、异步处理和后台任务:将耗时的操作安排为延迟任务,避免阻塞主线程或用户界面,提高系统的响应性能。执行批量数据处理,如日志分析、数据报表生成等。

胸纳甲兵,烽烟起可靖疆晏海。 1335

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

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

路人甲Java 2880

手把手教你用 Docker 构建自己的 CentOS 基础镜像

目录一、准备CentOS基础镜像二、撰写Dockerfile三、构建Docker镜像四、生成Docker容器 相信码农们经常会听说:虚拟机将成为历史,Docker才是未来 !!! 事实如此,现在大部分公司都在用 Docker 技术,想拥有自己的 Docker 专属镜像,那么 Dockerfile 就是入门,现在让我们一起来看一下 ------ 构建 Docker 镜像的全过程。 好文章 记得收藏...

Nick.Peng's Blogs 1万+

Hypack 应用于地形测量的高级设置操作要领

Hypack 是业界公认最好的水文导航测量软件,是目前世界上应用广泛的专业测量疏浚软件。它能够接收,处理,存储和输出各种数据。Hypack支持绝大部分GPS,测深仪(国内如海鹰HY1603单波束测沙仪),多波束测深仪,旁扫声纳,涌浪补偿器,水位仪,验潮仪以及声速剖面仪。利用 Hypack 可以进行导航,单波束测量,多波束测量,旁扫测量以及疏浚实时监测等。软件界面友好,使用方便。本文简要阐述Hypack 应用于地形测量的几个重要操作要领。

中游鱼的博客 2794

十种实现延迟任务的方案

延迟任务,相信大家都不陌生,很多业务场景都会用到。比如,你在淘宝上买了件商品,收到货物后,很少有人主动去点 “确认收货”,但细心的我们发现,经过若干天后,系统会自动确认收货,完结订单。让我们继续来看看......

吴名氏的博客 15万+

用友U8库龄分析避坑指南:物料出入库日期计算的5个常见错误

本文深入剖析了在用友U8系统中进行库龄分析时,物料出入库日期计算的五个常见陷阱。从数据源选择、业务类型过滤、计价方式逻辑、期初数据处理到查询性能优化,逐一解析错误根源并提供避坑指南,帮助企业获得准确的库龄数据,有效管理库存与资金风险。

weixin_29209801的博客 455

一次搞懂延迟任务及十种延迟任务的实现方式

本文是向大家介绍延迟任务的使用场景,以及延迟任务的十种实现方式,给大家带来有关延迟任务的不同思路和对实现方式的利弊参考。

weixin_43805705的博客 809

DCA决策曲线的解读和代码实现

0.决策曲线 决策曲线分析法(Decision Curve Analysis,DCA)是个与ROC曲线相提并论的相对比较新的模型评价方法。 关于它的原理,长篇大论的医学统计学知识解读实在不是我的强项,放上两个链接偷偷懒: https://www.plob.org/article/12455.html https://www.iikx.com/news/statistics/1622.html 如果想看文章,这篇里的fig7就是:https://www.ncbi.nlm.nih.gov/pmc/artic

weixin_42960896的博客 1万+

基础的pdf病毒木马

基础的pdf病毒木马 环境: 攻击机:Kali 目标操作系统:Windows 10或Windows 7 (关闭防火墙和安全软件) 目标软件:Adobe Reader 8.1.2 msf框架搜索:adobe-pdf 1.打开msfconsole 2.搜索 adobe_pdf模块 3.加载攻击模块并设置payload 4.运行生成pdf 5.将pdf拖入到win10 6.打开msf,加...

小小猪的博客 4624

10种延迟任务实现方式

好了,话不多说,直接进入今天的主题,本文的主要内容如下图所示: 什么是延迟任务? 顾明思议,我们把需要延迟执行的任务叫做延迟任务。 延迟任务的使用场景有以下这些: 红包 24小时未被查收,需要延迟执退还业务; 每个月账单日,需要给用户发送当月的对账单; 订单下单之后 30 分钟后,用户如果没有付钱,系统需要自动取消订单。 等事件都需要使用延迟任务。 延迟任务实现思路分析 延迟任...

MrLee的博客 7102

基于Simulink的伺服电机位置闭环控制仿真

本文带你从零构建了伺服电机位置闭环控制系统​ 的完整Simulink仿真模型,实现了:✅ 搭建PMSM伺服电机、逆变器、机械臂负载的主电路与编码器反馈模型;✅ 用MATLAB Function实现三环控制(位置环PID、坐标变换、电流环PI);✅ 验证阶跃定位(稳态误差0.002°、响应0.08s)与正弦跟踪(误差0.008°)的高性能;✅ 对比开环控制,明确位置闭环在精度、响应、抗扰上的优势;✅ 理解“三环嵌套+反馈校正”的核心思想(精密定位的工程实现)。核心收获。

amy_mhd的博客 173

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

这篇文章的诞生要感谢一位读者,是他让这篇优秀的文章有了和大家见面的机会,重点是优秀文章,哈哈。事情的经过是这样的...

技术专家 668

一文读懂 CST 时/频域求解器

本文系统介绍了CST Studio Suite中的关键网格技术——六面体网格、四面体网格以及TLM求解器专用的八叉树非均匀网格技术。重点阐述了六面体网格的三项核心技术:理想边界拟合(PBA)技术可精确处理曲面边界,薄片技术(TST)能高效模拟薄层结构,自适应八叉树网格实现计算资源最优分配。同时详细对比了六面体网格与四面体网格的特点及适用场景,前者适用于系统级EMC/EMI分析等时域问题,后者擅长处理复杂曲面和异质材料的频域问题。

一只豌豆象的博客 465

基于BenchmarkSQL的达梦数据库TPCC性能测试

中的用户和密码在达梦数据库中创建测试用户,并保证其表空间足够容纳生成的测试数据。如果开启了归档,还要考虑归档日志占用的空间。BenchmarkSQL默认不支持达梦数据库。这里我们让BenchmarkSQL把达梦数据库当作Oracle即可。Oracle数据库对应的测试配置文件为。是使用BenchmarkSQL进行性能测试的主要配置文件。是生成的测试报告,png文件是报告中包含的图片。拷贝达梦数据库驱动到BenchmarkSQL的。测试结束后,会在当前路径下生成一个以。规则命名的结果目录。

Sebastien23的博客 2635

史上最全的延迟任务实现方式汇总!附代码(强烈推荐)

这篇文章的诞生要感谢一位读者,是他让这篇优秀的文章有了和大家见面的机会,重点是优秀文章,哈哈。 事情的经过是这样的... 不用谢我,送人玫瑰,手有余香。相信接下来的内容一定不会让你失望,因为它将是目前市面上最好的关于“延迟任务”的文章,这也一直是我写作追求的目标,让我的每一篇文章都比市面上的好那么一点点。 好了,话不多说,直接进入今天的主题,本文的主要内容如下图所示: 什么是延迟任务? 顾明思...

磊哥聊Java 3227

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Redis实现延迟任务的几种方案

延迟任务(Delayed Task)是指在未来的某个时间点,执行相应的任务。也就是说,延迟任务是一种计划任务,它被安排在特定的时间后执行,而不是立即执行。Redis 本身并没有直接提供延迟任务的功能,但可以通过一些策略和手段,在 Redis 中手动实现延迟任务。

qq_35716689的博客 2万+

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

RabbitMQ实现延迟任务

Component@Autowiredmessage,// 设置延迟时间,单位毫秒});

GQ_666的博客 1384

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

上一篇: 手把手带领小伙伴们写一个分布式事务案例
下一篇: 消息服务:MQ使用场景与选型对比
JavaShark
博客等级 码龄4年 184粉丝 438原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值