分布式NoSQL(12)——使用redis构建简单社交网站和任务队列

目录

一、先进先出任务队列

rpush命令

blpop命令

编程要求

二、优先级任务队列

rpush命令

blpop命令

zrevrange

zadd

编程要求

三、定时任务队列

redis基本命令

python基本命令

编程要求

四、创建用户与动态

redis基本命令

python基本命令

编程要求

五、处理用户关系

redis基本命令

python基本命令

编程要求

六、状态与信息流

redis基本命令

编程要求

构建任务队列_文章投票网站----完整代码展示

构建简单的社交网站----完整代码展示 


一、先进先出任务队列

rpush命令

rpush:将一个值插入到列表尾部,保证后插入的在最尾部。

conn = redis.Redis()
conn.rpush("testlist", "end")执行前:a
b执行后:a
b
end

blpop命令

blpop:从列表头部阻塞式的弹出一个值,若列表中没有元素可共弹出时,则阻塞该命令直到超过timeout或发现可弹出元素为止。

conn = redis.Redis()
conn.blpop("alist", 5)执行前:a
b执行后:b执行结果:'alist' # 列表键名
'a' # 被弹出的值

如果是空列表,那么 blpop 命令将阻塞连接,直到等待超时,或有另一个客户端对列表执行 lpush 或 rpush 命令为止:

conn = redis.Redis()
conn.blpop("blist", 5)

等待2秒后,另一个客户端执行了:

conn.lpush("blist", "waiting 2 sec and got it")则执行结果为:'blist'
'waiting 2 sec and got it'

编程要求

Begin-End区域编写 add_task(task_name) 函数,实现将任务加入队列的功能,具体参数与要求如下:

  • 方法参数 task_name 是要加入的任务名称;
  • 推入任务的实现:将新的任务从列表 task:list 的尾部插入。

编写 pop_task() 函数,实现获取一个任务的功能,具体参数与要求如下:

  • 获取任务的实现:从列表 task:list 的头部阻塞式的弹出一个值,最长等待10秒,若超时仍未获取到,则重试该操作,直至弹出一个值;
  • 任务返回的实现:获取任务成功后,返回该值中弹出的元素值。
#!/usr/bin/env python
#-*- coding:utf-8 -*-

import redis

conn = redis.Redis()

# 将任务加入队列
def add_task(task_name):
    # 请在下面完成要求的功能
    #********* Begin *********#
    conn.rpush("task:list",task_name)
    #********* End *********#

# 获取一个任务
def pop_task():
    # 请在下面完成要求的功能
    #********* Begin *********#
    while True:
        task = conn.blpop("task:list",10)
        if not task:
            continue
        return task[1]

    #********* End *********#

二、优先级任务队列

程序有时会需要让特定的操作优先于其他操作执行,使用多个队列可以最简便的实现优先级特性。

一般来说,将任务分为高、中、低三种优先级别,高优先级任务会在第一时间被执行,在执行完所有高优先级任务后才会执行中等优先级任务,依次类推。

本关任务:使用 Redis 构建一个区分优先级的任务队列。

rpush命令

将一个值插入到列表尾部,保证后插入的在最尾部。

conn = redis.Redis()
conn.rpush("testlist", "end")执行前:a
b执行后:a
b
end

blpop命令

从列表头部阻塞式的弹出一个值,给定多个列表时,按列表的先后顺序依次检查,弹出第一个非空列表的头元素。

conn = redis.Redis()
conn.blpop("blist", "alist", 5)执行前:# alist
a
b
# blist
c
d执行后:# alist
a
b
# blist
d执行结果:'blist' # 列表键名
'c' # 被弹出的值

如果是空列表,那么 blpop 命令将阻塞连接,直到等待超时,或有另一个客户端对列表执行 lpush 或 rpush 命令为止:

conn = redis.Redis()
conn.blpop("clist", 5)

等待2秒后,另一个客户端执行了:

conn.lpush("clist", "waiting 2 sec and got it")则执行结果为:'clist'
'waiting 2 sec and got it'

zrevrange

返回有序集合中指定区间内的成员。其中成员的位置按分值递减(从大到小)排列。

conn = redis.Redis()
conn.zadd("testzset", "member1", 3)
conn.zadd("testzset", "member2", 1)
conn.zadd("testzset", "member3", 2)
conn.zrevrange("testzset", 0, -1)执行结果:member1
member3
member2

zadd

将成员加入到有序集合中,并确保其在正确的位置上。

conn = redis.Redis()
conn.zadd("testzset", "member2", 3)
conn.zadd("testzset", "member1", 2)
conn.zadd("testzset", "member3", 1)执行后:member3
member1
member2

编程要求

Begin-End区域编写 add_task_list(list_name, priority) 函数,实现设置任务队列优先级的功能,具体参数与要求如下:

  • 方法参数 list_name 是要加入的任务队列名字,priority 是要设置的优先权重,值越大则级别越高;
  • 设置队列优先级的实现:为了能按照优先权重排列任务队列,将任务队列加入到有序集合 task:priority 中,分值为 priority

编写 add_task(list_name, task_name) 函数,实现将任务加入队列的功能,具体参数与要求如下:

  • 方法参数 list_name 是要加入的任务队列名字,task_name 是要加入的任务名称;
  • 推入任务的实现:将新的任务从指定的列表尾部插入。

