社区/文章分享/浅析小程序云原生数据库设计与应用

浅析小程序云原生数据库设计与应用

背景

从软件工程的角度来看,软件开发经历了如下三个阶段:传统开发->敏捷迭代->Serverless。传统的开发模式和敏捷迭代开发模式除了需要开发者编写核心的业务逻辑外,都不可避免地需要对后端的基础设施进行管控和优化。比如,一个应用的逻辑可以很简单,可一旦涉及到应用的发布部署,就需要开发者花费大量精力进行服务器、数据库、网络等基础设施的申请和搭建,还要考虑这些后端基础设施的稳定性、可用性和监控指标。这一切耗时耗力又与产品的核心功能无关,对于需要快速开发和试错的产品,传统的模式开发速度慢、部署和运维成本较高。

1.png

随着 Serverless 概念的火热,越来越多的开发开始转向 Serverless 发展。“Serverless”并不是指后端没有服务器,而是将后端服务器及相关运维操作变得对上层应用开发者不可见和透明。用户无需关心后端的基础设施,直接通过云 API 一键接入云函数、云数据库和云存储来获取算力、数据库、存储等基础的后端能力。这种随用随取的开发模式,不但可以让开发者能更专注于自身的业务逻辑,还具有低成本、开发速度快以及免运维等诸多优势。

小程序需求 + Serverless 理念 = 小程序云开发

小程序云开发以微信作为小程序前端运行的依托,同时又通过接入云函数、云数据库和云存储,来达到对后端基础设施的开箱即用。这些特性可以在很大程度上解放小程序开发者的生产力,降低开发的成本和难度。

数据库设计

云数据库作为小程序云开发的重要组件之一,应具备以下特性/能力:
安全性:对于数据库而言,数据安全是第一位的;
易用性:开箱即用,用完即走,简单上手,免运维;
低成本:按量收费,精细化成本控制;
高性能:Nosql,支持高并发读写;
灵活性:(no-schema) 无固定的数据库表模式,当业务量扩展时可以非常方便的弹性伸缩。

围绕这 5 个主要需求,云开发数据库从架构设计等方面进行了相应的改造及优化,下面简单介绍一下:

2.png

最上面是小程序的接入客户端,中间部分是接入层,底层是数据库的存储层,当然,还有周边的管控、告警、备份、元数据管理等模块。

开发者通过云开发提供的 SDK,可以在小程序中一键获取云数据库的登录态,然后将数据读写请求发送给接入层。接入层收到用户的读写请求后,由 keeper 和 agent 这两个无状态的模块对接入的读写请求进行相关处理。

其中 keeper 主要负载请求的鉴权、认证缓存,以及读写请求数的统计,是云数据库权限校验,负载均衡和计费功能实现的核心模块。

agent 模块主要有以下几个功能:1)维护接入层到底层数据库实例的连接池,通过复用已建立的连接来减少请求鉴权和连接创建的耗时;2)统计请求的并发数,对读写请求的 QPS 进行平滑处理,避免短时间的毛刺影响数据库性能和可用性;3)在热迁移切换数据库实例时,将请求挂起,切换后再将请求恢复,来实现热迁移过程对用户的全程无感知。

最后,读写请求通过了接入层,再接下来会到达存储层进行数据库实例的读写。首先,为了保证数据库的高可用,每个数据库实例都是跨机房的三副本存储,节点(机房)故障都可以自动恢复;其次,数据安全性方面,数据库副本之间通过 raft-like 的副本集协议来保证它们数据的最终一致性,数据库备份也支持用户将数据回档到任意时刻;最后,我们也充分利用云数据库的日志、监控、拨测等模块收集的信息,设计了自动化运维平台,对常见数据库异常(比如磁盘只读等)进行秒级探测和自动处理,尽可能保证用户业务平稳运行。

当然,围绕小程序云开发数据库,我们做了一系列优化,包括且不限于:全链路免鉴权、连接数控制、按量付费、跨机房容灾、自动 FailOver、弹性扩缩容、数据库热迁移、智能 DBA、查询优化等等。鉴于篇幅原因,就不在这里展开。

应用

鉴于我们对于数据库 API 提供了非常简单易用的封装,云开发数据库的使用方式非常简单。可以从小程序端或者云函数端访问。

// 应用 - 小程序端
const db = wx.cloud.database()

db.collection('goods')
  .where({
    category: 'food'
  }}
  .limit(100)
  .get()
// 应用 - 云函数端
const cloud = require('wx-server-sdk')
cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
})
const db = cloud.database()

db.collection('goods')
  .where({
    category: 'food'
  }}
  .limit(100)
  .get()

下面通过一些实际例子介绍不同场景下云开发数据库的具体应用

电商类型小程序
直接使用基础的数据库读写能力来查询商品列表:

3.png

const db = wx.cloud.database()

db.collection('goods')
  .where({
    category: 'food'
  }}
  .limit(100)
  .get()

使用云数据库的聚合搜索能力对商品列表进行排序,模拟用户按"价格"、"评价"等不同纬度进行排序筛选:

4.png

const db = wx.cloud.database()

// 先根据评分降序排名,评分相同的,再根据价格升序排名
db.collection('goods')
  .aggregation()
  .sort({
    rank: -1,
    price: 1
  })
  .end()

对于一个订单而言,多个用户同时购买时,可以使用到云数据库的事务能力。将查询商品数量和下单放在一个事务里,保证操作的原子性。

const cloud = require('wx-server-sdk')
cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
})
const db = cloud.database()

db.runTransaction(async t => {
  const doc = t.collection('goods').doc('apple')

  const { data: price } = await doc.get()

  if (apple.amount === 0) {
    throw new Error('没有足够的苹果!')
  }

  await doc.update({
    amount: apple.amount - 1
  })
})

O2O 类型小程序
利用云数据库提供的地理位置搜索能力来满足 O2O 类应用的"寻找附近的商家"之类的场景:
5.png

const _ = db.command

// 找出距离给定位置 1 公里到 5 公里范围内的记录
db.collection('restaurants')
  .where({
    location: _.geoNear({
      geometry: db.Geo.Point(113.323809, 23.097732),
      minDistance: 1000,
      maxDistance: 5000
    })
  })
  .get()

社交类型小程序
使用云数据提供的实时推送功能来轻而易举地实现一个多人实时聊天室。

6.png

const db = wx.cloud.database()

db.collection('messages')
  .where({
    roomId: 1234
  })
  .watch({
    onSnapShot(docs) {
      console.log(docs)
    }
  })

总结

小程序云开发可以大大解放小程序开发者的生产力,降低开发的成本和难度。其中云数据库扮演了举足轻重的角色。云数据库针对小程序云开发的 5 大需求:安全性、易用性、低成本、高性能、灵活性,从数据库架构设计等方面做了诸多改造和优化,使得运输局可以更加贴合小程序的使用场景。

面向未来,在管控层我们也将提供更加细粒度的监控,更智能的 DBA 和更高效的弹性伸缩能力;在数据库内核层,我们将封装更多的底层存储引擎能力暴露给 API 层,并深度结合小程序的使用场景来进行定制化开发,进一步提升事务的性能等等。

我们相信,云开发数据库将在 Serverless 理念的指导下不断完善自己,并帮助广大开发者共同发展。