redis入门
文章目录
前言
以下是本篇文章正文内容,如有表达不全的问题还请指出,本文中插入的图片由网上下载,如涉及版权问题联系我删除,本文参考附属链接以及b站黑马课程。
一、sql与NoSql区别
1、 sql:关系型,结构化,有约束条件,事务acid特性,mysql
2、 nosql:非关联,非结构化,键值对/文档json嵌套/图表格式存 储,事务基本base特性,redis、mongo、elasticsearch
截图来自黑马程序员课程
总结:
解决关系型数据库缺点的思路:
nosql优势:
1、高并发读写
2、海量数据的高效率存储和访问
3、高可扩展性和高可用性
二、redis特征、数据类型、安装、连接
redis:内存高速缓存数据库,在服务器中用来存储一些需要频繁调取的热点数据,极大地提升速度,节约直接从磁盘读取数据的I/O开销
端口号:6379
掌握5种基本数据类型
:
重要!!!上图数据类型介绍以及在 linux系统安装redis及其java连接参考:https://www.oz6.cn/articles/58
redis连接报错:ERR Client sent AUTH, but no password is set
参考:https://blog.csdn.net/u014026084/article/details/105767907问:Windows系统能够满足我们日常开发需求,为什么要学习Linux?
1、Linux是开源的,用它来做服务器成本比较低;
2、我们开发的项目,绝大部分都会部署到Linux上
如果是Windows安装,cmd运行的命令参考:https://blog.csdn.net/u014212540/article/details/127866782
三、redis实战
1、基于session/redis的短信登录流程
- 做手机号验证,通过则构造手机验证码:使用random工具生成随机数作为验证码;
- 使用接口向短信平台发送手机号和验证码数据,然后短信平台再把验证码发送到指定手机号上,接口参数一般包括:手机号,随机验证码(或包含失效时间),平台接口地址,平台口令等;
- 接口返回成功则将手机号、验证码、操作时间存入
Session/Redis
中,作为后面验证使用。- 用户输入验证码点击登录,后台接收用户填写的验证码、手机号等;
- 对比提交的验证码与
Session/Redis
中的验证码是否一致,同时判断提交动作是否在有效期内;- 验证码正确且在有效期内,请求通过,再处理相应的业务。
- 存储格式:key:手机号保证唯一性(redis采用token作为key);value:验证码code、时间、用户名称等参数(redis可采用hashMap格式存储用户信息);
- 前端所做操作:接收login登录接口返回的cookie信息,或者是token,cookie信息前端不需要处理,如果是后续判断登录状态就可以在拦截器里获取用户请求时自动携带的cookie信息去和session库里的sessionId做比较。如果是获取token,可以保存到浏览器的sessionStorage中,后续该用户在操作其他页面时就会把token传到后端去处理,然后后台再根据token去redis库中的查找。
- 通过redis实现时需要token设置过期时间,比如半个小时,半个小时后就清除,避免内存溢出,只有用户在不断访问(通过登录校验操作判断),就需要不断更新有效期,而不是强制性到30分钟清除,可以在拦截器中实现,拦截验证通过就刷新有效期,没验证通过就跳出拦截器不需要做刷新操作。
参考:http://t.csdn.cn/4EvmD,
https://blog.csdn.net/qq_59138417/article/details/124578833,
参考课程:https://www.bilibili.com/video/BV1cr4y1671t?p=24&vd_source=4b1fe79c18ac174f9c6e77cc3b68e863
区别:
- session保存信息过多容易内存泄露,且返回给客户端的cookie信息容易被人窃取,不安全,但是实现较简单;
- redis容量大,采用k-v模式存储,且value存储格式多样化,key用token存储更安全
2、查询商铺信息添加、更新redis缓存
参考:https://www.bilibili.com/video/BV1cr4y1671t?p=35&vd_source=4b1fe79c18ac174f9c6e77cc3b68e863
查询商铺信息添加redis缓存实现方案:
java代码(读操作:缓存命中则返回,未命中则查数据库并写入缓存+设超时时间):
public Result queryById(Long id) {
// 先从redis查是否存在
String shopJson = stringRedisTemplate.opsForValue().get("cache:shop:" + id);
if (StrUtil.isNotBlank(shopJson)) {
Shop shop = JSONUtil.toBean(shopJson, Shop.class);
// 不为空就返回
return Result.ok(shop);
}
// 不存在就查数据库
Shop shop1 = getById(id);
if (shop1 == null) {
return Result.fail("店铺不存在");
}
// 存在则写入redis缓存,尽可能保证一致性,设置一个过期时间
stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop1), 30, TimeUnit.MINUTES);
return Result.ok(shop1);
}
redis缓存查询:
第二次再次调用该接口,明显速度快很多,且第二次不会去数据库查,而是直接从redis。但如果做了修改数据库的操作,可能会导致数据库与缓存中数据不一致,那么需要对缓存进行更新,有三种方法:
java代码写操作(先写数据库,再删缓存,确保原子性):
public Result update(Shop shop) {
if (shop.getId() == null) {
return Result.fail("店铺id不能为空");
}
// 更新数据库
updateById(shop);
// 删除缓存
stringRedisTemplate.delete("cache:shop:" + shop.getId());
return Result.ok();
}
3、缓存穿透
参考:https://www.bilibili.com/video/BV1cr4y1671t?p=40&vd_source=4b1fe79c18ac174f9c6e77cc3b68e863
java代码修改(通过写入控制至缓存,判读为空时就不会去查数据库了):
4、缓存崩溃
参考:https://www.bilibili.com/video/BV1cr4y1671t?p=42&vd_source=4b1fe79c18ac174f9c6e77cc3b68e863
5、缓存击穿
java代码:
@Override
public Result queryById(Long id) {
// 互斥锁解决缓存击穿
Shop shop = queryWithMutex(id);
if (shop == null){
return Result.fail("店铺不存在");
}
return Result.ok(shop);
}
public Shop queryWithMutex(Long id) {
// 先从redis查是否存在
String shopJson = stringRedisTemplate.opsForValue().get("cache:shop:" + id);
if (StrUtil.isNotBlank(shopJson)) {
// 不为空就返回
return JSONUtil.toBean(shopJson, Shop.class);
}
// 命中控制就返回错误值,而不是
if ("".equals(shopJson)) {
return null;
}
// 缓存重建:1、获取互斥锁;2、获取失败则休眠并重试;3、成功则查数据库并重建缓存
String lockKey= "lock:shop:"+id;
Shop shop1 = new Shop();
try {
boolean isLock = tryLock(lockKey);
if (!isLock) {
// 休眠重试
Thread.sleep(50);
queryWithMutex(id);
}
// 获取锁成功:不存在就查数据库
shop1 = getById(id);
// 模拟重建延迟
Thread.sleep(200);
if (shop1 == null) {
// 不存在则写入空值,防止缓存击穿
stringRedisTemplate.opsForValue().set("cache:shop:" + id, "", 2, TimeUnit.MINUTES);
return null;
}
// 存在则写入redis缓存,尽可能保证一致性,设置一个过期时间
stringRedisTemplate.opsForValue().set("cache:shop:" + id, JSONUtil.toJsonStr(shop1), 30, TimeUnit.MINUTES);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
unLock(lockKey);
}
return shop1;
}
private boolean tryLock(String key) {
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key,"1", 10, TimeUnit.MINUTES);
return BooleanUtil.isTrue(flag);
}
private void unLock(String key) {
stringRedisTemplate.delete(key);
}
6. RedisTemplate.execute()与opsFor()方法区别
封装RedisTemplate工具类,使用execute方法
区别:
execute() 需要 RedisConnection 对象,通过 RedisConnection 操作 Redis
被称为低级抽象(Low-Level Abstractions)opsFor 之类的被称为高级抽象(High-Level Abstractions),是为了提供更友好的模板类,底层还是调用的execute(),需要 RedisConnection 对象。所以我觉得 opsFor 就是对 execute() 的进一步封装。
参考:https://blog.csdn.net/bingguang1993/article/details/83782118
7. redis相关面试问题
8、redis五种数据结构,各自对应的场景
- 字符串(String):用于存储字符串,可以是任意类型的数据,例如整数、浮点数、JSON等。字符串的最大长度为 512MB。它适用于各种常用场景,比如将经常使用的数据存储在 Redis缓存、redis自增命令实现计数器、队列、分布式锁等。
- 列表(List):有序的字符串集合,可以从两端进行插入和删除操作。列表可以用于实现消息队列、社交网络的时间线等。
- 哈希(Hash):键值对的无序集合,可以对其中的单个键进行增、删、改、查操作。哈希适用于存储对象、用户属性、配置信息等。
- 集合(Set):无序的字符串集合,不允许重复元素的存在。集合提供了强大的集合操作,比如交集、并集、差集等。它适用于实现标签、兴趣爱好、好友关系、白名单等。
- 有序集合(Sorted Set):无序的字符串集合,每个元素都会关联一个分值,根据分值进行排序。有序集合既可以根据元素的插入顺序排序,也可以按照分值的大小进行排序。它适用于排行榜、计分系统、优先级队列等。
9、redis添加白名单
添加白名单,有很多可选的方案:配置文件写死、数据库配置一个白名单表 、redis的set实现白名单。
redis的set实现白名单:在登录、评价或在一些需要限制的地方,通过sismember命令来查询用户是否在白名单列表中,如果存在就返回1,不存在就返回0。参考:https://blog.csdn.net/mutf7/article/details/119841687