构建BAAS云服务—CLOUDDATA架构设计

年假清零
• 阅读 3022

CloudData是什么?

任何一个App都需要一个Server,我们认为,移动开发者(或组织)不应该把精力放在这些事情上面:

  • 搭建后端Server服务。

  • 编写后端Server代码。

  • 设计Server底层数据存储架构。

  • 关注Server的高可用、可扩展、负载均衡、高性能等诸多繁琐问题。

这些事情可能会耗掉你80%以上的时间和精力,结果服务可用性、稳定性、可扩展性等都可能不尽人意。

我们还认为,你应该把更多的精力聚焦在业务和App开发上面,这样不仅节约了时间和金钱,更重要的是你能把所有精力都放在业务本身上面,让你的应用在竞争中脱颖而出,领先竞争对手。

那么,说这么多,CloudData到底是什么鬼?

简单来讲就是针对App开发者提供的统一的后端对象数据存储服务,让用户不再关心Server端数据存储的问题,CloudData具有关系型、半结构化、json格式及单个对象具有事务性等特点,采用MongoDB 3作为存储系统,至于CloudData更多细节请参考MaxLeap CloudData相关文档。

构建BAAS云服务—CLOUDDATA架构设计

CloudData架构非常的简单,从上往下看,包括API Server、数据访问逻辑处理层和底层数据存储层,下面,我们分别介绍三个模块的具体实现原理。

Api Server

在Api Server层面,我们的语言采用的是java,针对java,当前比较流行的方式可能是:选择一个web 框架,可能是spring mvc,使用java servlet,然后选择一款 java web 容器,可能是tomcat或者jetty等,但我们并没有采用这样方式,因为,在我们看来,这种方式有几个缺点:架构显得有些笨拙,每次启动都需要部署到java web 容器中,不够轻量、同步通信,并发上不去,性能较低。因此,通过综合考量,我们选择了vert.x web框架,vert.x 基于事件驱动、非阻塞通信机制,这意味着,只需要很少的线程就能应付大量的并发,同时它也更加的轻量,拥有更高的性能,启动vert.x web服务只需要几十毫秒到几百毫秒之间,使用也是异常的简单,只要你稍稍懂一点http协议及网络编程几乎可以说是零学习成本:

HttpServer server = vertx.createHttpServer();
Router router = Router.router(vertx);

router.route("/index").handler(routingContext -> {
  HttpServerResponse response = routingContext.response();
  response.putHeader("content-type", "text/plain");
  // Write to the response and end it
  response.end("Hello World Maxleap!");
});

server.requestHandler(router::accept).listen(10086);

需要注意的是,vert.x web基于EventLoop单线程模式,所以,在写web服务的时候,不能写同步处理逻辑,否则会阻塞主线程,导致其他请求不能得到响应;另外,有异步编程经验的同学一定感受到这样的痛苦:callback方法泛滥成灾,大量的flatMap方法调用导致也导致代码的可读性差,除此之外,我们实在是找不到vert.x web框架的任何缺点。当然,这些缺点也并不是不可以避免,基于此,我们对vert.x web,稍稍加了一点东西,改变了一些实现策略:我们在逻辑处理阶段将异步变成同步,在框架后面,我们在变成异步,并且增加了对JAX-RS 部分规范支持,如果之前你觉得,基于vert.x web写rest接口还有一点点的学习成本,但现在基于我们改变之后的vert.x web写rest接口,你会发现跟以前并没有两样:

@GET
@Path(":className/:objectId")
public void get(RoutingContext context) {
  func(context, (ctx, cloudCode, appId, className, principal) -> {
    LASObject doc = lasDataEntityManager.get(appId, className, principal, new ObjectId(context.request().getParam("objectId")));
    if (doc == null) {
      ctx.response().end("{}");
    } else {
      ctx.response().end(MongoJsons.serialize(doc));
    }
  });
}

更多的Api Server相关细节请参考Maxleap的开源BaaS系统实现 https://github.com/MaxLeap/MyBaaS

数据访问层

这一层包含了CloudData的核心逻辑实现,相对来讲,是CloudData设计中最复杂的一层,当然,仅仅只是相对,所有的逻辑处理都在这一层,而Pandora是其具体实现,所以,我们重点会讲一讲Pandora的设计思路

Pandora是古希腊神话中最美的女人,她充满诱惑,携带危险来到人间,当然,在我们看来,危险与诱惑跟美丽的女人无关,如果说美丽的人会让你身处危险之中,那就请让我万劫不复吧。

构建BAAS云服务—CLOUDDATA架构设计

