在 Redis 中获取正确类型的值

Posted

技术标签:

【中文标题】在 Redis 中获取正确类型的值【英文标题】:Getting values with the right type in Redis 【发布时间】:2012-10-15 04:00:31 【问题描述】:

我在我的 python 应用程序中使用 redis 来存储简单的值,例如计数器和时间戳列表,但是我试图获取一个计数器并将它与一个数字进行比较,我遇到了一个问题。

如果我这样做:

import redis
...
myserver = redis.Redis("localhost")
myserver.set('counter', 5)

然后尝试像这样获取该值:

if myserver.get('counter') < 10:
     myserver.incr('counter')

然后我在 if 语句中得到一个类型错误,因为我在比较 '5' 我正在存储一个整数值并获得一个字符串 1(可以考虑作为不同的值)。

我的问题是:这应该像那样工作吗?我的意思是它是一个非常基本的类型,我知道我是否必须解析对象但一个 int?好像我做错了什么。

我缺少什么配置吗?

有没有办法让 redis 返回正确的类型而不是字符串? 我这样说是因为它对于列表和日期时间甚至浮点值都是一样的。

这可能是我正在使用的 redis-py 客户端而不是 redis 本身的问题吗?

【问题讨论】:

我一直在搞乱他们的发行版附带的 src/redis-cli。不幸的是,它看起来像 redis 将整数存储为字符串,并在执行 incr/decr 操作时推断出类型。 你可以使用 if int(myserver.get('counter')) 【参考方案1】:

一种解决方案是使用json.loads()intfloat 作为正确类型返回,而不是str

import json
import redis

redis = redis.from_url("redis://************")

def get(key, decode=True):
    """set decode to False when value stored as a string"""
    value = redis.get(key)
    if not decode:
        return value
    if value is not None:
        try:
            return json.loads(value)
        except json.decoder.JSONDecodeError:
            # not containing a JSON document
            return value

redis.set("foo", 2)
redis.set("bar", 2.75)

get("foo")
# 2
get("bar")
# 2.75

【讨论】:

【参考方案2】:

根据需要,我们有 3 种方式将数据存储到 Redis 中

字符串

所有数据都以字节形式存储和读回。 这是 Redis 使用的原生方式方法。 我们可以使用redis-cli 来检查和操作数据 但是,数据可能无法有效存储。例如,12345 存储为至少使用 5 个字节的字符串,即使它可以用 2 字节的无符号短整数表示。 您可以限制使用 Redis 提供的各种数据结构。 存储在 Redis 中的数据可以跨不同版本的 Python 和编程语言移植。

泡菜

所有 Python 数据都使用pickle 库转换为二进制数据,然后再存储到 Redis 中。 必须使用pickle 库将数据转换回 Python 数据类型,然后才能使用。 它可以对 Redis 不提供的复杂和动态数据结构进行建模。 一旦将数据转换回 Python 数据类型,我们就可以使用强大的 Python 来操作数据。 它在 Redis 中使用更多内存,因为 pickle 在其输出中包含元数据和引用。 不能使用redis-cli来操作数据,比如INCR。 存储在 Redis 中的数据不能跨不同版本的 Python 和编程语言移植。

结构

所有 Python 数据都使用 struct 库转换为二进制数据,然后再存储到 Redis 中。 必须使用struct 库将数据转换回 Python 数据类型,然后才能使用。 它可以对 Redis 不提供的复杂和静态数据结构进行建模。 一旦将数据转换回 Python 数据类型,我们就可以使用强大的 Python 来操作数据。 在这 3 种方法中,它使用的内存最少 不能使用redis-cli来操作数据,比如INCR。 存储在 Redis 中的数据可以跨不同版本的 Python 和编程语言移植。
from redis import Redis
import sys
import pickle
import struct

redis = Redis()

# Data
d = [1234, 3.1415, b'Good', True]
k = ['int', 'float', 'byte', 'bool']

## Using String
print(f'String\n"_"*50')
# Need to convert True to string or int or float
d2 = [1234, 3.1415, b'Good', int(True)]
redis.hset('k0', mapping=dict(zip(k,d2)))
print(redis.hmget('k0', *k))
print()


## Using Pickle
print(f'Pickle\n"_"*50')
packed = pickle.dumps(d)
print(f'Pickled data occupies sys.getsizeof(packed) bytes')
print(packed)
redis.set('k1', packed)
print(f'pickle.loads(redis.get("k1"))')
print()


## Using Struct
print(f'Struct\n"_"*50')
s = struct.Struct('H f 10s ?')
print(f'The structure occupies s.size bytes.')
packed = s.pack(*d)
print(f'packed')
redis.set('k2', s.pack(*d))
print(f's.unpack(redis.get("k2"))')

预期输出:

String
__________________________________________________
[b'1234', b'3.1415', b'Good', b'1']

Pickle
__________________________________________________
Pickled data occupies 69 bytes
b'\x80\x04\x95\x19\x00\x00\x00\x00\x00\x00\x00]\x94(M\xd2\x04G@\t!\xca\xc0\x83\x12oC\x04Good\x94\x88e.'
[1234, 3.1415, b'Good', True]

