有时候我们在开发过程中,很容易犯这样一个错误,就是在服务中写一个定时任务,然后也没做其它的处理就上线了。然后微服务架构为了保证高可用,一般都会部署多个服务,这个时候问题就来了,时间一到定时任务一启动,发现你部署多少个服务,就会跑多少个定时任务。如果服务器性能一般,定时任务占用内存又多,服务器跑死都有可能。

问题:那基于SpringCloud的架构中,这种情况我们应该如何处理呢?

这边我们先来简单概述一下,我们先来看一下任务执行的时序图。

简单的来说,我们可以分为以下步骤:

  • 第一步先获取当前服务ip
  • 第二步获取springcloud集群ip信息
  • 最后将当前ip和集群的ip进行对比,如果当前ip是集群中最小的ip则执行定时任务业务,如果不是则return掉。

我们先来看一下定时任务:


/**
 *
 * @author qiang220316
 * @date 2019/2/28
 */

@Component
public class WrokTask {
    @Autowired
    private IJobService jobService;

    private static String serviceName="provider";

    /**
     * 5秒更新一次
     */
    @Scheduled(fixedDelay = 5000)
    public void doWork(){
        if (!IPV4Util.ipCompare(this.jobService.serviceUrl(serviceName))) {
            return;
        }
        System.out.println(serviceName+"服务,地址为:"+IPV4Util.getIpAddress()+",正在执行task任务");
    }
}

定时任务中我们可以看到this.jobService.serviceUrl方法,这个方法的作用则是获取SpringCloud集群中服务信息,IPV4Util.ipCompare这个作用就是将当前服务IP和集群所有IP进行对比,如果当前服务IP是集群服务IP最小则返回true,反之返回false。

接下来我们再看一下,如果来获取SpringCloud集群信息:

@Service
public class JobServiceImpl implements IJobService {

   @Autowired
   private DiscoveryClient discoveryClient;
     
   @Override
   public List<URI> serviceUrl(String serviceName) {
      List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances(serviceName);
      List<URI> urlList = new ArrayList<URI>();
      if (CollectionUtils.isNotEmpty(serviceInstanceList)) {
         serviceInstanceList.forEach(si -> {
            urlList.add(si.getUri());
         });
      }
      return urlList;
   }

}

其实主要还是用到DiscoveryClient类中方法,我们就可以很轻松获取到集群信息。

最后我们再来看看IPV4Util这个工具类到底是怎么进行对比的呢?

public class IPV4Util {

    /**
     * @param ipAddress
     * @return
     */
    public static long ipToLong(String ipAddress) {
        long result = 0;
        String[] ipAddressInArray = ipAddress.split("\\.");
        for (int i = 3; i >= 0; i--) {
            long ip = Long.parseLong(ipAddressInArray[3 - i]);
            // left shifting 24,16,8,0 and bitwise OR
            // 1. 192 << 24
            // 1. 168 << 16
            // 1. 1 << 8
            // 1. 2 << 0
            result |= ip << (i * 8);
        }
        return result;
    }

    /**
     * @param ip
     * @return
     */
    public static String longToIp(long ip) {
        StringBuilder result = new StringBuilder(15);
        for (int i = 0; i < 4; i++) {
            result.insert(0, Long.toString(ip & 0xff));
            if (i < 3) {
                result.insert(0, '.');
            }
            ip = ip >> 8;
        }
        return result.toString();
    }

    /**
     * @param ip
     * @return
     */
    public static String longToIp2(long ip) {
        return ((ip >> 24) & 0xFF) + "." + ((ip >> 16) & 0xFF) + "." + ((ip >> 8) & 0xFF) + "." + (ip & 0xFF);
    }

    /**
     * 获取当前机器的IP
     *
     * @return
     */
    public static String getIpAddress() {
        try {
            for (Enumeration<NetworkInterface> enumNic = NetworkInterface.getNetworkInterfaces();
                 enumNic.hasMoreElements(); ) {
                NetworkInterface ifc = enumNic.nextElement();
                if (ifc.isUp()) {
                    for (Enumeration<InetAddress> enumAddr = ifc.getInetAddresses();
                         enumAddr.hasMoreElements(); ) {
                        InetAddress address = enumAddr.nextElement();
                        if (address instanceof Inet4Address && !address.isLoopbackAddress()) {
                            return address.getHostAddress();
                        }
                    }
                }
            }
            return InetAddress.getLocalHost().getHostAddress();
        } catch (IOException e) {
            //log.warn("Unable to find non-loopback address", e);
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 对比方法
     *
     * @param serviceUrl
     * @return
     */
    public static boolean ipCompare(List<URI> serviceUrl) {
        try {
            String localIpStr = IPV4Util.getIpAddress();
            long localIpLong = IPV4Util.ipToLong(localIpStr);
            int size = serviceUrl.size();
            if (size == 0) {
                return false;
            }

            Long[] longHost = new Long[size];
            for (int i = 0; i < serviceUrl.size(); i++) {
                String host = serviceUrl.get(i).getHost();
                longHost[i] = IPV4Util.ipToLong(host);
            }
            Arrays.sort(longHost);
            if (localIpLong == longHost[0]) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        long result = ipToLong("192.168.11.126");
        System.out.println("long转换为IP的结果: " + longToIp(result));
        System.err.println("result IP转换为long的结果: " + result);
//        long result2 = ipToLong("192.168.11.217");
//        System.err.println("result2IP转换为long的结果: " + result2);
//        System.out.println("long转换为IP的结果: " + longToIp(result));
//        System.out.println("long转换为IP的结果: " + longToIp2(result));
//        String ipAddress = getIpAddress();
//        System.err.println(ipAddress);
    }
}

这个工具类作用有以下几点:

  1. 获取当前服务ip
  2. 集群服务ip都转化成long类型数据,并进行排序
  3. 当前服务ip转化成long类型数据并和集群服务ip的long类型数据进行对比

我们通过这样的方法,就可以保证SpringCloud架构中定时任务只在一个服务在执行了,这边可能童鞋们会有一些疑问,为什么不用分布式调度框架来解决这个问题呢?当然我们也可以用分布式调度框架来解决这个问题,比如用elastic-job这个框架来就可以。但是引入第三方框架有时候会增加系统的复杂程度,学习成本也会相应的变大,最重要的是有些定时任务没必要进行分片,一个单点服务就可以搞定,就没必要耗费资源进行分片跑任务服务了。好了今天的内容就介绍到这边了,谢谢大家的阅读~

要更多干货、技术猛料的孩子,快点拿起手机扫码关注我,我在这里等你哦~

                                                       

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