1. Redis入门
1.1 基本特性
- Redis是一款基于键值对的NoSQL数据库
- 支持多种数据结构:字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等。
- Redis将所有的数据都存放在内存中,所以它的读写性能十分惊人。
- Redis还可以将内存中的数据以快照或日志的形式保存到硬盘上,以保证数据的安全性。(快照性能好,日志实时性高)
- Redis典型的应用场景包括:缓存、排行榜、计数器、社交网络、消息队列等(只要是频繁查询局部信息都可以用)
1.2 数据结构的使用
//1)开启redis
redis-cli
//2)选择redis库,有好几个
select 1;
select 2;
//3)清除分库的内容
flushdb
//4)查询库所有内容
keys *
//4)数据类型一:字符串(经常用户点赞数的统计)
set test:count 1 //给变量count赋值为"1"
set test:string abc //给变量string赋值为abc
get test:count //获取变量count的值
incr test:count //对变量自增1
decr test:count //对变量自减1
//5)数据类型二:哈希(map)
hset test:user username zhangsan //为哈希表的usename赋值zhagnsna
hset test:user id 1 //为哈希表的id赋值1
hget test:user id //查询
hget test:user username //查询
//6)数据类型三:双向列表(list)(可以从前插入和从后插入,查询前面和后面)
lpush test:ids 101 102 103 //从左边依次插入三个
llen test:ids //查询数组长度
lindex test:ids 0 //查询数组索引为0的值,103
lindex test:ids 2 //查询数组索引为2的值,101
lrange test:ids 0 2 //批量范围查询
rpop test:ids //从右边开始删除弹出值,101
rpop test:ids //从右边开始删除弹出值,102
//7)数据类型四:集合sets(无序)
sadd test:teachers aaa bbb ccc ddd //存入数据
scard test:teachers //查询集合的大小
spop test:teachers //随机弹出一个集合(**可以用于抽奖**)
//8)数据类型五:有序集合(sorted sets,带一个热度值排序,故常用于热度排行)
zadd test:students aaa 100 bbb 30 ccc 20 ddd 50 eee 10 fff 20
zcard test:students //查询有序集合的大小
zrank test:students ccc //查询ccc的排序为多少,从小到大排序
zrange test:students 0 2 //查询热度为0到2的对象是谁
2. Spring整合Redis
- 步骤一:引入依赖 spring-boot-starter-data-redis
- 步骤二:配置Redis
1)配置数据库参数(库,域名和端口)
2)编写配置类,构造Redis Template(建立连接Redis的工厂,还有序列化Value,Key,Hash的Key和Value)
3)后面就可以 通过Redis Template进行Redis的访问 - 步骤三:访问Redis
1)访问字符串:redisTemplate.opsForValue()
2)访问哈希:redisTemplate.opsForHash()
3)访问双向链表:redisTemplate.opsForList()
4)访问无序集合:redisTemplate.opsForSet()
5)访问有序集合:redisTemplate.opsForZSet() - 效果展示:在测试类
1)测试类,对字符串,双向链表,哈希,无序集合和有序集合
2)如果每次都是访问一个对象,可以进行绑定,这样就不用每次传入对象进去。
3)Redis不是关系型数据库(Mysql),不完全符合ACID
3. 点赞
3.0 效果
- 点赞:支持对帖子,评论,评论的评论点赞
- 首页点赞数量:统计帖子的点赞数量
- 详情页点赞数量:统计点赞数量;显示点赞状态
3.1 为什么需要Redis
因为一篇文章出来之后,可能会有非常多的人给点赞, 需要实时被人看到,所有访问的次数会非常多。
3.2 如何实现点赞操作:
- redis直接写Service层,不用写Dao层进行数据访问。(因为是写在内存里面)
- 为方便复用创建一个redis工具类。
(1):定义字符常量,方便后面的字符拼接命名
(2):字符拼接的方法。某个用户的赞和实体(帖子)的赞 - 创建LikeService:
(1):点赞方法:执行redisTemplate.execute(new SessionCallback() ,把数据存储到redis中;存储实体赞和用户喜欢赞;需要判断当前是否有赞,再进行赞的存储或者删除。
(2): 查询某实体点赞的数量,例如帖子,评论,评论的评论
(3): 查询某人对某实体的点赞状态(返回整数,方便后续如果点踩的情况)
///(4):查询某个用户获得的赞—后续的功能 - 创建LikeController:点赞的接口
(1):点赞功能
(2):查询某实体的点赞数量
(2):查询用户对该实体点赞的状态(点击两次,可能取消了赞)
(4):把数据传输给前端(异步请求) - 修改HomeController:把缓存中帖子点赞量调用方法加上(使得首页显示正确的赞)
- 修改index.html
(1):修改首页赞的显示 - 修改:DiscussPostController:对详情页请求加上方法:记录点赞数量和状态(三层评论都需要加上)
- 修改discuss-detial.html
(1):书写discuss.js,实现点赞/赞的按钮功能,并刷新页面(异步请求)
(2):修改详情页点赞数量统计
4. 我收到的赞
4.0 效果
- 重构点赞功能
(1):以用户为Key,记录点赞的数量
(2):increment(key),decrement(key),用于点赞和取消赞 - 开发个人主页
(1):以用户为key,查询点赞数量
4.1 重构点赞和开发个人主页步骤
- 在redisUtil中增加一个方法。用于生成用户点赞字符串(故不需要Dao层)
- LikeService:重构方法点赞,多传入一个实体的id。方便统计实体的赞;添加方法:获取用户的赞。(这里点赞功能需要事务管理,因为赞的状态和存入实体的赞应该需要保持一致的);添加方法:获取当前用户的赞
operations.opsForSet().add(entityLikeKey, userId);
operations.opsForValue().increment(userLikeKey);
- LikeController:修改点赞的功能,多了一个参数
- 修改:discuss-detai_html:修改点赞的操作(三个帖子处的点赞);
- 修改:discuss.js:点赞的异步请求,多一个参数
- UserController:书写个人主页请求
- 修改个人主页html:profile.html
5. 关注、取消关注
5.0 需求
- 开发关注,取消关注功能
- 统计用户的关注数和粉丝数
5.1 核心点
- 关注者和被关注者:若A关注了B,那么A是B的粉丝,B是A的目标
- 关注的对象可以用户,帖子,题目等,在实现的时候将这些目标抽象为实体。
- 关注者和被关注者这些信息也通过Redis查询。
5.2 实现步骤
- 修改RedisKeyUtil:添加两个数据:关注者和被关注者(记录两份数据,方便统计)
- 新建FolloweService:
(1)follow关注方法:需要存储关注者和被关注者信息,两份数据,需要保持一致,故使用事务操作
operations.multi();
//语句
return operations.exec();
(2)unfollow取消关注方法:和关注方法相反
3. 新建FolloweController:(异步请求)
(1)关注:调用Service方法:返回json:已经关注
(2)取消关注:调用Service方法:返回json:取消关注
4. 修改Profile.html:关注的逻辑在profile里面。(通过样式进行改变)
5. 修改:在FellowService中添加方法,能够查询更多的信息。(查询是否某用户关注的人和当前用户是否关注某实体)【丰富个人主页内容】
6. 修改:在FollowController添加方法:使得个人主页能够显示更多的信息 【丰富个人主页内容】
7. 修改:profile.html:【丰富个人主页内容】
6. 关注列表、粉丝列表
6.0 实现的功能
点击个人主页里面,点击关注者和被关注者数字显示那里,对应可以进入关注列表和粉丝列表。
6.1 大致思路
- 数据持久层Dao层
由于是频繁使用的数据,故使用Redis进行存储,不涉及数据库操作 - 业务层Service层
(1):查询某个人关注的人,支持分页展示
(2):查询某个人的粉丝人数,支持分页展示 - 表现层Controller层
(1):处理“查询关注的人”,查询关注的模板
(2):处理“查询被关注的人”,查询粉丝的模板
(这里的思路大同小异,就跳过了)
7. 优化登录模块
7. 1 使用Redis存储验证码
7.1.1 原因
- 验证码需要频繁的刷新与访问,对性能要求很高
- 验证码不需要持久保存,只需要保存一小会;之前验证码是存进了Session里面
- 分布式部署的时候,存在Session共享的问题。可能有多个服务器。
7.1.2 实现
- 修改RedisKeyUtil:添加验证码的字符名字,用户存储
- 修改LoginController:修改之前的逻辑:
1):修改验证码的存储:getKaptcha
(1):首先注销方法中的Session引用
(2):注销把生成的验证码存入Session语句,变为以下:
(3):验证码归宿:由于在注册或者登录的时候,不能标识用户,需要生成一串字符串,用来标识当前的登录/注册用户,也作为验证码的归宿方。
(4.):把验证码+归宿作为一个键值对存入缓存中。
// 验证码的归属(先分配一个随机字符串,标识当前状态)
String kaptchaOwner = CommunityUtil.generateUUID();
Cookie cookie = new Cookie("kaptchaOwner", kaptchaOwner);
cookie.setMaxAge(60);
cookie.setPath(contextPath);
response.addCookie(cookie);
// 将验证码存入Redis
String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS); //存活时间设置为60s
1):修改登录:login
(1):检查验证码的逻辑:从Session取值变为从数据库中取值。键为从cookie中拿到的随机生成字符串信息(键值拼接了),值为生成的验证码。
7. 2 使用Redis存储登录凭证
7.2.1 原因
- 处理每次请求的时候,都需要查询用户的登录凭证token,访问频率非常高。
7.2.2 实现
之前有一张ticket表,可以作废了;
- 修改RedisKeyUtil:添加登录凭证对象的字符名字生成
- 不需要ticket表了,故删去Dao层:LoginTicketMapper,使用注解:@Deprecated
- 修改UserService:注销Dao层LoginTicketMapper
(1):修改登录方法:把凭证ticket放入Redis中(ticket是一张表信息,会进行序列化为字符串)
(1):修改登出方法:先获取Redis中的ticket,然后修改里面登出的值,之后再存入
(1):修改查询凭证方法:通过名字查询Redis中的登录凭证方法。
7. 3 使用Redis缓存用户信息
7.3.1 原因
- 处理每次请求的时候,都需要查询用户的信息,访问频率高
7.3.2 实现
之前有一个用户信息表,不能够作废,需要持久保存
- 修改RedisKeyUtil:添加用户信息对象的字符名字生成
- 修改UserService:添加三种方法(主要都是对缓存进行的操作)
(1):getChache:优先从缓存中查找数据
(2):initCache:取不到值从数据库中获取值,然后更新到缓存中
(3):数据库中的信息变更时,直接删除缓存中的数据(不采用更改,是为了避免线程并发带来的问题) - 使用缓存的三个方法(UserService):
(1)findUserById:先获取Cache,如果不存在,初始化initCache
(1)activation:在账户激活的时候,修改状态,需要删除缓存:
(2)updateHeader:更新头部图像,需要进行初始化。
项目每一个阶段的代码:
文章参考:
牛客网的仿牛客项目:https://www.nowcoder.com/study/live/246
https://blog.csdn.net/qq_43351888/article/details/123943949?spm=1001.2014.3001.5502