介绍Redis中的字符串键

字符串

字符串建是 Redis 最基本的键值对类型, 这种类型的键值对会在数据库中把单独的一个值关联起来, 被关联的键和值可以为文本, 也可以是图片, 视屏, 音频等二进制数据.

SET key value

```Redis
SET number "10086"
> OK

SET book "Redis in action"
> OK
```

对于已经存在的 key, 再次赋值会覆盖原值, 若不想覆盖后面添加参数 NX, 相反, 默认 XX 允许覆盖
```Redis
SET key "10086" NX
> (nil)

SET key "10086" XX
> OK
```

GET key

```Redis
GET number
> "10086"
```

对于不存在的值, 返回空
```Redis
GET key_new
> (nil)
```

GETSET key new_value

```Redis
GETSET key "123456"
> "10086"
```

示例: 缓存

对数据进行缓存是Redis最常见的用法之一, 将数据存储在内存比存储在硬盘要快得多 首先定义缓存

PYTHON
class Cache:
    def __init__(self, client):
        self.client = client

    def set(self, key, value):
        self.client.set(key, value)

    def get(self, key):
        return self.client.get(key)

    def update(self, new_value, key):
        return self.client.getset(key, new_value) # 设置新值, 返回旧值
Click to expand and view more

然后缓存文本数据

PYTHON
client = Redis(decode_responses=True) # 使用文本编码方式打开客户端
cache = Cache(client)

cache.set("web_page", "<html><p>hello world</p></html>")
print(cache.get("web_page"))

print(cache.update("web_page", "<html><p>update<p></html>"))
print(cache.get("web_page"))
Click to expand and view more

下面是存储一个二进制图片的缓存示例

PYTHON
client = Redis() # 二进制编码打开客户端
cache = Cache(client)

image = open("DailyBing.jpg", "rb") # 二进制只读方式打开图片
data = image.read() # 读取文件内容
image.close() # 关闭文件

cache.set("daily_bing.jpg", data) # 将二进制图片缓存到键 daily_bing.jpg 中
print(cache.get("daily_bing.jpg")[:20]) # 读取二进制数据的前20字节
Click to expand and view more

b’\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00\x00\x00\x00\x00\x00'

示例: 锁

锁是一种同步机制, 用于保证一种资源任何时候只能被一个进程使用. 一个锁的实现通常有获取 (acquire) 和释放 (relase) 这两种操作.

PYTHON
from redis import Redis

VALUE_OF_LOCK = "locking"
class Lock:
    def __init__(self, client, key):
        self.client = client
        self.key = key
    def acquire(self):
        result = self.client.set(self.key, VALUE_OF_LOCK, nx=True)
        return result is True
    def relase(self):
        return self.client.delete(self.key) == 1

client = Redis(decode_responses=True)
lock = Lock(client, 'test-lock')
print("第一次获取锁:", lock.acquire())

print("第二次获得锁:", lock.acquire())
print("取消锁:", lock.relase())

print("第三次获得锁:", lock.acquire())
Click to expand and view more

第一次获取锁: True 第二次获得锁: False 取消锁: True 第三次获得锁: True

若要设置锁的时间 SET key value NX EX time 这样是原子性语法, 删除操作对应命令是 DEL key, 返回0表示 key 不存在, 返回1~N表示删除key的数量.

NX 确保锁只有在没有值时加锁成功, 若有值则返回 None, 通过检查 result 是否为 True 来判断是否获得了锁.

MSET key value [key value …]

同 SET 命令, MSET 执行成功后返回 OK, 并且会用新值覆盖旧值. 由于执行多条 SET 命令要客户端和服务端之间多次进行网络通讯, 因此 MSET 能减少程序执行操作的时间

MGET key [key …]

MSETNX key value [key value …]

若有任意一次键存在值, 则会取消所有操作, 并返回0. 只有所有键都没有值的时候, 执行才成功, 返回1.

示例: 存储文章信息

在构建应用程序的时候, 经常会需要批量设计和获取多项信息, 以博客为例:

