Redis介绍
特点及优点
1、开源的,使用C编写,基于内存且支持持久化2、高性能的Key-Value的NoSQL数据库3、支持数据类型丰富,字符串strings,散列hashes,列表lists,集合sets,有序集合sorted sets 等等4、支持多种编程语言(C C++ Python Java PHP ... )5、单进程单线程
与其他数据库对比
1、MySQL : 关系型数据库,表格,基于磁盘,慢2、MongoDB:键值对文档型数据库,值为类似JSON文档,数据结构相对单一3、Redis的诞生是为了解决什么问题?? # 解决硬盘IO带来的性能瓶颈
应用场景
1,缓存2,并发计数 - F() - 点赞3,排行榜4,生产者消费者模型...
redis版本
1、最新版本:5.02、常用版本:2.4、2.6、2.8、3.0(里程碑)、3.2、3.4、4.0(教学环境版本)、5.0
Redis附加功能
1、持久化 将内存中数据保存到磁盘中,保证数据安全,方便进行数据备份和恢复2、过期键功能 为键设置一个过期时间,让它在指定时间内自动删除 <节省内存空间> # 音乐播放器,日播放排名,过期自动删除3、事务功能 原子的执行多个操作4、主从复制5、Sentinel哨兵
安装
windows系统下的redis安装以及Redis卸载
进入需要安装的redis目录下面执行:
redis-server --service-install redis.windows.conf --service-name redis6379
卸载
redis-server --service-uninstall --service-name redis6379
或者sc delete redis6379
启动
net start redis6379
关闭
net stop redis6379
使用redis
1.启动redis服务
2.redis-cli -p 6379连接端口号为6379的redis
3.输入set k1 v1,就是往redis数据库中存放了一个键为k1值为v1的数据
4.输入 get k1即可获得数据库中保存的k1对应的值
5.退出当前客户端:shutdown(退出Redis),quit(返回)
6;关闭redis服务:net stop redis
7:客户端默认不显示中文redis-cli --raw -p 6379
Ubuntu
# 安装sudo apt-get install redis-server# 服务端启动sudo /etc/init.d/redis-server status | start | stop | restart# 客户端连接redis-cli -h IP地址 -p 6379 -a 密码
配置文件详解
配置文件所在路径
/etc/redis/redis.confmysql的配置文件/etc/mysql/mysql.conf.d/mysqld.cnf
设置连接密码
1、requirepass 密码2、重启服务 sudo /etc/init.d/redis-server restart3、客户端连接 redis-cli -h 127.0.0.1 -p 6379 -a 123456 127.0.0.1:6379>ping
允许远程连接
1、注释掉本地IP地址绑定 69行: # bind 127.0.0.1 ::12、关闭保护模式(把yes改为no) 88行: protected-mode no3、重启服务 sudo /etc/init.d/redis-server restart
通用命令 ==适用于所有数据类型==
# 切换库(number的值在0-15之间,db0 ~ db15)select number# 查看键keys 表达式 # keys * #正式环境中 慎重使用# 数据类型type key# 键是否存在exists key# 删除键del key# 键重命名rename key newkey# 清除当前库中所有数据(慎用)flushdb# 清除所有库中所有数据(慎用)flushall
数据类型
字符串类型(string)
特点
1、字符串、数字,都会转为字符串来存储2、以二进制的方式存储在内存中
字符串常用命令-==必须掌握==
# 1. 设置一个key-valueset key value# 2. 获取key的值get key# 3. key不存在时再进行设置(nx)set key value nx # not exists# 4. 设置过期时间(ex)set key value ex seconds# 5. 同时设置多个key-valuemset key1 value1 key2 value2 key3 value3# 6. 同时获取多个key-valuemget key1 key2 key3
字符串常用命令-==作为了解==
# 1.获取长度strlen key# 2.获取指定范围切片内容 [包含start stop]getrange key start stop# 3.从索引值开始,value替换原内容setrange key index value
数值操作-==字符串类型数字(必须掌握)==
# 整数操作incrby key 步长127.0.0.1:6379> set num 1
OK
127.0.0.1:6379> incrby num 1
(integer) 2decrby key 步长incr key : +1操作decr key : -1操作# 应用场景: 抖音上有人关注你了,是不是可以用INCR呢,如果取消关注了是不是可以用DECR# 浮点数操作: 自动先转为数字类型,然后再进行相加减,不能使用appendincrbyfloat key step
string命令汇总
# 字符串操作1、set key value2、set key value nx3、get key3、mset key1 value1 key2 value24、mget key1 key2 key35、set key value nx ex seconds6、strlen key # 返回旧值并设置新值(如果键不存在,就创建并赋值)7、getset key value# 数字操作7、incrby key 步长8、decrby key 步长9、incr key10、decr key11、incrbyfloat key number#(可为正数或负数)# 设置过期时间的两种方式# 方式一1、set key value ex 3# 方式二1、set key value2、expire key 5 # 秒3、pexpire key 5 # 毫秒# 查看存活时间ttl key> 0 剩余的存活时间-1 key存在 没有过期时间-2 key不存在# 删除过期persist key
string数据类型注意
# key命名规范可采用 - wang:email# key命名原则1、key值不宜过长,消耗内存,且在数据中查找这类键值的计算成本高2、不宜过短,可读性较差# 值1、一个字符串类型的值最多能存储512M内容
业务场景
借助过期时间,存放验证码;到期后,自动消亡
说明:通过redis单进程单线程的特点,由redis负责计数,并发问题转为串行问题
将mysql中的数据存储到redis字符串类型中
缓存 cache.set(key, value)
并发计数 - 点赞/秒杀
带有效期的验证码 - 短信验证码
练习
1、查看 db0 库中所有的键 # select 0# keys * [慎重,redis单进程单线程,如果key数量过大,会引起redis阻塞]2、设置键 trill:username 对应的值为 user001,并查看# set trill:username user0013、获取 trill:username 值的长度# strlen trill:username4、一次性设置 trill:password 、trill:gender、trill:fansnumber 并查看(值自定义)#mset trill:password 123 trill:gender M trill:fansnumber 205、查看键 trill:score 是否存在#exists trill:score 6、增加10个粉丝 #incrby trill:fansnumber 10 7、增加2个粉丝(一个一个加)#incr trill:fansnumber#incr trill:fansnumber8、有3个粉丝取消关注你了#decrby trill:fansnumber9、又有1个粉丝取消关注你了10、思考、思考、思考...,清除当前库#flushdb11、一万个思考之后,清除所有库 #flushall
列表数据类型(List)
特点
1、元素是字符串类型2、列表头尾增删快,中间增删慢,增删元素是常态3、元素可重复4、最多可包含2^32 -1个元素5、索引同python列表
列表常用命令
# 增1、从列表头部压入元素 LPUSH key value1 value2 返回:list长度2、从列表尾部压入元素 RPUSH key value1 value2 返回:list长度3、从列表src尾部弹出1个元素,压入到列表dst的头部 RPOPLPUSH src dst 返回:被弹出的元素4、在列表指定元素后/前插入元素 LINSERT key after|before value newvalue 返回: 1,如果命令执行成功,返回列表的长度 2,如果没有找到 pivot ,返回 -1 3,如果 key 不存在或为空列表,返回 0 # 查5、查看列表中元素 LRANGE key start stop # 查看列表中所有元素: LRANGE key 0 -16、获取列表长度 LLEN key# 删7、从列表头部弹出1个元素 LPOP key8、从列表尾部弹出1个元素 RPOP key9、列表头部,阻塞弹出,列表为空时阻塞 BLPOP key timeout10、列表尾部,阻塞弹出,列表为空时阻塞 BRPOP key timeout # 关于BLPOP 和 BRPOP 1、如果弹出的列表不存在或者为空,就会阻塞 2、超时时间设置为0,就是永久阻塞,直到有数据可以弹出 3、如果多个客户端阻塞再同一个列表上,使用First In First Service原则,先到先服务11、删除指定元素 LREM key count value -3 acount>0:表示从头部开始向表尾搜索,移除与value相等的元素,数量为count count<0:表示从尾部开始向表头搜索,移除与value相等的元素,数量为count count=0:移除表中所有与value相等的值 返回:被移除元素的数量 12、保留指定范围内的元素 LTRIM key start stop 返回:ok 样例: LTRIM mylist1 0 2 # 只保留前3条 # 应用场景: 保存微博评论最后500条 LTRIM weibo:comments 0 499# 改13、将列表 key 下标为 index 的元素的值设置为 value LSET key index newvalue
练习
1、查看所有的键#keys *2、向列表 spider:urls 中以RPUSH放入如下几个元素:01_baidu.com、02_taobao.com、03_sina.com、04_jd.com、05_xxx.com# rpush spider:urls 01_baidu.com 3、查看列表中所有元素# lrange spider:urls 0 -14、查看列表长度# llen spider:urls5、将列表中01_baidu.com 改为 01_tmall.com#lset spider:urls 0 01_tmall.com6、在列表中04_jd.com之后再加1个元素 02_taobao.com#linsert spider:urls after 04_jd.com 02_taobao.com7、弹出列表中的最后一个元素# rpop spider:urls8、删除列表中所有的 02_taobao.com# lrem spider:urls 0 02_taobao.com9、剔除列表中的其他元素,只剩前3条 # ltrim spider:urls 0 2
python交互redis
模块(redis)
Ubuntu
检查 pip3 freeze|grep 'redis'安装 sudo pip3 install redis
使用流程
import redis# 创建数据库连接对象r = redis.Redis(host='127.0.0.1',port=6379,db=0,password='123456')
import redis
r = redis.Redis(host='127.0.0.1', port=6379, db=0)
key_list = r.keys("*")for key in key_list:print(key.decode())print(r.exists('num'))
r.lpush('p1','tom','jack')print(r.lrange('p1',0,-1))
通用命令代码示例
import redisr = redis.Redis(host='127.0.0.1', port=6379, db=3, password='123456')# key_list = r.keys('*')# for key in key_list:# print(key.decode())#print(r.exists('k2'))
python操作list
# r.lpush('pyl1', 'Tom', 'Jack', 'Lili')# #[b'Lili', b'Jack', b'Tom']# print(r.lrange('pyl1', 0, -1))#b'Tom'#print(r.rpop('pyl1'))
list案例: 一个进程负责生产任务,一个进程负责消费任务
进程1: 生产者
import redis
r = redis.Redis(host='127.0.0.1', port=6379, db=0)# 任务类型(email)_发件人_收件人_内容task_str = '%s_%s_%s_%s'%('email','lilei','hanmeimei',':)')
r.lpush('pypc',task_str)print(task_str)
进程2: 消费者
import redis
r = redis.Redis(host='127.0.0.1', port=6379, db=0)while True:
task = r.brpop('pypc',10)# (b'pypc', b'email_lilei_hanmeimei_:)')
# print(task)if task:
task_str = task[1].decode()# print(task_str)task_list = task_str.split('_')# ['email', 'lilei', 'hanmeimei', ':)']print(task_list)
python操作string
#####string#######
import redis
r = redis.Redis(host='127.0.0.1', port=6379, db=0)
r.mset({'py1': 'Ma', 'py2': "Ding"})print(r.mget('py1','py2'))
# 数值操作# 返回值 int
# 10print(r.incrby('pys6', 10))# 返回值 字节串# b'10'print(r.get('pys6'))
位图操作bitmap强势点
定义
1、位图不是真正的数据类型,它是定义在字符串类型中2、一个字符串类型的值最多能存储512M字节的内容,位上限:2^32# 1MB = 1024KB# 1KB = 1024Byte(字节)# 1Byte = 8bit(位) 1 1 ... 6月1日 6月2日 ...
强势点
可以实时的进行统计,极其节省空间。官方在模拟1亿2千8百万用户的模拟环境下,在一台MacBookPro上,典型的统计如“日用户数”的时间消耗小于50ms, 占用16MB内存
SETBIT 命令
说明:设置某位置上的二进制值
语法:SETBIT key offset value
参数:offset - 偏移量 从0开始
value - 0或者1
示例:
# 默认扩展位以0填充127.0.0.1:6379> SET mykey abOK127.0.0.1:6379> GET mykey"ab"127.0.0.1:6379> SETBIT mykey 0 1(integer) 0127.0.0.1:6379> GET mykey"\xe1b"127.0.0.1:6379>
setbit 不存在的key 索引 值
在索引位之前 用 0 填充,按最低字节数补齐【0】
9 1 00000000 01000000
GETBIT 命令
说明:获取某一位上的值
语法:GETBIT key offset
示例:
127.0.0.1:6379> GETBIT mykey 3(integer) 0127.0.0.1:6379> GETBIT mykey 0(integer) 1127.0.0.1:6379>
BITCOUNT 命令
说明:统计键所对应的值中有多少个 1
语法:BITCOUNT key start end
参数:start/end 代表的是 字节索引
示例:
127.0.0.1:6379> SET mykey1 abOK127.0.0.1:6379[4]> BITCOUNT mykey(integer) 6127.0.0.1:6379[4]> BITCOUNT mykey 0 0(integer) 3
应用场景案例
# 网站用户的上线次数统计(寻找活跃用户) 用户名为key,上线的天作为offset,上线设置为1# 示例 用户名为 user1:login 的用户,今年第1天上线,第30天上线 SETBIT user1:login 0 1 SETBIT user1:login 29 1 BITCOUNT user1:login
代码实现
r.setbit('user1:login', 0, 1)r.bitcount('user1:login')
Hash散列数据类型
定义
1、由field和关联的value组成的键值对 redis_key {field:value,field1:value1}2、field和value是字符串类型3、一个hash中最多包含2^32-1个键值对
优点
1、节约内存空间 - 特定条件下 【1,字段小于512个,2:value不能超过64字节】 hash: hk1 {'username':'guoxiaonao', 'age': '18'} string: hk1_username: 'guoxiaonao' hk1_age : '18' string: hk1 : 'guoxiaonao_18_性别_xxxxx'2、可按需获取字段的值
缺点(不适合hash情况)
1,使用过期键功能:键过期功能只能对键进行过期操作,而不能对散列的字段进行过期操作2,存储消耗大于字符串结构
基本命令操作
# 1、设置单个字段HSET key field valueHSETNX key field value# 2、设置多个字段HMSET key field value field value# 3、返回字段个数HLEN key# 4、判断字段是否存在(不存在返回0)HEXISTS key field# 5、返回字段值HGET key field# 6、返回多个字段值HMGET key field filed# 7、返回所有的键值对HGETALL key# 8、返回所有字段名HKEYS key# 9、返回所有值HVALS key# 10、删除指定字段HDEL key field # 11、在字段对应值上进行整数增量运算HINCRBY key filed increment# 12、在字段对应值上进行浮点数增量运算HINCRBYFLOAT key field increment
python操作hash
# 1、更新一条数据的属性,没有则新建hset(name, key, value) # 2、读取这条数据的指定属性, 返回字符串类型hget(name, key)# 3、批量更新数据(没有则新建)属性,参数为字典hmset(name, mapping)# 4、批量读取数据(没有则新建)属性hmget(name, keys)# 5、获取这条数据的所有属性和对应的值,返回字典类型hgetall(name)# 6、获取这条数据的所有属性名,返回列表类型hkeys(name)# 7、删除这条数据的指定属性hdel(name, *keys)
Python代码hash散列
import redis
r = redis.Redis(host='127.0.0.1', port=6379, db=0)
r.hset('p6', 'uname','MACHEN')
r.hset('p6', 'age',18)# {b'uname': b'MACHEN', b'age': b'18'}print(r.hgetall('p6'))
r.hmset('p2', {'uname':'MaChen', 'age':18})# {b'uname': b'MaChen', b'age': b'18'}print(r.hgetall('p2'))# [b'uname', b'age']print(r.hkeys('p6'))# [b'MaChen', b'18']print(r.hvals('p2'))
应用场景:用户维度数据统计
用户维度统计 统计数包括:关注数、粉丝数、喜欢商品数、发帖数 用户为key,不同维度为field,value为统计数 比如关注了5人 HSET user:10000 fans 5 HINCRBY user:10000 fans 1
应用场景: 缓存 - redis+mysql+hash组合使用
mysql user表 username age desc sign
redis - user_guoxiaonao {desc:xxxx,sign:xxxx}
1, 查看用户个人信息 - 加缓存 hash - /user/detail/用户id - GET
查看username 和 age
return username is %s age is %s
2,用户更新个人信息 - GET 查询字符串 /user/update/1?age=33
数据库更新 - 删除缓存
提示 : redis连接对象 直接 在视图函数中初始化即可
视图函数中 全局变量 r = redis.Redis.......
原理
用户想要查询个人信息1、到redis缓存中查询个人信息2、redis中查询不到,到mysql查询,并缓存到redis3、再次查询个人信息
代码实现
代码实现
创建Django项目
django-admin startproject mysite1
创建应用
python manage.py startapp user
创建数据库
create database mysite1 default charset utf8;
stttings.py配置
注册应用
INSTALLED_APPS = [...,'user']
MySQL配置
DATABASES = {'default': {'ENGINE': 'django.db.backends.mysql','NAME': 'mysite1', # 数据库名称,需要自己定义'USER': 'root','PASSWORD': 'root', # 管理员密码'HOST': '127.0.0.1','PORT': 3306,}
}
models.py
from django.db import models# Create your models here.class User(models.Model):
username = models.CharField('用户名',max_length=11)
age = models.IntegerField('年龄')
数据库生成或更新迁移文件:
python3 manage.py makemigrations
执行迁移脚本程序:
python3 manage.py migrate
mysit1->urls.py
from django.contrib import adminfrom django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),path('user/',include('user.urls'))
]
user->urls.py
from django.urls import pathfrom . import views
urlpatterns = [
path('detail/',views.detail_view)
path('update/',views.update_view)
]
创建用户
C:\Users\mysite1>python manage.py shell
In [1]: from user.models import User
user = User.objects.create(username='MaChen',age=18)
user->views.py
from django.http import HttpResponseimport redisfrom .models import User# Create your views here.r = redis.Redis(host='127.0.0.1', port=6379, db=0)def detail_view(request, user_id):
cache_key = 'user:%s' % (user_id)if r.exists(cache_key):
data = r.hgetall(cache_key)
new_data = {k.decode(): v.decode() for k, v in data.items()}
username = new_data['username']
age = new_data['age']
html = 'username is %s age is %s' % (username, age)return HttpResponse(html)
user = User.objects.get(id=user_id)
age = user.age
username = user.username
html = 'username is %s age is %s' % (username, age)
r.hmset(cache_key, {'username': username, 'age': age})
r.exists(cache_key, 30)return HttpResponse(html)def update_view(request, user_id):
age = request.GET.get('age', 0)
user = User.objects.get(id=user_id)
user.age = age
user.save()
cache_key = 'user:%s'%(user_id)
r.delete(cache_key)return HttpResponse('updata is ok')
集合数据类型(set)
特点
1、无序、去重2、元素是字符串类型3、最多包含2^32-1个元素
基本命令
# 1、增加一个或者多个元素,自动去重;返回值为成功插入到集合的元素个数SADD key member1 member2# 2、查看集合中所有元素SMEMBERS key# 3、删除一个或者多个元素,元素不存在自动忽略SREM key member1 member2# 4、元素是否存在SISMEMBER key member# 5、随机返回集合中指定个数的元素,默认为1个SRANDMEMBER key [count]# 6、弹出成员SPOP key [count]# 7、返回集合中元素的个数,不会遍历整个集合,只是存储在键当中了SCARD key# 8、把元素从源集合移动到目标集合SMOVE source destination member# 9、差集(number1 1 2 3 number2 1 2 4 结果为3)SDIFF key1 key2 # 10、差集保存到另一个集合中SDIFFSTORE destination key1 key2# 11、交集SINTER key1 key2SINTERSTORE destination key1 key2# 11、并集SUNION key1 key2SUNIONSTORE destination key1 key2
案例: 新浪微博的共同关注
# 需求: 当用户访问另一个用户的时候,会显示出两个用户共同关注过哪些相同的用户# 设计: 将每个用户关注的用户放在集合中,求交集即可# 实现: user001 = {'peiqi','qiaozhi','danni'} user002 = {'peiqi','qiaozhi','lingyang'} user001和user002的共同关注为: SINTER user001 user002 结果为: {'peiqi','qiaozhi'}
python操作set
import redis
r = redis.Redis(host='127.0.0.1', port=6379, db=0)
r.sadd('pys1', 'Jack', 'Tom')# {b'Jack', b'Tom'}print(r.smembers('pys1'))# b'Tom'print(r.spop('pys1'))
r.sadd('pys2', 'a', 'b')
r.sadd('pys3', 'b', 'c')
r.sadd('pys4', 'b', 'c', 'd')# {b'b'}print(r.sinter('pys2', 'pys3', 'pys4'))# 4print(r.sunionstore('pys5', 'pys2', 'pys3', 'pys4'))# {b'b', b'd', b'a', b'c'}print(r.smembers('pys5'))
有序集合sortedset
特点
1、有序、去重2、元素是字符串类型3、每个元素都关联着一个浮点数分值(score),并按照分值从小到大的顺序排列集合中的元素(分值可以相同)4、最多包含2^32-1元素
示例
一个保存了水果价格的有序集合
分值 | 2.0 | 4.0 | 6.0 | 8.0 | 10.0 |
---|---|---|---|---|---|
元素 | 西瓜 | 葡萄 | 芒果 | 香蕉 | 苹果 |
一个保存了员工薪水的有序集合
分值 | 6000 | 8000 | 10000 | 12000 | |
---|---|---|---|---|---|
元素 | lucy | tom | jim | jack |
一个保存了正在阅读某些技术书的人数
分值 | 300 | 400 | 555 | 666 | 777 |
---|---|---|---|---|---|
元素 | 核心编程 | 阿凡提 | 本拉登 | 阿姆斯特朗 | 比尔盖茨 |
有序集合常用命令
# 在有序集合中添加一个成员 返回值为 成功插入到集合中的元素个数zadd key score member# 查看指定区间元素(升序)zrange key start stop [withscores]# 查看指定区间元素(降序)zrevrange key start stop [withscores]# 查看指定元素的分值zscore key member# 返回指定区间元素# offset : 跳过多少个元素# count : 返回几个# 小括号 : 开区间 zrangebyscore fruits (2.0 8.0zrangebyscore key min max [withscores] [limit offset count]# 每页显示10个成员,显示第5页的成员信息: # limit 40 10 (m-1)*n n# MySQL: 每页显示10条记录,显示第5页的记录# limit 40,10# limit 2,3 显示: 第3 4 5条记录# 删除成员zrem key member# 增加或者减少分值zincrby key increment member# 返回元素排名zrank key member# 返回元素逆序排名zrevrank key member# 删除指定区间内的元素zremrangebyscore key min max# 返回集合中元素个数zcard key# 返回指定范围中元素的个数zcount key min maxzcount salary 6000 8000 zcount salary (6000 8000# 6000zcount salary (6000 (8000#6000# 并集zunionstore destination numkeys key [weights 权重值] [AGGREGATE SUM|MIN|MAX]# zunionstore salary3 2 salary salary2 weights 1 0.5 AGGREGATE MAX# 2代表集合数量,weights之后 权重1给salary,权重0.5给salary2集合,算完权重之后执行聚合AGGREGATE # 交集:和并集类似,只取相同的元素zinterstore destination numkeys key1 key2 weights weight AGGREGATE SUM(默认)|MIN|MAX
python操作sorted set
import redis
r = redis.Redis(host='127.0.0.1', port=6379, db=0)
r.zadd('pyz1',{'tom':6000, 'jim':3000, 'jack':12000})#[(b'jim', 3000.0), (b'tom', 6000.0), (b'jack', 12000.0)]print(r.zrange('pyz1', 0, -1, withscores=True))print(r.zrangebyscore('pyz1',3000, 12000,start=1, num=2))print(r.zrank('pyz1','jim'))#[(b'jim', 3000.0), (b'tom', 6000.0), (b'jack', 12000.0)]print(r.zcount('pyz1', '(6000', 12000))#r.zadd('pyz2', {'jim':8000,'gxn':10000})r.zinterstore('pyz3', ('pyz1','pyz2'), aggregate='max')print(r.zrange('pyz1', 0, -1, withscores=True))print(r.zrange('pyz2', 0, -1, withscores=True))print(r.zrange('pyz3', 0, -1, withscores=True))
编写一个小说网站, 阅读量排行榜 功能; 要求如下:
1) 小说详情页 topic_detail() - 小说详情页 topic/detail/7
return 字符串 '文章id topic detail '
2) topic_index() - 小说网站首页
文章排行榜打印 [阅读量前十文章 id 和 阅读量]
return ‘这是小说网站首页’
path('topic/detail/', views.topic_detail),
path('topic/index', views.topic_index)
def topic_detail(request, topic_id):#小说详情页 - 读小说cache_key = 'topic:read'if r.zrank(cache_key, topic_id) is None:#排行榜中没有该文章idr.zadd(cache_key,{topic_id:1})else:
r.zincrby(cache_key, 1, topic_id)return HttpResponse('--topic detail')def topic_index(request):#小说网站首页all_topic = r.zrevrange('topic:read', 0, 9)for n, t_id in enumerate(all_topic):
rank = n + 1topic_id = t_id.decode()print('rank:%s id:%s'%(rank, topic_id))return HttpResponse('--topic index')
五大数据类型及应用场景
类型 | 特点 | 使用场景 |
---|---|---|
string | 简单key-value类型,value可为字符串和数字 | 常规计数(微博数, 粉丝数等功能);缓存 ; ‘desc_sign_...’ |
hash | 是一个string类型的field和value的映射表,hash特别适合用于存储对象 | 统计类的数据 - 存储部分可能需要变更的数据(比如用户信息);缓存 - {'desc':desc , 'sign':xxx} |
list | 有序可重复列表 | 消息/任务队列等 -> 生产者消费者模型的中间层 |
set | 无序不可重复列表 | 存储并计算关系(如微博,关注人或粉丝存放在集合,可通过交集、并集、差集等操作实现如共同关注、共同喜好等功能) |
sorted set | 每个元素带有分值的集合 | 各种排行榜 |
传统关系型数据库 - 事务
定义: 程序中 一系列的逻辑操作 【如 sql语句】,所有操作必须全部完成,或者 全部 不完成;不能出现中间状态
转账 :小明 转账 200元 -> 小红
1)扣除小明200元
2)将扣除的200元 增加 至 小红的账户
事务四大特性 ACID
原子性(Atomicity) : 事务中的操作,要么全部执行成功,要么全都不执行
一致性(Consistency): 事务执行始得数据从一个状态转换为另一个状态,最终前后完整性【数据总量不变,数据唯一性保持不变等】保持一致
隔离性 (Isolation): 多个并发事务访问时, 彼此之间互不干扰
持久性(Durability): 事务完成后,对于数据的改变 是永久
事务
特点
1. 单独的隔离操作:事务中的所有命令会被序列化、按顺序执行,在执行的过程中不会被其他客户端发送来的命令打断2. 不保证原子性:redis中的一个事务中如果存在命令执行失败,那么其他命令依然会被执行,没有回滚机制
事务命令
1、MULTI # 开启事务 mysql begin2、命令1 # 执行命令 3、命令2 ... ...4、EXEC # 提交到数据库执行 mysql commit4、DISCARD # 取消事务 mysql 'rollback'
使用步骤
# 开启事务127.0.0.1:6379> MULTIOK# 命令1入队列127.0.0.1:6379> INCR n1QUEUED# 命令2入队列127.0.0.1:6379> INCR n2QUEUED# 提交到数据库执行127.0.0.1:6379> EXEC1) (integer) 12) (integer) 1
事务中命令错误处理
# 1、命令语法错误,命令入队失败,直接自动discard退出这个事务 这个在命令在执行调用之前会发生错误。例如,这个命令可能有语法错误(错误的参数数量,错误的命令名) 处理方案:语法错误则自动执行discard案例:127.0.0.1:6379[7]> MULTIOK127.0.0.1:6379[7]> get aQUEUED127.0.0.1:6379[7]> getsss a(error) ERR unknown command 'getsss'127.0.0.1:6379[7]> 127.0.0.1:6379[7]> 127.0.0.1:6379[7]> EXEC(error) EXECABORT Transaction discarded because of previous errors.# 2、命令语法没错,但类型操作有误,则事务执行调用之后失败,无法进行事务回滚 我们执行了一个由于错误的value的key操作(例如对着String类型的value施行了List命令操作) 处理方案:发生在EXEC之后的是没有特殊方式去处理的:即使某些命令在事务中失败,其他命令都将会被执行。案例127.0.0.1:6379> MULTIOK127.0.0.1:6379> set num 10QUEUED127.0.0.1:6379> LPOP numQUEUED127.0.0.1:6379> exec1) OK2) (error) WRONGTYPE Operation against a key holding the wrong kind of value127.0.0.1:6379> get num"10"127.0.0.1:6379>
pipeline 流水线
定义:批量执行redis命令,减少通信io
注意:此为客户端技术
示例
import redis# 创建连接池并连接到redispool = redis.ConnectionPool(host='127.0.0.1', db=0, port=6379)
r = redis.Redis(connection_pool=pool)
pipe = r.pipeline()
t1
pipe.set('fans', 50)
pipe.incr('fans')
pipe.incrby('fans', 100)
pipe.execute()
t2# 流水线中的命令结果 只有在执行 execute()后才能拿到;需根据业务评估,当前业务是否可用流水线
性能对比
# 创建连接池并连接到redispool = redis.ConnectionPool(host = '127.0.0.1',db=0,port=6379)
r = redis.Redis(connection_pool=pool)def withpipeline(r):
p = r.pipeline()for i in range(1000):
key = 'test1' + str(i)
value = i+1p.set(key, value)
p.execute()def withoutpipeline(r):for i in range(1000):
key = 'test2' + str(i)
value = i+1r.set(key, value)
if __name__ == '__main__':
t1 = time.time()
withoutpipeline(r)
t2 = time.time()
print("time is %s" % (t2 - t1))
if __name__ == '__main__':
t1 = time.time()
withpipeline(r)
t2 = time.time()
print("time is %s" % (t2 - t1))
python 操作 redis事务
with r.pipeline(transaction=true) as pipe pipe.multi() pipe.incr("books") pipe.incr("books") values = pipe.execute()
watch - 乐观锁
作用: 事务过程中,可对指定key进行监听,命令提交时,若被监听key对应的值未被修改时,事务方可提交成功,否则失败
终端1> watch 怪兽id OK> multiOK> incr booksQUEUED> exec # 事务执行失败 现在的怪兽id 版本号 是否没变(nil)终端2watch之后,再开一个终端进入redis > incr 怪兽id # 修改book值(integer) 1
python操作watch
#同时对一个账户进行操作, 当前余额 * 2信用卡提额 - 500元 1000元 2000元 当前额度 * 2 初始 卡 额度 500我跟老王 共用信用卡 题额我跟老王同时打电话 提额最终结果 2000 元我 翻倍 500 * 2 10:00 500额度 10:05 500*2王 翻倍 1000 * 2 10:00 500额度 10:30 "500*2" 1000*2
import redisimport time
pool = redis.ConnectionPool(host='127.0.0.1', db=0, port=6379)
r = redis.Redis(connection_pool=pool)def double_account(user_id):# account_guoxiaonaokey = 'account_%s' % (user_id)with r.pipeline(transaction=True) as pipe:while True:try:
pipe.watch(key)
value = int(r.get(key))
value *= 2print('--new value is %s' % (value))print('--sleep start')
time.sleep(20)print('--sleep end --')
pipe.multi()
pipe.set(key, value)
pipe.execute()break
except redis.WatchError:print('---value changed')continue
return int(r.get(key))if __name__ == '__main__':
user_id = 'MaChen'print(double_account(user_id))
总结: redis - 事务 【1,命令能统一,按顺序执行,不被打断; 2,乐观锁】 pyredis - 流水线 - 客户端技术【仿照了redis事务实现的方式,在客户端集中存储了 所有的命令,统一打包发给redis数据库,从而降低客户端和服务器的网络IO次数】 pyredis 中 使用事务,需要结合流水线
数据持久化
持久化定义
将数据从掉电易失的内存放到永久存储的设备上
为什么需要持久化
因为所有的数据都在内存上,所以必须得持久化
RDB模式(默认开启)
1、保存真实的数据2、将服务器包含的所有数据库数据以二进制文件的形式保存到硬盘里面3、默认文件名 :/var/lib/redis/dump.rdb
创建rdb文件的两种方式
方式一:redis终端中使用SAVE或者BGSAVE命令
127.0.0.1:6379> SAVEOK# 特点1、执行SAVE命令过程中,redis服务器将被阻塞,无法处理客户端发送的命令请求,在SAVE命令执行完毕后,服务器才会重新开始处理客户端发送的命令请求2、如果RDB文件已经存在,那么服务器将自动使用新的RDB文件代替旧的RDB文件# 工作中定时持久化保存一个文件127.0.0.1:6379> BGSAVEBackground saving started# 执行过程如下1、客户端 发送 BGSAVE 给服务器2、服务器马上返回 Background saving started 给客户端3、服务器 fork() 子进程做这件事情4、服务器继续提供服务5、子进程创建完RDB文件后再告知Redis服务器# 配置文件相关/etc/redis/redis.conf263行: dir /var/lib/redis # 表示rdb文件存放路径253行: dbfilename dump.rdb # 文件名# 两个命令比较SAVE比BGSAVE快,因为需要创建子进程,消耗额外的内存# 补充:可以通过查看日志文件来查看redis都做了哪些操作# 日志文件:配置文件中搜索 logfilelogfile /var/log/redis/redis-server.log
方式二:设置配置文件条件满足时自动保存(使用最多)
# redis配置文件默认218行: save 900 1219行: save 300 10 表示如果距离上一次创建RDB文件已经过去了300秒,并且服务器的所有数据库总共已经发生了不少于10次修改,那么自动执行BGSAVE命令220行: save 60 10000 1、只要三个条件中的任意一个被满足时,服务器就会自动执行BGSAVE 2、每次创建RDB文件之后,服务器为实现自动持久化而设置的时间计数器和次数计数器就会被清零,并重新开始计数,所以多个保存条件的效果不会叠加 # 该配置项也可以在命令行执行 [不推荐] redis>save 60 10000
RDB缺点
1、创建RDB文件需要将服务器所有的数据库的数据都保存起来,这是一个非常消耗资源和时间的操作,所以服务器需要隔一段时间才创建一个新的RDB文件,也就是说,创建RDB文件不能执行的过于频繁,否则会严重影响服务器的性能2、可能丢失数据
AOF(AppendOnlyFile)
1、存储的是命令,而不是真实数据2、默认不开启# 开启方式(修改配置文件)1、/etc/redis/redis.conf 672行: appendonly yes # 把 no 改为 yes 676行: appendfilename "appendonly.aof"2、重启服务 sudo /etc/init.d/redis-server restart
AOF持久化原理及优点
# 原理 1、每当有修改数据库的命令被执行时, 2、因为AOF文件里面存储了服务器执行过的所有数据库修改的命令,所以给定一个AOF文件,服务器只要重新执行一遍AOF文件里面包含的所有命令,就可以达到还原数据库的目的# 优点 用户可以根据自己的需要对AOF持久化进行调整,让Redis在遭遇意外停机时不丢失任何数据,或者只丢失一秒钟的数据,这比RDB持久化丢失的数据要少的多
特殊说明
# 因为 虽然服务器执行一个修改数据库的命令,就会把执行的命令写入到AOF文件,但这并不意味着AOF文件持久化不会丢失任何数据,在目前常见的操作系统中,执行系统调用write函数,将一些内容写入到某个文件里面时,为了提高效率,系统通常不会直接将内容写入硬盘里面,而是将内容放入一个内存缓存区(buffer)里面,等到缓冲区被填满时才将存储在缓冲区里面的内容真正写入到硬盘里# 所以 1、AOF持久化:当一条命令真正的被写入到硬盘里面时,这条命令才不会因为停机而意外丢失 2、AOF持久化在遭遇停机时丢失命令的数量,取决于命令被写入到硬盘的时间 3、越早将命令写入到硬盘,发生意外停机时丢失的数据就越少,反之亦然
策略 - 配置文件
# 打开配置文件:/etc/redis/redis.conf,找到相关策略如下1、701行: alwarys 服务器每写入一条命令,就将缓冲区里面的命令写入到硬盘里面,服务器就算意外停机,也不会丢失任何已经成功执行的命令数据2、702行: everysec(# 默认) 服务器每一秒将缓冲区里面的命令写入到硬盘里面,这种模式下,服务器即使遭遇意外停机,最多只丢失1秒的数据3、703行: no 服务器不主动将命令写入硬盘,由操作系统决定何时将缓冲区里面的命令写入到硬盘里面,丢失命令数量不确定# 运行速度比较always:速度慢everysec和no都很快,默认值为everysec
AOF重写
思考:AOF文件中是否会产生很多的冗余命令?
为了让AOF文件的大小控制在合理范围,避免胡乱增长,redis提供了AOF重写功能,通过这个功能,服务器可以产生一个新的AOF文件 -- 新的AOF文件记录的数据库数据和原由的AOF文件记录的数据库数据完全一样 -- 新的AOF文件会使用尽可能少的命令来记录数据库数据,因此新的AOF文件的提及通常会小很多 -- AOF重写期间,服务器不会被阻塞,可以正常处理客户端发送的命令请求
示例
原有AOF文件 | 重写后的AOF文件 |
---|---|
select 0 | SELECT 0 |
sadd myset peiqi | SADD myset peiqi qiaozhi danni lingyang |
sadd myset qiaozhi | SET msg 'hello tarena' |
sadd myset danni | RPUSH mylist 2 3 5 |
sadd myset lingyang | |
INCR number | |
INCR number | |
DEL number | |
SET message 'hello world' | |
SET message 'hello tarena' | |
RPUSH mylist 1 2 3 | |
RPUSH mylist 5 | |
LPOP mylist |
AOF重写-触发
1、客户端向服务器发送BGREWRITEAOF命令 127.0.0.1:6379> BGREWRITEAOF Background append only file rewriting started2、修改配置文件让服务器自动执行BGREWRITEAOF命令 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb # 解释 1、只有当AOF文件的增量大于100%时才进行重写,也就是大一倍的时候才触发 # 第一次重写新增:64M # 第二次重写新增:128M # 第三次重写新增:256M(新增128M)
RDB和AOF持久化对比
RDB持久化 | AOF持久化 |
---|---|
全量备份,一次保存整个数据库 | 增量备份,一次保存一个修改数据库的命令 |
保存的间隔较长 | 保存的间隔默认为一秒钟 |
数据还原速度快 | 数据还原速度一般,冗余命令多,还原速度慢 |
执行SAVE命令时会阻塞服务器,但手动或者自动触发的BGSAVE不会阻塞服务器 | 无论是平时还是进行AOF重写时,都不会阻塞服务器 |
# 用redis用来存储真正数据,每一条都不能丢失,都要用always,有的做缓存,有的保存真数据,我可以开多个redis服务,不同业务使用不同的持久化,新浪每个服务器上有4个redis服务,整个业务中有上千个redis服务,分不同的业务,每个持久化的级别都是不一样的。
数据恢复(无需手动操作)
既有dump.rdb,又有appendonly.aof,恢复时找谁?先找appendonly.aof
配置文件常用配置总结
# 设置密码1、requirepass password# 开启远程连接2、bind 127.0.0.1 ::1 注释掉3、protected-mode no 把默认的 yes 改为 no# rdb持久化-默认配置4、dbfilename 'dump.rdb' 5、dir /var/lib/redis# rdb持久化-自动触发(条件)6、save 900 17、save 300 10 8、save 60 10000# aof持久化开启9、appendonly yes10、appendfilename 'appendonly.aof'# aof持久化策略11、appendfsync always12、appendfsync everysec # 默认13、appendfsync no# aof重写触发14、auto-aof-rewrite-percentage 10015、auto-aof-rewrite-min-size 64mb# 设置为从服务器16、salveof <master-ip> <master-port>
Redis相关文件存放路径
1、配置文件: /etc/redis/redis.conf2、备份文件: /var/lib/redis/*.rdb|*.aof3、日志文件: /var/log/redis/redis-server.log4、启动文件: /etc/init.d/redis-server# /etc/下存放配置文件# /etc/init.d/下存放服务启动文件
Redis主从复制
定义
1、一个Redis服务可以有多个该服务的复制品,这个Redis服务成为master,其他复制品成为slaves2、master会一直将自己的数据更新同步给slaves,保持主从同步3、只有master可以执行写命令,slave只能执行读命令
作用
分担了读的压力(高并发); 避免单点问题
原理
从服务器执行客户端发送的读命令,比如GET、LRANGE、SMEMMBERS、HGET、ZRANGE等等,客户端可以连接slaves执行读请求,来降低master的读压力
实现方式
方式一(Linux命令行实现)
redis-server --slaveof --masterauth
# 从服务端redis-server --port 6300 --slaveof 127.0.0.1 6379# 从客户端redis-cli -p 6300127.0.0.1:6300> keys * # 发现是复制了原6379端口的redis中数据127.0.0.1:6300> set mykey 123(error) READONLY You can't write against a read only slave.127.0.0.1:6300> # 从服务器只能读数据,不能写数据
方式二(Redis命令行实现)
# 两条命令1、>slaveof IP PORT2、>slaveof no one# 服务端启动redis-server --port 6301# 客户端连接@:~$ redis-cli -p 6301127.0.0.1:6301> keys *1) "myset"2) "mylist"127.0.0.1:6301> set mykey 123OK# 切换为从127.0.0.1:6301> slaveof 127.0.0.1 6379OK127.0.0.1:6301> set newkey 456(error) READONLY You can't write against a read only slave.127.0.0.1:6301> keys *1) "myset"2) "mylist" # 再切换为主127.0.0.1:6301> slaveof no oneOK127.0.0.1:6301> set name helloOK
#查看主/从
127.0.0.1:6301> info方式三(利用配置文件)
# 每个redis服务,都有1个和他对应的配置文件# 两个redis服务 1、6379 -> /etc/redis/redis.conf 2、6300 -> /home/tarena/redis_6300.conf# 修改配置文件vi redis_6300.confslaveof 127.0.0.1 6379port 6300# 启动redis服务redis-server redis_6300.conf# 客户端连接测试redis-cli -p 6300127.0.0.1:6300> hset user:1 username guods(error) READONLY You can't write against a read only slave.
问题:master挂了怎么办?
1、一个Master可以有多个Slaves2、Slave下线,只是读请求的处理性能下降3、Master下线,写请求无法执行4、其中一台Slave使用SLAVEOF no one命令成为Master,其他Slaves执行SLAVEOF命令指向这个新的Master,从它这里同步数据# 以上过程是手动的,能够实现自动,这就需要Sentinel哨兵,实现故障转移Failover操作
演示
1、启动端口6400redis,设置为6379的slave redis-server --port 6400 redis-cli -p 6400 redis>slaveof 127.0.0.1 63792、启动端口6401redis,设置为6379的slave redis-server --port 6401 redis-cli -p 6401 redis>slaveof 127.0.0.1 63793、关闭6379redis sudo /etc/init.d/redis-server stop4、把6400redis设置为master redis-cli -p 6400 redis>slaveof no one5、把6401的redis设置为6400redis的salve redis-cli -p 6401 redis>slaveof 127.0.0.1 6400# 这是手动操作,效率低,而且需要时间,有没有自动的???
Sentinel哨兵
Redis之哨兵 - sentinel
1、Sentinel会不断检查Master和Slaves是否正常2、每一个Sentinel可以监控任意多个Master和该Master下的Slaves
案例演示
1、环境搭建
# 共3个redis的服务1、启动6379的redis服务器 sudo /etc/init.d/redis-server start2、启动6380的redis服务器,设置为6379的从 redis-server --port 6380 tarena@tedu:~$ redis-cli -p 6380 127.0.0.1:6380> slaveof 127.0.0.1 6379 OK3、启动6381的redis服务器,设置为6379的从 redis-server --port 6381 tarena@tedu:~$ redis-cli -p 6381 127.0.0.1:6381> slaveof 127.0.0.1 6379
2、安装并搭建sentinel哨兵
# 1、安装redis-sentinelsudo apt install redis-sentinel验证: sudo /etc/init.d/redis-sentinel stop# 2、新建配置文件sentinel.conf
'1':哨兵数量port 26379sentinel monitor tedu 127.0.0.1 6379 1# 3、启动sentinel方式一: redis-sentinel sentinel.conf方式二: redis-server sentinel.conf --sentinel#4、将master的redis服务终止,查看从是否会提升为主sudo /etc/init.d/redis-server stop# 发现提升6381为master,其他两个为从# 在6381上设置新值,6380查看127.0.0.1:6381> set name teduOK# 启动6379,观察日志,发现变为了6381的从主从+哨兵基本就够用了
sentinel.conf解释
# sentinel监听端口,默认是26379,可以修改port 26379# 告诉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> <milliseconds>
python获取master