快速全球分布式用户配额管理架构
Architecture for fast globally distributed user quota management
我们构建了一个免费的全球分布式移动分析 REST API。这意味着我们在世界各地都有服务器,运行 同一应用程序的不同版本(美国、欧洲等)。这些服务位于负载均衡器之后,因此我无法保证如果 he/she 今天或明天发出请求,同一用户总是得到相同的 application/server。 API 是 public,但用户必须提供 API 密钥,以便我们将他们与他们的付费请求配额相匹配。
由于我们对每个请求都进行了大量的 c运行ching,因此我们希望尽可能减少请求时间,特别是 authentication/authorization 和配额监控。由于我们目前只使用一个用户数据库(必须位于一个数据中心),因此在某些情况下,美国用户会向美国的 application/server 发出请求,从而对欧洲用户进行身份验证。所以我们正在寻找一种解决方案,其中用户数据库交互:
- 在同一个应用程序服务器上发生
- 获取所有应用程序服务器之间的同步
- 应该可以轻松集成到 java 应用程序中
- 应该很快(每次请求都会发生变化)
我们目前所做的事情:
- 每个服务器上的单个数据库 > 不同步,噩梦
- 所有服务器的单一数据库 > 好的,当与 slave 一起使用作为后备时,但美国用户必须通过大西洋进行身份验证
- 开始安装 bdr 但在途中失败(没时间,太复杂,难以过渡)
- 看了redis.io
由于这是我的第一个全球分布式 REST API 我想知道其他公司是如何做到这一点的。 (yelp、Google 等)
非常感谢任何反馈,
干杯,
丹尼尔
没有正确答案,有几种方法可以执行此操作。我将描述我最熟悉的方法(这可能是最简单的方法之一,尽管可能不是最可靠的方法)。
1。分离认证和授权
首先分开认证和授权。跨大西洋的用户authenticating没问题,每个需要authorization跨大西洋的用户请求是not.主要用户凭据(例如密码哈希)是集中资源,没有办法解决。但是每次需要授权请求时,您都不需要主凭据。
这是我工作的一家公司的做法(虽然这是postgres
/python
/django
,与java
无关):
- 每个服务器在
memcached
中都有一个数据库查询缓存,但它不缓存用户身份验证(memcached
与您提到的 redis
非常相似)。
- 身份验证始终在主数据中心执行。
- 成功的身份验证生成一个用户 session,该用户将在一天后过期。
- 缓存 session。
这可能会产生这样一种情况,在这种情况下,一个用户可以在给定时间获得多个 session 授权的操作。算法是:如果至少有一个用户session没有过期,则该用户被授权。由于缓存是本地的,因此无需为每个请求进行跨大西洋获取数据的昂贵操作。
2。 Session复兴
Session 正好在一天后过期可能会让用户感到厌烦,因为他永远无法确定他的 session 是否会在接下来的几秒钟内过期。 session 授权的用户发出的每个请求都应将 session 生命周期再次延长至一整天。
这很容易实现:您需要在 session 中保留上次发出的请求的 timestamp
。并根据 timestamp
计算 session 的生命周期。如果不止一个 session 可以授权请求,则算法应 select 较年轻的 session (剩余生命周期最长)进行更新。
3。请求记录
这是我使用的实时系统的全部内容。答案的其余部分是关于我将如何扩展这样一个系统,使 space 用于记录请求和验证请求配额。
假设
让我们先做几个设计假设,然后讨论为什么它们是好的假设。您现在是 运行 一个分布式系统,因为并非系统上的所有数据都在一个地方。分布式系统应该优先考虑响应速度,并尽可能水平(不分层),为此牺牲一致性。
上面的session机制已经牺牲了一些一致性。例如,如果用户登录并连续与 server A
对话一天半,然后负载均衡器将用户指向 server B
,用户可能会惊讶于他需要再次登录。这是不太可能发生的情况:在大多数情况下,负载均衡器会在一天半的时间内将用户的请求平均分配给 server A
和 server B
。 server A
和 server B
届时都将有直播 session。
在您的问题中,您想知道 google 如何处理请求计数。我会在这里争辩说,它以一种不一致的方式处理它。我的意思是,如果您牺牲了一致性,就不能强制执行严格的请求上限。 google 或 yelp 等公司只需说:
"You have 12000 requests per month but if you do more than that you will pay [=25=].05 for every 10 requests above that".
这允许更简单的设计:您可以在请求发生后随时对请求进行计数,计数不需要实时发生。
最后一个假设:分布式系统将有重复的内部数据的问题。发生这种情况是因为系统的某些部分 运行 是实时的,并且某些部分正在进行批处理而无需停止实时系统或为其添加时间戳。发生这种情况是因为您不能 100% 确定实时系统在任何给定点的状态。来自客户的每个请求都必须具有某种唯一标识符,它可以像客户编号+序列号一样简单,但它需要存在于每个请求中。您也可以在收到请求时添加这样一个唯一标识符。
设计
现在我们扩展缓存在每台服务器上的用户 session(通常以不同的状态缓存在彼此不知道的不同服务器上)。对于每个客户请求,我们都会将请求的唯一标识符存储为 session 缓存的一部分。没错,中央数据库中的请求计数器并不是实时更新的。
在某个时间点(例如,日终处理)每个服务器执行一个批处理处理请求标识符:
- Live session 重复,副本已过期。
- 所有过期的session中的请求标识符被连接在一起,并写入中央数据库。
- 所有过期的 session 都被清除。
这会产生竞争条件,其中实时session可能会在服务器与中央数据库对话时接收请求。出于这个原因,我们不会从实时 session 中清除请求标识符。这反过来会导致请求标识符可能会两次记录到中央数据库的问题。正是出于这个原因,我们需要标识符是唯一的,中央数据库 将忽略已记录标识符的请求更新 .
优势
- 99.9% 正常运行时间,批处理不中断实时系统。
- 减少对数据库的写入,并减少与数据库的一般通信。
- 容易横向增长。
缺点
- 如果服务器出现故障,恢复执行的请求可能很棘手。
- 没有办法阻止客户发出比他允许的更多的请求(这是分布式系统的一个特点)。
- 需要为所有请求存储唯一标识符,一个计数器不足以统计请求数。
为用户开具发票不会改变,您只需查询中央数据库并查看客户执行了多少请求。
我们构建了一个免费的全球分布式移动分析 REST API。这意味着我们在世界各地都有服务器,运行 同一应用程序的不同版本(美国、欧洲等)。这些服务位于负载均衡器之后,因此我无法保证如果 he/she 今天或明天发出请求,同一用户总是得到相同的 application/server。 API 是 public,但用户必须提供 API 密钥,以便我们将他们与他们的付费请求配额相匹配。
由于我们对每个请求都进行了大量的 c运行ching,因此我们希望尽可能减少请求时间,特别是 authentication/authorization 和配额监控。由于我们目前只使用一个用户数据库(必须位于一个数据中心),因此在某些情况下,美国用户会向美国的 application/server 发出请求,从而对欧洲用户进行身份验证。所以我们正在寻找一种解决方案,其中用户数据库交互:
- 在同一个应用程序服务器上发生
- 获取所有应用程序服务器之间的同步
- 应该可以轻松集成到 java 应用程序中
- 应该很快(每次请求都会发生变化)
我们目前所做的事情:
- 每个服务器上的单个数据库 > 不同步,噩梦
- 所有服务器的单一数据库 > 好的,当与 slave 一起使用作为后备时,但美国用户必须通过大西洋进行身份验证
- 开始安装 bdr 但在途中失败(没时间,太复杂,难以过渡)
- 看了redis.io
由于这是我的第一个全球分布式 REST API 我想知道其他公司是如何做到这一点的。 (yelp、Google 等)
非常感谢任何反馈,
干杯,
丹尼尔
没有正确答案,有几种方法可以执行此操作。我将描述我最熟悉的方法(这可能是最简单的方法之一,尽管可能不是最可靠的方法)。
1。分离认证和授权
首先分开认证和授权。跨大西洋的用户authenticating没问题,每个需要authorization跨大西洋的用户请求是not.主要用户凭据(例如密码哈希)是集中资源,没有办法解决。但是每次需要授权请求时,您都不需要主凭据。
这是我工作的一家公司的做法(虽然这是postgres
/python
/django
,与java
无关):
- 每个服务器在
memcached
中都有一个数据库查询缓存,但它不缓存用户身份验证(memcached
与您提到的redis
非常相似)。 - 身份验证始终在主数据中心执行。
- 成功的身份验证生成一个用户 session,该用户将在一天后过期。
- 缓存 session。
这可能会产生这样一种情况,在这种情况下,一个用户可以在给定时间获得多个 session 授权的操作。算法是:如果至少有一个用户session没有过期,则该用户被授权。由于缓存是本地的,因此无需为每个请求进行跨大西洋获取数据的昂贵操作。
2。 Session复兴
Session 正好在一天后过期可能会让用户感到厌烦,因为他永远无法确定他的 session 是否会在接下来的几秒钟内过期。 session 授权的用户发出的每个请求都应将 session 生命周期再次延长至一整天。
这很容易实现:您需要在 session 中保留上次发出的请求的 timestamp
。并根据 timestamp
计算 session 的生命周期。如果不止一个 session 可以授权请求,则算法应 select 较年轻的 session (剩余生命周期最长)进行更新。
3。请求记录
这是我使用的实时系统的全部内容。答案的其余部分是关于我将如何扩展这样一个系统,使 space 用于记录请求和验证请求配额。
假设
让我们先做几个设计假设,然后讨论为什么它们是好的假设。您现在是 运行 一个分布式系统,因为并非系统上的所有数据都在一个地方。分布式系统应该优先考虑响应速度,并尽可能水平(不分层),为此牺牲一致性。
上面的session机制已经牺牲了一些一致性。例如,如果用户登录并连续与 server A
对话一天半,然后负载均衡器将用户指向 server B
,用户可能会惊讶于他需要再次登录。这是不太可能发生的情况:在大多数情况下,负载均衡器会在一天半的时间内将用户的请求平均分配给 server A
和 server B
。 server A
和 server B
届时都将有直播 session。
在您的问题中,您想知道 google 如何处理请求计数。我会在这里争辩说,它以一种不一致的方式处理它。我的意思是,如果您牺牲了一致性,就不能强制执行严格的请求上限。 google 或 yelp 等公司只需说:
"You have 12000 requests per month but if you do more than that you will pay [=25=].05 for every 10 requests above that".
这允许更简单的设计:您可以在请求发生后随时对请求进行计数,计数不需要实时发生。
最后一个假设:分布式系统将有重复的内部数据的问题。发生这种情况是因为系统的某些部分 运行 是实时的,并且某些部分正在进行批处理而无需停止实时系统或为其添加时间戳。发生这种情况是因为您不能 100% 确定实时系统在任何给定点的状态。来自客户的每个请求都必须具有某种唯一标识符,它可以像客户编号+序列号一样简单,但它需要存在于每个请求中。您也可以在收到请求时添加这样一个唯一标识符。
设计
现在我们扩展缓存在每台服务器上的用户 session(通常以不同的状态缓存在彼此不知道的不同服务器上)。对于每个客户请求,我们都会将请求的唯一标识符存储为 session 缓存的一部分。没错,中央数据库中的请求计数器并不是实时更新的。
在某个时间点(例如,日终处理)每个服务器执行一个批处理处理请求标识符:
- Live session 重复,副本已过期。
- 所有过期的session中的请求标识符被连接在一起,并写入中央数据库。
- 所有过期的 session 都被清除。
这会产生竞争条件,其中实时session可能会在服务器与中央数据库对话时接收请求。出于这个原因,我们不会从实时 session 中清除请求标识符。这反过来会导致请求标识符可能会两次记录到中央数据库的问题。正是出于这个原因,我们需要标识符是唯一的,中央数据库 将忽略已记录标识符的请求更新 .
优势
- 99.9% 正常运行时间,批处理不中断实时系统。
- 减少对数据库的写入,并减少与数据库的一般通信。
- 容易横向增长。
缺点
- 如果服务器出现故障,恢复执行的请求可能很棘手。
- 没有办法阻止客户发出比他允许的更多的请求(这是分布式系统的一个特点)。
- 需要为所有请求存储唯一标识符,一个计数器不足以统计请求数。
为用户开具发票不会改变,您只需查询中央数据库并查看客户执行了多少请求。