Note: see the link below for the English version of this article.

https://duongnt.com/redis-python-csharp

Đây là bài số 3 trong loạt 3 bài về distributed caching sử dụng Redis. Xin xem trang này để lấy đường link tới các phần còn lại.

Trong 2 bài trước, chúng ta đã tìm hiểu cách tích hợp Redis với ứng dụng Python hoặc C# .NET. Hôm nay, chúng ta sẽ dùng Python để đọc dữ liệu do C# ghi vào Redis và ngược lại.

Bước chuẩn bị

Cũng như trong 2 phần trước, chúng ta cần có server Redis để chạy code trong các ví dụ ngày hôm nay. Xin tham khảo cách thiết lập server Redis trong Docker tại bài đầu tiên.

Như ta biết, C# và Python là 2 ngôn ngữ với hệ thống kiểu dữ liệu khác nhau. Vì vậy, chúng ta chỉ có thể trao đổi dữ liệu với kiểu cơ bản. Có nghĩa là ta có thể trao đổi dữ liệu với kiểu string, integer hay boolean…, nhưng ta không thể trao đổi các kiểu dữ liệu do hệ thống hay người dùng định nghĩa như DateTime, StringBuilder or StopWatch. Ta có thể serialize các kiểu dữ liệu phức tạp đó trước khi ghi chúng vào Redis, nhưng khi sang ngôn ngữ khác ta sẽ không thể thực hiện bước deserialize.

Dùng Python để đọc dữ liệu do C# .NET ghi

Đọc dữ liệu string

Đây là code C# để ghi một string vào Redis. Nếu đã đọc phần 2 thì có lẽ các bạn sẽ thấy nó rất quen thuộc.

await _cache.SetAsync("string", Encoding.UTF8.GetBytes("Example")); Ghi string "Example" vào Redis

Ta dùng redis-cli để kiểm tra dữ liệu trong Redis (lưu ý là StackExchangeRedis ghi dữ liệu vào Redis dưới dạng hash).

hgetall string

1) "absexp"
2) "-1"
3) "sldexp"
4) "-1"
5) "data"
6) "Example"

Vì ở đây ta không quan tâm tới thời điểm dữ liệu bị hết hạn, ta chỉ cần đọc trường data của hash. Như đã nhắc đến trong phần một, ta sử dụng hàm hget. Lưu ý là giá trị trả về có kiểu là bytes.

string_bytes = await redis.hget('string', 'data')
string_value = string_bytes.decode('utf-8')
print(string_value) # Sẽ xuất ra "Example"

Ta cũng có thể dùng hàm hgetall để đọc về toàn bộ hash.

dict = await redis.hgetall('string') # dict là dictionary

Nội dung của dict là như sau.

{b'absexp': b'-1', b'sldexp': b'-1', b'data': b'Example'}

Giá trị Example là một trường trong dictionary được trả về. Lưu ý là không chỉ giá trị của trường mà cả key tương ứng cũng có kiểu dữ liệu là bytes. Dưới đây là code để lấy giá trị của trường data.

string_key = 'data'.encode('utf-8')
string_bytes = dict[string_key]
print(string_bytes.decode('utf-8')) # Sẽ xuất ra "Example"

Đọc dữ liệu số

Dưới đây là đoạn code C# để ghi một giá trị integer vào Redis.

await _cache.SetAsync("number", BitConverter.GetBytes(17)); //Ghi số 17 vào Redis

Ta lại dùng redis-cli để kiểm tra dữ liệu trong Redis.

hgetall number

1) "absexp"
2) "-1"
3) "sldexp"
4) "-1"
5) "data"
6) "\x11\x00\x00\x00"

Đoạn code để lấy ra dữ liệu dưới dạng bytes là rất đơn giản.

number_bytes = await redis.hget('number', 'data')

Để chuyển dữ liệu từ dạng bytes về lại dạng số, ta cần biết endian của dữ liệu đó. Như đã nói ở trên, số của ta là một integer, tương ứng với dạng int trong Python. Còn endian thì phụ thuộc vào ta thiết bị mà ta dùng để chạy code. Máy tính của tôi sử dụng endian nhỏ.

number_val = int.from_bytes(number_bytes, byteorder='little') 
print(number_val)# Sẽ xuất ra 17

