Redis数据库学习
1. redis介绍:Redis是非关系型的kv型数据库
- 特点:
- 开源的,使用C编写,基于内存且支持持久化
- 支持数据类型丰富,字符串strings、散列hashes、列表lists、集合sets、有序集合sorted sets等等
- 支持多种编程语言(C、C++、Python、Java、PHP···)
- 单进程单线程(核心业务使用的是单进程单线程,如数据的查询、写入等)
- Redis亮点功能:
- 持久化:将内存中的数据保存到磁盘中,保证数据安全,方便进行数据备份和回复
- 过期键功能:为键设置一个过期时间,让它在指定时间内自动删除,节省内存空间
- 事务功能:弱事务型的数据库,只是具备简单的事务功能
- 主从复制:如何让redis保持高可用状态,官方提供主从搭建方案
- sentinel哨兵:在搭配了基础的主从结构后,哨兵可做到自动故障转移
- 常见数据库对比
- Mysql:关系型数据库,表格,基于磁盘,速度慢
- MongoDB:键值对文档型数据库,值为JSON文档,数据结构相对单一
- Redis:非关系型数据库,解决硬盘IO带来的性能瓶颈
- 应用场景:
- 缓存:作为缓存存储介质,此业务场景最为常见,查询优先走redis,没有则走MySQL;可有效降低MySQL查询压力
- 并发计数:由于redis是单进程单线程,可有效解决并发请求计数场景,例如:微博点赞
- 排行榜:各大实时排行榜 -> 电商、游戏中的排行
- 生产者消费者模型:充当生产者消费者模型的中间层;生产者可将任务分发给redis,消费者从redis中获取任务
2. 安装与连接
- 安装:
sudo apt-get install redis-server
,安装后,ubuntu会自动将redis列为开机自动启动项; - 启动:
sudo /etc/init.d/redis-server status|start|stop|restart
;status:redis当前是否是启动状态 - 客户端连接:redis-cli -h IP地址 -p 6379 -a 密码,注:执行redis-cli后务必检查redis是否连接上,检查命令
ping
,如果连接上,redis会返回一个PONG
3. Redis配置及基础命令
-
配置文件
- 路径:
/etc/redis/redis.conf
- 修改配置文件前先备份配置文件
sudo cp /etc/redis/redis.conf /etc/redis/redis_bak.conf
sudo chown redis:redis /etc/redis/redis_bak.conf
修改用户与用户组(cp出来的备份文件默认用户与用户组均为root,需要进行修改)
- 添加密码
- 配置文件中添加
requirepass 密码
,vim进入配置文件,在500行左右,取消注释并添加密码 - 重启redis服务
sudo /etc/init.d/redis-server restart
- 客户端连接
redis-cli -h 127.0.0.1 -p 6379 -a 密码
-h:域名、-p:端口、-a:验证(即需要输入密码) - 注:添加密码后不用密码也可进入到redis,但是未连接到数据库,可以用
ping
检测是否正确连接,如下图显示NOAUTH说明需要密码登陆,可在127.0.0.1:6379>AUTH 密码
来添加密码,返回OK则说明密码验证成功 - 重启redis失败大概率为配置文件配置有误导致
- 解决方案1:终端中直接执行
redis-server /etc/redis/redis.conf
启动服务,若有报错,按照报错提示修改redis.conf - 解决方案2:还原备份文件(重命名配置文件)
- sudo mv /etc/redis/redis.conf /etc/redis/redis_error.conf
- sudo mv /etc/redis/redis_bak.conf /etc/redis/redis.conf
- 解决方案1:终端中直接执行
- 配置文件中添加
- 开启远程连接
- 注释掉本地IP地址绑定:69行:
#bind 127.0.0.1 ::1
- 关闭保护模式(把yes改为no):88行:
protected-mode no
- 重启服务
sudo /etc/init.d/redis-server restart
- 注释掉本地IP地址绑定:69行:
- 路径:
-
基础通用命令 - 即无关具体的数据类型
命令 说明 样例 select number
切换数据库(redis有16个数据库,
0-15为具体的数据库编号,
默认进入redis零号库)- info
查看redis服务的整体情况 - keys 表达式
查找所有符合给定模式的key,
正式环境中请勿使用此命令;
由于redis单进程单线程,当key很多时,
当前命令可能阻塞redisKEYS * 匹配数据库中的所有key
KEYS h?llo 匹配hello、hallo、hxllo等
KEYS h*llo 匹配hllo和heeeeello等type key
返回当前键的数据类型 - exists key
返回当前键是否存在(返回0代表当前key不存在,返回1代表存在) - del key
删除key - rename key newkey
重命名当前key的名字 - flushdb
清除当前所在数据库的数据 - flushall
清除所有数据库数据 -
4. redis 数据类型与操作
4.1 字符串类型string
- 基础概念:
- 在redis中字符串、整型的数字都会转为字符串来存储
- 以二进制的方式存储在内存中
-
key命名规范:易读,做到见名知意
-
key命名原则:key值不易过长,过长易消耗内存,且在数据中查找这类键值的计算成本高;不易过短,可读性差;
-
值:一个字符串类型的值最多能存储512M内容
-
常用命令
命令 说明 返回值 特殊参数 set key value nx ex
设置一个字符串的key - nx -> not exist 代表当key不存在时,才存储这个key
ex -> expire 过期时间,单位秒,不设置过期时间,则该key和value会常驻内存get key
获取key的值 key的值,不存在则返回‘nil’ - strlen key
获取key存储值的长度 - - getrange key start stop
获取指定范围切片内容(包含start、stop) - - setrange key index value
从索引值开始,用value替换原内容 最新长度 - mset key1 value1 key2 value2 key3 value3
批量添加key和value 添加成功则返回OK - mget key1 key2 key3
批量获取key的值 key的值,不存在则返回‘nil’ - -
数值操作
命令 功能 incrby key step
将key对应的value增加指定的step decrby key step
将key对应的value减少指定的step incr key
将key对应的value +1 decr key
将key对应的value -1 incrbyfloat key step
将key对应的value增加指定的step(浮点数) -
应用场景
- 缓存:将MySQL中的数据存储到redis字符串类型中
- 并发计数(点赞/秒杀):通过redis单进程单线程的特点,由redis负责计数,并发问题转为串行问题
- 带有效期的验证码:借助过期时间,存放验证码;到期后自动消亡
-
过期时间:默认情况下key没有过期时间,需要手动指定
- 方式一:直接用set的ex 参数
set key value ex 3(单位秒)
,只有字符串数据类型独有 - 方式二:使用expire通用命令,先添加键和值,再设置有效期;
set key value
;expire key 5(单位秒)
pexpire key 5(单位毫秒)
- 方式一:直接用set的ex 参数
-
检查过期时间:
ttl key
(通用命令);返回值-1:代表当前key没有过期时间;>0:代表当前key剩余存活时间;-2:代表当前key不存在 -
删除过期时间:
presist key
(把带有过期时间的key变成永久不过期);返回值:1代表删除过期时间成功;0代表当前key没有过期时间或者key不存在 -
Redis删除过期key机制
-
删除机制:每个redis数据库中,都会有一个特定的容器负责存储带有过期时间的key以及它对应的过期时间,这个容器称之为“过期字典”;针对过期字典中的key,redis结合惰性删除和定期删除两大机制,有效删除过期数据
-
惰性删除机制:当调用key时,检查是否过期,如果过期则删除
- 定期删除机制:主动定期扫描过期字典中的数据,检查是否过期(伪代码说明)
DB_NUMBERS = 16 #数据库数量
KEY_NUMBERS = 20 #每次检查key的数量,随机抽样
current_db = 0 #记录当前检查到哪个库
def activeExpireCycle():
for i in range(DB_NUMBERS):
if current_db == DB_NUMBERS:#当当前执行的数据库为数据库的总数量时,将current_db置零,从0号库重新开始
current_db = 0
#获取当前数据库
redisDB = server.db[current_db] #当前扫描的数据库,从0库开始
first_start = True #标记当前第一次删除的一个标记位
del_key_num = 0 #删除key的数量
current_db += 1 #将全局变量curre_db+1,为下一次扫描数据库做准备
while (first_start or del_key_num > KEY_NUMBERS/4): #当执行第一次删除或者删除key的数量大于每次检查数量的1/4时执行while循环
first_start = False #将first_start置为false
for j in range(KEY_NUMBERS): #从抽样数中随机拿key进行判断
_key = redisDB.randomExpireKey() #从当前选取的数据库中随机拿出一个key
if is_expire(_key): #判断当前的key是否过期,拿当前的时间戳与当前的key进行比较
#过期 则直接删除
delete_key(_key)
del_key_num += 1 #删除key的数量+1
if time_is_limit():
#若执行时间太长 默认是25毫秒
return #如果当前执行时间超过25毫秒,则直接中断当前循环
- 最大内存检查
- 最后一道保险 - maxmemory 配置选项:一旦内存量超过最大限制,redis会在执行命令时触发内存淘汰机制(需要手动在redis配置文件中激活maxmemory配置项,单位为字节)
- 主流淘汰机制:
- volatile-lru(Last Recently Used):从已设置过期时间的内存数据集中挑选最近最少使用的数据淘汰(在容器的最下方的数据相对来说是最少使用的数据)
- volatile-ttl(Time To Live):从已设置过期时间的内存数据集中挑选即将过期的数据淘汰
- volatile-random:从已设置过期时间的内存数据集中任意挑选数据淘汰
- allkeys-lru:从内存中所有数据集中挑选最近最少使用的数据淘汰、
- allkeys-random:从内存中所有数据集中任意挑选数据淘汰
- no-enviction:禁入大多写命令,可执行删除命令(默认)
4.2 redis数据类型 - 列表类型list
- 基础概念:
- 元素是字符串类型<只能存储字符串类型 >
- 列表头尾增删快,中间增删慢,增删元素是常态
- 元素可重复
- 最多包含232 -1个元素
- 索引同Python列表
-
常用命令
命令 说明 返回 LPUSH key value1 value2
从列表头部压入元素,
最先插入的元素在最右侧返回list最新的长度,如果key不存在,
redis会自动初始化一个keyRPUSH key value1 value2
从列表尾部压入元素,
最先插入的元素在最左侧返回list最新的长度,如果key不存在,
redis会自动初始化一个keyRPOPLPUSH src dst
从列表src尾部弹出一个元素,
压入到列表dst的头部返回被弹出的元素 LINSERT key after|befor value newvalue
在列表指定元素后/前插入元素 1. 如果命令执行成功,返回列表的长度
2.如果没有找到指定元素,返回-1
3.如果key不存在或为空列表,返回0LRANGE key start stop
查看列表中的元素(start起始索引,
stop结束索引,包含start和stop)返回索引从start到stop的元素 LLEN key
获取列表长度 返回列表的长度 LPOP key
从列表头部弹出一个元素 返回弹出的元素,不存在则返回nil RPOP key
从列表尾部弹出一个元素 返回弹出的元素,不存在则返回nil BLPOP key timeout
当key不存在时,从列表头部阻塞弹出,
列表为空时阻塞超过timeout时间后,还没有查到key则返回nil BRPOP key timeout
当key不存在时,从列表尾部阻塞弹出,
列表为空时阻塞超过timeout时间后,还没有查到key则返回nil LREM key count value
删除指定元素,
count>0:表示从头部开始向表尾搜索,移除与value相等的元素,数量为count;
count<0:表示从尾部开始项表头搜索,移除与value相等的元素,数量为count;
count=0:移除表中所有与value相等的值返回被移除元素的数量 LTRIM key start stop
保留指定范围内的元素 OK LSET key index newvalue
设置list指定索引的值,将原value改为newvalue -
关于BLPOP和BRPOP的说明:
- 如果弹出的列表不存在或者为空,就会阻塞
- 超时时间设置为0,就是永久阻塞,直到有数据可以弹出
- 如果多个客户端阻塞在同一个列表上,redis会使用First In First Service原则,先到先服务
4.3 pyredis 操作redis
- 检查ubuntu是否安装pyredis
sudo pip freeze|grep -i 'redis'
,如果已安装则会返回redis版本号 - 未安装则执行
sudo pip install redis
进行安装 - 操作流程:
- 建立数据库连接对象
import redis #创建数据库连接对象 r = redis.Redis(host = '127.0.0.1',port = 6379,db = 0,password='123456')
- host:连接数据库的域名
- port:端口号
- db:连接的数据库编号
- password:数据库登陆密码
- 连接对象.redis命令即可实现对数据库的操作,大部分命令跟redis终端使用命令雷同
- 例如:r.set(key,value)
- 建立数据库连接对象
4.4 生产者消费者模型
- django发邮件:django接到发送邮件请求后,与第三方邮件服务器进行通信,第三方邮件服务器返回发送邮件响应给到django,django将响应返回给用户,如果第三方邮件服务器处理任务很慢,那么django就会一直等待第三方邮件服务器返回响应,从而引发阻塞,为解决这种情况可以引入容器(redis[list]),django将发邮件任务交给容器来处理(一般采用先进先出->队列),由另外的服务器从容器中获取任务,并与第三方邮件服务器进行通信
- 生产者:发出任务
- 消费者:获取任务并处理任务
- 生产者消费者模型优点:降低生产者与消费者之间的依赖关系;生产者与消费者可以是两个独立的并发主体,互不干扰的运行;
- 生产者消费者模型的三二一原则:三种关系:生产者与生产者互斥、生产者与消费者同步互斥、消费者与消费者互斥;两个角色:生产者与消费者;一个场所:缓冲(容器)
4.5 位图操作
-
位图不是真正的数据类型,它是定义在字符串类型中
-
一个字符串类型的值最多能存储512M字节的内容,转换成位上限为:232
-
位图操作常用命令
命令 说明 语法 参数 返回值 SETBIT
设置某位置上的二进制值 SETBIT key offset value
offset - 偏移量 从0开始
value - 0或者1返回修改前bit位上的值 GETBIT
获取某一位上的值 GETBIT key offset
offset - 偏移量 从0开始 返回当前bit位上的值 BITCOUNT
统计键所对应的值中有多少个1 BITCOUNT key start end
start/end:代表的是字节索引 返回start到end字节的1的个数 -
Python中使用位图
- 生成连接对象r
r = redis.Redis(host='127.0.0.1',port = 6379,db = 0,password = '123456')
- 调用r.setbit/getbit/bitcount
- 生成连接对象r
-
SETBIT命令两种情况说明:
-
key不存在时:初始化最小字节数的bit位,默认补0
-
key存在时:1. 满足长度要求,直接修改bit位;2. 不满足长度要求,在原油value上初始化最小字节数的bit位
-
应用场景:
-
假设现在我们希望记录自己网站上的用户的上线频率,比如说A用户上线了多少天,B用户上线了多少天,诸如此类,以此作为数据,从而决定让哪些用户参加重要活动–这个模式可以用SETBIT和BITCOUNT来实现
-
比如说:每当用户在某一天上线的时候,我们就使用SETBIT,以用户名作为key,将那天所代表的网站的上线日作为offset参数,并将这个offset上的值设置为1.
-
举个例子:如果今天是网站上线的第100天,而用户Peter在今天阅览过网站,那么执行命令SETBIT Peter 100 1;如果命运Peter也继续阅览网站,那么执行命令SETBIT Peter 101 1,以此类推
-
当要计算Peter总共以来的上线次数时,就是用BITCOUNT命令:执行BITCOUNT Peter,得出的结果就是Peter上线的总天数
-
网站运行10年,占用的空间也只是每个用户10*365比特位(bit),也即是每个用户456字节,对于这种大小的数据来说,BITCOUNT 的处理速度就像GET和INCR操作一样快
4.6 redis数据类型 - 哈希类型
-
定义:
- 由field和关联的value组成的键值对(key:{field:value})
- field和value都必须是字符串类型
- 一个hash最多包含232-1个键值对
-
优点:
- 节约内存空间 - 特定条件下【1. field小于512个;2. value不能超过64字节】
- 可按需获取字段的值
-
缺点(不适合hash的情况):
- 使用过期键功能:键过期功能只能对key进行过期操作,而不能对hash的field进行过期操作
- 存储消耗大于字符串结构
-
常用命令
命令 说明 返回值 HSET key field name
设置单个字段 HSETNX key field name
设置单个字段,key中的field不存在才能成功添加 HMSET key field1 value1 field2 value2
设置多个字段 HLEN key
查看当前key的字段个数 返回key的字段数 HEXISTS key field
判断字段是否存在 不存在返回0 HGET key field
查询字段的value 返回当前字段的value,不存在则返回nil HMGET key field field
查询多个字段的value 返回每个字段的value HGETALL key
查询所有的键值对 返回所有的键值对 HKEYS key
查询所有字段名 返回所有字段名 HVALS key
查询所有的value 返回所有的value HDEL key field
删除指定字段 HINCRBY key field increment
对字段值进行整数增量运算 HINCRBYFLOAT key field increment
对字段进行浮点数增量运算 -
Python操作hash
- hset(name,key,value) - 更新一条数据的属性,没有则新建
- hget(name,key.value) - 读取这条数据的指定属性,返回字符串类型
- hmset(name,mapping) - 批量更新数据(没有则新建)属性,参数为字典
- hmget(name,keys) - 批量读取数据(没有则新建)属性
- hgetall(name) - 获取这条数据的所有属性和对应的值,返回字典类型
- hkeys(name) - 获取这条数据的所有属性名,返回列表类型
- hdel(name,*keys) - 删除这条数据的指定属性
-
应用场景
- 用户维度数据统计
- 原理:基于hash压缩特点,和字段可计数
- 例:统计数包括:关注数、粉丝数、喜欢商品数、发帖数;用户为key,不同维度为field,value为统计数;比如关注了5人:
HSET user:1000 fans 5
;HINCRBY user:10000 fans 1
- 缓存 - redis + mysql + hash组合使用
- 原理:hash可以按需取出字段数据,也比较适合做缓存
- 示例:用户想要查询个人信息:1.到redis缓存中查询个人信息,2.redis中查询不到,到mysql查询,并缓存到redis,3.再次查询个人信息
- 用户维度数据统计
-
示例
- 新建django项目rmsite1,数据库rmsite1,应用user;创建模型类User,字段 username【用户名】,desc【个人描述】;个人信息页 - 显示指定用户的用户名和个人描述,URL:/user/info/【优先走缓存,缓存过期时间自定义】;个人信息页更新,可进行当前个人描述更改,个人信息页走缓存,用户个人信息更新后,缓存清空
#file:settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'rmysite', 'HOST': '127.0.0.1', 'PORT': '3306', 'USER': 'root', 'PASSWORD': 'mysql', } }
#file:rmysite1/urls.py from django.contrib import admin from django.urls import path,include urlpatterns = [ path('admin/', admin.site.urls), path('user/', include('user.urls')), ]
#file:user/urls.py from django.urls import path from user import views urlpatterns = [ path('info/<int:p>',views.UserInfo ), path('update',views.UserUpdate ), ]
#file:views.py import redis from django.http import HttpResponse from django.shortcuts import render from user.models import User r = redis.Redis(host='127.0.0.1',port='6379',db=0,password='123456') def UserInfo(request,p): #先查缓存 user_id = ('user_%s'%p) if r.exists(user_id): users = r.hgetall(user_id) users_dicts = {k.decode():v.decode for k,v in users.items()} user_name = users_dicts['username'] user_desc = users_dicts['user_desc'] get_way = 'redis' return render(request,'userinfo.html',locals()) #缓存中不存在,查询数据库 try: users = User.objects.get(id = p) user_name = users.username user_desc = users.desc r.hmset(user_id,{'username':user_name,'user_desc':user_desc}) r.expire(user_id,60) get_way = 'mysql' return render(request, 'userinfo.html', locals()) except Exception as e: print('----get user error is %s'%e) return HttpResponse('----no user----') def UserUpdate(request): if request.method == 'GET': return render(request,'userupdate.html') else: username = request.POST.get('username') desc = request.POST.get('desc') #将数据存到MySQL try: user = User.objects.get(username=username) except Exception as e: print('---- update get user error %s'%e) return HttpResponse('--no user--') user.desc = desc user.save() #删除缓存 user_id = ('user_%s' % user.id) r.delete(user_id) return HttpResponse('----update is ok----')
#file:userinfo.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>个人信息页</title> </head> <body> <p> 查询方法为:{{ get_way }} </p> <p> 用户名: {{ user_name }} </p> <p> 个人描述: {{ user_desc }} </p> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>用户更新</title> </head> <body> <form action="/user/update" method="post"> <p>用户名<input type="text" name="username"></p> <p>个人描述<input type="text" name="desc"></p> <input type="submit" value="更新"> </form> </body> </html>
-
哈希结构底层介绍
-
Python中的字典与集合底层机构几乎相同
-
Python新建一个空字典后,Python会自动初始化一个长度为8的数组,键值对是存储在数组中对应的具体索引位置上;当执行
d['a'] = 'bcd'
后,Python会根据键来选择一个哈希函数,来计算哈希值,并类似于对8取余来计算出这个键值对应该存储的位置,将计算出的hash值、key、value一起存储在数组中对应索引的位置上;取值时先对key计算哈希值,再将哈希值对8取余来计算座位号,再查看这个座位号上是否有值 -
哈希碰撞:哈希函数计算哈希值的时候,存在一定的概率使得不同的两个key计算出的哈希值相同;另外也存在哈希值对8取余后余数相同的情况;这两种情况都称之为哈希碰撞(输入不同,但是计算出的座位号相同)
-
Python处理哈希碰撞:开放地址法 -> 根据当前位置增加一个偏移量重新计算哈希值
-
扩容:当已使用的座位数超过数组总数的2/3时,数组就需要进行扩容,一般采用4倍方式进行扩容,当数组长度超过5万,扩容采用2倍方式进行扩容
-
座位重排(rehash):当数组进行扩容后,数组中存放的所有键值对都要进行位置重排,重新计算哈希值;所以Python的字典是无序的
-
开放地址法存在的问题:假设键值对a与键值对b计算出的座位号相同,Python字典在插入键值对b时会采用开放地址法重新计算b的座位号,如果键值对a被删除,那么在查找键值对b的时候,Python计算出来的座位号在原来键值对a所在的位置上,但是键值对a座位上现在并没有元素;为解决这种问题,Python会在哈希碰撞的位置上进行标记,当哈西碰撞这个位置上的键值对被删除时,Python会标记该位置发生过哈希碰撞(Python在处理哈希碰撞时,保留了整个元素的行为轨迹),当查找键值对b时,Python会先去键值对a的位置上去找,由于键值对a的位置被标记过,所以Python会继续寻找下一个位置(保持探测链)
-
redis处理哈希碰撞:单链法 -> 当发生哈希碰撞时,redis会在发生哈希碰撞的座位的垂直方向再开辟一个存储空间(链表),将碰撞后的键值对存放在新开辟的座位上
-
redis处理扩容 :redis会将当前已使用的座位总数除以数组的长度,当计算出的值大于5,redis肯定进行扩容,当计算出的值大于1,redis会判定当前是否是较为空闲的状态,如果是较为空闲则进行扩容;
-
redis扩容方式:第一个大于used2的2的n次方,例如,当前使用的座位数为2,22=4,第一个2n大于4->n为3,扩容23
-
redis rehash:redis准备了两个座位表,大部分数据位于旧的座位表上,每一次调用某个key的时候,尝试迁移一个数据,逐步将旧座位表上的数据迁移到新表上,扩容后的查找会先查找旧表上的数据,再查询新表上的数据
4.7 redis数据类型 - 集合类型
-
基础概念:无序、去重;元素是字符串类型;最多包含232-1个元素
-
常用命令
命令 说明 返回值 SADD key member1 member2
增加一个或者多个元素,自动去重 返回成功插入到集合的元素个数 SMEMBERS key
查看集合中的所有元素 返回集合中的所有元素 SREM key member1 member2
删除一个或者多个元素,元素不存在则自动忽略 返回删除元素的个数 SISMEMBER key member
判断元素是否在集合中 返回1(存在),0(不存在) SRANDMEMBER key [count]
随机返回集合中指定个数的元素,默认为1 返回随机取出的元素 SPOP key [count]
弹出count个元素 返回成功弹出的元素 SCARD key
查询集合中元素的个数 返回集合中元素的个数 SMOVE source destination member
将集合source中的元素member移动到集合destination中,
如果destination不存在则会初始化一个集合返回成功移动元素的个数 SDIFF key1 key2
对集合key1 与集合key2 求差集 返回key1-key2的元素(如:number1 1 2 3 number2 1 2 4 结果为3) SDIFFSTORE destination key1 key2
将差集保存到另一个集合destination中 返回成功保存的元素的个数 SINTER key1 key2
对集合key1 与集合key2 求交集 返回key1与key2共有的元素(如:number1 1 2 3 number2 1 2 4 结果为1 2) SINTERSTORE destination key1 key2
将交集保存到另一个集合destination中 返回成功保存的元素的个数 SUNION key1 key2
对集合key1 与集合key2 求并集 返回key1+key2的元素(如:number1 1 2 3 number2 1 2 4 结果为1 2 3 4) SUNIONSTORE destination key1 key2
将并集保存到另一个集合destination中,并去重 返回成功保存的元素的个数 -
应用场景
- 社交类平台,共同好友 - 交集
- 纯随机类抽奖
- 防止元素重复
- 黑/白名单
-
底层结构:当存入集合的元素全部是整型时,集合的底层结构为类似于列表类型(intset),从小到大进行有序排列;当集合的元素含有字符串时,集合的底层结构就是hashtable
4.7 redis数据类型 - 有序集合
-
基础概念:有序、去重;元素是字符串类型;每个元素都关联着一个浮点数分值(score),并按照分值从小到大的顺序排列集合中的元素(分值可以相同);最多包含232-1个元素
-
常用命令
命令 说明 返回值 zadd key score member
在有序集合中添加一个成员 返回成功插入到集合中的元素个数 zrange key start stop [withscores]
以升序查看指定区间的元素 返回start到stop之间的元素,如果添加了withscores参数,还会返回score zrevrange key start stop [withscores]
以降序查看指定区间的元素 返回start到stop之间的元素,如果添加了withscores参数,还会返回score zscore key memeber
查看指定元素的分值 返回指定元素的分值 zrank key member
以升序查看元素的排名,排名序号从0开始 返回元素的排名 zrevrank key member
以降序查看元素的排名,排名序号从0开始 返回元素的排名 zrangebyscore key min max [withscores] [limit offset count]
查询指定区间元素,min/max:最小值/最大值区间,默认为闭区间;当使用 (min/(max
时,可开启开区间;offset:跳过多少个元素,count:返回元素个数返回指定区间元素 zrem key member
删除成员 zincrby key increment member
增加或者减少分值increment 返回增加或者减少后的分值 zremrangebyscore key min max
删除指定区间内的元素,(默认闭区间,可做开区间) 返回删除元素的个数 zcard key
查询集合中元素个数 返回集合中元素个数 zcount key min max
查询指定区间内的元素个数(默认闭区间,可做开区间) 返回指定区间元素的个数 zunionstore destination numkeys key [weights 权重值] [aggregate sum|min|max]
将合并后的元素存放到新集合destination中,numberkeys需要取并集的集合数量,weights后的权重值按集合的顺序,分别给到每个集合,算完权重之后再执行聚合aggregate 返回求并集后的集合元素数量 zinterstore destination numkeys key1 key2 weights weight aggregate [sum|min|max]
将取交集并后的元素存放到新集合destination中,numberkeys需要取并集的集合数量,weights后的权重值按集合的顺序,分别给到每个集合,算完权重之后再执行聚合aggregate 返回求交集后的集合元素数量 -
应用场景:排行榜
-
redis高级功能
- 事务
- 概念:逻辑上对数据的一组操作,这组操作要么一次全部成功,要么这组操作全部失败;是不可分割的一个工作单位
- 特性(ACID):
- 原子性(Atomicity):事务中所有操作是不可再分隔的原子单位,事务中所有操作要么全部执行成功,要么全部执行失败
- 一致性(Consistency):是事务对数据完整性约束的遵循,这些约束可能包括主键约束、外键约束或是一些用户自定义约束,事务执行的前后都是合法的数据状态,不会违背任何的数据完整性
- 隔离性(Isolation):事务与事务之间互不打扰
- 持久性(Durability):一个事务一旦成功提交,它对数据库的改变必须是永久的,即便数据库发生故障也应该不会对其产生任何影响
- redis的事务:redis是弱事务型的数据库,并不具备ACID全部特性;redis具备的特性:
- redis具备隔离性:事务中的所有命令会被序列化、按顺序执行,在执行的过程中不会被其他客户端发来的命令打断(单进程单线程)
- redis不保证原子性:redis中的一个事务如果存在命令执行失败,那么其他命令依然会被执行,没有回滚机制
- redis事务命令
multi
开启事务,类似于MySQL中的begin;当执行了MULTI之后,接下来的所有命令都会被redis放到一组事务里- 开启事务后,redis不会立即执行接下来的命令语句,redis会在服务里面开辟一块
- 事务
5 redis高级功能
5.1 事务
- 概念:逻辑上对数据的一组操作,这组操作要么一次全部成功,要么这组操作全部失败;是不可分割的一个工作单位
- 特性(ACID):
- 原子性(Atomicity):事务中所有操作是不可再分隔的原子单位,事务中所有操作要么全部执行成功,要么全部执行失败
- 一致性(Consistency):是事务对数据完整性约束的遵循,这些约束可能包括主键约束、外键约束或是一些用户自定义约束,事务执行的前后都是合法的数据状态,不会违背任何的数据完整性
- 隔离性(Isolation):事务与事务之间互不打扰
- 持久性(Durability):一个事务一旦成功提交,它对数据库的改变必须是永久的,即便数据库发生故障也应该不会对其产生任何影响
- redis的事务:redis是弱事务型的数据库,并不具备ACID全部特性;redis具备的特性:
- redis具备隔离性:事务中的所有命令会被序列化、按顺序执行,在执行的过程中不会被其他客户端发来的命令打断(单进程单线程)
- redis不保证原子性:redis中的一个事务如果存在命令执行失败,那么其他命令依然会被执行,没有回滚机制
- redis事务命令
multi
开启事务,类似于MySQL中的begin;当执行了MULTI之后,接下来的所有命令都会被redis放到一组事务里- 开启事务后,redis不会立即执行接下来的命令语句,redis会在服务里面开辟一块区域,将接收到的命令暂存于这个区域中,当终端输入EXEC命令后,redis开始按顺序执行事务中的命令
- DISCARD:取消事务,将事务中的命令全部清空,不会执行事务中的命令
- 事务特殊情况:
- 命令语法错误:命令入队失败,直接自动discard退出这个事务,这个命令在执行调用之前会发生错误,例如这个命令可能有语法错误:错误的参数数量、错误的命令名;
- 类型操作错误:命令语法没错,但类型操作有误,则事务执行调用之后失败,无法进行事务的回滚;当我们执行了一个由于错误的value的key操作会出现该现象(例如:对着string类型的value执行了list命令操作);redis对于发生在exec之后的错误是没有特殊方式去处理的,即使某些命令在事务中失败,其他命令还是会被执行
- Python使用redis事务:
- pipeline 流水线技术:批量执行redis命令,减少io通信次数
- 原理:效仿redis的事务,客户端将多个命令打包,一次通信发给redis,可明显降低redis服务的请求数
- 注意:此技术为客户端技术;如果一组命令中,一个命令需要上一个命令的执行结果才可以执行,则无法使用该技术
- 连接池pool:将已经创建好的连接保存在池中,接到请求时,直接使用已经创建好的连接,对数据库进行访问;减少重复建连的过程,用完后的连接会放回连接池中。
import redis #创建连接池并连接到redis pool = redis.ConnectionPool(host = '127.0.0.1',db = 0,port = 6379) r = redis.Redis(connection_pool = pool) pipe = r.pipeline() #创建一个流水线对象 pipe.set('fans',50) #命令语句 pipe.incr('fans') pipe.incrby('fans',100) pipe.execute() #执行命令语句,并返回执行后的结果
- Python操作redis事务:需要依赖于流水线技术
with r.pipeline(transaction = True) as pipe: #transaction :开启事务 pipe.multi() pipe.incr('fans') pipe.incr('fans') values = pipe.execute()
- watch - 乐观锁
- 概念:乐在事务过程中,使用乐观锁对指定key进行监听,命令提交时,若被监听的key对应的值未被修改,事务方可提交成功,否则提交失败,是一种解决资源竞争的方式,注意:必须在监听后开启事务
5.2 数据持久化 - RDB
- redis是内存型数据库,断电易失,顾需要将数据转移到磁盘上进行持久化存储
- redis提供的持久化方案:
- RDB(Redis Database) 默认开启:
- 保存真实的数据
- 将服务器包含的所有数据库数据以二进制文件的形式保存到硬盘中
- 默认文件名:/var/lib/redis/dump.rdb
- 文件名及目录可在配置文件中进行修改[
/etc/redis/redis.conf
]- 存放路径修改修改:约在263行:
dir /var/lib/redis
表示rdb文件存放路径 - 文件名修改:约在253行:
dbfilename dump.rdb
表示文件名
- 存放路径修改修改:约在263行:
- 触发RDB - redis终端
- 方式一:redis终端中使用SAVE;执行SAVE命令过程中,redis服务器将被阻塞,无法处理客户端发送的命令请求,在SAVE命令执行完毕后,服务器才会重新开始处理客户端发送的命令请求;如果RDB文件已经存在,那么服务器将会自动使用新的RDB文件替换旧的RDB文件
- 方式二:BGSAVE:客户端发送BGSAVE给服务器,服务器马上返回Background saving started 给客户端,服务器fork()子进程来处理SAVE操作,而服务器继续提供服务,子进程创建完RDB文件后再告知Redis服务器;
- 可通过查看redis日志文件来查看redis的持久化过程;log位置:
/var/log/redis/redis-server.log
- 触发RDB - 设置配置文件
#redis配置文件默认 218行:save 900 1 219行:save 300 10 #表示如果距离上一次创建RDB文件已经过去了300秒,并且服务器的所有数据库总共已经发生了不少于10次修改,那么自动执行BGSAVE命令 220行:save 60 10000 #继上一次RDB创建后60秒,如果这之间数据库总共发生了不少于10000此修改,那么自动执行BGSAVE #只要上面的三个条件中的任意一个被满足时,服务器就会自动执行BGSAVE #每次创建RDB文件后,服务器为实现自动持久化而设置的时间计数器和次数计数器就会被清零,并重新开始计数,所以多个保存条件的效果不会叠加
- 触发RDB - redis关闭:redis在正常关闭(
sudo /etc/init.d/redis-server stop
)时,也会执行保存rdb操作;注意:异常关闭时,无法自动触发RDB操作 - RDB说明:创建RDB文件需要将服务器所有的数据库的数据都保存起来,这是一个非常消耗资源和时间的操作,所以服务器需要隔一段时间才创建一个新的RDB文件,也就是说,创建RDB文件不能执行的过于频繁,否则严重影响服务器性能;可能丢失数据(两次存储之间发生宕机就会发生数据丢失)
- RDB(Redis Database) 默认开启:
5.3 数据持久化 - AOF
- 基础概念:存储的时命令,而不是真实的数据;默认情况下不开启
- 开启方式(修改配置文件)
/etc/redis/redis.conf
672行:appendonly yes
#把no改为yesappendfilename "appendonly.aof"
#存储文件名:appendonly.aof
- 重启服务:
sudo /etc/init.d/redis-server restart
- 执行原理:每当有修改数据库的命令被执行时;因为AOF文件里面存储了服务器执行过的所有数据库修改的命令,所以给定一个AOF文件,服务器只要重新执行一遍AOF文件里面包含的所有命令,就可以达到还原数据库的目的;用户可以根据自己的需要对AOF持久化进行调整,让redis在遭遇意外停机时不丢失任何数据,或者只丢失一秒的数据,这比RDB持久化丢失的数据要少的多
- 特殊说明:虽然服务器每执行一个修改数据库的命令,就会把执行的命令写入到AOF文件,但这并不意味着AOF持久化文件不会丢失数据,在目前常见的操作系统中,执行系统调用write函数,将一些内容写入到某个文件里面时,为了提高效率,系统通常不会直接将内容写入硬盘里面,而是将内容放入一个内存缓存区(buffer) 里面,等到缓冲区被填满时才将存储在缓冲区里面的内容真正写入到硬盘里面
- AOF持久化:只有当一条命令真正的被写入到硬盘里面时,这条命令才不会因为停机而意外丢失
- AOF持久化在遭遇停机时丢失命令的数量,取决于命令被写入到硬盘的时间
- 越早将命令写入到硬盘,发生意外停机时丢失的数据就越少,反之亦然
#打开配置文件:/etc/redis/redis.conf 1、701行:always #服务器每写入一条命令,就将缓冲区里面的命令写入到硬盘里面,服务器就算意外停机,也不会丢失任何已经成功执行的命令数据 2、702行:everysec(#默认) #服务器每一秒将缓冲区里面的命令写入到硬盘里面,这种模式下,服务器即使遭遇意外停机,最多只丢失一秒的数据 3、703行:no #服务器不主动将命令写入硬盘,由操作系统决定何时将缓冲区里面的命令写入到硬盘里面,丢失命令数量不确定
- AOF重写:
- 为了让AOF文件大小控制在合理范围,避免胡乱增长,redis提供了AOF重写功能,通过这个功能,服务器可以产生一个新的AOF文件
- 新的AOF文件记录的数据库数据和原有的AOF文件记录的数据库数据完全一样
- 新的AOF文件会使用尽可能少的命令来记录数据库数据,因此新的AOF文件的体积通常会小很多
- AOF重写期间,服务器不会被阻塞,可以正常处理客户端发送的命令请求
- AOF 重写 - 触发
- 客户端向服务器发送
BGREWRITEAOF
命令 - 修改配置文件,让服务器自动执行BGREWRITEAOF命令
auto-aof-rewrite-percentage 100 #当AOF文件的增量大于100%时才进行重写 auto-aof-rewrite-min-size 64mb #当AOF文件最小达到64MB时进行重写,当AOF文件达到128MB时进行第二次重写,AOF文件达到256MB时执行第三次重写
-
AOF VS RDB
RDB持久化 AOF持久化 全量备份,一次保存整个数据库 增量备份,一次保存一个修改数据库的命令 保存的间隔时间较长 保存的间隔时间默认为一秒钟 数据还原速度快 数据还原速度一般,冗余命令多,还原速度慢 执行SAVE命令时会阻塞服务器,但手动,
或者自动触发的BGSAVE不会阻塞服务器无论平时还是进行AOF重写时,
都不会阻塞服务器
5.4 主从复制
-
高可用:是系统架构设计中必须考虑的因素之一,它通常是指通过设计减少系统不能提供服务的时间
-
目标:消除架构中的单点故障
-
redis单进程单线程的模式,如果redis进程挂掉,相关依赖的服务就难以正常服务
-
redis提供的高可用方案:主从搭建 + 哨兵
-
主从复制:
- 一个redis服务可以有多个该服务的复制品,这个redis服务成为master,其他复制品成为slaves
- master会一直将自己的数据更新同步给slaves,保持主从同步
- 只有master可以执行写命令,slaves只能执行读命令
- 作用:分担了读的压力(高并发),提高可用性
- 原理:从服务器端执行客户端发送的读命令,客户端可以连接slaves执行读请求,来降低master的读压力
-
实现方式:Linux命令行
- 命令:
redis-server-slaveof<master-ip><master-port>--masterauth<master password>
#前端启动 redis-server --port 6300 --slaveof 127.0.0.1 6379 #启动一个redis端口号为6300,设置为127.0.0.1:6379的slave #新起客户端 执行:redis-cli -p 6300 #可进入slave
- 命令:
-
实现方式:redis命令行
- 命令:1.
>slaveof ip port
#成为谁的从 ;2.>slaveof no one
#自封为王
- 命令:1.
-
实现方式:配置文件
#每个redis服务都有一个和它对应的配置文件 1. 6379 -> /etc/redis/redis.conf #6379的配置文件路径 2. 6300 -> /home/day04/redis_6300.conf #添加6300的配置文件 #修改配置文件 slaveof 127.0.0.1 6379 port 6300 #启动redis服务 redis-server redis_6300.conf #客户端连接测试 redis-cli -p 6300
5.5 哨兵
- 基础概念:
- sentinel会不断检查master和slaves是否正常
- 每一个sentinel可以监听任意多个master和该master下的slaves
-
原理:哨兵进程定期与redis主从进行通信,当哨兵认为redis住阵亡后【通信无返回】,自动将切换工作完成
-
安装哨兵&使用哨兵
#1.安装 redis-sentinel sudo apt install redis-sentinel #执行 sudo /etc/init.d/redis-sentinel stop 检查redis-sentinel是否安装成功 #2.新建配置文件sentinel.conf port 26379 sentinel monitor groupname 127.0.0.1 6379 1 #设置监听的master #groupname:给监听的主从起个小组名 #127.0.0.1 6379:监听的master #1:票数;哨兵通常起奇数个,防止某个哨兵宕机后不能进行监视小组,多个哨兵进行投票表决,确认master是否是真的宕机,当投票数大于等于设置的票数,则投票通过,切换新的master #3.启动sentinel #方式一:redis-sentinel sentinel.conf #方式二:redis-server sentinel.conf --sentinel #4.将master的服务停止,查看是否会提升为主 sudo /etc/init.d/redis-server stop #发现提升6381为master,其他两个为从 #在6381上设置新值,使用6380查看
-
配置文件解读
# sentinel 监听端口,默认是26379,可以修改 port 26379 #如有多个哨兵,直接添加端口号即可,只要监听小组相同,哨兵便可以通过master找到其他哨兵 # 告诉sentinel去监听地址为ip:port 的一个master,这里的master-name可以自定义,quorum是一个数字,指明当有多少个sentinel认为一个master失效时,master才算真正失效 sentinel monitor <master-name> <ip> <redis-port> <quorum> # 如果master有密码,则需要添加该配置 sentinel auth-pass <master-name> <password> #master多久失联才认为是不可用了,默认是30秒 sentinel down-after-milliseconds <master-name> <millseconds> #单位是毫秒
-
Python操作哨兵
from redis.sentinel import Sentinel #生成哨兵连接 sentinel = Sentinel([('localhost',26379)],socket_timeout=0.1) #每个哨兵的域名与端口必须放在一个元组里面,多个哨兵放在同一个列表里面,socket_timeout:客户端与服务连接的超时时间设置,大于0.1秒则认为是超时 #初始化master链接 master = sentinel.master_for('groupname',socket_timeout = 0.1,db=1) #获取当前小组的master连接 salve = sentinel.slave_for('groupname',socket_timeout = 0.1,db=1) #获取当前小组中的slave连接 #使用redis相关命令 master.set('mymaster','yes') print(slave.get('mymaster'))