Note: phiên bản Tiếng Việt của bài này ở link dưới.
https://duongnt.com/redis-python-csharp-vie
This is the third part of a three-part series about Redis-based distributed caching. You can find the other parts from here.
In the previous two articles, we have looked at how to integrate Redis with a Python or C# .NET application. Today, we will use Python to read data written into Redis with C# and vice versa.
Before we start
Obviously, we need a Redis instance to run all the test code in this article. Please reference the first part for how to set up Redis using Docker.
And as we know, C# and Python are two different languages with their own type system. Because of this, we can only exchange primitive data types. This means while types like string
, integer
orboolean
,… are fine, framework or user-defined classes like DateTime
, StringBuilder
or StopWatch
are off limit. It’s true that we can serialize a custom type before writing it into Redis, but there is no reasonable way to deserialize that data on the other side.
Use Python to read data written with C# .NET
Read a string
This is the C# code to write a string into Redis. This should look familiar if you have read the second part of this series.
await _cache.SetAsync("string", Encoding.UTF8.GetBytes("Example")); Write the string "Example" into Redis cache
We use redis-cli
to check the value inside Redis (remember that StackExchangeRedis write data into Redis as hash).
hgetall string
1) "absexp"
2) "-1"
3) "sldexp"
4) "-1"
5) "data"
6) "Example"
Since we don’t care about the expiration settings, we only retrieve the data
field from the hash. As mentioned in part one, we can use the hget
method in this case. Remember that the returned value is of type bytes
.
string_bytes = await redis.hget('string', 'data')
string_value = string_bytes.decode('utf-8')
print(string_value) # Will print "Example"
Or we can retrieve the whole hash with hgetall
.
dict = await redis.hgetall('string') # dict is a dictionary
The content of dict
is below.
{b'absexp': b'-1', b'sldexp': b'-1', b'data': b'Example'}
The string Example
is an entry in the returned dictionary. Note that not only the values, but also the keys of dict
are of type bytes. Below is the code to retrieve the value of the data
field.
string_key = 'data'.encode('utf-8')
string_bytes = dict[string_key]
print(string_bytes.decode('utf-8')) # Will print "Example"
Read a number
This is the C# code to write an integer
into Redis.
await _cache.SetAsync("number", BitConverter.GetBytes(17)); // Write the number 17 into Redis cache
Again, we use redis-cli
to check the value inside Redis.
hgetall number
1) "absexp"
2) "-1"
3) "sldexp"
4) "-1"
5) "data"
6) "\x11\x00\x00\x00"
The code to retrieve the bytes
representation is simple.
number_bytes = await redis.hget('number', 'data')
To convert number_bytes
back into a number, we need its "endianness". As mentioned above, our number is an integer
, which is type int
in Python. The "endianness", however, depends on the system we run our code on. In my case, I’m using a Windows computer with little-endian.
number_val = int.from_bytes(number_bytes, byteorder='little')
print(number_val)# Will print 17
Of course, we can also read the whole hash.
dict = await redis.hgetall('number')
# {b'absexp': b'-1', b'sldexp': b'-1', b'data': b'\x11\x00\x00\x00'}
Use C# .NET to read data written with Python
Given the type restriction of StackExchangeRedis, the actual question is how can Python write data in a format that C# .NET code can understand. This means we need to write our data as bytes
into a field called data
of a hash.
Read a string
First, we need to write a string into Redis from the Python side. We can simply use the hset
method in this case. This is because aioredis will automatically encode our value.
await redis.hset('string_python', 'data', 'Example Python')
Or you can be explicit.
str_bytes = 'Example Python'.encode('utf-8')
await redis.hset('string_python', 'data', str_bytes)
Then we can use the C# code below to read that string from Redis.
var stringBytes = await _cache.GetAsync("string_python");
var stringVal = Encoding.UTF8.GetString(stringBytes) # stringVal is "Example"
Read a number
At first glance, the Python code to write a number into Redis seems simple.
await redis.hset('number_python', 'data', 1989)
But this is the result when we try to read number_python
from the C# side.
var numberBytes = await _cache.GetAsync("number_python");
var numberVal = BitConverter.ToInt32(numberBytes); // numberVal is 959985969
This is because aioredis will convert the number 1989
into a string, encode that string, then write the encoded string into Redis (we covered this in the first part of this series). To write a number using aioredis, we need to manually convert it into bytes
.
number_bytes = (1989).to_bytes(length=4, byteorder='little')
length
: this is the size of our number. Because we will convert the number into anInt32
on C# side, the length here will be 4 bytes (32 bits).byteorder
: as mentioned above, my machine uses little-endian; yours might differ.
Then we can write the bytes
value into Redis.
await redis.hset('number_python', 'data', number_bytes)
Now, the C# code should return the value we expected.
var numberBytes = await _cache.GetAsync("number_python");
var numberVal = BitConverter.ToInt32(numberBytes); // numberVal is 1989
How can we set cache entries to expire from Python side?
Perhaps you have noticed that we did not write the absexp
and sldexp
fields into Redis. Can we just add them back to set the expiration time for our cache entries? Unfortunately, those two fields alone is do not have any effect on the lifetime of any entry. Instead, we need to use the EXPIRE
command as documented here.
With aioredis, that command maps to the expire
method.
redis.expire(<mykey>, <seconds>)
For example, to set the string_python
entry to expire after one hour.
await redis.expire('string_python', 3600)
Conclusion
Although limited to primitive types, we managed to exchange data between a Python app and a C# .NET app via Redis. Interestingly, there is no concept of sliding expiration time in Redis. If so, how does StackExchangeRedis implement that feature? Let’s find out in the bonus part of this series.