springCloud微服務叢集部署定時任務保證只在一個伺服器執行一次
阿新 • • 發佈:2021-01-29
技術標籤:javajavaspringspring boot
首先說下情況,我們平常開發SpringCloud微服務的時候,若要確保高可用,同一服務都會部署多臺例項,然後註冊到Eureka上。
一般我們會把所有定時任務寫到一個服務裡,那平常單例項的時候,都可以正常執行。如果該定時任務服務部署多個例項,如何確保只在一個服務例項裡執行任務呢?
個人總結了下,可以有以下解決思路。
- 使用分散式鎖
藉助分散式鎖,確保多個例項裡的task只有競爭到鎖的例項任務才執行。比如,redis的分散式鎖。這種方式不好的地方是需要修改邏輯程式碼,增加了對redis的依賴。
/**
* 定時任務
*/
@Scheduled(fixedDelay = 30 * 1000)
public void task() {
if (redisUtil.get("task") == null) {
// 叢集部署定時任務互斥鎖,哪個應用伺服器拿到這把“鎖”,就有執行任務的權利,未拿到“鎖”的應用伺服器不進行任何任務相關的操作。
redisUtil.set("task", true, 25);// 過期時間是由任務排程的間隔時間決定的,只要小於兩次任務執行時間差,大於叢集間應用伺服器的時間差即可。
//定時任務的程式碼邏輯...
}
}
- 使用任務排程框架的叢集功能
之前我使用的是Quartz,Quartz是一個完全由Java編寫的開源作業排程框架,Quartz的叢集功能通過故障轉移和負載平衡功能為您的排程程式帶來高可用性和可擴充套件性。這種方式也有不好的地方,那就是要實現Quartz的叢集功能,需要修改Quartz的配置,而且是要額外增加Quartz叢集需要的資料庫表,如果一開始開發沒有考慮叢集,後面再加入改動會有點大。這裡就不做程式碼演示了…
- 最小ip執行
定時任務
@Component
public class Schedule {
@Autowired
private IJobService jobService;
@Value("${spring.application.name}")
private String serviceName;
@Scheduled(cron = "0 15 0 * * ?")
public void storageTotalByDay() {
if (IPV4Util.ipCompare(this.jobService.serviceUrl(serviceName))) {//當前微服務所在ip為最小ip時 執行定時任務
//定時任務的程式碼邏輯
}
}
介面
public interface IJobService {
//獲取微服務所在伺服器的所有ip
public List<URI> serviceUrl(String serviceName) ;
}
介面實現
@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;
}
}
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.100.19");
System.out.println("long轉換為IP的結果: " + longToIp(result));
System.err.println("result IP轉換為long的結果: " + result);
long result1 = ipToLong("192.168.100.40");
System.out.println("long1轉換為IP的結果: " + longToIp(result1));
System.err.println("result1 IP轉換為long的結果: " + result1);
// 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);
}
}