[电商架构-01]-购物车系统解析

254 阅读15分钟

首先谈谈购物车,于日常生活中,我们前往一些规模较大的商超进行购物时,通常会为顾客配备一个购物车。我们在商品专区对商品进行挑选,接着将自己意欲购买的东西置入其中。待完成全部商品的选购后,便满心欢喜地推着小推车前往收银台展开结算。

上文所述乃现实生活中的购物车,以最为经典的购物 App 淘宝为例,如下是两张淘宝 App 购物车的截图,可算是对现实世界里的购物车进行了一定程度的虚拟化呈现。

接下来我们讲一讲购物车系统的主要功能有哪些?

购物车在实际使用的时候,从用户的实际使用体验产品功能来讲,同时具有凑单、促销和收藏的作用。

  1. 凑单的时候,用户看商品详情页,有两个选择:一个是“马上购买”,另一个是“加入购物车”。要是用户自己需要的东西多,想一次买好多样商品,或者参加优惠活动(像满减、满赠这些),这时候就会把商品放进购物车来凑单。

  2. 促销这一块,购物车也有作用,可以用来提高客单价。有促销活动(满减、满赠等等)的时候,用户把商品加入购物车后,就能看看是不是满足优惠条件,还有优惠之后的价钱(不包括优惠券)。

  3. 收藏这方面,对大部分用户来说,购物车起更多的作用是收藏:“这个东西看着挺好,等以后再下单。”另外还有个筛选的用处。比如我网购的时候,会先加入购物车收藏,后面有时间再在购物车里筛选完了再。

  4. 发起结算,当我们选好了要要结算的商品之后,就可以调用结算系统,进行商品的结算,这个动作就相当于你在超市,选了一堆商品,在收银台经过服务员的扫码枪扫描商品的价格,计算出来一堆商品的最终结算价格,大概就是这样一个动作。

总结下来,购物车的主要功能就是把商品加入购物车、购物车列表商品展示、促销系统优惠券选择、发起结算下单这几个主要功能。

那接下来想一下,围绕上边的几个功能,我们需要做哪些设计呢?这个我们可以从几个功能点的动作入手

  • 商品的加购,就涉及到购物车的存储,我们就需要在存储模型上进行设计
  • 购物车的列表展示,就涉及到商品的查询,以及跟第三方系统商品服务的交互,获取商品详情
  • 促销优惠券的选择,就涉及到跟促销优惠券系统的交互
  • 发起结算,那就是带着要结算的商品信息跟结算系统进行交互

有了这些设计的初步判断只后,大家结合实际的各类电商平台的使用过程想一想,除了我们上边所说的几个情况之外还有没有其他的问题?

1.购物车系统中存在一些复杂的情况。当用户未登录时在浏览器中加购,即便关闭浏览器再打开,加购的商品仍会保存在用户电脑中,所以商品依然存在。

2.若用户先加购,再登录,那么在登录前加购的商品会自动合并到用户名下,登录后购物车中自然有这些商品。然而,当关闭浏览器再打开时,此时虽处于未登录状态,但之前未登录时加购的商品已合并到刚登录的用户名下,所以此时购物车为空。

3.若使用手机登录相同用户,看到的将是该用户的购物车,无论在手机 App、电脑还是微信中登录,只要是同一用户,看到的购物车都是相同的,因此第二步加购的商品是存在的。

这些问题若开发者未考虑周全,会导致用户使用购物车时出现诸多问题,如商品莫名丢失或购物车莫名多出商品等。

要解决这些问题,存储设计需遵循以下原则:

  • 未登录时,要临时暂存购物车商品;
  • 用户登录时,需将暂存购物车商品合并到用户购物车中,并清除暂存购物车;
  • 用户登录后,购物车中的商品要在各种终端(如浏览器、手机 APP、微信等)中保持同步。实际上,购物车系统需保存两类购物车,即未登录时的“暂存购物车”和登录后的“用户购物车”。

那么,以上交互场景以及问题都说完了,那么我们来设计下购物车的存储。从上面的问题我们可以看到,购物车系统其实是分为临时存储跟持久化存储的,比如,我们在一些网站购买东西,购物车的内容只是临时存储在浏览器或者App本地的。

客户端存储选择:

  • Session不太合适。SESSION保留时间短,且SESSION的数据实际上还是保存在服务端
  • Cookie,数量和长度的限制。每个domain最多只能有20条cookie,每个cookie长度不能超过4KB,否则会被截掉
  • LocalStorage拓展了cookie 的 4K 限制,localStorage 会可以将第一次请求的数据直接存储到本地,这个相当于一个 5M 大小的针对于前端页面的数据库,相比于 cookie 可以节约带宽,但是这个却是只有在高版本的浏览器中才支持的。

那么我们应该选择哪一种来作为存储呢?

其实各有优劣:

使用 Cookie 存储购物车数据,实现起来可谓相当简单便捷。在进行加减购物车以及合并购物车等操作时,由于服务端具备读写 Cookie 的能力,所以所有相关逻辑都能够在服务端顺利实现,这也使得客户端和服务端之间请求的次数相对较少,更为高效。

