انتقل إلى المحتوى الرئيسي

TP14.4: المهام المجدولة

أهداف العمل التطبيقي​

  • تكوين المهام المجدولة
  • استخدام تعبيرات Cron
  • تنفيذ تقارير تلقائية
  • إدارة المهام غير المتزامنة

المدة المقدرة: 45 دقيقة


الهيكلية​

تطبيق Spring Boot
|
+-- Scheduler Thread Pool
|
+-- تقرير يومي (0 0 6 * * *)
+-- تنظيف كل ساعة (0 0 * * * *)
+-- مزامنة الأسعار (fixedRate = 60000)
+-- فحص الصحة (fixedDelay = 30000)

الجزء 1: التكوين (10 دقائق)​

1.1 التطبيق​

package com.example.scheduling;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableScheduling
@EnableAsync
public class SchedulingApplication {

public static void main(String[] args) {
SpringApplication.run(SchedulingApplication.class, args);
}
}

1.2 التكوين​

package com.example.scheduling.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

@Configuration
public class SchedulingConfig implements SchedulingConfigurer {

@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setTaskScheduler(taskScheduler());
}

@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.setThreadNamePrefix("scheduler-");
scheduler.setErrorHandler(t -> {
System.err.println("Task error: " + t.getMessage());
});
return scheduler;
}
}

1.3 application.yml​

app:
scheduling:
enabled: true
cleanup:
max-age-hours: 24
report:
email: admin@example.com

الجزء 2: المهام المجدولة (20 دقيقة)​

2.1 خدمة المهام​

package com.example.scheduling.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicInteger;

@Service
public class ScheduledTaskService {

private static final Logger log = LoggerFactory.getLogger(ScheduledTaskService.class);
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

private final AtomicInteger taskCounter = new AtomicInteger(0);

// كل 10 ثواني
@Scheduled(fixedRate = 10000)
public void heartbeat() {
int count = taskCounter.incrementAndGet();
log.info("[Heartbeat #{}] Application is running at {}",
count, LocalDateTime.now().format(formatter));
}

// كل 30 ثانية بعد انتهاء السابقة
@Scheduled(fixedDelay = 30000)
public void cleanupTask() {
log.info("[Cleanup] Starting cleanup task...");

// محاكاة التنظيف
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}

log.info("[Cleanup] Cleanup completed - {} items removed",
(int)(Math.random() * 100));
}

// كل دقيقة (في بداية الدقيقة)
@Scheduled(cron = "0 * * * * *")
public void everyMinute() {
log.info("[Cron] Minute task executed at {}",
LocalDateTime.now().format(formatter));
}

// كل 5 دقائق
@Scheduled(cron = "0 */5 * * * *")
public void everyFiveMinutes() {
log.info("[Cron] Five-minute task executed");
generateQuickReport();
}

// كل يوم في الساعة 6 صباحاً
@Scheduled(cron = "0 0 6 * * *")
public void dailyReport() {
log.info("[Daily] Generating daily report...");
// توليد التقرير
log.info("[Daily] Daily report generated and sent");
}

// كل يوم اثنين في الساعة 9 صباحاً
@Scheduled(cron = "0 0 9 * * MON")
public void weeklyReport() {
log.info("[Weekly] Generating weekly report...");
}

private void generateQuickReport() {
log.info("Quick report: Active threads = {}, Free memory = {} MB",
Thread.activeCount(),
Runtime.getRuntime().freeMemory() / (1024 * 1024));
}

// طريقة غير متزامنة
@Async
public void asyncTask(String taskName) {
log.info("[Async] Starting async task: {} on thread {}",
taskName, Thread.currentThread().getName());

try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}

log.info("[Async] Completed async task: {}", taskName);
}
}

2.2 خدمة الإحصائيات​

package com.example.scheduling.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicLong;