编写 pop_task()函数,实现获取一个任务的功能,具体参数与要求如下:

  • 排序任务队列的实现:将有序集合 task:priority 中的所有成员按照分值递减的顺序排列;
  • 获取任务的实现:按照上述队列的顺序,从第一个非空列表的头部阻塞式的弹出一个值,最长等待10秒,若超时仍未获取到,则重试该操作,直至弹出一个值;
  • 任务返回的实现:获取任务成功后,返回该值中弹出的元素值。
#!/usr/bin/env python
#-*- coding:utf-8 -*-

import redis

conn = redis.Redis()

# 新建任务队列/改变任务队列优先级
def add_task_list(list_name, priority):
    # 请在下面完成要求的功能
    #********* Begin *********#
    conn.zadd("task:priority",list_name,priority)
    #********* End *********#

# 将任务加入队列
def add_task(list_name, task_name):
    # 请在下面完成要求的功能
    #********* Begin *********#
    conn.rpush(list_name,task_name)
    #********* End *********#

# 获取一个任务
def pop_task():
    # 请在下面完成要求的功能
    #********* Begin *********#
    queues = conn.zrevrange("task:priority",0,-1)
    while True:
        task = conn.blpop(queues,10)
        if not task:
            continue
        return task[1]

    #********* End *********#

三、定时任务队列

redis基本命令

zadd:将成员加入到有序集合中,并确保其在正确的位置上。

conn = redis.Redis()
conn.zadd("testzset", "member2", 3)
conn.zadd("testzset", "member1", 2)
conn.zadd("testzset", "member3", 1)执行后:member3
member1
member2

rpush:将一个值插入到列表尾部,保证后插入的在最尾部。

conn = redis.Redis()
conn.rpush("testlist", "end")执行前:a
b执行后:a
b
end

zrange:返回有序集合中指定区间内的成员。

conn = redis.Redis()
conn.zrange("testzset", 0, 1, withscores=True)

Redis中的内容:

member3 1
member1 2
member2 3执行结果:[('member3', 1.0), ('member1', 2.0)]

zrem:从有序集合中移除指定成员。

conn = redis.Redis()
conn.zrem("testzset", "member1")执行前:member3
member1
member2执行后:member3member2执行结果:1

python基本命令

返回当前时间的时间戳:

time.time()

使进程休眠 0.5 秒:

time.sleep(0.5)

编程要求

Begin-End区域编写 execute_later(task_name, delay=0) 函数,实现添加定时任务的功能,具体参数与要求如下:

  • 方法参数 task_name 是要加入的任务名称,delay 是任务的延迟时间;
  • 添加定时任务的实现:若延迟时间大于0,则将任务加入到有序集合 task:delayed 中,分值为任务的执行时间,等于当前时间戳加上延迟时间;
  • 添加普通任务的实现:若延迟时间小于等于0,则将其插入到列表task:list的尾部。

编写 pop_task() 函数,实现转移可执行任务的功能,具体参数与要求如下:

  • 获取队列中第一个任务的实现:不断尝试获取有序集合task:delayed中按分值递增顺序的第一个元素;
  • 判断该任务是否可执行的实现:若未取到任务或者任务的执行时间未到,则休眠 0.01 秒,然后继续尝试获取第一个任务;
  • 任务转移的实现:从有序集合task:delayed中移除该任务,成功后,将该任务插入到列表task:list的尾部。
#!/usr/bin/env python
#-*- coding:utf-8 -*-

import time
import redis

conn = redis.Redis()

# 添加定时任务
def execute_later(task_name, delay=0):
    # 请在下面完成要求的功能
    #********* Begin *********#
    if delay > 0:
        conn.zadd("task:delayed",task_name,time.time()+delay)
    else:
        conn.rpush("task:list",task_name)

    #********* End *********#

# 转移可执行任务
def pop_task():
    # 请在下面完成要求的功能
    #********* Begin *********#
    while True:
        task = conn.zrange("task:delayed",0,0,withscores=True)
        if not task or task[0][1] > time.time():
            time.sleep(0.01)
            continue

        task = task[0][0]
        if conn.zrem("task:delayed",task):
            conn.rpush("task:list",task)    

    #********* End *********#

 

四、创建用户与动态

redis基本命令

hget:从哈希中获取指定域的值。

conn = redis.Redis()
conn.hget("testhash", "field1")执行结果:2

incr:将 key 中储存的数字值增一。

conn = redis.Redis()
conn.incr("testcount")执行前:不存在 testcount 键。执行后:testcount 1

hset:将哈希中域 field 的值设为 value

conn = redis.Redis()
conn.hset("testhash", "field1", 2)
conn.hset("testhash", "field2", 4)执行前:{'field1': '1'}执行后:{'field1': '2', 'field2': '4'}

hmset:同时将多个域-值对设置到哈希表中。

conn = redis.Redis()
conn.hmset("test_hash", {
        'id': 1,
        'name': 'educoder',
        'age': 2,
})执行后:{'age': '2', 'id': '1', 'name': 'educoder'}

hincrby:为哈希中指定域的值增加增量 increment,用于统计。

conn = redis.Redis()
conn.hincrby("testhash", "field1", 1)执行前:{'field1': '1'}执行后:{'field1': '2'}

pipeline:将多条命令按照先后顺序放进一个队列中,一般配合execute一同使用,原子性(atomic)地执行队列里的命令。