而采用 LocalStorage 存储的话,实现过程则相对复杂一些。客户端和服务端都需要承担一定的业务逻辑实现任务。不过,LocalStorage 具有诸多显著优势,其存储容量远大于 Cookie 的 4KB 上限,并且不会像 Cookie 那样,无论是否实际使用,每次请求都必须携带,从而能够有效节省带宽资源,这一点是非常值得称道的。

所以我们要根据实际的使用场景来决定使用哪一种临时存储,如果是规模量比较小,可以使用Cookie,如果业务量比较大,可以考虑使用LocalStorage。

前边只是大段的文字阐述,那么具体存储的数据机构是什么样子的呢?大概就是下边这样,其实就是一大段JSON,包含一些商品信息,优惠券信息等等,这不是一个标准的答案,因为每一家的业务场景都是不一样的,所以购物车的具体字段设计会有所差别,但都大差不差。

{
  "cartId": "user123Cart45",
  "userId": "user123",//临时生成
  "items": [
    {
      "itemId": "prod789",
      "productName": "Smartphone X20",
      "quantity": 1,
      "price": 999.99,
      "currency": "USD",
      "attributes": {
        "color": "Black",
        "storage": "256GB"
      }
    },
    {
      "itemId": "prod456",
      "productName": "Wireless Headphones Pro",
      "quantity": 2,
      "price": 199.99,
      "currency": "USD",
      "attributes": {
        "color": "White"
      }
    },
    {
      "itemId": "prod123",
      "productName": "Smart Watch 4S",
      "quantity": 1,
      "price": 299.99,
      "currency": "USD",
      "attributes": {
        "model": "4S",
        "size": "Medium"
      }
    }
  ],
  "coupons": [
    {
      "couponId": "coupon567",
      "code": "SAVE20",
      "description": "Save 20% on your first purchase",
      "discountType": "percentage",
      "discountAmount": 20,
      "currency": "USD"
    },
    {
      "couponId": "coupon890",
      "code": "SMART50",
      "description": "Get $50 off on Smart Watch",
      "discountType": "fixed",
      "discountAmount": 50,
      "currency": "USD",
      "appliedTo": ["prod123"] // 指定优惠券适用于购物车中的特定商品
    }
  ],
  "shipping": {
    "method": "Standard",
    "cost": 9.99,
    "currency": "USD",
    "estimatedDelivery": "2023-04-05T23:59:59Z"
  },
  "totalPrice": 1509.96,
  "status": "Active"
}

说完了,临时存储,那么接下来说下持久化存储,跟临时存储一样的,持久化存储也是存储的一个大JSON,我们可以把这些数据做转成结构化对象的方式,用一些存储系统进行存储。那么,我们用什么存储合适呢?如果是结构化的方式,在设计可能会有些麻烦,举个例子,在不考虑并发的前提下,我们将数据存储到mysql,存储的数据结构大概会是这样

但是我们考虑到Mysql是关系型数据库,虽然说提供丰富的查询方式和事务机制,但是对于一些高并发的场景,或者说是产品需求方进行一些个性化的场景功能开发,比如统计一下今天加购的商品总数量,虽然使用Mysql就很容易实现,但是当数据量达到一定量的话,对性能会有一些影响,从而使得C端用户使用体验下降,造成转化率低的问题。

因为,我们不仅要有事务的特性,还要求高并发MongoDB是一个不错的选择,下面我们来说下,mongoDB作为购物车存储的优点:

一、灵活性与可扩展性

  1. 模式灵活:购车计划通常包含各种不同类型的数据,如车辆信息(品牌、型号、配置等)、客户信息(姓名、联系方式、需求偏好等)、金融方案(贷款额度、利率、还款期限等)。MongoDB 的文档数据模型允许存储非结构化和半结构化数据,无需预先定义严格的表结构,可以根据具体的购车计划需求灵活地添加、修改和删除字段,适应不断变化的业务需求。
    • 例如,当新的车辆配置选项出现或者客户提出特殊的金融需求时,可以很容易地在现有的数据文档中添加相应的字段,而无需对数据库结构进行大规模的修改。
  1. 水平可扩展:随着购车业务的发展,数据量和访问量可能会不断增加。MongoDB 支持水平扩展,可以通过添加更多的服务器节点轻松地扩展存储容量和处理能力。这使得 MongoDB 能够应对不断增长的购车计划数据和用户请求,确保系统的性能和可用性。
    • 例如,当购车平台的用户数量增多,查询和写入操作频繁时,可以添加更多的 MongoDB 副本集成员或分片节点,以分担负载,提高系统的响应速度。

二、高性能

  1. 快速读写操作:在购车计划的存储和管理中,需要频繁地进行数据的读取和写入操作。MongoDB 针对读写操作进行了优化,能够快速处理大量的并发请求。它采用内存映射文件的方式,将数据加载到内存中进行操作,大大提高了数据的访问速度。
    • 例如,客户在查询特定车型的购车计划或者提交新的购车申请时,MongoDB 能够迅速响应,提供实时的查询结果和快速的写入操作,确保用户体验的流畅性。
  1. 索引支持:MongoDB 提供了丰富的索引类型,包括单字段索引、复合索引、文本索引等,可以根据购车计划的具体需求创建合适的索引来提高查询性能。例如,可以为车辆型号、客户姓名、金融方案等常用查询字段创建索引,加快数据检索速度。
    • 比如,当销售人员需要快速查找符合特定金融方案的购车计划时,索引可以大大减少查询时间,提高工作效率。

