Trong một số trường hợp khi các request đến server, chúng ta có thể muốn đưa các request vào hàng đợi và xử lý chúng theo trình tự hoặc theo thứ tự có ưu tiên sau đó thông báo cho người dùng sau khi các tác vụ được hoàn thành. Để các hệ thống của chúng ta có thể được kết hợp một cách mềm dẻo linh hoạt và cung cấp trải nghiệm người dùng tốt hơn vì người dùng sẽ không bị lock lại khi đợi phản hổi từ server.
Đôi khi các nghiệp vụ cần ưu tiên các nhiệm vụ theo thứ tự trước sau dựa vào một số tiêu chí. Ví dụ: yêu cầu của người dùng VIP phải được xử lý trước trước khi xử lý bất kỳ yêu cầu nào của người dùng thông thường. Chúng ta có thể thấy hành vi này trong khi bay trên máy bay, vận chuyển sản phẩm trực tuyến, v.v. Ví dụ trong hình dưới đây, các tác vụ màu đỏ là nhiệm vụ có mức độ ưu tiên cao trong khi các tác vụ màu xanh có mức độ ưu tiên thấp. Mặc dù có một nhiệm vụ màu xanh (ưu tiên thấp) ở vị trí thứ hai trong hàng đợi, một nhiệm vụ màu đỏ (có mức độ ưu tiên cao) ở vị trí thứ tư nhưng nó sẽ được xử lý trước khi xử lý bất kỳ tác vụ nào có mức độ ưu tiên thấp.
Hãy xem cách chúng ta có thể xử lý vấn đề này bằng cách sử dụng Redis như sample application dưới đây:
Sample application
- Như thường lệ để giữ mọi thứ đơn giản, chúng ta sẽ giả định rằng class Task sẽ tìm số ở vị trí thứ N trong chuỗi Fibonacci.
- Sẽ có một số danh mục như LOW, HIGH, URGENT để ưu tiên các nhiệm vụ.
- Các nhiệm vụ URGENT được thực hiện trước HIGH sau đó đến LOW.
Ta sẽ chia ứng dụng thành 3 module: model, task-executor, task-scheduler
Model
public enum Priority implements Serializable {
LOW,
HIGH,
URGENT
}
@Getter
@AllArgsConstructor
public class Task implements Comparable<Task>, Serializable {
private final Priority priority;
private final int number;
@Override
public int compareTo(Task o) {
return o.getPriority().compareTo(this.getPriority());
}
}
Task-executor
- task-executor là một dịch vụ sẽ thăm dò Redis để nhận các nhiệm vụ. Redis hoạt động giống như một hàng đợi (queue) chứa các nhiệm vụ.
@Service
public class FibService {
// intentional - 2^N
public long calculate(int n){
if(n < 2)
return n;
return calculate(n - 1) + calculate(n - 2);
}
}
Queue bean
@EnableScheduling
@SpringBootApplication
public class TaskExecutorApplication {
@Autowired
private RedissonClient redissonClient;
public static void main(String[] args) {
SpringApplication.run(TaskExecutorApplication.class, args);
}
@Bean
public RPriorityBlockingQueue<Task> getQueue(){
return this.redissonClient.getPriorityBlockingQueue("task");
}
}
Executor
@Service
public class Executor {
@Autowired
private RPriorityBlockingQueue<Task> priorityQueue;
@Autowired
private FibService fibService;
@Scheduled(fixedDelay = 1000)
public void runTask() throws InterruptedException {
System.out.println("----------------------------------------");
Task task = this.priorityQueue.take();
System.out.println("Priority : " + task.getPriority());
System.out.println("Input : " + task.getNumber());
System.out.println("Result : " + this.fibService.calculate(task.getNumber()));
}
}
Chúng ta sử dụng redisson client libarary cho Redis với cấu hình bên dưới.
singleServerConfig:
idleConnectionTimeout: 10000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
password: null
subscriptionsPerConnection: 5
clientName: null
address: "redis://master:6379"
subscriptionConnectionMinimumIdleSize: 1
subscriptionConnectionPoolSize: 50
connectionMinimumIdleSize: 24
connectionPoolSize: 64
database: 0
dnsMonitoringInterval: 5000
threads: 2
nettyThreads: 2
codec: !<org.redisson.codec.FstCodec> {}
transportMode: "NIO"
Task-Scheduler
- task-Scheduler là một ứng dụng web mà thông qua đó chúng gửi các nhiệm vụ đến hàng đợi Redis queue.
- Nó cung cấp một REST API để gửi nhiệm vụ
@RestController
@RequestMapping("/task")
public class TaskController {
@Autowired
private RPriorityBlockingQueue<Task> priorityBlockingQueue;
@GetMapping("/{priority}/{number}")
public void schedule(@PathVariable String priority, @PathVariable int number){
this.priorityBlockingQueue.add(this.getTask(priority, number));
}
private Task getTask(final String priority, final int number){
return new Task(
Priority.valueOf(priority.toUpperCase()),
number
);
}
}
docker-compose file
version: '3'
services:
master:
container_name: master
image: redis
ports:
- 6379:6379
task-scheduler:
build: ./task-scheduler
image: vinsdocker/task-scheduler
ports:
- 8080:8080
task-executor:
build: ./task-executor
image: vinsdocker/task-executor
redis-commander:
container_name: redis-commander
hostname: redis-commander
image: rediscommander/redis-commander:latest
restart: always
environment:
- REDIS_HOSTS=master:master
ports:
- 8081:8081
- Khi chúng ta chạy lệnh
docker-compose up
, docker sẽ build các file docker image và thiết lập chạy ứng dụng. Chúng ta gửi 100 nhiệm vụ ưu tiên thấp với input đầu vào là vị trí 46 trong dãy số Fibonacci. Phải mất một khoảng thời gian đáng kể để xử lý từng nhiệm vụ. Khi chúng đã có 98 nhiệm vụ trong hàng đợi, chúng ta tiếp tục gửi một yêu cầu ưu tiên với input đầu vào là vị trí 33 trong dãy số Fibonacci. Kết quả là nó sẽ được thực thi ngay lập tức và sau đó các tác vụ thấp còn lại được tiếp tục.
task-executor_1 | Priority : LOW
task-executor_1 | Input : 46
task-executor_1 | Result : 1836311903
task-executor_1 | ----------------------------------------
task-executor_1 | Priority : LOW
task-executor_1 | Input : 46
task-executor_1 | Result : 1836311903
task-executor_1 | ----------------------------------------
task-executor_1 | Priority : HIGH
task-executor_1 | Input : 33
task-executor_1 | Result : 3524578
task-executor_1 | ----------------------------------------
task-executor_1 | Priority : LOW
task-executor_1 | Input : 46
task-executor_1 | Result : 1836311903
task-executor_1 | ----------------------------------------
Tổng kết
Chúng ta vừa hoàn thành một ví dụ nhỏ sử dụng mẫu hàng đợi ưu tiên (priority-queue pattern) và design pattern này sẽ rất hữu ích khi hệ thống cần xử lý nhiều tác vụ với mức độ ưu tiên khác nhau. Hi vọng bài viết hữu ích với mọi người.
Thanks for watching!
Nguồn: https://thenewstack.wordpress.com/2021/10/27/msdp-priority-queue-pattern/
Follow me: thenewstack.wordpress.com