conn = redis.Redis()
pipe = conn.pipeline(True) # 事务开始
pipe.incr("counter")
pipe.incr("counter")
pipe.incr("counter")
pipe.execute() # 事务执行执行结果:[1, 2, 3],通过下标即可获取对应命令的执行结果。

python基本命令

将字符串全小写化:

'Hello, Educoder'.lower()执行结果:'hello, educoder'

返回当前时间的时间戳。

time.time()

使用格式化拼接字符串:

"My name is %s, I'm %i years old"%('educoder', 2)执行结果:"My name is educoder, I'm 2 years old"

编程要求

Begin-End区域编写 create_user(login_name, real_name) 函数,实现创建新用户的功能,具体参数与要求如下:

  • 方法参数login_name为用户登录名,real_name为用户真名;
  • 用户登录名预处理的实现:将用户登录名转换成全小写格式;
  • 重名检测的实现:查询哈希键users中是否存在与用户登录名同名的域,若存在,则不允许重新创建该用户,返回None
  • 分配用户编号的实现:对计数器user:id递增1,并将递增后的值作为新用户的编号;
  • 存储用户信息的实现:使用事务一次性提交:
    • 存储登录名的实现:将用户登录名记录到哈希键users当中,值为该用户编号;
    • 存储详情的实现:按照如下示意将用户信息存储到哈希键user:{id}中:

  • 返回创建结果的实现:返回新创建用户的编号。

编写 create_post(uid, content) 函数,实现创建新动态的功能,具体参数与要求如下:

  • 方法参数uid为发布动态的用户编号,content为要发布的动态内容;
  • 用户合法性检测的实现:查找用户编号对应详情信息哈希user:{uid}是否存在login_name域,若存在,则记录,若不存在,则不允许创建新动态,返回None
  • 分配动态编号的实现:对计数器post:id递增1,并将递增后的值作为新动态的编号;
  • 存储动态信息的实现:按照如下示意将动态信息存储到哈希键post:{id}中:

  • 更新用户动态数的实现:为该用户编号对应的详情信息哈希user:{uid}中的posts域的值加1
  • 返回创建结果的实现:返回新创建动态的编号。
#!/usr/bin/env python
#-*- coding:utf-8 -*-

import re
import time
import redis

conn = redis.Redis()

# 创建新用户
def create_user(login_name, real_name):
    # 请在下面完成要求的功能
    #********* Begin *********#
    login_name = login_name.lower()
    if conn.hget("users",login_name):
        return None
    
    uid = conn.incr("user:id")
    pipe = conn.pipeline(True)
    pipe.hset("users", login_name, uid)
    pipe.hmset("user:%i" % (uid), {
        'login_name': login_name,
        'id': uid,
        'real_name': real_name,
        'followers': 0,
        'following': 0,
        'posts': 0,
        'last_signup': time.time(),
    })
    pipe.execute()

    return uid
    #********* End *********#

# 为用户创建新动态
def create_post(uid, content):
    # 请在下面完成要求的功能
    #********* Begin *********#
    pipe = conn.pipeline(True)
    pipe.hget("user:%i"%(uid), "login_name")
    pipe.incr("post:id")
    login_name, pid = pipe.execute()

    if not login_name:
        return None

    pipe.hmset("post:%i"%(pid), {
        'id': pid,
        'uid': uid,
        'content': content,
        'posted': time.time(),
        'user_name': login_name,
    })
    pipe.hincrby("user:%i"%(uid), 'posts')
    pipe.execute()
    
    return pid
    #********* End *********#

五、处理用户关系

redis基本命令

zscore:返回有序集合中指定成员的分值。

conn = redis.Redis()
conn.zscore("testzset", "member1")
conn.zscore("testzset", "not_exists_member")

testzset内容如下:

执行结果:

100.0
None

zadd:将成员加入到有序集合中,并确保其在正确的位置上。

conn = redis.Redis()
conn.zadd("testzset", "member2", 3)
conn.zadd("testzset", "member1", 2)
conn.zadd("testzset", "member3", 1)执行后:member3
member1
member2执行结果:111

hincrby:为哈希中指定域的值增加增量 increment,用于统计。

conn = redis.Redis()
conn.hincrby("testhash", "field1", 1)执行前:'field1': '1'}执行后:{'field1': '2'}

zrem:从有序集合中移除指定成员。

conn = redis.Redis()
conn.zrem("testzset", "member1")执行前:member3
member1
member2执行后:member3
member2执行结果:1

pipeline:将多条命令按照先后顺序放进一个队列中,一般配合execute一同使用,原子性(atomic)地执行队列里的命令。

conn = redis.Redis()
pipe = conn.pipeline(True) # 事务开始
pipe.incr("counter")
pipe.incr("counter")
pipe.incr("counter")
pipe.execute() # 事务执行执行结果:[1, 2, 3],通过下标即可获取对应命令的执行结果。

python基本命令

使用格式化拼接字符串:

"My name is %s, I'm %i years old"%('educoder', 2)执行结果:"My name is educoder, I'm 2 years old"

返回当前时间的时间戳。

time.time()

将字符串转换为整型数据:

int("1")执行结果:1

取一个数的相反数:

a = 1
b = -a
print b执行结果:-1

编程要求

Begin-End区域编写 follow(uid, other_uid) 函数,实现关注用户的功能,具体参数与要求如下:

  • 方法参数uid为当前用户编号,other_uid为被关注的用户编号;
  • 避免重复关注的实现:如果被关注的用户编号已经在当前用户的关注列表following:{uid}中,则不重复关注,直接返回None
  • 建立关注关系的实现:使用事务一次性提交:
    • 将被关注的用户编号加入到当前用户的关注列表following:{uid}中,分值为当前时间戳。
    • 将当前用户编号加入到被关注用户的粉丝列表中followers:{other_uid},分值为当前时间戳。
  • 修改统计数据的实现:若关系建立成功,则使用事务一次性提交:
    • 将当前用户详情user:{uid}中的关注数following1
    • 将被关注用户详情user:{other_uid}中的粉丝数followers1
  • 返回执行结果的实现:返回True

编写 unfollow(uid, other_uid) 函数,实现取消关注的功能,具体参数与要求如下:

  • 方法参数uid为当前用户编号,other_uid为被取消关注的用户编号;
  • 避免重复取消关注的实现:如果被关注的用户编号已经不在当前用户的关注列表following:{uid}中,则不重复取消关注,直接返回None
  • 删除关注关系的实现:使用事务一次性提交:
    • 从当前用户的关注列表following:{uid}中移除被取消关注用户编号。
    • 从被取消关注用户的粉丝列表中followers:{other_uid}移除当前用户编号。
  • 修改统计数据的实现:若关系删除成功,则使用事务一次性提交:
    • 将当前用户详情user:{uid}中的关注数following1
    • 将被取消关注用户详情user:{other_uid}中的粉丝数followers1
  • 返回执行结果的实现:返回True

注意: 关注列表和粉丝列表均为有序集合,存储成员时,分值均为当前时间戳; 用户详情为上一关中创建的哈希结构。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import re
import time
import redis

conn = redis.Redis()

# 关注用户
def follow(uid, other_uid):
    # 请在下面完成要求的功能
    #********* Begin *********#
    fkey1 = "following:%s"%(uid)
    fkey2 = "followers:%s"%(other_uid)

    if conn.zscore(fkey1,other_uid):
        return None

    now = time.time()
    pipe = conn.pipeline(True)
    pipe.zadd(fkey1,other_uid,now)
    pipe.zadd(fkey2,uid,now)
    following,followers = pipe.execute()

    pipe.hincrby("user:%s"%(uid),'following',int(following))
    pipe.hincrby("user:%s"%(other_uid),'followers',int(followers))
    pipe.execute()
    return True




    #********* End *********#

# 取消关注
def unfollow(uid, other_uid):
    # 请在下面完成要求的功能
    #********* Begin *********#
    fkey1 = "following:%s"%(uid)
    fkey2 = "followers:%s"%(other_uid)

    if not conn.zscore(fkey1, other_uid):
        return None

    pipe = conn.pipeline(True)
    pipe.zrem(fkey1, other_uid)
    pipe.zrem(fkey2, uid)
    following, followers = pipe.execute()

    pipe.hincrby("user:%s"%(uid), 'following', -int(following))
    pipe.hincrby("user:%s"%(other_uid), 'followers', -int(followers))
    pipe.execute()

    return True

  #********* End *********#

# 创建新用户
def create_user(login_name, real_name):
    login_name = login_name.lower()
    if conn.hget("users", login_name):
        return None

    uid = conn.incr("user:id")
    pipe = conn.pipeline(True)
    pipe.hset("users", login_name, uid)
    pipe.hmset("user:%i"%(uid), {
        'login_name': login_name,
        'id': uid,
        'real_name': real_name,
        'followers': 0,
        'following': 0,
        'posts': 0,
        'last_signup': time.time(),
    })
    pipe.execute()

    return uid

# 为用户创建新动态
def create_post(uid, content):
    pipe = conn.pipeline(True)
    pipe.hget("user:%i"%(uid), 'login_name')
    pipe.incr("post:id")
    login_name, pid = pipe.execute()

    if not login_name:
        return None

    pipe.hmset("post:%i"%(pid), {
        'id': pid,
        'uid': uid,
        'content': content,
        'posted': time.time(),
        'user_name': login_name,
    })
    pipe.hincrby("user:%i"%(uid), 'posts')
    pipe.execute()

    return pid

 

六、状态与信息流

redis基本命令

zadd:将成员加入到有序集合中,并确保其在正确的位置上。

conn = redis.Redis()
conn.zadd("testzset", "member2", 3)
conn.zadd("testzset", "member1", 2)
conn.zadd("testzset", "member3", 1)执行后:member3
member1
member2执行结果:111

zrange:返回有序集合中指定区间内的成员。

conn = redis.Redis()
conn.zrange("testzset", 0, 1)执行结果:['member3', 'member1']

zrevrange:按分值递减的顺序返回有序集合中指定区间内的成员。

conn = redis.Redis()
conn.zrevrange("testzset", 0, -1)执行结果:['member2', 'member1', 'member3']

hgetall:返回哈希表中所有的域-值对。

conn = redis.Redis()
conn.hgetall("testhash")执行结果:{'field1': '2'}

hget:从哈希中获取指定域的值。

conn = redis.Redis()
conn.hget("testhash", "field1")执行结果:2

pipeline:将多条命令按照先后顺序放进一个队列中,一般配合execute一同使用,原子性(atomic)地执行队列里的命令。

