स्प्रिंग शेड्यूल्ड टास्क क्लस्टर्ड वातावरण में चल रहा है


97

मैं एक एप्लिकेशन लिख रहा हूं जिसमें एक क्रॉन जॉब है जो हर 60 सेकंड में निष्पादित होता है। कई उदाहरणों पर आवश्यक होने पर एप्लिकेशन को स्केल में कॉन्फ़िगर किया जाता है। मैं केवल प्रत्येक 60 सेकंड पर 1 उदाहरण पर कार्य को निष्पादित करना चाहता हूं (किसी भी नोड पर)। बॉक्स से बाहर मुझे इसका कोई हल नहीं मिल रहा है और मुझे आश्चर्य है कि यह पहले कई बार नहीं पूछा गया है। मैं स्प्रिंग 4.1.6 का उपयोग कर रहा हूं।

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

7
मुझे लगता है कि क्वार्ट्ज आपके लिए सबसे अच्छा समाधान है: stackoverflow.com/questions/6663182/…
सेलेरर

CronJobमें उपयोग करने पर कोई सुझाव kubernetes?
13:27 पर ch271828n

जवाबों:


97

एक शेडलॉक परियोजना है जो इस उद्देश्य को पूरा करती है। आप केवल उन कार्यों को एनोटेट करते हैं जिन्हें निष्पादित करते समय लॉक किया जाना चाहिए

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

स्प्रिंग और एक लॉकपॉइडर कॉन्फ़िगर करें

@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "10m")
class MySpringConfiguration {
    ...
    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
       return new JdbcTemplateLockProvider(dataSource);
    }
    ...
}

1
मैं बस "अच्छा काम" कहना चाहता हूं। लेकिन ... अच्छी सुविधा यह होगी कि यदि लाइब्रेरी डेटाबेस नाम को कोड में स्पष्ट रूप से उपलब्ध कराए बिना खोज सकती है ... सिवाय इसके कि यह उत्कृष्ट काम करता है!
Krzysiek

मेरे लिए ओरेकल और स्प्रिंग बूट डेटा जपा स्टार्टर के साथ काम करता है।
महेंद्रन अय्यारसामी कांडार

क्या यह समाधान स्प्रिंग 3.1.1 के लिए काम करता है। कृपया और जावा 6? कृपया बताओ।
विकास शर्मा

मैंने MsSQL और स्प्रिंग बूट जेपीए के साथ कोशिश की और मैंने एसक्यूएल भाग में लिबास स्क्रिप्ट का इस्तेमाल किया .. अच्छी तरह से काम करता है .. धन्यवाद
शीतल

यह वास्तव में अच्छा काम कर रहा है। हालाँकि मैं थोड़ा जटिल मामला मिला, आप कृपया एक नज़र डाल सकते हैं। धन्यवाद!!! stackoverflow.com/questions/57691205/…
डेटन वांग


15

क्लस्टर में किसी कार्य को सुरक्षित करने के लिए एक और सरल और मजबूत तरीका है। आप डेटाबेस पर आधारित हो सकते हैं और कार्य को केवल तभी निष्पादित कर सकते हैं जब नोड क्लस्टर में "लीडर" हो।

इसके अलावा जब कोई नोड विफल होता है या क्लस्टर में शटडाउन होता है तो दूसरा नोड लीडर बन जाता है।

आपके पास एक "लीडर इलेक्शन" मैकेनिज्म बनाने के लिए और हर बार यह जाँचने के लिए कि आपका लीडर है:

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

उन चरणों का पालन करें:

1. ऑब्जेक्ट और तालिका को निर्धारित करें जो क्लस्टर में प्रति नोड एक प्रविष्टि रखती है:

@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. सेवा है कि एक) डेटाबेस में नोड डालें, ख) नेता के लिए जाँच करें

@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. डेटाबेस को भेजने के लिए कि आपके जीवित हैं

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

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

4.आप तैयार हैं! कार्य को अंजाम देने से पहले जाँच लें कि क्या आप नेता हैं:

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

इस मामले में SystemService और SettingEnum क्या है? ऐसा लगता है कि यह बहुत सरल है और बस एक टाइमआउट मान देता है। उस मामले में क्यों न केवल कठिन समयबाह्य कोड?
tlavarea

@mspapant, SettingEnum.SYSTEM_CONFIGURATION_SYSTEM_NODE_ALIVE_TIMEOUT क्या है? मैं यहाँ उपयोग करने वाला इष्टतम मान क्या होना चाहिए?
user525146

@tlavarea क्या आपने इस कोड को लागू किया, मेरे पास DateUtils.hasExpired पद्धति के बारे में एक प्रश्न है? क्या यह कस्टम तरीका है या यह एक अपाचे आम बर्तन है?
user525146

10

