1. 业务背景
现有一个任务是通过异步去远程请求一个服务的数据,并等待该服务调用接口返回数据,在这个过程中如何判断请求的数据能够在规定时间内返回呢?
2. 处理方法
2.1 利用Redis来实现超时控制
1)在请求远程方法的时候,将该操作ID保存到数据库中,并设置超时时间
if (parseMap.containsKey("code") && String.valueOf(parseMap.get("code")).equals("201")){
// 缓存到redis中,用于定时器查询接口返回结果
redisTemplate.opsForValue().set(ReceiveService.ESIM_REMOTE_QUEUE_CACHE + uuid, uuid, ReceiveService.ESIM_REMOTE_TIMEOUT, TimeUnit.MINUTES);
}
2)在redis的配置文件中打开配置选项:搜索:notify-keyspace-events Ex 找到后,放开注释,保存。其作用就是开启redis监听key过期通知。
3)针对redis数据丢失事件,进行数据处理。
public class RedisKeyExpirationListener extends MessageListenerAdapter {
@Autowired
private RedisTemplate<String,String> redisTemplate;
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
/**
* 针对redis数据失效事件,进行数据处理
*/
@Override
public void onMessage(Message message, byte[] topic) {
String key = message.toString();
if(StringUtils.isNotBlank(key) && key.contains(ReceiveService.ESIM_REMOTE_QUEUE_CACHE)) {
//去掉前缀,并放入失效列表
key = key.replaceAll(ReceiveService.ESIM_REMOTE_QUEUE_CACHE,"");
redisTemplate.opsForList().rightPush(ReceiveService.ESIM_REMOTE_QUEUE_CACHE, key);
}
}
public boolean lockBySecondsTime(String key, long expirationTime){
// 此方法只适用于在 expirationTime 时间段内进行锁竞争的场景。如果超过 expirationTime 时间段,
// 锁自动失效,之前获取到锁的线程还在运行,就失去了分布式锁的意义,慎重根据自己的场景来使用。
Long timeStamp = new Date().getTime() + (expirationTime * 1000);
// 通过setNx获取锁
return ifAbsent(key, String.valueOf(timeStamp), expirationTime, TimeUnit.SECONDS);
}
public boolean ifAbsent(String key, String value, long expirationTime , TimeUnit timeUnit) {
Boolean res = (Boolean) redisTemplate.execute(new RedisCallback() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
return connection.stringCommands().set(key.getBytes(), value.getBytes(),
Expiration.from(expirationTime, timeUnit), RedisStringCommands.SetOption.ifAbsent());
}
});
return res == null ? false : res;
}
}
4)当数据被放入失效列表的时候,我们需要有一个定时任务随时监听这个列表,并对其进行处理。
@Component
public class RedisKeyExpirationThread implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private TestService testService;
@Autowired
private RedisTemplate redisTemplate;
private static ApplicationContext applicationContext;
//任务处理
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if(event.getApplicationContext().getParent() == null) {
applicationContext = event.getApplicationContext();
// 创建线程
new Thread(new Runnable() {
@Override
public void run() {
Thread.currentThread().setName("RedisKeyExpirationThread");
while (true) {
try {
String key = (String) redisTemplate.opsForList().leftPop(ReceiveService.ESIM_REMOTE_QUEUE_CACHE);
if (StringUtils.isNotBlank(key)) {
System.out.println("Expiration Key: " + key);
key = key.replaceAll(ReceiveService.ESIM_REMOTE_QUEUE_CACHE, ""); //得到操作ID
testService.handleTimeout(key);
}else {
Thread.sleep(10 * 1000);
}
} catch (Exception exception) {
exception.printStackTrace();
} finally {
try {
Thread.sleep(3 * 1000);
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
}).start();
}
}
}
2.2 利用MySQL数据库来实现超时控制
1)在远程调用接口获取数据时,需要保存调用时间;
2)启用一个定时器多久轮询一次,查询数据库的数据;
3)用当前时间减去一开始保存的调用时间,判断是否超时来进行数据处理。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Timer;
import java.util.TimerTask;
public class TimeoutControlWithMySQL {
private static final String URL = "jdbc:mysql://your_host:your_port/your_database";
private static final String USER = "your_username";
private static final String PASSWORD = "your_password";
public static void main(String[] args) {
// 记录调用时间并插入到数据库
long callTime = System.currentTimeMillis();
insertCallTime(callTime);
// 启动定时器进行轮询
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
checkTimeout();
}
}, 0, 5000); // 每 5 秒轮询一次
}
private static void insertCallTime(long callTime) {
try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement stmt = conn.prepareStatement("INSERT INTO call_times (timestamp) VALUES (?)")) {
stmt.setLong(1, callTime);
stmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
private static void checkTimeout() {
try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement stmt = conn.prepareStatement("SELECT timestamp FROM call_times LIMIT 1");
ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
long callTime = rs.getLong("timestamp");
long currentTime = System.currentTimeMillis();
if (currentTime - callTime > 10000) { // 假设超时时间为 10 秒
// 进行超时处理
System.out.println("超时,进行数据处理...");
} else {
System.out.println("未超时");
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}