conn = redis.Redis()
pipe = conn.pipeline(True) # 事务开始
pipe.incr("counter")
pipe.incr("counter")
pipe.incr("counter")
pipe.execute() # 事务执行执行结果:[1, 2, 3],通过下标即可获取对应命令的执行结果。

python基本命令

使用格式化拼接字符串:

"My name is %s, I'm %i years old"%('educoder', 2)执行结果:"My name is educoder, I'm 2 years old"

将字符串转换为浮点数:

float("1.23")执行结果:1.23

编程要求

Begin-End区域编写 get_home_timeline(uid) 函数,实现获得主页时间线的功能,具体参数与要求如下:

  • 方法参数uid为要获取主页时间线的用户编号;
  • 获取动态编号的实现:从存储用户主页时间线的有序集合home:{uid}中按照分值递减的顺序取出所有成员;
  • 获取动态详情的实现:遍历动态编号,使用事务一次性获取每个动态编号对应动态详情哈希键post:{pid}的所有域-值对;
  • 返回主页时间线的实现:返回事务执行的结果。

编写 post(uid, content) 函数,实现发布动态并将动态推送给粉丝的功能,具体参数与要求如下:

  • 方法参数uid为要发布动态的用户编号,content为要发布的动态内容;
  • 发布动态的实现:调用第一关中实现的create_post方法,并接收返回的动态编号,若发布失败,则取消发布,返回None
  • 获取发布时间的实现:从新发布的动态编号对应的动态详情哈希键post:{pid}中获取posted域;
  • 更新个人主页的实现:将新发布的动态编号存储到个人主页有序集合键profile:{uid}中,分值为转为浮点数后的发布时间;
  • 更新粉丝主页时间线的实现:遍历用户的粉丝列表followers:{uid},将新发布的动态编号存储到每个粉丝的主页时间线的有序集合home:{follower_id}中,分值为转为浮点数后的发布时间;
  • 返回发布结果的实现:返回新发布的动态编号。
#!/usr/bin/env python
#-*- coding:utf-8 -*-

import re
import time
import redis

conn = redis.Redis()

# 获得主页时间线
def get_home_timeline(uid):
    # 请在下面完成要求的功能
    #********* Begin *********#
    post_ids = conn.zrevrange("home:%s"%(uid),0,-1)
    pipe = conn.pipeline(True)
    for pid in post_ids:
        pipe.hgetall("post:%s"%(pid))
    return pipe.execute()

    #********* End *********#

# 发布动态并将动态推送给粉丝
def post(uid, content):
    # 请在下面完成要求的功能
    #********* Begin *********#
    pid = create_post(uid,content)
    if not pid:
        return None

    posted = conn.hget("post:%s"%(pid),"posted")
    conn.zadd("profile:%s"%(uid),pid,float(posted))
    followers = conn.zrange("followers:%s"%(uid),0,-1)

    pipe = conn.pipeline(False)
    for follower in followers:
        pipe.zadd("home:%s"%(follower),pid,float(posted)) 
    pipe.execute()
    return pid

    #********* End *********#

# 关注用户
def follow(uid, other_uid):
    fkey1 = "following:%s"%(uid)
    fkey2 = "followers:%s"%(other_uid)

    if conn.zscore(fkey1, other_uid):
        return None

    now = time.time()
    pipe = conn.pipeline(True)
    pipe.zadd(fkey1, other_uid, now)
    pipe.zadd(fkey2, uid, now)
    following, followers = pipe.execute()

    posts = conn.zrevrange("profile:%s"%(other_uid), 0, 100, withscores=True)
    if posts:
        pipe.zadd("home:%s"%(uid), **dict(posts))

    pipe.hincrby("user:%s"%(uid), 'following', int(following))
    pipe.hincrby("user:%s"%(other_uid), 'followers', int(followers))
    pipe.execute()

    return True

# 取消关注
def unfollow(uid, other_uid):
    fkey1 = "following:%s"%(uid)
    fkey2 = "followers:%s"%(other_uid)

    if not conn.zscore(fkey1, other_uid):
        return None

    pipe = conn.pipeline(True)
    pipe.zrem(fkey1, other_uid)
    pipe.zrem(fkey2, uid)
    following, followers = pipe.execute()

    posts = conn.zrevrange("profile:%s"%(other_uid), 0, -1)
    if posts:
        pipe.zrem("home:%s"%(uid), *posts)

    pipe.hincrby("user:%s"%(uid), 'following', -int(following))
    pipe.hincrby("user:%s"%(other_uid), 'followers', -int(followers))
    pipe.execute()

    return True

# 创建新用户
def create_user(login_name, real_name):
    login_name = login_name.lower()
    if conn.hget("users", login_name):
        return None

    uid = conn.incr("user:id")
    pipe = conn.pipeline(True)
    pipe.hset("users", login_name, uid)
    pipe.hmset("user:%i"%(uid), {
        'login_name': login_name,
        'id': uid,
        'real_name': real_name,
        'followers': 0,
        'following': 0,
        'posts': 0,
        'last_signup': time.time(),
    })
    pipe.execute()

    return uid

# 为用户创建新动态
def create_post(uid, content):
    pipe = conn.pipeline(True)
    pipe.hget("user:%i"%(uid), 'login_name')
    pipe.incr("post:id")
    login_name, pid = pipe.execute()

    if not login_name:
        return None

    pipe.hmset("post:%i"%(pid), {
        'id': pid,
        'uid': uid,
        'content': content,
        'posted': time.time(),
        'user_name': login_name,
    })
    pipe.hincrby("user:%i"%(uid), 'posts')
    pipe.execute()

    return pid

 

