Computer >> Máy Tính >  >> Lập trình >> Redis

Lua:Hướng dẫn cho người dùng Redis

Bạn đã nghe nói rằng Redis có một ngôn ngữ kịch bản được nhúng, nhưng bạn chưa thử? Dưới đây là chuyến tham quan về những điều bạn cần hiểu để sử dụng sức mạnh của Lua với máy chủ Redis của bạn.

Xin chào, Lua!

Tập lệnh Redis Lua đầu tiên của chúng tôi chỉ trả về một giá trị mà không thực sự tương tác vớiRedis theo bất kỳ cách nào có ý nghĩa:

local msg = "Hello, world!"
return msg

Điều này đơn giản như nó nhận được. Dòng đầu tiên thiết lập một biến cục bộ với ourmessage và dòng thứ hai trả về giá trị đó từ máy chủ Redis đến theclient. Lưu tệp này cục bộ dưới dạng hello.lua và chạy nó như vậy:

redis-cli --eval hello.lua

Sự cố kết nối?

redis-cli này ví dụ giả sử rằng bạn đang chạy cục bộ Redisserver. Nếu bạn đang làm việc với một máy chủ từ xa như RedisGreen, bạn sẽ cần chỉ định thông tin máy chủ và cổng. Tìm Kết nối onyour bảng điều khiển RedisGreen của bạn để nhanh chóng sao chép thông tin đăng nhập cho máy chủ của bạn.

Xem thêm:Không thể kết nối với Redis tại 127.0.0.1:6379:Kết nối bị từ chối.

Chạy điều này sẽ in “Xin chào, thế giới!”. Đối số đầu tiên của EVAL là tập lệnh lua hoàn chỉnh - ở đây chúng tôi đang sử dụng cat lệnh để đọc tập lệnh từ một tệp. Đối số thứ hai là số Rediskeys mà tập lệnh sẽ truy cập. Tập lệnh “Hello World” đơn giản của chúng tôi không truy cập vào bất kỳ khóa nào, vì vậy chúng tôi sử dụng 0 .

Truy cập Khóa và Đối số

Giả sử chúng tôi đang xây dựng một công cụ rút ngắn URL. Mỗi khi một URL đến, chúng tôi muốn lưu trữ và trả về một số duy nhất có thể được sử dụng để truy cập URL sau này.

Chúng tôi sẽ sử dụng tập lệnh Lua để nhận ID duy nhất từ ​​Redis bằng cách sử dụng INCR và ngay lập tức lưu trữ URL trong một hàm băm được khóa bởi ID duy nhất:

local link_id = redis.call("INCR", KEYS[1])
redis.call("HSET", KEYS[2], link_id, ARGV[1])
return link_id

Chúng tôi đang truy cập Redis lần đầu tiên tại đây, sử dụng call() hàm. call() Các đối số của là các lệnh để gửi đến Redis:trước tiên chúng ta INCR <key> , sau đó chúng ta HSET <key> <field> <value> . Hai lệnh này sẽ chạy tuần tự— Redis sẽ không thực hiện bất kỳ điều gì khác trong khi tập lệnh này thực thi và nó sẽ chạy nhanh chóng.

Chúng tôi đang truy cập hai bảng Lua, KEYSARGV . Các bảng là các bảng kết hợp và là cơ chế duy nhất của Lua để cấu trúc dữ liệu. Đối với các mục đích của chúng tôi, bạn có thể coi chúng tương đương với một mảng trong bất kỳ ngôn ngữ nào mà bạn cảm thấy thoải mái nhất, nhưng hãy lưu ý hai Lua-isms này có lợi cho ngôn ngữ mới:

  • Các bảng dựa trên một bảng, tức là, lập chỉ mục bắt đầu từ 1. Vì vậy, phần tử đầu tiên trong mytablemytable[1] , thứ hai là mytable[2] , v.v.

  • Các bảng không thể chứa giá trị nil. Nếu một thao tác sẽ mang lại một bảng gồm [ 1, nil, 3, 4 ] , kết quả thay vào đó sẽ là [ 1 ] - bảng bị cắt ngắn ở giá trị nil đầu tiên.

Khi chúng tôi gọi tập lệnh này, chúng tôi cũng cần chuyển các giá trị cho KEYSARGV những cái bàn. Trong giao thức Redis thô, lệnh có dạng như sau:

