redis是什么?
是一种以键值对形式存储的数据库,特点是基于内存存储,读写快,性能高,常用于缓存、消息队列等应用情境
redis的五种数据类型是什么?
分别是String、Hash、List、Set和Zset(操作命令很多这里只写部分关键的,其他查一查即可)
①String类型
redis中最基本的数据结构,key是String类型,value可以存储字符串、整型或浮点数
SET <KEY> <VALUE> //写入
GET <KEY> //读取
②Hash类型
在value部分提供了一个field作为value的键,因此一个Hash可以存储多个字段和对应的值,适合用来存储对象
HSET <KEY> <FIELD> <VALUE> //写入
HGET <KEY> <FIELD> //读取
③List类型
有序可重复,相当于LinkedList,可以从列表的两端进行插入或删除
LPUSH <KEY> <ELEMENT> //从列表左边插入
LPOP <KEY> //移除左侧第一个元素
右侧同理,用RPUSH和RPOP
④Set类型
无序不重复
SADD <KEY> <MEMBER>... //添加一个或多个
SREM <KEY> <MEMBER>... //删除一个或多个
SCARD <KEY> //返回元素个数
⑤Zset类型
有序集合,每个元素都带有一个score属性,用score来排序
ZADD <KEY> <SCORE> <MEMBER>... //添加一个或多个
ZREM <KEY> <MEMBER>... //删除一个或多个
ZSCORE <KEY> <MEMBER> //获取指定元素的score值
在Java中怎么使用redis?
我们会用RedisTemplate
使用方法:
①导入依赖
<!--Redis-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--连接池依赖-->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>
②在application.yml中配置redis
spring:redis:host:127.0.0.1 # Redis服务器地址port:6379 # Redis服务器连接端口password: # Redis服务器连接密码(默认为空)lettuce:pool:max-active:8 #最大连接max-idle:8 #最大空闲连接min-idle:0 #最小空闲连接max-wait:100 #连接等待时间
③在你要的文件中注入RedisTemplate
@Autowired
private RedisTemplate redisTemplate
④代码实例
redisTemplate.opsForValue.set(key,value); //存
String id = redisTemplate.opsForValue.get(key); //取
redisTemplate.delete(key); //删除
可以在存数据的时候设置超时时间,避免因为只存出现内存不足的情况(如果不引入超时删除,就会出现redis只存不删的情况,时间长了内存会爆)
redisTemplate.opsForValue.set(key,value,超时时间,超时单位); //存
可以给已有的键值对设置过期时间
redisTemplate.expire(key,超时时间,超时单位);
提示:key可以用 : 来分隔,这样可以清楚层级,比如 login:user:code
Redis作用
一、redis+session登录校验
二、缓存数据
优点:降低后端负端,提高读写效率和性能,更好应对高并发
以前的项目中我们是直接访问数据库,众所周知mysql的性能很差,那我们就可以引入redis作为缓存,当用户发起请求时,我们可以先去redis看看有没有对应的数据,如果有我们就直接取用,不用访问数据库了;如果没有我们才去数据库取,同时把读取到的数据写入redis,方便下一次取用
但是会有一个问题:如果redis或者mysql其中一个地方的数据发生更改,没有通知另一个,就会出现redis中的数据和数据库中的数据不一致,也就是redis和数据库的缓存一致性问题
为了解决缓存一致性问题,我们可以使用缓存更新策略
第一种是内存淘汰,就是redis中存太多了它会自动杀掉
第二种是超时剔除,就是前面的给缓存设置超时时间
第三种是主动更新,就是我们手动添加逻辑,一致性最好,但是也最麻烦
因此我们常常采用主动更新策略,里面又有三种
第一种是旁路缓存,也是最常用的方法:读操作先找缓存,没有再找数据库,然后把数据保存到缓存;写操作就是在更新数据库后删除缓存。这样操作相对最少出问题
第二种是读写穿透,将缓存和数据库整合成一个服务。调用者只需与缓存交互,读操作缓存未命中时,服务从数据库加载数据并写入缓存;写操作时,服务先更新缓存,再同步更新数据库,但是实现难一点,而且对数据库要求高
第三种是异步写回,调用者只操作缓存,然后缓存再异步更新到数据库中,有可能出现一致性问题
三、缓存穿透等问题
(1)缓存穿透
缓存穿透就是指用户请求的数据在缓存和数据库中都不存在(比如乱传一个id),那么因为缓存没有这个数据,每次请求都会打到数据库,然后数据库返回一个null;如果有人故意大量制造这样的请求,就会有大量的请求直接打到数据库,导致数据库崩溃,这就是缓存穿透问题
解决缓存穿透有主要两种方法:
① 缓存空对象,如果用户请求一个在缓存和数据库中都不存在的数据,那就缓存null值,当下一次再请求,就直接返回null。但我觉得没什么鸟用因为只要请求一直换他就要在redis一直新增缓存直接爆炸
② 布隆过滤 在请求到达缓存前,先用布隆过滤器进行判断,存在就放行,不存在就拒绝
其余方法有加强权限校验,设置限流等
(2)缓存雪崩
缓存雪崩是指同一时间大量的key失效或者redis直接宕机导致大量的请求直接打到数据库
解决方法:
① 给不同的key添加不同的TTL随机值
② 用redis集群 主从机制 哨兵检测 主挂了就换一个从上
③ 使用多级缓存
④ 做限流
(3)缓存击穿
缓存击穿是指某些被大量访问且缓存重建复杂的热点key失效了,导致大量的请求直接打到数据库
解决方法:
①互斥锁
当大量请求来到时,只让第一个到达的线程进行缓存的重建,此时其余请求会被卡住,直到这个缓存重建完成,优点是返回的数据是准确的,缺点是性能差
对于第一个线程:获得互斥锁,进行缓存重建
对于后面的线程:获取不到互斥锁,休眠,过段时间重试
②逻辑过期
热点key永不过期,但是要设置一个过期时间的字段(不是设置过期时间)。当请求到达时会根据过期时间的字段进行判断,如果过期了,那就用互斥锁,创建一个新的线程单独负责缓存的重建,而它和其余线程直接返回当前key的数据(也就是错误的旧数据),优点是线程无需等待性能好,缺点是数据一致性差
对于第一个线程:判断缓存是否过期,如果过期,获得互斥锁,新建一个进程进行缓存重建,自己先返回旧的数据
对于后面的线程:获取不到互斥锁,直接先返回旧的数据
提示:在获取到锁后最好再进行一次判断
(4)超卖问题
超卖问题就是比如有一个秒杀活动,此时大量的线程发起购买请求,线程a查到库存为1,线程b查到库存也为1,然后线程a调用请求会让库存 -1,而线程b不知道(因为查到的库存为1),所以也会让库存 -1,本来库存只有1,现在却触发了两次扣减库存,让库存为 -1,这就是超卖问题
解决方法:加锁
①悲观锁
悲观指认为线程安全问题一定会发生。因此在操作数据之前需要先获取锁,让这些线程串行执行,优点是不出错,缺点是性能差
synchronized、lock都属于悲观锁
②乐观锁
乐观指认为线程安全问题不一定发生,不加锁,只在数据进行更新操作时检查在此期间数据是否被更改
乐观锁的方法通常会给数据额外引入一个标识(比如版本号或者时间戳)来标记数据是否被更改
对于一个线程来说,去更新时发现如果数据已被更改就更新失败;否则更新成功
但是这些只在单一服务下好用,多集群就失效了,要用分布式锁