+3

[MSDP] - Priority Queue Pattern

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.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí