问题
最近遇到的需求是,event主题库服务会接收其他N个服务发送来的http或者mq的更新请求,但是event主题库服务后续是多实例部署,就存才 同时更新一单数据的风险,需要保证单线程操作更新业务,查阅了资料:实现逻辑是redis实现多实例锁,多线程并发等待,直接上代码,,
1,初始化线程池
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorUtil {
/**
* 线程数
*/
private static final int CORE_SIZE = 10;
/**
* 最大值
*/
private static final int MAX_SIZE = 20;
/**
* 线程空闲多久进行回收
*/
private static final long KEEP_ALIVE_TIME = 60;
/**
* 阻塞队列的大小
*/
private static final int QUEUE_SIZE = 2000;
/**
* 使用多少的队列
*/
private static int USER_QUEUE_SIZE = 0;
/**
* 使用多少的队列
*
* @return 队列值
*/
public static int getUserQueueSize() {
return USER_QUEUE_SIZE;
}
private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_SIZE, MAX_SIZE, KEEP_ALIVE_TIME,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(QUEUE_SIZE), new ThreadPoolExecutor.AbortPolicy());
public static ThreadPoolExecutor getThreadPool() {
new ThreadPoolExecutor(CORE_SIZE, MAX_SIZE, KEEP_ALIVE_TIME,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(QUEUE_SIZE), new ThreadPoolExecutor.AbortPolicy());
USER_QUEUE_SIZE = threadPool.getQueue().size();
return threadPool;
}
}
2,初始化redis配置(redis的单实例模式)
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@ConfigurationProperties(prefix = "spring.redis")
@Component
@Data
public class RedisProperties {
private String host;
private int port;
private String password;
}
package com.bxt.event.infrastructure.redis;
import lombok.Data;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.Jedis;
import javax.annotation.Resource;
@Configuration
public class RedisConfig {
public static Jedis getJedisCluster(RedisProperties redisProperties) {
// 获取redis的ip及端口号等相关信息
Jedis jedis = new Jedis(redisProperties.getHost(), redisProperties.getPort(), 10000, 10000, false);
jedis.auth(redisProperties.getPassword());
return jedis;
}
}
3,初始化redis锁(redis的api原理自行百度)
import redis.clients.jedis.Jedis;
import java.util.Collections;
public class RedisTool {
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
private static final Long RELEASE_SUCCESS = 1L;
/**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
return LOCK_SUCCESS.equals(result);
}
/**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
return RELEASE_SUCCESS.equals(result);
}
}
4,代码实例(使用)
import com.alibaba.fastjson.JSON;
import com.bxt.common.exception.InteractiveException;
import com.bxt.event.dto.TrafficEventVO;
import com.bxt.event.infrastructure.redis.RedisProperties;
import com.bxt.event.infrastructure.redis.RedisTool;
import com.bxt.event.po.TrafficEventPO;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import redis.clients.jedis.Jedis;
import java.util.Date;
import java.util.UUID;
@Data
@Slf4j
@AllArgsConstructor
public class RedisLockUpdate implements Runnable {
private TrafficEventService trafficEventService;
private String TRAFFIC_UPDATE_LOCKKEY;
private String fanoutExchange;
private RabbitTemplate rabbitTemplate;
private TrafficEventPO param;
private String operation;
private TrafficEventVO source;
private Jedis jedis;
@Override
public void run() {
boolean flag = false;
String uuid = UUID.randomUUID().toString();
while (true) {
if (RedisTool.tryGetDistributedLock(jedis, TRAFFIC_UPDATE_LOCKKEY, uuid, 1000 * 60)) {
try {
//业务逻辑
// ***
} catch (Exception e) {
e.printStackTrace();
} finally {
RedisTool.releaseDistributedLock(jedis, TRAFFIC_UPDATE_LOCKKEY, uuid);
flag = true;
}
}
if (flag) {
break;
}
}
}
}
private void operationTrafficEvent(TrafficEventVO source, String operation) {
TrafficEventPO target = mapStructBase.TrafficEventVOConvertToTrafficEventPO(source);
target.transformData(source);
Jedis jedis = RedisConfig.getJedisCluster(redisProperties);
RedisLockUpdate redisLockUpdate
= new RedisLockUpdate(trafficEventService, TRAFFIC_UPDATE_LOCKKEY, fanoutExchange, rabbitTemplate, target, operation, source, jedis);
ThreadPoolExecutor poolExecutor = ThreadPoolExecutorUtil.getThreadPool();
poolExecutor.execute(redisLockUpdate);
}