背景:因为最近应用压力过大,准备改造成单应用的集群。所以定时任务可能会同时对缓存中的数据进行处理,所以需要增加一个锁的方法。这里我将外部循环抽取成公共部分,单个业务处理需要自行集成实现 processObj 方法逻辑。Redis的加解锁方法自行实现。
import com.sun.istack.internal.NotNull;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
public abstract class AbstractCtgRedisLock<T>{
/**
* 添加分布式锁遍历集合 collection, 外部处理循环异常,具体 业务处理实现 processObj 方法
* 外部循环不做异常处理,仅释放锁,请外部自行处理异常情况
*
* @param collection 需要遍历的集合 例如:[{"id":1,"name":"Jack"},{"id":2,"name":"Tony"},{"id":3,"name":"Lisa"}]
* @param prefixKey 分布式锁前缀,XXXX_%s格式,内部通过String.format格式化,当此字段为空时,直接使用 field 字段对应的值 加锁 例如:“LOCK_PERSON_%s”
* @param function 属性获取函数 例如:Class::getId
* @param expireSeconds 过期时间,秒 例如:60
* @return 返回成功处理集合的field属性对应的值 例如:成功处理了Jack和Lisa,返回[{1},{3}]
*/
public List<String> lockAndForeach(@NotNull Collection<T> collection, String prefixKey, Function<T, String> function, int expireSeconds){
CtgRedis ctgRedis = CtgUtils.getCtgRedis();
List<String> res = new ArrayList<>();
for (T t : collection) {
// 调用该属性的get方法获取属性的值
String fieldFormat = function.apply(t);
String result = doLoop(prefixKey, expireSeconds, ctgRedis, t, fieldFormat);
// 处理结果添加
if(StringUtils.hasText(result)){
res.add(result);
}
}
return res;
}
/**
* 添加分布式锁遍历集合 collection, 外部处理循环异常,具体 业务处理实现 processObj 方法
* 外部循环不做异常处理,仅释放锁,请外部自行处理异常情况
*
* @param collection 需要遍历的集合 字符串集合
* @param prefixKey 分布式锁前缀,XXXX_%s格式,内部通过String.format格式化,当此字段为空时,直接使用 field 字段对应的值 加锁 例如:“LOCK_PERSON_%s”
* @param expireSeconds 过期时间,秒 例如:60
* @return 返回成功处理集合的field属性对应的值 例如:成功处理了Jack和Lisa,返回[{1},{3}]
*/
public List<String> lockAndForeach(@NotNull Collection<T> collection, String prefixKey, int expireSeconds){
CtgRedis ctgRedis = CtgUtils.getCtgRedis();
List<String> res = new ArrayList<>();
for (T t : collection) {
String result = doLoop(prefixKey, expireSeconds, ctgRedis, t, t.toString());
if(StringUtils.hasText(result)){
res.add(result);
}
}
return res;
}
private String doLoop(String prefixKey, int expireSeconds, CtgRedis ctgRedis, T t, String fieldFormat) {
String lockKey;
if(StringUtils.hasText(prefixKey)){
lockKey = String.format(prefixKey, fieldFormat);
}else{
// 没有format前缀时,直接使用属性值
lockKey = fieldFormat;
}
try{
if (ctgRedis.lock(lockKey, expireSeconds)) {
// 实际单个业务处理方法
processObj(t);
return fieldFormat;
}
} finally{
ctgRedis.releaseLock(lockKey);
}
return null;
}
/**
* 注意处理异常,否则会导致结合遍历中断
*
* @param obj 泛型入参
*/
protected abstract void processObj(T obj);
}