抖音基础版(字节跳动青训项目)
一、项目介绍
- 本抖音项目是基于grpc通讯协议开发的高性能微服务,不仅使用gin作为业务层框架,gorm框架作为持久层框架,还使用预编译sql防止sql注入,同时该项目结合连接池技术来构建连接工厂和复用grpc连接来提高系统的性能,这样可以有效的处理高并发场景下的挑战,还可以通过减少频繁创建和销毁grpc连接带来的性能开销
- 项目服务地址:https://1024code.com/codecubes/jpyi9rm
- 项目地址:https://github.com/fineCoderWithLove/douyin-base
二、项目实现
2.1技术选型
-
gin:提供grpc服务使用protobuf进行数据传输。
-
JWT:token生成和权限的校验
-
Gorm:对Mysql执行ORM操作,Go-redis:操作Redis对频繁更改的数据进行缓存以便更快的响应。
-
Redis:对点赞/取消赞,视频的喜欢量/评论量,用户的喜欢量,总点赞量缓存Redis中,设置定时任务,并且使数据同步到数据库中。
-
Zap:高性能日志打印
-
ffmpeg:进行视频取帧,作为视频的封面
-
七牛云:使用七牛云做对象存储,用来存储视频,图片等静态资源。
-
pprof:使用pprof进行性能测试
2.2架构设计
由于项目的耦合度不高,所以采用微服务架构来缓解服务器的压力,项目分为api层,业务服务层,数据层
-
api层负责鉴权和分发请求调用远程服务来返回数据
-
业务层负责与数据库进行交互和逻辑处理
2.3代码目录介绍
├─base-service # 基础服务
│ ├─cmd # 启动类
│ ├─global # 定义全局信息
│ │ └─constant # 定义全局常量
│ ├─handler # 业务处理
│ ├─model # 定义常用结构体
│ │ └─video
│ ├─proto # proto文件
│ │ └─favorite
│ ├─test # 测试类
│ ├─util # 封装工具类
│ └─videoproto # 视频的proto文件
├─douyin-api # 外部网关
│ ├─api # grpc服务调用
│ ├─cmd # 启动类
│ ├─global # 定义全局变量
│ ├─globalinit # 定义全局日志信息
│ │ └─constant
│ ├─proto # proto文件
│ ├─redis # 封装redis工具类
│ ├─router # 加载路由信息
│ └─util # 封装工具类
├─interaction-service # 互动模块
│ ├─dao # gen代码生成器
│ │ └─gen
│ ├─global # 定义全局信息
│ │ └─constant
│ ├─handler # 处理业务信息
│ ├─model # 定义常用结构体
│ ├─proto # proto文件
│ │ ├─comment
│ │ ├─favorite
│ │ ├─user
│ │ └─video
│ └─server # grpc启动类
│ ├─comment
│ └─favorite
├─log # 输入的日志信息
│ └─info
└─social-service # 社交模块├─cmd # 启动类├─global # 定义全局变量├─handler # 处理业务├─proto # proto文件│ ├─favorite│ ├─message│ ├─relation│ └─user└─util # 封装的工具类
接口文档地址: https://apifox.com/apidoc/shared-09d88f32-0b6c-4157-9d07-a36d32d7a75c/api-50717106
三、测试结果
3.1功能测试
功能项 | 接口名称 | 测试点 | 模块 | 结果 |
---|---|---|---|---|
基础接口 | 视频流接口 | 不限制登录状态,返回按投稿时间倒序的视频列表 | base-service | 测试通过 |
基础接口 | 用户注册 | 新用户注册时提供用户名,密码即可,用户名需要保证唯一。创建成功后返回用户 id 和权限token | base-service | 测试通过 |
基础接口 | 用户登录 | 通过用户名和密码进行登录,登录成功后返回用户 id 和权限 token | base-service | 测试通过 |
基础接口 | 用户信息 | 获取用户的 id、昵称,如果实现社交部分的功能,还会返回关注数和粉丝数 | base-service | 测试通过 |
基础接口 | 投稿列表 | 登录用户选择视频上传 | base-service | 测试通过 |
基础接口 | 发布列表 | 用户的视频发布列表,直接列出用户所有投稿过的视频 | base-service | 测试通过 |
互动接口 | 赞操作 | 登录用户对视频的点赞和取消点赞操作 | interaction-servic | 测试通过 |
互动接口 | 喜欢列表 | 用户的所有点赞视频 | interaction-service | 测试通过 |
互动接口 | 评论操作 | 登录用户对视频进行评论 | interaction-service | 测试通过 |
互动接口 | 评论列表 | 查看视频的所有评论,按发布时间倒序 | interaction-service | 测试通过 |
社交接口 | 关注操作 | 已登录的用户对其他用户进行关注 | social-service | 测试通过 |
社交接口 | 关注列表 | 已登录的用户查询其他用户的关注列表 | social-service | 测试通过 |
社交接口 | 粉丝列表 | 已登录的用户查询用户的粉丝列表 | social-service | 测试通过 |
社交接口 | 好友列表 | 已登录的用户查询好友列表 | social-service | 测试通过 |
社交接口 | 发送消息 | 已登录的用户给其他用户发送消息 | social-service | 测试通过 |
社交接口 | 聊天记录 | 已登录的用户查询与其他用户的聊天记录 | social-service | 测试通过 |
用户测试样例
用户鉴权失败样例
3.2性能测试
- 我们使用pprof进行性能监测,因为每次请求grpc都会产生连接和销毁连接造成服务的性能消耗,思考后我把grpc的连接设置成一个全局变量,后来发现这个全局变量有一个问题,在并发情况下,用同一个全局变量会导致读写错误。
- 经过思考,我设置了互斥锁的全局变量,但是性能提升不是很明显。
- 经过搜索引擎查询资料,最后利用线程池技术,简单工厂设计模式设计出了一个GrpcFactory工厂,每次只需要调用工厂就可以返回连接配合利用grpc的keep-alive使得grpc的连接开销变小。性能测试图如下:
优化前
优化后
四、项目总结与反思
4.1目前存在的问题
- 聊天记录存储到mysql中可能导致查询聊天记录mysql压力过大
- 敏感词过滤要耗费大量内存。
4.2已经识别的优化项
- 判断user和video是否存在的时候,可以直接从redis中判断增加速度
- 应该将聊天记录缓存到redis中{token:create_time}的形式,因为前端需要不断获取到最晚消息的发布时间
- 上传视频进行异步发送,减少用户等待时间优化用户体验。
- 因为迭代次数过多,代码冗余过多,代码内容不清晰,应该进行适当的封装和复用。
4.3架构演进的可能性
- 分库分表
- 做到数据库的读写分离
- 我们会在第七届青训营使用Hertz和Kitex重构代码
- 使用Minio做对象存储而不是使用七牛云
- 准备用机器学习训练模型加快强感词的过滤效果和速度而不是使用普通算法
4.4项目中的反思和总结
- 代码应该尽可能优雅的写法,让以后的改动是方便的,应该满足开放封闭原则。
- 一个优秀的程序员应该让别的程序员更好的工作,我的队友给我提供了很多的工具,让我工作更加高效。
- 测试是一个项目的重点,没有测试的软件是不合格的,而测试的关键则是边界点的问题。
- 每一个同步的位置都是并发情况下容易发生错误的地方,都要加上互斥锁。
- 一个项目应该敢为极致,在自己力所能及的地方做到最好,应该尝试多种可能性,寻找最好的解决办法!
五、部署
- 安装ffmpeg环境
- 改变每个模块中global的mysql连接和redis连接
- 改变base-service下的video中的七牛云密匙和仓库名称
- Linux下执行命令./run.sh
六、演示视频
【消失的token作品视频】https://www.bilibili.com/video/BV1634y1T71p?vd_source=04ce138fbcd8dc0d65299e3dccf2b3f1
后续迭代
数据库索引的建立
- 用户登录接口索引设置
用户登录接口,要验证用户名和密码的正确性,所以我们给user表的name和password字段加上了联合索引,避免了回表查询。 - 视频流接口索引设置
视频流接口需要查询晚于某一个时间的视频,所以我们在视频的发布时间需要创建索引,同时考虑到了索引失效的问题,对已经有的sql进行优化。 - 关注列表索引设计
我们的关注表是attention,其中字段只有user_id和touser_id,我们在获取关注列表的时候需要对这两个字段进行查询。 - 用户的喜欢列表索引
用户喜欢的列表需要查询favorites表,我们在user_id,video_id创建了idx_favorites_user_video联合索引。 - 软删除评论的索引
- 我们一开始设置软删除的时候是bool类型,但是这样使得索引效率不高,因为字段只有true和false,区分度低。
- 最后我们设置一个删除的时间,提高了区分度,使得idx_delete_comment索引使用更高效。
- 查询评论的索引
查询评论是根据视频的id查询的,我们在comments表的video_id创建了idx_select_comment_list 索引。
字典树算法实现敏感词过滤
在第七届,我们会使用机器学习训练模型来处理敏感词。