Redis Giao dịch

Giao dịch

MULTI, EXEC, DISCARD và WATCH là các lệnh liên quan đến giao dịch trong Redis. Giao dịch cho phép thực hiện nhiều lệnh cùng một lúc và đảm bảo hai điều quan trọng:

  • Giao dịch là một hoạt động riêng biệt: Tất cả các lệnh trong giao dịch được sắp xếp theo thứ tự và thực hiện tuần tự. Trong quá trình thực hiện, giao dịch không bị gián đoạn bởi các yêu cầu lệnh từ các client khác.
  • Giao dịch là một hoạt động nguyên tử: Tất cả các lệnh trong giao dịch hoặc được thực hiện hoàn toàn hoặc không thực hiện gì cả.

Lệnh EXEC chịu trách nhiệm kích hoạt và thực hiện tất cả các lệnh trong giao dịch:

  • Nếu client mở giao dịch bằng lệnh MULTI nhưng sau đó bị ngắt kết nối mà không thực hiện EXEC, thì tất cả các lệnh trong giao dịch sẽ không được thực hiện.
  • Ngược lại, nếu client thành công trong việc thực hiện EXEC sau khi mở giao dịch, thì tất cả các lệnh trong giao dịch sẽ được thực hiện.

Khi sử dụng AOF (Append Only File) để lưu trữ dữ liệu, Redis sẽ sử dụng lệnh write(2) duy nhất để ghi giao dịch vào đĩa.

Tuy nhiên, nếu máy chủ Redis bị tắt do lỗi phần cứng hoặc quản trị viên, có thể chỉ một phần của giao dịch được ghi vào đĩa.

Nếu Redis phát hiện lỗi này khi khởi động lại, nó sẽ dừng và báo lỗi. Sử dụng chương trình redis-check-aof có thể sửa chữa vấn đề này: nó sẽ loại bỏ thông tin về giao dịch không hoàn chỉnh trong tệp AOF, đảm bảo máy chủ có thể khởi động bình thường.

Bắt đầu từ phiên bản 2.2, Redis cũng hỗ trợ CAS (check-and-set) thông qua khóa lạc quan (optimistic lock). Chi tiết hơn, xem phần sau của tài liệu.

Cách sử dụng

Lệnh MULTI dùng để bắt đầu một giao dịch, luôn trả về OK. Sau khi chạy MULTI, client có thể gửi bất kỳ số lượng lệnh nào, các lệnh này không được thực hiện ngay lập tức mà được đưa vào một hàng đợi. Khi lệnh EXEC được gọi, tất cả các lệnh trong hàng đợi sẽ được thực hiện.

Ngược lại, bằng cách gọi DISCARD, client có thể làm trống hàng đợi giao dịch và hủy bỏ giao dịch.

Dưới đây là một ví dụ về giao dịch, tăng giá trị của hai khóa foobar một cách nguyên tử:

> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1

Lệnh EXEC trả về một mảng, mỗi phần tử trong mảng là kết quả của lệnh tương ứng trong giao dịch. Thứ tự các phần tử trong mảng tuân theo thứ tự lệnh được gửi.

Khi client đang trong trạng thái giao dịch, tất cả các lệnh gửi vào đều trả về QUEUED, và các lệnh này sẽ được thực hiện khi lệnh EXEC được gọi.

Lỗi trong giao dịch

Khi sử dụng giao dịch, có thể gặp hai loại lỗi:

  • Lệnh trong giao dịch có thể gặp lỗi trước khi thực hiện EXEC. Ví dụ, lệnh có thể gặp lỗi cú pháp (số lượng tham số không đúng, tên tham số không đúng, v.v.) hoặc lỗi nghiêm trọng hơn như thiếu bộ nhớ (nếu máy chủ đã đặt giới hạn bộ nhớ tối đa bằng maxmemory).
  • Lệnh có thể gặp lỗi sau khi gọi EXEC. Ví dụ, lệnh có thể xử lý kiểu khóa không chính xác, như sử dụng lệnh danh sách trên khóa chuỗi, v.v.

Đối với lỗi xảy ra trước khi thực hiện EXEC, client trước đây kiểm tra kết quả trả về của lệnh enqueue: nếu lệnh trả về QUEUED, thì lệnh đã được enqueue thành công; ngược lại, lệnh enqueue thất bại. Nếu có lệnh enqueue thất bại, hầu hết các client sẽ dừng và hủy bỏ giao dịch.

