Redis 的集和 set 键允许用户将任意多个不同的元素存储到集和中, 既可以是文本数据, 也可以是二进制数据. 其与列表有以下两个明显的区别:

下面介绍结合键的各个命令

Set 集和

示例: 唯一计数器

例如, 一个网站想要统计浏览量和用户量

PLAINTEXT
from redis import Redis

class UniqueCounter:
    def __init__(self, client, key):
        self.client = client
        self.key = key
    def count_in(self, item):
        return self.client.sadd(self.key, item)
    def get_result(self):
        return self.client.scard(self.key)

client = Redis(decode_responses=True)
counter = UniqueCounter(client, "ip counter")
print("Add ip", counter.count_in("8.8.8.8"))
print("Add ip", counter.count_in("9.9.9.9"))
print("Add ip", counter.count_in("10.10.10.10"))

print("Numbers of IP:", counter.get_result())
Click to expand and view more

示例: 点赞

点赞功能可以使用集和来实现, 保证了每个用户对同一个内容只能点1次赞

PLAINTEXT
from redis import Redis

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

    def cast(self, user):
        """执行点赞 True/False"""
        return self.client.sadd(self.key, user)

    def undo(self, user):
        """取消点赞"""
        self.client.srem(self.key, user)

    def is_liked(self, user):
        """是否已点赞"""
        return self.client.sismember(self.key, user)

    def get_all_liked_users(self):
        """所有点赞用户"""
        return self.client.smembers(self.key)

    def count(self):
        """点赞人数"""
        return self.client.scard(self.key)

client = Redis(decode_responses=True)
like_topic = Like(client, "topic::10086::like")

print("Peter like:", like_topic.cast("peter"))
print("Mary like:", like_topic.cast("mary"))
print("Liked Users:", like_topic.get_all_liked_users())
print("How many likes:", like_topic.count())
print("Peter liked:", like_topic.is_liked("peter"))
print("Dan liked:", like_topic.is_liked("dan"))
Click to expand and view more

示例: 投票

问答网站、文章推荐网、论坛这类注重内容质量的网站上通常会提供投票功能, 用户可以通过投票来支持一项内容或者反对一项内容:

例如 Stackoverflow 上面会对回答的答案进行投票, 帮助用户发现高质量的问题和答案.

PYTHON
from redis import Redis

def vote_up_key(vote_target):
    """赞成 vote_target 用户集和 key"""
    return vote_target + "::vote_up"

def vote_down_key(vote_target):
    """反对 vote_target 用户集和 key"""
    return vote_target + "::vote_down"

class Vote:
    def __init__(self, client, vote_target):
        self.client = client
        self.vote_up_set = vote_up_key(vote_target)
        self.vote_down_set = vote_down_key(vote_target)

    def is_voted(self, user):
        """检查用户是否已投过票"""
        return self.client.sismember(self.vote_up_set, user) or self.client.sismember(self.vote_down_set, user)

    def vote_up(self, user):
        """user 投赞成票"""
        if self.is_voted(user):
            return False
        self.client.sadd(self.vote_up_set, user)
        return True

    def vote_down(self, user):
        """user 投反对票"""
        if self.is_voted(user):
            return False
        self.client.sadd(self.vote_down_set, user)
        return True

    def undo(self, user):
        """取消用户投票"""
        self.client.srem(self.vote_up_set, user)
        self.client.srem(self.vote_down_set, user)

    def vote_up_count(self):
        """赞成票的数量"""
        return self.client.scard(self.vote_up_set)

    def get_all_vote_up_users(self):
        """所有投赞成票的用户"""
        return self.client.smembers(self.vote_up_set)

    def vote_down_count(self):
        """反对票的数量"""
        return self.client.scard(self.vote_down_set)

    def get_all_vote_down_users(self):
        """所有投反对票的用户"""
        return self.client.smembers(self.vote_down_set)

client = Redis(decode_responses=True) # 是否将字节数据自动解码额日字符串
question_vote = Vote(client, "question::10")
print("Peter 投支持票:", question_vote.vote_up("peter"))
print("Jack 投支持票:", question_vote.vote_up("jack"))
print("Tom 投支持票:", question_vote.vote_up("tom"))
print("Mary 投反对票:", question_vote.vote_down("mary"))

print("支持票数量:", question_vote.vote_up_count())
print("反对票数量:", question_vote.vote_down_count())

print("支持票用户:", question_vote.get_all_vote_up_users())
print("反对票用户:", question_vote.get_all_vote_down_users())

# 取消用户投票(为了多次运行代码)
question_vote.undo("peter")
question_vote.undo("jack")
question_vote.undo("tom")
question_vote.undo("mary")
Click to expand and view more

示例: 社交关系

Twitter 这类社交软件都可以通过关注或者加好友的方式, 构成一种社交关系. 这些网站上的用户都可以关注其他用户, 也可以被其他用户关注. 通过正在关注名单(following list), 用户可以查看自己正在关注的用户及其人数; 通过关注者名单(follower list), 用户可以查看有哪些人正在关注自己.

