基于空间数据库MongoDB实现全国电影票预定系统

本文涉及的产品
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
简介: 前言 受到中文社区《电商参考架构第二部分:库存优化方法》启发,想到了去年做过类似的电影票预定系统,如果用MongoDB去做存储支撑,那应该是怎样架构的呢?本文的目的是为了更好的学习掌握MongoDB,所以某些设计上更偏向于功能的展示,在实际使用上要因地制宜的改变,合适才是最好的。 需求 电影票

前言

受到中文社区《电商参考架构第二部分:库存优化方法》启发,想到了去年做过类似的电影票预定系统,如果用MongoDB去做存储支撑,那应该是怎样架构的呢?本文的目的是为了更好的学习掌握MongoDB,所以某些设计上更偏向于功能的展示,在实际使用上要因地制宜的改变,合适才是最好的。

需求

电影票预定系统与电商系统非常类似,都可以抽象理解为商品的售卖。进一步的讲电影票系统是电商系统的一个库存特例场景:

  • 每个场次,每个座位,都只有一个库存
  • 每个订单所预定的座位有锁定状态,在支付前对应的作为不能被再次购买
  • 订单涉及到的座位要不全成功,要不全失败
  • “全国”级的,数据容量不是太大问题,但性能上要支持水平扩展

PS:实际上的理论TPS并不高,目前全国5000家影院,假设平均8个影厅,每个厅200个位置,每个影厅6个场次,早中晚各3个高峰,每个高峰1个小时。计算得出TPS大概是:5000 8 6 * 200/ 3 / 3600 = 4400 TPS;但是设计上我们还是要保证性能的可水平扩展,否则怎么体现MongoDB的特色呢?^-^

描述信息文档结构

影院描述信息

保存最基本的影院信息,包括地理信息,名称,_id为MongoDB由MongoDB自动分配

CinemaManager.cinema_detail

{
  _id: <ObjectID>,
  name: "<cinema name>",
  city: "<city name>"
  location: [<longitude>, <latitude>],
  comments: "<detail message>"
}
AI 代码解读

例如:

rs0:PRIMARY> db.cinema_detail.insert({ 
    "name" : "大时代电影院", 
    "city" : "杭州",
    "location" : [ 120.13, 30.16 ],  
    "comments" : "IMAX 4K,有停车位"  
});
AI 代码解读

因为影院信息的查询一般都是按照城市和名称,或者地理坐标检索,所以这里建立两个索引

Index1:城市+名称的复合索引,因为查询电影院时一般都会指定城市名

rs0:PRIMARY> db.cinema_detail.ensureIndex({city:1, name:1})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 2,
    "numIndexesAfter" : 2,
    "ok" : 1
}
AI 代码解读

注意,这里使用的是复合索引,所以针对 city + name的查询,或者city的查询是有效的,只查找name字段是无法通过索引优化的。

Index2:地理坐标索引,用来应付"最近的电影院"类查询

rs0:PRIMARY> db.cinema_detail.ensureIndex({location: "2d"})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 3,
    "numIndexesAfter" : 4,
    "ok" : 1
}
AI 代码解读

例如,查询在杭州最近的某个电影院

rs0:PRIMARY> db.cinema_detail.find({city:"杭州", location: { $near: [1.0, 2.0] }}).pretty()
{
    "_id" : ObjectId("559a3ef8c6058dae1ac49ce8"),
    "name" : "大时代电影院",
    "city" : "杭州",
    "location" : [
        120.13,
        30.16
    ],
    "comments" : "IMAX 4K,有停车位"
}
AI 代码解读

影厅描述信息

theater_detail.cinema_id与cinema_detail._id集合形成references关系,通过cinema_detail._id可以快速找到所属影厅的信息。另一个关键字段theater_detail.seat用来描述座位信息,每排所有的座位是一个数组,不同排可以有不同数量的座位。

CinemaManager.theater_detail

{
  _id: <ObjectID>,
  cinema_id: <ObjectID(cinema_detail._id)>, 
  name: <theater name>,
  seat: 
  {
      row1: [<seat valid>],
      row2: [<seat valid>],
      row3: [<seat valid>],
      <seat row>: [<seat valid>]
  }
  comments: "<detail message>"
}
AI 代码解读
rs0:PRIMARY> db.theater_detail.insert({ 
  cinema_id:ObjectId("559a3ef8c6058dae1ac49ce8"), 
  name:"IMAX厅", 
  seat:
  {
    row1: [1, 1, 1, 1], 
    row2: [1, 1, 1], 
    row3: [1, 1, 1, 1], 
    row4: [1, 1, 1, 1, 1], 
  },
  comments: "可容纳哦xxx人,弧形荧幕"
})