Struct
__________________________________________________
The structure occupies 19 bytes.
b'\xd2\x04\x00\x00V\x0eI@Good\x00\x00\x00\x00\x00\x00\x01'
(1234, 3.1414999961853027, b'Good\x00\x00\x00\x00\x00\x00', True)

【讨论】:

【参考方案3】:

虽然利用set_response_callback 对简单的数据类型来说很好,但如果您想知道存储字典、列表、元组等内容的最快和最简单的方法 - 并保留它们可能或可能不是的 Python 原生数据类型包含——我推荐使用python内置的pickle库:

# Imports and simplified client setup
>>> import pickle
>>> import redis
>>> client = redis.Redis()
# Store a dictionary
>>> to_store = 'a': 1, 'b': 'A string!', 'c': [1, True, False, 14.4]
>>> client.set('TestKey', pickle.dumps(to_store))
True
# Retrieve the dictionary you just stored.
>>> retrieved = pickle.loads(client.get('TestKey'))
'a': 1, 'b': 'A string!', 'c': [1, True, False, 14.4]

这是一个简单的客户端,它将减少上述示例中的 pickle 样板,并为您提供一个干净的接口,用于在 Redis 中存储和检索本机 python 数据类型:

"""Redis cache."""
import pickle
import redis

redis_host = redis.Redis()


class PythonNativeRedisClient(object):
    """A simple redis client for storing and retrieving native python datatypes."""

    def __init__(self, redis_host=redis_host):
        """Initialize client."""
        self.client = redis_host

    def set(self, key, value, **kwargs):
        """Store a value in Redis."""
        return self.client.set(key, pickle.dumps(value), **kwargs)

    def get(self, key):
        """Retrieve a value from Redis."""
        val = self.client.get(key)
        if val:
            return pickle.loads(val)
        return None

redis_client = PythonNativeRedisClient()

用法:

>>> from some_module import redis_client
>>> to_store = 'a': 1, 'b': 'A string!', 'c': [1, True, False, 14.4]
>>> redis_client.set('TestKey', to_store)
True
>>> retrieve = redis_client.get('TestKey')
'a': 1, 'b': 'A string!', 'c': [1, True, False, 14.4]

【讨论】:

【参考方案4】:

你可以设置 decode_respone 像 True

redis.StrictRedis(host="localhost", port=6379, db=0, decode_responses=True)

【讨论】:

好像不行 >>> r= redis.StrictRedis(decode_responses=True) >>> r.set('world', 123) True >>> r.get('world') '123'【参考方案5】:

正如@favoretti 所说,响应回调可以解决问题。一点也不复杂,只要一条线就可以搞定。

In [2]: import redis
In [3]: r = redis.Redis()
In [10]: r.set_response_callback('HGET', float)
In [11]: r.hget('myhash', 'field0')
Out[11]: 4.6

对于hmget,它返回的是一个字符串列表,而不是单个字符串,所以你需要构造一个更全面的回调函数:

In [12]: r.set_response_callback('HMGET', lambda l: [float(i) for i in l])

In [13]: r.hmget('myhash', 'field0')
Out[13]: [4.6]

hgetall 也一样。

【讨论】:

尝试为“HMGET”和“HGETALL”设置浮动回调,但不幸的是失败了。我猜它会尝试转换哈希键名。【参考方案6】:

这是我的测试。两个redis连接:一个返回int类型,另一个返回float

import redis

age_field = redis.Redis()
age_field.set_response_callback('HGET', int)
age_field.hget('foo', 'age')
# OUT: 40
a =age_field.hget('foo', 'age')
type(a)
# OUT: <type 'int'>

gpa_field = redis.Redis()
gpa_field.set_response_callback('HGET', float)
gpa_field.hget('foo', 'gpa')
# OUT: 2.5
b = gpa_field.hget('foo', 'gpa')
type(b)
# OUT: <type 'float'>

【讨论】:

【参考方案7】:

看起来redis就是这样存储数据的:

redis 127.0.0.1:6379> set counter 5
OK
redis 127.0.0.1:6379> type counter
string
redis 127.0.0.1:6379> incr counter
(integer) 6
redis 127.0.0.1:6379> type counter
string

如果您真的想要,您可能会为redis-py 客户端打补丁以推断数据类型。

【讨论】:

显然我只是认为 redis-py 会有一个机制来解决这个问题,谢谢【参考方案8】:

从技术上讲,您需要自己处理。

但是,请查看this link,尤其是在他们的 README 中提到解析器和响应回调的部分,也许这是您可以使用的东西。问题是这对你来说是不是矫枉过正。

【讨论】:

谢谢我检查了解析器,但它们似乎使我想做的事情过于复杂,最后我决定使用 cpickle 来处理数据的序列化

以上是关于在 Redis 中获取正确类型的值的主要内容,如果未能解决你的问题,请参考以下文章

如何获取redis中String类型的全部key值

promisify redis 获取并返回正确的打字稿类型

如何使用Java来获取redis中某个key的所有数据

从 RegQueryValueEx 获取正确的值

怎么查看redis key的值

redis数据类型和操作指令