बैच और शेड्यूल्ड जॉब्स आम तौर पर अपने स्वयं के स्टैंडअलोन सर्वरों पर चलते हैं, जो ग्राहक सामना करने वाले ऐप्स से दूर होते हैं, इसलिए किसी एप्लिकेशन में एक नौकरी शामिल करना एक सामान्य आवश्यकता नहीं है जो एक क्लस्टर पर चलने की उम्मीद है। इसके अतिरिक्त, समान वातावरण में नौकरियों को समान रूप से समान नौकरी के अन्य उदाहरणों के बारे में चिंता करने की आवश्यकता नहीं है, इसलिए एक और कारण है कि नौकरी के उदाहरणों को अलग करना एक बड़ी आवश्यकता नहीं है।

एक सरल उपाय यह होगा कि आप स्प्रिंग प्रोफाइल के अंदर अपनी नौकरियों को कॉन्फ़िगर करें। उदाहरण के लिए, यदि आपका वर्तमान विन्यास है:

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

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

इसे इसमें बदलें:

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

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

फिर, scheduledसक्रिय किए गए प्रोफ़ाइल ( -Dspring.profiles.active=scheduled) के साथ सिर्फ एक मशीन पर अपना एप्लिकेशन लॉन्च करें ।

यदि प्राथमिक सर्वर किसी कारण से अनुपलब्ध हो जाता है, तो बस सक्षम प्रोफ़ाइल के साथ एक और सर्वर लॉन्च करें और चीजें ठीक काम करना जारी रखेंगी।


यदि आप नौकरियों के लिए भी स्वचालित विफलता चाहते हैं तो चीजें बदल जाती हैं। फिर, आपको सभी सर्वरों पर काम को चालू रखने और एक सामान्य संसाधन जैसे डेटाबेस तालिका, क्लस्टर किए गए कैश, JMX चर, आदि के माध्यम से सिंक्रनाइज़ेशन की जांच करने की आवश्यकता होगी।


57
यह एक मान्य समाधान है, लेकिन यह एक संकुल वातावरण होने के पीछे के विचार का उल्लंघन करेगा, जहां यदि एक नोड नीचे है, तो दूसरा नोड अन्य अनुरोधों की सेवा कर सकता है। इस वर्कअराउंड में, यदि "शेड्यूल किया गया" प्रोफ़ाइल वाला नोड नीचे चला जाता है, तो यह बैकग्राउंड जॉब नहीं चलेगा
अहमद हशम

3
मुझे लगता है कि हम रेडिस का उपयोग परमाणु getऔर setऑपरेशन के साथ कर सकते हैं ।
थान गुयेन वान

आपके सुझाव के साथ कई समस्याएँ हैं: 1. आप आम तौर पर एक क्लस्टर के प्रत्येक नोड के लिए एक ही समान विन्यास चाहते हैं, इसलिए वे 100% विनिमेय होंगे और उन्हें उसी लोड के तहत समान संसाधनों की आवश्यकता होगी जो वे साझा करते हैं। 2. "समाधान" नोड के नीचे जाने पर आपके समाधान को मैनुअल हस्तक्षेप की आवश्यकता होगी। 3. यह अभी भी गारंटी नहीं देगा कि नौकरी वास्तव में सफलतापूर्वक चली गई थी, क्योंकि वर्तमान निष्पादन को संसाधित करने से पहले "कार्य" नोड नीचे चला गया था और पहले "नीचे जाने" के बाद नया "टास्क रनर" बनाया गया है, न जाने क्या यह समाप्त हो गया था या नहीं।
मोशे बिक्सेनस्पैन

1
यह बस संकुल वातावरण के विचार का उल्लंघन करता है, आपके द्वारा सुझाए गए दृष्टिकोण के साथ कोई समाधान नहीं हो सकता है। आप उपलब्धता सुनिश्चित करने के लिए प्रोफाइल सर्वर को भी नहीं दोहरा सकते क्योंकि इससे अतिरिक्त लागत और संसाधनों की अनावश्यक बर्बादी होगी। @ तन्ह द्वारा सुझाया गया समाधान इससे बहुत साफ है। MUTEX के समान ही सोचें। स्क्रिप्ट चलाने वाला कोई भी सर्वर रेडिस जैसी कुछ वितरित कैश में एक अस्थायी लॉक का अधिग्रहण करेगा और फिर पारंपरिक लॉकिंग की अवधारणाओं के साथ आगे बढ़ेगा।
अनुज प्रधान

2

dlock डेटाबेस इंडेक्स और बाधाओं का उपयोग करके केवल एक बार कार्य चलाने के लिए डिज़ाइन किया गया है। आप बस नीचे जैसा कुछ कर सकते हैं।

