Redis框架详解
1、Redis的介绍
Redis是当前比较热门的NOSQL系统之一,它是一个开源的使用ANSI c语言编写的key-value存储系统"。Redis数据都是缓存在计算机内存中,它可以周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,实现数据的持久化。
2、Redis的数据类型
2.1、String字符串
字符型类型在 Redis 中采用二进制,这说明该类型存入和获取的数据相同,在 Redis 中字符串类型的 Value 最多可以容纳数据长度是512M。
- 赋值: set key value
- 取值:get key/getset key value
- 删除:del key
- 数值自增和自减: incr key/decr key
- 为key增加一个指定数值: incrby key increment
- 为key减少一个指定数值: decrby key decrement
- 拼凑字符串: append key value
具体操作如图
2.2、hash列表
Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
- 赋值: hset key field value 为指定的key设置field/value对
- 赋值: hmset key field value [field2 value2…] 设置key中多个filed/value
- 取值: hget key field
- 取值: hmget key fileds 获取 key 中的多个filed 的值
- 取值: hgetall key 获取 key 中所有的 filed-value
- 删除: hdel key field[field…] 可以删除一个或多个字段,返回值是被删除的字段个数
- 删除 del key 删除整个list
- 增加数字: hincrby key field increment 设置key 中 filed 的值增加 increment
- 是否存在: hexists key field 判断指定 key 中的 filed 是否存在
- field的数量: hlen key 获取 key 所包含的 field 的数量
- 获得所有的key :hkeys key
- 获得所有的value :hvals key
具体操作如图
2.3、list
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
- 头部插入:lpush key values[value1 value2…] 在指定的key所在头部插入所有的values,如果该 key 不存在,该命令在插入之前会创建一个与该 key 关联的空链表。插入成功,返回元素的个数
- 尾部插入:rpush key values[value1 value2…]
- 获得链表中从start 到 end元素的值:lrange key start end 若为-1则表示链表尾部元素,-2为倒数第二个
- 头部弹出:lpop key 返回并弹出指定的key 关联的链表中第一个元素,如果key 不存在 返回nil
- 尾部弹出:rpop key
- 获得列表中元素格式:llen key
- lpushx key value:当参数中指定的 key 存在时,向关联的list的头部插入 value。如果不存在,将不进行插入
- rpushx key value:在该list的尾部添加
- lrem key cont value :删除 coount 个值为 value 的元素,如果count大于0,从头到尾遍历并删除,如果count 小于0,则从尾向头遍历并删除。如果 count 等于0,则删除链表中所有等于value 的元素
- lset key index value :设置链表中的index 的脚标的元素值,0代表链表的头元素,-1代表链表的尾元素。角标不存在则抛异常
- linsert key before|after pivot value :在 pivot 元素前或者后插入 value 这个元素
- rpoplpush resource destination:将链表中的尾部元素弹出并添加到头部(循环操作)
2.4、set集合
在Redis 中,可以将 Set 类型看作为没有排序的字符集合,Set 集合中不允许出现重复的元素
- sadd key values[value1 value2…]:向 set 中添加数据,如果该 key 的值已有则不会重复添加
- srem key members[member1 member2…]:删除 set 中指定的成员
- smembers key:获得 set 中所有的成员
- sismember key member:判断参数中指定的成员是否在该 set 中,1表示存在,0表示不存在或key本身不存在
- sdiff key1 key2…:返回 key1 与 key2 中相差的成员,而且与key的顺序有关。即返回差集。(A-B)
- sinter key1 key2 key3…:返回交集(A∩B)
- sunion key1 key2 key3…:返回并集(A∪B)
- scard key:获取 set 中成员的数量
- srandmember key:随机返回 set 中的一个成员
- sdiffsotre destination key1 key2…:将key1、key2相差的成员存储在destination 上
- sintersotre destination key[key…]:将返回的交集存储在 destination上
- sunionstore destination key[key…]:将返回的并集存储在 destination 上
3、Redis的持久化
由于redis是一个内存数据库,所有的数据都是保存在内存当中的,内存当中的数据极易丢失,所以redis的数据持久化就显得尤为重要,在redis当中,提供了两种数据持久化的方式,分别为RDB以及AOF,且redis默认开启的数据持久化方式为RDB方式。
3.1、rdb(保存快照)
Redis会定期保存数据快照至一个rbd文件中,并在启动时自动加载rdb文件,恢复之前保存的数据。可以在配置文件中配置Redis进行快照保存的时机:
save [seconds][changes]
意为在[seconds]秒内如果发生了[changes]次数据修改,则进行一次RDB快照保存,例如
save 60 100
会让Redis每60秒检查一次数据变更情况,如果发生了100次或以上的数据变更,则进行RDB快照保存。
优点
1) 对性能影响最小。如前文所述,Redis在保存RDB快照时会fork出子进程进行,几乎不影响Redis处理客户端请求的效率。
2) 每次快照会生成一个完整的数据快照文件,所以可以辅以其他手段保存多个时间点的快照(例如把每天0点的快照备份至其他存储媒介中),作为非常可靠的灾难恢复手段。
3)使用RDB文件进行数据恢复比使用AOF要快很多
缺点
1) 快照是定期生成的,所以在Redis crash时或多或少会丢失一部分数据。
2) 如果数据集非常大且CPU不够强(比如单核CPU),Redis在fork子进程时可能会消耗相对较长的时间,影响Redis对外提供服务的能力。
3.2、AOF(预写日志)
采用AOF持久方式时,Redis会把每一个写请求都记录在一个日志文件里。在Redis重启时,会把AOF文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新。AOF默认是关闭的,如要开启,进行如下配置:
appendonly yes
AOF提供了三种fsync配置,always/everysec/no,通过配置项[appendfsync]指定:
appendfsync no:不进行fsync,将flush文件的时机交给OS决定,速度最快
appendfsync always:每写入一条日志就进行一次fsync操作,数据安全性最高,但速度最慢
appendfsync everysec:折中的做法,交由后台线程每秒fsync一次
优点
-
最安全,在启用appendfsync always时,任何已写入的数据都不会丢失,使用在启用appendfsync everysec也至多只会丢失1秒的数据
-
AOF文件在发生断电等问题时也不会损坏,即使出现了某条日志只写入了一半的情况,也可以使用redis-check-aof工具轻松修复。
-
AOF文件易读,可修改,在进行了某些错误的数据清除操作后,只要AOF文件没有rewrite,就可以把AOF文件备份出来,把错误的命令删除,然后恢复数据。
缺点
-
AOF文件通常比RDB文件更大
-
性能消耗比RDB高
-
数据恢复速度比RDB慢
4、缓存雪崩
4.1、我们为什么要用缓存(Redis)
4.2、如果缓存挂了呢
4.3、雪崩场景
- Redis挂掉了,请求全部走数据库。
- 对缓存数据设置相同的过期时间,导致某段时间内缓存失效,请求全部走数据库。
缓存雪崩如果发生了,很可能就把我们的数据库搞垮,导致整个服务瘫痪!
4.4、如何解决缓存雪崩
- 对于“Redis挂掉了,请求全部走数据库”这种情况
- 事发前:实现Redis的高可用(主从架构+Sentinel(哨兵) 或者Redis Cluster(集群)),尽量避免Redis挂掉这种情况发生。
- 事发中:万一Redis真的挂了,我们可以设置本地缓存(ehcache)+限流(hystrix),尽量避免我们的数据库被干掉(起码能保证我们的服务还是能正常工作的)
- 事发后:redis持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。
- 对于“对缓存数据设置相同的过期时间,导致某段时间内缓存失效,请求全部走数据库。”这种情况
- 在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。
5、缓存穿透
5.1、什么是缓存穿透
缓存穿透是指查询一个一定不存在的数据。由于缓存不命中,并且出于容错考虑,如果从数据库查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,失去了缓存的意义。
比如,我们有一张数据库表,ID都是从1开始的(正数)。但是可能有黑客想把我的数据库搞垮,每次请求的ID都是负数。这会导致我的缓存就没用了,请求全部都找数据库去了,但数据库也没有这个值啊,所以每次都返回空出去。
缓存穿透如果发生了,也可能把我们的数据库搞垮,导致整个服务瘫痪!
5.2、如何解决缓存穿透
1) 由于请求的参数是不合法的(每次都请求不存在的参数),于是我们可以使用布隆过滤器(BloomFilter)或者压缩filter提前拦截,不合法就不让这个请求到数据库层!
2)当我们从数据库找不到的时候,我们也将这个空对象设置到缓存里边去。下次再请求的时候,就可以从缓存里边获取了。