构建任务队列_文章投票网站----完整代码展示

# _*_ coding : utf-8 _*_

import time
ONE_WEEK_IN_SECONDS = 7*24*60*60
def article_vote(r, user_id, article_id):
    # time.time()获取当前时间,减去最大时效与文章发布时间比较
    cutoff = time.time() - ONE_WEEK_IN_SECONDS
    if r.zscore("time",article_id) < cutoff:
        return False
    if r.sadd('voted:' + article_id, user_id):
        # 创建成功会返回1,否则返回0
        r.zincrby("score", 1, article_id)
        # 这个不能搞反python3中是zincrby(key,score,member)
        return True
    return False

# 创建文章数据
# 把文章数据存储到redis中。setps:创建文章id;文章作者加入到改文章已投票名单中;信息to_redis
# 将article发布time和init_vote加入到time和score两个zset中
def post_article(r, user, title, link):
    # 创建新的文章id,整数计数器对article键执行自增
    # 如果键不存在,article的值会被初始化为0,然后执行自增命令
    article_id = str(r.incr("article"))
    voted = "voted:" + article_id
    r.sadd(voted, user)
    # 为键设置过期时间,投票超一周到期,到期后数据已投票用户名单用处不大删除减少redis内存消耗
    # r.expire(key,seconds)
    r.expire(voted, ONE_WEEK_IN_SECONDS)
    now = time.time()
    article = "article:" + article_id
    r.hmset(article,{
        'title':title,
        'link':link,
        'poster':user
    })
    # 初始化投票数和创建时间,分数在前面
    r.zadd("score",{article_id:1})
    r.zadd("time",{article_id:now})
    return article_id


# 获取排序为start到end的文章id,根据id得到文章数据,id作为文章的键
def get_articles(r, start, end, order='score'):
    articles = []
    # zrevrange返回有序集合内指定区间的成员
    # r.zrevrange(key,start,stop):start开始的数组下标,stop结束的数组下标
    ids = r.zrevrange(order, start, end)
    for id in ids:
        # 获取文章的全部数据关卡里有问题,键为id的hash键是没有的,应该是article:id键
        article_data = r.hgetall('article:'+id)
        article_data['id'] = id
        articles.append(article_data)
    return articles

# 清空
def to_del(r):
    to_del = (r.keys('time') + r.keys('voted:*') + r.keys('score') + r.keys('article*'))
    r.delete(*to_del)

def insert_data(r):
    try:
        user,title,link = input("请输入文章作者、标题和链接(空格隔开)").split()
    except:
        print("请重新输入内容:")
        user = input("文章作者:")
        title = input("标题:")
        link = input("链接:")
    article_id = str(post_article(r, user, title, link))
    print("我们用id:{}发布了一篇新文章".format(article_id))
    print('内容为:',end='')
    # r.hdel("article:" + article_id, 'time')
    hash_article = r.hgetall("article:" + article_id)
    print(hash_article)
    article_vote(r, user, article_id)
    print("这篇文章现在的票数为:",end='')
    v_nums = len(r.smembers('voted:'+article_id))
    print(v_nums)
    print("目前文章的票数由高到低的排序是:")
    articles = get_articles(r,0,-1)
    print(articles)


import redis
import warnings
warnings.filterwarnings("ignore")

if __name__ == '__main__':
    pool = redis.ConnectionPool(host="localhost",port=6379,decode_responses=True)
    r = redis.Redis(connection_pool=pool)
    print("创建文章请输入:1")
    print("投票请输入:2")
    print("删除已有数据请输入:3")
    print("查看文章票数排名请输入:4")
    print("操作结束请输入:任意字符")
    while True:
        try:
            operation = eval(input("请输入需要执行的功能:"))
            if operation == 1:
                insert_data(r)
            elif operation == 2:
                try:
                    user_id,article_id = input("请输入用户id、文章id:").split()
                except:
                    print("输入格式错误,请重新操作")
                    continue
                flag = article_vote(r,user_id,article_id)
                if flag:
                    print("投票成功")
#                   v_nums = len(r.smembers('voted:'+article_id))#得到集合的成员数
                    v_nums = int(r.zscore('score',article_id))
                    print("这篇文章现在的票数为:",v_nums)
                else:
                    print("投票失败")
            elif operation == 3:
                try:
                    to_del(r)
                    print("数据已清除")
                except:
                    print("数据清除失败")
            elif operation == 4:
                print("目前文章的票数由高到低的排序是:")
                articles = get_articles(r, 0, -1)
                print(articles)
            else:
                print("操作结束")
                break
        except:
            print("操作结束")
            break

构建简单的社交网站----完整代码展示 

#!/usr/bin/env python
# coding: utf-8

# redis client-server mode使用pipeline的好处,减少io次数,
# 可以执行多个命令后,一次性返回所有结果,大大减少网络上的消耗
# users 存储login_name 值为uid  密码不存数据库里

import redis
import time
import warnings
warnings.filterwarnings("ignore")