通过 MSET、MSETNX、MGET 命令, 可以实现上面提到的这些批量设置和批量获取操作

PYTHON
from redis import Redis
from datetime import datetime

class Article:
    def __init__(self, client, article_id):
        """根据id创建文章id"""
        self.client = client
        self.id = str(article_id)
        self.title_key = "article::" + self.id + "::title"
        self.content_key = "article::" + self.id + "::content"
        self.author_key = "article::" + self.id + "author"
        self.create_at_key = "article::" + self.id + datetime.now()

    def create(self, title, content, author):
        """创建文章"""
        article_data = {
            self.title_key: title,
            self.content_key: content,
            self.author_key: author,
            self.create_at_key: datetime.now(),
        }
        return self.client.msetnx(article_data)

    def get(self):
        """获取文章信息"""
        result = self.client.mget(
            self.title_key,
            self.content_key,
            self.author_key,
            self.create_at_key,
        )
        return {
            "id": self.id,
            "title": result[0],
            "content": result[1],
            "author": result[2],
            "create_at_key": result[3],
        }

    def update(self, title=None, content=None, author=None):
        """更新文章"""
        article_data = {}
        if title is not None:
            article_data[self.title_key] = title
        if content is not None:
            article_data[self.content_key] = content
        if author is not None:
            article_data[self.author_key] = author
        return self.client.mset(article_data)

client = Redis(decode_responses=True)
article = Article(client, 10086)

# 创建文章
print(article.create("message", "hello world", "sx"))

# 获取文章信息
print(article.get())

# 更新文章作者
print(article.update(author="join"))
Click to expand and view more

上面程序使用了多个字符串键存储文章信息: article::<id>::<attribute>

STRLEN key

对于存在的键, 返回字节长度信息. 对于不存在的键, 返回0

GETRANGE key start end

REDIS
SET message "hello world"
GETRANG message 0 4
> hello

GETRANGE message -5 -1
> world
Click to expand and view more

SETRANGE key index subsitute

REDIS
set message "hello world"
SETRANGE message 6 Redis
> (integer) 11

GET message
> hello Redis
Click to expand and view more

当用户给定的新内容比被替换内容长的时候, SETRANGE 会自动扩展被修改的字符串值

REDIS
SETRANGE message 5 ", this is a message"
> (integer) 24

GET message
> "hello, this is a message"
Click to expand and view more

当用户给出的索引长度超出被替换字符长度时, 字符串末尾到 index-1 之间部分将使用空字符串填充为0

REDIS
SET greeting "hello"
SETRANGE greeting 10 "hello"
> (integer) 15

GET greeting
> "hello\x00\x00\x00\x00\x00world"
Click to expand and view more

示例: 给文章存储程序加上文章长度计数功能和文章御览功能给

PYTHON
class Article:
    ...

    def get_content_len(self):
        return self.client.strlen(self.content_key)

    def get_content_perview(self, preview_len):
        start_index = 0
        end_index = preview_len - 1
        return self.client.getrange(self.content, start_index, end_index)
Click to expand and view more

APPEND key suffix

若用户给定的 key 不存在, 则相当于 SET key suffix

示例: 存储日志

很多程序运行的时候会产生日志, 日志记录了程序的运行状态以及执行过的重要操作. 若每条日志存储一个键值对, 则会消耗很多资源, 且分散在数据库中, 需要额外的时间查找日志, 这里将不同日志拼接在同一个值里面.

PYTHON
from redis import Redis

LOG_SEPERATOR = "\n"

class Log:
    def __init__(self, client, key):
        self.client = client
        self.key = key

    def add(self, new_log):
        new_log += LOG_SEPERATOR
        self.client.append(self.key, new_log)

    def get_all(self):
        all_logs = self.client.get(self.key)
        if all_logs is not None:
            log_list = all_logs.split(LOG_SEPERATOR)
            log_list.remove("") # 删除默认多余的空字符串
            return log_list
        else:
            return []
Click to expand and view more

数字值