扯远了,Pandora是Maxleap统一数据访问层的实现,主要是针对mongo数据的操作,所以,不仅仅是用在CloudData中,在Maxleap中所有应用中,需要访问mongo数据库的都是通过Pandora来进行的,并且提供了异步和同步两种接口

构建BAAS云服务—CLOUDDATA架构设计

Pandora主要实现了两种模式的数据访问,CloudData和NativeData。

Clouddata是Maxleap App数据的访问,凡是访问应用的数据,都是通过这个模块,CloudData数据的访问稍显复杂一些,包含Pointer、Relation等自定义数据结构的实现;数据ACL的实现;自定义操作指令如$relationTo等

NativeData是对mongo指令友好的封装,与mongo原生指令并没有两样。

Pandora最为核心的功能是实现了资源限制和数据库访问的路由策略,这对数据库进行平滑的动态扩展及迁移提供了可能性,当然,目前Maxleap也实现了这一点,当用户的应用因为数据量、访问量上升需要升级后台mongo数据库的时候,这一切都是无感知的,不会影响你的线上服务照常运行,当然,某些特殊情况下,可能需要短暂的停顿服务几分钟,并且仅仅是写停顿,读不受影响。

在Maxleap项目中,使用Pandora最大的好处是,开发人员无需关心后台mongo的架构,数据一致性、mongo服务升级降级等诸多繁琐问题,当然,你甚至连数据存储在那个mongo集群都不要知道,因为,使用Pandora我实在想不到你还需要了解数据存储在哪里的理由。

构建BAAS云服务—CLOUDDATA架构设计

Clouddata数据存储架构设计

如果你对Baas有些许,如果你去Maxleap提供的服务有所了解,我想,你一定会关心Cloudapp后端数据存储架构是怎么设计的,成千过万的应用数据我们是怎么存储的、你的应用可用性问题、服务是否有足够的保障,接下来,我们正要介绍这一点。

先看一下业界某BaaS服务的Clouddata的设计:

构建BAAS云服务—CLOUDDATA架构设计

(图片来自网络)

在我们看来,这是一种糟糕的设计,如果我们没有理解错的话,所有的用户数据都在一个大集群里面,后端是一个大的sharded集群,所有的用户数据的访问都会经由一组mongos路由服务(毫无疑问,这绝对是一个风险,试想,如果mongos出了问题,整改后端数据存储就会down掉),当然,这并不是主要问题,主要问题在于:

1、数据在一个庞大的集群中,出现问题的概率也会大很多,比如有10000个应用,那么出现问题的概率也会大10000倍,1个应用出现一次问题,那么整个服务都会因为某一个应用出现问题而受到影响。

2、数据规模大了,运维困难,又如,如果mongo异常down掉,恢复时间绝对的是一个煎熬的过程,而这个时候,整个服务都不可用。

3、稳定性较差,每一个应用都有可能发送令人恶心的指令,可能会导致资源抢占(比如最常见的情况是没有索引的查询,会引起热数据污染,结果集返回大量数据,抢占了网络带宽等),这个时候,整个集群都会受到影响,可能导致整个集群的吞吐量下降,反应到应用层面,可能就是大量应用请求短时间超时,读写失败

构建BAAS云服务—CLOUDDATA架构设计

这是我们Maxleap其中的一台产品Mongo Server所表现的性能,当然,你可能会惊讶于数据在磁盘也能够拥有4000/s的吞吐量,当然,这一切得益于我们使用SSD硬盘所带来的效果。这张图也告诉我们,尽可能的小心你的查询,当服务器趋于吞吐平稳的时候,不要造成内存数据污染,但如果是所有的应用在一个大集群,这种情况将不可避免,因为,你永远都不会知道用户会写怎样的查询指令,而一旦有这样的指令,影响将会扩散到整个大集群其他的应用。

4、效率低下,需要更多的硬件成本,比如,当你的读写压力变大的时候,你不得不加新的机器去copy集群的全量数据,造成磁盘和内存的浪费,因为你应用的热数据在每一个mongo节点都有(当然,为什么我列在第四点,可能你觉得这并不重要,硬件从来都不是问题,更重要的是,你觉得,公司从来都不缺这点钱)

好了,是时候看看我们的CloudData的设计了,当然,也是极其的简单,遵循3个原则:

1、用户资源隔离(毫无疑问,这点是最重要的)

2、动态可无限扩展(不管你有多少个库,1万还是100万)

3、分而治之