Tuy nhiên, từ phiên bản Redis 2.6.5, máy chủ sẽ ghi nhận lỗi enqueue và từ chối thực hiện giao dịch khi client gọi EXEC.

Trước phiên bản Redis 2.6.5, Redis chỉ thực hiện các lệnh enqueue thành công và bỏ qua các lệnh enqueue thất bại. Cách xử lý mới giúp việc bao gồm giao dịch trong pipeline trở nên đơn giản, vì gửi giao dịch và đọc kết quả trả về chỉ cần giao tiếp với máy chủ một lần.

Đối với lỗi xảy ra sau khi gọi EXEC, không có xử lý đặc biệt: ngay cả khi có lệnh trong giao dịch gặp lỗi, các lệnh khác vẫn tiếp tục thực hiện.

Từ góc độ giao thức, dễ hiểu hơn. Dưới đây là một ví dụ, lệnh LPOP sẽ gặp lỗi mặc dù cú pháp của nó là chính xác:

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
MULTI
+OK
SET a 3
abc
+QUEUED
LPOP a
+QUEUED
EXEC
*2
+OK
-ERR Operation against a key holding the wrong kind of value

Lệnh EXEC trả về hai bulk-string-reply: một là OK và một là -ERR. Cách biểu diễn lỗi trong giao dịch tùy thuộc vào client.

Quan trọng nhất là nhớ rằng, ngay cả khi có lệnh trong giao dịch gặp lỗi, các lệnh khác trong hàng đợi vẫn tiếp tục thực hiện — Redis không dừng thực hiện các lệnh trong giao dịch.

Dưới đây là một ví dụ khác, khi lệnh enqueue gặp lỗi, lỗi sẽ được trả về ngay lập tức cho client:

MULTI
+OK
INCR a b c
-ERR wrong number of arguments for 'incr' command

Vì lệnh INCR được gọi với số lượng tham số không chính xác, lệnh này enqueue thất bại.

Tại sao Redis không hỗ trợ rollback

Nếu bạn có kinh nghiệm sử dụng cơ sở dữ liệu quan hệ, việc "Redis không rollback giao dịch khi có lỗi mà vẫn tiếp tục thực hiện các lệnh còn lại" có thể khiến bạn cảm thấy lạ lẫm.

Đây là những ưu điểm của cách làm này:

  • Lệnh Redis chỉ thất bại do lỗi cú pháp (và những lỗi này không thể phát hiện được khi enqueue), hoặc lệnh được sử dụng trên kiểu khóa không chính xác: Điều này có nghĩa là, về mặt thực tế, lỗi lệnh thường do lỗi lập trình, và những lỗi này nên được phát hiện trong quá trình phát triển, không phải trong môi trường sản xuất.
  • Không cần hỗ trợ rollback, nội bộ Redis có thể giữ được đơn giản và nhanh chóng.

Một số ý kiến cho rằng cách Redis xử lý giao dịch có thể gây ra lỗi, nhưng cần lưu ý rằng, trong hầu hết các trường hợp, rollback không giải quyết được lỗi do lập trình. Ví dụ, nếu bạn muốn tăng giá trị của khóa lên 1 bằng lệnh INCR, nhưng vô tình tăng lên 2, hoặc áp dụng INCR trên kiểu khóa không chính xác, rollback không thể giải quyết những trường hợp này.

Hủy bỏ giao dịch

Khi thực hiện lệnh DISCARD, giao dịch sẽ bị hủy, hàng đợi giao dịch được làm trống, và client thoát khỏi trạng thái giao dịch:

> SET foo 1
OK
> MULTI
OK
> INCR foo
QUEUED
> DISCARD
OK
> GET foo
"1"

Sử dụng check-and-set để thực hiện khóa lạc quan

Lệnh WATCH cung cấp hành vi check-and-set (CAS) cho giao dịch Redis.

Các khóa được WATCH sẽ được giám sát và phát hiện sự thay đổi. Nếu ít nhất một khóa được giám sát bị thay đổi trước khi thực hiện EXEC, toàn bộ giao dịch sẽ bị hủy, và EXEC trả về nil-reply để chỉ ra giao dịch đã thất bại.

