NoSQL 特点
- 方便扩展(数据之间无关系, 很好扩展)
- 大数据 高性能(Redis 一秒写8万次, 读11万次, NoSQL的缓存记录级, 是一种细粒度的缓存, 性能会比较高)
- 数据类型是多样性的(不需事先设计数据库!随取随用)
- 传统 RDBMS 和 NoSQL 区别
- 传统的 RDBMS
- 结构化组织
- SQL
- 数据和关系都存在单独的表中
- 操作语言,数据定义语言
- 严格的一致性
- 基础的事务
- NoSQL
- 不仅仅是数据
- 没有固定的查询语言
- 键值对, 列存储, 文档存储, 图形存储(社交关系)
- 最终一致性
- CAP 定理 和 BASE理论(异地多活)
- 高性能 高可用 高可扩展
- 传统的 RDBMS
- 3V + 3高: 海量,多样,实时; 高并发,高可拓,高性能
大公司业务模型
- 业务模型
- 商品的基本信息(名称,价格,商家信息): 关系型数据库 MySQL / Oracle
- 商品的描述、评论(文字比较多): 文档型数据库中 MongoDB
- 图片: 分布式文件系统(FastDFS); 淘宝自己的(TFS); Google自己的(GFS); Hadoop(HDFS);
- 商品关键字(搜索): solr elasticsearch; 淘宝自己的(ISearch)
- 商品热门的波段信息(秒杀): 内存数据库 Redis Tair Memache...
- 商品的交易, 外部的支付接口: 第三方应用
- 数据层解决方案: 统一数据服务层 UDSL(在网站应用集群和底层数据源之间,构建一层代理,统一数据层)
- 模型数据映射
- 实现业务模型各属性与底层不同类型数据源的模型数据映射
- 目前支持关系数据库,iSearch, redis, mongodb
- 统一的查询和更新API
- 提供了基于业务模型的统- -的查 询和更新的API,简化网站应用跨不同数据源的开发模式。
- 性能优化策略
- 字段延迟加载,按需返回设置
- 基于热点缓存平台的二级缓存。
- 异步并行的查询数据:异步并行加载模型中来自不同数据源的字段
- 并发保护:拒绝访问频率过高的主机IP或IP段
- 过滤高危的查询:例如会导致数据库崩溃的全表扫描
- 模型数据映射
NoSQL 四大分类
- KV 键值对
- 新浪: Redis
- 美团: Redis + Tair
- 阿里, 百度: Redis + memecache
- 文档型数据库(bson 格式和json 一样)
- MongoDB
- 是一个基于分布式文件存储的数据库(C++编写), 主要用来处理大量的文档
- 是一个介于关系型数据库和非关系型数据中
中间的产品, 功能最丰富, 最像关系型数据库的
- ConthDB
- MongoDB
- 列存储数据库
- HBase
- 分布式文件系统
- 图关系数据库(社交, 拓扑图): 不是存图形, 放的是关系(朋友圈社交网络, 广告推荐)
- Neo4j
- InfoGrid
Redis 入门
- 官网
- Redis: Remote Dictionary Server, 远程字典服务
- 会周期性的把更新的数据写入磁盘 或 把修改操作写入追加的记录文件, 并在些基础上实现master-slave(主从)同步
Redis 能干嘛
- 内存存储 持久化, 内存中是断电即失, 所以说持久化很重要(rdb、aof)
- 效率高,可以用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器(浏览量! )
特性
- 多样的数据类型
- 持久化
- 集群
- 事务
Redis 是单线程的
- 为什么使用单线程: Redis 是很快的, Redis 基于内存操作, CPU 不是 Redis 性能瓶颈. Redis 的瓶颈是根据机器的内存和网络带宽(需通过网络传输)决定的, 既然可用单线程来实现, 就使用单线程了
- Redis 是 C语言写的, 官方提供的数据为 100000+ 的QPS, 不比 Memecache 差
- 为什么单线程还这么快: Redis 是将所有的数据全部放在内存中的, 所以说使用单线程操作效率是最高的, 多线程(CPU上下文会切换, 耗时的操作), 对于内存来说, 若没有上下文效率就是最高的, 多次读写都是在一个 CPU 上的, 在内存情况下, 这个方案是最佳的方案
安装
- 默认端口号: 6379
- Windows 安装
- Window 在 Github 上下载(停更很久了, 不建议在 Window 上开发)
- Github 下载 > 解压到指定目录
- redis-server.exe : 启动服务
- redis-cli.ext : 启动客户端
- redis-check-aof.exe : 检查
aof持久化文件是否配置正确 - redis-benchmark.exe : 测试性能的
- Linux 安装
- 官网下载安装包:
wget http://download.redis.io/releases/redis-版本号.tar.gz - 移动到
/opt目录(安装包程序放在 /opt 下):mv redis-版本号.tar.gz /opt - 解压:
tar -zxvf redis-版本号.tar.gz - 进入解压后的文件:
cd redis-版本号> 可以看到配置文件redis.conf - 基本的环境安装:
yum install gcc-c++ # 安装 c++ 环境 gcc -v # 查看结果 make # 初始化需要的文件 make install # 安装需要的文件 - redis 的默认安装路径(所有的安装路径都一样):
/usr/local/bin- 能看到
redis-server和redis-cli
- 能看到
- 管理配置文件:
- 创建一个目录
mkdir iconfig - 将 Rddis 配置文件拷贝到此配置文件目录:
cp /opt/redis-版本号/redis.conf iconfig
- 创建一个目录
- Redis 默认不是后台启动, 修改配置文件
vim iconfig/redis.conf # 打开配置文件 bind 127.0.0.1 # 限制可访问的主机, 默认本机, 0.0.0.0 任意计算机 port 6379 # 默认端口号 databases 16 # 默认有多少个数据库 daemonize no # 是否以守护进行运行(后台进程) - 启动 Redis 服务(配置文件路径):
redis-server /usr/local/bin/iconfig/redis.conf - 启动 Redis 客户端(端口号 6379):
redis-cli -p 端口号
- 官网下载安装包:
性能测试:
reddis-benchmark 命令参数
基础知识
- 默认有16个(0~15)数据库, 默认使用第0个
- Redis 可用作
数据库,缓存,消息中间件MQ
五大基本数据类型
基本使用
ps -ef|grep redis # 查看进程是否开启
# 关闭 Redis 服务
shutdown # 关闭 Redis 服务
exit # 退出 Redis 客户端
select 数据库编号 # 切换数据库, 默认16个(0~15)
DBSIZE # 当前数据库大小
flushall # 清空`全部`数据数据
flushdb # 清空`当前`数据库
ping # 测试连接, 返回 PONG
set key1 val1
get key1
clear # 清屏
对 key 的操作
keys * # 查看所有的 key
exists key1 # 查看当前 key 是否存在: 1 存在; 0 不存在
hexists key1 field1 # 查看当前对象的 field1 是否存在: 1 存在; 0 不存在
move key1 1 # 移除 key: `1`当前数据库
expire key1 多少秒后过期 # 设置过期时间
ttl key1 # 查看 key 多久后过期: -2 已过期
type key1 # 当前 key 的类型: string number
String/Number
get key1 # 获取值
set key1 val1
setnx key1 val1 # 不存在才设置(set if not exist), 在分布式锁中常用: 1 成功; 0 失败
setex key1 过期时间秒 val1 # 设置值时, 设置过期时间(set with expire)
# ============== 字符串 ==============
append key1 "追加字符串"
strlen key1 # 字符串长度
getrange key1 开始下标 结束下标 # 截取范围(可用负数): 闭区间
setrange key1 开始下标 替换值 # 替换值, 替换的长度是替换值的长度
# ============== 数值 ==============
incr key1 步长 # 自增(只能是数字): 步长默认为1
decr key1 步长 # 自减
# ============== 批量操作 ==============
mset key1 val1 key2 val2 # 批量设置
mget key1 key2 # 批量获取
msetnx key1 val1 key2 val2 # 不存在才设置(mset if not exist), 在分布式锁中常用: 1 成功; 0 失败
# ============== 对象操作 ==============
set user:1 {name:chh,age:18} # 不能有空格: 只能通过 user:1 来获取
mset user:1:name chh user:1:age 18 # 只能通过 user:1:name 来获取
# ============== 组合命令 ==============
getset key1 val1 # 先获取值 再设置值
List
# ============== 添加 ==============
lpush key1 val1 # 左侧推入
rpush key1 val1 # 右侧推入
lset key1 下标 val1 # 设置指定下标的值(更新操作): 此下标必须存在
linsert key1 before/after "指定值" val1 # 在“指定值” 前/后 插入值
# ============== 移除 ==============
lpop key1 # 左侧弹出
rpop key1 # 右侧弹出
rpoplpush key1 key2 # 从key1右侧弹出; 并key2左侧推入
lrem key1 移除个数 移除的值 # 移除指定值(精确匹配)
ltrim key1 开始下标 结束下标 # 切换开始下标前 & 结束下标后面的
# ============== 获取 ==============
llen key # 获取长度
lrange key1 0 -1 # 截取范围: 闭区间
lindex key1 下标 # 根据下标获取值
Set 集合
sadd key1 val1 # 新增
srem key1 val1 # 移除
spop key1 # 随机移除成员
srandmember key1 个数 # 随机返回`指定个数`成员(默认1个): s_rand_member
smove key1 key2 val1 # 移动: 将 val1 从 key1 中, 移动到 key2 中
smembers key1 # 获取所有成员: member(成员)
sismember key1 val1 # 是否存在此成员: 1 存在; 0 不存在
scard key1 # 获取元素个数
sdiff key1 key2 # 差集
sinter key1 key2 # 交集 intersection
sunion key1 key2 # 并集 union
Hash(Map 集合)
hset key1 field1 val1 # 设置
hsetnx key1 field1 val1 # 不存在才设置(set if not exist), 在分布式锁中常用: 1 成功; 0 失败
hmset key1 field1 val1 field2 val2
hdel key1 field1 # 删除
hget key1 field1 # 获取
hmget key1 field1 field2
hgetall key1 # 获取全部的数据: field1(字段) val1 field2 val2
hkeys key1 # 所有的 key
hvals key1 # 所有的 val
hincrby key1 field1 # 自增
hdecrby key1 field1 # 自减
hlen key1 # 字段的数量
Zset(有序集合)
- 存储班级成绩表, 工资表排序
- 消息的缓急程序
- 排行榜
zadd key1 score1 val1 # 增加(键 权重 值)
zrange key1 开始下标 结束下标 # 截取范围(从小到大排序): 闭区间
zrevrange key1 开始下标 结束下标 # 截取范围(从大到小排序): 闭区间
zrangebyscore key1 最小值 最大值 withscores # 通过权重来截取范围: -inf(无穷小) +inf(无穷大); withscores 显示权重
zcount key1 最小值 最大值 # 统计此范围成员的数量
zrem key1 val1 # 删除
zcard key1 # 成员个数
三种特殊数据类型
geo 地理位置
- 经线度查询
- 朋友的定位, 附近的人, 打车距离计算
geoadd key1 log lat 地点标识名 # 添加经纬度度(两极的无法直接添加, 一般是下载城市数据, 通过代码一次性导入)
geoadd china:city 116.40 39.90 beijin # 例 添加北京
geopos key1 地点标识名 # 获取到坐标值
geodist key1 地点标识名1 地点标识名2 单位 # 两个地点之间的距离: m(默认) km mi(英里) ft(英尺)
# 以给定的`经纬度`为中心, 找出某一半径内的元素(附近的人): withdist 返回直线距离; withcoord 返回经纬度; count 返回的数量
georadius key1 log lat 半径 单位 withdist withcoord count 返回的数量
# 以给定的`地点标识名`为中心, 找出某一半径内的元素(附近的人)
georadiusbymember key 地点标识名 半径 单位 withdist withcoord count 返回的数量
zrange key1 开始下标 结束下标
zrem key1 地点标识名1 # 删除
hyperloglog
- 统计基数(不重复的元素): 最多只使用 12kb 的内存
- 统计 UV 任务, 0.81% 的误差, 不允许容错, 就使用 set 或自己的数据类型即可
pfadd key1 val1 val2 val3 ... # 添加元素
pfcount key1 # 统计基数(不重复的数据)
pfmerge key1 合并源1 合并源2 # 合并数据
bitmaps(位存储)
- 统计用户信息: 活跃/不活跃; 登录/未登录; 打卡/未打卡
- 只有两个状态的, 都可使用 bitmaps, 都是操作二进制, 只有01两个状态
- 类似于
数组
setbit key1 位下标 状态 # 状态只有 0 / 1
getbit key1
bitcount key1 [开始下标 结束下标] # 统计所有状态为 1 的个数
Redis 配置文件
# ============ unit 限制内存的使用大小, 大小写不敏感 ============
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
# ============ 引入多个配置文件 ============
# include /path/to/local.conf
# include /path/to/other.conf
# ============ 网络配置(NETWORK) ============
bind 127.0.0.1 # 可通过本机哪个 ip 地址访问
# bind 192.168.1.100 10.0.0.1 # 绑定多个网卡
# bind 0.0.0.0 # 允许全部的网卡
protected-mode yes # 是否开启 受保护模式
port 6379 # 端口号: 默认 6379
# ============ 通用配置(GENERAL) ============
daemonize no # 是否以守护进程运行(后台) 修改成 yes
pidfile /var/run/redis_6379.pid # 配置文件的 pid 文件路径: 以守护进程方式运行, 必须指定
loglevel notice # 日志级别
# debug (a lot of information, useful for development/testing) # 开发/测试
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) # 生产环境使用
# warning (only very important / critical messages are logged)
logfile "" # 日志的文件位置名
databases 16 # 默认的数据库数量(0~15)
always-show-logo yes # 是否在启动时 显示 logo
# ============ 快照(rdb模式): 持久化 保存到硬盘 SNAPSHOTTING ============
# 持久化: 在规定的时间内, 执行了多少次操作, 则持久化到文件 .rdb .aof
save 900 1 # 若 900s(15min) 内, 1 个key修改, 就进行持久化操作
save 300 10 # 若 300s(5min) 内, 10 个key修改, 就进行持久化操作
save 60 10000 # 若 60(1min) 内, 10000 个key修改, 就进行持久化操作(高并发)
stop-writes-on-bgsave-error yes # 持久化错误, 是否继续工作
rdbcompression yes # 是否压缩 rdb 文件, 需消耗 cpu 资源
rdbchecksum yes # 保存 rdb 文件时, 进行错误的检查
dir ./ # 持久化保存的目录: 默认当前目录下
# ============ 主从复制(REPLICATION) ============
# ============ 安全配置(SECURITY) ============
requirepass 密码 # 默认不需要密码, 正常通过命令行设置密码
# config get requirepass -- 获取密码
# config set requirepass "密码" -- 设置密码
# auth 密码 -- 登录
# ============ 客户端限制(CLIENTS) ============
maxclients 10000 # 限制最大客户端连接数(10000 个)
# ============ 内存使用限制(MEMORY MANAGEMENT) ============
maxmemory <bytes> # 配置最大使用内存
maxmemory policy # 内存到达最大的处理策略
# volatile-lru -> remove the key with an expire set using an LRU algorithm 只对设置了过期的key进行LRU(默认)
# allkeys-lru -> remove any key according to the LRU algorithm 删除 LRU 算法的key
# volatile-random -> remove a random key with an expire set 随机删除即将过期的key
# allkeys-random -> remove a random key, any key 随机删除
# volatile-ttl -> remove the key with the nearest expire time (minor TTL) 删除即将过期的
# noeviction -> don't expire at all, just return an error on write operations 永不过期 返回错误
# ============ aof 配置(APPEND ONLY MODE) ============
appendonly no # 是否开启, 默认使用 rdb 方式持久化, 在大部分情况下, rdb 完全够用
appendfilename "appendonly.aof" # 持久化的文件名
appendfsync everysec # 每秒执行一个同步(sync), 可能会丢失这 1s 的数据
# appendfsync always # 每次修改都会同步(sync), 消耗性能
# appendfsync no # 不执行同步(sync), 由操作系统同步数据, 速度最快
no-appendfsync-on-rewrite no # 是否重写: 当记录日志的文件超过规定值, 是否重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb # 最大限制为 64M, 超过大小, fork一个新的进程来将我们的文件进行重写
Redis 持久化
RDB
- 在指定的时间间隔内, 将内存中的数据快照写入磁盘, 恢复时, 直接读取快照文件
- 持久化过程
- Redis 会单独创建(fork)一个子进程来进行持久化, 先将数据写入到一个临时文件中,
- 待持久化过程都结束后, 再用此临时文件替换上次持久化好的文件
- 整个过程中, 主进程不进行IO操作, 确保了极高的性能
- 若需大规模数据的恢复, 且对于数据恢复的完整性不是非常敏感, 那 rdb 比 aof 更高效
- rdb 保存的文件是
dump.rdb - 备份的触发条件
- 执行 flushall 命令
- 退出客户端 exit
- 满足配置文件中 save 的规则
- 恢复 rdb 文件内的数据
- 查看 rdb 文件, 存放路径:
config get dir - rdb 文件放在我们的 redis 启动目录就可以, redis 启动时会自动检查 dump.rdb, 并恢复数据
- 查看 rdb 文件, 存放路径:
- 优缺点
- 优点
- 适合大规模的数据恢复
- 对数据的完整性要求不高
- 缺点
- 需要一定的时间间隔进行操作, 若 redis 意外宕机, 则最后一次修改数据就没有了
- fork 进程的时候, 会占用一定的内存空间
- 优点
配置文件
# ============ 快照(rdb模式): 持久化 保存到硬盘 SNAPSHOTTING ============
# 持久化: 在规定的时间内, 执行了多少次操作, 则持久化到文件 .rdb .aof
save 900 1 # 若 900s(15min) 内, 1 个key修改, 就进行持久化操作
save 300 10 # 若 300s(5min) 内, 10 个key修改, 就进行持久化操作
save 60 10000 # 若 60(1min) 内, 10000 个key修改, 就进行持久化操作(高并发)
stop-writes-on-bgsave-error yes # 持久化错误, 是否继续工作
rdbcompression yes # 是否压缩 rdb 文件, 需消耗 cpu 资源
rdbchecksum yes # 保存 rdb 文件时, 进行错误的检查
dir ./ # 持久化保存的目录: 默认当前目录下
AOF(Append Only File)
将我们所有命令都记录下来, 相当于 history, 恢复时把这个文件全部执行一遍
- 以日志的形式记录每个
写操作, 只许追加文件但不可改写文件, Redis 启动时,会读取该文件内容, 全部执行一次 - aof 保存的文件是
appendonly.aof - aof 文件有错误, redis 启动不起来, 通过
redis-check-aof --fix appendonly.aof来修复(输入 y) - 优缺点
- 优点
- 每次修改都同步, 文件的完整性更好
- 默认每秒同步一次, 可能会丢失一秒的数据
- 设置
从不同步, 效率是最高的
- 缺点
- 相对于数据文件来说, aof 远远大于 rdb, 恢复的速度更慢
- aof 运行效率也比 rdb 慢, 所以我们 redis 默认配置就是 rdb
- 优点
配置文件
# ============ aof 配置(APPEND ONLY MODE) ============
appendonly no # 是否开启, 默认使用 rdb 方式持久化, 在大部分情况下, rdb 完全够用
appendfilename "appendonly.aof" # 持久化的文件名
appendfsync everysec # 每秒执行一个同步(sync), 可能会丢失这 1s 的数据
# appendfsync always # 每次修改都会同步(sync), 消耗性能
# appendfsync no # 不执行同步(sync), 由操作系统同步数据, 速度最快
no-appendfsync-on-rewrite no # 是否重写: 当记录日志的文件超过规定值, 是否重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb # 最大限制为 64M, 超过大小, fork一个新的进程来将我们的文件进行重写
扩展
- RDB持久化方式能够在指定的时间间隔内对你的数据进行快照存储
- AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据, AOF命令以Redis协议追加保存每次写的操作到文件末尾, Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
- 只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以
不使用任何持久化 - 同时开启两种持久化方式
- 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整
- RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份), 快速重启,而且不会有AOF可能潜在的Bug,留着作为一个万一的手段
- 性能建议
- 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留
save 900 1这条规则 - 如果Enable(开启) AOF , 好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一是带来了持续的I0, 二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率, AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值
- 如果不Enable AOF, 仅靠Master-Slave Repllcation实现高可用性也可以,能省掉一大笔I0,也减少了rewrite时带来的系统波动。代价是如果 Master/Slave 同时倒掉, 会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个,微博就是这种架构
- 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留
Redis 事务操作
- Redis 事务本质: 一组命令的集合, 一个事务中所有命令都被
序列化, 在事务执行过程中, 会按顺序执行 - Redis 单条命令是保持
原子性的, 但事务不保证原子性 - Redis 事务没有隔离级别的概念
- 开启事务 > 命令入队 > 执行事务
# ========== 队列 set set set 执行 ==========
multi # 开启事务
set k1 v1 # 命令入队
set k2 v2 # 命令入队
set k3 v3 # 命令入队
exec # 执行事务
discard # 取消事务
# ========== 编译型异常(代码有问题, 命令错误), 所有命令都不会执行 ==========
multi
set k1 v1
set k2 v2
getset k2 # 错误的命令: 正确的是 getset k2 v21; EXECABORT Transaction discarded because of previous errors
exec # 执行事务报错
# ========== 运行时异常(1/0): 错误命令抛出异常, 其它命令正常执行 ==========
set k1 "v1"
multi
incr k1 # 错误的命令: 字符串不能自增 (error) ERR value is not an integer or out of range
set k2 v2 # OK
get k2 # OK
exec # 执行事务报错
监控(锁)
- 悲观锁: 很悲观, 认为任何时候都会出问题, 无论做什么都会加锁(性能差)
- 乐观锁:
- 很悲观, 认为任何时候都
不会出问题, 无论做什么都不会加锁, 更新数据时判断一下, 在此期间是否有人修改过这个数据 - 获取 version
- 更新时比较 version
- 很悲观, 认为任何时候都
set money 100 # 收的钱
set out 0 # 花的钱
watch money # 监控指定的 key
multi # 开启事务
decrby money 20 # 收的钱 -20
incrby out 20 # 花的钱 +20
exec # 执行事务
# 无其他线程修改数据: 可正常完成事务, 返回值
# 有其他线程修改数据(修改其中的一个金额): 不会执行事务, 返回 nil
unwatch # 解除监视, 再重新执行监视操作(重新获取 version)
Redis 实现订阅发布(消息队列)
subscribe 管道名 # 订阅指定的消息
unsubscribe 管道名 # 退订指定的消息
psubscribe 管道名正则 # 订阅符合正则的消息
punsubscribe 管道名正则 # 退订符合正则的消息
publish 管道名 消息内容 # 发布消息: 所有订阅了此管道的人, 都会接收到
Redis 主从复制: 从机只能读 不能写
- 主节点(master/leader):
写为主; 从节点(slave/follower):读为主 - 至少需要
一主二从, 哨兵模式需要?????? - 数据的复制是单向的, 只能由主节点 => 从节点
- 主从复制的作用
- 数据冗余: 主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
- 故障恢复: 当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余
- 负载均衡: 在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务, 分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量
- 高可用基石: 除了上述作用以外, 主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
info replication # 查看当前库的信息
# role:master # 当前角色
# ============ 从机看主机的信息 ============
# master_host:127.0.0.1 # 主机ip
# master_port:6379 # 主机端口号
# ============ 主机看从机的信息 ============
# connected_slaves:2 # 从机多少个
# salve0:ip=127.0.0.1,port=6380,state=online,offset=42,lag=1 # 从机1的信息
生成配置文件
#!/usr/bin/bash
for port in $(seq 0 5) # 循环生成配置文件
do
# 计算端口号
let "port+=6379"
# 写入配置到配置文件(替换成真实的配置文件存放路径)
cat << EOF >/usr/local/bin/iconfig/redis-${port}.conf
# 真实的配置内容
port $port # 1. 端口号
daemonize yes # 2. 是否以守护进程运行(后台)
pidfile /var/run/redis_${port}.pid # 3. 配置文件的 pid 文件路径: 以守护进程方式运行, 必须指定
logfile "${port}.log" # 4. 日志文件路径
dbfilename dump${port}.rdb # 5. rdb 保存路径
EOF
done
循环启动服务
#!/usr/bin/bash
for port in $(seq 0 5) # 循环
do
# 计算端口号
let "port+=6379"
# 指定配置文件 启动服务: 替换成真实的配置文件存放路径
redis-server /usr/local/bin/iconfig/redis-${port}.conf
done
ps -ef | grep redis # 查看服务启动结果
配置主从(命令行): 真实情况是在配置文件里 配置
默认情况下, 每台 Redis 服务器都是主节点, 一般情况下配置从机就可以了
redis-cli -p 从机1端口号 # 进入`从机`
slaveof 主机ip 主机端口号 # 指定主服务器的 host port(认父亲)
配置主从(配置文件): 常用
默认情况下, 每台 Redis 服务器都是主节点, 一般情况下配置从机就可以了
bind 0.0.0.0 # 使得Redis服务器可以跨网络访问
requirepass "密码" # 设置密码
slaveof 192.168.11.128 6379 # 指定主服务器的 host port(认父亲)
masterauth 123456 # 主服务器密码
层层链路模式: 爷爷 > 爸爸 > 儿子 > 孙子
- 当爷爷走了, 爸爸就当头(谋朝篡位): 在爸爸中执行命令
slaveof no one - 当爷爷回来, 也是个光杆, 需要重新配置
说明
- 主机断开连接, 从机依旧连接到主机的, 但是没有写操作, 这个时候,主机如果回来了, 从机依旧可以直接获取到主机写的信息
- 若使用
命令行配置的主从, 从机重启, 会变回主机, 重新设置成从机, 立马会从主机中获取值
复制原理
- Slave(从机) 启动成功连接到 Master(主机) 后会发送一个
sync(同步)命令 - Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后, Master 将传送整个数据文件到slave ,并完成一次完全同步
- 全量复制(首次同步): 在 slave 服务在接收到数据库文件数据后,将其存盘并加载到内存中
- 增量复制(后续同步): Master 继续将新的所有收集到的修改命令依次传给slave ,完成同步
Redis 哨兵模式(自动选举老大的模式, 所有的集群都用哨兵模式)
- 谋朝篡位的自动版, 能够后台监控主机是否故障, 根据投票数
自动将从库转换成主库 - 哨兵模式: 哨兵是一个独立的进程, 其原理是
哨兵通过发送命令, 等待 Redis 服务器响应, 从而监控是否还活着- 通过发送命令, 让 Redis 服务器返回运行状态(包括主服务器和从服务器)
- 当哨兵监测到 Master 宕机, 会自动将 slave 切换成 Master, 然后通过
发布订阅模式通知其他从服务器修改配置文件, 从新认父亲
- 监测过程
- 若主服务器宕机, 哨兵1先检测到这个结果, 系统并不会马上进行
failover过程, 仅仅是哨兵1主观认为不可用, 这个现象成为主观下线 - 当后面的哨兵也检测到主服务器不可用,且数据达到一定值时, 哨兵间进行一次投票, 投票多的成为主服务器
- 投票结果由一个哨兵发起, 进行 failover(故障转移)操作, 切换成功后, 会通过
发布订阅模式, 让各个哨兵把自己监控的从服务器实现切换主机, 这个过程称为客观下线
- 若主服务器宕机, 哨兵1先检测到这个结果, 系统并不会马上进行
- 配置哨兵
- 修改配置文件:
cp /opt/redis-版本号/sentinel.conf iconfig/sentinel-端口号.conf# protected-mode no # 禁止保护模式(关闭了保护模式,便于测试) # 'sentinel monitor' 代表监控; 'mymaster' 代表服务器的名称(可自定义); 主服务器ip; 主服务器端口号; 2 代表`大于等于2个`哨兵认为主服务器不可用的时候, 才进行failover(故障转移)操作 sentinel monitor mymaster 192.168.11.128 6379 2 # 配置监听的主服务器 # sentinel auth-pass <master-name> <password> sentinel auth-pass mymaster 主服务器密码 # 验证服务器密码: mymaster 服务器名称; - 启动哨兵:
redis-sentinel iconfig/sentinel-端口号.conf - 当主服务器宕机: 谋朝篡位过程
- 当原主服务器再次回来, 会转换成此节点当从机
- 修改配置文件:
- 优缺点
- 优点
- 哨兵集群, 基于主从复制模式, 所有的主从配置优点, 它全有
- 主从可切换, 故障可转移, 系统的可用性就会更好
- 哨兵模式就是主从模式的升级, 手动到自动, 更加健壮
- 缺点
- Redis 不好在线扩容的, 集群容量达到上限, 在线扩容就十分麻烦
- 实现哨兵模式的配置其实是很麻烦的, 里面配置项很多
- 优点
哨兵模式的全部配置
# Example sentinel.conf
port 26379 # 哨兵 sentinel 实例运行的端口默认 26379
dir /tmp # 哨兵 sentinel 工作目录
# 哨兵 sentinel 监控的是 redis 主节点的 ip port
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
# master-name: 可自命名的主节点名字, A-z、0-9、.-_ 组成。
# quorum: 配置几个哨兵认为主服务器宕机, 才进行failover(故障转移)操作
sentinel monitor mymaster 127.0.0.1 6379 2
# 哨兵1 连接主从的密码, 注意`主从密码必须一致`: 当在 Redis 中开启了密码(requirepass foobared)
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster 配置的密码 # 验证服务器密码: mymaster 服务器名称;
# 指定 多少ms 主节点未应答哨兵, 哨兵主观上认为主节点下线, 默认30秒
sentinel down-after-milliseconds mymaster 30000 # mymaster 服务器名称
# 故障转移的操作: 指定在发生 failover(故障转移) 主从切换时最多可以有多少个slave同时对新的master进行同步,
# 这个数字越小,完成 failover 所需的时间就越长,
# 这个数字越大,就意味着越多的 slave 因为 replication 而不可用。
# 可以通过将这个值设为 1, 来保证每次只有一个slave处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面
# 1. 同一个 sentinel 对同一个 master 两次 failover 之间的间隔时间。
# 2. 当一个 slave 从一个错误的 master 那里同步数据开始计算时间。直到s1ave被纠正为向正确的master那里同步数据时。
# 3. 当想要取消一个正在进行的 failover 所需要的时间
# 4. 当进行 failover 时, 配置所有 slaves 指向新的 master 所需的最大时间, 不过, 即使过了这个超时, slaves 依然会被正确配置为指向 master, 但是就不按 parallel-syncs 所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000 # mymaster 服务器名称;
# SCRIPTS EXECUTION
# 配置当某一事件发生时 所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
# 对于脚本的运行结果有以下规则:
# 若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为 10
# 若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
# 如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
# 一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行
# 通知型脚本: 当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,这时这个
# 通知脚本
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh # mymaster 服务器名称; 脚本路径
# 谋朝串位后 的回调
# 当一个 master 由于 failover 而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是 "failover"
# <role> 是“Teader"或者"observer"中的一个。
# 参数 from-ip, from-port, to-ip,to-port是用来和旧的master和新的master (即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
缓存穿透及解决
概念
查询一个数据, 但 redis 缓存中没有, 于是向持久层数据库 Mysql 查询, 发现也没有, 当用户很多时(或恶意攻击), 缓存都不会命中, 都是请求持久层数据库, 给持久层数据库造成很大的压力, 就相当于出现了缓存穿透
解决方案1: 布隆过滤器
布隆过滤器是一种数据结构, 对所有可能查询的参数以 hash 形式存储, 在控制层先进行校验, 不符合则丢弃请求
解决方案2: 存储空值 10s 左右
- 问题
- 若空值被缓存, 就意味着需更多的空间来存储
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致, 对需保持一致性的业务会有影响
缓存击穿及解决
概念
一个 key 非常热点, 在不停的扛着大并发, 当这个 key 缓存失效的瞬间, 大量的并发就直接请求 MySQL,
解决方案
- 设置热点数据永不过期
- 加互斥锁
- 分布式锁: 保证每个 key, 同时只能有一个线程去查询后端服务, 其它线程未获取分布式锁的权限, 因此只能等待, 这种方式将高并发的压力转移到分布式锁上, 因此对分布式的锁考验很大
缓存雪崩及解决
概念
- 指在某一个时间段, 缓存集中过期失效 或 Rdids 宕机
- 集中过期, 不是非常致命, 最致命的是服务器某个节点
宕机 断网 断电 - 淘宝双11, 停掉一些服务(客服,退货), 保证主要的服务可用
解决方案
- Redis 高可用: 既然可能挂掉, 就多增设几台 redis, 一台挂掉, 其它可继续工作(集群, 异地多活)
- 限流降级: 在缓存失效后, 通过
加锁或队列来控制读数据库写缓存的线程数量 - 数据预热: 在正式部署前, 先把可能的数据先预先访问一遍, 即在将发生大并发访问前 手动触发加载需要的key, 设置不同的过期时间, 让缓存失效的时间尽量均匀