一、Redis概述
Redis是一个NoSql(非关系型)类型的数据库,采用key-value模式进行存储,能够解决cpu和io压力,提高性能支持持久化,原理采用了单线程+多路io复用使得cpu能发挥最大性能,一般用于作为缓存数据库辅助持久化数据库。
二、数据操作和数据类型
1、键(key)
keys * //查看当前库所有键
exists key //判断key是否存在
type key //查看key是什么类型
del key //删除指定的key
unlink key // 删除key(异步操作)
expire key 10 //以秒为单位,设置过期时间
ttl key //查看key还有多久过期,-1表示永不过期,-2表示已过期
select //切换库,默认16个(0~15),默认在0
dbsize //查看当前库key数量
flushdb //清空当前库
flushall //通杀全部库
2、字符串(String)
最基本的类型,一个key对应一个value。是二进制安全的,可以包含任何数据,比如一张图片或者一个序列化的对象。最多是512M。
set key value //向库中添加数据或改变key的value值
get key //获取
append key value //在原值末尾对key进行追加
strlen key//获取值的长度
setnx key value //只有在key不存在时,设置key的值
incr key //只能对数字值操作,将值增1,若为空,则设置为1
decr key //只能对数字值操作,将值减1,若为空,则设置为-1
incrby/decrby key (步长) //自定义步长
mset key value key value //同时设置多个值
mget key key //同时获取多个值
msetnx //同时设置,当且仅当key都不存在(具有原子性)
getrange key 0 3 //根据起始位置和结束位置获取值的范围,类似substring
setrange key 0 value//从起始位置索引复写
setex key 过期时间 value //设置过期时间
getset key // 获取旧值的同时设置了新的值
数据结构:类似Arraylist,采用预分配方式,小于1M时,扩容将加倍空间,超过1M时,每次多扩1M的空间。
3列表(List)
列表是字符串列表,单键多值,按插入顺序存储,可以添加到头部或者尾部。
lpush/rpush key value key value //从左边或右边插入一个或多个值
lpop/rpop key //从左边或右边吐出一个值
rpoplpush key key //从右边取出一个值放到另一个左边
lrange key start stop //按索引从左到右获取值
lindex key index //按索引获取值
llen key //获取列表长度
linsert key before value newvalue //在value前或后插入一个新值
lrem key n value //从左边删除n个(value)
lset key index value //将列表key下标为index的值换为value
数据结构:快速链表quickList
4、set集合
无序,排重。底层是hash表
sadd key value value //添加成员元素
smembers key //取出所有成员值
sismember key value //判断是否有value值
scard key //返回该集合的元素个数。
srem key value1 value2 // 删除集合中的某个元素
spop key //随机从该集合中吐出一个值
srandmember key n //随机从该集合中取出 n 个值,不会从集合中删除
smove source destination value //把集合中一个值从一个集合移动到另一个集合
sinter key1 key2 //返回两个集合的交集元素
sunion key1 key2 //返回两个集合的并集元素
sdiff key1 key2 //返回两个集合的差集元素(key1 中的,不包含 key2 中的)
5、Zset(有序集合)
Redis 有序集合 zset 与普通集合 set 非常相似,是一个没有重复元素的字符串集合。
不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复的。
因为元素是有序的,所以可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。
访问有序集合的中间元素也是非常快的,因此能够使用有序集合作为一个没有重复成员的智能列表。
zadd key score1 value1 score2 value2 //将一个或多个 member 元素及其 score 值加入到有序集 key 当中
zrange key start stop [WITHSCORES] :返回有序集 key 中,下标在 <start><stop> 之间的元素
当带 WITHSCORES,可以让分数一起和值返回到结果集
zrangebyscore key min max [withscores] [limit offset count]:返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。
zrevrangebyscore key max min [withscores] [limit offset count] :同上,改为从大到小排列
zincrby key increment value //为元素的 score 加上增量
zrem key value //删除该集合下,指定值的元素
zcount key min max //统计该集合,分数区间内的元素个数
zrank key value //返回该值在集合中的排名,从 0 开始。
数据结构
SortedSet(zset)是 Redis 提供的一个非常特别的数据结构,一方面它等价于 Java 的数据结构 Map<String, Double>,可以给每一个元素 value 赋予一个权重 score,另一方面它又类似于 TreeSet,内部的元素会按照权重 score 进行排序,可以得到每个元素的名次,还可以通过 score 的范围来获取元素的列表。
zset 底层使用了两个数据结构
hash,hash 的作用就是关联元素 value 和权重 score,保障元素 value 的唯一性,可以通过元素 value 找到相应的 score 值
跳跃表,跳跃表的目的在于给元素 value 排序,根据 score 的范围获取元素列表
三、发布和订阅
一种消息模式,发送者发送消息,订阅者接受消息。可以订阅任意数量频道
subscribe 频道 //订阅频道
publish 频道 消息 //向频道发送消息
四、Jedis
能使用Java操控Redis,需要引入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
连接测试
public class JedisDemo {
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.57.101", 6379);
String pong = jedis.ping();
System.out.println("连接成功:" + pong);
jedis.close();
}
}
五、事务和锁机制
事务定义
Redis的事务是一个单独的隔离操作,将事务中的命令序列化,不会被其他客户端发送的请求打断。
输入Multi开启事务,命令以此进入队列但不执行,输入exec开始依次执行。可以通过discard放弃组队。当组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。组队时命令不报错,而执行时报错,则该命令取消,其他正常执行。
事务冲突
多个请求操作数据导致结果不合理
悲观锁
传统的关系型数据库经常使用(行锁,表锁,读锁,写锁),缺点:效率低
乐观锁
操作前得到同样版本,在操作后,对版本同步更新,其他请求操作时检验版本,若不同则不能操作。
适用于多读的应用类型,提高吞吐量,Redis采用该方式
Redis事务的三大特性
-
单独的隔离操作
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
-
没有隔离级别的概念
队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行。
-
不保证原子性
事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚 。
六、持久化操作
1、RDB
在指定的时间间隔内将内存中的数据集快照写入磁盘, 即snapshot快照,恢复时是将快照文件直接读到内存里。
Redis会单独创建一个子进程(fork)来进行持久化。共享物理内存
先将数据写入到一个临时文件中,待持久化过程完成后,再将这个临时文件内容覆盖到dump.rdb。写时复制技术
整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
优点
- 适合大规模的数据恢复;
- 对数据完整性和一致性要求不高更适合使用;
- 节省磁盘空间;
- 恢复速度快。
缺点
- Fork 的时候,内存中的数据被克隆了一份,大致 2 倍的膨胀性需要考虑;
- 虽然 Redis 在 fork 时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能;
- 在备份周期在一定间隔时间做一次备份,所以如果 Redis 意外 down 掉的话,就会丢失最后一次快照后的所有修改。
2、AOF
以日志的形式来记录每个写操作(增量保存),将 Redis 执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,Redis 启动之初会读取该文件重新构建数据,换言之,如果 Redis 重启就会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
如果同时开启,默认读取aof文件
当 AOF 文件的大小超过所设定的阈值时,Redis 就会启动 AOF 文件的内容压缩,只保留可以恢复数据的最小指令集。可以使用命令 bgrewriteaof。
优点
- 备份机制更稳健,丢失数据概率更低;
- 可读的日志文本,通过操作 AOF 稳健,可以处理误操作。
缺点
- 比起 RDB 占用更多的磁盘空间;
- 恢复备份速度要慢;
- 每次读写都同步的话,有一定的性能压力;
- 存在个别 Bug,造成不能恢复。
七、主从复制
主机数据更新后根据配置和策略, 自动同步到备机的 master/slaver 机制,Master 以写为主,Slaver 以读为主。
- 读写分离,性能扩展
- 容灾快速恢复
- 一主多从!
当一个从机挂掉后,需重新启动,重启后并不会继续作为从机,需重新填入,之后复制数据。
薪火相传
从服务器可以作为其他从服务器的主机
反客为主
slaveof no one
哨兵模式
新建sentinel.conf文件
sentinel monitor 别名 端口 数量
主机恢复服务后,作为从机
选举规则
根据优先级别,slave-priority/replica-priority,优先选择优先级靠前的。
根据偏移量,优先选择偏移量大的。
根据 runid,优先选择最小的服务。
八、应用问题
1、缓存穿透
- 大量非正常访问
- 压力巨大
- 命中率低
解决方案
-
对空值缓存
如果一个查询返回的数据为空(不管是数据是否不存在),仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟。
-
设置可访问的名单(白名单):
使用 bitmaps 类型定义一个可以访问的名单,名单 id 作为 bitmaps 的偏移量,每次访问和 bitmap 里面的 id 进行比较,如果访问 id 不在 bitmaps 里面,进行拦截,则不允许访问。
-
采用布隆过滤器
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。
布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
将所有可能存在的数据哈希到一个足够大的 bitmaps 中,一个一定不存在的数据会被这个 bitmaps 拦截掉,从而避免了对底层存储系统的查询压力。
-
进行实时监控
当发现 Redis 的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。
2、缓存击穿
key 对应的数据存在,但在 redis 中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端 DB 压垮。
- 数据库访问压力瞬间增大。
- redis 中没有出现大量 key 过期,redis 正常运行。
- (即某个经常访问的 key 过期,突然有大量访问这个数据)
如何解决
-
预先设置热门数据
在 redis 高峰访问之前,把一些热门数据提前存入到 redis 里面,加大这些热门数据 key 的时长。
-
实时调整
现场监控哪些数据热门,实时调整 key 的过期时长。
-
使用锁
3、缓存雪崩
- 数据库压力巨大
- 查询的大量key过期
导致数据库,应用,redis全部崩溃,对底层系统冲击十分严重
解决方案
-
构建多级缓存架构
nginx 缓存 + redis 缓存 + 其他缓存(ehcache等)
-
使用锁或队列:
用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况。
-
设置过期标志更新缓存:
记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际 key 的缓存。
-
将缓存失效时间分散开:
比如我们可以在原有的失效时间基础上增加一个随机值,比如 1~5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
九、集群
1、问题
容量不够,进行扩容
并发操作,实现分摊
代理主机——无中心化主机
2、集群
水平扩容,启动多个redis节点,将数据库分布存储
cluster-enabled 开启集群
cluster-config-file nodes-6379.conf设定节点配置文件
cluster-node-timeout 失联时间
3、插槽
16384个,使用公式计算,平均分配