【面试题】SpringCloud架构中如何保证定时任务只在一个服务在执行
有时候我们在开发过程中,很容易犯这样一个错误,就是在服务中写一个定时任务,然后也没做其它的处理就上线了。然后微服务架构为了保证高可用,一般都会部署多个服务,这个时候问题就来了,时间一到定时任务一启动,发现你部署多少个服务,就会跑多少个定时任务。如果服务器性能一般,定时任务占用内存又多,服务器跑死都有可能。问题:那基于SpringCloud的架构中,这种情况我们应该如何处理呢?这边我们先来简...
有时候我们在开发过程中,很容易犯这样一个错误,就是在服务中写一个定时任务,然后也没做其它的处理就上线了。然后微服务架构为了保证高可用,一般都会部署多个服务,这个时候问题就来了,时间一到定时任务一启动,发现你部署多少个服务,就会跑多少个定时任务。如果服务器性能一般,定时任务占用内存又多,服务器跑死都有可能。
问题:那基于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);
}
}
这个工具类作用有以下几点:
- 获取当前服务ip
- 集群服务ip都转化成long类型数据,并进行排序
- 当前服务ip转化成long类型数据并和集群服务ip的long类型数据进行对比
我们通过这样的方法,就可以保证SpringCloud架构中定时任务只在一个服务在执行了,这边可能童鞋们会有一些疑问,为什么不用分布式调度框架来解决这个问题呢?当然我们也可以用分布式调度框架来解决这个问题,比如用elastic-job这个框架来就可以。但是引入第三方框架有时候会增加系统的复杂程度,学习成本也会相应的变大,最重要的是有些定时任务没必要进行分片,一个单点服务就可以搞定,就没必要耗费资源进行分片跑任务服务了。好了今天的内容就介绍到这边了,谢谢大家的阅读~
要更多干货、技术猛料的孩子,快点拿起手机扫码关注我,我在这里等你哦~
更多推荐
所有评论(0)