@Service
public class StatisticsService {

private static final Logger log = LoggerFactory.getLogger(StatisticsService.class);

private final AtomicLong requestCount = new AtomicLong(0);
private final AtomicLong errorCount = new AtomicLong(0);

public void incrementRequests() {
requestCount.incrementAndGet();
}

public void incrementErrors() {
errorCount.incrementAndGet();
}

// تقرير الإحصائيات كل دقيقة
@Scheduled(cron = "0 * * * * *")
public void reportStatistics() {
long requests = requestCount.getAndSet(0);
long errors = errorCount.getAndSet(0);

log.info("[Stats] Last minute: {} requests, {} errors, Error rate: {}%",
requests,
errors,
requests > 0 ? (errors * 100 / requests) : 0);
}

// فحص الصحة كل 30 ثانية
@Scheduled(fixedRate = 30000)
public void healthCheck() {
Runtime runtime = Runtime.getRuntime();
long usedMemory = (runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024);
long maxMemory = runtime.maxMemory() / (1024 * 1024);
int usagePercent = (int) (usedMemory * 100 / maxMemory);

if (usagePercent > 80) {
log.warn("[Health] High memory usage: {}% ({} MB / {} MB)",
usagePercent, usedMemory, maxMemory);
} else {
log.debug("[Health] Memory usage: {}% ({} MB / {} MB)",
usagePercent, usedMemory, maxMemory);
}
}
}

الجزء 3: وحدة التحكم للاختبارات (10 دقائق)​

package com.example.scheduling.controller;

import com.example.scheduling.service.ScheduledTaskService;
import com.example.scheduling.service.StatisticsService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/api/tasks")
public class TaskController {

private final ScheduledTaskService taskService;
private final StatisticsService statisticsService;

public TaskController(ScheduledTaskService taskService,
StatisticsService statisticsService) {
this.taskService = taskService;
this.statisticsService = statisticsService;
}

@PostMapping("/async/{name}")
public ResponseEntity<Map<String, String>> triggerAsyncTask(@PathVariable String name) {
taskService.asyncTask(name);
return ResponseEntity.ok(Map.of(
"message", "Async task '" + name + "' started",
"status", "RUNNING"
));
}

@GetMapping("/simulate-traffic")
public ResponseEntity<Map<String, String>> simulateTraffic(
@RequestParam(defaultValue = "100") int requests) {

for (int i = 0; i < requests; i++) {
statisticsService.incrementRequests();
if (Math.random() < 0.05) { // 5% أخطاء
statisticsService.incrementErrors();
}
}

return ResponseEntity.ok(Map.of(
"message", requests + " requests simulated"
));
}
}

الجزء 4: الاختبارات (10 دقائق)​

4.1 مراقبة السجلات​

ابدأ التطبيق وراقب السجلات:

[Heartbeat #1] Application is running at 2024-01-15 10:00:00
[Cron] Minute task executed at 2024-01-15 10:01:00
[Cleanup] Starting cleanup task...
[Cleanup] Cleanup completed - 45 items removed
[Stats] Last minute: 0 requests, 0 errors, Error rate: 0%

4.2 اختبار المهام غير المتزامنة​

# تشغيل مهمة غير متزامنة
curl -X POST http://localhost:8080/api/tasks/async/test-task-1

# محاكاة حركة المرور
curl "http://localhost:8080/api/tasks/simulate-traffic?requests=500"

4.3 التحقق من الإحصائيات​

انتظر دقيقة واحدة وراقب تقرير الإحصائيات في السجلات.


تعبيرات Cron - مرجع​

التعبيرالوصف
0 * * * * *كل دقيقة
0 */5 * * * *كل 5 دقائق
0 0 * * * *كل ساعة
0 0 6 * * *كل يوم في الساعة 6
0 0 9 * * MONكل يوم اثنين في الساعة 9
0 0 0 1 * *أول يوم من الشهر

قائمة التحقق من التحقق​

  • المهام المجدولة تعمل
  • السجلات تظهر التنفيذات
  • المهام غير المتزامنة تعمل
  • الإحصائيات محسوبة

العودة إلى الوحدة 14

العودة إلى الصفحة الرئيسية