下面使用集和来维护这种关系:

PYTHON
def following_key(user):
    return user + "::following"

def follower_key(user):
    return user + "::follower"

class Relationship:
    def __init__(self, client, user):
        self.client = client
        self.user = user

    def follow(self, target):
        """关注目标用户"""
        user_following_set = following_key(self.user)
        self.client.sadd(user_following_set, target)

        target_follower_set = follower_key(target)
        self.client.sadd(target_follower_set, self.user)

    def unfollow(self, target):
        """取消关注目标用户"""
        user_following_set = following_key(self.user)
        self.client.srem(user_following_set, target)

        target_follower_set = follower_key(target)
        self.client.srem(target_follower_set, self.user)

    def is_following(self, target):
        """是否关注了目标用户"""
        user_following_set = following_key(self.user)
        return self.client.sismember(user_following_set, target)

    def get_all_following(self):
        """所有user关注的用户"""
        user_following_set = following_key(self.user)
        return self.client.smembers(user_following_set)

    def get_all_follower(self):
        """所有关注user的用户"""
        user_follower_set = follower_key(self.user)
        return self.client.smembers(user_follower_set)

    def count_following(self):
        """user关注的用户数量"""
        user_following_set = following_key(self.user)
        return self.client.scard(user_following_set)

    def count_follower(self):
        """关注user的用户数量"""
        user_follower_set = follower_key(self.user)
        return self.client.scard(user_follower_set)
Click to expand and view more

示例: 抽奖

为了推销产品并回馈消费者, 商家经常举办一些抽奖活动, 消费者可以抽奖获取礼品. 下面代码展示了使用集和实现的抽象程序, 这个成会把所有参与抽奖的玩家都添加到一个集和中, 然后通过 SRANDMEMBER 命令随机地选出获奖者.

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

    def add_player(self, user):
        """添加用户到抽奖名单中"""
        self.client.sadd(self.key, user)

    def get_all_players(self):
        """返回参加抽奖活动的所有用户"""
        return self.client.smembers(self.key)

    def player_count(self):
        """返回抽奖用户数量"""
        return self.client.scard(self.key)

    def draw(self, number):
        """抽取指定数量的获奖者"""
        return self.client.srandmember(self.key, number)
Click to expand and view more

考虑到完整的抽奖者名单可能会有用, 所以这个抽奖程序使用了随机获取元素的 SRANDMEMBER 命令, 而不是随机移除元素的 SPOP 命令. 如果不需要保留完整的名单, 也可以使用 SPOP 命令实现抽奖程序.

因为对集合执行交集、并集、差集等集合计算需要耗费大量的资源, 所以用户应该尽量使用SINTERSTORE等命令来存储并重用计算结果, 而不要每次都重复进行计算. 此外, 当集合计算涉及的元素数量非常大时, Redis服务器在进行计算时可能会被阻塞. 这时, 可以考虑使用Redis的复制功能, 通过从服务器来执行集合计算任务, 从而确保主服务器可以继续处理其他客户端发送的命令请求.

示例: 使用反向索引构建商品筛选器

在访问购物类网站的时候, 通常可以通过一些标签来筛选产品. 这时候, 对每个产品可以建立一个集和, 对每个标签也都建立一个集和, 这样就得到了一份物品到关键字, 以及关键字到物品的映射关系.

PYTHON
def make_item_key(item):
    return "InvertedIndex::" + item + "::keyword"

def make_keyword_key(keyword):
    return "InvertedIndex::" + keyword + "::item"

class InvertedIndex:
    def __init__(self, client):
        self.client = client

    def add_index(self, item, *keywords):
        """为物品添加关键字"""
        # 将给定物品添加到
        item_key = make_item_key(item)
        result = self.client.sadd(item_key, *keywords)
        # 遍历关键字集和, 将该物品添加进去
        for keyword in keywords:
            keyword_key = make_keyword_key(keyword)
            self.client.sadd(keyword_key, item)
        # 返回添加关键字数量作为结果
        return result

    def remove_index(self, item, *keywords):
        """移除物品的关键字"""
        item_key = make_item_key(item)
        result = self.client.srem(item_key, *keywords)
        for keyword in keywords:
            keyword_key = make_keyword_key(keyword)
            self.client.srem(keyword_key, item)
        return result

    def get_keywords(self, item):
        """获取物品所有的关键字"""
        return self.client.smembers(make_item_key(item))

    def get_items(self, *keywords):
        """根据给定的关键字获取物品"""
        # 根据给定的关键字计算出与之对应的集合 key
        keyword_key_list = map(make_keyword_key, keywords)
        # 将这些集和 key 做并集
        return self.client.sinter(*keyword_key_list)
Click to expand and view more

Start searching

Enter keywords to search articles

↑↓
ESC
⌘K Shortcut