ক্লাস্টারযুক্ত পরিবেশে স্প্রিং শিডিউড টাস্ক চলছে


98

আমি একটি অ্যাপ্লিকেশন লিখছি যার ক্রোন জব রয়েছে যা প্রতি 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/...
selalerer

ব্যবহার কোন পরামর্শ CronJobমধ্যে kubernetes?
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);
    }
    ...
}

4
আমি কেবল "ভাল কাজ!" বলতে চাই। তবে ... দুর্দান্ত বৈশিষ্ট্যটি হ'ল যদি গ্রন্থাগারটি কোডে স্পষ্ট না করে ডাটাবেসের নাম আবিষ্কার করতে পারে ... তবে এটি দুর্দান্ত কাজ করে!
ক্রিজিসিক

ওরাকল এবং স্প্রিং বুট ডেটা জেপিএ স্টার্টার সহ আমার জন্য কাজ করে।
মাহেন্দ্রন আয়রসামি কান্দিয়ার

এই সমাধানটি কি বসন্তের জন্য কাজ করে 3.1.1. রিলিজ এবং জাভা 6? প্লিজ বলো.
বিকাশ শর্মা

আমি এমএসএসকিউএল এবং স্প্রিং বুট জেপিএ দিয়ে চেষ্টা করেছি এবং আমি এসকিউএল অংশে লিকুইব্যাস স্ক্রিপ্ট ব্যবহার করেছি .. ভাল কাজ করে .. ধন্যবাদ
শীটাল

এটি সত্যই ভাল কাজ করছে। তবে আমি এখানে কিছুটা জটিল মামলার মুখোমুখি হয়েছি দয়া করে একবার দেখে নিতে পারেন। ধন্যবাদ !!! stackoverflow.com/questions/57691205/…
ডেটন ওয়াং


15

একটি ক্লাস্টারে কোনও কাজ নিরাপদে সম্পাদন করার জন্য এটি একটি সহজ এবং দৃ simple় উপায়। আপনি ডাটাবেসের ভিত্তিতে এবং ক্লাস্টারের নোডটি "লিডার" হলে কেবলমাত্র কার্য সম্পাদন করতে পারেন।

এছাড়াও যখন কোনও নোড ব্যর্থ হয় বা ক্লাস্টারে শাটডাউন হয় তখন অন্য নোড নেতা হয়।

আপনার সমস্ত কিছু "নেতা নির্বাচন" প্রক্রিয়া তৈরি করা এবং প্রতিবার আপনার নেতা কিনা তা যাচাই করার জন্য:

@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 +
            '}';
}

}

২) পরিষেবাটি তৈরি করুন যা ক) ডাটাবেজে নোড sertোকান, খ) নেতার জন্য চেক করুন

@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);
        }
    }
}

এক্ষেত্রে সিস্টেম সার্ভিস এবং সেটিংইনাম কী? দেখে মনে হচ্ছে এটি অত্যন্ত সহজ এবং কেবল একটি সময়সীমা মান দেয়। সেক্ষেত্রে হার্ড কোড টাইমআউট কেন হবে না?
tlavarea

@ এমস্প্যান্ট, সেটিংইনাম কী? SYSTEM_CONFIGURATION_SYSTEM_NODE_ALIVE_TIMEOUT? আমার এখানে সর্বোত্তম মানটি ব্যবহার করা উচিত?
ব্যবহারকারী525146

@lavarea আপনি কি এই কোডটি প্রয়োগ করেছেন, আমার ডেট ইউটিলস.হাস এক্সপায়ার্ড পদ্ধতি সম্পর্কে একটি প্রশ্ন আছে? এটি কাস্টম পদ্ধতি বা এটি অ্যাপাচি সাধারণ ব্যবহারগুলি?
ব্যবহারকারী525146

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) দিয়ে কেবল একটি মেশিনে আপনার অ্যাপ্লিকেশন চালু করুন ।

যদি প্রাথমিক সার্ভারটি কোনও কারণে অনুপলব্ধ হয়ে যায় তবে কেবল প্রোফাইল সক্ষম করেই অন্য একটি সার্ভার চালু করুন এবং জিনিসগুলি ঠিক ঠিক কাজ করতে থাকবে।


আপনি যদি চাকরীর জন্য স্বয়ংক্রিয় ব্যর্থতা চান তবে বিষয়গুলি পরিবর্তন হয়। তারপরে, আপনাকে সমস্ত সার্ভারে কাজ চালিয়ে যেতে হবে এবং একটি সাধারণ সংস্থান যেমন একটি ডাটাবেস টেবিল, একটি ক্লাস্টার ক্যাশে, একটি জেএমএক্স ভেরিয়েবল ইত্যাদির মাধ্যমে সিঙ্ক্রোনাইজেশন পরীক্ষা করতে হবে will


58
এটি একটি কার্যকর বৈকল্পিক, তবে এটি ক্লাস্টারযুক্ত পরিবেশ থাকার পিছনে ধারণাকে লঙ্ঘন করবে, যেখানে নোড ডাউন থাকলে, অন্য নোড অন্যান্য অনুরোধগুলি সরবরাহ করতে পারে। এই কর্মসূচীতে, "নির্ধারিত" প্রোফাইলযুক্ত নোডটি যদি নিচে চলে যায়, তবে এই পটভূমির কাজটি চলবে না
আহমেদ হাশেম