# 创建用户
def create_user(conn, login_name, real_name):
    login_name = login_name.lower()  # 转化为小写
    if not conn.hexists("users", login_name):
        pipe = conn.pipeline(True)
        userid = conn.incr("user:id")  # 获得用户id
        conn.hset("users", login_name, userid)  # 加入用户列表
        conn.hmset(f"user:{userid}", {  # 用户信息
            "login_name": login_name,
            "id": userid,
            "real_name": real_name,
            "followers": 0,
            "following": 0,
            "posts": 0,
            "last_signup": time.time()
        })
        pipe.execute()
        print("创建用户成功")
        return userid
    print(f"已存在用户{login_name},创建用户失败")       # 如果存在该怎么做?加循环
    return None


# 查看全部用户
def show_users(r):
    try:#格式化后没有user:id,r.get('user:id')返回空,None
        id_count = eval(r.get('user:id'))
    except:
        id_count = 0
    print(f"一共有{id_count}名用户")
    if id_count: #等于0无用户
        for i in range(1,id_count+1):
            print("-"*15,f"第{i}个用户的用户信息","-"*15)
            show_user(r,i,'id')
            print("-"*50)


# 查看用户信息  # 可以根据用户id或用户名login_name查看用户信息
def show_user(r,var,flag='login_name'):
    if flag == 'id':
        # hexists()判断字段是否存在
        if not r.exists(f'user:{var}'): # 用户信息不存在
            print("没有此用户!")
            return
        data = r.hvals(f"user:{var}")
    if flag == 'login_name':
        if not r.hexists('users',var):
            print("没有此用户!")
            return
        uid = r.hget('users',var)
        data = r.hvals(f'user:{uid}')
    print("用户登录名:" + data[0])
    print("用户id:" + data[1])
    print("用户真名:" + data[2])
    print("用户粉丝数:" + data[3])
    print("用户关注数:" + data[4])
    print("用户动态数:" + data[5])
    # time.strftime() 格式化时间,返回以可读字符串表示的当地时间
    # time.localtime()默认时间为本地的当前时间的,1970-1-1 08:00:00以来经过的秒数
    tm = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(float(data[6])))
    print(f"用户最后登录时间:{tm}")


def user_exists(r, uid):
    id_max = eval(r.get('user:id'))  # 是数字在范围内且是整数存在,否则不存在
    try:
        if isinstance(uid, str):  # isinstance()函数来判断uid是否是str类型
            uid = eval(uid)
        if (uid > 0) and (uid <= id_max) and isinstance(uid, int):
            return True
        else:
            return False
    except:
        return False


# 关注用户
def follow(r, uid, other_uid):
    if r.zscore(f'following:{uid}', other_uid):
        print('已关注该用户,请勿重复关注')
        return
    if not user_exists(r, other_uid):
        print("不存在该用户,取关失败")
        return
    now = time.time()
    pipe = r.pipeline(True)  # 事务开始
    pipe.zadd(f'following:{uid}', {other_uid: now})
    pipe.zadd(f'followers:{other_uid}', {uid: now})
    following, followers = pipe.execute()  # 事务执行
    posts = r.zrevrange(f'profile:{other_uid}', 0, 100,withscores=True)
    # 拉取other_uid的前100条动态(不满100就是所有),[(postid:time),...]
    if posts:
        pipe.zadd(f"home:{uid}", dict(posts))
        # 将关注的用户的动态加入到用户的主页时间线中
    pipe.hincrby(f'user:{uid}', 'following', int(following))
    pipe.hincrby(f'user:{other_uid}', 'followers', int(followers))
    pipe.execute()
    print("关注成功")
    return True


# 取消关注用户
def unfollow(r, uid, other_uid):
    if not user_exists(r, other_uid):
        print("不存在该用户,取关失败")
        return
    if not r.zscore(f'following:{uid}', other_uid):
        print("已取消关注该用户")
        return
    pipe = r.pipeline(True)  # 开始事务
    pipe.zrem(f'following:{uid}', other_uid)
    pipe.zrem(f'followers:{other_uid}', uid)
    following, followers = pipe.execute()
    posts = r.zrevrange(f'profile:{other_uid}', 0, -1)
    # 获取所有与other_uid有关的动态,只获取动态编号
    if posts:
        pipe.zrem(f'home:{uid}', *posts)
        # 删除uid主页时间线中other_uid的动态线 #带星号参数不传参时默认空元组
    pipe.hincrby(f'user:{uid}', 'following', -int(following))
    pipe.hincrby(f'user:{other_uid}', 'followers', -int(followers))
    pipe.execute()
    print("取关成功")
    return True


# 查看关注列表
def show_followings(r, uid):
    if not eval(r.hget(f'user:{uid}', 'following')):  # 判断是否为空
        print("关注列表为空")
        return
    print("-" * 10, "关注列表", '-' * 10)
    for id in r.zrange(f'following:{uid}', 0, -1):  # other_uid
        show_user(r, id, 'id')
    print('-' * 12, 'end', '-' * 13)


# 查看粉丝列表
def show_followers(r, uid):
    if not eval(r.hget(f'user:{uid}', 'followers')):  # 判断粉丝列表是否为空
        print("粉丝列表为空")
        return
    print("-" * 10, "粉丝列表", '-' * 10)
    for id in r.zrange(f'followers:{uid}', 0, -1):  # other_uid
        show_user(r, id, 'id')
    print('-' * 12, 'end', '-' * 13)


