/Mini-Tiktok

这是一个来源于字节跳动后端青训营的项目。A project from ByteDance backend youth training camp.

Primary LanguageGo

Mini-Tiktok 抖音精简版

前言:

  这是来源于字节跳动后端青训营的项目之一,当时虽然报名了,但是因为临近期末而且是课多的大二,组里其他人也没什么空,唯一一次能做团队大项目的机会就这么泡汤了。

  但是现在大三了,为了准备投递实习的后端项目,我又得回来自己一个人把这个项目给实现了(自己一个人当架构,开发,测试,运维,累死了)。大家这个时候都在准备复习,笔试面试,我还在准备项目,这也是前期偷懒摸鱼留下来的苦果......可恶!!!


项目介绍:

简介:

  本项目由一个拥有有趣的灵魂的人独自开发,使用了微服务架构(但也没那么“微”),下面列举用到的框架和组件等:

  • 微服务框架(包含 rpc, api 网关功能): go-zero
  • 服务注册中心:Etcd
  • 视频转码: ffmpeg
  • 消息队列: Kafka
  • 存储: Redis, MySQL, Aliyun OSS

      项目实现的功能如下:

  • 视频流接口
  • 用户注册
  • 用户登录
  • 获取用户信息
  • 投稿接口
  • 发布列表
  • 点赞操作
  • 喜欢列表
  • 评论操作
  • 评论列表
  • 关注操作
  • 关注列表
  • 粉丝列表

      项目根据以上功能将项目分为四个服务:用户服务(user),视频服务(video),鉴权服务(jwt),投稿转码服务(publish)。

      其中,投稿转码服务使用 Kafka 消费消息来接收 Api 发来的请求,因为投稿功能在 api 层只完成校验文件格式并上传原视频至 OSS 的工作后便响应客户端成功消息,转码工作是异步交给转码服务完成的。所以这也会出现客户端收到“成功发布”的消息后,需要延迟一会儿才能看见自己投稿视频的情况。为什么这么做呢?因为有的视频网站是这样的,转码并审核完成后再通知你投稿成功。(不过这项目并没有通知功能 🤣)

      另外,在视频服务中也使用了 Kafka 消费消息的方式,来让一个独立进程完成点赞的异步写入数据库功能。 其它人做这个项目可能把评论,关注这些操作也通过 MQ 异步写入数据库,我个人是认为点赞是流量最大的操作,需要使用 MQ 削峰。而评论,关注操作根据实际观察,并没有那么大的流量。(大家都很吝啬 🤣 十万点赞的视频只得到几千条评论和几百或几千的关注)

    项目特点:

       关于 Redis 的多组操作,我都尽量用 lua 进行封装,整合成一次 RTT 即可处理,有些服务需要访问 Redis 的次数因此被削减到了原来的一半以下。具体可以查看各服务文件夹下的 app/rpc/model/redisCache 文件夹,每个服务都封装了很多 Redis lua 方法。

       另外关于缓存的设计也值得一提,在服务(用户,视频)启动时,会从 MySQL 将最可能成为热点的一些数据挑出来进行缓存的预热。并且用于缓存预热的数据数量可以从服务配置文件中自行修改。此外,几乎每一个查询服务都有可能做到不需要访问数据库。具体缓存是什么类型,怎么样缓存的,可以自行查看对应的类型定义文件( xxxModel.go),以及 redisCache 下的 redis.go。

      总的来说,项目的主要特点就是高性能。例如组件选用的都是性能较高的,比如 Kafka 做消息队列,go-zero 中使用的 zrpc 是基于高性能 rpc 框架 grpc 封装的等等。还有缓存的设计,减少 RTT 次数的优化。另外在 MySQL 表中也设计了不少用于优化查询的索引,比如对保存时间戳的字段 "create_time" 建立索引,用于加速按时间倒序的查询等等。

      那么说了特点,缺点呢?由于追求性能,数据一致性是很难保证的。比如项目存在这样一个问题:即点赞这样的热点操作我是没有设计让它去碰数据库的(是指响应客户端前,不包括异步写库时)。那么用户如果通过一些工具,比如用 postman 对于点赞接口重复请求进行点赞怎么办?现在只是通过用户最近点赞过的视频 id 缓存来判断是否点过赞,默认是缓存 30 条视频。而如果数据已经不在该缓存中,又会响应点赞成功了。对于这一个问题,首先在 MySQL 的点赞关系表中 user_id 和 video_id 是 联合主键,所以插入相同点赞记录会失败,这时候我们再判断点赞记录是否已存在,若存在则不做任何修改操作,返回错误信息。而不存在的话,我们就把该点赞的缓存删除来保持数据一致。也就是说,负责消费 kafka 消息来异步写 db 的 “消费者” 消费失败时,我并没有做消息重试机制,而是一种回滚缓存的操作来保持数据一致。不过由于本项目客户端没有通知功能,所以用户并不能知道自己所做的操作后来失败了。

      除了数据一致性难保证,安全方面也存在问题,比如为了高性能,jwt 鉴权并不打算使用 Redis 弄什么黑名单功能(不过引入 Redis 这样一来 jwt 的特点也就消失了),配置也是密码什么的全部写在本地配置,没有让 Etcd 去实现配置中心的功能。此外,错误发生时响应客户端的消息也没能做很好的设计,比如有些直接把错误信息丢给客户端了。🤣🤣


    后记

      给的抖音精简版客户端的 bug 多得真是一言难尽......还有接口的要求可能是为了让实现变得简单,拿取的信息并没有分页,不过我还是按照分页来进行缓存设计了。倒也不是按分页设计缓存不能用,比如为每个人维护 30 条最新发布过的视频缓存,多数用户还是能靠只访问缓存从而就能够获取所有发布视频信息的,只有少数的高产用户需要访问数据库。(哎呀~写到这里才想起来还有更多的人什么视频都不发,我忘记给这种人也建立缓存来标记一下发布视频是空的啦!下次吧!)

      各服务的详细介绍可以自行查看源码,或者等我之后在个人网站更新的博客(很快就会出来啦),发布之后会更新这里的 Readme 的。🍭🍭🍭

      附赠原来的接口文档:https://www.apifox.com/apidoc/shared-8cc50618-0da6-4d5e-a398-76f3b8f766c5/api-18345145