一、哈希表的功能和应用
哈希表(Hash Table)是一种数据结构,它实现了“键-值”(Key-Value) 的映射。根据Key就能快速找到Value。 并且,无论有多少个键值对,查询时间始终不变。Python 的字典就是基于哈希表实现的。
在Redis中也有一个数据结构叫作哈希表。
在Redis中,使用哈希表可以保存大量数据,且无论有多少数据,查询时间始终保持不变。
Redis的一个哈希表里面可以储存2的32次幂-1 (约等于43亿)个键值对。
1、使用Redis记录用户在线状态
(1)使用字符串记录用户的在线状态
程序的逻辑非常简单,包括以下几个步骤:
- 用户登录时,在Redis中添加一个字符串,Key为用户账号,Value为1
- 用户退出网站时,从Redis中删除账号名对应的Key
- 查询时,程序尝试从Redis中获取用户账号对应的字符串:如果值为1,则表示“在线”;如果值为None则表示“不在线”
import redis
client = redis.Redis()
def set_online_status(user_id):
"""
当用户登录网站的时候,调用这个函数,在Redis中设置一个字符串
:param user_id: 用户账号
:return: None
"""
client.set(user_id, 1)
def set_offline_status(user_id):
"""
当用户登出网站时调用这个函数,从Redis中删除这个以用户账号为Key的字符串
:param user_id: 用户账号
:return: None
"""
client.delete(user_id)
def check_online_status(user_id):
"""
检查用户是否在线,如果在线,那么get用户账号对应的Key就会返回1,否则返回None
:param user_id: 用户账号
:return: bool
"""
online_status = client.get(user_id)
if online_status and online_status.decode() == '1':
return True
return False
(2)使用哈希表记录用户的在线状态
import redis
client = redis.Redis()
def set_online_status(user_id):
"""
当用户登录网站的时候,调用这个函数,在Redis中,名为user_online_status的哈希表中添加一个字段,字段名为用户账号,值为1
:param user_id: 用户账号
:return: None
"""
client.hset('user_online_status', user_id, 1)
def set_offline_status(user_id):
"""
当用户登出网站时调用这个函数,从Redis中名为user_online_status的哈希表中删除一个字段,字段名为用户账号
:param user_id: 用户账号
:return: None
"""
client.hdel('user_online_status', user_id)
def check_online_status(user_id):
"""
检查用户是否在线,如果哈希表user_online_status中以用户账号为名的字段,就返回True,否则返回False
:param user_id: 用户账号
:return: bool
"""
return client.hexists('user_online_status', user_id)
2、使用Python向哈希表中添加数据
- hset一次只能添加一个键值对
- hmset一次可以添加多个键值对
import redis
import json
client = redis.Redis()
client.hset('people_info', '张小二', json.dumps({'age': 17, 'salary': 100, 'address': '北京'}))
other_people = {
'王小三': json.dumps({'age': 20, 'salary': 9999, 'address': '四川'}),
'张小四': json.dumps({'age': 30, 'salary': 0, 'address': '山东'}),
'刘小五': json.dumps({'age': 24, 'salary': 24, 'address': '河北'}),
'周小六': json.dumps({'age': 56, 'salary': 87, 'address': '香港'})
}
client.hmset('people_info', other_people)
print('添加完成')
![](https://img.haomeiwen.com/i14270006/58116661a365df51.png)
3、使用Python从哈希表中读取数据
- hkeys:用于获取所有字段的字段名,返回的数据是包含bytes型数据的列表
client.hkeys('哈希表名')
- hget:获取一个字段的值
client.hget('哈希表名', '字段名')
- hmget:一次性获取多个字段的值
client.hmget('哈希表名', ['字段名1', '字段名2',......,'字段名n])
- hgetall:获取一个哈希表中的所有字段名和值
client.hgetall('哈希表名')
import redis
client = redis.Redis()
# 获取所有字段名
field_names = client.hkeys('people_info')
for name in field_names:
print(name.decode())
print("------------------------")
# 获取一条数据
info = client.hget('people_info', '张小二')
print(info.decode())
print("------------------------")
# 获取多条数据
info_list = client.hmget('people_info', ['王小三', '刘小五'])
for info in info_list:
print(info.decode())
print("------------------------")
# 获取所有字段名和值
all_info = client.hgetall('people_info')
print(all_info)
print("------------------------")
![](https://img.haomeiwen.com/i14270006/8230497f82ea6884.png)
4、使用Python判断哈希表中是否存在某字段,并获取字段数量
- hexists:判断一个哈希表中是否有某个字段
client.hexists('哈希表名', '字段名')
- hlen:查看一个哈希表中有多少个字段
client.hlen('哈希表名')
# 判断字段是否存在
if client.hexists('people_info', '张小二'):
print('有张小二这个字段')
else:
print('没有张小二这个字段')
print("------------------------")
field_num = client.hlen('people_info')
print(f'people_info哈希表中一个有{field_num}个字段')
![](https://img.haomeiwen.com/i14270006/867c8c2095e1ad4b.png)
5、在 Redis交互环境redis-cli中读/写哈希表
(1)、向哈希表中添加内容
hset 哈希表名 字段名 值
hmset 哈希表名 字段名1 值1 字段2 值2 字段名n 值n
(2)、从哈希表中读取内容
hkeys 哈希表名
hget 哈希表名 字段名
hmget 哈希表名 字段名1 字段名2 字段名3
hgetall 哈希表名
(3)、判断字段是否存在和获取字段数量
hexists 哈希表名 字段名
hlen 哈希表名
二、发布消息/订阅频道
1、实现一对多的消息发布
(1)使用字符串实现一对多的消息发布功能
发送端代码
import redis
import datetime
import json
client = redis.Redis()
while True:
message = input('请输入需要发布的信息:')
now_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
data = {'message': message, 'time': now_time}
client.set('message', json.dumps(data))
接收端代码
import redis
import time
import json
client = redis.Redis()
print('开始接收消息...')
last_message_time = None
while True:
data = client.get('message')
if not data:
time.sleep(1)
continue
info = json.loads(data.decode())
message = info['message']
send_time = info['time']
if send_time == last_message_time:
# 这条信息已经接收过了,不需要重复接收
time.sleep(1)
continue
print(f'接收到新信息:{message}, 发送时间为:{send_time}')
last_message_time = send_time
![](https://img.haomeiwen.com/i14270006/e5b1305b68211d8e.png)
![](https://img.haomeiwen.com/i14270006/f873d9ca032dad09.png)
(2)使用字符串的弊端
使用字符串进行消息的发布,虽然说代码简单易懂,但它也存在诸多问题。举例如下:
- 接收端不知道发送端什么时候发布消息,因此必须持续不断检查Redis,浪费系统资源。
- 由于轮询查询,所以消息有延迟。
- 如果发送端在1秒内连续更新10条,则后一条会覆盖前一 条, 而接收端每1秒才获取一次数据, 必然导致最多漏掉9条数据。要减少遗漏数量就需要增加轮询频率,进一步增大系统开销。
(3)使用Redis的“发布/订阅”模式实现消息通信
“发布/订阅”模式是Redis自带的一对多消息通信模式。使用“发布/订阅”模式不仅可以解决字符串通信遇到的各种问题,而且代码更简洁。
发送端代码如下
import redis
import json
import datetime
client = redis.Redis()
while True:
message = input('请输入需要发布的信息:')
now_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
data = {'message': message, 'time': now_time}
client.publish('pubinfo', json.dumps(data))
接收端
import redis
import json
client = redis.Redis()
listener = client.pubsub(ignore_subscribe_messages=True)
listener.subscribe('pubinfo')
for message in listener.listen():
data = json.loads(message['data'].decode())
print(f'接收到新消息:{data["message"]},发送时间为:{data["time"]}')
2、在Python中发布消息/订阅频道
发布消息
client.publish('频道名','消息')
订阅频道
client = redis.Redis()
listener = client.pubsub()
listener.subscribe('频道名')
for message in listener.listen():
print('接收到新消息')
多个频道
listener = client.pubsub()
listener.subscribe('频道名1','频道名2','频道名n')
import redis
client = redis.Redis()
listener = client.pubsub(ignore_subscribe_messages=True)
listener.subscribe('computer', 'math', 'shopping')
for message in listener.listen():
channel = message['channel'].decode()
data = message['data'].decode()
print(f'频道:{channel} 发了一条新信息:{data}')
![](https://img.haomeiwen.com/i14270006/ca8d231469c69826.png)
3、在redis-cli中发布消息/订阅频道
发布消息
publish 频道名 消息
订阅频道
subscribe 频道名1 频道名2 频道名n
三、有序集合
1、实现排行榜功能
(1)、使用传统数据库实现排序
import pymongo
handler = pymongo.MongoClient('mongodb://root:123456@localhost').chapter_9.rank_data
result = handler.find({}).sort('score',-1)
for row in result :
print(row)
![](https://img.haomeiwen.com/i14270006/864639232571314b.png)
(2)、使用数据库排序的弊端
具体到一个实际例子,比如说直播网站观众向主播送礼物的排行版,
如果直接在数据库里面进行排序,弊端有以下几点:
- 排行榜会实时更新,数据每一次变化都要排序,会对数据库的性能造成影响。
- 频繁更新数据,导致数据库性能下降。
- 数据量太大时排序时间缓慢。
- 对被排序字段添加索引会占用更多空间。
(3)、使用有序集合
import pymongo
import redis
handler = pymongo.MongoClient('mongodb://root:123456@localhost').chapter_9.rank_data
client = redis.Redis()
rows = handler.find({}, {'_id': 0})
for row in rows:
print(row)
client.zadd('rank', {row['user_id']:row['score']})
![](https://img.haomeiwen.com/i14270006/499e3994dfee0fa2.png)
显示特定排名的用户
position = client.zrevrank('rank', 10017)
print(f'用户:10017排名为:{position + 1}')
显示全部排名
rank = client.zrevrange('rank', 0, 10000, withscores=True)
print('从高到低排名如下:')
for index, one in enumerate(rank):
print(f'用户id: {one[0].decode()}, 积分:{one[1]},排名第:{index + 1}')
![](https://img.haomeiwen.com/i14270006/06e177264c0e60f2.png)
2、使用 Pyhon读写有序集合
(1)、向有序集合添加数据
name1 = '王小二'
name2 = '张三'
client.zadd('age_rank',{name1:18,name2:26,'小明':10})
![](https://img.haomeiwen.com/i14270006/500579128a125432.png)
(2)、修改评分
把“王小二”的年龄增加三岁,把“小明”的年龄减0.5岁
client.zincrby('age_rank',3,'王小二')
client.zincrby('age_rank',-0.5,'小明')
![](https://img.haomeiwen.com/i14270006/1075094b20129574.png)
(3)、对有序集合元素基于评分范围进行排序
- zrangebyscore:根据评分按照从小到大的顺序排序
- zrevrangebyscore:根据评分按照从大到小的顺序排序
在rank集合中,对积分在10~100范围内的人员进行倒序排序,并返回前三条。
rank_10_100 = client.zrevrangebyscore('rank',100,10,0,3,withscores=False)
print(rank_10_100 )
# 返回列表
rank_10_100_tuple = client.zrevrangebyscore('rank',100,10,0,3,withscores=True)
print(rank_10_100_tuple )
# 返回元素为元组的列表
![](https://img.haomeiwen.com/i14270006/38b9e0b6d7a2af09.png)
(4)、对有序集合基于位置进行排序
- zrange:对评分按照从小到大的顺序排序
- zrevrange:对评分按照从大到小的顺序排序
开始位置写“0”,结束位置写“4”,则取出最小的5个元素
rank_5_tuple = client.zrevrange('rank',0,4,withscores=True)
print(rank_5_tuple )
(5)、根据值查询排名,根据值查询评分
查询一个值在有序列表中的排名
client.zrank('有序列表名','值')
client.zrevrank('有序列表名','值')
查询一个值的评分
client.zscore('有序列表名','值')
(6)、其他常用方法
查询有序集合有多少值
client.zcard('有序集合名')
查询在某个评分范围内的值有多少
client.zcount('有序集合名',评分下限,评分上限)
3、在Redis交互环境redis-cli中使用有序集合
四、Redis 的安全管理
1、设置密码并开放外网访问
requirepass
![]()
![](https://img.haomeiwen.com/i14270006/7af472b9e8a87e2a.png)
Python连接
client = redis.Redis(host='127.0.0.1',port=3129,password='123456')
2、禁用危险命令
配置文件添加
rename-command CONFIG ""
rename-command FLUSHDB sfjafjfaerawe
rename-command FLUSHALL IWERDE
rename-command PEXPIRE OKASETTW
rename-command SHUTDOWN ""
rename-command BGREWRITEAOF SEWERWEFSDF
rename-command BGSAVE ASDFPEWE
rename-command SAVE ASDFKLEWE
rename-command DEBUG
网友评论