我如何使Twitter后端
#api #go #distributedsystems #redis

1.简介

我正在使用微服务架构为Twitter设计一个后端。该设计基于Twitter的工程师的实施,我进行了一些轻微的修改以提高可扩展性和提高性能。随着我的进展,我将编码和实施此设计。

github项目链接:Twitter Backend

2.先决条件

基本系统设计概念,例如:

  • JWT
  • Caching
  • 负载平衡器
  • 消息队列/总线
  • 服务注册表

3.Features

  • 用户注册/登录-JWT
  • 用户应该能够发推文。
  • 用户可以删除推文
  • 用户可以看到其他用户的推文时间表(带有转发)。
  • 用户可以获取他们的家庭时间表。
  • 用户可以关注/取消关注其他人。
  • 系统应该是可扩展的。

4.挑战我们面对的

  • Twitters大量的每日推文对于扩展平台可能具有挑战性。与写作相比,Twitter本质上是一种读繁重的应用。这意味着用户消耗推文的用户比积极发推文更多。 Twitter仍会产生5800条推文,每秒btwð。

  • 由于作为读取重量应用程序,数据库将经历重负载。我们可以使用Redis等缓存来减少重负载。

  • 当推文发推文时,将发送到队列,因此可能需要一些时间才能显示在用户的主页上,但这是可以的,但是我们必须确保这次不增加gap。

  • JWT密钥共享以及其他服务

5.数据库设计

Twitter database Design

在此中,用户表与许多与推文,喜欢,转发,user_follows的一对一关系。

6.实施

验证服务

使用会话需要每次请求时都需要呼叫数据库。由于我们遵循微服务体系结构,我们将使用JWT来减少数据库调用。

Symmetric Key sharing

为了验证这些JWT令牌,我们必须分享每个服务的主密钥,这不是最佳实践。什么是解决方案不对称键!。基本上不对称密钥意味着我们将使用键加密并用另一个键进行解密。

Asymmetric key Sharing

主键将用于使令牌和从键只能用于验证这些令牌。为了创建不对称令牌,我们需要生成一个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
}

推文服务

推文服务负责发布推文,并将其推向时间线服务的消息队列。此外,该服务负责处理诸如转发和喜好之类的推文交互。转发也被推到消息队列以确保关注者可以看到它们。

Twitter Tweet Service

通过使用消息队列,可以实时和实时处理推文。兔子被用作该项目开发的消息队列。

在将推文发送到消息队列之前,它们存储在Postgres和Redis中。 REDIS用于存储该推文的副本,以及有关喜欢和转发推文的用户的信息。用户推文存储在分类的集合中,其关键格式“用户:$ {用户名}:Tweets” ,促进有效的数据组织和检索。

时间轴服务

Twitter Timleine service

时间轴服务的任务是获取用户时间轴和家庭时间轴。为了处理从MQ获得的传入推文,我们将使用一个单独的Goroutine。我们的方法将涉及每个新消息的迭代,如果作者的关注者数量低于x,我们将采用推送方法。否则,我们将选择拉动方法。

推方法

Twitter Fanout
在这种方法中,推文将发送给作者的每个关注者。这将存储在redis的关注者时间轴中,其密钥类似于“用户:$ {username}:时间轴”

  • 这种方法有什么问题?

假设一条推文作者拥有 1,000,000 关注者,因此使用此方法,时间表必须将此推文发送给100万用户。即使对于燃烧的快速redis ,也需要时间才能完成。在这种情况下,我们选择拉动方法

拉方法

当关注者的数量超过X时,拉动方法更有效,Twitter使用它来处理名人的推文。如果用户要求加载时间表,我们将首先检查他们是否遵循任何名人,可以通过维护用户关注的名人列表来完成。如果他们这样做,我们将从名人那里提取最新推文,并将其与用户的时间表合并,从而返回由此产生的推文。这种方法可确保只有活跃用户会收到数据,例如请求和服务机制。相比之下,推动方法将推文推向活跃的用户和不活动用户,从而浪费了带宽。

负载平衡器

Twitter Load Balancer

由于我们有多种服务,我们想要一种将请求引导到每个服务的方法,因此负载平衡器来了。 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