1. Redis 入门
Redis 官方没有 Windows 的安装包,但是有其他非官方的。将 Redis 安装包解压缩后进入根目录。
由于 Redis 列表的两端都可以存取数据,因此可以作为队列或栈。
redis-server.exe #启动Redis服务器
redis-cli #启动Redis客户端
select 0 #Redis中共有16个库,选择其中一个
flushdb #刷新(清空)数据库
keys * #查询当前数据库所有key
keys test* #查询以test开头的key
type test:student #查看某个key的类型
exists test:user #查看某个key是否存在
expire test:students 10 #设置过期时间
set test:count 1 #Redis提倡用冒号连接两个单词,存String类型的值
get test:count #取String类型的值
incr test:count #自增
decr test:count #自减
hset test:user id 1 #存Hash类型的值
hset test:user username zhangsan
hget test:user id #取Hash类型的值
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 #查看索引范围在0~2之间的值
rpop test:ids
sadd test:teachers aaa bbb ccc ddd eee
scard test:teachers #统计集合中的元素个数
spop test:teachers #随机弹出一个元素,可用于抽奖的实现
smembers test:teachers #查看集合中的元素
zadd test:students
zcard test:students #统计有序集合中的元素个数
zscore test:students ccc #查看ccc的分数
zrank test:students ccc #查看ccc的排名
zrange test:students 0 2 #取排名在0~2之间的数据
2. Spring 整合 Redis
SpringBoot 提供的 RedisTemplate 可以实现 Redis 存储 Java 对象,是通过序列化的方式实现的。对于 RedisTemplate 配置主要是配序列化的方式,Redis 中无法直接存储 Java 对象,因此如果我们需要将 Java 对象存到 Redis 数据库里,需要指定一种序列化的方式。
Redis 的默认方式是使用 JdkSerializationRedisSerializer,它是 JDK 提供的序列化功能。优点是反序列化时不需要提供类型信息(class),但缺点是需要实现 Serializable 接口,还有序列化后的结果非常庞大,是JSON格式的5倍左右,这样就会消耗 Redis 服务器的大量内存。还有就是如果使用默认的 JDK 序列化方式,Redis 存储的实际是二进制的值,查看 K-V 值时会出现乱码。
Redis 为什么要序列化? 序列化最终的目的是为了对象可以跨平台存储,和进行网络传输。而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是字节数组。所以凡是需要进行『跨平台存储』和『网络传输』的数据,都需要进行序列化。
3. 点赞
因为是存到 Redis,操作比较简单,所以就不单独写数据访问层了,而是直接写业务层。在操作 Redis 时,是通过 key 进行操作的,为了能让 key 复用,因此需要一个生成 Redis key 的工具。
4. 统计某个用户收到的赞
5. 关注、取消关注
某个用户关注的实体: following:userId:entityType
某个用户拥有的粉丝:follower:entityType:entityId
关注操作有两个子过程:1. 关注目标的 follower 加一。2. follower 的 关注目标加一。因此需要使用 Redis 事务。
取消关注的过程同上,不过删除的时候不需要时间分数。
6. 关注列表、粉丝列表
关注列表和粉丝列表的逻辑基本一致,因此我们一起开发。
逻辑层:查询某用户关注的人、查询某用户的粉丝。
表现层:判断当前用户是否已关注了这个实体,如果已经关注了,就不能再关注了。然后调用逻辑层。
7. 优化登录模块
利用Redis存储验证码、登录凭证、用户信息,并在一段时间后自动删除这些数据,从而提高服务器的处理能力。
使用 Redis 存储登录凭证后,login_ticket
表就可以不用了,但user
表还是需要的,将 User 放入缓存只是为了加快访问速度。
7.1 使用 Redis 存储验证码,解决分布式下的session共享问题
- 验证码需要频繁的访问与刷新,对性能要求较高
- 验证码不需永久保存,通常在很短的时间后就会失效
- 使用 Redis 可以解决分布式部署时 Session 共享的问题
表现层:
- 创建一个随机字符串放在 Cookie 里传给客户端,设置 Cookie 为 60 秒失效。
- 将这个随机字符串当作 Redis key,将验证码存入Redis,也设置为 60 秒失效。
之前是从 session 中取,现在是从 Redis 中取。每次生成验证码都会给客户端下发一个临时的凭证(Cookie)。
在分布式下,如果继续使用 session,会出现一些单服务中不存在的问题。例如客户端发起一个请求,这个请求到达 Nginx 之后,被 Nginx 转发到 Tomcat A 上,然后在 Tomcat A 上往 session 中保存了一份数据,下次又来一个请求,这个请求被转发到 Tomcat B 上,此时再去 session 中获取数据,发现没有之前的数据。对于这样的问题,思路很简单,就是将各个服务之间需要共享的数据保存到一个公共的地方(主流方案就是 Redis)。
7.2 使用 Redis 存储登录凭证
之前每次请求时,都要从 Cookie 中获取凭证字符串,调用逻辑层来查询登录凭证 LoginTicket,若凭证有效,则当前线程的 hostHolder 持有用户。
而这种请求的访问频率非常高,而逻辑层又要调用 MySQL 查询登录凭证,这样就很慢了,因此将登录凭证 loginTicket实体 存到 Redis 中。设置 LoginTicketMapper 不推荐使用,然后使用 Redis 重构。具体地说,每次登录都会生成新的且唯一的LoginTicket,然后拼接 ticket 作为 ticketKey,将 loginTicket 登陆凭证序列化为字符串作为 value 存入 Redis 中。
因为需要保留用户登录的记录,LoginTicket 永远不删,退出时只是更改 LoginTicket 的状态。
7.3 使用 Redis 存储用户信息
处理每次请求时,都要根据凭证查询用户信息,访问的频率非常高,所以考虑重构 findUserById 函数。拼接 UserId 作为 Key,User 作为 value:
- 优先从缓存中取 User。
- 取不到时从MySQL中取,但是将这个User初始化到缓存中。
数据变更时删除缓存数据。(这里涉及缓存一致性的问题,同时更新 MySQL 和 Redis 可能有并发的问题,这里是先更新再删除,关于缓存一致性的问题可查看『Redis 进阶』章节)
参考
- redis 数据hash存储