概念
关系型数据库/sql: mysql,oracle
非关系型数据库/nosql: redis(存在内存中),mongodb(存在硬盘上)
1.redis是以key-value形式存储
2.redis支持持久化
redis用途
1.做缓存
2.存储session
3.游戏排行榜
4.对速度要求比较高的数据存储
5.做消息队列
redis 五大数据类型
1. 字符串
格式:set [key] [value]
string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
string类型是Redis最基本的数据类型,一个键最大能存储512MB。
2. 列表
redis列表是简单的字符串列表,按照插入顺序排序,你可以添加一个元素列表的头部(左边)或者尾部(右边)
格式:lpush [name] [value] # 在key对应list的尾部添加字符串元素
格式:rpush [name] [value] # 在key对应list的头部添加字符串元素
格式:lrem [name] [index] # key对应list中删除count个和value相同的元素
格式:llen [name] # 返回对应list长度
3. 哈希(字典)
格式: hmset [name] [key1] [value1] [key2] [value2]
Redis hash 是一个键值(key=>value)对集合。
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
4. 集合(set)
格式:sadd [name] [value]
redis的Set是string类型的无序集合
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都为O(1)
5. 有序集合(zset)
格式: zadd name score value
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。
什么是Redis持久化?Redis有哪几种持久化方式?优缺点是什么?
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失
Redis 提供了两种持久化方式:RDB(默认) 和AOF
RDB(Redis DataBase):
功能核心函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数
AOF(Append-only file):
每当执行服务器(定时)任务或者函数时flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作
aof写入保存:
WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件
SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。
比较:
1、aof文件比rdb更新频率高,优先使用aof还原数据。
2、aof比rdb更安全也更大
3、rdb性能比aof好
4、如果两个都配了优先加载AOF
redis有哪些架构,以及特点
单机版
特点:简单
缺点:1、内存容量有限 2、处理能力有限 3、无法高可用。
主从复制
特点:
1、master/slave 角色
2、master/slave 数据相同
3、降低 master 读压力在转交从库
问题:
1、无法保证高可用
2、没有解决 master 写的压力
Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。 只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同。
哨兵
特点:
1、保证高可用
2、监控各个节点
3、自动故障迁移
缺点:
主从模式,切换需要时间丢数据
没有解决 master 写的压力
Redis sentinel 是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时自动进行故障转移。其中三个特性:
监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应 用程序发送通知。
自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。
集群(proxy 型):
特点:
1、无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。
2、数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。
3、可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除。
4、高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本
5、实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave到 Master 的角色 提升。
缺点:
1、资源隔离性较差,容易出现相互影响的情况。
2、数据通过异步复制,不保证数据的强一致性
从redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
redis常用命令
Keys [pattern] # 查看keys是否存在,Keys * 表示查看所有key
Exists [key] # 查看key是否存在,有返回1,无返回0
Set [key] [value] # 设置键值或修改已有键的值
Get [key] # 获取单个值
Setnx [key] [value] # 有则不变,无则设置
Setex [key] [seconds] [value] # 添加并设置过期时间
Expire [key] [seconds] # 设置过期时间
TTL [key] # 查看过期时间
Mset [key] [value] [key] [value]... # 设置多个键值
Getset [key] [value] # 修改为新值并返回旧值
Mget [key] [key]... # 获取多个值
Incr [key] # 使key的值自增1(注意:值必须为整数),若不存在则设置,并返回增后的值
incrby [key] [increment] # 同Incr,可指定自增的值
Decr [key] # 使key的值自减1(注意:值必须为整数),若不存在则设置,并返回减后的值
Decrby [key] [decrement] # 同Decr,可指定自减的值
Append [key] [value] # 在已有的值后追加字符串,没有则设置
Strlen [key] # 返回值的长度
move [key] [id] # 将 key及value移至指定库
persist [key] # 取消过期时间
Select [id] # 选择数据库0-15
randomkey # 随机返回一个kay
Rename [key] # 重命名key
Type [key] # 返回数据类型
怎么使用redis分布式锁
先拿setnx来争抢锁,抢到之后,再用expire给锁加一个 过期时间,防止锁忘记释放
Redis做异步队列
一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。
缺点:在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。
什么是缓存穿透?如何避免?什么是缓存雪崩?何如避免?
缓存穿透
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意 的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
如何避免?
1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。
缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统 崩溃。
如何避免?
1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和 写缓存,其他线程等待。
2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长 期
3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
比较redis和Memcached
redis Memcached
数据类型 字符串,列表,字典,集合,有序集合 字符串
持久化 支持 不支持
线程线程 单线程/单进程,但速度非常快 速度较快
Redis与Mysql双写一致性方案解析
一、前言:一般用redis来缓存的流程为:
但对于更新完数据,先更新缓存,还是先删除缓存有很大争议,因此,进行以下讨论
二、一致性方案
1.先更新数据库,再更新缓存
2.先删除缓存,再更新数据库
3.先更新数据库,再删除缓存
三、先更新数据库,再更新缓存
普遍反对,有两种原因:
1.(线程安全角度)同时请求A和请求B进行更新操作,可能会出现
1、A更新了数据
2、B更新了数据
3、线程B更新了缓存
4、线程A更新了缓存
*此时缓存中是A的数据
2.业务场景:
*若业务需求是更新远远大于读取,而每次都要更新缓存,则造成很大的性能浪费
*若写入的数据不是直接写入缓存,而是要经过一些计算再写入,则也会造成性能浪费。显然,删除缓存更为合适
四、先删除缓存,再跟新数据库
也会导致不一致的原因:
-假设线程A进行更新操作,B进行查询操作
1、A要更新数据,先删除缓存
2、B查询时发现没有缓存
3、B去数据库拿到旧值,并写入缓存
4、A将新数据写入数据库
*此时缓存中依然是旧数据
-解决方案(延时双删策略):
1.先删除缓存
2.写入数据库
3.休息1s,再删除缓存
问题1:1s的时间怎么确定?
根据具体的业务情况而定
问题2:采用了mysql的读写分离架构怎么办?
1、请求A进行写操作,删除缓存
2、请求A将数据写入数据库了,
3、请求B查询缓存发现,缓存没有值
4、请求B去从库查询,这时,还没有完成主从同步,因此查询到的是旧值
5、请求B将旧值写入缓存
数据库完成主从同步,从库变为新值上述情形,就是数据不一致的原因。解决方案依然是延时双删,在1s后再加 几百ms,等待主从同步完成
问题3:第二次删除如果失败怎么办?
在下面方案中具体阐述...
五、先更新数据库,再删缓存
常用的是这种,虽然理论上还是有点问题:
-假设线程A进行查询,线程B进行更新
1.缓存刚好失效
2.请求A进行查询,查到一个旧值
3.请求B进行更新数据库
4.请求B进行删除缓存
5.请求A将取到的旧值添加到缓存,造成脏数据
-讨论
这种情况发生的情况几乎不存在,因为查询的速度要远大于写入的速度,因此在2进行后不可能会等3,4执行完了,才执行5。那如果删除缓存失败了怎么办?
为了解决删除缓存失败的问题,可用下面两种方案:
一、如图
二、如图
备注说明:上述的订阅binlog程序在mysql中有现成的中间件叫canal,可以完成订阅binlog日志的功能。另外,重试机制,可采用的是消息队列的方式。如果对一致性要求不是很高,直接在程序中另起一个线程,每隔一段时间去重试即可,这些大家可以灵活自由发挥,只是提供一个思路。
python操作redis
1.简单使用
import redis
conn = redis.Redis(host='localhost',port=6379) # 获取reis链接
conn.set('age','18') # 存入数据(reids里存的都是byte格式数据)
name = conn.get('name') # 获取数据
print(name)
2. redis连接池
1.新建一个模块conn_pool.py
import redis
POOL=redis.ConnectionPool(host='localhost',port=6379,max_connections=1000)
2.使用连接池
from conn_poll import POOL
conn=redis.Redis(connection_pool=POOL)
print(conn.get('name'))
3.需要重点掌握的字符串操作
get set mget mset incr decr append