复杂均衡多台机器Job同时执行,如何只有一台执行(利用 redis lua脚本竞争执行权)

发布时间:2021-12-14作者:laosun阅读(1646)

复杂均衡多台机器Job同时执行,如何只有一台执行(利用

    我们在开发系统软件的时候,定时任务是少不了的,如果定时任务非单独服务包,那么在部署的时候,如果要搭建复杂均衡,我们免不了要考虑定时任务同时执行的问题。

    博客利用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);
    }

    image.png


    该方式并不能判断机器的压力,获得执行权全凭借机器运气以及先后顺序, 无法达到复杂均衡,请持续关注本博客,后期会根据系统的压力值进行判断,到底应该由谁来执行


1 +1

版权声明

 Java  源码  开源  redis

 请文明留言

1 条评论