专栏首页强仔仔【面试题】SpringCloud架构中如何保证定时任务只在一个服务在执行

【面试题】SpringCloud架构中如何保证定时任务只在一个服务在执行

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/linzhiqiang0316/article/details/88047138

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

问题:那基于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这个框架来就可以。但是引入第三方框架有时候会增加系统的复杂程度,学习成本也会相应的变大,最重要的是有些定时任务没必要进行分片,一个单点服务就可以搞定,就没必要耗费资源进行分片跑任务服务了。好了今天的内容就介绍到这边了,谢谢大家的阅读~

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 使用HttpServletRequestWrapper重写Request请求参数

    目的: 改变请求参数的值,满足项目需求(如:过滤请求中 lang != zh 的请求)

    林老师带你学编程
  • MongodbGFS存储大文件(java版)

    Mongodb 是一个开源的no-sql分布式数据库,Mongodb也为我们提供了基于文件的GFS分布式存储系统。因此利用Mongodb我们完全可以实现一个分...

    林老师带你学编程
  • 使用Guava实现限流器

    在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。限流可以认为服务降级的一种,限流通过限制请求的流量以达到保护系统的目的。

    林老师带你学编程
  • Silverlight:双向绑定综合应用-自动更新集合汇总字段

    场景:有一家公司(类名:Company),它有N多员工(类名:Employee)。要在界面上用网格显示所有员工的姓名、工资,并且当操作用户在网格里对员工进行增减...

    菩提树下的杨过
  • 干货:构建一个可实现CI/CD的tomcat容器应用镜像

    一、应用容器化的方法 应用容器化,常见的方法有三种: ? 以上三种方式: 第一种本地构建最常见,也比较简单,但效率太低。 第二种方式是通过CI构建。这种方式则...

    魏新宇
  • 括号匹配

    用户7727433
  • C# 操作线程的通用类[测试通过]

    进程管理就是对服务器性能的管理和协调,在程序的运行角度来看非常重要,也可以根据操作进程的手段,衍生很多实用和智能的功能,以下就是介绍一个自己写的进程通用操作类,...

    Java中文社群_老王
  • 三分钟学会 Java 单元测试

    前言: 此篇文章使用 Junit 4.0, 希望给无任何单元测试经验的开发者, 能在最短的时间内, 开展单元测试的工作◦ 本文:  学习 Junit 的测试框架...

    Ken Fang 方俊贤
  • 1313 质因数分解

    1313 质因数分解 2012年NOIP全国联赛普及组 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 青铜 Bronze 题目...

    attack
  • 从 LongAdder 中窥见并发组件的设计思路

    最近在看阿里的 Sentinel 的源码的时候。发现使用了一个类 LongAdder 来在并发环境中计数。这个时候就提出了疑问,JDK 中已经有 AtomicL...

    用户2060079

扫码关注云+社区

领取腾讯云代金券