企业缓存产品介绍
Memcached
优点:高性能读写、单一数据类型、支持客户端式分布式集群、一致性hash、多核结构、多线程读写性能高
缺点:无持久化、节点故障可能出现缓存穿透、分布式需要客户端实现、跨机房数据同步困难、架构扩容复杂度高
Redis
优点:高性能读写、多数据类型支持、数据持久化、高可用架构、支持自定义虚拟内存、支持分布式分片集群、单线程读写性能极高
缺点:多线程读写较Memcached慢
结论
Memcached适合多用户访问,每个用户少量的rw
Redis适合,少用户访问,每个用户大量的rw
安装和部署
Redis下载地址,一般使用3.x版本
Redis持久化(内存数据保存到磁盘)
介绍
两种持久化方式:RDB、AOF
RDB持久化:
- 可以在制定的时间间隔内生成数据集的时间点快照(point-in-time snapshot),新快照会覆盖老快照
- 优点:速度快,适合做备份,主从复制也是基于RDB持久化功能实现的
- 缺点:会有数据丢失
AOF持久化(Append-only log file)
- 记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集
- AOF文件中的命令全部以Redis协议的格式来保存,新命令会被追加到文件的末尾
- 优点:可以最大程度保证数据不丢
- 缺点:日志记录量级比较大
持久化触发方式比较
save和bgsave的区别
- 共同点:都能实现RDB持久化功能
- 不同点:
- SAVE:前台使用,会阻塞redis正常写入,直到持久化完成
- BGSAVE:后台使用,开启子线程,异步的持久化功能,不会阻塞redis正常写入
配置持久化
RDB持久化配置:
$> vim ?/redis.conf
dir /data/...
dbfilename dump.rdb
save 900 1
save 300 10
save 60 10000
AOF持久化配置
$> vim ?/redis.conf
dir /data/...
dbfilename dump.rdb
save 900 1
save 300 10
save 60 10000
appendonly yes
appendfsync always
appendfsync everysec
Redis 持久化磁盘 IO 方式及其带来的问题
有 Redis 线上运维经验的人会发现 Redis 在物理内存使用比较多,但还没有超过实际物理内存总容量时就会发生不稳定甚至崩溃的问题,有人认为是基于快照方式持久化的 fork 系统调用造成内存占用加倍而导致的,这种观点是不准确的,因为 fork 调用的 copy-on-write 机制是基于操作系统页这个单位的,也就是只有有写入的脏页会被复制,但是一般你的系统不会在短时间内所有的页都发生了写入而导致复制,那是什么原因导致 Redis 崩溃呢?
答案是 Redis 的持久化使用了 Buffer IO 造成的,所谓 Buffer IO 是指 Redis 对持久化文件的写入和读取操作都会使用物理内存的 Page Cache,而大多数数据库系统会使用 Direct IO 来绕过这层 Page Cache 并自行维护一个数据的 Cache,而当 Redis 的持久化文件过大(尤其是快照文件),并对其进行读写时,磁盘文件中的数据都会被加载到物理内 存中作为操作系统对该文件的一层 Cache,而这层 Cache 的数据与 Redis 内存中管理的数据实际是重复存储的,虽然内核在物理内存紧张时会做 Page Cache 的剔除工作,但内核很可能认为某块 Page Cache 更重要,而让你的进程开始 Swap,这时你的系统就会开始出现不稳定或者崩溃了。我们的经验是当你的 Redis 物理内存使用超过内存总容量的 3/5 时就会开始比较危险了。
Redis数据类型(多数据类型支持)
介绍
Memcached:只有键值对的方式(key: value)
Redis:
- string: 字符串
- hash: 字典
- list: 列表
- set: 集合
- sortset: 有序集合
数据类型存储结构
数据类型 | 名字 | Key | Value |
---|---|---|---|
string | 字符串 | name | zhangsan |
hash | 字典 | stu_1 | id:101 name:zhangsan |
list | 列表 | [day10, day9..., day1] | |
set | 集合 | seta | (张三, 李四, ...)->有下标索引 |
sortset | 有续集合 | ssa | (张三, 李四, ...)->有下标索引,自排序 |
数据的应用场景
- string 字符串类型
- 特点:Key: Value
- 应用场景:会话缓存、计数器
- hash 字典类型
- 特点:Key: Value
- value也是键值对的形式,
stu_1|id:101 name:zhangsan
- value也是键值对的形式,
- 应用场景:数据库缓存
- 数据库缓存的手工模拟:
- MySQL中拼接
hmset
语句 - 将数据导入redis
- 检查数据
- MySQL中拼接
- 特点:Key: Value
- list 类表类型
- 特点:Key: Value
- value是一个反向列表:
[day10, day9..., day1]
- 是一个反向列表,每一个值都有自己的下标索引
- 实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销
- value是一个反向列表:
- 应用场景:微信朋友圈的即时消息展示
- 特点:Key: Value
- set 集合类型
- 特点:Key: Value
- 当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集
SINTER
、并集SUNION
、差集SDIFF
的操作
- 当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集
- 应用场景:好友系统(共同好友)
- 特点:Key: Value
- sortset/zset 有续集合
- 特点:Key: Value
- 应用场景:排行榜
Redis消息模式
介绍
一种架构设计理念,帮助解决在架构中,资源有效利用方面提供有效的协调。
redis的消息模式有两种:消息队列、发布订阅
发布订阅
publisher 发布者:PUBLISH
channel 频道
subscriber 订阅者:SUBSCRIBE
Redis 事务
Redis事务和MySQL事务的区别:
- Redis的事务是基于队列实现的,redis是乐观锁机制,仅实现了原子性的保证,属于弱事务支持
- MySQL的事务是基于事务日志和悲观锁机制、MVCC、ISOLASTION等机制一起保证,强事务支持
底层工作原理:
- Redis事务是执行语句通过队列挨个执行,最终通过
EXEC
命令传入内存中- Redis多个事务可以同时修改某一个值,这是没有关系的,因为只要没有执行
EXEC
命令,事务对值的修改就都没有写入到内存中。多个事务根据EXEC
命令执行的先后顺序,先后将自己队列中的操作写入内存中。
- Redis多个事务可以同时修改某一个值,这是没有关系的,因为只要没有执行
- MySQL事务执行过程分为:
为避免Redis中多个事务同时修改一个键值对的值,造成业务逻辑混乱,使用WATCH
命令:
WATCH
命令:
- 在事务开始前,监控事物中想要操作的键值对的名
- 如果在提交时,发现这个键值对被修改过,那么提交会失败
- 变相的保证了事务的隔离性、数据的一致性
Key的通用操作
命令 | 作用 | 示例 |
---|---|---|
KEYS |
查看已存在的键的名字 | KEYS *, KEYS a, KEYS a* |
TYPE |
返回键所存储值的类型 | TYPE a |
EXPIRE/PEXPIRE |
以秒/毫秒设定生存时间 | EXPIRE test 100 |
TTL/PTTL |
以秒/毫秒为单位返回生存时间 | TTL test |
PEPRSIST |
取消生存时间设置 | PEPRSIST test |
DEL |
删除一个key
|
DEL test |
EXISTS |
检查是否存在,0-不存在 | EXISTS test |
RENAME |
变更key 的名字 |
RENAME test a |
INFO |
查看数据库信息 |
info memory ->监控内存, info cpu ->监控cpu, info replication -监控主从 |
Client list |
查看正在连接的会话 | |
Client kill ip:port |
关闭连接 | |
CONFIG GET * |
查看数据库信息 | |
CONFIG RESETSTAT |
重修统计 | |
CONFIG GET/SET/REWRITE |
动态修改 | |
DBSIZE |
返回当前键值对数量 | |
FLUSHALL |
清空所有的键值对->全局 | |
SELECT |
进入库,默认0 | SELECT 0~15 |
FLUSHDB |
清空全部的键值对->库级别 | Redis一共有16个库,默认使用0 |
MONITOR |
监控实时指令 |
MONITOR >>/tmp/mom.log ->只监控成功的操作 |
SHUTDOWN |
关闭服务器 | redis-cli -a root shutdown |
Redis主从复制
原理
- 副本库通过
slaveof 10.0.0.51 6379
命令,连接主库,并发送SYNC
给主库 - 主库收到
SYNC
,会立即触发BGSAVE
,后台保存RDB
,发送给副本库 - 副本库接收后会应用
RDB
快照 - 主库会陆续将中间产生的新的操作,保存并发送给副本库
- 到此,主复制集就正常工作了
- 以后,主库只要发生新的操作,都会以命令传播的的形式自动发送给副本库
- 所有复制相关信息,从info信息中都可以查到,即使重启任何节点,它的主从关系依然都在
- 如果发生主从关系断开时,从库数据没有任何损坏,在下次重连之后,从库发送
PSYNC
给主库 - 主库只会将从库缺失部分的数据同步给从库应用,达到快速恢复主从的目的
主从数据一致性保证
min-slaves-to-write 1
min-slaves-max-lag 3
主库是否需要开启持久化
如果不开,有可能主库重启操作,造成所有主从数据丢失。
实操
- 创建多个节点
- 开启主从
- 查询主从状态
- 解除主从
redis-cli -p 6391 -a 123 SLAVEOF no one
Redis高可用:Sentinel
- 监控
- 自动选主,切换(
6381 slaveof no one
) - 2号从库(6382)指向新主库(6381)
- 应用透明
- 自动处理故障节点
Sentinel搭建过程
- 配置文件
> mkdir /data/26380
> cd /data/26380
> vim sentinel.conf
port 26380
dir "/data/26380"
sentinel monitor mymaster 127.0.0.1 6380 1 // 1 表示多sentinel情况下,判定阈值
sentinel down-after-milliseconds mymaster 5000 // 5000ms 之后没有响应,认为宕机
sentinel auth-pass mymaster 123
-
启动
redis-sentinel /data/26380/sentinel.conf &>/tmp/sentinel.log &
-
如果存在问题
- 重新准备1主2从环境
- kill掉sentinel进程
- 删除sentinel目录下的所有文件
- 重新搭建sentinel
停主库测试
启动源主库(6380),查看状态
Redis Cluster->Redis集群
介绍
- Redis数据存储形式是key:value形式
- 将全部的数据,均匀分布到slot中
- 一整套集群中,slot总共16384个,平均分配在各个分片节点上(一般为3组)
- slot编号:0~16383
高性能
- 在多分片节点中,将16384个槽位,均匀分布到多个分片节点中
- 存数据时,将key做crc16(key),然后对16384取模,得出槽位值(0~16383)
- 根据计算得出的槽位值,找到相应的分片节点的主节点,存储到相应的槽位上
- 如果客户端当时连接的节点不是将来要存储的分片节点,分片集群会将客户端连接切换到真正存储节点进行数据存储
高可用
- 在搭建集群时,会为每一个分片的主节点,对应一个从节点
- 实现slaveof的功能,同时当主节点down,实现类似于sentinel的自动failover功能
- 如果有多台物理机,最好将主从节点分布在不同主机上,防止宕机
自动转向
- redis集群每一个分片节点上会存储其余节点的分片信息
- 如果连接到一个分片节点,但是想要的信息不再这个节点上,则会根据存储的分片信息,重新连上正确的分片节点
Redis如何将每一个键值对分配到唯一的slot中
对于一个key:value
键值对:
root = crc16(key)%16384
0<=root<=16383
- root即为slot号码
如何应对hash碰撞?
分布式集群管理
添加分片节点
- 增加新的节点
- 添加主节点
- 转移slot(重新分片)
- 添加一个从节点
删除一个分片节点
删除master节点之前,首先使用reshard移除master的全部slot,然后再删除当前节点
Redis一些概念
缓存穿透
概念: 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:
- 采用布隆过滤器,使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接过滤掉
- 访问key未在DB查询到值,也将空值写进缓存,但可以设置较短过期时间
- 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截
缓存雪崩
概念: 大量的key设置了相同的过期时间,导致缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决方案: 可以给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。
缓存击穿
概念: 一个存在的key,在缓存使其的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。
解决方案:
- 在访问key之前,采用
SETNX(set if not exists)
来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key - 设置热点数据永远不过期