EVAL $incrset.lua 2 links:counter links:url https://malcolmgladwellbookgenerator.com/

Khi gọi EVAL , sau tập lệnh mà chúng tôi cung cấp 2 dưới dạng số KEYS sẽ được truy cập, sau đó chúng tôi liệt kê KEYS của chúng tôi và cuối cùng chúng tôi cung cấp giá trị cho ARGV .

Thông thường, khi chúng tôi xây dựng ứng dụng bằng tập lệnh Redis Lua, thư viện Redisclient sẽ đảm nhận việc chỉ định số lượng khóa. Codeblock ở trên được hiển thị đầy đủ, nhưng đây là cách dễ dàng hơn để thực hiện việc này tại dòng lệnh:

Liên kết
redis-cli --eval incrset.lua links:counter links:urls , https://malcolmgladwellbookgenerator.com/

Khi sử dụng --eval như trên, dấu phẩy phân tách KEYS[] từ ARGV[] các mặt hàng.

Chỉ để làm rõ ràng mọi thứ, đây là tập lệnh ban đầu của chúng tôi một lần nữa, lần này là với KEYSARGV mở rộng:

local link_id = redis.call("INCR", "links:counter")
redis.call("HSET", "links:urls", link_id, "https://malcolmgladwellbookgenerator.com")
return link_id

Khi viết tập lệnh Lua cho Redis, mọi khóa được truy cập chỉ được truy cập bởi KEYS bàn. ARGV bảng được sử dụng để chuyển tham số - đây là giá trị của URL mà chúng tôi muốn lưu trữ.

Logic có điều kiện:increx và hincrex

Ví dụ của chúng tôi ở trên lưu liên kết cho trình rút ngắn URL của chúng tôi, nhưng chúng tôi cũng cần theo dõi số lần một URL đã được truy cập. Để làm được điều đó, chúng tôi sẽ tiếp tục sử dụng hàm băm trong Redis. Khi người dùng đi kèm với một số nhận dạng liên kết, chúng tôi sẽ kiểm tra xem nó có tồn tại hay không và tăng bộ đếm của chúng tôi cho nó nếu có:

if redis.call("HEXISTS", KEYS[1], ARGV[1]) == 1 then
  return redis.call("HINCRBY", KEYS[1], ARGV[1], 1)
else
  return nil
end

Mỗi khi ai đó nhấp vào liên kết ngắn, chúng tôi chạy tập lệnh này để theo dõi rằng liên kết đó đã được chia sẻ lại hay chưa. Chúng tôi gọi tập lệnh bằng EVAL và chuyển vào các liên kết links:visits cho khóa đơn của chúng tôi và mã nhận dạng liên kết được trả về từ tập lệnh trước của chúng tôi dưới dạng đối số duy nhất.

Tập lệnh sẽ trông gần như giống nhau nếu không có hàm băm. Dưới đây là chỉ số làm tăng một khóa Redis tiêu chuẩn chỉ khi nó tồn tại:

if redis.call("EXISTS",KEYS[1]) == 1 then
  return redis.call("INCR",KEYS[1])
else
  return nil
end

SCRIPT LOAD và EVALSHA

Hãy nhớ rằng khi Redis đang chạy một tập lệnh Lua, nó sẽ không chạy bất cứ thứ gì khác. Các tập lệnh tốt nhất chỉ đơn giản là mở rộng vốn từ vựng Redis hiện có về các hoạt động dữ liệu nguyên tử nhỏ với một chút logic nhỏ nhất cần thiết. Các lỗi trong tập lệnh Lua có thể khóa hoàn toàn máy chủ Redis - tốt nhất là giữ mọi thứ ngắn gọn và dễ gỡ lỗi.

Mặc dù chúng thường khá ngắn, chúng tôi không cần chỉ định thời gian đầy đủ của tập lệnh Lua mà chúng tôi muốn chạy. Trong một ứng dụng thực, thay vào đó, bạn sẽ đăng ký eachof các tập lệnh Lua của mình với Redis khi ứng dụng của bạn khởi động (hoặc khi bạn triển khai), sau đó gọi các tập lệnh sau bằng mã nhận dạng SHA-1 duy nhất của chúng.

