Sửa lỗi Redis 'NOSCRIPT No matching script. Please use EVAL.'

intermediate🔴 Redis2026-05-01| Redis (tất cả phiên bản), bất kỳ ứng dụng nào sử dụng Redis Lua scripting (Node.js, Python, Go, PHP, v.v.)

Error Message

NOSCRIPT No matching script. Please use EVAL.
#redis#lua#evalsha#scripting#cache

Cách khắc phục nhanh

Redis đưa ra lỗi NOSCRIPT khi không thể tìm thấy script Lua khớp với mã hash SHA1 mà bạn đã cung cấp thông qua EVALSHA. Vì Redis lưu trữ các script trong RAM, bất kỳ sự gián đoạn nào đối với máy chủ đều có thể xóa sạch bộ nhớ đệm này. Giải pháp là bắt lỗi trong ứng dụng của bạn và quay lại sử dụng lệnh EVAL đầy đủ.

# Luồng logic cho một giải pháp bền vững
try:
    return redis.evalsha(script_sha1, keys, args)
catch error "NOSCRIPT":
    # Script bị thiếu trong bộ nhớ đệm; gửi toàn bộ mã nguồn
    # Việc này cũng giúp lưu lại script vào bộ nhớ đệm cho lần sau
    return redis.eval(script_content, keys, args)

Nguyên nhân gốc rễ: Tại sao Script bị mất

Redis tối ưu hóa hiệu suất bằng cách lưu script vào bộ nhớ đệm. Hãy tưởng tượng bạn có một script Lua nặng 10KB. Việc gửi nó qua mạng 1.000 lần mỗi giây sẽ lãng phí 10MB băng thông mỗi giây. Redis tránh điều này bằng cách cho phép bạn gửi script một lần (nhận được mã hash SHA1 40 ký tự) và sau đó sử dụng EVALSHA <SHA1> cho tất cả các lần gọi sau đó.

Tuy nhiên, bộ nhớ đệm này không ổn định. Lỗi NOSCRIPT thường kích hoạt trong bốn tình huống sau:

- **Khởi động lại máy chủ:** Redis không lưu lại bộ nhớ đệm script vào các tệp RDB hoặc AOF. Việc khởi động lại sẽ xóa sạch bộ nhớ.
- **Phân mảnh cụm (Cluster Sharding):** Đây là một sai lầm phổ biến. Mỗi nút trong một Redis Cluster duy trì bộ nhớ đệm script riêng của nó. Nếu client của bạn kết nối với một nút master mới chưa từng thấy script đó, nó sẽ thất bại.
- **Xóa thủ công:** Quản trị viên hoặc một tiến trình bảo trì định kỳ có thể đã thực thi lệnh `SCRIPT FLUSH`, xóa sạch tất cả logic đã lưu trong bộ nhớ đệm.
- **Áp lực bộ nhớ:** Nếu Redis đạt đến giới hạn `maxmemory`, nó có thể loại bỏ dữ liệu. Mặc dù bộ nhớ cho script được xử lý riêng biệt, nhưng sự mất ổn định cực độ có thể dẫn đến hành vi bộ nhớ đệm không mong muốn.

Cách xử lý vĩnh viễn

1. Mô hình "Try-Catch-Reload" (Cách tiếp cận tốt nhất)

Đừng giả định rằng script luôn có sẵn. Thay vào đó, hãy thiết kế client của bạn để xử lý sự vắng mặt của nó một cách khéo léo. Dưới đây là một ví dụ triển khai bằng Node.js (ioredis):

const script = "return redis.call('get', KEYS[1])";
const sha1 = "d0c0316d2d385f76263e699b664d50c76ca4c01d";

try {
  await redis.evalsha(sha1, 1, "user:session");
} catch (err) {
  if (err.message.includes("NOSCRIPT")) {
    // Chuyển hướng sang EVAL. Việc này sẽ thực thi VÀ lưu lại script vào bộ nhớ đệm.
    await redis.eval(script, 1, "user:session");
  } else {
    throw err;
  }
}

2. Tải trước trong quá trình khởi động

Nếu ứng dụng của bạn phụ thuộc vào các script cốt lõi, hãy tải chúng trong giai đoạn khởi chạy (bootstrap). Điều này đảm bảo bộ nhớ đệm đã được làm nóng trước khi yêu cầu đầu tiên của người dùng đến.

# Tải script thủ công qua CLI
redis-cli SCRIPT LOAD "return redis.call('get', KEYS[1])"
# Đầu ra: "d0c0316d2d385f76263e699b664d50c76ca4c01d"

Trong mã nguồn thực tế, hãy lặp qua các tệp .lua của bạn và chạy SCRIPT LOAD cho từng tệp. Tuy nhiên, hãy luôn giữ mô hình Try-Catch, vì nó đóng vai trò như một lưới an toàn quan trọng nếu máy chủ khởi động lại giữa phiên làm việc.

3. Xử lý Redis Cluster

Trong một cụm, việc chạy SCRIPT LOAD trên một nút sẽ không đồng bộ hóa sang các nút khác. Hầu hết các thư viện cấp cao (như redis-py hoặc ioredis) cung cấp các đối tượng "Script" tự động xử lý việc tải lên nhiều nút. Hãy sử dụng các lớp trừu tượng này thay vì các lệnh thô để đơn giản hóa việc quản lý cụm.

Xác minh: Kiểm tra bộ nhớ đệm

Bạn có thể xác minh xem một script hiện có trong bộ nhớ đệm hay không mà không cần thực sự chạy nó. Sử dụng lệnh SCRIPT EXISTS với mã hash SHA1 của bạn:

# Kiểm tra mã SHA1 cụ thể của bạn
redis-cli SCRIPT EXISTS d0c0316d2d385f76263e699b664d50c76ca4c01d

# Kết quả:
# 1) (integer) 1  <-- Đã lưu trong bộ nhớ đệm và sẵn sàng
# 1) (integer) 0  <-- Thiếu; sẽ gây ra lỗi NOSCRIPT

Mẹo chuyên nghiệp và Phòng ngừa

Chỉ cần một khoảng trắng ẩn hoặc một dòng trống thừa ở cuối script cũng sẽ thay đổi hoàn toàn mã hash SHA1. Điều này thường dẫn đến những phiên gỡ lỗi khó khăn khi mã nguồn trông giống hệt nhau nhưng các mã hash lại không khớp. Tôi luôn sử dụng một công cụ định dạng nhất quán cho các khối Lua để ngăn chặn điều này.

Cá nhân tôi sử dụng Hash Generator trên ToolCraft để xác minh mã hash script của mình. Nó chạy hoàn toàn trong trình duyệt của bạn. Điều này có nghĩa là logic và các khóa của bạn không bao giờ rời khỏi máy tính, giúp mã nội bộ của bạn an toàn trong khi bạn gỡ lỗi.

Đọc thêm

- [Hướng dẫn Redis Lua API chính thức](https://redis.io/docs/manual/programmability/lua-api/)
- [Tài liệu tham khảo lệnh EVALSHA](https://redis.io/commands/evalsha/)
- [Tài liệu lệnh SCRIPT LOAD](https://redis.io/commands/script-load/)

Related Error Notes