发布时间:2021-12-14作者:laosun阅读(1188)
我们在开发系统软件的时候,定时任务是少不了的,如果定时任务非单独服务包,那么在部署的时候,如果要搭建复杂均衡,我们免不了要考虑定时任务同时执行的问题。
博客利用redis lua写了一个竞争执行权的脚本。
实现原理
1、我们每台服务在启动成功之后理解生成一个ServerId,这个ServerId可以是UUID,可以是任何不重复的编号。博主是生成的UUID,然后使用static静态变量进行保存。
2、当定时任务到点执行的时候,我们利用lua脚本进行争夺执行权。lua脚本如下
if (redis.call('exists', KEYS[1]) == 0) then redis.call('set', KEYS[1], ARGV[1]) redis.call('expire', KEYS[1], ARGV[2]) return ARGV[1] else return (redis.call('get', KEYS[1])) end
KEYS[1]:cluster_server(随便定义个redis key)
ARGV[1]:当前服务ServerId
ARGV[2]:过期时间,单位秒
我们可以保存到本地,命名为:demo.lua
测试语法如下:
redis-cli -h 链接IP -a 密码 --eval /Users/sun/Documents/demo.lua cluster_server , 00bf3d4cbe094e5c83e62181fd7b9aa0 10
注:cluster_server 和 后边的ARGV参数中间是有空格+逗号的。
源码程序如下:
定义个枚举:RedisLuaEnum.java
package com.sunjs.commons.enums; /** .::::. .::::::::. ::::::::::: 佛主保佑、永无Bug ..:::::::::::' '::::::::::::' .:::::::::: '::::::::::::::.. ..::::::::::::. ``:::::::::::::::: ::::``:::::::::' .:::. ::::' ':::::' .::::::::. .::::' :::: .:::::::'::::. .:::' ::::: .:::::::::' ':::::. .::' :::::.:::::::::' ':::::. .::' ::::::::::::::' ``::::. ...::: ::::::::::::' ``::. ```` ':. ':::::::::' ::::.. '.:::::' ':'````.. */ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import java.util.List; import java.util.Map; /** * redis lua自制脚本 * * @author sun */ public enum RedisLuaEnum { /** 分布式部署,竞争执行权 **/ CLUSTER_EXECUTE("分布式部署,竞争执行权", "if (redis.call('exists', KEYS[1]) == 0) then redis.call('set', KEYS[1], ARGV[1]) redis.call('expire', KEYS[1], ARGV[2]) return ARGV[1] else return (redis.call('get', KEYS[1])) end"); private String name; private String value; private RedisLuaEnum(String name, String value) { this.name = name; this.value = value; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public static String getName(String value) { for (RedisLuaEnum lua : RedisLuaEnum.values()) { if (value.equals(lua.getValue())) { return lua.getName(); } } return null; } public static List<Map<String, String>> enumToList() { RedisLuaEnum[] lua = RedisLuaEnum.values(); List<Map<String, String>> list = Lists.newArrayList(); for (int i = 0; i < lua.length; i++) { Map<String, String> map = Maps.newHashMap(); map.put("name", lua[i].getName()); map.put("value", lua[i].getValue()); list.add(map); } return list; } @Override public String toString() { return name + " " + value; } }
争夺工具类
ClusterCommons.java
package com.sunjs.commons; /** .::::. .::::::::. ::::::::::: 佛主保佑、永无Bug ..:::::::::::' '::::::::::::' .:::::::::: '::::::::::::::.. ..::::::::::::. ``:::::::::::::::: ::::``:::::::::' .:::. ::::' ':::::' .::::::::. .::::' :::: .:::::::'::::. .:::' ::::: .:::::::::' ':::::. .::' :::::.:::::::::' ':::::. .::' ::::::::::::::' ``::::. ...::: ::::::::::::' ``::. ```` ':. ':::::::::' ::::.. '.:::::' ':'````.. */ import com.google.common.collect.Lists; import com.sunjs.common.utils.LogKit; import com.sunjs.commons.enums.RedisLuaEnum; import com.sunjs.constants.RedisConstants; import com.sunjs.core.Const; import lombok.extern.slf4j.Slf4j; import redis.clients.jedis.Jedis; import java.util.List; /** * 集群工具类 * * @author sun */ @Slf4j public class ClusterCommons { /** * 集群执行权争夺(无论是否成功,都返回竞选成功的系统ServerId) * 注意负载机器的时间同步不要超过10秒,最好是时间同步一致 * * @author sun * @return boolean */ public static boolean clusterExecuteRunFor() { return clusterExecuteRunFor(Const.SERVER_ID); } /** * 集群执行权争夺(无论是否成功,都返回竞选成功的系统ServerId) * 注意负载机器的时间同步不要超过10秒,最好是时间同步一致 * * @author sun * @param serverId 系统启动生成的ServerId * @return boolean */ public static boolean clusterExecuteRunFor(String serverId) { String contestServerId = clusterExecuteRunFor(serverId, 10); log.info(LogKit.append("集群执行权争夺", "当选服务ServerId:{}", "当前服务ServerId:{}"), contestServerId, serverId); if (serverId.equals(contestServerId)) { return true; } else { return false; } } /** * 集群执行权争夺(无论是否成功,都返回竞选成功的系统ServerId) * 注意负载机器的时间同步不要超过10秒,最好是时间同步一致 * * @author sun * @param serverId 系统启动生成的ServerId * @param sencond 竞争成功当选多久的主席(秒) * @return java.lang.String */ public static String clusterExecuteRunFor(String serverId, Integer sencond) { List<String> keyList = Lists.newArrayList(); keyList.add(RedisConstants.CLUSTER_SERVER); List<String> argsList = Lists.newArrayList(); argsList.add(serverId); argsList.add(String.valueOf(sencond)); Jedis jedis = RedisHandle.getCache().getJedis(); String result = (String) jedis.eval(RedisLuaEnum.CLUSTER_EXECUTE.getValue(), keyList, argsList); jedis.close(); return result; } }
我的Job服务执行时先执行竞选
代码如下:
// 竞选执行权 if (!ClusterCommons.clusterExecuteRunFor()) { log.info("{}:任务定时启动,执行权竞争失败,放弃执行", packageClass); return false; } else { log.info("{}:任务定时启动,成功夺得执行权,开始执行任务", packageClass); }
该方式并不能判断机器的压力,获得执行权全凭借机器运气以及先后顺序, 无法达到复杂均衡,请持续关注本博客,后期会根据系统的压力值进行判断,到底应该由谁来执行
版权属于: 技术客
原文地址: https://www.sunjs.com/article/detail/a78f21dbd8fb4837a3a18420d460ab07.html
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。