2015-07-08 15 views
32

Tôi đang viết một ứng dụng có lệnh cron thực thi sau mỗi 60 giây. Ứng dụng được định cấu hình để mở rộng khi được yêu cầu trên nhiều phiên bản. Tôi chỉ muốn thực thi nhiệm vụ trên 1 instance mỗi 60 giây (Trên bất kỳ nút nào). Ra khỏi hộp tôi không thể tìm thấy một giải pháp cho điều này và tôi ngạc nhiên nó đã không được hỏi nhiều lần trước đây. Tôi đang sử dụng Spring 4.1.6.Tác vụ theo lịch trình mùa xuân đang chạy trong môi trường nhóm

<task:scheduled-tasks> 
     <task:scheduled ref="beanName" method="execute" cron="0/60 * * * * *"/> 
    </task:scheduled-tasks> 

Mọi hướng dẫn sẽ được đánh giá cao. Cảm ơn

+3

Tôi nghĩ rằng thạch anh là giải pháp tốt nhất cho bạn: http: // stackoverflow.com/questions/6663182/using-quartz-to-schedule-single-job-across-multiple-stateless-app-servers – selalerer

Trả lời

9

Công việc hàng loạt và được lên lịch thường chạy trên các máy chủ độc lập của riêng họ, cách xa ứng dụng khách hàng, vì vậy không yêu cầu công việc trong ứng dụng dự kiến ​​chạy trên cụm. Ngoài ra, các công việc trong môi trường nhóm thường không cần phải lo lắng về các trường hợp khác của cùng một công việc đang chạy song song vì vậy một lý do khác tại sao cô lập các cá thể công việc không phải là một yêu cầu lớn.

Một giải pháp đơn giản là định cấu hình công việc của bạn bên trong Hồ sơ mùa xuân. Ví dụ, nếu cấu hình hiện tại của bạn là:

<beans> 
    <bean id="someBean" .../> 

    <task:scheduled-tasks> 
    <task:scheduled ref="someBean" method="execute" cron="0/60 * * * * *"/> 
    </task:scheduled-tasks> 
</beans> 

thay đổi nó để:

<beans> 
    <beans profile="scheduled"> 
    <bean id="someBean" .../> 

    <task:scheduled-tasks> 
     <task:scheduled ref="someBean" method="execute" cron="0/60 * * * * *"/> 
    </task:scheduled-tasks> 
    </beans> 
</beans> 

Sau đó, khởi động ứng dụng của bạn trên chỉ là một máy với cấu hình scheduled kích hoạt (-Dspring.profiles.active=scheduled).

Nếu máy chủ chính không khả dụng vì lý do nào đó, chỉ cần khởi chạy một máy chủ khác với cấu hình được bật và mọi thứ sẽ tiếp tục hoạt động tốt.


Mọi thứ sẽ thay đổi nếu bạn muốn tự động chuyển đổi dự phòng cho công việc. Sau đó, bạn sẽ cần phải duy trì công việc đang chạy trên tất cả các máy chủ và kiểm tra đồng bộ hóa thông qua một tài nguyên chung như bảng cơ sở dữ liệu, bộ nhớ cache cụm, biến JMX, v.v.

+18

Đây là giải pháp hợp lệ, nhưng điều này sẽ vi phạm ý tưởng đằng sau việc có môi trường nhóm, nếu một nút bị hỏng, nút kia có thể phục vụ các yêu cầu khác. Trong cách giải quyết này, nếu nút có cấu hình "được lên lịch" bị hỏng, thì công việc nền này sẽ không chạy –

+1

Tôi nghĩ chúng ta có thể sử dụng Redis với hoạt động 'get' và' set' nguyên tử để giải quyết điều đó. –

10

Đây là một cách đơn giản và mạnh mẽ khác để thực thi an toàn một công việc trong một cụm. Bạn có thể dựa trên cơ sở dữ liệu và thực thi tác vụ chỉ khi nút là "người dẫn đầu" trong cụm.

Ngoài ra khi nút bị lỗi hoặc tắt trong cụm, một nút khác đã trở thành người dẫn đầu.

Tất cả bạn phải là tạo ra một cơ chế "lãnh đạo cuộc bầu cử" và mọi thời gian để kiểm tra xem bạn là người lãnh đạo:

@Scheduled(cron = "*/30 * * * * *") 
public void executeFailedEmailTasks() { 
    if (checkIfLeader()) { 
     final List<EmailTask> list = emailTaskService.getFailedEmailTasks(); 
     for (EmailTask emailTask : list) { 
      dispatchService.sendEmail(emailTask); 
     } 
    } 
} 

Thực hiện theo những bước sau:

1.Define đối tượng và bảng giữ một mục nhập mỗi nút trong cluster:

