1、概述:Redis是一款基于内存以KV键值对存储的中间件技术,常用做缓存,支持数据持久化。
2、数据类型:
常用5种数据类型:String(字符串)、List(列表)、set(集合)、hash(哈希)、zset(有序集合)
结构类型 | 结构存储的值 | 应用 |
---|---|---|
String(字符串) | 可以是字符串、整数或浮点数 | 计数、共享session信息 |
List(列表) | 双向链表,链表上的每个节点都包含一个字符串 | 消息队列 |
set(集合) | 包含字符串的无序集合 | 聚合(交并差),点赞,抽奖,共同关注 |
hash(哈希) | 包含键值对的无序散列结合 | 缓存对象,购物车 |
zset(有序集合) | 和散列一样,用于存储键值对(score字段排序) | 排行榜 |
3、Java操作Redis步骤
步骤一:导入坐标
org.springframework.boot
spring-boot-starter-data-redis
步骤二: 在application.properties中添加redis相关配置
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=192.168.0.24
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=1000
步骤三:在需使用Redis的地方@Autowired装配
@Autowired;
private RedisTemplate redisplate;
Spring-data-redis后它为我们提供了两个模板类帮助我们实现CRUD:
(1)RedisTemplate key value泛型都是object
(2)StringRedisTemplate key value泛型都是string
注意:
五大数据类型 : redisTemplate.opsForValue();//操作字符串 *
redisTemplate.opsForList();//操作List *
redisTemplate.opsForSet();//操作Set *
redisTemplate.opsForZSet();//操作ZSet *
redisTemplate.opsForHash();//操作Hash *
例如:添加套餐信息时,有套餐图片的选择,上传至七牛云服务器返回一个文件名存入数据库。
在实际项目中为提高用户体验,选择文件后就会上传至服务器。但用户选择图片后若选择取消按钮,那么服务器上就会产生“脏写”。解决这一问题可引入Redis,当用户选择图片上传至服务器后返回的文件名存入key为serverPic的set集合中,点击添加按钮后将写入数据库的同时把文件名再存入到一个key为dbPic的set集合中。启动任务调度对serverPic与dbPid作差集遍历删除服务器的文件与Redis中的文件名。
异步上传图片文件接口代码:
添加按钮请求的接口:
启动任务调度:
选择预约套餐后填写基本信息,点击发送验证码:以邮箱作为key,验证码内容为value存储至Redis中并设置key的有效时间为5分钟,信息输入点击提交会将输入的验证码与Redis中的码做比对
发送邮件并存储验证码至Redis:
与Redis中的数据比对:
下面补充一些面试中Redis的相关问题:
(1)高性能:假如用户是第一次访问数据库中的某些数据,这个过程是比较慢,毕竟是硬盘中读取。但是,如果说,用户访问的数据属于高频数据并且不经常改变的话,那么我们就可以很放心地将用户访问的数据存在缓存中。保证用户下一次在访问的时候就可以直接从缓冲中获取了,操作缓存就是直接操作内存,速度会很快
(2)高并发:一般像MYSQL这里的数据库QPS大概在1W左右,但是使用redis缓存很容易达到10W+
QPS:服务器每秒可以执行的查询次数
直接操作缓存能够承受的数据库请求数量远远大于直接访问数据库的,所有我们可以考虑把数据库中的部分数据转移到缓存中,这样用户的一部分请求会直接到缓存这里而不用经过数据库。进而,提高了系统整体的并发
因为内存是有限的,如果缓存中的所有数据都是一直保存的话,很容易Out Of Memory
Redis自带了给缓存数据设置过期时间的功能
注意:Redis中除了字符串类型有自己独有设置过期时间的命令setex外,其他数据类型都需要依靠expire命令来设置过期时间。另外persist命令可以移除一个键的过期时间。
应用场景:需要某个数据只在某一段时间段内存在,如验证码3分钟内有效
Redis通过一个叫做过期字典(可以看作是hash表)来保存数据过期的时间。过期字典的键指向Redis数据库中某个key,过期字段的值是一个long long类型的整数,这个整数保存了key所指向的数据库键的过期时间(毫秒精度的UNIX时间戳)
过期字典存储在redisDB中
typedef struct redisDb { dict *dict; //数据库键空间,保存着数据库中所有键值对 dict *expires // 过期字典,保存着键的过期时间 ... } redisDb;
常用的过期数据的删除策略就两个:
(1)惰性删除:只会在取出key的时候才对数据进行过期检查。这样对cpu最友好,但是可能会早晨太多过期key没有被删除
(2)定期删除:每隔一段时间抽取一批key执行删除过期key操作。并且,Redis底层会通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响
定期删除对内存更加友好,惰性删除对CPU更加友好。两者各有千秋,所以Redis采用的是定期删除+惰性删除
缓存穿透
缓存穿透是指用于请求的数据在缓存中不存在即没有命中,同时在数据库中也不存在,导致用户每次请求该数据都要去数据库中查询一遍,然后返回空。
如果有恶意攻击者不断请求系统中不存在的数据,会导致短时间大量请求落在数据库上,造成数据库压力过大,甚至击垮数据库系统。
缓存穿透常用解决方案:
1、布隆过滤器 2、返回空对象
1、布隆过滤:布隆过滤器其实采用的是哈希思想来解决这个问题,通过一个庞大的二进制数组,走哈希思 想去判断当前这个要查询的这个数据是否存在,如果布隆过滤器判断存在,则放行,这个请求会去访问 redis,哪怕此时redis中的数据过期了,但是数据库中一定存在这个数据,在数据库中查询出来这个数 据后,再将其放入到redis中, 假设布隆过滤器判断这个数据不存在,则直接返回 这种方式优点在于节约内存空间,存在误判,误判原因在于:布隆过滤器走的是哈希思想,只要哈希思 想,就可能存在哈希冲突
2、缓存空对象思路分析:当我们客户端访问不存在的数据时,先请求redis,但是此时redis中没有数据, 此时会访问到数据库,但是数据库中也没有数据,这个数据穿透了缓存,直击数据库,我们都知道数据 库能够承载的并发不如redis这么高,如果大量的请求同时过来访问这种不存在的数据,这些请求就都会 访问到数据库,简单的解决方案就是哪怕这个数据在数据库中也不存在,我们也把这个数据存入到redis 中去,这样,下次用户过来访问这个不存在的数据,那么在redis中也能找到这个数据就不会进入到缓存
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,请求直接落在数据库上,引起数据库压力过大甚至宕机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库
解决方案:给不同key的TTL添加随机值、利用Redis集群提高服务可用性
给缓存业务添加降级限流策略、给业务添加多级缓存
缓存击穿问题也叫热点key问题,就是一个被高并发访问并且缓存重建业务比较复杂的key突然失效了,无数的请求访问会再瞬间给数据库带来巨大的冲击。常见的解决方案有两种:互斥锁、逻辑过期
6种淘汰策略,Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据;
○no-eviction:当内存不足以容纳新写入数据时,新写入操作会报错;
○allkeys-lru: 当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key; ○allkeys-random: 当内存不足以容纳新写入数据时,在键空间中,随机移除某个key; ○volatile-Iru: 当内存不足以容纳新写入数据时,在设置了过期时间的键健空间中,移除最近最少使用的key;
○volatile-random: 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key;
○volatile-ttl: 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除;