一、Redis
1、概述
Redis是速度非常快的非关系型内存键值数据库,可以存储键和物种不同类型的值之间的映射。
Redis支持很多特性,如数据持久化到硬盘,使用复制扩展读性能,使用分片来扩展写性能。
2、数据类型
(1)STRING
可以存储字符串、整数或者浮点数。对整个字符串或者字符串中的一部分执行操作;对整数和浮点数执行自增或者自减操作。
示例:
set hello word
get hello ------ "world"
del hello ------ (integer) 1
get hello ------ (nil)
(2)LIST
可以存储列表。从两端压入或者弹出元素,对单个或者多个元素进行修剪,只保留一个范围内的元素。
示例:
lpush list-key item ------ (integer) 1
lpush list-key item2 ------ (integer) 2
lpush list-key item ------ (integer) 3
lrange list-key 0 -1 ------ 1) "item" 2) "item2" 3) "item"
lindex list-key 1 ------ "item2"
lpop list key ------ "item"
lrange list-key 0 -1 ------ 1) "item2" 2) "item"
(3)SET
无序集合。添加、获取、移除单个元素;检查一个元素是否存在于集合中;计算交集,并集、差集,从集合里面随机获取元素。
示例:
sadd set-key item ------ (integer) 1
sadd set-key item2 ------ (integer) 1
sadd set-key item3 ------ (integer) 1
sadd set-key item ------ (integer) 0
smembers set-key ------ 1) "item" 2) "item2" 3) "item3"
smembers set-key item4 ------ (integer) 0
smembers set-key item ------ (integer) 1
srem set-key item2 ------ (integer) 1
srem set-key item2 ------ (integer) 0
smembers set-key ------ 1) "item" 2) "item3"
(4)HASH
包含键值对的无须散列表。可以添加获取和移除单个元素;检查一个元素是否存在于集合中;计算交集、并集、差集;从集合里面随机获取元素。
示例:
hset hash-key syb-key1 value1 ------ (integer) 1
hset hash-key sub-key2 value2 ------ (integer) 1
hset hash-key sub-key1 value1 ------ (integer) 0
hgetall hash-key ------1) "sub-key1" 2) "value1" 3) "sub-key2" 4) "value2"
hdel hash-key sub-key2 ------ (integer) 1
hdel hash-key sub-key2 ------ (integer) 0
hget hash-key sub-key1 ------ "value1"
hgetall hash-key ------ 1)"sub-key1" 2)"value1"
(5)ZSET
有序集合。可以添加获取和删除元素;根据分支范围或者成员来获取元素;计算一个键的排名。
示例:
zadd zset-key 728 member1------ (integer) 1
zadd zset-key 982 member1------ (integer) 1
zadd zset-key 982 member1 ------ (integer) 0
zrange zset-key 0 -1 withscores ------ 1) "member1" 2) "728" 3) "member0" 4) "982"
zrangebyscore zset-key 0 800 withscore ------ 1) "member1" 2) "728"
zrem zset-key member1 ------ (integer) 1
zrem zset-key member1 ------ (integer) 0
zrange zset-key 0 -1 withscores ------ 1) "member0" 2) "982"
二、Redis面试
1、什么是Redis
Redis是一个基于内存的高性能key-value数据库。
2、Redis的特点
Redis本质上是一个key-value类型的内存数据库,整个数据库加载在内存中进行操作,定期通过异步操作把数据库数据flush到硬盘进行保存。
由于是在内存操作,所以性能很出色,每秒可以处理超过10万次读写操作,是已知性能最快的key-value DB。
同时Redis支持多种数据结构,单个value的最大限制是1GB。
Redis的主要缺点是数据库容量收到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
3、Redis的好处
速度快:内存中操作。数据类型多:string,list,set,sorted set,hash。支持事务:操作都是原子性。丰富的特性:可用于缓存,消息,按key设置过期时间。
4、Redis和memcache的对比
两者都是非关系型内存键值数据库,主要有以下不同:
数据类型:Memcached 仅支持字符串类型,而 Redis 支持五种不同的数据类型,可以更灵活地解决问题。
数据持久化:Redis 支持两种持久化策略:RDB 快照和 AOF 日志,而 Memcached 不支持持久化。
分布式:Memcached 不支持分布式,只能通过在客户端使用一致性哈希来实现分布式存储,这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点。Redis Cluster 实现了分布式的支持。
内存管理机制:在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘,而 Memcached 的数据则会一直在内存中。Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题。但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。
5、Redis数据淘汰策略
volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-ttl:从以设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
allkeys-lru:从数据集中挑选最少使用的数据淘汰
allkeys-random:从数据集中选择任意数据淘汰
no-enviction:禁止驱逐数据
6、为什么redis需要将所有数据放在内存中
Redis为了达到最快的读写速度将数据读到内存中,并通过异步的方式将数据写入磁盘,所以redis具有快速和数据持久化的特征。如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。
7、Redis的单进程单线程的
Redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。
8、Redis的并发竞争问题
Redis是单进程单线程的,采用队列模式将并发访问变为串行访问,Redis本身没有锁的改良,Redis对于多个客户端连接并不存在竞争;但是Jedis客户端对Redis进行并发访问时会发生连接超时,数据转换错误、阻塞、客户端关闭连接等问题,这些问题都是由于客户端连接混乱造成。有两种解决方法:
客户端角度,为保证每个客户端间正常有序与Redis进行通信,对连接进行池优化,同时对客户端读写Redis操作采用内部锁sychronized。
服务器角度,利用setnx实现锁。
9、Redis事务
在Redis中,MULTI/EXEC/DISCARD/WATCH这四个命令是我们实现事务的基石。
Redis中事务的实现特征:
在事务中所有命令都将会被串行化的顺序执行,事务执行期间,Redis不会再为其他客户端的请求提供任何服务,从而保证了事务中的所有命令被原子的执行。
Redis事务中如果有一条命令执行失败,其后的命令仍然会被继续执行。
通过MULTI命令开启事务,使用EXEC/DISCARD命令来提交/回滚事务。
事务开启之前,如果客户端和服务器出现通讯故障导致网络断开,其后所有待执行的语句都将不会被服务器执行。而如果网络中断发生在客户端执行EXEC命令后,那么该事务中的所有命令都会被服务器执行。
当使用Append-only模式时,Redis会通过调用系统函数write将该事务内的所有写操作炸本次调用中全部写入磁盘。然而在写入的过程中出现系统崩溃,那么此时也许只有部分数据被写入磁盘,而另外一部分数据却已丢失。
Redis服务器会在重新启动时执行一系列必要的一致性检测,一旦发现类似问题,就会立即退出并给出相应的错误提示。
10、WATCH命令和基于CAS的乐观锁
WATCH命令可用于提供CAS功能,WATCH命令在事务执行之前监控了key,在WATCH之后key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Null multi-bulk应答以通知调用者事务执行失败。
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
11、Redis持久化的方式
RDB 持久化
将某个时间点的所有数据都存放到硬盘上。可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。如果系统发生故障,将会丢失最后一次创建快照之后的数据。如果数据量很大,保存快照的时间会很长。
AOF 持久化
将写命令添加到 AOF 文件(Append Only File)的末尾。使用 AOF 持久化需要设置同步选项,从而确保写命令什么时候会同步到磁盘文件上。这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。有以下同步选项:
always 每个写命令都同步,选项会严重减低服务器的性能;
everysec 每秒同步一次,选项比较合适,可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响;
no 让操作系统来决定何时同步,选项并不能给服务器性能带来多大的提升,而且也会增加系统崩溃时数据丢失的数量。
随着服务器写请求的增多,AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。
快照 snapshots:缺省情况下,Redis把数据快照存放在磁盘上的二进制文件中,文件名为dump.rdb。原理:Redis forks,子进程开始讲数据写到临时的RDB文件中,当子进程完成写RDB文件,用新文件替换老文件。这种方式可以使Redis使用copy-on-write技术。
快照 AOF:快照模式不健壮,当系统停止或者Redis进程被关闭,写入Redis的数据就会丢失。
虚拟内存方式:当key很小,value很大时,使用虚拟内存的效果会比较好,节约较大的内存。
12、Redis缓存失效策略和主键失效机制
作为缓存系统都要定期清理无效数据,就需要一个主键失效和淘汰策略。
在Redis中,有生存期的key被称为volatile,在创建缓存时,要为给定的key设置生存期,当key过期时,它可能会被删除。
生存时间可以通过DEL命令来删除整个key来移除,或者被SET和GETSET命令覆盖原来的数据,也就是说,修改key对应的value和使用另外相同的key和value来覆盖后,当前的数据的生存时间不同。使用PERSIST命令可以在不删除key的情况下,移除key的生存时间,让key重新成为一个persistent key。
对一个带有生存时间的key执行EXPIRE命令,新指定的生存时间会取代旧的生存时间。
13、最大缓存配置
在redis中,允许用户设置最大使用内存的大小,server.maxmemory默认为0,没有指定最大缓存,如果有行的数据添加,超过最大内存,则会使redis崩溃。redis数据集大小上升到一定大小的时候,就会实行淘汰策略。
14、使用Redis的场景
会话缓存 Session Cache;全页缓存 FPC;队列;排行榜/计数器;发布/订阅。
15、Redis支持的Java客户端有哪些
Redisson,Jedis,lettuce等,官方推荐Redisson。
16、Jedis和Redisson对比
Jedis是Redis的java实现的客户端,API提供了较全面的Redis命令支持。
Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。
17、Redis的内存优化
尽可能的使用散列表 hash,散列表使用的内存非常小。
18、一个字符串的值存储最大容量是512M 。
19、Redis哈希槽
Redis集群没有使用一致性hash,而是使用了hash槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来确定放置哪个槽。
20、 Redis的主从复制模型
为了使在部分及诶按失败或者发部分节点无法通信的情况下集群仍然可以使用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品。
三、缓存
1、缓存雪崩
数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库cpu和内存负载过高,甚至宕机。
防止缓存雪崩的方式:
缓存的高可用性
缓存层设计成高可用,防止缓存大面积故障。即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务,例如 Redis Sentinel 和 Redis Cluster 都实现了高可用。
缓存降级
可以利用ehcache等本地缓存(暂时支持),但主要还是对源服务访问进行限流、资源隔离(熔断)、降级等。
当访问量剧增、服务出现问题仍然需要保证服务还是可用的。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级,这里会涉及到运维的配合。
降级的最终目的是保证核心服务可用,即使是有损的。比如推荐服务中,很多都是个性化的需求,假如个性化需求不能提供服务了,可以降级补充热点数据,不至于造成前端页面是个大空白。
在进行降级之前要对系统进行梳理,比如:哪些业务是核心(必须保证),哪些业务可以容许暂时不提供服务(利用静态页面替换)等,以及配合服务器核心指标,来后设置整体预案,比如:
(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
(2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
(3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
(4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
Redis备份和快速预热
Redis数据备份和恢复;
快速缓存预热。
提前演练
最后,建议还是在项目上线前,演练缓存层宕掉后,应用以及后端的负载情况以及可能出现的问题,对高可用提前预演,提前发现问题。
2、缓存穿透
缓存穿透是指查询一个不存在的数据,由于在redis里面没有命中,需要从mysql或者其他数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要去mysql或者其他数据库查询,造成缓存穿透。
解决办法:
如果查询数据库为空,那设置一个默认值放置在缓存中,设置一个过期时间或者单有值时将缓存中的值替换掉;可以给key设置一些规则,然后查询之前先过滤掉不符合规则的key。
3、缓存并发
redis是单进程单线程操作,多个client并发操作,按照先到先执行的原则,其余的阻塞。另外的解决方案是把redis.set操作放在队列中使其串行化,必须一个一个的执行。
4、缓存预热
缓存预热就是系统上线后,将相关的珲春数据直接加载到缓存系统,这样可以避免在用户请求时,先查询数据库,然后再将数据缓存的问题。
解决思路:使用缓存刷新页面,人工刷新;数据量不大时,可以在项目启动时自动进行加载。
5、redis缓存和mysql数据一致性
不管是先写mysql数据库,再删除redis缓存,还是先删除缓存,在写库,都有可能出现数据不一致的情况。
解决方案:
(1)采用延时双删策略,在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。具体步骤是:先删除缓存,再写数据库,设置休眠时间,再次删除缓存。
设置的休眠时间主要是根据业务逻辑的耗时进行设计,确保读请求结束,写请求可以删除读请求造成的缓存脏数据。当然这个时间还需要考虑redis和数据库主从同步的耗时。
设置缓存过期时间,给缓存设置过期时间,是保证最终一致性的解决方案。所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新的值然后回填缓存。
结合双删策略+缓存超时设置,最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求耗时。
(2)异步更新缓存
Mysql binlog增量订阅消费+消息队列+增量数据更新到redis;读Redis时,热数据基本都在redis;写mysql时,增删改都是操作mysql;更新redis数据:mysql的数据操作binlog,来更新到redis;
redis更新主要数据操作分为全量操作(将全部数据一次性写入到redis)和增量操作(实时更新),这里的增量指的是mysql的update,insert,delete变更的数据。
读取binlog分析,利用消息队列,推送更新各台的redis缓存数据,这样一旦mysql中产生了行的写入、更新、删除等操作,就可以把binlog相关的信息推送到redis,redis在根据binlog的记录,对redis进行更新。这种机制很类似mysql的主从备份机制。