Redis简介
Redis简介
一 介绍
redis是一个基于内存的高性能Key-Value数据库,是存储数据的容器,与关系型数据库一样,也能实现对数据的增删查改。
1 特点
- nosql:not only structured query language (不仅仅结构化查询语言) 是一类操作的总称
- Key-Value:为了处理非结构化数据,使用key-value的数据结构。
- 处理结构化数据(表格)的技术----关系型数据库。
- 处理非结构化数据(日志文件、网页数据)的技术----redis
- 在内存中运行:redis把数据放在内存中处理
- 运行结构:
- 服务端
- 登录redis服务端操作数据的客户端
- 优劣
- 优点:速度快
- 缺点:储存资源稀缺,断电数据丢失
- 运行结构:
- 分布式:为应对容量小的问题采用分布式集群的使用方式。
- 持久化: 为应对断电数据丢失,可以按照响应的机制,将内存数据保存一份到磁盘。
2 基础命令
- Keys * 查看当前redis服务端内存中所有的数据key值。
- exists Key 查看对应的key值数据是否在redis内存中存在。
- expire/pexpire Key time expire 对某个key的数据做秒单位的超时,pexpire对key值做毫秒单位的超时,一旦到达超时条件将会在内存中把数据删除。
- ttl/pttl Key 在执行设置了超时时间的key值上,查看这个key的剩余时间。ttl操作一个key能够看到剩余时间单位是秒,pttl看得是毫秒。
- del Key 对指定的key-value进行删除操作。
- save 将内存数据输出到持久化文件中保存.
- flushall 删除所有数据,将当前redis服务的内存数据和持久化文件中的数据全部清空。
3 数据类型及相关命令
-
String
- set Key value
- EX(后缀参数,下同):在set时直接设置超时秒数。
- PX:在set时直接设置超时毫秒数
- NX:在执行set时,会先判断redis中有没有该key值,如果有则无法set,没有则可以set成功。表示只有第一个set数据的客户端可以成功,后续都会失败。
- XX:在执行set时,会判断redis中有没有key值,有的时候才会set成功,没有则不成功。表示,使用XX的客户端没有新建的权限。
- get Key 从redis中读取key值的value数据。在redis中value最大数据长度1GB。
- incr/incrby decr/decrby 执行计步器,可以增加、减少数值。对应value字符串数据必须是纯数字。
一般使用String类型的value数据实现缓存功能,并且可以利用代码的序列化和反序列化的方法,将对象序列化为字符串。
- set Key value
-
hash
hash在redis中底层双层map形式存在,key的value是map,所以可以对应对象的数据结构。
- hset Key field value在redis中创建一个key值为key对应第二层map的key为field,第二层map的value为value。
- hget Key field hget可以读取到一个属性的值,指定某个key的某个属性读取。
- hkeys/hvals key hkeys key从key值的hash数据结构中将所有的field属性名称返回。hvals key从key的hash数据结构中将所有的field的值返回。
- hdel Key field 删除的是一个hash结构中的某个属性和值。
- hincrby 自增
-
List 双向链表
list底层是双向链表,可以从头和尾部处理数据,实现队列的结构(为了处理消息队列逻辑)。
- lpush/rpush Key value l/r 表示左和右。lpush从链表头部,插入数据,rpush从链表的尾部,插入数据。
- lrange Key start end 可以对一个list链表中的元素范围内的数据读取返回。start=0 end=-1 就可以查询一个list所有内容。
- rpop/lpop key rpop从链表的尾部删除元素,并且将删除的元素值返回。lpop从链表的头部删除元素,将元素值返回(remove)。
可以利用list的类型实现排队队列的处理逻辑,先来先得,先到先处理。
-
SET
可以将不同的,不重复的元素值,放到一个没有顺序概念的集合中,实现value数据在redis的管理。
- sadd Key members
- srandmember key[count]随机在key对应set集合中选取count个元素.
- srem Key 元素值 可以对集合中某个元素进行去除的操作,当删除成功,返回1,删除失败返回0。
- sismember 查看一个元素是否属于这个集合
-
ZSET
在set基础之上,实现了排序的方式,元素也是不可以重复,就是在元素的数据上绑定了一个
评分的数字。网站各种排名,都可以使用ZSET有序集合。
- zadd Key score member 将一个元素绑定一个分数后,写入到一个有序集合中.
- rank/range
- zrank key member:查看member的排名。
- zrange key start stop:查询排名从start开始到stop范围内所有元素起始排名是0,可以对stop=-1查到末尾。
- zrangebyscore key minscore maxscore:查看上限评分和下限评分之间的所有元素并排序。
- zrem Key member 将元素从zset类型的数据中删除。
二. 淘汰策略
长期将Redis作为缓存使用,难免会遇到内存空间存储瓶颈,当Redis内存超出物理内存限制时,内存数据就会与磁盘产生频繁交换,使Redis性能急剧下降。此时如何淘汰无用数据释放空间,存储新数据就变得尤为重要。
对此,Redis在生产环境中,采用配置参数maxmemory 的方式来限制内存大小。当实际存储内存超出maxmemory 参数值时,开发者们可以通过这几种方法——Redis内存淘汰策略,来决定如何腾出新空间继续支持读写工作。
当前Redis3.0版本支持的淘汰策略有6种:
- volatile-lru:从设置过期时间的数据集(server.db[i].expires)中挑选出最近最少使用的数据淘汰。没有设置过期时间的key不会被淘汰,这样就可以在增加内存空间的同时保证需要持久化的数据不会丢失。
- volatile-ttl:策略基本上与volatile-lru相似,从设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰,ttl值越大越优先被淘汰。
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。当内存达到限制无法写入非过期时间的数据集时,可以通过该淘汰策略在主键空间中随机移除某个key
- allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰,该策略要淘汰的key面向的是全体key集合,而非过期的key集合。
- allkeys-random:从数据集(server.db[i].dict)中选择任意数据淘汰。
- no-enviction:禁止驱逐数据,也就是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失,这也是系统默认的一种淘汰策略。
三. 持久化
Ⅰ. RDB(快照)持久化
保存某个时间点的全部数据快照,将Reids在内存中的数据库记录定时dump到磁盘上的RDB持久化。
先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
优势
-
一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。
-
对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。
-
性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。
-
相比于AOF机制,如果数据集很大,RDB的启动效率会更高。
劣势
-
如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。
-
由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。
配置
-
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。
-
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。
-
save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。
Ⅱ. AOF(append only file)持久化
原理是将Reids的操作日志以追加的方式写入文件
记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
优势
-
该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。
-
由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。
-
如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。
-
AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。
劣势
-
对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
-
根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。
配置
在Redis的配置文件中存在三种同步方式,它们分别是:
-
appendfsync always #每次有数据修改发生时都会写入AOF文件。
-
appendfsync everysec #每秒钟同步一次,该策略为AOF的缺省策略。
-
appendfsync no #从不同步。高效但是数据不会被持久化
二者选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些 eventually consistent的意思了。
四. 哨兵机制
哨兵可以看作是redis中提供的一个高可用结构里的特殊进程,专门用来负责监听主从结构.实现判断宕机故障,完成故障转移,记录数据和角色信息的一个集群。
【原理】
- 哨兵启动会连接主节点,同时监听多个主从,需要指定主从结构代号。
- 从主节点获取info返回的结果,包括从节点状态,记录到哨兵内存
- 每秒中向所有的集群节点发送一个心跳检测(rpc 远程通信协议)
- 发现宕机:
- 从节点宕机:哨兵仅仅会在内存记录中记录一份宕机状态
- 主节点宕机:发起投票选举,让所有哨兵进程判断主节点宕机之后,进行过半的投票,如果选举结果没有任何一个过半情况,重新进行选举,为了防止重新选举:
- 可以设置从节点的优先级slave_priority 在redis.conf配置文件设置,默认100 。
- 从配置结构上解决这个问题
- 一旦重新选举完成,哨兵将会修改从节点的redis.conf配置文件记录他是主节点,将其他的集群的从节点挂接到这个新的主节点上,一旦原有主节点恢复启动,哨兵将其挂接到新master下成为从节点 。
【优点】
解决了redis容量小,和单节点并发能力弱的缺点,同时保证了单个数据分片的数据高可用。
【缺点】
- 很难实现分布式,分布式计算逻辑很难使用一致性hash。
- 做不到这种结构的分布式中key和node解耦。
- 当某一个key值,一致性hash计算分片,和某个主从绑定,想把key迁移到别的主从无法实现的.不容易解决数据倾斜。
五. CLUSTER集群
Ⅰ.特点
-
集群所有节点之间是两两互联的,并且通信使用二进制协议优化传输速度(redis-cluster结构的基础)
-
哨兵进程消失了,但是哨兵高可用监听功能整合到了master节点,通过master节点来实现过半选举,监听整个集群功能.集群最小结构就是保证3个master
- 因为哨兵逻辑整合到master设计到集群的过半选举
- 只有3个master时,允许集群宕机一个master(集群容忍度最小是1)
- 最大的集群扩展上限(官方数据redis节点1000个)
-
redis-cli进程客户端访问集群任何一个节点,都可以实现集群的分布式数据管理。因为集群内部引入了分布式的计算逻辑,可以将发送的数据进行计算之后帮助客户端重定向到正确节点来最终处理数据。
-
集群中使用了新的分布式计算逻辑---hash槽。引入了16384个槽道号概念[0,…,16383]。key值做为计算数据,先进行hash取模计算(本质就是取余16384),key值不变对应整数取余结果不变 对应槽道号slot.又由于master管理的不同的槽道号,得到与节点的对应关系,松耦合因为槽道可以迁移 key-->slot-->node
Ⅱ. 槽道原理
-
构成
- 16384位的二进制,以2048个元素的byte数据存储在每个节点内存里---位序列
- 16384个元素的数组,元素值,指向的是一个内存的节点对象node---共享数组/索引数组
-
位序列
-
每个节点在集群创建之初都会根据槽道管理权的分配创建一个二进制数据。
-
主节点的二进制:将管理的槽道和二进制中的bit值做对应关系。
16384位的二进制,位数0,1,2,…16383 对应槽道号;相当于每一个槽道号都能在这个主节点的二进制中找到一个bit与其对应,如果对应的bit是1,表示该槽道号在本节点有管理权,如果是0没有管理权.
-
从节点的二进制:因为从节点没有槽道管理权的,二进制的值0
-
使用位序列判断所属权
如在8002中调用set name haha
- 计算name的槽道号 name-->5798
- 到位序列中找5798下标对应的bit-->0
- 判断5798这个槽道8002没有所属权
-
-
索引/共享数组
-
集群的创建阶段会两两互联通信
经过两两互联通信,每个节点中的节点信息对象,都会通信给其他所有节点,所以登录一个创建好的集群中任何一个节点,能够通过cluster nodes查看到所有节点信息.
集群的节点越多,每个节点保存的所有节点信息数据量越大.扩展上限1000个节点原因之一.理论上分片个数可以16384,每个主节点管理一个槽道.不管理槽道的主节点可以有无数个.
-
分配槽道状态
经过集群的创建,经过节点信息的相互交互,可以进行槽道的分配,不进行槽道分配16384个槽道未分配状态,集群不可以,一旦在某个节点指定管理的某一批槽道,随着二进制的变化作为互联的数据告知其他所有节点.
-
创建数组
每个集群节点中,只要集群创建完毕都会保存一个完全一样的数组对象.他是在一开始就创建的,但是元素的赋值是在分配槽道时完成的
-
分配槽道时,二进制在变化,两两互联中携带相互传递,所有节点都能获取集群槽道的所有分配结果.所有节点将会对这个数组进行赋值。
-
数组创建所属权判断完毕之后寻找正确管理
- 8002调用set name
- name-->5798-->bit 0没有管理权
- 到数组中拿到5798下标的元素引用变量--->8001详细信息包括ip port
- 让客户端重定向到8001
-
六. 常见问题
Ⅰ. 缓存雪崩
redis中,缓存层承载着大量请求,有效的保护了存储层。一旦缓存层由于某些原因(比如网络波动)整体不能提供服务,所有的请求都会达到存储层,存储层的调用量暴增,就会造成存储层挂掉。
解决方案
-
保证缓存层服务高可用性
即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务,比如 Redis Sentinel 和 Redis Cluster 都实现了高可用。
-
依赖隔离组件为后端限流并降级
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
-
数据预热
可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
-
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
-
如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
Ⅱ. 缓存穿透
缓存穿透是指查询一个一定不存在的数据,由于缓存不命中,接着查询数据库也无法查询出结果,因此也不会写入到缓存中,这将会导致每个查询都会去请求数据库,造成缓存穿透;
解决方案:
-
布隆过滤
对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;
【BloomFilter】布隆过滤器
在使用的时候,定义1个字节数组以及3个不同的哈希函数。对于一个数据,先利用这3个哈希函数进行计算,然后将计算结果映射到数组的某个位置上。
如果一个元素经过这3个哈希函数映射,映射到某个为0的位置则说明元素一定不存在,如果映射到3个1,只能说明可能存在。
- BloomFilter只能判断元素没有,不能判断有。
- BloomFilter的优势在于易于理解和实现,劣势在于随着数据增多,误判率也越来越高。
-
缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;
缺陷:
- 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
Ⅲ. 缓存击穿
缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞
解决方案:
-
使用互斥锁
业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法
-
设置热点数据永远不过期