三、丰富的查询语言和功能

  1. 强大的查询能力:MongoDB 提供了类似于 SQL 的查询语言,但更加灵活和强大。可以使用各种查询操作符(如比较操作符、逻辑操作符、数组操作符等)进行复杂的查询。这对于购车计划的查询和分析非常有用,可以根据不同的条件筛选出符合要求的购车计划。
    • 例如,可以查询特定价格范围内、特定品牌且配置了特定选装件的车辆的购车计划,或者查询具有特定还款期限和利率组合的金融方案的购车计划。
  1. 聚合框架:MongoDB 的聚合框架提供了强大的数据处理和分析功能。可以对购车计划数据进行分组、聚合、计算等操作,生成各种统计信息和报表。这对于了解购车业务的趋势、分析客户需求、评估金融方案的效果等非常有帮助。
    • 比如,可以通过聚合框架统计不同品牌车辆的购车计划数量、计算平均贷款额度、分析不同地区客户的购车偏好等。

四、高可用性和可靠性

  1. 复制集:MongoDB 支持复制集架构,通过将数据复制到多个节点上,可以提供高可用性和数据冗余。如果主节点出现故障,副本节点可以自动接管,确保系统的持续运行。这对于购车计划存储非常重要,保证了数据的安全性和业务的连续性。
    • 例如,在硬件故障、网络中断或软件升级等情况下,复制集可以确保购车计划数据始终可用,不会因为单点故障而导致数据丢失或系统中断。
  1. 自动故障恢复:MongoDB 具有自动故障恢复机制,当节点出现故障后,系统会自动尝试恢复故障节点,并将其重新加入到复制集中。这减少了人工干预的需求,提高了系统的可靠性和稳定性。
    • 例如,如果某个 MongoDB 节点由于硬件故障而宕机,系统会自动检测到故障,并启动恢复过程。在故障节点恢复后,它会自动同步数据,确保数据的一致性。

mongoDB作为购物车,存储商品也不是银弹,当然了也是有缺点的

MongoDB 用于购车计划存储有缺点。

其一,占用内存大,它通过内存映射文件操作数据,虽提高读写性能,但数据量大且并发高时会占用过多内存,增加硬件成本。

其二,复杂多表关联查询较慢,相比传统关系型数据库,处理涉及多个相关表的复杂查询时性能可能受影响。

其三,存在数据一致性问题,复制集可能出现最终一致性问题,导致数据不同步,影响业务处理。其四,学习成本高,开发人员需花时间掌握其文档数据模型和查询语言。

其五,事务支持有限,处理复杂业务流程时可能无法满足严格的事务要求,如同时更新多个信息需全部成功或失败。这些缺点在使用 MongoDB 存储购车计划时需加以考虑。

综上所述:

设计购物车存储时,需要考虑以下几个关键原则来确保系统的高效性、可扩展性和用户体验:

  • 性能,购物车操作需要快速响应,因此存储设计应支持高并发访问,减少延迟。
  • 可扩展性,随着用户数量的增加,购物车存储应该能够水平扩展,以便在不降低性能的情况下处理更多请求。
  • 数据一致性, 确保在更新、添加或删除商品时,购物车数据保持一致,特别是在分布式系统中。
  • 持久化,购物车中的数据可能需要持久化存储,以便在用户重新访问时恢复购物车状态。
  • 用户会话管理, 购物车数据应与用户会话关联,确保数据的准确性和安全性。
  • 可恢复性,在系统故障或更新时,购物车数据应能够恢复,避免用户丢失数据。
  • 安全性,保护购物车数据不受未授权访问,确保用户数据的安全。
  • 灵活性,支持不同类型的商品和服务,包括价格、库存、促销等信息。
  • 成本效益,存储解决方案应经济高效,平衡性能和成本。
  • 易用性,设计应便于开发人员实现和维护,同时提供良好的用户体验。
  • 缓存机制,利用缓存减少对数据库的直接访问,提高读取速度。
  • 索引优化,对频繁查询的字段建立索引,加快搜索和检索速度。
  • 事务管理:确保购物车操作的原子性,特别是在处理支付和库存更新时。

综上所述,购物车系统是现实世界中的购物车在互联网中的抽象实现,电商系统极为重要。它可用于凑单、促销及收藏,方便用户一次性购买多样商品、享受优惠活动及暂存心仪商品待日后下单。在技术选型上,购物车存储需考虑多方面因素。临时存储可根据业务规模在 Cookie 和 LocalStorage 中选择。持久化存储方面,MongoDB 有灵活性、可扩展性及高性能等优点,但也存在一些缺点。需综合考虑性能、可扩展性、数据一致性等原则,以满足购物车存储需求,提升用户体验。