分布式系统经常要遇到定时任务执行的问题,不能重复执行,但很多时候又不能统一到一个微服务里面,因为这样就失去了微服务的意义。由于我的系统只有寥寥几个定时任务,而且都是按天执行的,我就弄了这么个小东西来控制分布式定时任务。
我使用的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