Chuyển tới nội dung chính

Khoá phân tán & giới hạn request

Cung cấp khóa phân tán và giới hạn tần suất request qua annotation @Lock, @RateLimit cùng LockTemplate cho khóa thủ công. Dependency chỉ định nghĩa abstraction; muốn chạy thực tế cần thêm một implementation backend (trong workspace là govex-cloud-redisson).

Khi nào sử dụng

  • Tránh chạy trùng tác vụ định kỳ giữa nhiều instance của cùng một service.
  • Bảo vệ thao tác đồng thời trên cùng một bản ghi/tài nguyên nghiệp vụ.
  • Giới hạn tần suất gọi API (ví dụ gửi OTP, gửi thông báo) theo toàn hệ thống hoặc theo IP.
  • Cần khóa thủ công trong code với API LockTemplate thay vì annotation.

Cài đặt

Thêm dependency govex-cloud-lock và backend Redisson (không cần khai version vì đã được quản lý qua BOM):

<dependency>
<groupId>vn.govex.cloud</groupId>
<artifactId>govex-cloud-lock</artifactId>
</dependency>
<dependency>
<groupId>vn.govex.cloud</groupId>
<artifactId>govex-cloud-redisson</artifactId>
</dependency>

Version do BOM vn.govex.cloud:dependencies quản lý — xem Cài đặt.

Cấu hình

Cả hai tính năng mặc định tắt — annotation chưa có tác dụng cho tới khi bật và có backend tương ứng:

PropertyMô tảMặc định
govex.lock.enabledBật tính năng khóa phân tán (LockTemplate và annotation @Lock).false
govex.lock.typeLoại LockExecutor sử dụng; đặt redisson để dùng backend Redisson.
govex.lock.delimiterKý tự phân tách khi build lock key.:
govex.lock.expireThời gian hết hạn của khóa (ms).30000
govex.lock.acquire-timeoutThời gian chờ tối đa để lấy khóa (ms).3000
govex.lock.retry-intervalKhoảng nghỉ giữa các lần thử lấy khóa (ms).100
govex.lock.primary-executorClass LockExecutor mặc định; bỏ trống thì lấy executor đầu tiên.executor đầu tiên
govex.lock.lock-key-prefixTiền tố lock key.lock:
govex.ratelimit.enabledBật tính năng giới hạn tần suất request (annotation @RateLimit).false
govex.ratelimit.typeLoại RateLimiter sử dụng; đặt redisson để dùng backend Redisson.
govex.ratelimit.prefixTiền tố ghép vào ratelimit key.rỗng
govex.ratelimit.delimiterKý tự phân tách khi build ratelimit key.:
govex.ratelimit.limitSố permit tối đa trong một cửa sổ; dùng khi annotation không khai báo limit.60
govex.ratelimit.durationĐộ dài cửa sổ thời gian (theo time unit của annotation).10
govex.ratelimit.retry-timeThời gian chờ tối đa thêm để lấy permit; 0 nghĩa là không chờ.0

Backend Redisson cho @Lock/@RateLimit

Backend Redisson nằm ở govex-cloud-redisson, dùng một RedissonClient chung cho cả khoá phân tán, giới hạn tần suất và external cache. RedissonClient được tạo từ redisson-spring-boot-starter theo cấu hình spring.data.redis.*:

spring:
data:
redis:
host: localhost
port: 6379

govex:
lock:
enabled: true
type: redisson
ratelimit:
enabled: true
type: redisson
  • RedissonLockExecutor chỉ được tạo khi govex.lock.enabled=truegovex.lock.type=redisson; RateLimiter tương tự khi govex.ratelimit.enabled=truegovex.ratelimit.type=redisson.
  • Cần Redis đang chạy; dependency nạp thêm file config/redisson.yml trên classpath như property source bổ sung nếu cần tinh chỉnh RedissonClient.
  • Bean redisTemplate chỉ được tạo khi ứng dụng chưa có bean cùng tên.

Sử dụng

Bước 1 — bật tính năng và chọn backend:

govex:
lock:
enabled: true
type: redisson
ratelimit:
enabled: true
type: redisson
limit: 60
duration: 10

Bước 2 — khóa phân tán bằng annotation. Key được build từ lock-key-prefix + delimiter + name (hoặc tên class/method) + các tham số SpEL trong keys:

@Lock(keys = "#id")
public void rebuildIndex(String id) {
// ...
}

Ví dụ thực tế cho tác vụ cron — tránh chạy trùng giữa các instance:

@Trace
@Lock
@Scheduled(cron = "0 0 0 * * ?")
public void execute() {
// ...
}

Bước 3 — giới hạn tần suất request; mặc định đếm theo toàn hệ thống, dùng limitType để đếm theo IP:

@RateLimit(name = "send-otp", limit = 5, duration = 60,
unit = TimeUnit.SECONDS, limitType = RateLimit.LimitType.IP)
public void sendOtp(String phone) {
// ...
}

Bước 4 — khóa thủ công bằng LockTemplate khi không dùng annotation:

lockTemplate.execute("job:daily", () -> {
doDailyJob();
return null;
});

lockTemplate.executeWithoutResult("job:daily", this::doDailyJob);

Lưu ý

  • Phải có ít nhất một bean LockExecutor/RateLimiter trong context. Bật govex.lock.enabled mà không có executor sẽ lỗi ngay lúc khởi tạo (executors must have at least one).
  • Với govex-cloud-redisson, cần RedissonClient trong context và đặt govex.lock.type=redisson / govex.ratelimit.type=redisson.
  • Khi không lấy được khóa, chiến lược mặc định (DefaultLockFailureStrategy) ném LockException.
  • Annotation hoạt động qua AOP proxy: gọi method có annotation từ một method khác trong cùng class (self-invocation) sẽ không được chặn — dùng LockTemplate cho trường hợp này.
  • Giá trị expire/acquireTimeout bằng 0 trên annotation nghĩa là lấy giá trị mặc định từ property govex.lock.*.
  • Toàn bộ trạng thái khóa/ratelimit nằm ở backend của implementation.