# 创建动态
def create_post(r, uid, content):
    if 'login_name' not in r.hkeys(f'user:{uid}'):
        return None  # 如果不存在用户信息
    login_name = r.hget(f'user:{uid}', 'login_name')
    # 自增id,默认为0
    pipe = r.pipeline(True)
    # 这里不用直接postid=pipe.incr("post:id"),
    # postid接受的是Pipeline<ConnectionPool<Connection<host=localhost,port=6379,db=0>>>
    postid = r.incr("post:id")
    pipe.hmset(f"post:{postid}", {
        "id": postid,
        "uid": uid,
        "content": content,
        "posted": time.time(),
        "user_name": login_name
    })
    pipe.hincrby(f"user:{uid}", "posts", 1)
    pipe.execute()
    print("发布动态成功")
    return postid


# 发布动态并将动态推送给粉丝
def post(r, uid, content):
    pid = create_post(r, uid, content)  # 获得动态id,int类型
    if pid is None:
        print("动态发布失败,请重新检查输入信息")
        return None
    tm = r.hget(f'post:{pid}', "posted")  # 获取动态创建时间
    r.zadd(f'profile:{uid}', {pid: float(tm)})
    fids = r.zrange(f'followers:{uid}', 0, -1)  # 粉丝列表
    for id in fids:
        r.zadd(f'home:{id}', {pid: float(time.time())})  # 加入到用户的主页时间线中
    print("动态推送成功")
    return pid


# 删除动态
def del_post(r, uid, pid):
    if r.exists(f'post:{pid}'):
        r.delete(f'post:{pid}')
        r.hincrby(f'user:{uid}', 'posts', -1)
        # 删除动态时间线中的该动态信息
        r.zrem(f'profile:{uid}', pid)
        # 删除粉丝主页中的该动态
        fids = r.zrange(f'followers:{uid}', 0, -1)
        for id in fids:
            r.delete(f'home:{id}')  # 从用户主页线中删除
        print("动态删除成功")
        return True
    else:
        print("动态删除失败")
        return None


# 删除用户 #用户注销了,有关信息删除掉
def del_user(r, uid):
    if r.exists(f"user:{uid}"):
        # 删除动态
        for i in r.zrange(f"profile:{uid}", 0, -1):
            del_post(r, uid, i)
        login_name = r.hget(f'user:{uid}', 'login_name')
        r.delete(f"user:{uid}")  # 删除用户详细信息
        r.hdel('users', login_name)  # 删除用户信息
        print("删除用户成功")
        return True
    print("没有找到此用户,删除失败")
    return None


# 获取主页时间线
def get_home_timeline(r, uid):
    if not r.exists(f'home:{uid}'):
        print(f"用户{uid}的主页线为空")
        return
    desc_pids = r.zrevrange(f'home:{uid}', 0, -1)  # 主页线动态,最新动态在前面
    pipe = r.pipeline(True)  # 开启事务
    for pid in desc_pids:
        pipe.hgetall(f'post:{pid}')
    uid_home = pipe.execute()
    print(f'用户{uid}的主页时间线:{uid_home}')


# 删除所有有关数据---格式化
def init(r):
    pipe = r.pipeline(True)
    pipe.delete("users")
    keys = (
            r.keys("user:*") + r.keys("followers:*") + r.keys("following:*") +
            r.keys("post:*") + r.keys("profile:*") + r.keys("home:*")
    )
    if keys:
        pipe.delete(*keys)
    pipe.execute()


def main():
    pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True)
    r = redis.Redis(connection_pool=pool)
    function = ['创建用户', '查看全部用户信息', '查看用户信息', '查看关注列表', '查看粉丝列表', '关注', '取关', '发布动态', '删除动态', '获取主页时间线', '删除用户', '格式化']
    print(f"暂时拥有的功能:{function}")
    print("输入拥有功能以外的参数信息则退出操作")
    while True:
        try:
            operation = input("请输入需要执行的操作:")
            if operation == "创建用户":
                login_name = input("用户名:")
                real_name = input("真实姓名:")
                create_user(r, login_name, real_name)
            elif operation == "查看全部用户信息":
                show_users(r)
            elif operation == "查看用户信息":
                flag = input("查询方式:id/login_name:")
                var = ""
                if flag == 'id':
                    var = input("id:")
                elif flag == 'login_name':
                    var = input("login_name:")
                show_user(r, var, flag)
            elif operation == "查看关注列表":
                uid = input("用户id:")
                show_followings(r, uid)
            elif operation == "查看粉丝列表":
                uid = input("用户id:")
                show_followers(r, uid)
            elif operation == "关注":
                uid = input("用户id:")
                other_uid = input("要关注的用户的id:")
                follow(r, uid, other_uid)
            elif operation == "取关":
                uid = input("用户id:")
                other_uid = input("要取关的用户的id:")
                unfollow(r, uid, other_uid)
            elif operation == "发布动态":
                uid = input("用户id:")
                content = input("要发布的内容:")
                post(r, uid, content)
            elif operation == "删除动态":
                uid = input("用户id:")
                pid = input("动态编号:")
                del_post(r, uid, pid)
            elif operation == "获取主页时间线":
                uid = input("用户id:")
                get_home_timeline(r, uid)
            elif operation == "删除用户":
                uid = input("删除用户id:")
                del_user(r, uid)
            elif operation == "格式化":
                init(r)
                print("格式化成功")
            else:
                print("操作结束")
                break
        except:
            print("操作结束")
            break


if __name__ == '__main__':
    main()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值