Redis系统学习
一、什么是Redis?
- Redis是用C语言开发的一个开源、免费、高性能键值对内存数据库
- 它提供5种数据类型来存储值:字符串类型、散列类型、列表类型、集合类型、有序集合类型
- 他是一种NoSQL数据库
- 可用于缓存、内存数据库、消息队列等
1.1 什么是NoSQL?
- NoSQL,即Not-Only-SQL,泛指非关系型数据库
- NoSQL数据库为了解决高并发、高可用、高可拓展、大数据存储问题而产生的数据库解决方案
- NoSQL可以作为关系型数据库的良好补充,但不是替代关系型数据库
1.2 NoSQL数据库分类
1.2.1 键值存储数据库
相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
典型应用: 内容缓存,主要处理大量数据的高访问负载
数据模型: 一系列键值对
优势: 快速查询
劣势: 存储的数据缺少结构化
1.2.2 列存储数据库
相关产品: Cassardra、HBase、Riak
典型应用: 分布式的文件系统
数据模型: 以列簇式存储,将同一列数据存在一起
优势: 查找速度快、可拓展性强,更容易进行分布式拓展
劣势: 功能相对局限
1.2.3 文档型数据库
说明: 与Key-Value类似,Value是结构化的,即可嵌套
相关产品: Mongo DB、Couch DB
典型应用: Web应用
数据模型: 一系列键值对
优势: 数据结构要求不严格
1.3 Redis的应用场景
- 内存数据库(登陆信息、购物车信息、用户浏览记录等)
- 缓存服务器(商品数据、广告数据等)
- 解决分布式集群架构中的session分离问题(session共享)
- 任务队列(秒杀、抢购、12306等)
- 支持发布-订阅的消息模式
- 应用排行榜
- 网站访问统计
- 数据过期处理(精确到毫秒)
二、安装启动
2.1 解压二进制安装包
1 | tar -xf redis-3.2.9.tar.gz # 加上-z参数可以显示解压进度 |
2.2 启动
2.2.1 前端启动
1 | 直接运行bin/redis-server将以前端模式启动 |
2.2.2 守护进程启动
- 修改
redis-conf
配置文件,将文件中的daemonize
项改为yes
- bind 127.0.0.1改为bind <redis实例所在机器的真实IP>,如:bind 192.168.10.133(本地测试忽略)
1 | 指定配置文件位置启动 |
2.3 其他命令介绍
- redis-server: 启动Redis服务
- redis-cli: 进入Redis命令客户端
- redis-benchmark: 性能测试工具
- redis-check-aof: aof文件进行检查的工具
- redis-check-dump: rdb文件进行检查的工具
- redis-sentinel: 启动哨兵监控服务
2.4 Redis客户端
2.4.1 自带的命令客户端
1 | 默认进入当前机器的6379端口所在的Redis |
2.4.2 程序客户端-Jedis
- Redis不仅可以使用命令客户端进行操作,还可以使用程序客户端进行操作
- 现在的主流语言都有客户端支持,比如:Java、C、C#、C++、PHP、Node.js、Go等
- Java的客户端有: Jedis、Redisson、Jredis、JDBC-Redis
2.4.3 多数据库支持
默认一共是16个数据库,每个数据库之间是相互隔离(但是可以使用flushall一次清空所有的库)。数据库的数量是在redis.conf中配置的。
切换数据库使用命令:select 数据库编号(0-15)
例如:select 1
三、通用命令
3.1 set & get命令
1 | redis通过set key value的方式塞值 |
3.2 keys命令
1 | 返回满足给定pattern的所有key |
3.3 setnx命令
1 | setnx只能在key不存在的场景下可以塞值成功 |
该命令可以用于分布式锁,只能赋值成功一次的场景: 把key当作是锁
3.4 append命令
1 | set sayhi "hello" |
3.5 strlen命令
1 | strlen sayhi |
3.6 同时设置多个值和获取多个值
1 | mset k1 v1 k2 v2 k3 v3 |
3.7 del命令
del命令是根据key来删除的,所以5种数据类型通用
1 | del name |
3.8 判断一个key是否存在
1 | exists k1 |
3.9 给一个key重命名
1 | rename k1 k11 |
3.10 判断一个value的类型
1 | type k11 |
3.11 设置缓存过期时间(生存时间)
Redis
在实际使用过程中,更多的用作缓存.然而缓存的数据一般都是需要设置生存时间的.
即:到期后数据销毁
1 | expire key seconds 设置生存时间(秒) |
四、Redis数据类型
4.1 String类型
1 | 赋值 |
4.1.1 String类型递增数字
1 | 如果我们key的value是可以转换成正整数的字符串 |
4.2 Hash类型
Hash类型使用hset
命令,不区分插入和更新操作,当执行插入操作的时候返回1;当执行更新操作的时候返回0
该类型可以理解为他的value是一个map
,即: 键 - (属性:值)
属性就可以理解为map的key;值则是map的值
这样的数据结构更能体现Java对象的特征
语法: hset key field value
注意事项: 存储对象属性经常发生增删改操作的数据
1 | hset "tom" age 20 |
4.2.1 Hash类型递增数字
同样,hash类型也是可以递增数字的,例如:
1 | 不存在hincr命令,只有hincrby |
4.2.2 判断属性是否存在
1 | hexists tom age |
4.2.3 获取所有的field或所有的值
1 | hkeys tom |
4.2.4 获取所有的field和所有的值
1 | hgetall tom |
4.2.5 获取字段数
1 | hlen tom |
4.3 List类型
内部实现是双向链表,所以向两端添加元素的时间复杂度为O(1),获取越接近两端的元素速度就越快.
这意味着,即使是一个有几千万个元素的列表,获取头部和尾部的10条记录也是极快的.
4.3.1 向列表增加和查看列表
1 | 向列表左边增加元素 |
4.3.2 从列表两端弹出元素
1 | lpop list1 |
利用列表,从一端添加,从另一端pop就可以实现消息队列的效果
4.3.3 获取列表长度
1 | llen list1 |
4.3.4 删除列表中指定的值(且指定删除的个数)
语法: lrem 列表 个数 值
1 | 删除前 |
4.3.5 获取指定索引的元素值
1 | lrange list1 0 -1 |
4.3.6 将列表中元素插入另一个列表
1 | lrange list1 0 -1 |
4.4 Set类型
不可重复(去重)
4.4.1 新增&删除
1 | sadd s1 1 2 3 4 5 |
4.4.2 判断集合中是否存在该元素
1 | sismember s1 6 |
4.4.3 集合运算
1 | 已知: |
4.4.4 获取集合中元素的个数
1 | scard s1 |
4.4.5 从集合中弹出一个元素
必须要注意的是,此处的弹出(删除)是随机的.因此可以使用它来实现抽奖系统等
1 | 传递的数字代表要弹出几个元素,而不是弹出什么元素 |
4.5 SortedSet类型(别名:ZSet)
有序集合实现排序的方式:通过给每一个元素指定一个分数,然后基于分数进行排名
1 | 语法:zadd 集合名 分数 元素 |
4.5.1 获取元素的分数
1 | zscore z1 Jack |
4.5.2 删除元素
1 | zrem z1 Jack |
五、Redis事务
5.1 Redis事务介绍
- Redis事务是通过
MULTI
、EXEC
、DISCARD
和WATCH
这四个命令实现的 - Redis的单个命令都是原子性的,所以这里确保事务性的对象是命令集合
- Redis将命令集合序列化并确保处于同一事务的命令集合连续且不被打断的执行
- 不支持事务回滚
5.2 命令介绍
5.2.1 MULTI命令
用于标记一个事务块的开始
Redis会将后续的命令逐个放入队列中,然后才能使用EXEC命令原子化地执行这个序列
5.2.2 EXEC命令
在一个事务中执行所以先前放入命令队列中的命令,然后恢复正常的连接状态
5.2.3 DISCARD命令
清除所有在先前一个事务中放入命令队列中的命令,然后恢复正常的连接状态
5.2.4 WATCH命令
当某个事务需要按条件执行时,就要使用这个命令.将给定的键设置为受监控的
使用这个命令可以实现Redis的乐观锁
使用unwatch
清除所有先前为一个事务监控的键
5.2.5 示例
演示事务开启并取消:
1 | MULTI # 开启事务命令集合 |
演示事务开启并执行:
1 | MULTI # 开启事务命令集合 |
演示WATCH乐观锁:
1 | 检视初始值 |
5.3 Redis事务失败
- Redis语法错误
- Redis类型错误
5.3.1 为什么Redis不支持事务回滚?
- 大多数事务失败是因为语法错误或类型错误,这两种错误都可以在开发阶段预见
- Redis为了性能方面忽略了事务回滚
六、Redis实现分布式锁
6.1 锁的处理
单应用中使用锁:单进程多线程
Synchronize、Lock
分布式应用中使用锁:多进程
6.2 分布式锁的实现方式
- 数据库的乐观锁
- 基于Zookeeper的分布式锁
- 基于Redis的分布式锁
6.3 分布式锁的注意事项
- 互斥性:在任意时刻,只有一个客户端能持有锁
- 同一性:加锁和解锁必须是同一个客户端,客户端不能把别人加的锁
- 避免死锁:即使有一个客户端持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端加锁
6.4 实现分布式锁
6.4.1 获取锁
在SET命令中,有很多选项可用来修改命令的行为,以下是SET命令可用的基本语法。
1 | SET key value [EX seconds] [PX milliseconds] [NX|XX] |
- EX seconds:设置指定的到期时间(以秒为单位)。
- PX milliseconds:设置指定的到期时间(以毫秒为单位)。
- NX:仅在键不存在的时候设置键。
- XX:仅在键已存在的时候才设置。
1 | /** |
1 | /** |
6.4.2 释放锁
1 | /** |
1 | /** |
七、持久化方案
Redis是一个内存数据库,为了保证数据的持久性,它提供了两种持久化方案:
- RDB方式(默认)
- AOF方式
7.1 RDB方式
7.1.1 介绍
- RDB是Redis默认采用的持久化方式
- RDB方式是通过快照(snapshotting)完成的,当符合一定条件时Redis会自动将内存中的数据进行快照并持久化到硬盘。默认保存在dump.rdb文件中。(本机位置:/usr/local/bin/dump.rdb)
- Redis会在指定的情况下触发快照
- 符合自定义配置的快照规则
- 执行save或者bgsave命令
- 执行flushall命令
- 执行主从复制操作
7.1.2 配置dbfilename指定rdb快照文件的名称
1 | # The filename where to dump the DB |
注意事项
- redis在进行快照的过程中不会修改RDB文件,只有快照结束后才会将旧的文件替换成新的,也就是说任何时候RDB文件都是完整的。
- 这就使得我们可以通过定时备份RDB文件来实现redis数据库的备份, RDB文件是经过压缩的二进制文件,占用的空间会小于内存中的数据,更加利于传输。
7.1.3 自定义快照规则
在redis.conf中设置自定义快照规则:
1 | 格式:save <seconds> <changes> |
特别说明:
1. Redis启动后会读取RDB快照文件,将数据从硬盘载入到内存。
2. 根据数据量大小与结构和服务器性能不同,这个时间也不同。通常将记录一千万个字符串类型键、大小为1GB的快照文件载入到内存中需要花费20~30秒钟。
7.1.4 RDB的优缺点
优点:
RDB可以最大化Redis的性能:父进程在保存RDB文件时唯一要做的就是fork出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无序执行任何磁盘I/O操作。同时这个也是一个缺点,如果数据集比较大的时候,fork可以能比较耗时,造成服务器在一段时间内停止处理客户端的请求
缺点:
使用RDB方式实现持久化,一旦Redis异常退出,就会丢失最后一次快照以后更改的所有数据。这个时候我们就需要根据具体的应用场景,通过组合设置自动快照条件的方式来将可能发生的数据损失控制在能够接受范围。如果数据相对来说比较重要,希望将损失降到最小,则可以使用AOF方式进行持久化
7.2 AOF方式
7.2.1 介绍
- 默认情况下Redis没有开启AOF(append only file)方式的持久化
- 开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件,这一过程显然会降低Redis的性能,但大部分情况下这个影响是能够接受的,另外使用较快的硬盘可以提高AOF的性能。
- 可以通过修改redis.conf配置文件中的appendonly参数开启
1 | appendonly yes |
AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的。
默认的文件名是appendonly.aof,可以通过appendfilename参数修改:
1 | appendfilename appendonly.aof |
7.2.2 参数说明
auto-aof-rewrite-percentage 100 表示当前aof文件大小超过上一次aof文件大小的百分之多少的时候会进行重写。如果之前没有重写过,以启动时aof文件大小为准
auto-aof-rewrite-min-size 64mb 限制允许重写最小aof文件大小,也就是文件大小小于64mb的时候,不需要进行优化
7.2.3 同步磁盘数据
Redis每次更改数据的时候, aof机制都会将命令记录到aof文件,但是实际上由于操作系统的缓存机制,数据并没有实时写入到硬盘,而是进入硬盘缓存。再通过硬盘缓存机制去刷新到保存到文件。
参数说明:
appendfsync always 每次执行写入都会进行同步 , 这个是最安全但是是效率比较低的方式
appendfsync everysec 每一秒执行(默认)
appendfsync no 不主动进行同步操作,由操作系统去执行,这个是最快但是最不安全的方式
7.2.4 AOF文件损坏以后如何修复
服务器可能在程序正在对 AOF 文件进行写入时停机, 如果停机造成了 AOF 文件出错(corrupt), 那么 Redis 在重启时会拒绝载入这个 AOF 文件, 从而确保数据的一致性不会被破坏。
当发生这种情况时, 可以用以下方法来修复出错的 AOF 文件:
为现有的 AOF 文件创建一个备份。
使用 Redis 附带的 redis-check-aof 程序,对原来的 AOF 文件进行修复。
redis-check-aof –fix readonly.aof
重启 Redis 服务器,等待服务器载入修复后的 AOF 文件,并进行数据恢复。
7.3 如何选择RDB和AOF
一般来说,如果对数据的安全性要求非常高的话,应该同时使用两种持久化功能。
如果可以承受数分钟以内的数据丢失,那么可以只使用 RDB 持久化。
有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快 。
两种持久化策略可以同时使用,也可以使用其中一种。如果同时使用的话, 那么Redis重启时,会优先使用AOF文件来还原数据
八、Redis主从复制
持久化保证了即使redis服务重启也不会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据丢失,不过通过redis的主从复制机制就可以避免这种单点故障。
说明:
主redis中的数据有两个副本(replication)即从redis1和从redis2,即使一台redis服务器宕机其它两台redis服务也可以继续提供服务。
主redis中的数据和从redis上的数据保持实时同步,当主redis写入数据时通过主从复制机制会复制到两个从redis服务上。
只有一个主redis,可以有多个从redis。
主从复制不会阻塞master,在同步数据时,master 可以继续处理client 请求
一个redis可以即是主又是从
8.1 主从配置
8.1.1 主redis配置
无需特殊配置。
8.1.2 从redis配置
修改从服务器上的redis.conf文件:
1 | slaveof <masterip> <masterport> |
8.2 实现原理
Redis的主从同步,分为全量同步和增量同步。
只有从机第一次连接上主机是全量同步
断线重连有可能触发全量同步也有可能是增量同步(master判断runid是否一致)
除此之外的情况都是增量同步
8.2.1 全量同步
Redis的全量同步过程主要分三个阶段:
同步快照阶段:Master创建并发送快照给Slave,Slave载入并解析快照。Master同时将此阶段所产生的新的写命令存储到缓冲区。
同步写缓冲阶段:Master向Slave同步存储在缓冲区的写操作命令。
同步增量阶段:Master向Slave同步写操作命令。
8.2.2 增量同步
Redis增量同步主要指Slave完成初始化后开始正常工作时,Master发生的写操作同步到Slave的过程。
通常情况下,Master每执行一个写命令就会向Slave发送相同的写命令,然后Slave接收并执行。
九、Redis Sentinel哨兵机制
Redis主从复制的缺点:没有办法对master进行动态选举,需要使用Sentinel机制完成动态选举。
说明:
Sentinel(哨兵)进程是用于监控redis集群中Master主服务器工作的状态
在Master主服务器发生故障的时候,可以实现Master和Slave服务器的切换,保证系统的高可用(HA)
其已经被集成在redis2.6+的版本中,Redis的哨兵模式到了2.8版本之后就稳定了下来。
9.1 哨兵进程的作用
监控(Monitoring): 哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常。
提醒(Notification): 当被监控的某个Redis节点出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。
自动故障迁移(Automatic failover):当一个Master不能正常工作时,哨兵(sentinel) 会开始一次自动故障迁移操作。
它会将失效Master的其中一个Slave升级为新的Master, 并让失效Master的其他Slave改为复制新的Master;
当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用现在的Master替换失效Master。
Master和Slave服务器切换后,Master的redis.conf、Slave的redis.conf和sentinel.conf的配置文件的内容都会发生相应的改变,即,Master主服务器的redis.conf配置文件中会多一行slaveof的配置,sentinel.conf的监控目标会随之调换。
9.2 哨兵进程的工作方式
每个Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的Master主服务器,Slave从服务器以及其他Sentinel(哨兵)进程发送一个 PING 命令。
如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel(哨兵)进程标记为主观下线(SDOWN)。
如果一个Master主服务器被标记为主观下线(SDOWN),则正在监视这个Master主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认Master主服务器的确进入了主观下线状态。
当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认Master主服务器进入了主观下线状态(SDOWN), 则Master主服务器会被标记为客观下线(ODOWN)。
在一般情况下, 每个 Sentinel(哨兵)进程会以每 10 秒一次的频率向集群中的所有Master主服务器、Slave从服务器发送 INFO 命令。
当Master主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的 Master主服务器的所有 Slave从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。
若没有足够数量的 Sentinel(哨兵)进程同意 Master主服务器下线, Master主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel(哨兵)进程发送 PING 命令返回有效回复,Master主服务器的主观下线状态就会被移除。
9.3 案例演示
修改从机的sentinel.conf
1 | #sentinel monitor <master-name> <master ip> <master port> <quorum> |
其他配置项说明:
1 | # Example sentinel.conf |
通过redis-sentinel启动哨兵服务:
1 | ./redis-sentinel sentinel.conf |
十、Redis Cluster集群
redis3.0以后推出的redis cluster 集群方案,redis cluster集群保证了高可用、高性能、高可扩展性。
架构细节:
(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
(2)节点的fail是通过集群中超过半数的节点检测失效时才生效.
(3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
(4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护
Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点
最小节点数:3台
(1)节点失效判断:集群中所有master参与投票,如果半数以上master节点与其中一个master节点通信超过(cluster-node-timeout),认为该master节点挂掉.
(2)集群失效判断:什么时候整个集群不可用(cluster_state:fail)?
Ø 如果集群任意master挂掉,且当前master没有slave,则集群进入fail状态。也可以理解成集群的[0-16383]slot映射不完全时进入fail状态。
如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态。