https://www.bilibili.com/video/BV1gu4y1Q7Z6/?vd_source=dc2c38f80860d6614a3028d4c18bce80 https://www.bilibili.com/video/BV1Jh4y1N7J6/?vd_source=dc2c38f80860d6614a3028d4c18bce80
该项目实现一款极简版抖音应用后台功能,主要采用了微服务架构设计。主要功能如下:
- 用户注册及登陆鉴权。
- 视频流推送。
- 用户点赞及评论,查看评论信息等互动功能。
- 用户互相关注,用户关系管理等相关功能。
- 好友间进行即时通信,消息缓存及在线推送等功能。
项目的解耦后的7个微服务功能与技术特征见下表:
服务名称 | 服务功能 | 代码框架 | 数据传输协议 | 服务路径 | IDL |
---|---|---|---|---|---|
Api | HTTP interface | hertz | http | cmd/api | ------ |
User | 用户信息管理 | kitex/gorm | protobuf3 | cmd/user | idl/user.proto |
Redis | 缓存热点数据 | kitex/gorm | protobuf3 | cmd/redis | idl/redis.proto |
Relation | 管理用户关系 | kitex/gorm | protobuf3 | cmd/relation | idl/relaton.proto |
Chat | 用户通信服务 | kitex/gorm | protobuf3 | cmd/chat | idl/chat.proto |
Interaction | 视频互动服务 | kitex/gorm | protobuf3 | cmd/interaction | idl/interaction.proto |
Video | 视频流推送 | kitex/gorm | protobuf3 | cmd/video | idl/video.proto |
- kitex: 微服务通信框架,生成微服务调用API及协议
- hertz: Golang 微服务 HTTP 框架,具有高易用性、高性能、高扩展性等特点
- gorm: 访问数据库接口服务
- redis: 缓存热点数据、减少 I/O;比如视频点赞数、作品数、粉丝数、关注数等用户更新较多的数据存放在redis,并且在 redis 中进行更新,减少访问数据库的 I/O 开销;同时这些数据使用 redis 有序集合 ZSET 存储,后续需要排行榜功能,可以直接调用 redis API;
- jaeger: 访问地址:http://localhost:16686/search 跨进程追踪(cross-process):调用另一个微服务 数据库追踪 进程内部的追踪(in-process):在一个函数内部的追踪
- docker: Docker 是一个轻量级的容器,的启动时间只需要一秒钟,从而大大的减少了开发、测试和部署的时间。轻松部署和扩展.
- jwt: 登录认证; JSON Web Token(JWT)是一个轻量级的认证规范,这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息。其本质是一个 token ,是一种紧凑的 URL 安全方法,用于在网络通信的双方之间传递。
- minio: 用户上传视频、头像等二进制数据,使用 minio 对其进行转换,生成 url;An object 是二进制数据,有时也称为 Binary 大对象 (BLOB)。 Blob 可以是图像、音频文件、电子表格,甚至 二进制可执行代码。 像 MinIO 这样的对象存储平台提供了专用的 用于存储、检索和搜索 blob 的工具和功能。
主要文件如下:
Dir | Introduction |
---|---|
cmd | API 网关、微服务代码文件 |
data | 保存用户上传的视频源文件 |
idl | proto、 thrift 文件,用于生成微服务 API、数据传输协议 |
kitex_gen | kitex 工具生成的微服务 API |
minio | 记录对象存储meta数据 :视频转化为 url |
pkg | 数据库创建表格 sql 文件、中间件 middleware、trace、常量 constants、错误机制 errno、工具函数 utils |
tools | 保存项目中用到的第三方工具及配置文件,比如 redis.conf |
docker-compose.yml | docker 启动文件 |
微服务代码结构, 以 user 微服务为例 (其它微服务结构类似):
File | Introduction |
---|---|
Makefile | 记录 kitex 生成微服务接口命令 |
cache | 登陆用户数据记录于内存,数据定时入库,用户数据修改前先查找cache层,再查数据库曾,减少数据库 I/O次数; |
dal | 数据库操作接口,用于 user 增删改查 |
handler.go | user 服务接口 |
main.go | user 服务主函数入口; |
pack | 用于数据打包,转化;比如数据库访问 model 转化为 proto格式 |
rpc | 调用其它微服务接口 |
service | 服务逻辑处理 |
docker-compose up # 执行前,注意把 tools 里面的 redis.conf 文件放到 docker-compose.yml 中配置的redis配置文件路径下
cd cmd/redis
sh build.sh
sh output/bootstrap.sh
cd cmd/user
sh build.sh
sh output/bootstrap.sh
cd cmd/video
sh build.sh
sh output/bootstrap.sh
cd cmd/relation
sh build.sh
sh output/bootstrap.sh
cd cmd/chat
sh build.sh
sh output/bootstrap.sh
cd cmd/...
sh build.sh
sh output/bootstrap.sh
cd cmd/api
./run.sh
visit http://127.0.0.1:16686/
on browser.
- 热门用户数据会被拉到redis中,减少 I/0 开销;
- 利用redis缓存热点数据;比如视频点赞数、作品数、粉丝数、关注数等用户更新较多的数据存放在了 redis,并且在 redis 中进行更新,减少访问数据库的 I/O 开销;同时这些数据使用 redis 有序集合 ZSET 存储,后续需要排行榜功能,可以直接调用 redis API;
- 数据库分库设计
用多数据库保存服务数据,解决高并发场景问题:
- 大量请求阻塞:在高并发场景下,大量请求都需要操作数据库,导致连接数不够了,请求处于阻塞状态。
- SQL 操作变慢:如果数据库中存在一张上亿数据量的表,一条 SQL 没有命中索引会全表扫描,这个查询耗时会非常久,开启慢查询日志。
- 存储出现问题 :业务量剧增,单库数据量越来越大,给存储造成巨大压力。
- redis
- 当前 redis 还是用的是单 redis client,为了提高并发效率,改进优化为 redis连接池
- redis 集群部署,提高 redis 高可用
- 负载均衡机制
- 各微服务进行服务器横向扩展,提高并发率;使用一致性哈希等负载均衡算法,将各微服务的服务请求依照权重等因素,分给各服务器;
- 消息队列
- 各服务间添加消息队列,各服务间解耦,减少数据丢失
以下是对应用对外部提供的API的一些测试结果。
curl --location --request GET '127.0.0.1:8080/douyin/feed/' \
--header 'Content-Type: application/json' \
--data-raw '{
"latest_time" :
"token"
}'
curl --location --request POST '127.0.0.1:8080/douyin/user/register/' \
--header 'Content-Type: application/json' \
--data-raw '{
"username":"lorain",
"password":"123456"
}'
curl --location --request POST '127.0.0.1:8080/douyin/user/login/' \
--header 'Content-Type: application/json' \
--data-raw '{
"username":"lorain",
"password":"123456"
}'
curl --location --request GET '127.0.0.1:8080/douyin/user/' \
--header 'Content-Type: application/json' \
--data-raw '{
"user_id":"lorain",
"token":"123456"
}'
curl --location --request POST '127.0.0.1:8080/douyin/publish/action/' \
--header 'Content-Type: application/json' \
--data-raw '{
"data":"lorain",
"token":"123456",
"title":"zzzzz"
}'
curl --location --request GET '127.0.0.1:8080/douyin/publish/list/' \
--header 'Content-Type: application/json' \
--data-raw '{
"token":"123456",
"user_id":"1111545"
}'
curl --location --request POST '127.0.0.1:8080/douyin/favorite/action/' \
--header 'Content-Type: application/json' \
--data-raw '{
"token":"123456",
"video_id":"1111545",
"action_type":"1"
}'
curl --location --request GET '127.0.0.1:8080/douyin/favorite/list/' \
--header 'Content-Type: application/json' \
--data-raw '{
"user_id":"1111545",
"token":"123456",
}'
curl --location --request POST '127.0.0.1:8080/douyin/comment/action/' \
--header 'Content-Type: application/json' \
--data-raw '{
"token":"123456",
"video_id":"1111545",
"action_type":"1",
"comment_text":"test",
"comment_id":"546",
}'
curl --location --request GET '127.0.0.1:8080/douyin/comment/list/' \
--header 'Content-Type: application/json' \
--data-raw '{
"token":"123456",
"video_id":"1111545",
}'
curl --location --request POST '127.0.0.1:8080/douyin/relation/action/' \
--header 'Content-Type: application/json' \
--data-raw '{
"token":"123456",
"to_user_id":"1111545",
"action_type":"1"
}'
curl --location --request GET '127.0.0.1:8080/douyin/relation/follow/list/' \
--header 'Content-Type: application/json' \
--data-raw '{
"user_id":"1111545",
"token":"123456",
}'
curl --location --request GET '127.0.0.1:8080/douyin/relation/follower/list/' \
--header 'Content-Type: application/json' \
--data-raw '{
"user_id":"1111545",
"token":"123456",
}'
curl --location --request GET '127.0.0.1:8080/douyin/relation/friend/list/' \
--header 'Content-Type: application/json' \
--data-raw '{
"user_id":"1111545",
"token":"123456",
}'
curl --location --request POST '127.0.0.1:8080/douyin/message/action/' \
--header 'Content-Type: application/json' \
--data-raw '{
"token":"123456",
"to_user_id":"1111545",
"action_type":"1",
"content":"douyin"
}'
curl --location --request GET '127.0.0.1:8080/douyin/message/chat/' \
--header 'Content-Type: application/json' \
--data-raw '{
"token":"123456",
"to_user_id":"1111545",
}'