@Scheduled(cron = "30 30 3 * * *")
@TryLock(name = "executeMyTask", owner = SERVER_NAME, lockFor = THREE_MINUTES)
public void execute() {

}

इसका उपयोग करने के बारे में लेख देखें ।


3
यदि प्लॉक का उपयोग कर रहे हैं। मान लें कि हम लॉक को बनाए रखने के लिए डीबी का उपयोग कर रहे हैं। और क्लस्टर में नोड में से एक ताला लेने के बाद अप्रत्याशित रूप से नीचे गिर गया तो इस परिदृश्य में क्या होगा? क्या यह गतिरोध की स्थिति में होगा?
बैडमैन

1

मैं लॉक करने के लिए एक डेटाबेस तालिका का उपयोग कर रहा हूं एक समय में केवल एक ही कार्य तालिका में सम्मिलित कर सकता है। दूसरे को एक DuplicateKeyException मिलेगी। @Scheduled एनोटेशन के आसपास एक पहलू द्वारा डाला और हटाए जाने का तर्क हैंडल्ड है । मैं स्प्रिंग बूट 2.0 का उपयोग कर रहा हूं

@Component
@Aspect
public class SchedulerLock {

    private static final Logger LOGGER = LoggerFactory.getLogger(SchedulerLock.class);

    @Autowired
    private JdbcTemplate jdbcTemplate;  

    @Around("execution(@org.springframework.scheduling.annotation.Scheduled * *(..))")
    public Object lockTask(ProceedingJoinPoint joinPoint) throws Throwable {

        String jobSignature = joinPoint.getSignature().toString();
        try {
            jdbcTemplate.update("INSERT INTO scheduler_lock (signature, date) VALUES (?, ?)", new Object[] {jobSignature, new Date()});

            Object proceed = joinPoint.proceed();

            jdbcTemplate.update("DELETE FROM scheduler_lock WHERE lock_signature = ?", new Object[] {jobSignature});
            return proceed;

        }catch (DuplicateKeyException e) {
            LOGGER.warn("Job is currently locked: "+jobSignature);
            return null;
        }
    }
}


@Component
public class EveryTenSecondJob {

    @Scheduled(cron = "0/10 * * * * *")
    public void taskExecution() {
        System.out.println("Hello World");
    }
}


CREATE TABLE scheduler_lock(
    signature varchar(255) NOT NULL,
    date datetime DEFAULT NULL,
    PRIMARY KEY(signature)
);

3
क्या आपको लगता है कि यह पूरी तरह से काम करेगा? क्योंकि यदि ताला लेने के बाद नोड में से कोई एक नीचे उतरेगा, तो दूसरों को यह पता नहीं चलेगा कि लॉक क्यों है (टेबल में काम करने के लिए आपकी केस पंक्ति प्रविष्टि के अनुरूप)।
बैडमैन

0

आप इसे पूरा करने के लिए db- अनुसूचक जैसे एक एम्बेड करने योग्य अनुसूचक का उपयोग कर सकते हैं । इसमें लगातार निष्पादन होता है और एकल नोड द्वारा निष्पादन की गारंटी के लिए एक सरल आशावादी लॉकिंग तंत्र का उपयोग किया जाता है।

उदाहरण कोड का उपयोग-केस कैसे प्राप्त किया जा सकता है:

   RecurringTask<Void> recurring1 = Tasks.recurring("my-task-name", FixedDelay.of(Duration.ofSeconds(60)))
    .execute((taskInstance, executionContext) -> {
        System.out.println("Executing " + taskInstance.getTaskAndInstance());
    });

   final Scheduler scheduler = Scheduler
          .create(dataSource)
          .startTasks(recurring1)
          .build();

   scheduler.start();

-1

स्प्रिंग संदर्भ को क्लस्ट नहीं किया गया है इसलिए वितरित एप्लिकेशन में कार्य को प्रबंधित करना थोड़ा मुश्किल है और आपको राज्य को सिंक्रनाइज़ करने के लिए jgroup का समर्थन करने वाले सिस्टम का उपयोग करने की आवश्यकता है और अपने कार्य को कार्रवाई को निष्पादित करने के लिए प्राथमिकता दें। या फिर आप का प्रबंधन करने के संकुल हा सिंगलटन jboss हा पर्यावरण की तरह सेवा EJB संदर्भ इस्तेमाल कर सकते हैं https://developers.redhat.com/quickstarts/eap/cluster-ha-singleton/?referrer=jbd या आप संकुल कैश और पहुँच लॉक संसाधन इस्तेमाल कर सकते हैं सेवा और पहली सेवा के बीच ताला लेने से कार्रवाई में सुधार होगा या आप अपनी सेवा को संप्रेषित करने के लिए अपने स्वयं के jgroup को लागू करेंगे और कार्रवाई को एक नोड पर कर सकते हैं

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.