The first tec blog it's about how to set tasks and then task will be executed at the you time want to happen. so we are going to use redis to handle this . it's a data structure [ZSetOperations] that can pop the biggest score time out when the specific time comes. below is core code of implementing task schedule.

                        class: ConsumeTask
package com.delaytask.demo.consumer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
/**
 * @author WEILIN
 */
@Slf4j
@Service
public class ConsumeTask {
private static final String REDIS_KEY_POST = "task:happen:time";
private RedisTemplate redisTemplate;
private ScheduledExecutorService ex = Executors.newSingleThreadScheduledExecutor();
public ConsumeTask(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}

/**
* task: This design must have practical business meaning. It can also be a combination of json
* string + timestamp. timestamp indicates the time when the task is to be executed regularly.
*/
public void produce(String id, Long timestamp) {
log.info("begin add content :{} to redis, the timestamp is :{}", id, timestamp);
ZSetOperations zso = redisTemplate.opsForZSet();
Boolean add;
if (isNumeric(id)) {
  add = zso.add(REDIS_KEY_POST, Long.parseLong(id), timestamp);
} else {
  add = zso.add(REDIS_KEY_POST, id, timestamp);
}
if (Boolean.TRUE.equals(add)) {
  Long size = zso.size(REDIS_KEY_POST);
  if (size != null) {
    Set> set = zso.rangeWithScores(REDIS_KEY_POST, 0L, size);
    if (set != null) {
      set.forEach(tuple -> log.info("member:{} score:{}", tuple.getValue(), tuple.getScore()));
    }
  }
}
log.info("add task to redis is {}", add);
}


/**
* Executes with a fixed delay, which is the delay between the termination of one execution and
* the start of the next.
*/
public void execTaskTake() {
ex.scheduleWithFixedDelay(new Task(), 1, 1, TimeUnit.SECONDS);
}

public class Task implements Runnable {
@Override
public void run() {
  long time = System.currentTimeMillis();
  ZSetOperations zso = redisTemplate.opsForZSet();
  //Take out overdue tasks and take 30 items at a time. adjust them appropriately here.
  Set objects = zso.rangeByScore(REDIS_KEY_POST, 0, time, 0, 30);
  if (objects == null || objects.isEmpty()) {
    return;
  }
  for (Object task : objects) {
    //And delete the corresponding data key + id in redis
    Long count = zso.remove(REDIS_KEY_POST, task);
    log.info("Task from redis is :{}", task);
    if (count != null && count == 1) {
      try {
        //The real business logic processing is to send a request
        log.info("handle logic ..., vo is {}", task);
      } catch (Exception e) {
        log.error("sendPost have error pls check: {}, send content:{}", e, task);
      } finally {
        log.info("record log here plz ... send content:{}", task);
      }
    }
  }
}
}
public static boolean isNumeric(String str) {
for (int i = str.length(); --i >= 0; ) {
  if (!Character.isDigit(str.charAt(i))) {
    return false;
  }
}
return true;
}
}
                    class:TestExec
package com.delaytask.demo.controller;
import com.google.gson.Gson;
import com.delaytask.demo.consumer.ConsumeTask;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.Data;
/**
 * @author WEILIN
 */
@RestController
public class TestExec {
  @Autowired
  private ConsumeTask task;
  @RequestMapping("/add/{path}")
  public String addData(@PathVariable("path") Integer path) {
    Person person = new Person();
    person.setAge(15);
    person.setName("jack……&¥#werw~`4~");
    long num = 1000L;
    if (path == 1) {
      //1655380824000    1655391624000   1655388538000
      task.produce(new Gson().toJson(person), 1655389018000L);
    } else {
      task.produce(Long.toString(num), 1655384424000L);
    }
    return "success";
  }
}
@Data
class Person {
  private String name;
  private int age;
}