文章目录
- 第1关:Redis中的数据结构
- [第2关:使用 Python 与 Redis 交互](#第2关:使用 Python 与 Redis 交互)
- 第3关:使用Python+Redis实现文章投票网站后端功能编程要求
- 第4关:字符串、列表与集合
- 第5关:哈希与有序集合
- 第6关:Redis基本事务与其他命令
- 第7关:使用Redis管理登录令牌
- 第8关:使用Redis实现购物车
- 第9关:使用Redis做页面缓存
- 第10关:使用Redis做数据缓存
第1关:Redis中的数据结构
编程要求
根据提示,打开命令行,启动 Redis 客户端并创建一些值:
使用默认配置后台启动 Redis 服务器
启动 Redis 客户端 redis-cli
设置字符串
键为 hello
值为 redis
设置列表,键为 educoder-list
从列表左侧推入元素 hello
从列表右侧推入元素 educoder
从列表右侧推入元素 bye
从列表右侧弹出一个元素
设置集合,键为 educoder-set
添加元素 c
添加元素 python
添加元素 redis
删除元素 c
设置哈希,键为 educoder-hash
添加键:python,值为:language
添加键:ruby,值为:language
添加键: redis,值为:database
删除键 ruby
设置有序列表,键为 educoder-zset
添加成员 jack,分值为 200
添加成员 rose,分值为 400
添加成员 lee,分值为 100
测试说明
我会对你编写的代码进行测试:
测试输入:无;
预期输出:
redis
{'python': 'language', 'redis': 'database'}
['hello', 'educoder']
set(['python', 'redis'])
[('lee', 100.0), ('jack', 200.0), ('rose', 400.0)]
这是整体的第一关代码,在这篇文章是分开的代码点击此处跳转至该文章
代码示例如下(第一关的代码是按行粘贴,在命令行输入的):
c
redis-cli
set hello redis
lpush educoder-list hello
rpush educoder-list educoder
rpush educoder-list bye
rpop educoder-list
sadd educoder-set c
sadd educoder-set python redis
del educoder-set c
hset educoder-hash python language
hset educoder-hash ruby language
hset educoder-hash redis database
hdel educoder-hash ruby
zadd educoder-zset 200 jack
zadd educoder-zset 400 rose
zadd educoder-zset 100 lee
sadd educoder-set python redis
第2关:使用 Python 与 Redis 交互
编程要求
根据提示,在右侧Begin-End区域补充代码,实现使用 Python 编写程序与 Redis 交互:
使用方法2创建客户端r1连接到 Redis
设置下表中的两个字符串键:
键 值
test1 hello
test2 Redis
测试说明
我会对你编写的代码进行测试:
获取test1和test2键的值。
测试输入:无
测试输出:
hello
Redis
代码示例如下:
c
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import redis
def write_redis():
#********* Begin *********#
pool = redis.ConnectionPool(host='127.0.0.1', port=6379, decode_responses=True)
r = redis.Redis(connection_pool=pool)
r.set("test1", "hello")
r.set("test2", "Redis")
#********* End *********#
第3关:使用Python+Redis实现文章投票网站后端功能编程要求
根据提示,在右侧Begin-End区域补充代码,完成简化版文章投票网站的后端处理逻辑:
在 article_vote() 函数中:
该方法作用是:对文章投票
参数说明:
r:Redis 客户端
user_id:投票用户
article_id:被投票文章
已提供一周前 Unix 时间戳,存放在变量 cutoff
当满足以下条件时,为文章投一票:
该文章发布不超过一周
该用户没有为该文章投过票
在 post_article() 函数中:
该方法作用是:创建文章
参数说明:
r:Redis 客户端
user:发布用户
title:文章标题
link:文章链接
已提供:
article_id,新文章 ID
voted,新文章已投票用户名单存储键名
article,新文章详细信息存储键名
now,文章创建时间
按照 ID 递增的顺序依次创建文章
保证发布文章的用户不能给自己的文章投票
文章在发布一周后删除已投票用户名单
存储文章详细信息到 Redis 中,包括字段:
文章标题
文章链接
发布用户
存储文章的发布时间和初始投票数
初始投票数为 1
在 get_articles() 函数中:
该方法作用是:对文章进行排序
参数说明:
r:Redis 客户端
start:从排序为 start 的文章开始获取
end:到排序为 end 的文章结束获取
order:排序方式,分为两种:
time:按时间排序
score:按投票数排序
已提供文章信息空列表,articles
实现按时间/投票数排序
将排序后的文章及其全部信息组成一个列表:
按照不同排序规则取出排序在参数提供的区间范围内的文章
及每篇文章的全部信息,包括文章 ID
测试说明
我会对你编写的代码进行测试:
测试输入:无;
预期输出:
We posted a new article with id: 1
Its HASH looks like:
{'poster': 'username', 'link': 'http://www.google.com', 'title': 'A title'}
We voted for the article, it now has votes: 2
The currently highest-scoring articles are:
[{'id': '1'}]
代码示例如下:
c
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import time
ONE_WEEK_IN_SECONDS = 7 * 24 * 60 * 60
def article_vote(r, user_id, article_id):
cutoff = time.time() - ONE_WEEK_IN_SECONDS
# 请在下面完成要求的功能
#********* Begin *********#
if r.zscore('time', article_id) < cutoff:
return
if r.sadd('voted:' + article_id, user_id):
r.zincrby('score', article_id, 1)
#********* End *********#
def post_article(r, user, title, link):
article_id = str(r.incr('article'))
voted = 'voted:' + article_id
r.sadd(voted, user)
r.expire(voted, ONE_WEEK_IN_SECONDS)
now = time.time()
article = 'article:' + article_id
# 请在下面完成要求的功能
#********* Begin *********#
r.hmset(article, {
'title': title,
'link': link,
'poster': user,
})
r.zadd('score', article_id, 1)
r.zadd('time', article_id, now)
#********* End *********#
return article_id
def get_articles(r, start, end, order='score'):
ids = r.zrevrange(order, start, end)
articles = []
# 请在下面完成要求的功能
#********* Begin *********#
for id in ids:
article_data = r.hgetall(id)
article_data['id'] = id
articles.append(article_data)
#********* End *********#
return articles
第4关:字符串、列表与集合
编程要求
根据提示,在右侧Begin-End区域补充代码,完成任务分配的后端处理逻辑:
在 task_empty() 方法中:
从 Redis 中获取列表 task_list 的长度,判断是否为 0
若为 0,则返回 True
若不为 0,则返回 False
在 get_task() 方法中:
从列表 task_list 的最右侧弹出一个元素,赋值给 task
将 task 的值设置到 Redis 的字符串键 current_task 中
在 get_unallocated_staff() 方法中:
从集合 unallocated_staff 中随机返回一个元素,赋值给 staff
将上面的 staff 从集合 unallocated_staff 移动到集合 allocated_staff 中
返回(return)staff 的值
在 allocate_task(staff) 方法中:
将参数 staff 的值追加到 Redis 字符串键 current_task 的尾部,中间以 : 间隔
将追加后的字符串键 current_task 从左侧推入列表 task_queue
将字符串键 current_task 的值设置为 "None"
测试说明
我会对你编写的代码进行测试:
测试输入:
task_1 task_2 task_3 task_4 task_5
staff_1 staff_2 staff_3 staff_4 staff_5
预期输出:
Init task list: ['task_1', 'task_2', 'task_3', 'task_4', 'task_5']
Init staff list: set(['staff_4', 'staff_5', 'staff_1', 'staff_2', 'staff_3'])
Cur task list is empty: False
Get new task: task_5
Current staff is allocated: True
Current staff is unallocated: False
Current task is: None
Allocated all tasks
Task queue length: 5
Task list is empty: True
Allocated_staff: set(['staff_4', 'staff_5', 'staff_1', 'staff_2', 'staff_3'])
Unallocated_staff: set([])
代码示例如下:
c
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import redis
conn = redis.Redis()
def task_empty():
# 请在下面完成判断任务列表是否为空
#********* Begin *********#
return int(conn.llen("task_list")) == 0
#********* End *********#
def get_task():
# 请在下面完成获取一个任务
#********* Begin *********#
task = conn.rpop("task_list")
conn.set("current_task",task)
#********* End *********#
def get_unallocated_staff():
# 请在下面完成获取一个未分配的员工
#********* Begin *********#
staff=conn.srandmember("unallocated_staff")
conn.smove("unallocated_staff","allocated_staff",staff)
return staff
#********* End *********#
def allocate_task(staff):
# 请在下面完成分配任务
#********* Begin *********#
conn.append("current_task",':'+str(staff))
conn.lpush("task_queue",conn.get("current_task"))
conn.set("current_task","None")
#********* End *********#
第5关:哈希与有序集合
编程要求
根据提示,在右侧Begin-End区域补充代码,完成带优先级的队列系统的后端处理逻辑:
在 set_task_info(task_id) 方法中:
使用参数 task_id 作为域,初始状态 "init" 作为值构成域-值对,存放在 task_status 哈希键中。
在 add_task_to_queue(task_id, priority) 方法中:
参数说明:
task_id 为任务 ID
priority 为任务优先级。
将分值(优先级)为 priority 的成员 task_id 存入有序集合 task_queue 中。
注意将参数 priority 转换为整型
调用 set_task_info() 方法,传入参数 task_id
在 get_task() 方法中:
新建变量 task_list_by_priority,值为:
使用 ZREVRANGE 命令按照分值(优先级)从大到小顺序返回有序集合 task_queue 的全部成员。
新建变量 current_task,值为:
task_list_by_priority 中的第一个元素(下标为 0)
将成员 current_task 从有序集合 task_queue 中移除
修改哈希 task_status 中的 current_task 域的值为 "processing"
返回(return)current_task 的值
测试说明
我会对你编写的代码进行测试:
测试输入:
1 2 3 4 5 6 7 8 9 10
2 4 9 1 0 5 8 6 7 3
预期输出:
Add new task: 1, priority: 2, status: init
Add new task: 2, priority: 4, status: init
Add new task: 3, priority: 9, status: init
Add new task: 4, priority: 1, status: init
Add new task: 5, priority: 0, status: init
Add new task: 6, priority: 5, status: init
Add new task: 7, priority: 8, status: init
Add new task: 8, priority: 6, status: init
Add new task: 9, priority: 7, status: init
Add new task: 10, priority: 3, status: init
Before: task list is: ['3', '7', '9', '8', '6', '2', '10', '1', '4', '5']
Get new task: 3
After: task list is: ['7', '9', '8', '6', '2', '10', '1', '4', '5']
Current task status: processing
代码示例如下:
c
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import redis
conn = redis.Redis()
# 初始化任务信息到 Redis 中
def set_task_info(task_id):
# 请在下面完成要求的功能
#********* Begin *********#
conn.hset("task_status",task_id,"init")
#********* End *********#
# 将任务添加至任务队列
def add_task_to_queue(task_id, priority):
# 请在下面完成要求的功能
#********* Begin *********#
conn.zadd("task_queue",task_id,int(priority))
set_task_info(task_id)
#********* End *********#
# 从任务队列中取出优先级最高的任务
def get_task():
# 请在下面完成要求的功能
#********* Begin *********#
task_list_by_priority=conn.zrevrange('task_queue',0,-1)
current_task=task_list_by_priority[0]
conn.zrem('task_queue',current_task)
conn.hset("task_status",current_task,"processing")
return current_task
#********* End *********#
第6关:Redis基本事务与其他命令
编程要求
根据提示,在右侧Begin-End区域补充代码,完成网络约车的后端处理逻辑:
在 request_cab(user_id, priority) 方法中:
判断是否存在哈希键 request:info:用户ID 的 time 域:
提示:可使用 HEXISTS 命令
若存在,则直接 return
若不存在,做如下操作
使用事务提交下列命令:
将参数 user_id 从最左侧推入列表 cab:queue
使用 HMSET 命令设置哈希键 request:info:用户ID:
域 time,值为 time.time()
域 priority,值为参数 priority
将上述哈希键的过期时间设置为 10分钟
在 allocate() 方法中:
使用 SORT 命令对列表 cab:queue 排序,并将结果赋值给 cab_queue:
使用 BY 参数
参考键为哈希键 request:info:*,其中 * 为占位符
使用上述参考键中的 priority 域
使用 DESC 参数做倒序排序
取出 cab_queue 的第一个元素(下标为 0)赋值给 current_respond
从列表 cab:queue 中移除变量 current_respond 中包含的元素
返回(return)current_respond
测试说明
我会对你编写的代码进行测试:
测试输入:
1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1
预期输出:
Receive new request: 1, priority: 9, is_expired? True
Receive new request: 2, priority: 8, is_expired? True
Receive new request: 3, priority: 7, is_expired? True
Receive new request: 4, priority: 6, is_expired? True
Receive new request: 5, priority: 5, is_expired? True
Receive new request: 6, priority: 4, is_expired? True
Receive new request: 7, priority: 3, is_expired? True
Receive new request: 8, priority: 2, is_expired? True
Receive new request: 9, priority: 1, is_expired? True
Before: request queue: ['1', '2', '3', '4', '5', '6', '7', '8', '9']
Allocate new request: 1
After: request queue: ['2', '3', '4', '5', '6', '7', '8', '9']
Repeat request in few seconds:
Before: request queue length: 8
After: request queue length: 8
代码示例如下:
c
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import time
import redis
conn = redis.Redis()
# 用户端发起派车请求
def request_cab(user_id, priority):
# 请在下面完成要求的功能
#********* Begin *********#
if conn.hexists('request:info:' + str(user_id), 'time'):
return
pipe = conn.pipeline()
pipe.lpush('cab:queue', user_id)
pipe.hmset('request:info:'+str(user_id), {'time': time.time(), 'priority':priority})
pipe.expire('request:info:'+ str(user_id), 10 * 60)
pipe.execute()
#********* End *********#
# 平台选择优先级最高的派车请求并派车
def allocate():
# 请在下面完成要求的功能
#********* Begin *********#
cab_queue=conn.sort('cab:queue',by='request:info:*->priority',desc=True)
current_respond=cab_queue[0]
conn.lrem('cab:queue', current_respond, 1)
return current_respond
#********* End *********#
# 用户端取消派车请求
def cancel_cab(user_id):
conn.expire('request:info:' + str(user_id), 0)
conn.lrem('cab:queue', user_id)
第7关:使用Redis管理登录令牌
编程要求
根据提示,在右侧Begin-End区域补充代码,完成令牌管理的后端处理逻辑:
在 check_token(token) 方法中:
使用 hget() 方法从哈希 login 中取出参数 token 域的值
返回(return)上述值
在 update_token(token, user_id) 方法中:
参数说明:
token 为令牌
user_id 为该令牌对应的用户 ID
获得当前时间并赋值给 timestamp
使用事务提交下列命令:
将域 token 与值 user_id 对存入哈希键 login 中
将成员 token 存入有序集合 recent:token 中,分值为 timestamp
在 clean_tokens() 方法中:
使用当前时间减去 86400 得到一周前时间戳,并赋值给 one_week_ago_timestamp
使用 zrangebyscore 方法获取有序集合 recent:token 中
分值大于等于 0
小于等于 one_week_ago_timestamp 的所有成员
并赋值给变量 expired_tokens
使用 zremrangebyscore 方法移除有序集合 recent:token 中
分值大于等于 0
小于等于 one_week_ago_timestamp 的所有成员
移除哈希 login 中所有与变量 expired_tokens 中相同的域
使用指针形式传入参数 *expired_tokens
测试说明
我会对你编写的代码进行测试:
测试输入:1,2,3,4,5
预期输出:
loged user: []
[ADD!]User 1 add token
Login with 1's token, match user: 1, have_timestamp: True
loged user: ['1']
[ADD!]User 2 add token
Login with 2's token, match user: 2, have_timestamp: True
loged user: ['1', '2']
[ADD!]User 3 add token
Login with 3's token, match user: 3, have_timestamp: True
loged user: ['1', '2', '3']
[ADD!]User 4 add token
Login with 4's token, match user: 4, have_timestamp: True
loged user: ['1', '2', '3', '4']
[ADD!]User 5 add token
Login with 5's token, match user: 5, have_timestamp: True
Login with expired token
User not_exist_user add token
Clean Tokens
Login with expired token, match user: None
代码示例如下:
c
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import time
import redis
conn = redis.Redis()
# 核对令牌,并返回该令牌对应的用户 ID
def check_token(token):
# 请在下面完成要求的功能
#********* Begin *********#
return conn.hget('login', token)
#********* End *********#
# 更新令牌,同时存储令牌的创建时间
def update_token(token, user_id):
# 请在下面完成要求的功能
#********* Begin *********#
timestamp = time.time()
pipe = conn.pipeline()
pipe.hset('login', token, user_id)
pipe.zadd('recent:token', token, timestamp)
pipe.execute()
#********* End *********#
# 清理过期令牌
def clean_tokens():
# 请在下面完成要求的功能
#********* Begin *********#
one_week_ago_timestamp = time.time() - 86400
expired_tokens = conn.zrangebyscore('recent:token', 0, one_week_ago_timestamp)
conn.hdel('login', *expired_tokens)
#********* End *********#
第8关:使用Redis实现购物车
编程要求
根据提示,在右侧Begin-End区域补充代码,实现购物车的后端处理逻辑:
在 add_item(name, price) 方法中:
使用 item_id 键自增作为商品 ID
商品信息键名为 item::info,其中 为商品ID
为商品信息哈希键设置:
域为 "name",值为参数 name
域为 "price",值为参数 price
为商品信息哈希键设置过期时间:30 天后
返回(return)商品 ID
在 add_to_cart(user_id, item, count) 方法中:
如果参数 count 大于 0:
为哈希键 cart:,其中 为参数 user_id 设置:
域为参数 item
值为参数 count
否则:
移除哈希键 cart:,其中 为参数 user_id 中:
参数为 item 的域
在 get_cart_info(user_id) 方法中:
获取 user_id 对应用户的购物车信息并返回(return)
测试说明
我会对你编写的代码进行测试:
测试输入:
pen
2.5
car
20000
shirt
99
user:1
3
2
user:2
1
8
预期输出:
Add item 1! Infos: {'price': '2.5', 'name': 'pen'}, have_ttl? True
Add item 2! Infos: {'price': '20000.0', 'name': 'car'}, have_ttl? True
Add item 3! Infos: {'price': '99.0', 'name': 'shirt'}, have_ttl? True
Add item 3 to cart user:1, count: 2, info: {'3': '2'}
Add item 1 to cart user:2, count: 8, info: {'1': '8'}
代码示例如下:
c
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import redis
conn = redis.Redis()
# 添加商品
def add_item(name, price):
# 请在下面完成要求的功能
#********* Begin *********#
item_id = conn.incr('item_id')
item_info_key = 'item:' + str(item_id)+ ":info"
conn.hmset(item_info_key,{"name":name,"price":price})
conn.expire(item_info_key,30 * 24 * 60 * 60)
return item_id
#********* End *********#
# 加入购物车
def add_to_cart(user_id, item, count):
# 请在下面完成要求的功能
#********* Begin *********#
if count > 0:
conn.hset("cart:"+user_id,item,count)
else:
conn.hrem('cart:'+user_id,item)
#********* End *********#
# 获取购物车详情
def get_cart_info(user_id):
# 请在下面完成要求的功能
#********* Begin *********#
return conn.hgetall("cart:"+user_id)
#********* End *********#
第9关:使用Redis做页面缓存
编程要求
根据提示,在右侧Begin-End区域补充代码,实现使用Redis缓存网页:
创建变量 page_key,值为:
对参数 request_url 哈希编码并转化成字符串
使用字符串 cache: 与上述字符串前后拼接
尝试从 Redis 中读取字符串键,键名为 page_key 的值
若读取成功,则返回该键中的值
若读取失败,则:
创建变量 content,值为:
使用字符串 content for 与参数 request_url 前后拼接
使用 SETEX 命令将变量 content 存至字符串键:
键名为 page_key 的值
生存时间为 600 秒
测试说明
我会对你编写的代码进行测试:
测试输入:无;
预期输出:
Cache: request http://example.educoder.net/?productId=X, content: content for http://example.educoder.net/?productId=X, have_ttl? True
Get response: 'content for http://example.educoder.net/?productId=X'
Cache the request again!
Get response: 'content for http://example.educoder.net/?productId=X'
Cache: request http://example.educoder.net/?productId=X, content: content for http://example.educoder.net
代码示例如下:
c
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import redis
conn = redis.Redis()
# 使用 Redis 做页面缓存
def cache_request(request_url):
# 请在下面完成要求的功能
#********* Begin *********#
page_key = 'cache:' + str(hash(request_url))
content = conn.get(page_key)
if not content:
content = "content for " + request_url
conn.setex(page_key, content, 600)
return content
#********* End *********#
第10关:使用Redis做数据缓存
编程要求
根据提示,在右侧Begin-End区域补充代码,实现使用Redis实现数据缓存:
在 add_cache_list(data_id, delay) 方法中:
参数说明:
data_id 是需要缓存的数据的唯一标识
delay 是该数据的更新周期
将分值为 delay 的成员 data_id 存入有序集合 cache:delay 中
将分值为当前时间 time.time() 的成员 data_id 存入有序集合 cache:list 中
在 cache_data() 方法中:
获取到有序集合 cache:list 中根据分值排序的第一个元素并赋值给变量 next
获取当前时间 time.time() 并赋值给变量 now
若 next 不存在或 next 的第一个元素的分值大于 now:
等待 100 毫秒
从 next 中取出第一个元素的成员值并赋值给变量 data_id
根据 data_id 从有序集合 cache:delay 中取出它的更新周期并赋值给变量 delay
判断 delay 是否大于 0:
若小于等于 0:
从有序集合 cache:list 中移除 data_id 成员
从有序集合 cache:delay 中移除 data_id 成员
删除该数据对应的缓存键 cache:data:,其中 的值为data_id
若大于 0:
构建哈希 {'id': data_id, 'data': 'fake data'} 并赋值给变量 data,作为待缓存的伪造数据
将分值为当前时间+更新周期的成员 data_id 加入有序集合 cache:list 中
更新该成员的缓存键 cache:data:*,值为:data 编码成 JSON 格式 json(dumps(data))
测试说明
我会对你编写的代码进行测试:
测试输入:无;
预期输出:
Schedule caching of itemX every 5 seconds
Cache list looks like:
['itemX']
Cached data looks like:
'{"data": "fake data", "id": "itemX"}'
Force delete cache
The cache was cleared? True
代码示例如下:
c
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import time
import json
import redis
conn = redis.Redis()
# 将数据加入缓存队列
def add_cache_list(data_id, delay):
# 请在下面完成要求的功能
#********* Begin *********#
conn.zadd('cache:delay', data_id, delay)
conn.zadd('cache:list', data_id, time.time())
#********* End *********#
# 缓存数据
def cache_data():
# 请在下面完成要求的功能
#********* Begin *********#
next = conn.zrange('cache:list', 0, 0, withscores=True)
now = time.time()
if not next or next[0][1] > now:
time.sleep(0.1)
data_id = next[0][0]
delay = conn.zscore('cache:delay', data_id)
if delay <= 0:
conn.zrem('cache:delay', data_id)
conn.zrem('cache:list', data_id)
conn.delete('cache:data:' + data_id)
else:
data = {'id': data_id, 'data': 'fake data'}
conn.zadd('cache:list', data_id, now + delay)
conn.set('cache:data:' + data_id, json.dumps(data))
#********* End *********#