session是一种会话技术,我们知道http是无状态协议的,就是这次连接传输数据后,下次连接服务器是不知道这次的请求是谁的,因此我们要做一个标记,让服务器知道每次请求是哪个(客户端)浏览器发出的,就是请求的时候服务器会创建一个session把session的值保存在服务器,把sessionID返回给浏览器,请求的时候把sessionID放在请求头中,这样服务器解析之后就能发现是哪个浏览器发来的请求
session是存在服务器的,只是把sessionID返回给浏览器。这样我们把浏览器关掉,session也不会实现,但是只是丢失了sessionID,这样也是访问不到的。
session是由服务器创建的,存放在服务器中,把sessionID返回给浏览器,请求的时候,每次请求把sessionID就到请求头中,服务器解析以后就知道是哪个浏览器
我们知道session是保存在服务器的,这样当我们的项目做了负载均衡以后,如果在session中存了数据,那么就有可能有有些项目取不到session中的数据,这就是分布式session问题
通常我们在开发后台管理系统时,会使用 Session 来保存用户的会话(登录)状态,这些 Session 信息会被保存在服务器端,但这只适用于单系统应用,如果是分布式系统此模式将不再适用。
例如用户一的 Session 信息被存储在服务器一,但第二次访问时用户一被分配到服务器二,这个时候服务器并没有用户一的 Session 信息,就会出现需要重复登录的问题,问题在于分布式系统每次会把请求随机分配到不同的服务器。
分布式系统单独存储 Session 流程图:
因此,我们需要借助 Redis 对这些 Session 信息进行统一的存储和管理,这样无论请求发送到那台服务器,服务器都会去同一个 Redis 获取相关的 Session 信息,这样就解决了分布式系统下 Session 存储的问题。
分布式系统使用同一个 Redis 存储 Session 流程图:
而我们知道 Redis 处于内存,内存数据掉电易失!所以如果想要使用 Redis 作为数据库,必须要保证其持久性
只要是存储层,为了保证数据安全,都会有如下两个通用的功能:
项目中点赞、关注、验证码这几个功能都是把redis当做数据库使用的,没有当做缓存使用,不会有缓存和数据库的一致性问题。
缓存点赞和关注:
(1)首页上会显示被点赞了多少次,所以Redis缓存用户点赞数量,用String类型,以用户ID为key,点赞时,自增,取消赞时,自减;用户点赞key的设计了这个没啥,就是一个用户id就好了存储的value也就是数量就好了,like:user:userId -> int,这个操作的设计是为了后面发送系统消息的需求提供的之后再讲业务逻辑。
(2)点赞可以对帖子点赞,也可以对帖子的评论点赞,缓存实体点赞数,用set类型,为了能看对谁点的赞,以点赞对象为key,用户给实体点赞时添加进列表,取消赞时则移除;
首先这是个实体点赞操作部署关注操作对吧,所以key里面要有like字段,
然后点赞操作我们需要知道这是个帖子啊还是评论啊,还是回复啊所以我们还需要实体的类型是吧,
再者就是这个实体的id了。然后这个点赞是我点的吧需要我的id对吧。
所以key为like:entity:entityType:entityId这样设计还能提供拓展性如果以后提供了更多的点赞种类都能使用比如我还想增加一个给用户直接点赞的需求那么只需要type+1就好了。
然后巧妙的地方来了value我选用的是set(userid),为了能看谁点的赞。因为我们需要考虑到这个用户有可能是点错了啦要取消点赞哒,所以存储set可以判断这个用户有没有点过赞然后才能进行操作,而且set还能获得点赞的数量这样岂不美哉。
(3)缓存粉丝列表,也就是关注操作,使用zset,存入粉丝的id和关注的时间戳,使用zCard获得粉丝数量。利用reverseRange的时间戳反向排序,按关注时间加载粉丝列表。
key设计要分为两个一个是某个实体(用户)关注的实体(用户)列表,一个是一个实体(用户)被关注的实体(用户)列表,说白了就是我的关注和我的粉丝
// 某个用户关注的实体
// followee:userId:entityType -> zset(entityId,now)
// userId -> 某个关注别人的关注者id
// entityType -> 关注的类型,可以是帖子可以是人等待
// value是zset(entityId,now) 是一个有序集合以时间为score,以实体id为值
// 某个实体拥有的粉丝
// follower:entityType:entityId -> zset(userId,now)
// entityType -> 实体类型可以是用户可以是帖子等等
// entityId -> 这个实体的id
// 有了这两个参数可以唯一的表示某个实体,比如说如果这个实体是人那么entityType就是人entityId就是userId如果这个实体是帖子那么entityType就是帖子entityId就是帖子id
// value是zset(userId,now)是一个有序集合,集合中值是userId表示是哪个用户关注了这个实体
点赞 具体实现:
存在redis里就像操作map里,存数据取数据是面向key编程的,所以为了让key反复复用,最好给redis写一个工具专门生成key.,写一个工具类 RedisKeyUtil ,帖子和评论都统称为实体,我们要完成的就是存实体的赞的key,写 getEntityLikeKey 方法,传入 entityType, entityId (实体类型和实体ID)返回某个实体(帖子或回复)的赞的key值 。key由多个单词拼接而成,中间采用冒号隔开,有的单词是固定的,有些单词是动态的,设计方法如下所示:
public class RedisKeyUtil {
private static final String SPLIT = ":";
private static final String PREFIX_ENTITY_LIKE = "like:entity";
// 某个实体的赞
// like:entity:entityType:entityId -> set(userId)用一个集合存赞,谁给这个实体点的赞就存上
public static String getEntityLikeKey(int entityType, int entityId) {
return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
}
}
set(userId)用一个集合存赞,谁给这个实体点的赞就存上。
① 在帖子详情页面对帖子内容点赞, entityType=0,对回复点赞, entityType=1,通过 /like 映射传递给 LikeController 层中的 like 方法。
②like 方法通过调用 likeService 层的 like 进行点赞功能,然后再依次调用 findEntityLikeCount,findEntityLikeStatus 方法查询该帖子的点赞数量,查询当前登录用户的点赞状态。
③ likeService 层的 like 方法首先判断是否已经点过赞了,如果没有,就在Redis中该帖子key对应的set中加入点赞者的userId,并且对该帖子用户的赞的总数加一。如果已经赞过则移除UserId,并且对该帖子用户的赞的总数减一。
④ 将点赞的数量和状态封装进map,通过json字符串返回。在浏览器中显示。
收到的赞
前提:在 util 的 RedisKeyUtil 工具类中写 getUserLikeKey 方法,返回某个用户的赞总数的key。
关注
实现关注和取关的操作,还要统计用户的关注数和粉丝数。Follower粉丝,Followee目标。依旧用Redis实现。
还是先从key开始,在RedisKeyUtil里声明两个前缀,也就是要存两份数据,在这里拼两份用zset(entityId,now)以当前时间作为分数来统计
1.在他人个人主页点击关注,根据 profile.js 异步请求映射到 FollowController 层的 /follow。传入entityType 和 entityId。然后调用 followService.follow 方法进行关注。
2.followService.follow 方法首先根据 userId, entityType, entityId 获取到前提中的两个key,开启事务,对当前登录用户的关注的key对应的 ZSet 加入 entityId。被关注用户的粉丝的key对应的 ZSet 加入 userId(Redis存的是有序Set,score采用当前时间插入)。
4.返回关注成功Json。
5.此时按钮显示已关注,若再次点击则映射到 /unfollow。调用 followService.unfollow 方法取消关注。与第2步骤逻辑基本相同,分别减一