redis就是我们常说的Nosql数据库的一种,redis中的数据是存储到内存中的,所以读写速度非常快,因此被应用与缓存方面。redis支持事务、持久化、LUA脚本、LRU驱动事件、多种集群方案
高性能
redis将数据存储到内存中,相较于从磁盘读取(磁盘-内存-读取出来),速度快得多。如果数据库中的数据发送改变之后,同步缓存中的数据即可
高并发
在高请求的情况下,直接访问数据库必将对数据库产生高额的负载。我们可以考虑将热点数据转移到缓存中,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
对于java而已,也可以使用map或者guava作为缓存,这些缓存是本地缓存,最主要的特点是轻量以及快速,生命周期随着jvm的摧毁而结束,在多实例的情况下,每份实例都需要保存各自的一份缓存,缓存数据无法同步,且不具备一致性
使用redis作为缓存,在多实例的情况下,**各实例共用一份缓存数据,数据具有一致性。**缺点是需要保持redis服务的高可用,架构较为复杂
redis是单线程的模型,因为其内部使用文件事件处理器file event handler
,这个文件事件处理器是单线程的。它采用IO多路复用机制同时监听多个Socker,根据Socker上的事件选择对应的事件处理器进行处理。多个Socker可能会并发产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个Socket,会将Socket产生的事件放入队列中排队,事件派发器每次从队列中取出一个事件,把这个事件交给对应的事件处理器进行处理。
文件事件处理器结构
- 多个Socket
- IO多路复用程序
- 文件事件派生器
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
1、支持的数据类型不同:redis不仅仅支持k/v类型的数据,同时提供list,set,zset和hash等数据类型的存储,而memcached只支持简单的数据类型String,以健值对存储
2、redis支持持久化机制,可以将内存中的数据持久化到硬盘,而memcached把数据全部存在内存中
3、模型上,memcached是多线程,非阻塞IO复用的网络模型,而redis是单线程,多路IO复用模型。
1、基本数据类型
2、过期时间
我们 set key 的时候,都可以给一个 expire time,就是过期时间,通过过期时间我们可以指定这个 key 可以存活的时间。时间到了,这个key将会被删除
过期时间的删除机制为:定期删除和惰性删除
- 定期删除:redis默认每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,过期则删除。
- 惰性删除:定期删除可能会导致很多过期的key没有被删除掉,加入过期的key还没有被删除,还停留在内存,当用户查一下那个key之后,才会被redis删除。这就是惰性删除。
除了这两个机制外,还存在一种现象,即定期删除为删除的key,我们又一直没有访问,那么这个key就会一直存在内存中,此时解决这个问题就得用redis的内存淘汰机制
方案:
- 通过配置redis.conf中的maxmemory这个值来开启内存淘汰功能
- 根据应用场景,选择淘汰策略。
原理:当用户申请更大的内存时(set命令),redis检查内存使用情况,当内存使用情况大于maxmemory则开始根据用户配置的不同淘汰策略来淘汰内存(key),从而换取一定的内存
内存淘汰策略有:
- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
- no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
- 4.0版本后增加以下两种:
- volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
- allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key
保证redis挂掉之后再重启数据可以进行数据的恢复,即将内存中的数据持久化到硬盘中
redis保证数据的持久化有两种方案:快照(RDB)以及只追加文件(AOF)
快照(RDB)(默认)
redis可以通过快照的方式将内存中的数据持久化到硬盘,即将内存中的数据进行备份,生成rdb文件,当下次redis启动的时候,可以读取这个rdb文件进行数据的读取。也可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能)
快照的生成方式
使用BGSAVE或者SAVE命令。
服务端配置自动触发
接收到shutdown指令时进行save
BGSAVE命令:当接收到客户端的BGSAVE命令的时候,redis会调用fork来创建一个子进程,然后子进程负责将快照写入磁盘,而父进程则继续处理命令。
fork命令:当一个进程创建子进程的时候,底层的操作系统会创建这个进程的一个副本,在类unix系统中创建子进程的操作会进行优化:在刚开始的时候,父子进程共享相同内存,直到父进程或子进程对内存进行了写之后,对被写入的内存的共享才会结束服务`
save命令:使用save命令创建快照,redis服务器在快照创建完毕之前将不再响应任何其他的命令,即阻塞状态
服务器配置的方式:用户在redis.conf中设置了save配置选项,redis会在save选项条件满足之后自动触发一次BGSAVE命令,如果设置多个save配置选项,当任意一个save配置选项条件满足,redis也会触发一次BGSAVE命令。
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
服务器接收到shutdown指令:当redis接收到关闭服务器的请求时,会执行一个save命令,阻塞所有的客户端,不再执行客户端执行发送的任何命令,并且在save命令执行完毕之后关闭服务器
2、AOF持久化方案
这种方式可以将所有客户端执行的写命令记录到日志文件中,AOF持久化会将被执行的写命令写到AOF的文件末尾,以此来记录数据发生的变化,因此只要redis从头到尾执行一次AOF文件所包含的所有写命令,就可以恢复AOF文件的记录的数据集.
aof的持久化方案默认关闭,可以将其开启
1.开启AOF持久化
- a.修改 appendonly yes 开启持久化
- b.修改 appendfilename “appendonly.aof” 指定生成文件名称
日志追加频率:
- 说明: 每个redis写命令都要同步写入硬盘,严重降低redis速度
- 解释: 如果用户使用了always选项,那么每个redis写命令都会被写入硬盘,从而将发生系统崩溃时出现的数据丢失减到最少;遗憾的是,因为这种同步策略需要对硬盘进行大量的写入操作,所以redis处理命令的速度会受到硬盘性能的限制;
- 注意: 转盘式硬盘在这种频率下200左右个命令/s ; 固态硬盘(SSD) 几百万个命令/s;
- 警告: 使用SSD用户请谨慎使用always选项,这种模式不断写入少量数据的做法有可能会引发严重的写入放大问题,导致将固态硬盘的寿命从原来的几年降低为几个月。
- 说明: 每秒执行一次同步显式的将多个写命令同步到磁盘
- 解释: 为了兼顾数据安全和写入性能,用户可以考虑使用everysec选项,让redis每秒一次的频率对AOF文件进行同步;redis每秒同步一次AOF文件时性能和不使用任何持久化特性时的性能相差无几,而通过每秒同步一次AOF文件,redis可以保证,即使系统崩溃,用户最多丢失一秒之内产生的数据。
- 说明: 由操作系统决定何时同步
- 解释:最后使用no选项,将完全有操作系统决定什么时候同步AOF日志文件,这个选项不会对redis性能带来影响但是系统崩溃时,会丢失不定数量的数据,另外如果用户硬盘处理写入操作不够快的话,当缓冲区被等待写入硬盘数据填满时,redis会处于阻塞状态,并导致redis的处理命令请求的速度变慢。
修改同步频率
AOF的方式也同时带来了另一个问题。持久化文件会变的越来越大。例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的。因为要恢复数据库的状态其实文件中保存一条set test 100就够了。为了压缩aof的持久化文件Redis提供了AOF重写(ReWriter)机制。
1、AOF重写
# 1.客户端方式触发重写
- 执行BGREWRITEAOF命令 不会阻塞redis的服务
# 2.服务器配置方式自动触发
- 配置redis.conf中的auto-aof-rewrite-percentage选项
- 如果设置auto-aof-rewrite-percentage值为100和auto-aof-rewrite-min-size 64mb,并且启用的AOF持久化时,那么当AOF文件体积大于64M,并且AOF文件的体积比上一次重写之后体积大了至少一倍(100%)时,会自动触发,如果重写过于频繁,用户可以考虑将auto-aof-rewrite-percentage设置为更大
2、重写原理
重写并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,替换掉原有的文件,这点和快照类似
重写流程:
- 1. redis调用fork ,现在有父子两个进程 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令
- 2. 父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
- 3. 当子进程把快照内容写入以命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
- 4. 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。
Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。
在传统的关系式数据库中,常常用 ACID 性质来检验事务功能的可靠性和安全性。在 Redis 中,事务总是具有原子性(Atomicity)、一致性(Consistency)和隔离性(Isolation),并且当 Redis 运行在某种特定的持久化模式下时,事务也具有持久性(Durability)。
redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
1、缓存雪崩:即redis中的值在同一时间大部分过期,而此时有大量请求过来,会造成数据库短时间内承受大量请求而崩掉
解决办法:
2、缓存穿透:请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决办法: 有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。