1.特性
- 速度非常快的非关系型数据库
- 可以存储键与5种不同类型的值之间的映射
- 可以将内存中键值对数据持久化到硬盘
- 可以使用赋值特性来扩展读性能
- 可以使用客户端分片来扩展写性能
2.注意点
- 在向列表推入新元素之后,会返回列表当前的长度。
- lindex key index 获取指定索引位置的值
- 如果集合元素非常多,使用SMEMBERS这个命令可能会非常慢
- REDIS事务和关系型数据库有很大的不同,REDIS事务中某个命令因数据结构而出错时,该命令不会执行,而事务中的其他命令依然会执行
3.示例
为了更好的理解redis的用处,可以根据多个实例来理解它。
-
3.1 对文章进行投票
-
3.1.1 redis数据类型设计,db.py
import redis def get_redis_client(): return redis.Redis(host='****', port=*, db=*) if __name__ == '__main__': # 测试连接 redis_cli: redis = get_redis_client() # rs: bool = redis_cli.set("name", "bob") # rs1: bytes = redis_cli.get('name') # print(rs, rs1.decode('utf-8')) # 实例化命令管道 pipe = redis_cli.pipeline() try: # 开启事务 pipe.multi() # 使用hash结构存储每篇文章信息 article_entity = { 'title': '活着', 'link': 'http://huozhe.com', 'poster': '余华', 'time': 1559185143.32, 'votes': 528 } pipe.hmset('article:10086', article_entity) # 根据发布时间排序文章有序集合(zset结构) article_time = { 'article:10086': 1559185143.32, 'article:10087': 1559185155.32, 'article:10088': 1559185188.32 } pipe.zadd('time:', article_time) # 根据文章分数排序文章有序集合 article_score = { 'article:10086': 7463, 'article:10087': 19431, 'article:10088': 5732 } pipe.zadd('score:', article_score) # 每篇文章已投票用户组(使用set结构) article_user = ('user:23', 'user:34', 'user:77', 'user:45') pipe.sadd('voted:10086', *article_user) # 执行事务 pipe.execute() print('redis数据填充成功') except Exception as e: print(e) pass finally: # 返回连接 pipe.reset()
3.1.2 实现文章投票逻辑,article_vote.py
-
import time
from db import get_redis_client
class ArticleVote(object):
# 一周的秒数
ONE_WEEK_IN_SECONDS = 7 * 86400
# 一票的分数
VOTE_SCORE = 432
# 每页文章数
ARTICLE_PER_PAGE = 25
def __init__(self, conn):
self.conn = conn
def article_vote(self, user: str, article: str):
"""
文章投票函数
:param user: 投票用户 user:22
:param article: 文章 article:10088
:return:
"""
# 计算一周前的时间点
cutoff = time.time() - self.ONE_WEEK_IN_SECONDS
# 判断文章发布时间是否过了一周
if self.conn.zscore('time:', article) < cutoff:
return False
article_id = article.partition(':')[-1]
try:
# 将用户添加到该文章已投票用户列表中去,根据返回bool判断是否投过票
if self.conn.sadd('voted:' + article_id, user):
# 增加对应文章的分数
self.conn.zincrby(name='score:', value=article, amount=self.VOTE_SCORE)
# 增加对应文章的票数
self.conn.hincrby(article, 'votes', 1)
return True
return False
except Exception as e:
print(e)
def post_article(self, user, title, link):
"""
发布新文章
:param user:
:param title:
:param link:
:return:
"""
# 字符出自增生成文章id
article_id = self.conn.incr('article:')
# 将作者添加到以投票用户组,并设置过期时间为一周
voted = 'voted:' + article_id
self.conn.sadd(voted, user)
self.conn.expire(voted, self.ONE_WEEK_IN_SECONDS)
# 添加文章,添加文章分数和时间有序集合
now = time.time()
article = 'article:' + article_id
self.conn.hmset(article, {
'title': title,
'link': link,
'poster': user,
'time': now,
'votes': 1
})
self.conn.zadd('score:', article, now + self.VOTE_SCORE)
self.conn.sadd('time:', article, now)
return article_id
def get_articles(self, page, order='score:'):
"""
获取分数高的文章
:param page:
:param order:
:return:
"""
# 获取起始页
start = (page - 1) * self.ARTICLE_PER_PAGE
end = start + self.ARTICLE_PER_PAGE - 1
ids = self.conn.zrevrange(order, start, end)
# 文章列表
articles = []
for _id in ids:
article_detail = self.conn.hgetall(_id)
article_detail['id'] = _id
articles.append(article_detail)
return articles
def add_remove_group(self, article_id: str, add_group: list, remove_group: list):
"""
添加移除文章到分组
:param article_id:
:param add_group:
:param remove_group:
:return:
"""
article = 'article:' + article_id
# 将该文章添加到所属分组中
for group in add_group:
self.conn.sadd('group:' + group, article)
# 将文章从分组中移除
for group in remove_group:
self.conn.srem('group:' + group, article)
def get_group_articles(self, group: str, page: int, order: str = 'score:'):
"""
获取分组文章
:param group:
:param page:
:param order:
:return:
"""
key = order + group
# 如果缓存中没有该数据,就去redis中获取
if not self.conn.exists(key):
self.conn.zinterstore(key, ['group:' + group, order], aggregate='max')
# 将结果缓存60s
self.conn.expire(key, 60)
return self.get_articles(page, key)
if __name__ == '__main__':
conn = get_redis_client()
article_vote = ArticleVote(conn)
# 36号用户投票10086
result = article_vote.article_vote('user:36', 'artile:10086')
print(result)
这个例子时redis实战里面的例子,它上面推荐说sadd,zincrby,hincrby这三个命令要使用事务,但是redis的事务和普通关系型数据事务很不一样,事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。这一切都因为redis的高性能。
我在编写程序时,zincrby这个命令因为参数的错误导致没有添加成功,而其他两条命令成功了,即使添加了事务也不能解决问题,并且在程序中我是用到了某条命令的返回值,但是这条命令时加入了批处理事务中的,在当时那瞬间时不会执行到redis的,这也是redis事务的弊端。如果我们在程序中要想安全又想使用到某条命令的返回值来做选择性操作,我们可以使用redis的分布式锁redlock。