Ví dụ, giả sử chúng ta cần tăng giá trị của một khóa một cách nguyên tử (giả sử INCR không tồn tại).

Đầu tiên, chúng ta có thể làm như sau:

val = GET mykey
val = val + 1
SET mykey $val

Thực hiện này hoạt động tốt khi chỉ có một client. Tuy nhiên, khi nhiều client cùng thực hiện thao tác này trên cùng một khóa, sẽ xảy ra điều kiện cạnh tranh. Ví dụ, nếu client A và B cùng đọc giá trị ban đầu của khóa, như 10, cả hai client sẽ đặt giá trị khóa là 11, nhưng kết quả đúng phải là 12.

Với WATCH, chúng ta có thể giải quyết vấn đề này một cách dễ dàng:

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

Nếu có client khác thay đổi giá trị của mykey giữa lệnh WATCH và EXEC, giao dịch sẽ thất bại. Chương trình cần làm là thử lại thao tác cho đến khi không có va chạm.

Loại khóa này được gọi là khóa lạc quan, một cơ chế khóa mạnh mẽ. Vì hầu hết các client truy cập các khóa khác nhau, va chạm thường ít xảy ra, nên thường không cần thử lại.

Hiểu về WATCH

WATCH làm cho lệnh EXEC phải thực hiện có điều kiện: giao dịch chỉ được thực hiện nếu tất cả các khóa được giám sát không bị thay đổi. Nếu điều kiện này không thỏa mãn, giao dịch sẽ không được thực hiện. Tìm hiểu thêm->

Lệnh WATCH có thể được gọi nhiều lần. Giám sát khóa bắt đầu sau khi WATCH được thực hiện và kéo dài cho đến khi gọi EXEC.

Người dùng cũng có thể giám sát nhiều khóa trong một lệnh WATCH, như sau:

redis> WATCH key1 key2 key3
OK

Khi EXEC được gọi, giám sát tất cả các khóa sẽ bị hủy, bất kể giao dịch có thành công hay không.

Ngoài ra, khi client ngắt kết nối, giám sát khóa cũng sẽ bị hủy.

Sử dụng lệnh UNWATCH không có tham số để hủy giám sát tất cả các khóa. Đối với các giao dịch cần thay đổi nhiều khóa, đôi khi chương trình cần khóa nhiều khóa cùng một lúc, sau đó kiểm tra giá trị hiện tại của các khóa có phù hợp với yêu cầu của chương trình hay không. Nếu không phù hợp, lệnh UNWATCH có thể được sử dụng để hủy giám sát các khóa, từ bỏ giao dịch và chờ lần thử tiếp theo.

Sử dụng WATCH để tạo ZPOP

WATCH có thể được sử dụng để tạo các thao tác nguyên tử mà Redis không tích hợp sẵn. Ví dụ, mã dưới đây tạo ra lệnh ZPOP gốc, có thể loại bỏ nguyên tử phần tử có score nhỏ nhất từ một tập hợp có thứ tự:

WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC

Chương trình chỉ cần lặp lại đoạn mã này cho đến khi EXEC trả về không phải là nil-reply.

Redis Script và giao dịch

Theo định nghĩa, script trong Redis chính là một giao dịch, vì vậy mọi thứ có thể thực hiện trong giao dịch cũng có thể thực hiện trong script. Và nói chung, sử dụng script đơn giản và nhanh hơn.

Vì tính năng script được giới thiệu từ phiên bản Redis 2.6, trong khi giao dịch tồn tại từ sớm hơn, nên Redis có cả hai cách để xử lý giao dịch.

Tuy nhiên, chúng tôi không có kế hoạch loại bỏ giao dịch trong thời gian ngắn, vì giao dịch cung cấp cách tránh điều kiện cạnh tranh mà không cần script, và việc thực hiện giao dịch không phức tạp.

Nhưng trong tương lai, có thể tất cả người dùng sẽ chỉ sử dụng script để thực hiện giao dịch. Nếu điều này xảy ra, chúng tôi sẽ bãi bỏ và cuối cùng loại bỏ giao dịch.

Thẻ: Redis MULTI exec DISCARD Watch

Đăng vào ngày 28 tháng 6 lúc 10:51