1.简介
我正在使用微服务架构为Twitter设计一个后端。该设计基于Twitter的工程师的实施,我进行了一些轻微的修改以提高可扩展性和提高性能。随着我的进展,我将编码和实施此设计。
github项目链接:Twitter Backend
2.先决条件
基本系统设计概念,例如:
- JWT
- Caching
- 负载平衡器
- 消息队列/总线
- 服务注册表
3.Features
- 用户注册/登录-JWT
- 用户应该能够发推文。
- 用户可以删除推文
- 用户可以看到其他用户的推文时间表(带有转发)。
- 用户可以获取他们的家庭时间表。
- 用户可以关注/取消关注其他人。
- 系统应该是可扩展的。
4.挑战我们面对的
-
Twitters大量的每日推文对于扩展平台可能具有挑战性。与写作相比,Twitter本质上是一种读繁重的应用。这意味着用户消耗推文的用户比积极发推文更多。 Twitter仍会产生5800条推文,每秒btwð。
-
由于作为读取重量应用程序,数据库将经历重负载。我们可以使用Redis等缓存来减少重负载。
-
当推文发推文时,将发送到队列,因此可能需要一些时间才能显示在用户的主页上,但这是可以的,但是我们必须确保这次不增加gap。
-
JWT密钥共享以及其他服务
5.数据库设计
在此中,用户表与许多与推文,喜欢,转发,user_follows的一对一关系。
6.实施
验证服务
使用会话需要每次请求时都需要呼叫数据库。由于我们遵循微服务体系结构,我们将使用JWT来减少数据库调用。
为了验证这些JWT令牌,我们必须分享每个服务的主密钥,这不是最佳实践。什么是解决方案不对称键!。基本上不对称密钥意味着我们将使用键加密并用另一个键进行解密。
主键将用于使令牌和从键只能用于验证这些令牌。为了创建不对称令牌,我们需要生成一个RSA密钥,然后创建一个私人和公共证书。
// generate key
privatekey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
fmt.Printf("Cannot generate RSA key\n")
os.Exit(1)
}
publickey := &privatekey.PublicKey
然后,我们将此键转移到 private.pem 文件中,以进一步用于生成JWT令牌
// dump private key to file
var privateKeyBytes []byte = x509.MarshalPKCS1PrivateKey(privatekey)
privateKeyBlock := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privateKeyBytes,
}
privatePem, err := os.Create("private.pem")
if err != nil {
fmt.Printf("error when create private.pem: %s \n", err)
os.Exit(1)
}
为 public.pem 证书执行此操作,我们使用此证书验证Jet令牌。
// dump public key to file
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publickey)
if err != nil {
fmt.Printf("error when dumping publickey: %s \n", err)
os.Exit(1)
}
publicKeyBlock := &pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyBytes,
}
publicPem, err := os.Create("../public.pem")
if err != nil {
fmt.Printf("error when create public.pem: %s \n", err)
os.Exit(1)
}
用 private.pem cerificate
生成JWT令牌的代码
func GenerateAccessToken(name, Id string) (string, error) {
privateKey, err := ioutil.ReadFile("private.pem")
if err != nil {
log.Fatal(err)
}
rsaprivateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKey)
if err != nil {
log.Fatal(err)
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
"name": name,
"ID": Id,
"exp": time.Now().Add(10 * time.Minute).Unix(),
})
tokenString, err := token.SignedString(rsaprivateKey)
if err != nil {
return "", errors.New("failed to create Token")
}
return tokenString, nil
}
现在,我们可以使用 public.pem 证书验证JWT令牌。我们可以进一步制作中间件功能来验证JWT令牌。
func ValidateJwt(signedToken string) (*jwt.Token, jwt.MapClaims, error) {
// Load the RSA public key from a file
publicKey, err := ioutil.ReadFile("../public.pem")
if err != nil {
log.Fatal(err)
}
rsaPublicKey, err := jwt.ParseRSAPublicKeyFromPEM(publicKey)
if err != nil {
log.Fatal(err)
}
// Parse the signed JWT and verify it with the RSA public key
token, err := jwt.Parse(signedToken, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexepcted signing method: %v", token.Header["alg"])
}
return rsaPublicKey, nil
})
claims, ok := token.Claims.(jwt.MapClaims)
if ok && token.Valid {
return token, claims, nil
}
return nil, nil, err
}
推文服务
推文服务负责发布推文,并将其推向时间线服务的消息队列。此外,该服务负责处理诸如转发和喜好之类的推文交互。转发也被推到消息队列以确保关注者可以看到它们。
通过使用消息队列,可以实时和实时处理推文。兔子被用作该项目开发的消息队列。
在将推文发送到消息队列之前,它们存储在Postgres和Redis中。 REDIS用于存储该推文的副本,以及有关喜欢和转发推文的用户的信息。用户推文存储在分类的集合中,其关键格式“用户:$ {用户名}:Tweets” ,促进有效的数据组织和检索。
时间轴服务
时间轴服务的任务是获取用户时间轴和家庭时间轴。为了处理从MQ获得的传入推文,我们将使用一个单独的Goroutine。我们的方法将涉及每个新消息的迭代,如果作者的关注者数量低于x,我们将采用推送方法。否则,我们将选择拉动方法。
推方法
在这种方法中,推文将发送给作者的每个关注者。这将存储在redis的关注者时间轴中,其密钥类似于“用户:$ {username}:时间轴” 。
- 这种方法有什么问题?
假设一条推文作者拥有 1,000,000 关注者,因此使用此方法,时间表必须将此推文发送给100万用户。即使对于燃烧的快速redis ,也需要时间才能完成。在这种情况下,我们选择拉动方法。
拉方法
当关注者的数量超过X时,拉动方法更有效,Twitter使用它来处理名人的推文。如果用户要求加载时间表,我们将首先检查他们是否遵循任何名人,可以通过维护用户关注的名人列表来完成。如果他们这样做,我们将从名人那里提取最新推文,并将其与用户的时间表合并,从而返回由此产生的推文。这种方法可确保只有活跃用户会收到数据,例如请求和服务机制。相比之下,推动方法将推文推向活跃的用户和不活动用户,从而浪费了带宽。
负载平衡器
由于我们有多种服务,我们想要一种将请求引导到每个服务的方法,因此负载平衡器来了。 Haproxy被用作该项目开发的负载平衡器。 Haproxy将其请求引导到适当的服务。
服务注册表
在这种情况下,我使用领事作为服务注册表中的每个启动注册到服务注册。我以某种方式配置了领事,即每10s之后,它就会向特定端点提出请求,如果它无法做出响应,则意味着我们的服务降低了。领事为我们提供了一个网络界面,我们可以在其中查看服务是否在线。
Web接口:Localhost:8500
Docker
我的Twitter-backend已被停靠,因此您只用一个命令运行它。 Docker可以轻松地帮助我设置这个项目。对于那些不知道Docker的人,只需1-2个命令即可运行任何数据库,加载平衡器或缓存。您还可以将您的应用程序扩展到生产中。
结论
这个项目很有趣,我通过构建这个项目学到了很多东西,希望您学到了一些新知识。请告诉我我犯的任何错误或如何改善。请务必在GitHub(可用文档)上查看我的项目,并查看我在Twitterð的线程。
github:https://github.com/leoantony72/twitter-backend
Twitter:https://twitter.com/serpico_z/status/1605899167439757312