分布式系统经常要遇到定时任务执行的问题,不能重复执行,但很多时候又不能统一到一个微服务里面,因为这样就失去了微服务的意义。由于我的系统只有寥寥几个定时任务,而且都是按天执行的,我就弄了这么个小东西来控制分布式定时任务。
我使用的redis分布式锁来控制分布式定时任务的方式,实际上适用于定时任务较少的情况,而且不适用于瞬时反复执行的定时任务。这种情况下,如果加上分布式定时任务框架,如Elastic-Job这种,显然就很重了,所以这是一个极轻量级的方式。这种方式显然很难支持作业分片、失效转移、重新触发执行以及执行过程中服务挂掉之后的重新执行。所以使用这种方式要慎重,如果系统存在后续定时任务大规模扩展、定时任务需要分片或者有瞬时反复执行的定时任务等情况,则这种简单的方式就不适用了。
原理其实很简单,各个微服务系统同时向redis申请加锁,由于redis是单线程的,所以只能有一个加锁成功,然后后面的全部得不到锁。得到锁的执行定时任务,得不到的,就放弃执行定时任务。
为啥要用lua脚本呢?因为好用啊,亲,操作的原子性能得到保证。
小demo:
@Test
public void testgg(){
try{
jedisCluster.del("schedule:lock:sss");
String dateStr = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
for (int i=0;i<30;i++){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//lua脚本
String luaScript = "local vals = redis.call('setnx', KEYS[1],'lock') if vals > 0 then redis.call('expire',KEYS[1], 1) end if vals > 0 then return 1 end return 0";
//执行lua脚本
Object lockVal = jedisOperationUtils.executeLuaScript(luaScript,1, "schedule:lock:sss");
System.out.println(lockVal.toString()+" "+Thread.currentThread().getName());
if(lockVal!=null && Integer.valueOf(lockVal.toString())>0){
//得到锁,执行定时任务的内容
}
}
});
thread.start();
Thread.currentThread().sleep(100);
}
try{
Thread.currentThread().sleep(10000);
}catch (Exception e){
System.out.println("ooooooooooooooooooooooooooooo");
e.printStackTrace();
}
}catch (Exception e){
System.out.println("IIIIIIIIIIIIIII");
e.printStackTrace();
}
}
这个例子就大概表达了整个思路的意思。其实相当简单。
里面的
jedisOperationUtils.executeLuaScript(......)
方法是封装的,我不需要ARGV[],所以就没封装进去。
/**
* lua脚本执行
* @param luaScript
* @param keyCount
* @param keys
* @return
*/
public Object executeLuaScript(String luaScript,int keyCount,String ... keys){
Object object = null;
try{
object = jedisCluster.eval(luaScript,keyCount,keys);
if(object == null){
return null;
}
}catch(Exception e){
log.error("执行redislua脚本失败",e);
return null;
}
return object;
}
OVER
来源:oschina
链接:https://my.oschina.net/u/2001043/blog/2245321