文章目录
1.管道
redis-py
默认在执行每次请求都会创建和断开一次连接操作,如果想要在一次请求中指定多个命令,则可以使用 pipline
实现一次请求指定多个命令,并且默认情况下一次 pipline
是原子性操作
import redis
pool = redis.ConnectionPool(host='10.211.55.4', port=6379)
r = redis.Redis(connection_pool=pool)
pipe = r.pipeline(transaction=True)
pipe.set('name', 'jason')
pipe.set('role', 'boss')
pipe.execute()
2.事务
1.定义
命令 | 含义 |
---|---|
multi | 标记一个事务的开始 |
exec | 执行所有事务内的命令 |
watch key1,key2 | 监听视一个或多个key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断 |
unwatch | 取消watch命令对所有key的监视 |
discard | 取消事务 |
redis 127.0.0.1:6379> WATCH lock lock_times
OK
redis 127.0.0.1:7000> multi
OK
redis 127.0.0.1:7000> set a aaa
QUEUED
redis 127.0.0.1:7000> set b bbb
QUEUED
redis 127.0.0.1:7000> set c ccc
QUEUED
redis 127.0.0.1:7000> exec
redis 127.0.0.1:6379> UNWATCH
2.分布式锁
import uuid
import math
import time
import redis
from redis import WatchError, Redis
pool = redis.ConnectionPool(host='127.0.0.1', port=6379)
r = redis.Redis(connection_pool=pool)
def acquire_lock_with_timeout(conn, lock_name, acquire_timeout=3, lock_timeout=2):
identifier = str(uuid.uuid4())
lock_name = f'lock:{lock_name}'
lock_timeout = int(math.ceil(lock_timeout))
end = time.time() + acquire_timeout
while time.time() < end:
# 如果不存在这个锁则加锁并设置过期时间,避免死锁
if conn.setnx(lock_name, identifier):
conn.expire(lock_name, lock_timeout)
return identifier
# 如果存在锁,且这个锁没有过期时间则为其设置过期时间,避免死锁
elif conn.ttl(lock_name) == -1:
conn.expire(lock_name, lock_timeout)
time.sleep(0.001)
return False
def release_lock(conn: Redis, lock_name, identifier):
# python 中 redis 事务是通过pipeline的封装实现的
with conn.pipeline() as pipe: # 默认transaction=True
lock_name = 'lock:' + lock_name
while True:
try:
# watch锁,multi后如果该key被其他客户端改变,事务操作会抛出WatchError异常
pipe.watch(lock_name)
ident = pipe.get(lock_name)
if ident and ident.decode('utf-8') == identifier:
pipe.multi() # 事务开始
pipe.delete(lock_name)
pipe.execute()
return True
pipe.unwatch()
break
except WatchError:
pass
return False
3.发布订阅
1.定义
命令 | 作用 |
---|---|
publish channel msg | 将信息message发送到指定的频道channel |
subscribe channel | 订阅频道,可以同时订阅多个频道 |
unsubscribe channel | 取消订阅指定的频道,如果不指定频道,则会取消订阅所有频道 |
psubscirbe pattern pattern | 订阅多个符合给定模式的频道,每个模式以 * 作为匹配符比如 it* 匹配所有以 it 开头的频道,诸如此类 |
punsubscribe pattern pattern | 退订指定的规则, 如果没有参数则会退订所有规则 |
pubsub subcommand | 查看订阅与发布系统状态 |
注意:
使用发布订阅模式实现的消息队列,当有客户端订阅channel后只能收到后续发布到该频道的消息,之前发送的不会缓存,必须Provider和Consumer同时在线
2.Python
订阅者:
import redis
r = redis.Redis(host='127.0.0.1')
pub = r.pubsub() #查看订阅与发布系统状态
pub.subscribe("fm104.5")# 订阅电台
pub.parse_response() #解析结果
while 1:
msg = pub.parse_response()
print(msg)
发布者
import redis
pool = redis.ConnectionPool(host='127.0.0.1', port=6379, password='')
r = redis.Redis(connection_pool=pool)
r.publish("fm104.5", "Hi,yuan!")
应用场景
- 聊天系统
- 微博的订阅
4.Redis持久化
Redis
是一种内存型数据库,一旦服务器进程退出,数据库的数据就会丢失,为了解决这个问题,Redis
提供了两种持久化的方案,将内存中的数据保存到磁盘中,避免数据的丢失
持久化方式 | 定义 |
---|---|
RDB(Redis Database Backup file) | 将内存数据保存到磁盘的二进制文件dump.rdb中,速度更快,一般用作备份,也称之为基于快照的持久化,( 主从复制就是基于RDB持久化功能实现) |
AOF(append Only File) | 每一个写命令追加到appendonly.aof中,可以最大程度的保证redis数据安全,类似于mysql的binlog |
1.redis持久化之RDB
redis
提供了RDB持久化
的功能,这个功能可以将redis
在内存中的的状态保存到硬盘中,它可以手动执行,也可以再redis.conf
中配置,定期执行,RDB持久化产生的RDB文件是一个经过压缩的二进制文件,这个文件被保存在硬盘中,redis可以通过这个文件还原数据库当时的状态
redi_1.conf
daemonize yes
protected-mode yes
bind 192.168.211.133 127.0.0.1
port 6380
requirepass foobared
logfile /data/6380/redis.log #指定redis的运行日志,存储位置
dir /data/6380 #指定redis的数据文件,存放路径
dbfilename redis1_dump.rdb #指定数据持久化的文件名字
save 900 1 # 900秒之内有1个修改的命令操作,如set .mset,del
save 300 10 # 在300秒内有10个修改类的操作
save 60 10000 # 60秒内有10000个修改类的操作
2.redis持久化之AOF
AOF(append-only log file)记录服务器执行的所有变更操作命令(例如set del等),并在服务器启动时,通过重新执行这些命令来还原数据集,AOF 文件中的命令全部以redis协议的格式保存,新命令追加到文件末尾
优点:最大程序保证数据不丢
缺点:日志记录非常大
redis-client 写入数据 > redis-server 同步命令 > AOF文件
AOF持久化配置参数
appendonly yes #生成appendonly.aof文件
appendfsync always #总是修改类的操作
everysec #每秒做一次持久化
no #依赖于系统自带的缓存大小机制
redis.conf
daemonize yes
protected-mode yes
bind 192.168.211.133 127.0.0.1
port 6381
requirepass foobared
logfile /data/6381/redis.log
dir /data/6381
appendonly yes
appendfsync everysec
5.Redis主从同步
注意:
2.8以后实现PSYNC的机制,实现断线重连
步骤一:准备一主两从的数据库实例
redis_6380.conf
bind 192.168.211.133 127.0.0.1
port 6380
daemonize yes
protected-mode yes
requirepass foobared
dir /data/6380
pidfile /data/638/redis.pid
loglevel notice
logfile "/data/6380/redis.log"
dbfilename dump_6380.rdb
redis_6381.conf
bind 192.168.211.133 127.0.0.1
port 6381
slaveof 192.168.211.133 6380
daemonize yes
protected-mode yes
requirepass foobared
masterauth foobared
dir /data/6381
pidfile /data/6381/redis.pid
loglevel notice
logfile "/data/6381/redis.log"
dbfilename dump_6381.rdb
redis_6382.conf
bind 192.168.211.133 127.0.0.1
port 6382
slaveof 192.168.211.133 6380
daemonize yes
protected-mode yes
requirepass foobared
masterauth foobared
dir /data/6382
pidfile /data/6382/redis.pid
loglevel notice
logfile "/data/6381/redis.log"
dbfilename dump_6382.rdb
启动三个redis实例
redis-server redis_6380.conf
redis-server redis_6381.conf
redis-server redis_6382.conf
步骤二: 配置主从同步
#6381
redis-cli -h 192.168.211.133 -p 6381 -a foobared
#6382
redis-cli -h 192.168.211.133 -p 6382 -a foobared
#6380
redis-cli -h 192.168.211.133 -p 6380 -a foobared #检查主从状态
info replication
步骤三: 测试主库写入数据,检查从库数据
#主
192.168.211.133:6380> set name chaoge
#从
192.168.211.133:6381>get name
步骤四: 手动进行主从复制故障切换
#关闭主库6380
redis-cli -h 192.168.211.133 -p 6380 -a foobared
shutdown
#检查从库主从信息,此时master_link_status:down
redis-cli -p 6381
info replication
redis-cli -p 6382
info replication
关闭6380的主库后,在6381 6382之间选一个新的主库
1.关闭6381的从库身份
redis-cli -h 192.168.211.133 -p 6381 -a foobared
info replication
slaveof no one
2.将6382设为6381的从库
6382连接到6381:
[root@db03 ~]# redis-cli -h 192.168.211.133 -p 6382 -a foobared
127.0.0.1:6382> SLAVEOF no one
127.0.0.1:6382> SLAVEOF 127.0.0.1 6381
6.集群
1.原理
正对数据量大,需要高并发场景,redis提供了redis-cluster解决方案
客户端分片
redis3.0集群采用P2P模式,完全去中心化,将redis所有的key分成了16384个槽位,每个redis实例负责一部分slot,集群中的所有信息通过节点数据交换而更新,redis实例集群主要思想是将redis数据的key进行散列,通过hash函数特定的key会映射到指定的redis节点上
数据分布原理
分布式数据库首要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整个数据的一个子集
常见的分区规则
哈希分区和顺序分区。
Redis Cluster
采用哈希分区规则,因此接下来会讨论哈希分区规则哈希分区又分为:节点取余分区,一致性哈希分区,虚拟槽分区(redis使用的)
虚拟槽分区
虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希函数把所有的数据映射到一个固定范围内的整数集合,整数定义为槽(slot)
Redis Cluster槽的范围是0 ~ 16383
槽是集群内数据管理和迁移的基本单位,采用大范围的槽的主要目的是为了方便数据的拆分和集群的扩展,每个节点负责一定数量的槽
2.搭建
1.配置多个redis服务
cluster_6380.conf
bind 192.168.211.133 127.0.0.1
port 6380
daemonize yes
protected-mode yes
requirepass foobared
masterauth foobared
dir /data/6380
pidfile /data/6380/redis.pid
loglevel notice
logfile "/data/6380/redis.log"
dbfilename dump_6380.rdb
cluster-enabled yes
cluster-config-file nodes-6380.conf
cluster-require-full-coverage no
cluster_6381.conf,cluster_6382.conf,cluster_6383.conf,cluster_26380.conf,cluster_26381.conf,cluster_26382.conf的配置同上将所有的6380改成相应的端口,命令如下 sed -i "s#6380#6381#g" cluster_6381.conf
注意
#开启集群模式 cluster-enabled yes #集群内部的配置文件 cluster-config-file nodes-6380.conf #redis cluster需要16384个slot都 正常的时候才能对外提供服务,换句话说,只要任何一个slot异常那么整个cluster不对外提供服务,因此生产环境一般为no cluster-require-full-coverage no
2.开启redis-cluster
- 下载,编译,安装Ruby
- 安装rubygem redis
- 安装redis-trib.rb命令
第一步,安装ruby
#下载ruby源码包
wget https://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz
#解压缩ruby源码包
tar -zxvf ruby-2.3.1.tar.gz
#编译且安装
./configure --prefix=/opt/ruby/
make && make install
#yum安装
yum install ruby -y #-y 表示当安装过程提示选择全部为"yes"
第二步,配置ruby环境变量
vim /etc/profile
#写入如下配置
PATH=$PATH:/opt/ruby/bin
#重新加载/etc/profile
source /etc/profile
#检查ruby和gem的环境,ruby相当Python,gem相当于pip
ruby -v
gem -v
第三步,安装ruby操作redis包
wget http://rubygems.org/downloads/redis-3.3.0.gem
gem install -l redis-3.3.0.gem
第四步,安装redis-trib.rb命令
find / -name redis-trib.rb #默认会在redis数据库的编译安装路径下
cp /opt/redis/src/redis-trib.rb /usr/local/bin/
第五步,修改 client.rb文件中的password(如果配置文件中设置了密码)
#查找client.rb文件
find / -name client.rb
添加实际的密码
DEFAULTS = {
:url => lambda { ENV["REDIS_URL"] },
:scheme => "redis",
:host => "127.0.0.1",
:port => 6379,
:path => nil,
:timeout => 5.0,
:password => "foobared",
:db => 0,
:driver => nil,
:id => nil,
:tcp_keepalive => 0,
:reconnect_attempts => 1,
:inherit_socket => false
}
第六步,一键开启redis-cluster集群
#每个主节点,有一个从节点,代表--replicas 1
redis-trib.rb create --replicas 1 192.168.211.133:6380 192.168.211.133:6381 192.168.211.133:6382 192.168.211.133:26380 192.168.211.133:26381 192.168.211.133:26382
#集群自动分配主从关系 7000、7001、7002为 7003、7004、7005 主动关系
第六步,查看集群状态
redis-cli -h 192.168.211.133 -p 6380 -a foobared cluster info
#等同于查看nodes-7000.conf文件节点信息
redis-cli -p 6380 cluster nodes
#集群主节点状态
redis-cli -p 6380 cluster nodes | grep master
#集群从节点状态
redis-cli -p 6380 cluster nodes | grep slave
3.Python
1.安装
pip install redis-py-cluster
2.使用
from rediscluster import RedisCluster
if __name__ == '__main__':
try:
# 构建所有的节点,Redis会使⽤CRC16算法,将键和值写到某个节点上
startup_nodes = [
{'host': '192.168.26.128', 'port': '7000'},
{'host': '192.168.26.130', 'port': '7003'},
{'host': '192.168.26.128', 'port': '7001'},
]
# 构建StrictRedisCluster对象
src=RedisCluster(startup_nodes=startup_nodes,decode_responses=True)
# 设置键为name、值为ja的数据
result=src.set('name','ja')
print(result)
# 获取键为name
name = src.get('name')
print(name)
except Exception as e:
print(e)
7.哨兵
1.Redis-Sentinel
redis-Sentinel是redis官方推荐的高可用性解决方案,redis-Sentinel作为一个独立的进程,用于监控多个master-slave集群,自动发现master宕机,进行自动切换slave 到master
2.Redis 工作机制
- 每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令
- 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过
down-after-milliseconds
选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线 - 如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态
- 当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态,则Master会被标记为客观下线
- 若没有足够数量的 Sentinel 同意Master 已经下线, Master 的客观下线状态就会被移除
- 若 Master 重新向 Sentinel 的 PING 命令返回有效回复,Master 的主观下线状态就会被移除
主观下线:Subjectively Down,简称 SDOWN,指的是当前Sentinel实例对某个redis服务器做出的下线判断
SDOWN适合于Master和Slave,只要一个 Sentinel 发现Master进入了ODOWN,这个 Sentinel 就可能会被其他 Sentinel 推选出, 并对下线的主服务器执行自动故障迁移操作
客观下线:Objectively Down,简称 ODOWN,指的是多个 Sentinel 实例在对Master Server做出 SDOWN 判断,并且通过 SENTINEL,is-master-down-by-addr
命令互相交流之后,得出的Master Server下线判断,然后开启failover
ODOWN只适用于Master,对于Slave的 Redis 实例,Sentinel在将它们判断为下线前不需要进行协商,所以Slave的 Sentinel永远不会达到ODOWN
3.配置哨兵
前提 完成3.Reedis主从同步
redis_sentinel_26380.conf
bind 192.168.211.133 127.0.0.1
port 26380
daemonize yes
dir /data/26380
logfile "26380.log
# 当前Sentinel节点监控 192.168.119.10:6379 这个主节点
# 2代表判断主节点失败至少需要2个Sentinel节点节点同意
# mymaster是主节点的别名
sentinel monitor mymaster 192.168.119.10 6380 2
#每个Sentinel节点都要定期PING命令来判断Redis数据节点和其余Sentinel节点是否可达,如果超过30000毫秒30s且没有回复,则判定不可达
sentinel down-after-milliseconds mymaster 30000
#原来的从节点会向新的主节点发起复制操作,限制每次向新的主节点发起复制操作的从节点个数为1
sentinel parallel-syncs mymaster 1
#故障转移超时时间为180000毫秒
sentinel failover-timeout mymaster 180000
redis-sentinel-26380.conf,redis-sentinel-26381.conf的配置差异仅仅是第二行的port(端口)不同
然后启动三个sentinel哨兵
redis-sentinel redis-sentinel-26379.conf
redis-sentinel /etc/redis-sentinel-26380.conf
redis-sentinel /etc/redis-sentinel-26381.conf
此时查看哨兵是否成功通信
redis-cli -h 192.168.211.133 -p 26380 -a foobaredinfo replication
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.119.10:6379,slaves=2,sentinels=3
#看到最后一条信息正确即成功了哨兵,哨兵主节点名字叫做mymaster,状态ok,监控地址是192.168.119.10:6379,有两个从节点,3个哨兵
4.Python
# 导入redis sentinel包
from redis.sentinel import Sentinel
# 指定sentinel的地址和端口号
sentinel = Sentinel([('localhost', 26380)], socket_timeout=0.1)
# 获取以下主库和从库的信息
sentinel.discover_master('mymaster')
sentinel.discover_slaves('mymaster')
# 配置读写分离
# 写节点
master = sentinel.master_for('mymaster', socket_timeout=0.1)
# 读节点
slave = sentinel.slave_for('mymaster', socket_timeout=0.1)
# 读写分离测试
master.set('name2', 'xiaopanwye2')
slave.get('mymaster')
8.Django中的应用
1.单机应用
setting.py
CACHES = {
"default": { # 默认
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://192.168.103.143:6379/0",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
"session": { # session
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://192.168.103.143:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
views.py
from django_redis import get_redis_connection
redis_conn = get_redis_connection('verify_code')