4
আমি মনে করি আমরা আর্কাইভ করতে রেডিসকে পারমাণবিক getএবং setঅপারেশন সহ ব্যবহার করতে পারি ।
থানহ এনগুইন ভ্যান

আপনার পরামর্শ নিয়ে বেশ কয়েকটি সমস্যা রয়েছে: ১. আপনি সাধারণত একটি ক্লাস্টারের প্রতিটি নোডের সঠিক একই কনফিগারেশনটি দেখতে চান, সুতরাং সেগুলি 100% বিনিময়যোগ্য হবে এবং তাদের ভাগ করা একই বোঝার অধীনে একই সংস্থান প্রয়োজন। ২. "টাস্ক" নোড নেমে গেলে আপনার সমাধানটির জন্য ম্যানুয়াল হস্তক্ষেপের প্রয়োজন হবে। ৩. এটি এখনও গ্যারান্টি দেয় না যে কাজটি আসলে সফলভাবে পরিচালিত হয়েছিল, কারণ "টাস্ক" নোডটি বর্তমান সম্পাদন প্রক্রিয়াজাতকরণ শেষ করার আগেই নেমে গিয়েছিল এবং প্রথমটি নিচে যাওয়ার পরে নতুন "টাস্ক রানার" তৈরি করা হয়েছিল, তা জেনেও না। এটা শেষ হয়েছে কি না।
মোশে বিক্সেনশপনার

4
এটি কেবল ক্লাস্টারযুক্ত পরিবেশের ধারণাটিকে লঙ্ঘন করে, আপনার প্রস্তাবিত পদ্ধতির কোনও সমাধান হতে পারে না। প্রাপ্যতা নিশ্চিত করতে আপনি এমনকি প্রোফাইল সার্ভারগুলি প্রতিলিপি করতে পারবেন না কারণ এটির জন্য অতিরিক্ত ব্যয় এবং অপ্রয়োজনীয় সম্পদের অপচয়ও হবে in @ থানহ কর্তৃক প্রস্তাবিত সমাধান এর চেয়ে অনেক বেশি পরিষ্কার। MUTEX হিসাবে একই চিন্তা করুন। স্ক্রিপ্টটি চালিত যে কোনও সার্ভার রেডিসের মতো কিছু বিতরণ করা ক্যাশে একটি অস্থায়ী লক অর্জন করবে এবং তারপরে traditionalতিহ্যবাহী লকিংয়ের ধারণাগুলি নিয়ে এগিয়ে যাবে।
অনুজ প্রধান

2

ডলকটি ডাটাবেস সূচক এবং সীমাবদ্ধতাগুলি ব্যবহার করে কেবল একবার কাজ চালানোর জন্য ডিজাইন করা হয়েছে। আপনি নীচের মতো কিছু করতে পারেন।

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

}

এটি ব্যবহার সম্পর্কে নিবন্ধটি দেখুন ।


4
যদি তালা ব্যবহার করে থাকে তবে মনে করুন আমরা লক বজায় রাখার জন্য ডিবি ব্যবহার করছি। এবং ক্লাস্টারের একটি নোড লক নেওয়ার পরে অপ্রত্যাশিতভাবে নেমে এসেছিল তবে এই দৃশ্যে কী হবে? এটা কি অচলাবস্থায় থাকবে?
ব্যাডম্যান

1

লকিংয়ের জন্য আমি একটি ডাটাবেস টেবিল ব্যবহার করছি একবারে কেবল একটি কাজ টেবিলে একটি সন্নিবেশ করতে পারে। অন্যটি একটি ডুপ্লিকেটকিএসেক্সেপশন পাবে। Chedোকান এবং মুছুন যুক্তি হ'ল @ নির্ধারিত টীকাটির চারপাশে একটি দিক দ্বারা হ্যান্ডল্ড হয় । আমি স্প্রিং বুট 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)
);

4
আপনি কি মনে করেন এটি পুরোপুরি কার্যকর হবে? কারণ লক নেওয়ার পরে যদি কোনও নোড নীচে নেমে আসে তবে অন্যরা কেন লক রয়েছে তা জানতে পারবেন না (আপনার ক্ষেত্রে সারণিতে কাজের সাথে সম্পর্কিত সারি এন্ট্রি)।
Badman

0

এটি সম্পন্ন করার জন্য আপনি এমবিডেডেড শিডিয়ুলারের মতো ডিবি-শিডিয়ুলার ব্যবহার করতে পারেন । এটি অবিচ্ছিন্নভাবে মৃত্যুদন্ড কার্যকর করেছে এবং একটি একক নোড দ্বারা মৃত্যুদণ্ড কার্যকর করার গ্যারান্টি দিতে একটি সহজ আশাবাদী লকিং ব্যবস্থা ব্যবহার করে।

কীভাবে ব্যবহার-কেস অর্জন করা যায় তার উদাহরণ কোড:

   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 ha পরিবেশের জন্য পরিচালনা করতে ejb প্রসঙ্গটি ব্যবহার করতে পারেন https://developers.redhat.com/quickstarts/eap/cluster-ha-singleton/?referrer=jbd অথবা আপনি ক্লাস্টার ক্যাশে এবং অ্যাক্সেস লক সংস্থান ব্যবহার করতে পারেন পরিষেবা এবং প্রথম পরিষেবাটির মধ্যে লকটি কার্যকর হবে বা আপনার পরিষেবাটি যোগাযোগ করতে এবং এক নোডের ক্রিয়াটি সম্পাদন করতে আপনার নিজের জগ্রুপের বাস্তবায়ন করবে

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.