基于脚手架微服务的视频点播系统-服务端开发部分接口定义,数据库表设计,视频索引设计,缓存与消息队列设计部分
- 零.序言
- 一.接口定义部分
- 二.数据库表设计
- 三.ES索引设计
- 四.Redis缓存设计
-
- 4.1缓存策略
-
- 4.1.1MySQL与Redis的缓存双写同步策略
- [4.1.2 策略1](#4.1.2 策略1)
- [4.1.3 策略2](#4.1.3 策略2)
- [4.1.4 策略3](#4.1.4 策略3)
- 4.2数据淘汰策略:
- 4.3缓存设计
-
- [4.3.1 验证码](#4.3.1 验证码)
- [4.3.2 会话](#4.3.2 会话)
- [4.3.3 授权信息](#4.3.3 授权信息)
- [4.3.4 用户信息](#4.3.4 用户信息)
- [4.3.5 用户统计数据](#4.3.5 用户统计数据)
- [4.3.6 用户身份角色](#4.3.6 用户身份角色)
- [4.3.7 视频信息](#4.3.7 视频信息)
- [4.3.8 视频统计数据](#4.3.8 视频统计数据)
- [4.3.9 主页视频列表缓存](#4.3.9 主页视频列表缓存)
- [4.3.10 用户视频列表缓存](#4.3.10 用户视频列表缓存)
- [4.3.11 分类视频列表缓存](#4.3.11 分类视频列表缓存)
- [4.3.12 标签视频列表缓存](#4.3.12 标签视频列表缓存)
- [4.3.13 状态视频列表缓存](#4.3.13 状态视频列表缓存)
- 五.消息队列设计
-
- [5.1 延时消息队列](#5.1 延时消息队列)
-
- [5.1.2 视频点赞/播放量同步队列](#5.1.2 视频点赞/播放量同步队列)
-
- [5.1.2.1 队列元素:](#5.1.2.1 队列元素:)
- [5.1.2.2 消息格式:](#5.1.2.2 消息格式:)
- [5.1.2 缓存延迟删除消息队列设计](#5.1.2 缓存延迟删除消息队列设计)
-
- [5.1.2.1 队列元素:](#5.1.2.1 队列元素:)
- [5.1.2.2 消息格式:](#5.1.2.2 消息格式:)
- [5.2 普通消息队列](#5.2 普通消息队列)
-
- [5.2.1 ⽂件删除消息队列](#5.2.1 ⽂件删除消息队列)
-
- [5.2.1.1 队列元素:](#5.2.1.1 队列元素:)
- [5.2.1.2 消息格式:](#5.2.1.2 消息格式:)
- [5.2.2 视频转码消息队列](#5.2.2 视频转码消息队列)
-
- [5.2.2.1 队列元素:](#5.2.2.1 队列元素:)
- [5.2.2.2 消息格式:](#5.2.2.2 消息格式:)
零.序言
在我们开始编写服务端代码时,我们先来看下服务端的整体架构:

我们实现时分为五个模块:
- 网关子服务:对客⼾端请求进⾏权限检测, 并作为客⼾端与业务⼦服务之间的通信中转。
- 存储子服务:提供⽂件的上传与下载操作。
- 视频子服务:提供视频信息的各项操作。
- 转码子服务:对上传的视频进⾏HLS转码操作。
- 用户子服务:提供⽤⼾信息的各项操作。
不过在实现这些服务之前,我们需要先进行几项预备工作:定义RESTfil接口(protobuf),设计数据库表(mysql),设计视频索引(elasticsearch),缓存(redis)与消息队列设计(rabbitmq)。当然这里只是概述我们需要做的操作,除了接口定义部分其他部分的实现我们放到对应子服务实现时再进行细致理解
一.接口定义部分
我们此前在客户端业务逻辑层定义的RESTful风格接口,现在需要在服务端通过Protobuf进行重构定义。
因为我们的所有客户端请求会发起http请求到网关,然后网关再通过rpc调用相应的子服务,大致演示流程如下:

所以基于我们之前在客户端实现业务逻辑定义的一系列RESTful接口,这里我们为服务端也来定义下相关接口,不过需要注意的是,这里因为手机验证码无法个人使用,所以我们之前改为了邮箱验证,因此下面的部分接口与手机号相关的部分会有些许更改。不用担心客户端的更改,后面联调部分我们在对客户端进行调整即可。
1.1基础接口
proto
syntax = "proto3";//声明语法版本
package CommunicationInterface;//定义包名-名称为通信接口
option cc_generic_services = true;//是否启用rpc服务
//身份类型
enum IdentityType {
IDENTITYTYPE_UNKNOWN = 0;
IDENTITYTYPE_CUSER = 1;
IDENTITYTYPE_BUSER = 2;
};
//角色类型
enum RoleType {
ROLETYPE_UNKNOWN = 0;
ROLETYPE_SUPERADMIN = 1;
ROLETYPE_ADMIN = 2;
ROLETYPE_USER = 3;
ROLETYPE_GUEST = 4;
};
//用户状态
enum UserStatus {
USERSTATUS_UNKNOWN = 0;
USERSTATUS_USING = 1;
USERSTATUS_BANNED = 2;
};
//是否关注
enum FollowStatus {
FOLLOWSTATUS_UNFOLLOWED = 0;
FOLLOWSTATUS_FOLLOWED = 1;
};
//是否为访客
enum GuestStatus {
GUESTSTATUS_ISGUEST = 0;
GUESTSTATUS_NOTGUEST = 1;
};
//视频状态
enum VideoStatus {
VIDEOSTATUS_UNKNOWN = 0;
VIDEOSTATUS_UNCHECKED = 1; //待审核
VIDEOSTATUS_CHECKED = 2; //审核通过
VIDEOSTATUS_REJECTED = 3; //审核驳回
VIDEOSTATUS_DELETED = 4; //已下架
VIDEOSTATUS_TRANSCODE = 5; //转码中
};
//用户信息结构
message UserInfo {
optional string userId = 1; //用户ID
optional string email = 2; //邮箱
optional string nickname = 3; //昵称
repeated RoleType roleType = 4; //角色类型
repeated IdentityType identifyType = 5; //身份类型
optional int32 likeCount = 6; //喜欢数
optional int32 playCount = 7; //播放数
optional int32 followedCount = 8; //关注数
optional int32 followerCount = 9; //粉丝数
optional UserStatus userStatus = 10; //用户状态
optional FollowStatus isFollowing = 11; //是否关注
optional string userMemo = 12; //用户备注
optional string userCTime = 13; //用户创建时间
optional string avatarFileId = 14; //头像文件ID
};
//管理员信息结构
message AdminInfo {
optional string userId = 1; //用户ID
optional string nickname = 2; //昵称
optional RoleType roleType = 3; //角色类型
optional UserStatus userStatus = 4; //用户状态
optional string userMemo = 5; //用户备注
optional string email = 6; //邮箱
};
//定义文件信息结构
message FileInfo {
optional string fileId = 1;//文件ID
optional int32 fileSize = 2;//文件大小
optional string uploader = 3;//上传者ID
optional string fileMime = 4;//文件类型
optional bytes fileData = 5;//文件数据
};
//定义视频信息结构
message VideoInfo {
optional string videoId = 1;
optional string userId = 2;
optional string userAvatarId = 3;
optional string nickname = 4;
optional int32 videoType = 5;
repeated int32 videoTag = 6;
optional string videoFileId = 7;
optional string photoFileId = 8;
optional int32 likeCount = 9;
optional int32 playCount = 10;
optional int32 videoSize = 11;
optional string videoDesc = 12;
optional string videoTitle = 13;
optional int32 videoDuration = 14;
optional string videoUpTime = 15;
optional string checkerId = 16;
optional string checkerName = 17;
optional string checkerAvatar = 18;
optional int32 videoStatus = 19;
};
//定义弹幕信息结构
message BarrageInfo {
optional string barrageId = 1;
optional string userId = 2;
optional string videoId = 3;
optional string barrageContent = 4;
optional int64 barrageTime = 5;
};
1.2用户相关接口
proto
syntax = "proto3";//声明语法版本
package CommunicationInterface;//定义包名-名称为通信接口
option cc_generic_services = true;//是否启用rpc服务
import "base.proto";//导入基础定义文件
//临时用户登录请求
message TempLoginReq{
optional string requestId = 1;//请求ID
};
message TempLoginResult{
optional string sessionId = 1;//会话ID
};
message TempLoginResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
optional TempLoginResult result = 4;//结果
};
//会话登录
message SessionLoginReq{
optional string requestId = 1;//请求ID
optional string sessionId = 2;//会话ID
};
message SessionLoginResult{
optional bool isGuest = 1;//是否为游客
};
message SessionLoginResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
optional SessionLoginResult result = 4;//结果
};
//获取邮箱验证码
message GetCodeReq{
optional string requestId = 1;//请求ID
optional string sessionId = 2;//会话ID
optional string email = 3;//邮箱
};
message GetCodeResult{
optional string codeId = 1;//验证码ID
};
message GetCodeResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
optional GetCodeResult result = 4;//结果
};
//邮箱的注册/登录
message VcodeLogin{
optional string requestId = 1;//请求ID
optional string sessionId = 2;//会话ID
optional string email = 3;//邮箱
optional string verifyCode = 4;//验证码
optional string codeId = 5;//验证码ID
};
message VcodeLoginResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
};
//密码登录
message PasswordLoginReq{
optional string requestId = 1;//请求ID
optional string sessionId = 2;//会话ID
optional string username = 3;//用户名
optional string password = 4;//密码
};
message PasswordLoginResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
};
//退出登录
message LogoutReq{
optional string requestId = 1;//请求ID
optional string sessionId = 2;//会话ID
};
message LogoutResult{
optional string sessionId = 1;//会话ID
};
message LogoutResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
optional LogoutResult result = 4;//结果
};
//设置用户头像
message SetAvatarReq{
optional string requestId = 1;//请求ID
optional string sessionId = 2;//会话ID
optional string fileId = 3;//文件ID
};
message SetAvatarResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
};
//设置用户昵称
message SetNicknameReq{
optional string requestId = 1;//请求ID
optional string sessionId = 2;//会话ID
optional string nickname = 3;//昵称
};
message SetNicknameResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
};
//设置登录密码
message SetPasswordReq{
optional string requestId = 1;//请求ID
optional string sessionId = 2;//会话ID
optional string password = 3;//密码
};
message SetPasswordResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
};
//设置用户状态-给管理员使用
message SetStatusReq{
optional string requestId = 1;//请求ID
optional string sessionId = 2;//会话ID
optional string userId = 3;//用户ID
optional UserStatus userStatus = 4;//用户状态
};
message SetStatusResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
};
//获取用户详细信息
message GetUserInfoReq{
optional string requestId = 1;//请求ID
optional string sessionId = 2;//会话ID
optional string userId = 3;//用户ID
};
message GetUserInfoResult{
optional UserInfo userInfo = 1;//用户信息
};
message GetUserInfoResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
optional GetUserInfoResult result = 4;//结果
};
//新增关注
message NewAttentionReq{
optional string requestId = 1;//请求ID
optional string sessionId = 2;//会话ID
optional string userId = 3;//被关注用户ID
};
message NewAttentionResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
};
//取消关注
message DelAttentionReq{
optional string requestId = 1;//请求ID
optional string sessionId = 2;//会话ID
optional string userId = 3;//被取消关注用户ID
};
message DelAttentionResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
};
//新增管理员
message NewAdministratorReq{
optional string requestId = 1;//请求ID
optional string sessionId = 2;//会话ID
optional AdminInfo userInfo = 3;//管理员信息
};
message NewAdministratorResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
};
//删除管理员
message DelAdministratorReq{
optional string requestId = 1;//请求ID
optional string sessionId = 2;//会话ID
optional string userId = 3;//管理员用户ID
};
message DelAdministratorResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
};
//编辑管理员
message SetAdministratorReq{
optional string requestId = 1;//请求ID
optional string sessionId = 2;//会话ID
optional AdminInfo userInfo = 3;//管理员信息
};
message SetAdministratorResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
};
//通过⼿机号获取管理员信息-调整后为通过邮箱获取
message GetAdminByPhoneReq{
optional string requestId = 1;//请求ID
optional string sessionId = 2;//会话ID
optional string email = 3;//邮箱
};
message GetAdminByPhoneResult{
optional AdminInfo userInfo = 1;//管理员信息
};
message GetAdminByPhoneResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
optional GetAdminByPhoneResult result = 4;//结果
};
//获取管理员列表
// {
// "requestId": "string",
// "errorCode": 0,
// "errorMsg": "",
// "result": {
// "totalCount":0
// "userList": [
// {
// "userId": "string",
// "nickname": "string",
// "roleType": "string",
// "phoneNumber":
// "string",
// "userStatus":
// "string",
// "userMemo": "string"
// }
// ]
// }
// }
message GetAdminListByStatusReq{
optional string requestId = 1;//请求ID
optional string sessionId = 2;//会话ID
optional int32 pageIndex = 3;//页码
optional int32 pageCount = 4;//每页数量
optional UserStatus userStatus = 5;//用户状态
};
message GetAdminListByStatusResult{
optional int32 totalCount = 1;//管理员总数
repeated AdminInfo userList = 2;//管理员列表
};
message GetAdminListByStatusResp{
optional string requestId = 1;//请求ID
optional int32 errorCode = 2;//错误码
optional string errorMsg = 3;//错误信息
optional GetAdminListByStatusResult result = 4;//结果
};
service UserService{
rpc tempLogin(TempLoginReq) returns (TempLoginResp);//临时用户登录
rpc sessionLogin(SessionLoginReq) returns (SessionLoginResp);//会话登录
rpc getCode(GetCodeReq) returns (GetCodeResp);//获取邮箱验证码
rpc vcodeLogin(VcodeLogin) returns (VcodeLoginResp);//邮箱的注册/登录
rpc passwordLogin(PasswordLoginReq) returns (PasswordLoginResp);//密码登录
rpc logout(LogoutReq) returns (LogoutResp);//退出登录
rpc setAvatar(SetAvatarReq) returns (SetAvatarResp);//设置用户头像
rpc setNickname(SetNicknameReq) returns (SetNicknameResp);//设置用户昵称
rpc setPassword(SetPasswordReq) returns (SetPasswordResp);//设置登录密码
rpc setStatus(SetStatusReq) returns (SetStatusResp);//设置用户状态-给管理员使用
rpc getUserInfo(GetUserInfoReq) returns (GetUserInfoResp);//获取用户详细信息
rpc newAttention(NewAttentionReq) returns (NewAttentionResp);//新增关注
rpc delAttention(DelAttentionReq) returns (DelAttentionResp);//取消关注
rpc newAdministrator(NewAdministratorReq) returns (NewAdministratorResp);//新增管理员
rpc delAdministrator(DelAdministratorReq) returns (DelAdministratorResp);//删除管理员
rpc setAdministrator(SetAdministratorReq) returns (SetAdministratorResp);//编辑管理员
rpc getAdminByPhone(GetAdminByPhoneReq) returns (GetAdminByPhoneResp);//通过手机号获取管理员信息-调整后为通过邮箱获取
rpc getAdminListByStatus(GetAdminListByStatusReq) returns (GetAdminListByStatusResp);//获取管理员列表
};
1.3视频相关接口
proto
syntax = "proto3";//声明语法版本
package CommunicationInterface;//定义包名-名称为通信接口
option cc_generic_services = true;//是否启用rpc服务
import "base.proto";//导入基础定义文件
//新增视频信息
message newVideoReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional VideoInfo videoInfo = 3; //视频信息
};
message newVideoResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
};
//视频删除
message RemoveVideoReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional string videoId = 3; //视频ID
};
message RemoveVideoResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
};
//判断点赞情况
message JudgeLikeReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional string videoId = 3; //视频ID
};
message JudgeLikeResult{
optional bool isLike = 1; //是否已点赞
};
message JudgeLikeResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
optional JudgeLikeResult result = 4; //结果
};
//点赞操作
message SetLikeReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional string videoId = 3; //视频ID
};
message SetLikeResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
};
//播放量增加
message SetPlayReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional string videoId = 3; //视频ID
};
message SetPlayResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
};
//新增视频弹幕
message NewBarrageReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional string videoId = 3; //视频ID
optional BarrageInfo barrageInfo = 4; //弹幕信息
};
message NewBarrageResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
};
//获取视频弹幕列表
message GetBarrageReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional string videoId = 3; //视频ID
};
message GetBarrageResult{
repeated BarrageInfo barrageList = 1; //弹幕列表
};
message GetBarrageResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
optional GetBarrageResult result = 4; //结果
};
//视频审核
message CheckVideoReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional string videoId = 3; //视频ID
optional bool checkResult = 4; //审核结果,true-通过,false-驳回
};
message CheckVideoResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
};
//视频上架
message SaleVideoReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional string videoId = 3; //视频ID
};
message SaleVideoResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
};
//视频下架
message HaltVideoReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional string videoId = 3; //视频ID
};
message HaltVideoResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
};
//获取用户视频列表
message userVideoListReq {
optional string requestId = 1;
optional string sessionId = 2;
optional string userId = 3;
optional int32 pageIndex = 4;
optional int32 pageCount = 5;
optional string lastVideoId = 6;
};
message userVideoListResult {
repeated VideoInfo videoList = 1;
};
message userVideoListRsp {
optional string requestId = 1;
optional int32 errorCode = 2;
optional string errorMsg = 3;
optional userVideoListResult result = 4;
}
//获取状态视频列表
message StatusVideoListReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional int32 videoStatus = 3; //视频状态
optional int32 pageIndex = 4; //页码
optional int32 pageCount = 5; //每页数量
};
message StatusVideoListResult{
optional int32 totalCount = 1; //视频总数
repeated VideoInfo videoList = 2; //视频列表
};
message StatusVideoListResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
optional StatusVideoListResult result = 4; //结果
};
//获取主页视频列表
message AllVideoListReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional int32 pageIndex = 3; //页码
optional int32 pageCount = 4; //每页数量
};
message AllVideoListResult{
repeated VideoInfo videoList = 1; //视频列表
};
message AllVideoListResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
optional AllVideoListResult result = 4; //结果
};
//获取分类视频列表
message TypeVideoListReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional int32 videoTypeId = 3; //视频分类ID
optional int32 pageIndex = 4; //页码
optional int32 pageCount = 5; //每页数量
};
message TypeVideoListResult{
repeated VideoInfo videoList = 1; //视频列表
};
message TypeVideoListResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
optional TypeVideoListResult result = 4; //结果
};
//获取标签视频列表
// {
// "requestId": "string",
// "sessionId": "string",
// "videoTag": 0,
// "pageIndex": 0,
// "pageCount": 0
// }
// {
// "requestId": "string",
// "errorCode": 0,
// "errorMsg": "",
// "result": {
// "videoList": [
// {
// "videoId": "string",
// "userId": "string",
// "userAvatarId":"string",
// "nickname": "string",
// "videoFileId":
// "string",
// "photoFileId":
// "string",
// "likeCount": 0,
// "playCount": 0,
// "videoSize": 0,
// "videoDesc": "string",
// "videoTitle":
// "string",
// "videoDuration": 0,
// "videoUpTime":
// "string"
// }
// ]
// }
// }
message TagVideoListReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional int32 videoTag = 3; //视频标签ID
optional int32 pageIndex = 4; //页码
optional int32 pageCount = 5; //每页数量
};
message TagVideoListResult{
repeated VideoInfo videoList = 1; //视频列表
};
message TagVideoListResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
optional TagVideoListResult result = 4; //结果
};
//获取搜索视频列表
message KeyVideoListReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional string searchKey = 3; //搜索关键字
optional int32 pageIndex = 4; //页码
optional int32 pageCount = 5; //每页数量
};
message KeyVideoListResult{
repeated VideoInfo videoList = 1; //视频列表
};
message KeyVideoListResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
optional KeyVideoListResult result = 4; //结果
};
service VideoService{
rpc newVideo(newVideoReq) returns (newVideoResp);//新增视频信息
rpc removeVideo(RemoveVideoReq) returns (RemoveVideoResp);//视频删除
rpc judgeLike(JudgeLikeReq) returns (JudgeLikeResp);//判断点赞情况
rpc setLike(SetLikeReq) returns (SetLikeResp);//点赞操作
rpc setPlay(SetPlayReq) returns (SetPlayResp);//播放量增加
rpc newBarrage(NewBarrageReq) returns (NewBarrageResp);//新增视频弹幕
rpc getBarrage(GetBarrageReq) returns (GetBarrageResp);//获取视频弹幕列表
rpc checkVideo(CheckVideoReq) returns (CheckVideoResp);//视频审核
rpc saleVideo(SaleVideoReq) returns (SaleVideoResp);//视频上架
rpc haltVideo(HaltVideoReq) returns (HaltVideoResp);//视频下架
rpc getUserVideoList(userVideoListReq) returns (userVideoListRsp);//获取用户视频列表
rpc getStatusVideoList(StatusVideoListReq) returns (StatusVideoListResp);//获取状态视频列表
rpc getAllVideoList(AllVideoListReq) returns (AllVideoListResp);//获取主页视频列表
rpc getTypeVideoList(TypeVideoListReq) returns (TypeVideoListResp);//获取分类视频列表
rpc getTagVideoList(TagVideoListReq) returns (TagVideoListResp);//获取标签视频列表
rpc getKeyVideoList(KeyVideoListReq) returns (KeyVideoListResp);//获取搜索视频列表
};
1.4文件相关接口
proto
syntax = "proto3";//声明语法版本
package CommunicationInterface;//定义包名-名称为通信接口
option cc_generic_services = true;//是否启用rpc服务
import "base.proto";//导入基础定义文件
//图片文件上传-因为使用的是传统的http请求,所以需要额外定义空的请求和响应消息,由网关去将rpc请求转化为http请求
message HttpUploadPhotoReq{
};
message UploadPhotoReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional FileInfo fileInfo = 3; //文件信息
};
message HttpUploadPhotoResp{
};
message UploadPhotoResult{
optional string fileId = 1; //文件ID
};
message UploadPhotoResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
optional UploadPhotoResult result = 4; //结果
};
//图片文件下载-因为使用的是传统的http请求,所以需要额外定义空的请求和响应消息,由网关去将rpc请求转化为http请求
message HttpDownloadPhotoReq{
};
message DownloadPhotoReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional string fileId = 3; //文件ID
};
message HttpDownloadPhotoResp{
};
message DownloadPhotoResp{
optional string fileId = 1; //文件ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
optional FileInfo result = 4; //结果
};
//视频文件上传
message HttpUploadVideoReq{
};
message UploadVideoReq {
optional string requestId = 1;
optional string sessionId = 2;
FileInfo fileInfo = 3;
};
message HttpUploadVideoResp{
};
message UploadVideoResult{
optional string fileId = 1; //文件ID
};
message UploadVideoResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
optional UploadPhotoResult result = 4; //结果
};
//视频文件下载
message HttpDownloadVideoReq{
};
message DownloadVideoReq{
optional string requestId = 1; //请求ID
optional string sessionId = 2; //会话ID
optional string fileId = 3; //文件ID
};
message HttpDownloadVideoResp{
};
message DownloadVideoResp{
optional string requestId = 1; //请求ID
optional int32 errorCode = 2; //错误码
optional string errorMsg = 3; //错误信息
optional FileInfo result = 4; //结果
};
service FileService{
rpc uploadPhoto(UploadPhotoReq) returns (UploadPhotoResp); //上传图片
rpc downloadPhoto(DownloadPhotoReq) returns (DownloadPhotoResp); //下载图片
rpc uploadVideo(UploadVideoReq) returns (UploadVideoResp); //上传视频
rpc downloadVideo(DownloadVideoReq) returns (DownloadVideoResp); //下载视频
};
1.5网关相关接口
proto
syntax = "proto3";//声明语法版本
package CommunicationInterface;//定义包名-名称为通信接口
option cc_generic_services = true;//是否启用rpc服务
import "user.proto";//导入用户定义文件
import "video.proto";//导入视频定义文件
import "file.proto";//导入文件定义文件
service HttpService{
rpc tempLogin(TempLoginReq) returns (TempLoginResp);//临时用户登录
rpc sessionLogin(SessionLoginReq) returns (SessionLoginResp);//会话登录
rpc getCode(GetCodeReq) returns (GetCodeResp);//获取邮箱验证码
rpc vcodeLogin(VcodeLogin) returns (VcodeLoginResp);//邮箱的注册/登录
rpc passwordLogin(PasswordLoginReq) returns (PasswordLoginResp);//密码登录
rpc logout(LogoutReq) returns (LogoutResp);//退出登录
rpc setAvatar(SetAvatarReq) returns (SetAvatarResp);//设置用户头像
rpc setNickname(SetNicknameReq) returns (SetNicknameResp);//设置用户昵称
rpc setPassword(SetPasswordReq) returns (SetPasswordResp);//设置登录密码
rpc setStatus(SetStatusReq) returns (SetStatusResp);//设置用户状态-给管理员使用
rpc getUserInfo(GetUserInfoReq) returns (GetUserInfoResp);//获取用户详细信息
rpc newAttention(NewAttentionReq) returns (NewAttentionResp);//新增关注
rpc delAttention(DelAttentionReq) returns (DelAttentionResp);//取消关注
rpc newAdministrator(NewAdministratorReq) returns (NewAdministratorResp);//新增管理员
rpc delAdministrator(DelAdministratorReq) returns (DelAdministratorResp);//删除管理员
rpc setAdministrator(SetAdministratorReq) returns (SetAdministratorResp);//编辑管理员
rpc getAdminByPhone(GetAdminByPhoneReq) returns (GetAdminByPhoneResp);//通过手机号获取管理员信息-调整后为通过邮箱获取
rpc getAdminListByStatus(GetAdminListByStatusReq) returns (GetAdminListByStatusResp);//获取管理员列表
//注意下面文件rpc接口需要使用定义的Httpmessage
rpc uploadPhoto(HttpUploadPhotoReq) returns (HttpUploadPhotoResp); //上传图片
rpc downloadPhoto(HttpDownloadPhotoReq) returns (HttpDownloadPhotoResp); //下载图片
rpc uploadVideo(HttpUploadVideoReq) returns (HttpUploadVideoResp); //上传视频
rpc downloadVideo(HttpDownloadVideoReq) returns (HttpDownloadVideoResp); //下载视频
//视频相关接口
rpc newVideo(newVideoReq) returns (newVideoResp);//新增视频信息
rpc removeVideo(RemoveVideoReq) returns (RemoveVideoResp);//视频删除
rpc judgeLike(JudgeLikeReq) returns (JudgeLikeResp);//判断点赞情况
rpc setLike(SetLikeReq) returns (SetLikeResp);//点赞操作
rpc setPlay(SetPlayReq) returns (SetPlayResp);//播放量增加
rpc newBarrage(NewBarrageReq) returns (NewBarrageResp);//新增视频弹幕
rpc getBarrage(GetBarrageReq) returns (GetBarrageResp);//获取视频弹幕列表
rpc checkVideo(CheckVideoReq) returns (CheckVideoResp);//视频审核
rpc saleVideo(SaleVideoReq) returns (SaleVideoResp);//视频上架
rpc haltVideo(HaltVideoReq) returns (HaltVideoResp);//视频下架
rpc getUserVideoList(userVideoListReq) returns (userVideoListRsp);//获取用户视频列表
rpc getStatusVideoList(StatusVideoListReq) returns (StatusVideoListResp);//获取状态视频列表
rpc getAllVideoList(AllVideoListReq) returns (AllVideoListResp);//获取主页视频列表
rpc getTypeVideoList(TypeVideoListReq) returns (TypeVideoListResp);//获取分类视频列表
rpc getTagVideoList(TagVideoListReq) returns (TagVideoListResp);//获取标签视频列表
rpc getKeyVideoList(KeyVideoListReq) returns (KeyVideoListResp);//获取搜索视频列表
};
二.数据库表设计
2.1用户管理
2.1.1身份表
| 字段 | 类型 | 约束 | 空值 | 备注 |
|---|---|---|---|---|
| ID | INT | PK | NOT NULL | |
| 身份ID | VARCHAR(64) | UK | NOT NULL | |
| 身份类型 | TINYINT | UK | NOT NULL | 0-未知 1-C端用户 2-B端用户 |
| 身份备注 | VARCHAR(64) | NOT NULL |
2.1.2角色表
| 字段 | 类型 | 约束 | 空值 | 备注 |
|---|---|---|---|---|
| ID | INT | PK | NOT NULL | |
| 角色ID | VARCHAR(64) | UK | NOT NULL | |
| 角色类型 | TINYINT | UK | NOT NULL | 0-未知 1-超级管理员 2-普通管理员 3-普通用户 4-临时用户 |
| 角色备注 | VARCHAR(64) | NOT NULL |
2.1.3操作表
描述系统中包含有哪些操作
| 字段 | 类型 | 约束 | 空值 | 备注 |
|---|---|---|---|---|
| ID | INT | PK | NOT NULL | |
| 操作ID | VARCHAR(64) | UK | NOT NULL | |
| 操作 | VARCHAR(255) | NK | NOT NULL | URL |
| 操作备注 | VARCHAR(64) | NOT NULL |
2.1.4用户表
| 字段 | 类型 | 约束 | 空值 | 备注 |
|---|---|---|---|---|
| ID | BIGINT UNSIGNED | PK | NOT NULL | |
| 用户ID | VARCHAR(64) | UK | NOT NULL | |
| 绑定手机号 | VARCHAR(32) | UK | NULL | |
| 邮箱 | VARCHAR(32) | UK | NULL | |
| 用户昵称 | VARCHAR(64) | UK | NULL | 用于C端显示 |
| 备用昵称 | VARCHAR(64) | NULL | 用于B端显示 | |
| 密码 | VARCHAR(32) | NULL | ||
| 头像文件ID | VARCHAR(64) | NULL | ||
| 用户备注 | TEXT | NULL | ||
| 用户状态 | TINYINT | NOT NULL | 0-禁用 1-启用 | |
| 注册时间 | DATETIME | NOT NULL |
2.1.5会话表
会话信息表中⽤⼾ID字段是否为空,决定了当前会话是否是⼀个临时会话(判断依据就是当前指定ID的会话,有没有关联⽤⼾)
| 字段 | 类型 | 约束 | 空值 | 备注 |
|---|---|---|---|---|
| ID | BIGINT UNSIGNED | FK | NOT NULL | |
| 会话ID | VARCHAR(64) | UK | NOT NULL | |
| 用户ID | VARCHAR(64) | UK | NULL | 为空代表未登录 |
2.1.6用户身份角色关系表
⼀个用户,可以有多个⾝份,也可以有多个角色,但是⼀个用户在⼀个身份中只能有⼀个角色。
通过这张表,就可以获取到某个用户有哪些角色(主要用于访问权限控制,只有知道了用户拥有哪些角色,才能确定用户能够进行哪些操作)。
| 字段 | 类型 | 约束 | 空值 | 备注 |
|---|---|---|---|---|
| ID | BIGINT UNSIGNED | PK | NOT NULL | |
| 用户ID | VARCHAR(64) | FK | NOT NULL | |
| 身份类型 | TINYINT | FK | NOT NULL | |
| 角色类型 | TINYINT | FK | NOT NULL |
2.1.7权限角色关系表(授权表)
描述哪个⻆⾊可以进行什么操作,通过这张表,可以获取到哪个操作允许哪些⻆⾊执⾏。
| 字段 | 类型 | 约束 | 空值 | 备注 |
|---|---|---|---|---|
| ID | INT | PK | NOT NULL | |
| 权限操作 | VARCHAR(255) | NK | NOT NULL | URL |
| 角色类型 | TINYINT | FK | NOT NULL |
2.1.8用户关注关系表
| 字段 | 类型 | 约束 | 空值 | 备注 |
|---|---|---|---|---|
| ID | BIGINT UNSIGNED | PK | NOT NULL | |
| 粉丝用户ID | VARCHAR(64) | NK | NOT NULL | 自己关注的主播列表 |
| 主播用户ID | VARCHAR(64) | NK | NOT NULL | 关注自己的粉丝列表 |
2.2文件管理
在视频点播平台下,⽂件的类型总体⼤致分为两类:图⽚/视频,且⽂件最终都是存储在FASTDFS的分布式⽂件系统中。
⽂件在传输时需要根据文件的扩展名确定HTTP传输⽂件时的Content-Type字段。
⽂件下载时的两个特殊的mime:
- .m3u8 : application/m3u
- .ts : video/mp2t
基于以上情况,定义文件信息表如下:
2.2.1文件元信息表
描述并持久化文件的元信息
| 字段 | 类型 | 约束 | 空值 | 备注 |
|---|---|---|---|---|
| ID | BIGINT UNSIGNED | PK | NOT NULL | |
| 文件ID | VARCHAR(64) | UK | NOT NULL | |
| 上传用户ID | VARCHAR(64) | NK | NOT NULL | |
| 存储路径 | VARCHAR(128) | NOT NULL | FASTDFS存储ID | |
| 文件大小 | INT UNSIGNED | NOT NULL | ||
| 文件mime | VARCHAR(16) | NOT NULL | Content-Type |
2.3视频管理
2.3.1视频元信息表
| 字段 | 类型 | 约束 | 备注 |
|---|---|---|---|
| ID | BIGINT UNSIGNED | PK | |
| 视频ID | VARCHAR(64) | UK | |
| 视频文件ID | VARCHAR(64) | 用于查找视频文件 | |
| 封面文件ID | VARCHAR(64) | 用于查找封面文件 | |
| 上传用户ID | VARCHAR(64) | FK | 用于查找用户视频 |
| 审核用户ID | VARCHAR(64) | ||
| 视频标题 | VARCHAR(255) | ||
| 视频简介 | TEXT | ||
| 播放量 | INT UNSIGNED | ||
| 视频时长 | INT UNSIGNED | ||
| 上架时间 | DATETIME | ||
| 视频状态 | TINYINT | NK | 0-未知/所有状态 1-审核中 2-审核通过,上架中 3-审核驳回 4-已下架 5-转码中 6-转码失败 |
2.3.2用户点赞关联表
| 字段 | 类型 | 约束 | 备注 |
|---|---|---|---|
| ID | BIGINT UNSIGNED | PK | |
| 用户ID | VARCHAR(64) | ||
| 视频ID | VARCHAR(64) | NK |
2.3.3视频标签关联表
| 字段 | 类型 | 约束 | 备注 |
|---|---|---|---|
| ID | BIGINT UNSIGNED | PK | |
| 视频ID | VARCHAR(64) | FK | 用于查找视频标签 |
| 标签ID | INT | NK | 用户查找标签视频 |
| 分类ID | INT | NK | 用于查找分类视频 |
2.3.4弹幕信息表
| 字段 | 类型 | 约束 | 备注 |
|---|---|---|---|
| ID | BIGINT UNSIGNED | PK | |
| 弹幕ID | VARCHAR(64) | UK | |
| 视频ID | VARCHAR(64) | FK | |
| 用户ID | VARCHAR(64) | ||
| 弹幕内容 | VARCHAR(255) | ||
| 弹幕时间 | INT | 相对起始秒数 |
三.ES索引设计
因为在项⽬中需要提供⼀个关键字检索视频的功能,因此使⽤ES实现数据检索功能。在设计视频索引时需要考虑以下⽅⾯:
• 设置视频信息索引时,考虑分⻚获取数据,排序等功能,排序的依据应该是检索的权重排名。
• 在视频信息中,最为频繁修改的字段为:点赞量,播放量;然⽽ES中并不适合⽤于存储经常修改的数据,因此并不适合将所有字段都存储到ES中,因此在ES中仅存储索引关键字检索字段以及视频ID字段,检索依据字段,视频状态,这样通过关键字搜索到视频后,通过视频ID从Mysql/Redis数据库中查询获取视频统计数据即可。
ES + MySQL/Redis 混合存储是最常⻅的解决⽅案,适合⼤多数场景。它结合了ES的⾼性能检索能⼒和MySQL/Redis的事务⽀持能⼒/⾼性能,但需要维护数据同步。
特性
• ⾼性能:Redis适合存储频繁更新的字段,读写性能⾼。
• 事务⽀持:MySQL⽀持事务和频繁更新。
• 检索性能:ES作为⼆级索引,可以提供⾼效的检索能⼒。
• 减少ES压⼒:将频繁更新的字段从ES中剥离,减少写⼊压⼒。
3.1索引文档字段说明
| 字段 | 类型 | 备注 |
|---|---|---|
| 视频ID | keyword | |
| 视频标题 | text | analyzer: ikmax boost: 3 |
| 视频描述 | text | analyzer: ikmax boost:1 |
| 视频状态 | bool | index:false |
css
PUT /cpp_videoplayer_test
{
"settings": {
"analysis": {
"analyzer": {
"ikmax": {
"type": "custom",
"tokenizer": "ik_max_word"
}
}
}
},
"mapping": {
"dynamic": false,
"properties": {
"video_id": {
"type": "keyword"
},
"title": {
"type": "text",
"analyzer": "ikmax",
"boost": 3
},
"describe": {
"type": "text",
"analyzer": "ikmax",
"boost": 1
},
"staus": {
"type": "boolean"
}
}
}
}
只有审核完毕后上架中的视频,才需要添加到ES中存储,视频状态为true。 视频⼀旦被下架,则视频状态处于false,搜索时需要被过滤掉。
四.Redis缓存设计
4.1缓存策略
4.1.1MySQL与Redis的缓存双写同步策略
背景
在系统中,为了提高数据的获取效率,我们将数据库中的数据也向缓存中添加了一份。但是一条数据分在不同的位置进行存储,就会出现数据一致性问题(即缓存中的数据可能与数据库中的数据不一致)。为此,设计出了缓存的双写同步策略来解决该问题。
双写同步策略
在数据发生改变时,执行以下步骤:
- 先更新/删除数据库数据;
- 然后删除缓存;
- 延迟再次进行缓存删除(延迟时间根据数据操作时间而定)。
缓存同步情况分析
1. 先删除缓存,后更新/删除数据库数据
- 操作步骤:删除缓存 → 更新/删除数据库。
- 问题:操作缓存与操作数据库并非原子操作。可能在删除缓存后,还未完成数据库操作时,其他线程又重新将旧的无效数据加载到缓存中,导致缓存中存放的是无效数据。
2. 先更新/删除数据库数据,再删除缓存
- 操作步骤:更新/删除数据库 → 删除缓存。
- 问题 :虽然其他线程在缓存未命中时会从数据库加载最新数据,但由于数据库操作和缓存操作并非原子操作,可能出现以下情况:
- 线程A未命中缓存,从数据库加载数据;
- 在数据还未加载到缓存时,当前线程删除缓存;
- 线程A继续运行,将旧的无效数据加载到缓存中,导致数据不一致。
3. 先更新/删除数据库数据,再删除缓存,延迟二次删除缓存
- 操作步骤 :
- 更新/删除数据库;
- 删除缓存;
- 延迟再次删除缓存(延迟时间根据数据从数据库加载到缓存所需时间而定)。
- 改进点:针对第二种情况进行了优化。即使出现第二种情况中提到的缓存加载了旧数据,延迟删除会再次将无效数据从缓存中删除,从而保证重新从数据库加载的是最新数据。
4.1.2 策略1
实现步骤:
- 设置缓存过期时间:相当于定期删除过期缓存重新同步数据
- 数据获取流程:优先从缓存获取,缓存为空则从数据库获取,获取后添加缓存
- 数据修改处理 :当数据库中数据被修改时,删除相关缓存项
- 向消息队列发布消息,进行缓存的延迟二次删除(双写策略)
应用场景:
数据的修改频率不高的场景
- 验证码
- 会话
- 用户元信息
- 用户身份角色类型
- 视频元信息
- 用户视频列表
4.1.3 策略2
实现步骤:
- 缓存设置:缓存不设置过期时间
- 数据获取流程:优先从缓存获取,缓存为空则从数据库获取,获取后添加缓存
- 数据同步机制 :添加缓存的同时,发布消息队列任务:5~10min后,将缓存数据同步到数据库
- 同步到数据库的同时删除缓存并向消息队列发布消息,进行缓存的延迟二次删除(双写策略)
应用场景:
对数据实时性要求较高,且数据的修改频率较高的场景,修改数据的时候直接修改缓存,定期向数据库同步数据
- 用户统计数据
- 用户总视频点赞量
- 用户总视频播放量
- 视频统计数据
- 视频播放量
- 视频点赞量
4.1.4 策略3
实现步骤:
- 缓存初始化:缓存为空时,从数据库加载批量数据到缓存
- 过期策略:将缓存过期时间定义较短(10~20分钟),使用定时过期实现定期同步(不在数据改变时手动更新)
- 数据获取流程:优先从缓存获取,缓存为空则从数据库获取,获取后添加缓存
- 缓存维护:若缓存数据量低于批量值,则同步修改缓存/直接删除缓存,重新进行同步
应用场景:
允许一定时间的缓存与数据库数据不一致场景
- 主页视频列表
- 分类视频列表
- 标签视频列表
技术实现细节:
缓存中各个列表各自加载1000条ID信息,默认20条/页的情况下,相当于默认50页数据,以zset进行存储 {video_id, play_count},根据播放量排序后,直接从缓存获取第N页ID即可。
若获取的偏移量超过1000,则从数据库获取数据(无需放入缓存,因为很少有人能翻到50页以后)
4.2数据淘汰策略:
内存占满后,删除最久未访问数据。
bash
/* redis.conf */
maxmemory-policy allkeys-lru
maxmemory 1gb
4.3缓存设计
4.3.1 验证码
- 过期时间:5~10min
- 缓存类型:hash
- key :
verify_CODE_ID - 字段 :
- 会话ID:用户标识,标识当前验证码属于哪个客户端
- 验证码ID:验证码唯一标识
- 验证码:四位数字字符
4.3.2 会话
- 过期时间:120~180min
- 缓存类型:hash
- key :
session_SESSION_ID - 字段 :
- 会话ID
- 用户ID:该字段是否存在,用于判断会话是否是临时会话
4.3.3 授权信息
授权信息描述哪些操作可以被哪些用户执行。
- 过期时间:永久(该信息适用于所有用户,在数据库缓存改变的时候重新加载)
- 缓存类型:set
- key :
permission_roles_URL - 值:SET:[角色类型]
4.3.4 用户信息
- 过期时间:60~120min
- 缓存类型:hash
- key :
user_meta_USER_ID - 字段 :
- 用户ID
- 用户昵称
- 备用昵称
- 用户头像文件ID
- 用户状态
- 用户备注
- 用户注册时间
4.3.5 用户统计数据
- 过期时间:不限制,由子服务进行同步管理
- 缓存类型:hash
- key :
user_count_USER_ID - 字段 :
- 总播放量
- 总点赞量
- 粉丝数量
- 关注数量
说明:没有和用户信息放在一起是为了防止用户信息缓存过期,导致统计数据还没有同步到数据库就被自动删除。
4.3.6 用户身份角色
缓存用户与身份角色的关联信息,主要用于用户的访问权限控制。
- 过期时间:60~120min
- 缓存类型:hash
- key :
user_identify_role_USER_ID - 字段 :
<身份类型,角色类型>:一个用户在一个身份中只能拥有一个角色- 例如:
<identify1:role1>, <identify2:role2>
- 例如:
说明:没有放入到用户信息缓存中是因为用户身份和角色有可能会存在多个,因此在用户信息中不便于扩展。
4.3.7 视频信息
- 过期时间:60~120min
- 缓存类型:hash
- key :
video_meta_VIDEO_ID - 字段 :
- 视频ID
- 上传者ID
- 审核者ID
- 视频文件ID
- 封面文件ID
- 标题
- 简介
- 时长
visibly - 状态 - 上传时间
4.3.8 视频统计数据
- 过期时间:不限制,由子服务进行同步管理
- 缓存类型:hash
- key :
video_count_VIDEO_ID - 字段 :
- 播放量
- 点赞量
4.3.9 主页视频列表缓存
视频列表的缓存,不缓存所有的视频信息,而是缓存批量的视频ID。获取视频列表时,先分页获取视频ID,再通过视频ID获取视频信息。
原因:
- Redis缓存中,hash值中不允许再存储hash,因此一个zset集合中不可能存储hash数据。
- 将视频的数据批量序列化后,就没办法分页获取了。
- 过期时间:10~20min
- 缓存类型:zset(视频ID&时间戳为权重)
- key :
home_video_list - 值:ZSET:[{member:视频ID,date:时间戳}, ...]
默认缓存1000条视频ID,定期更新(允许一定时间内的数据不一致)。
4.3.10 用户视频列表缓存
- 过期时间:30~60min
- 缓存类型:zset
- key :
user_video_list_USER_ID - 值:ZSET:[{member:视频ID,score:播放量}, ...]
默认缓存用户所有的视频ID列表,用户上传/删除/下架时删除缓存。
4.3.11 分类视频列表缓存
- 过期时间:10~20min
- 缓存类型:zset
- key :
category_video_list_CATEGORY_TYPE - 值:ZSET:[{member:视频ID,score:播放量}, ...]
默认缓存1000条视频ID,定期更新(允许一定时间内的数据不一致)。若缓存数据量不足1000,则在指定分类视频上传/删除/下架时删除缓存。
4.3.12 标签视频列表缓存
- 过期时间:30~60min
- 缓存类型:zset
- key :
tag_video_list_TAG_TYPE - 值:ZSET:[{member:视频ID,score:播放量}, ...]
默认缓存1000条视频ID,定期更新(允许一定时间内的数据不一致)。若缓存数据量不足1000,则在指定分类视频上传/删除/下架时删除缓存。
4.3.13 状态视频列表缓存
不进⾏缓存------因为这个接口是在管理员管理视频时获取的,且获取就是为了修改,因此没必要进行缓存。
五.消息队列设计
5.1 延时消息队列
5.1.2 视频点赞/播放量同步队列
⽤于视频⼦服务增加视频播放量(将视频的统计数据加载到缓存)时,发布延时消息,在延时5~10min后,将缓存的播放量同步到数据库中,进⽽减少⾼频对数据库的修改操作。
5.1.2.1 队列元素:
- 交换机:delete_cache_exchange(延迟消息类型,3s)
- 队列:delete_cache_queue
- BindingKey:delete_cache_queue
5.1.2.2 消息格式:
message.proto
proto
message CacheToDBMsg {
repeated string video_id = 1;
}
该消息由视频⼦服务发布,也由视频⼦服务订阅处理,交换机类型。
5.1.2 缓存延迟删除消息队列设计
主要通过双写策略实现MySQL数据库与Redis缓存之间的数据同步:
- 修改数据库数据,删除缓存
- 可能在修改与删除之间有其他服务/线程⼜加载了⽼数据到缓存中
- 发布延时1~3s(根据操作⼀次数据所需时间⽽定)的消息,再次删除缓存
- 这时候如果其他执⾏流加载重新加载数据必然都是最新数据
5.1.2.1 队列元素:
- 交换机:delete_cache_exchange(延迟消息类型,3s)
- 队列:delete_cache_queue
- BindingKey:delete_cache_queue
5.1.2.2 消息格式:
message.proto
proto
message DeleteCacheMsg {
repeated string key = 1;
}
该消息由⽤⼾/视频/⽂件⼦服务发布,由⽤⼾/视频/⽂件⼦服务进⾏订阅处理。
5.2 普通消息队列
5.2.1 ⽂件删除消息队列
主要⽤于⽤⼾⼦服务更换头像⽂件,或视频⼦服务删除视频时,发布消息进⾏⽂件的删除。
5.2.1.1 队列元素:
- 交换机:delete_file_exchange(直接交换)
- 队列:delete_file_queue
- BindingKey:delete_file_queue
5.2.1.2 消息格式:
message.proto
proto
message DeleteFileMsg {
repeated string file_id = 1;
}
该消息由⽤⼾/视频⼦服务发布,由⽂件⼦服务订阅处理。
5.2.2 视频转码消息队列
主要⽤于视频⼦服务新增视频信息时,发布消息给转码⼦服务对视频的对应视频⽂件进⾏HSL转码。
5.2.2.1 队列元素:
- 交换机:hls_transcode_exchange(直接交换)
- 队列:hls_transcode_queue
- BindingKey:hls_transcode_queue
5.2.2.2 消息格式:
message.proto
proto
message HLSTranscodeMsg {
repeated string video_id = 1;
}
该消息由视频⼦服务发布,由转码⼦服务进⾏订阅处理。