@Entity(name = "SYS_NODE") 
public class SystemNode { 

/** The id. */ 
@Id 
@GeneratedValue(strategy = GenerationType.IDENTITY) 
private Long id; 

/** The name. */ 
@Column(name = "TIMESTAMP") 
private String timestamp; 

/** The ip. */ 
@Column(name = "IP") 
private String ip; 

/** The last ping. */ 
@Column(name = "LAST_PING") 
private Date lastPing; 

/** The last ping. */ 
@Column(name = "CREATED_AT") 
private Date createdAt = new Date(); 

/** The last ping. */ 
@Column(name = "IS_LEADER") 
private Boolean isLeader = Boolean.FALSE; 

public Long getId() { 
    return id; 
} 

public void setId(final Long id) { 
    this.id = id; 
} 

public String getTimestamp() { 
    return timestamp; 
} 

public void setTimestamp(final String timestamp) { 
    this.timestamp = timestamp; 
} 

public String getIp() { 
    return ip; 
} 

public void setIp(final String ip) { 
    this.ip = ip; 
} 

public Date getLastPing() { 
    return lastPing; 
} 

public void setLastPing(final Date lastPing) { 
    this.lastPing = lastPing; 
} 

public Date getCreatedAt() { 
    return createdAt; 
} 

public void setCreatedAt(final Date createdAt) { 
    this.createdAt = createdAt; 
} 

public Boolean getIsLeader() { 
    return isLeader; 
} 

public void setIsLeader(final Boolean isLeader) { 
    this.isLeader = isLeader; 
} 

@Override 
public String toString() { 
    return "SystemNode{" + 
      "id=" + id + 
      ", timestamp='" + timestamp + '\'' + 
      ", ip='" + ip + '\'' + 
      ", lastPing=" + lastPing + 
      ", createdAt=" + createdAt + 
      ", isLeader=" + isLeader + 
      '}'; 
} 

}

2.Create dịch vụ mà a) chèn nút trong cơ sở dữ liệu, b) kiểm tra cho lãnh đạo

@Service 
@Transactional 
public class SystemNodeServiceImpl implements SystemNodeService, ApplicationListener { 

/** The logger. */ 
private static final Logger LOGGER = Logger.getLogger(SystemNodeService.class); 

/** The constant NO_ALIVE_NODES. */ 
private static final String NO_ALIVE_NODES = "Not alive nodes found in list {0}"; 

/** The ip. */ 
private String ip; 

/** The system service. */ 
private SystemService systemService; 

/** The system node repository. */ 
private SystemNodeRepository systemNodeRepository; 

@Autowired 
public void setSystemService(final SystemService systemService) { 
    this.systemService = systemService; 
} 

@Autowired 
public void setSystemNodeRepository(final SystemNodeRepository systemNodeRepository) { 
    this.systemNodeRepository = systemNodeRepository; 
} 

@Override 
public void pingNode() { 
    final SystemNode node = systemNodeRepository.findByIp(ip); 
    if (node == null) { 
     createNode(); 
    } else { 
     updateNode(node); 
    } 
} 

@Override 
public void checkLeaderShip() { 
    final List<SystemNode> allList = systemNodeRepository.findAll(); 
    final List<SystemNode> aliveList = filterAliveNodes(allList); 

    SystemNode leader = findLeader(allList); 
    if (leader != null && aliveList.contains(leader)) { 
     setLeaderFlag(allList, Boolean.FALSE); 
     leader.setIsLeader(Boolean.TRUE); 
     systemNodeRepository.save(allList); 
    } else { 
     final SystemNode node = findMinNode(aliveList); 

     setLeaderFlag(allList, Boolean.FALSE); 
     node.setIsLeader(Boolean.TRUE); 
     systemNodeRepository.save(allList); 
    } 
} 

/** 
* Returns the leaded 
* @param list 
*   the list 
* @return the leader 
*/ 
private SystemNode findLeader(final List<SystemNode> list) { 
    for (SystemNode systemNode : list) { 
     if (systemNode.getIsLeader()) { 
      return systemNode; 
     } 
    } 
    return null; 
} 

@Override 
public boolean isLeader() { 
    final SystemNode node = systemNodeRepository.findByIp(ip); 
    return node != null && node.getIsLeader(); 
} 

@Override 
public void onApplicationEvent(final ApplicationEvent applicationEvent) { 
    try { 
     ip = InetAddress.getLocalHost().getHostAddress(); 
    } catch (Exception e) { 
     throw new RuntimeException(e); 
    } 
    if (applicationEvent instanceof ContextRefreshedEvent) { 
     pingNode(); 
    } 
} 

/** 
* Creates the node 
*/ 
private void createNode() { 
    final SystemNode node = new SystemNode(); 
    node.setIp(ip); 
    node.setTimestamp(String.valueOf(System.currentTimeMillis())); 
    node.setCreatedAt(new Date()); 
    node.setLastPing(new Date()); 
    node.setIsLeader(CollectionUtils.isEmpty(systemNodeRepository.findAll())); 
    systemNodeRepository.save(node); 
} 

/** 
* Updates the node 
*/ 
private void updateNode(final SystemNode node) { 
    node.setLastPing(new Date()); 
    systemNodeRepository.save(node); 
} 

/** 
* Returns the alive nodes. 
* 
* @param list 
*   the list 
* @return the alive nodes 
*/ 
private List<SystemNode> filterAliveNodes(final List<SystemNode> list) { 
    int timeout = systemService.getSetting(SettingEnum.SYSTEM_CONFIGURATION_SYSTEM_NODE_ALIVE_TIMEOUT, Integer.class); 
    final List<SystemNode> finalList = new LinkedList<>(); 
    for (SystemNode systemNode : list) { 
     if (!DateUtils.hasExpired(systemNode.getLastPing(), timeout)) { 
      finalList.add(systemNode); 
     } 
    } 
    if (CollectionUtils.isEmpty(finalList)) { 
     LOGGER.warn(MessageFormat.format(NO_ALIVE_NODES, list)); 
     throw new RuntimeException(MessageFormat.format(NO_ALIVE_NODES, list)); 
    } 
    return finalList; 
} 

/** 
* Finds the min name node. 
* 
* @param list 
*   the list 
* @return the min node 
*/ 
private SystemNode findMinNode(final List<SystemNode> list) { 
    SystemNode min = list.get(0); 
    for (SystemNode systemNode : list) { 
     if (systemNode.getTimestamp().compareTo(min.getTimestamp()) < -1) { 
      min = systemNode; 
     } 
    } 
    return min; 
} 

/** 
* Sets the leader flag. 
* 
* @param list 
*   the list 
* @param value 
*   the value 
*/ 
private void setLeaderFlag(final List<SystemNode> list, final Boolean value) { 
    for (SystemNode systemNode : list) { 
     systemNode.setIsLeader(value); 
    } 
} 

}