rs0:PRIMARY> db.theater_detail.insert({ 
  cinema_id:ObjectId("559a3ef8c6058dae1ac49ce8"), 
  name:"中国巨幕厅", 
  seat:
  {
    row1: [1, 1, 1, 1], 
    row2: [1, 1, 1], 
    row3: [1, 1, 1, 1]
  },
  comments: "可容纳哦xxx人,弧形荧幕"
})
AI 代码解读

建立索引

rs0:PRIMARY> db.theater_detail.ensureIndex({cinema_id:1})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}
AI 代码解读

影片描述信息

影片说明

{
  _id: <ObjectID>,
  name: "<movie name>", 
  director: "director name"
  actor: [<actor name>]
  comments: "<detail message>"
}
AI 代码解读
rs0:PRIMARY> db.movie_detail.insert({
  name: "一路向西",
  director: "胡耀辉",
  actor:["张建声", "王宗尧", "胡耀辉", "何佩瑜", "张暖雅", "郭颖儿"],
  comments: "该影片描写的是当代香港社会中普通年轻人对“爱”与“性”的追求而逐渐改变的心路历程的故事"
})
AI 代码解读

索引

rs0:PRIMARY> db.movie_detail.ensureIndex({name:1})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}
AI 代码解读

影片放映文档结构

放映信息包含放映时间段,放映影厅,票价。虽然Document结构可以做复杂的嵌套,但原则上期望Document尽量小,利用数据Shard,性能优化。所以在movie_schedule的设计上每个影片的每场放映独立一个Document表达。

{
  _id: <ObjectID>,
  cinema_id: <ObjectID(cinema_detail._id)>
  movie_id: <ObjectID(movie_detail._id)>, 
  theater_id: <ObjectID(theater_detail._id)>,
  start_time: <ISODate>,
  end_time: <ISODate>,
  comments: "<detail message>"
}
AI 代码解读

movie_schedule的References关系较多,需要与电影院,影厅,电影三者分别建立关系。

db.movie_schedule.insert({
  cinema_id:ObjectId("559a3ef8c6058dae1ac49ce8"),
  movie_id:ObjectId("559b68f372b34f216246cb1d"),
  theater_id:ObjectId("559b625072b34f216246cb1b"),
  start_time: ISODate("2015-07-07T10:00:00.00Z"),
  end_time: ISODate("2015-07-07T12:00:00.000Z"),
  comments: "首映"
)}

db.movie_schedule.insert({
  cinema_id:ObjectId("559a3ef8c6058dae1ac49ce8"),
  movie_id:ObjectId("559b68f372b34f216246cb1d"),
  theater_id:ObjectId("559b625072b34f216246cb1b"),
  start_time: ISODate("2015-07-07T12:30:00.00Z"),
  end_time: ISODate("2015-07-07T14:30:00.000Z"),
  comments: ""
)}
AI 代码解读

还是建立一个复合索引,优化查询某一电影院的某部影片(的某一影厅)上映信息

rs0:PRIMARY> db.movie_schedule.ensureIndex({cinema_id:1, movie_id:1, theater_id:1})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}
AI 代码解读

PS:也可以建立相应的索引,用来优化某一时间段内的影片信息查询,读者自行思考

交易系统

至此,基本的信息文档集合均已建立完成,一般的查询需求都可以满足了。接下来是重点:库存售卖系统。抽象的来看,售卖系统就是对上诉所有集合的一个整合,外加一套库存字段。我们认为一场放映就是一个主商品,每个座位可以认为是这个商品的SKU,每个SKU都是1份。

通过Reference关系结合movie_schedule与theater_detail,注意这里引用了

{
  _id: <ObjectID>,
  movie_schedule_id: <ObjectID(movie_schedule._id)>
  theater_id: <ObjectID(theater_detail._id)>,
  seat:
  {
    row1: [2, 2, 2, 2], 
    row2: [2, 2, 2], 
    row3: [2, 2, 2, 2], 
    row4: [2, 2, 2, 2, 2], 
  }
}
AI 代码解读

注意,这里不仅仅是Reference的引用关系,还复制了theater_detail.seat字段,每个seat都有一个库存数字,因为在MongoDB中一个Document的操作是可以保证原子的,不需要对Collection加任何锁。数字2并不是表示可以卖2次:

  • 数字2表示,可销售
  • 数字1表示,已锁定
  • 数字0表示,已售完

交易逻辑上可通过FindAndModify + $inc,原子性的修改库存信息。其他的描述信息是否需要再次冗余取决于具体的业务状况了,具体问题具体分析。我本人更倾向于目前的数据结构方案,不做过多的冗余,原因:

  1. 数据订正复杂,多一个冗余,多一份复杂
  2. 其他信息基本都是静态数据,数据量又小,完全可以通过Cache技术解决读取问题

