redis和memcache的比较
1 、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储;
2 、Redis当物理内存用完时,可以将一些很久没用到的value 交换到磁盘;
3 、memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化);
4、memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复;
5、memcache是多线程,redis是单线程
6、redis存储的value最大可以达到1GB,而memcache只有1MB
7、过期策略--memcache在set时就指定,Redis可以在设置值之后通过expire单独设定过期时间
8、Memcached主要用于缓存数据,Redis除了用于数据缓存之外,还能用作NoSQL数据库、消息队列等。
redis五种类型
String
hash
list
set
zset
redis各种类型简介
String
string 是 redis 最基本的类型,可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。
string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。
string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。
常用命令:get、set、incr、decr、mget
hash
hash是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
常用命令:hget,hset,hgetall 等
list
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
常用命令:lpush,rpush,lpop,rpop,lrange
set
Redis的Set是string类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。(不允许重复的成员)
常用命令:sadd,spop,smembers,sunion
zset
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。
常用命令:zadd,zrange,zrem,zcard
redis各种类型的使用场景
Redis持久化是如何工作的?
什么是持久化?
简单来讲就是将数据放到断电后数据不会丢失的设备中,也就是我们通常理解的硬盘上。
数据库在进行写操作时到底做了哪些事?
1、客户端向服务端发送写操作(数据在客户端的内存中)。
2、数据库服务端接收到写请求的数据(数据在服务端的内存中)。
3、服务端调用write这个系统调用,将数据往磁盘上写(数据在系统内存的缓冲区中)。
4、操作系统将缓冲区中的数据转移到磁盘控制器上(数据在磁盘缓存中)。
5、磁盘控制器将数据写到磁盘的物理介质中(数据真正落到磁盘上)。
当数据库系统故障时,这时候系统内核还是完好的。那么此时只要我们执行完了第3步,那么数据就是安全的,因为后续操作系统会来完成后面几步,保证数据最终会落到磁盘上。
当系统断电时,这时候上面5项中提到的所有缓存都会失效,并且数据库和操作系统都会停止工作。所以只有当数据在完成第5步后,才能保证在断电后数据不丢失。
什么是数据损坏
所谓数据损坏,就是数据无法恢复,上面我们讲的都是如何保证数据是确实写到磁盘上去,但是写到磁盘上可能并不意味着数据不会损坏。比如我们可能一次写请求会进行两次不同的写操作,当意外发生时,可能会导致一次写操作安全完成,但是另一次还没有进行。如果数据库的数据文件结构组织不合理,可能就会导致数据完全不能恢复的状况出现。
通常也有三种策略来组织数据,以防止数据文件损坏到无法恢复的情况:
如何解决数据损坏问题
1、通过配置数据同步备份的方式,在数据文件损坏后通过数据备份来进行恢复。实际上MongoDB在不开启操作日志,通过配置Replica Sets时就是这种情况。
2、在上面基础上添加一个操作日志,每次操作时记一下操作的行为,这样我们可以通过操作日志来进行数据恢复。因为操作日志是顺序追加的方式写的,所以不会出现操作日志也无法恢复的情况。这也类似于MongoDB开启了操作日志的情况。
3、更保险的做法是数据库不进行旧数据的修改,只是以追加方式去完成写操作,这样数据本身就是一份日志,这样就永远不会出现数据无法恢复的情况了。实际上CouchDB就是此做法的优秀范例。
Redis的第一个持久化策略:RDB快照
Redis支持将当前数据的快照存成一个数据文件的持久化机制。而一个持续写入的数据库如何生成快照呢。Redis借助了fork命令的copy on write机制。在生成快照时,将当前进程fork出一个子进程,然后在子进程中循环所有的数据,将数据写成为RDB文件。
我们可以通过Redis的save指令来配置RDB快照生成的时机,比如你可以配置当10分钟以内有100次写入就生成快照,也可以配置当1小时内有1000次写入就生成快照,也可以多个规则一起实施。这些规则的定义就在Redis的配置文件中,你也可以通过Redis的CONFIG SET命令在Redis运行时设置规则,不需要重启Redis。
Redis的RDB文件不会坏掉,因为其写操作是在一个新进程中进行的,当生成一个新的RDB文件时,Redis生成的子进程会先将数据写到一个临时文件中,然后通过原子性rename系统调用将临时文件重命名为RDB文件,这样在任何时候出现故障,Redis的RDB文件都总是可用的。
但是,我们可以很明显的看到,RDB有它的不足,就是一旦数据库出现问题,那么我们的RDB文件中保存的数据并不是全新的,从上次RDB文件生成到 Redis停机这段时间的数据全部丢掉了。在某些业务下,这是可以忍受的,我们也推荐这些业务使用RDB的方式进行持久化,因为开启RDB的代价并不高。 但是对于另外一些对数据安全性要求极高的应用,无法容忍数据丢失的应用,RDB就无能为力了,所以Redis引入了另一个重要的持久化机制:AOF日志。
Redis的第二个持久化策略:AOF日志
AOF日志的全称是Append Only File,从名字上我们就能看出来,它是一个追加写入的日志文件。与一般数据库不同的是,AOF文件是可识别的纯文本,它的内容就是一个个的Redis标准命令。
每一条写命令都生成一条日志,AOF文件会越来越大,所以Redis又提供了一个功能,叫做AOF rewrite。其功能就是重新生成一份AOF文件,新的AOF文件中一条记录的操作只会有一次,而不像一份老文件那样,可能记录了对同一个值的多次操作。其生成过程和RDB类似,也是fork一个进程,直接遍历数据,写入新的AOF临时文件。在写入新文件的过程中,所有的写操作日志还是会写到原来老的 AOF文件中,同时还会记录在内存缓冲区中。当AOF rewrite操作完成后,会将所有缓冲区中的日志一次性写入到临时文件中。然后调用原子性的rename命令用新的 AOF文件取代老的AOF文件。
Redis持久化性能是否可靠?
从上面的流程我们能够看到,RDB是顺序IO操作,性能很高。而同时在通过RDB文件进行数据库恢复的时候,也是顺序的读取数据加载到内存中。所以也不会造成磁盘的随机读取错误。
而AOF是一个写文件操作,其目的是将操作日志写到磁盘上,所以它也同样会遇到我们上面说的写操作的5个流程。那么写AOF的操作安全性又有多高呢?实际上这是可以设置的,在Redis中对AOF调用write写入后,何时再调用fsync将其写到磁盘上,通过appendfsync选项来控制,下面appendfsync的三个设置项,安全强度逐渐变强。
1、appendfsync no
当设置appendfsync为no的时候,Redis不会主动调用fsync去将AOF日志内容同步到磁盘,所以这一切就完全依赖于操作系统的调试了。对大多数Linux操作系统,是每30秒进行一次fsync,将缓冲区中的数据写到磁盘上。
2、appendfsync everysec
当设置appendfsync为everysec的时候,Redis会默认每隔一秒进行一次fsync调用,将缓冲区中的数据写到磁盘。但是当这一 次的fsync调用时长超过1秒时。Redis会采取延迟fsync的策略,再等一秒钟。也就是在两秒后再进行fsync,这一次的fsync就不管会执行多长时间都会进行。这时候由于在fsync时文件描述符会被阻塞,所以当前的写操作就会阻塞。 所以,结论就是:在绝大多数情况下,Redis会每隔一秒进行一次fsync。在最坏的情况下,两秒钟会进行一次fsync操作。 这一操作在大多数数据库系统中被称为group commit,就是组合多次写操作的数据,一次性将日志写到磁盘。
3、appednfsync always
当设置appendfsync为always时,每一次写操作都会调用一次fsync,这时数据是最安全的,当然,由于每次都会执行fsync,所以其性能也会受到影响。
而在利用RDB和利用AOF启动上,其启动时间有一些差别。RDB的启动时间会更短,原因有两个,一是RDB文件中每一条数据只有一条记录,不会像 AOF日志那样可能有一条数据的多次操作记录。所以每条数据只需要写一次就行了。另一个原因是RDB文件的存储格式和Redis数据在内存中的编码格式是一致的,不需要再进行数据编码工作。在CPU消耗上要远小于AOF日志的加载
Redis的集群模式
1、主从复制
2、哨兵模式
3、Redis官方提供的Cluster集群模式(服务端)
4、Jedis sharding集群(客户端sharding)
5、利用中间件代理,比如豌豆荚的codis等
主从复制
Slave可以自动重连Master,但是在连接成功之后,一次完全同步将被自动执行。
为了分担Master的读操作压力,Slave服务器可以为客户端提供只读操作的服务,写服务仍然必须由Master来完成
哨兵模式
无论是主从模式,还是哨兵模式,这两个模式都有一个问题,不能水平扩容,并且这两个模式的高可用特性都会受到Master主节点内存的限制。
Sentinel(哨兵)进程是用于监控redis集群中Master主服务器工作的状态,在Master主服务器发生故障的时候,可以实现Master和Slave服务器的切换,保证系统的高可用。
哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常。
当被监控的某个Redis节点出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。
当一个Master不能正常工作时,哨兵(sentinel) 会开始一次自动故障迁移操作,它会将一个Slave升级为新的Master, 并让其他Slave改为复制新的Master;当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址
缺点
Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
Redis官方 Cluster集群模式
任何两个节点之间都是相互连通的。客户端可以与任何一个节点相连接,然后就可以访问集群中的任何一个节点。对其进行存取和其他操作。
当我们的存取的key到达的时候,redis会根据crc16的算法得出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。
理论上集群中的每个节点至少有一个备用的redis服务。
如果有一半以上的节点去ping一个节点的时候没有回应,集群就认为这个节点宕机了,然后去连接它的备用节点。如果某个节点和所有从节点全部挂掉,我们集群就进入faill状态。还有就是如果有一半以上的主节点宕机,那么我们集群同样进入发力了状态。
Jedis sharding集群
Redis Sharding可以说是在Redis cluster出来之前业界普遍的采用方式,其主要思想是采用hash算法将存储数据的key进行hash散列,这样特定的key会被定到特定的节点上。
利用中间件代理
中间件的作用是将我们需要存入redis中的数据的key通过一套算法计算得出一个值。然后根据这个值找到对应的redis节点,将这些数据存在这个redis的节点中。
缓存穿透、缓存击穿、缓存雪崩
缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,
如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案
接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
解决方案
设置热点数据永远不过期。
加互斥锁,当缓存中没有的时候,只允许一个线程去数据库获取,其余线程自旋等待
缓存雪崩
缓存雪崩是指缓存中大批量数据同时过期,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案
设置热点数据永远不过期。
缓存数据的过期时间设置为固定+随机,防止同一时间大量数据过期现象发生。
缓存双写一致性
数据库和缓存中存储的数据不一致的问题
1、先更新数据库,再更新缓存
2、先删除缓存,再更新数据库,再延迟删缓存
3、先更新数据库,再删除缓存
第三种最靠谱
redis应用
1、分布式锁
2、延时队列
zset +score + 轮询(score > now)
3、定时任务(分布式锁)
4、接口频率控制zset
5、服务发现
zset实现,多个服务列表,用多个zset,score存储心跳,value存储服务地址,
服务提供者每隔几秒执行一次zadd来更新心跳时间,服务提供者停止服务时,使用zrem删除服务
服务有可能异常终止,需要专门的线程定时清理停止的服务,
通知消费者服务列表发生了变更,可以使用版本号轮询机制,当服务列表变更时,递增版本号
如果消费者依赖了很多服务列表,就需要轮询很多版本号,这样的IO效率会比较低下,
可以使用全局版本号+服务版本号来控制
如果全局版本号没有改变,则说明整体没有改变,如果全局版本号改变,再去轮询各个服务列表的子版本号
6、位图
节省存储空间
7、模糊计数
hyperLogLog 误差0.81%
8、布隆过滤器
9、发布/订阅
Redis的过期策略和内存淘汰机制
过期的数据,不会及时删除
如何删除?
定期删除+惰性删除
定期删除
redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。
定期删除可能会导致很多过期key到了时间并没有被删除掉,所以就得靠惰性删除了
惰性删除
获取某个key的时候,redis会检查一下 ,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除
如果定期删除漏掉了很多过期key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?
大量过期key堆积在内存里,导致redis内存块耗尽了
如何处理?
内存淘汰机制
内存淘汰机制
redis的内存占用过多的时候,此时会进行内存淘汰,有如下一些策略
1、noeviction:当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧
2、allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
3、allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key,这个一般没人用吧
4、volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key(这个一般不太合适)
5、volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
6、volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除
布隆过滤器
布隆过滤器是一种数据结构
可以用来告诉你 “某样东西一定不存在或者可能存在”。
特点是高效地插入和查询
有点是相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少
缺点是其返回的结果是概率性的,而不是确切的。