基于以上3点,我们的设计是为每一个应用创建一个独立mongo集群,是不是太简单了,有木有,但是,我们认为这是最科学的,将大集群按照应用分解成小集群,大问题分解成小问题,以大化小,分而治之,不管在哪里,都是靠谱的,并且还特别有效,最近几年,火的不要不要的被广泛传播的软件架构方式‘微服务’不也是同样的道理么?当然,你可以说,微服务更倾向于‘单一职责’原理,当然,只要你高兴,随你怎么想。

也许,你可能会质疑这是否会浪费资源,因为,每个人都会有这样直观上的错觉,但是,事实上,不仅没有浪费资源,还会大大的节约资源,这一点,在上面已经讨论到,数据在一个大集群中,副本集越多,浪费的内存越多,磁盘越多。第二点,起一个mongo集群,并不会浪费多少内存。

当然更重要的是,我们并不是真正为每一个应用创建一个mongo集群,毕竟,这样做,很多时候并没有意义,因为,90%的应用可能并没有较高的访问量,50%的以上的应用,可能从来都不会被频繁访问。

所以,我们的策略是,当用户第一次注册Maxleap创建一个应用的时候,用户的数据放在一个默认的集群中,在Maxleap中,这个集群叫User Default Cluster,当你的用户有一定的访问量的时候(没有访问量也没有关系,只要你是我们的付费用户)我们会把你公司所有的应用部署在一个单独的mongo集群中,再往后,你应用访问量继续上升,我们就把你的应用在单独升级到一个mongo集群,当然,这一切的升级过程,都是平滑无缝的进行的,没有人能够感知的到,包括我们自己。

构建BAAS云服务—CLOUDDATA架构设计

So,我们Maxleap CloudData是这样子的:

构建BAAS云服务—CLOUDDATA架构设计

好了,关于Pandora及CloudData我们先简单介绍到这里,更多的细节我们到时候会专门拧一个细节出来讨论。

数据存储层

上面我们早已经知道,我们使用的Mongo作为Clouddata作为主要存储系统,更早之前,在我们mongo还在2.6之前,我们还使用了redis作为热数据缓存,但后来我mongo升级到3.0之后,变去掉了redis,所以,整个底层数据存储,就只剩下mongo,整个系统设计又进一步变得更加简单,之所以去掉redis,这不是我们所要讨论的重点,我们会在相关的文章中进行详细说明。

这里,我们重点介绍mongo的集群架构设计方案

构建BAAS云服务—CLOUDDATA架构设计

集群特点:

每一个mongo集群都是一个复制集:1主1从1投票节点,还有一个备份节点(Norns-backup是我们自主实现的一个增量实时备份系统)
每一个mongo集群都在一个VPC网络里面
每一个mongo集群3个节点都在不同的可用区里面(不同的机房,机房是光纤直连,网络延迟大概咋500us)
部分mongo集群的数据存储在LVM上面
每一个mongo集群都使用SSD硬盘
每一个mongo集群我们都提供瞬时10倍以上的请求峰值


原文作者来自 MaxLeap 团队 基础服务及架构组成员:赵静

关于MaxLeap
MaxLeap移动云服务平台为企业提供一站式的移动研发和运营云服务,帮助企业快速研发和上线移动应用,平台提供数据云存储,云引擎,支付管理,IM,数据分析和营销自动化等服务。
MaxLeap官网链接: https://maxleap.cn

如果您正在学习移动研发和云服务等方面的讯息,不妨关注我们的微信服务号MaxLeapSvc,我们将不定期推送相关干货。敬请期待!

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Peter20 Peter20
4年前
mysql中like用法
like的通配符有两种%(百分号):代表零个、一个或者多个字符。\(下划线):代表一个数字或者字符。1\.name以"李"开头wherenamelike'李%'2\.name中包含"云",“云”可以在任何位置wherenamelike'%云%'3\.第二个和第三个字符是0的值wheresalarylike'\00%'4\
Stella981 Stella981
4年前
Kerberos无约束委派的攻击和防御
 0x00前言简介当ActiveDirectory首次与Windows2000Server一起发布时,Microsoft就提供了一种简单的机制来支持用户通过Kerberos对Web服务器进行身份验证并需要授权用户更新后端数据库服务器上的记录的方案。这通常被称为Kerberosdoublehopissue(双跃点问题),
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
4年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Stella981 Stella981
4年前
Nginx的几个常用配置和技巧
一个站点配置多个域名server {    listen       80;    server_name  opscoffee.cn b.opscoffee.cn;}server\_name后跟多个域名即可,多个域名之间用空格分隔一个服务配置多个站点server {