正文
,以及性能更好。
我们的许多原始 API 和 worker 是用 Python 开发,我们将这些功能逐渐移植到高并发的 Java 服务中。我们最初将设备数据存储在一组分片的(sharding)postgres 数据库中,但是访问规模的增加速度超过了我们添加新数据库分片的能力,因此我们将数据库迁移到 HBase 和 Cassandra。
CDP 是用于处理推送通知投递的一系列服务。这些服务都用于处理相同类型数据的请求,但是出于性能原因,每个服务以非常不同的方式索引数据。例如,我们有一个系统负责处理广播消息(向所有注册到同一个应用的设备推送同样的消息),此服务及数据存储的设计,与我们的个性化通知服务的设计是非常不同。
我们把所有长时间运行的进程都理解成服务。这些长时间进程在监测,配置和日志记录方面遵循我们的通用规范模板,以便部署和运维。
通常,我们的服务有两种类型:RPC 服务或队列消费者服务。
RPC服务
(非常类似于 gRPC 的内部框架)
提供一些功能命令和内部服务同步交互,而队列消费服务则处理来自 Kafka 的消息并且对那些消息执行服务特定操作。
数据库
为了满足我们的性能和规模要求,我们非常依赖 HBase 和 Cassandra 来满足数据存储需求。虽然 HBase 和 Cassandra 都是 NoSQL 存储,但是它们有非常不同的 tradeoff,并影响我们在什么场景该使用哪个存储。
HBase 高吞吐的扫描查找并返回较多数据,而 Cassandra 擅长较低的基数查找(返回的数据较少)。两者都允许大量的写吞吐量,这是我们的要求,因为来自用户手机的所有元数据更新都是实时的。
他们如何应对失败的策略也各不相同。
HBase 在失败的情况下倾向于保证一致性和分区容错性(C + P),而 Cassandra 则有利于可用性和分区容错性(A + P)。
每个 CDP 服务具有非常具体的情况,因此被设计为便于所需的访问模式。
作为一个通用规则,
每个数据库仅由单个服务访问
,该服务负责通过提供专用的接口来满足其他服务的数据访问的需求。
在服务及其后台数据库之间实现这种 1:1 关系,具有许多优点。
-
通过将服务的后端数据存储作为实现细节,而不是共享资源,我们获得了灵活性。
-
我们可以调整服务的数据模型,而只更改服务的代码。
-
使用跟踪更直接,这使得容量规划更容易。
-
故障排除更容易。将服务和数据库打包作为逻辑单元极大地简化了故障排除过程。我们不必想知道“还有谁可以访问这个数据库,使其以这种方式运行?”相反,我们可以依赖于来自服务本身的应用程序级监测,并且只关注一组访问模式。
-
因为只有一个服务与数据库交互,我们可以执行几乎所有的维护活动,而不会停机。重型维护任务成为一个服务级别的问题:数据修复,模式迁移甚至切换到完全不同的数据库,而不会中断服务。
确实,当将应用程序分解为较小的服务时,可能会有一些性能损失。但它带来的可扩展性和高可用性方面获得的灵活性,超过了性能降低带来的损失。