下面介绍使用字符串键存储数字值:

每当存储一个值到字符串键里面的时候, 有下面两种情况

  1. C 语言 long long int 类型的整数, 取值范围为 -2^63 ~ 2^63-1 (超出范围会被当成字符串)
  2. C 语言 long double 类型的浮点数

为了方便地处理字符串键的值, Redis 提供了一系列加法和减法操作命令, 下面介绍这些命令

INCRBY key increment DECRBY key increment

如果类型为浮点数, 使用上面方法会报错 (key的值 和 increment 都必须为整数)

当该命令遇到**不存在的键**时, 会将键的值初始化为0, 然后再执行操作

INCR key DECR key

INCRBYFLOAT key increment

INCRBYFLOAT 命令即执行加法操作, 也可以执行加法操作, 并且操作对象和 increment 都既可以为整数也可以为浮点数

虽然 Redis 没有限制字符串键存储浮点数的小数位数, 但是 INCRBYFLOAT 最多只会保留小数点后的17位数字, 超出部分将被截断

示例: ID 生成器

identifier 标识符, 经常在程序中使用, 通常以数字形式出现, 并通过递增的方法创建新的ID.

PYTHON
from redis import Redis

class IdGenerator:
    def __init__(self, client, key):
        self.client = client
        self.key = key
    def produce(self):
        """生成下一个id"""
        return self.client.incr(self.key)
    def reserve(self, n):
        """初始化"""
        result = self.client.set(self.key, n, nx=1) # key 不存在才行
        return result is True

client = Redis(decode_responses=True)
id_generator = IdGenerator(client, "user::id")
print(id_generator.reserve(1000000)) # 保留100万个ID -> True
print(id_generator.produce()) # 生成ID, 均大于100万

print(id_generator.reserve(1000)) # 已存在 -> False
Click to expand and view more

示例: 计数器

除了ID生成器, 计数器也是常用的组件之一, 例如点赞回复数量, 播放量等.

PYTHON
from redis import Redis

class Counter:
    def __init__(self, client, key):
        self.client = client
        self.key = key
    def increase(self, n=1):
        return self.client.incr(self.key, n)
    def decrease(self, n=1):
        return self.client.decr(self.key, n)
    def get(self):
        value = self.client.get(self.key)
        if value in None:
            return 0
        else:
            return int(value)
    def reset(self):
        old_value = self.client.getset(self.key)
        if old_value is None:
            return 0
        else:
            return(old_value)

client = Redis(decode_responses=True)
counter = Counter(client, "counter::page_viewed")

print(counter.increase()) # +1
print(counter.increase())
print(counter.increase(10)) # +10

print(counter.decrease()) # -1
print(counter.decrease(5)) # -5

print(counter.reset()) # 重置计数器
print(counter.get()) # 返回计数器当前值
Click to expand and view more

注: 在 redis-py 中 INCR 和 INCRBY 都使用 .incr() 方法

示例: 限速器

为了保障系统的安全性和性能, 并保证重要资源不被滥用, 应用程序需要对用户的行为进行限制

上面机制的实现可以使用限速器, 下面是一个限速器示例代码, 该程序将操作最大可执行次数存储在一个字符串里面, 每次用户进行该操作后就将其减1

PYTHON
from redis import Redis

class Limter:
    def __init__(self, client, key):
        self.client = client
        self.key = key
    def set_max_execute_times(self, max_execut_time):
        self.client.set(self.key, max_execut_time)
    def still_valid_to_execute(self):
        num = self.client.decr(self.key)
        return (num >= 0)
    def remaining_execute_times(self):
        num = int(self.client.get(self.key))
        if num < 0:
            return 0
        else:
            return num

client = Redis(decode_responses=True)
limter = Limter(client, "wrong_password_limter")

print(limter.set_max_execute_times(5)) # 最多5次输入错误密码
print(limter.still_valid_to_execute()) # 前5次 True, 之后 False
Click to expand and view more

Start searching

Enter keywords to search articles

↑↓
ESC
⌘K Shortcut