Tất nhiên ta cũng có thể lấy ra toàn bộ hash.

dict = await redis.hgetall('number')

# {b'absexp': b'-1', b'sldexp': b'-1', b'data': b'\x11\x00\x00\x00'}

Dùng C# .NET để đọc dữ liệu do Python ghi

Như ta đã biết, StackExchangeRedis chỉ đọc và ghi được dữ liệu với format định sẵn. Vì thế, vấn đề ở đây là làm thế nào để Python ghi được dữ liệu vào Redis với format mà StackExchangeRedis có thể hiểu được. Ta cần ghi dữ liệu dưới dạng bytes vào một trường có tên là data trong một hash.

Đọc dữ liệu string

Đầu tiên, ta cần ghi string vào Redis bằng Python. Trong trường hợp này ta chỉ cần gọi hàm hset như dưới đây. Đó là vì aioredis sẽ tự động encode dữ liệu thay ta.

await redis.hset('string_python', 'data', 'Example Python')

Hoặc ta có thể tự encode dữ liệu.

str_bytes = 'Example Python'.encode('utf-8')
await redis.hset('string_python', 'data', str_bytes)

Sau đó, ta có thể dùng code C# dưới đây để đọc string từ Redis.

var stringBytes = await _cache.GetAsync("string_python");
var stringVal = Encoding.UTF8.GetString(stringBytes) # stringVal là "Example"

Đọc dữ liệu số

Khi thoạt nhìn qua, có vẻ code Python trong trường hợp này cũng rất đơn giản.

await redis.hset('number_python', 'data', 1989)

Nhưng kết quả khi ta dùng C# để đọc giá trị của number_python là như sau.

var numberBytes = await _cache.GetAsync("number_python");
var numberVal = BitConverter.ToInt32(numberBytes); // numberVal là 959985969

Nguyên nhân là vì aioredis sẽ chuyển số 1989 sang dạng string, encode string đó, rồi ghi giá trị của string đã encode vào Redis (ta đã nhắc đến điều này trong phần một của loạt bài). Để ghi số vào Redis bằng aioredis, ta cần tự mình chuyển nó sang dạng bytes.

number_bytes = (1989).to_bytes(length=4, byteorder='little')
  • length: đây là kích thước của số. Bởi vì dữ liệu của ta có kiểu Int32 trong C#, kích cỡ của nó là 4 byte (32 bit).
  • byteorder: như đã nói ở trên, máy tính của tôi dùng endian nhỏ; thiết bị của bạn có thể dùng endian lớn.

Sau đó, ta ghi giá trị của bytes ở trên vào Redis.

await redis.hset('number_python', 'data', number_bytes)

Lúc này, đoạn code C# sẽ trả về kết quả đúng như ta mong đợi.

var numberBytes = await _cache.GetAsync("number_python");
var numberVal = BitConverter.ToInt32(numberBytes); // numberVal là 1989

Cách dùng Python để đặt thời điểm cache hết hạn

Có lẽ các bạn cũng để ý là ta không ghi hai trường absexpsldexp vào Redis. Liệu ta có thể bổ sung 2 trường này để thiết lập thời điểm cache hết hạn được không? Rất tiếc là không, bản thân 2 trường đó không có ý nghĩa gì với Redis cả. Như có thể thấy tại đây, lệnh ta cần dùng là EXPIRE.

Trong package aioredis, lệnh đó tương ứng với hàm expire.

redis.expire(<mykey>, <seconds>)

Ví dụ: để khiến cache với key là string_python hết hạn sau 1 giờ đồng hồ, ta dùng đoạn code sau.

await redis.expire('string_python', 3600)

Kết thúc

Mặc dù bị hạn chế chỉ sử dụng được các kiểu dữ liệu cơ bản, chúng ta đã dùng được Redis để trao đổi dữ liệu giữa ứng dụng C# .NET và Python. Một điều thú vị ở đây là trong Redis không hề tồn tại khái niệm sliding expiration. Vậy StackExchangeRedis đã viết chức năng đó như thế nào? Chúng ta sẽ tìm hiểu điều này trong bài đặc biệt của loạt bài này.

A software developer from Vietnam and is currently living in Japan.

One Thought on “Trao đổi dữ liệu giữa ứng dụng Python và C# .NET thông qua Redis”

Leave a Reply