1、redis常见的使用场景
- 缓存:例如:热点数据缓存(例如报表、明星出轨),对象缓存、全页缓存、可以提升热点数据的访问数据
- 数据共享分布式 :如token 因为 Redis 是分布式的独立服务,可以在多个应用之间共享
- 分布式锁 :String 类型setnx方法,只有不存在时才能添加成功,返回true
- 全局ID int类型,incrby,利用原子性 例子:incrby userid 25 。incrby 命令用于将存储在键上的数字按指定的值增加。 如果键不存在,则在执行操作之前将其设置为
0
- 计数器 Redis Incr 命令将 key 中储存的数字值增一。例如:文章的阅读量、微博点赞数
- 购物车 key:用户id;field:商品id;value:商品数量。
- 点赞、签到、打卡
假如微博ID是t1001,用户ID是u3001 用 like:t1001 来维护 t1001 这条微博的所有点赞用户
点赞了这条微博:sadd like:t1001 u3001
取消点赞:srem like:t1001 u3001
点赞的所有用户:smembers like:t1001
-
维护商品所有的标签
用 tags:i5001 来维护商品所有的标签
sadd tags:i5001 画面清晰细腻
sadd tags:i5001 真彩清晰显示屏
sadd tags:i5001 流程至极
2、redis的五种数据类型及常见的使用场景
- string(字符串) 它能够存储任何形式的数据,包括字符串、图片、视频等,允许单个字符类型的最大容量为512MB,一般常用在需要计数的场景
- list(列表) Redis 的 list 的实现为一个 双向链表,即可以支持反向查找和遍历,更方便操 。发布与订阅或者说消息队列
- hash(字典) 常用在系统中对象数据的存储。电商网站购物车的设计与实现。为了区别与Redis中的键值对的称呼,hash中的键称为field,hash(字典) 是由field和关联的value组成的map,其中field和value都是字符串类型。
- set(集合) 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景,共同喜爱,共同粉丝等场景
- zset(有序集合) 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息。会员短期体验之过期失效
3、redis常见的持久化机制
RDB 指定的时间间隔能对你的数据进行快照存储,实际过程是fork主进程得到子进程,子进程共享主进程的内存数据。先将数据集合写入临时文件、写入成功后,再替换之前的文件,整个数据库只有一个dump.rdb文件,方便持久化
AOF:每一个收到的写命令都通过write函数追加到文件中,AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF。可以定期对aof文件进行重写,达到压缩目的
AOF文件比RDB 更新频率高,优先使用AOF还原数据,如果两个都配了,优先加载AOF
4、缓存雪崩、缓存穿透、缓存击穿
缓存雪崩是指在同一时间内由于大量的缓存失效,导致大量原本应该去访问缓存的请求都去查询数据库,而对数据库的cpu和内存造成巨大的压力,严重的可能导致数据库宕机
解决方案:
-
事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃,设置不同的失效时间,在缓存进行失效时间设置的时候,从某个适当的值域中随机一个时间作为失效时间即可
-
事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
-
事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
缓存穿透是指查询一个一定不存在的数据,由于缓存时不命中就从数据库查询,查不到就不写入数据库中,导致这个不存在的数据每次请求都会去数据库查看,引起缓存穿透
解决方案:
- 非法请求的限制,因此在 API 入口处我们要判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库
- 对查询结果为空的情况下,也进行缓存,该缓存的key设计短点
- 通过布隆过滤器过滤一个一定不存在的key,避免对底层系统带来查询的压力
缓存击穿:
如果缓存中的某个热点数据过期了,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接访问数据库,数据库很容易就被高并发的请求冲垮,这就是缓存击穿的问题
解决方案:
-
若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。
-
若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。
-
若缓存的数据更新频繁或者缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动的重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存
5、如何保证缓存和数据库数据一致性
1.先更新数据库再删除缓存
2.采用延时双删策略:在写库前后都进行删除缓存操作,并且设置合理的超时时间
3.基于订阅binlog的同步机制(可以采用canal),一步更新缓存
6、Redis过期删除策略
惰性删除:只有当访问一个key时候,才会判断该key是否已经过期,过期才清理
定期删除:每一段时间,会扫描一定数量的数据库expires字典中的key,并清理其中已经过期的key
可以通过修改配置文件 redis.conf
的 hz
选项来调整这个次数:
Redis中同时使用了惰性过期和定时过期两种过期策略
7、Redis淘汰策略
当使用的内存大于 maxmemory
时,就会触发 Redis 主动淘汰内存方式,在 redis.conf
配置文件中,可以设置 maxmemory-policy
来设置内存淘汰方式:比如:maxmemory-policy noeviction
常见的淘汰策略:
volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰;
volatile-ttl:从已设置过期时间的数据集中移除即将过期的 key (minor TTL);
volatile-random:从已设置过期时间的数据集中任意选择数据淘汰 ;
allkeys-random:无差别的随机移除;
allkeys-lru:从数据集合中挑选最近最少使用的数据淘汰
noeviction:不移除任何 key,只是返回一个写错误 ,默认选项,一般不会选用。
8、基于redis的分布式锁
分布式锁,是一种跨进程的跨机器节点的互斥锁,它可以用来保证多机器节点对于共享资源访问的排他性。我觉得分布式锁和线程锁本质上是一样的,线程锁的生命周期是单进程多线程,分布式锁的声明周期是多进程多机器节点。
基于Redis分布式锁,它里面提供了SETNX命令可以实现锁的排他性,当key不存在就返回1,存在就返回0。然后还可以用expire命令设置锁的失效时间,从而避免死锁问题。
当然有可能存在锁过期了,但是业务逻辑还没执行完的情况。 所以这种情况,可以写一个定时任务对指定的key进行续期。Redisson这个开源组件,就提供了分布式锁的封装实现,并且也内置了一个Watch Dog机制来对key做续期。Redis是一个AP模型,所以在集群架构下由于数据的一致性问题导致极端情况下出现多个线程抢占到锁的情况很难避免
9、redission常见的问题
lock.tryLock(10, TimeUnit.SECONDS);如果指定时间,在锁时间到期后就不会自动续期,可能造成当业务逻辑执行时间超过10s,锁已经释放,此时如果还有其他线程访问,又可以得到锁,当业务逻辑在30s执行完成后,它删除的是下一个线程的锁,而不是它自己的锁
lock.tryLock();如果没有指定时间,锁默认时间为30s,只要占锁成功,就会启动一个定时任务,每隔10s中就会自动给锁重新设置过期时间,新的过期时间就是看门狗的默认时间(30s)
10、布隆过滤器
布隆过滤器可以判断某个数据一定不存在,但是无法判断一定存在。
Redis 实现布隆过滤器的底层就是通过 bitmap 这种数据结构,至于如何实现,这里就不重复造轮子了,介绍业界比较好用的一个客户端工具——Redisson。
应用场景
- 网页爬虫对URL的去重,避免爬去相同的URL地址。
- 垃圾邮件过滤,从数十亿个垃圾邮件列表中判断某邮箱是否是杀垃圾邮箱。
- 解决数据库缓存击穿,黑客攻击服务器时,会构建大量不存在于缓存中的key向服务器发起请求,在数据量足够大的时候,频繁的数据库查询会导致挂机。
- 秒杀系统,查看用户是否重复购买。
public static void main(String[] args) {
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.14.104:6379");
config.useSingleServer().setPassword("123");
//构造Redisson
RedissonClient redisson = Redisson.create(config);
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("phoneList");
//初始化布隆过滤器:预计元素为100000000L,误差率为3%
bloomFilter.tryInit(100000000L,0.03);
//将号码10086插入到布隆过滤器中
bloomFilter.add("10086");
//判断下面号码是否在布隆过滤器中
System.out.println(bloomFilter.contains("123456"));//false
System.out.println(bloomFilter.contains("10086"));//true
}
11、Redis集群方案
redis有三种集群模式:主从模式、哨兵模式、分片集群模式
生产上常用分片集群方案
使用分片集群可以解决上述问题,分片集群特征:
1、集群中有多个master,每个master保存不同数据 每个master都可以有多个slave节点 。
2、master之间通过ping监测彼此健康状态 客户端请求可以访问集群任意节点,最终都会被转发到正确节点
3、redis-cluster把所有的物理节点都映射到0-16383的slot上,集群负责维护每个节点上数据slot的分配。
12、简述全量同步的流程
1.slave节点请求增量同步
2.master节点判断replid(简述全量同步的流程),发现不一致,拒绝增量同步
3.master将完整内存数据生成RDB,发送RDB到slave ,slave清空本地数据,加载master的RDB
4.master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
5.slave执行接收到的命令,保持与master之间的同步