先插入一个我们的商品

db.movie_item.insert({
  movie_schedule_id : ObjectId("559b6ee472b34f216246cb1e"),
  theater_id : ObjectId("559b625072b34f216246cb1b"),
  seat : 
  {
    row1: [2, 2, 2, 2], 
    row2: [2, 2, 2], 
    row3: [2, 2, 2, 2], 
    row4: [2, 2, 2, 2, 2], 
  }
})
AI 代码解读

索引

rs0:PRIMARY> db.movie_item.ensureIndex({movie_schedule_id:1})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}
AI 代码解读

锁定座位的动作,锁定第4排的3号位置(从1开始计数)和锁定第4排的2号位置:

db.movie_item.findAndModify({
  query: { "_id":ObjectId("559b790f72b34f216246cb22"), "seat.row4.2":2 },  
  update: { $inc: {"seat.row4.2":-1}},
  upsert: false
})

db.movie_item.findAndModify({
  query: { "_id":ObjectId("559b790f72b34f216246cb22"), "seat.row4.1":2 },  
  update: { $inc: {"seat.row4.1":-1}},
  upsert: false
})
AI 代码解读

分别锁定了第4排3号(row4[2]),第4排2号(row4[1]),
注意,这里是分两次锁定的,锁定操作并不需要原子完成,否则会造成用户锁定失败概率的上升。

rs0:PRIMARY> db.movie_item.find({_id:ObjectId("559b790f72b34f216246cb22")}).pretty()
{
    "_id" : ObjectId("559b790f72b34f216246cb22"),
    "movie_schedule_id" : ObjectId("559b6ee472b34f216246cb1e"),
    "theater_id" : ObjectId("559b625072b34f216246cb1b"),
    "seat" : {
        "row1" : [
            2,
            2,
            2,
            2
        ],
        "row2" : [
            2,
            2,
            2
        ],
        "row3" : [
            2,
            2,
            2,
            2
        ],
        "row4" : [
            2,
            1,
            1,
            2,
            2
        ]
    }
}
AI 代码解读

OK,交易成功以此类推,同时修改两个库存到0,这里利用了findAndModify的原子特性

db.movie_item.findAndModify({
  query: { 
    _id:ObjectId("559b790f72b34f216246cb22"), 
    $and:[ {"seat.row4.2":1}, {"seat.row4.1":1}] 
  },  
  update: { 
    $inc: {"seat.row4.2":-1, "seat.row4.1":-1} 
  },  
  upsert: false
})
AI 代码解读

再查下集合看看:

rs0:PRIMARY> db.movie_item.find({_id:ObjectId("559b790f72b34f216246cb22")}).pretty()
{
    "_id" : ObjectId("559b790f72b34f216246cb22"),
    "movie_schedule_id" : ObjectId("559b6ee472b34f216246cb1e"),
    "theater_id" : ObjectId("559b625072b34f216246cb1b"),
    "seat" : {
        "row1" : [
            2,
            2,
            2,
            2
        ],
        "row2" : [
            2,
            2,
            2
        ],
        "row3" : [
            2,
            2,
            2,
            2
        ],
        "row4" : [
            2,
            0,
            0,
            2,
            2
        ]
    }
}
AI 代码解读

总结

一套全国级的电影票系统会比这复杂的多,本文的目的还是以教程为主,主要是说明MongoDB如何构建一个电影票系统,但距离生长系统还是有一定的距离,仍有很多其他的技术点需要讨论,可以延伸开的还有,下单失败,过期未付款,数据唯一性等问题。