3.ping cơ sở dữ liệu để gửi rằng bạn đang còn sống

@Override 
@Scheduled(cron = "0 0/5 * * * ?") 
public void executeSystemNodePing() { 
    systemNodeService.pingNode(); 
} 

@Override 
@Scheduled(cron = "0 0/10 * * * ?") 
public void executeLeaderResolution() { 
    systemNodeService.checkLeaderShip(); 
} 

4.you đã sẵn sàng! Chỉ cần kiểm tra xem bạn là người lãnh đạo trước khi thực hiện các nhiệm vụ:

@Override 
@Scheduled(cron = "*/30 * * * * *") 
public void executeFailedEmailTasks() { 
    if (checkIfLeader()) { 
     final List<EmailTask> list = emailTaskService.getFailedEmailTasks(); 
     for (EmailTask emailTask : list) { 
      dispatchService.sendEmail(emailTask); 
     } 
    } 
} 
+0

Trong trường hợp này SystemService và SettingEnum là gì? Có vẻ như nó cực kỳ đơn giản và chỉ trả về một giá trị thời gian chờ. Trong trường hợp đó, tại sao không chỉ khó mã thời gian chờ? – tlavarea

+0

@mspapant, SettingEnum.SYSTEM_CONFIGURATION_SYSTEM_NODE_ALIVE_TIMEOUT là gì? Giá trị tối ưu tôi nên sử dụng ở đây là gì? – user525146

+0

@tlavarea bạn đã triển khai mã này chưa, tôi có câu hỏi về phương thức DateUtils.hasExpired? nó là phương pháp tùy chỉnh hay nó là một ngôn ngữ thông dụng apache? – user525146

31

Có một dự án ShedLock phục vụ chính xác mục đích này. Bạn chỉ cần chú thích nhiệm vụ cần được khóa khi thực hiện

@Scheduled(...) 
@SchedulerLock(name = "scheduledTaskName") 
public void scheduledTask() { 
    // do something 
} 

Configure mùa xuân và một LockProvider (SQL và Mông Cổ hiện đang được hỗ trợ)

@Bean 
public TaskScheduler taskScheduler(LockProvider lockProvider) { 
    int poolSize = 10; 
    return SpringLockableTaskSchedulerFactory 
      .newLockableTaskScheduler(poolSize, lockProvider); 
} 
+0

Tôi chỉ muốn nói "Làm tốt lắm!". Nhưng ... Các tính năng tốt đẹp sẽ được nếu thư viện có thể khám phá tên cơ sở dữ liệu mà không cung cấp nó rõ ràng trong mã ... Ngoại trừ việc nó hoạt động xuất sắc! – Krzysiek

+0

Làm việc với tôi với bộ khởi động dữ liệu khởi động Oracle và Spring. –

+0

Giải pháp này có hoạt động cho Spring 3.1.1.RELEASE và java 6 không? Làm ơn nói đi. –

Các vấn đề liên quan