redis-cli SCRIPT LOAD "return 'hello world'"
# "5332031c6b470dc5a0dd9b4bf2030dea6d65de91"

redis-cli EVALSHA 5332031c6b470dc5a0dd9b4bf2030dea6d65de91 0
# "hello world"

Một lệnh gọi rõ ràng tới SCRIPT LOAD thường là không cần thiết trong các ứng dụng trực tiếp vì EVAL tải ngầm tập lệnh được chuyển cho nó. Ứng dụng có thể thử EVALSHA lạc quan và quay trở lại EVAL chỉ khi không tìm thấy script.

Nếu bạn là một lập trình viên Ruby, hãy xem Shopify’s Wolverine, công cụ này giúp đơn giản hóa việc tải và lưu trữ các tập lệnh Lua cho các ứng dụng Ruby. Đối với các lập trình viên PHP, Predis hỗ trợ thêm Luascripts để được gọi giống như chúng là các lệnh Redis bình thường. Nếu bạn sử dụng những công cụ này hoặc các công cụ khác để chuẩn hóa tương tác của mình với Lua, hãy cho tôi biết— Tôi muốn tìm hiểu xem còn có gì khác ngoài đó.

Khi nào thì sử dụng Lua?

Hỗ trợ của Redis cho Lua hơi trùng lặp với WATCH / MULTI / EXEC khối, nhóm các hoạt động để chúng được thực thi cùng nhau. Vì vậy, làm thế nào để bạn chọn để sử dụng cái này hơn cái kia? Mỗi thao tác trong một MULTI khối cần phải phụ thuộc, nhưng với Lua, các hoạt động sau này phụ thuộc vào kết quả của các hoạt động trước đó. Sử dụng tập lệnh Lua cũng có thể tránh các điều kiện có thể gây chết máy khách chạy chậm khi WATCH được sử dụng.

Theo những gì chúng ta đã thấy tại RedisGreen, hầu hết các ứng dụng sử dụng Lua cũng sẽ sử dụngMULTI / EXEC, nhưng không phải ngược lại. Hầu hết các tập lệnh Lua thành công đều rất nhỏ và chỉ hoàn thiện một tính năng duy nhất mà ứng dụng của bạn cần nhưng không phải là một phần của Từ vựng đỏ.

Tham quan Thư viện

Trình thông dịch Redis Lua tải bảy thư viện:cơ sở, bảng, chuỗi, toán học, gỡ lỗi, cjson và cmsgpack. Firstseveral là các thư viện tiêu chuẩn cho phép bạn thực hiện các thao tác cơ bản mà bạn mong đợi từ bất kỳ ngôn ngữ nào. Hai điều cuối cùng cho phép Redis hiểu JSON và MessagePack— đây là một tính năng cực kỳ hữu ích và tôi tiếp tục tự hỏi tại sao tôi không thấy nó được sử dụng thường xuyên hơn.

Các ứng dụng web có API công khai có xu hướng có JSON nằm xung quanh. Vì vậy, có thể bạn có một loạt các đốm màu JSON được lưu trữ trong các khóa Redis bình thường và bạn muốn tích lũy một số giá trị cụ thể bên trong chúng, như thể bạn đã lưu trữ chúng dưới dạng băm. Với sự hỗ trợ JSON của Redis, điều đó thật dễ dàng:

if redis.call("EXISTS", KEYS[1]) == 1 then
  local payload = redis.call("GET", KEYS[1])
  return cjson.decode(payload)[ARGV[1]]
else
  return nil
end

Ở đây chúng tôi kiểm tra xem khóa có tồn tại hay không và nhanh chóng trả về nil nếu không. Sau đó, loại bỏ giá trị JSON khỏi Redis, phân tích cú pháp nó bằng cjson.decode() và trả về giá trị được yêu cầu ở đó.

redis-cli set apple '{ "color": "red", "type": "fruit" }'
# OK

redis-cli --eval json-get.lua apple , type
# "fruit"

Việc tải tập lệnh này vào máy chủ Redis của bạn cho phép bạn xử lý các giá trị JSON được lưu trữ trongRedis như thể chúng là các hàm băm. Nếu các đối tượng của bạn có kích thước tương đối nhỏ, điều này thực sự khá nhanh, mặc dù chúng tôi phải phân tích cú pháp giá trị trên mỗi lần truy cập.