相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
目录
打赏
0
2
2
1
9250
分享
相关文章
|
17天前
|
微服务——MongoDB常用命令1——数据库操作
本节介绍了 MongoDB 中数据库的选择、创建与删除操作。使用 `use 数据库名称` 可选择或创建数据库,若数据库不存在则自动创建。通过 `show dbs` 或 `show databases` 查看所有可访问的数据库,用 `db` 命令查看当前数据库。注意,集合仅在插入数据后才会真正创建。数据库命名需遵循 UTF-8 格式,避免特殊字符,长度不超过 64 字节,且部分名称如 `admin`、`local` 和 `config` 为系统保留。删除数据库可通过 `db.dropDatabase()` 实现,主要用于移除已持久化的数据库。
53 0
|
17天前
|
微服务2——MongoDB单机部署4——Linux系统中的安装启动和连接
本节主要介绍了在Linux系统中安装、启动和连接MongoDB的详细步骤。首先从官网下载MongoDB压缩包并解压至指定目录,接着创建数据和日志存储目录,并配置`mongod.conf`文件以设定日志路径、数据存储路径及绑定IP等参数。之后通过配置文件启动MongoDB服务,并使用`mongo`命令或Compass工具进行连接测试。此外,还提供了防火墙配置建议以及服务停止的两种方法:快速关闭(直接杀死进程)和标准关闭(通过客户端命令安全关闭)。最后补充了数据损坏时的修复操作,确保数据库的稳定运行。
50 0
|
17天前
|
从 MongoDB 到 时序数据库 TDengine,沃太能源实现 18 倍写入性能提升
沃太能源是国内领先储能设备生产厂商,数十万储能终端遍布世界各地。此前使用 MongoDB 存储时序数据,但随着设备测点增加,MongoDB 在存储效率、写入性能、查询性能等方面暴露出短板。经过对比,沃太能源选择了专业时序数据库 TDengine,生产效能显著提升:整体上,数据压缩率超 10 倍、写入性能提升 18 倍,查询在特定场景上也实现了数倍的提升。同时减少了技术架构复杂度,实现了零代码数据接入。本文将对 TDengine 在沃太能源的应用情况进行详解。
34 0
消防行业如何借助时序数据库 TDengine 打造高效的数据监控与分析系统
本篇文章来自“2024,我想和 TDengine 谈谈”征文活动的优秀投稿,深入探讨了如何在消防行业中运用 TDengine 进行业务建模。文章重点介绍了如何通过 TDengine 的超级表、标签设计和高效查询功能,有效管理消防监控系统中的时序数据。作者详细阐述了实时监控、报警系统以及历史数据分析在消防行业中的应用,展示了 TDengine 在数据压缩、保留策略和分布式架构下的强大优势。
30 0
基于ssm的社区物业管理系统,附源码+数据库+论文+任务书
社区物业管理系统采用B/S架构,基于Java语言开发,使用MySQL数据库。系统涵盖个人中心、用户管理、楼盘管理、收费管理、停车登记、报修与投诉管理等功能模块,方便管理员及用户操作。前端采用Vue、HTML、JavaScript等技术,后端使用SSM框架。系统支持远程安装调试,确保顺利运行。提供演示视频和详细文档截图,帮助用户快速上手。
56 17
基于ssm的台球厅管理系统,附源码+数据库+论文
本项目为新锐台球厅管理系统,支持管理员和会员两种角色。管理员可进行会员管理、台球桌管理、订单管理等;会员可查看台球桌、预约、购买商品等。技术框架基于Java,采用B/S架构,前端使用Vue+HTML+JavaScript+CSS+LayUI,后端使用SSM框架,数据库为MySQL。运行环境为Windows,JDK8+MySQL5.7+Tomcat8.5。提供演示视频及详细文档截图。
基于ssm的网络直播带货管理系统,附源码+数据库+论文
该项目为网络直播带货网站,包含管理员和用户两个角色。管理员可进行主页、个人中心、用户管理、商品分类与信息管理、系统及订单管理;用户可浏览主页、管理个人中心、收藏和订单。系统基于Java开发,采用B/S架构,前端使用Vue、JSP等技术,后端为SSM框架,数据库为MySQL。项目运行环境为Windows,支持JDK8、Tomcat8.5。提供演示视频和详细文档截图。
57 10
基于ssm的超市会员(积分)管理系统,附源码+数据库+论文,包安装调试
本项目为简单内容浏览和信息处理系统,具备管理员和员工权限。管理员可管理会员、员工、商品及积分记录,员工则负责积分、商品信息和兑换管理。技术框架采用Java编程语言,B/S架构,前端使用Vue+JSP+JavaScript+Css+LayUI,后端为SSM框架,数据库为MySQL。运行环境为Windows,JDK8+Tomcat8.5,非前后端分离的Maven项目。提供演示视频和详细文档,购买后支持免费远程安装调试。
79 19
[Java计算机毕设]基于ssm的OA办公管理系统的设计与实现,附源码+数据库+论文+开题,包安装调试
OA办公管理系统是一款基于Java和SSM框架开发的B/S架构应用,适用于Windows系统。项目包含管理员、项目管理人员和普通用户三种角色,分别负责系统管理、请假审批、图书借阅等日常办公事务。系统使用Vue、HTML、JavaScript、CSS和LayUI构建前端,后端采用SSM框架,数据库为MySQL,共24张表。提供完整演示视频和详细文档截图,支持远程安装调试,确保顺利运行。
76 17
阿里云连续五年获评为Gartner®云数据库管理系统魔力象限领导者
阿里云连续五年获评为Gartner®云数据库管理系统魔力象限领导者

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等