Nếu bạn đang làm việc trên một API nội bộ cho một hệ thống yêu cầu hiệu suất, bạn có thể chọn MessagePack thay vì JSON, vì nó nhỏ hơn và nhanh hơn. cho JSON:

if redis.call("EXISTS", KEYS[1]) == 1 then
  local payload = redis.call("GET", KEYS[1])
  return cmsgpack.unpack(payload)[ARGV[1]]
else
  return nil
end

Những con số rắc rối

Lua và Redis có các hệ thống loại khác nhau, vì vậy điều quan trọng là phải hiểu giá trị có thể thay đổi như thế nào khi vượt qua biên giới Redis-Lua. Khi một số đến từLua quay trở lại ứng dụng khách Redis, nó sẽ trở thành số lớn hơn - bất kỳ chữ số nào sau dấu thập phân đều bị loại bỏ:

local indiana_pi = 3.2
return indiana_pi

Khi bạn chạy tập lệnh này, Redis sẽ trả về một số nguyên là 3 - bạn sẽ mất các mảnh pi hấp dẫn. Có vẻ đơn giản, nhưng mọi thứ trở nên phức tạp hơn một chút khi bạn bắt đầu tương tác với Redis ở giữa kịch bản. Ví dụ:

local indiana_pi = 3.2
redis.call("SET", "pi", indiana_pi)
return redis.call("GET", "pi")

Giá trị kết quả ở đây là astring:"3.2" Tại sao? Redis không có loại số chuyên dụng. Khi chúng ta SET lần đầu tiên giá trị, Redis lưu nó dưới dạng một chuỗi, mất tất cả bản ghi về thực tế là Lua ban đầu nghĩ giá trị là một số thực. Khi chúng tôi rút giá trị ra sau đó, nó vẫn là một chuỗi.

Các giá trị trong Redis được truy cập bằng GET / SET nên được coi là chuỗi ngoại trừ khi các hoạt động số như INCRDECR đang chống lại họ. Các phép toán số đặc biệt sẽ thực sự trả về các câu trả lời số nguyên (và điều chỉnh giá trị được lưu trữ theo các quy tắc toán học), nhưng “kiểu” của giá trị được lưu trữ trong Redis vẫn là giá trị chuỗi.

Gotchas:Tóm tắt

Đây là những lỗi phổ biến nhất mà chúng tôi thấy khi làm việc với Lua trong Redis:

  • Các bảng là một bảng dựa trên Lua, không giống như hầu hết các ngôn ngữ phổ biến. Phần tử đầu tiên trong bảng KEYS là KEYS[1] , thứ hai là KEYS[2] , v.v.

  • Giá trị nil kết thúc một bảng trong Lua. Vì vậy, [ 1, 2, nil, 3 ] sẽ tự động trở thành [1, 2] . Không sử dụng giá trị nil trong bảng.

  • redis.call sẽ phát sinh lỗi Lua kiểu ngoại lệ, trong khi redis.pcall sẽ tự động bẫy bất kỳ lỗi nào và trả lại chúng có thể kiểm tra được.

  • Số Lua được chuyển đổi thành số nguyên khi được gửi đến Redis - mọi thứ cuối cùng là dấu thập phân bị mất. Chuyển đổi bất kỳ số dấu phẩy động nào thành chuỗi trước khi trả về.

  • Đảm bảo chỉ định tất cả các khóa bạn sử dụng trong tập lệnh Lua của mình trong KEYS bảng, nếu không các tập lệnh của bạn có thể sẽ bị hỏng trong các phiên bảnRedis trong tương lai.

  • Các tập lệnh Lua cũng giống như bất kỳ hoạt động nào khác trong Redis:không có gì khác runswhile mà chúng đang được thực thi. Hãy coi các mô tả như một cách để mở rộng vốn từ vựng của máy chủ Redis - giữ cho từ ngắn gọn và cho điểm.

Đọc thêm

Có rất nhiều tài nguyên tuyệt vời dành cho Lua và Redis trực tuyến - đây là một vài tài nguyên tôi sử dụng:

  • Tài liệu EVAL
  • Thư viện tập lệnh Lua của RedisGreen
  • Hướng dẫn sử dụng Lua
  • Thư mục Hướng dẫn Lua