设计 Mint.com

1. 梳理 User Case 和 约束

Use cases

作用域内的Use Case
  1. User 连接到 financial account
  2. Service 从 Account 中提取 transactions
    • 日常 Update
    • 整理 transaction
      • 所有的手动目录由 User 覆盖
      • 没有自动化的重排机制
        · - 通过目录分析月消费
  3. Service 推荐 budget
    • 允许 user 去手动设置 budget
    • 没有自动化的 重组目录
  4. Service 有高可用
作用域外
  1. Service 执行额外的日志记录和分析

约束和假设

状态假设
  1. 每个 Transaction 的尺寸
  • user_id - 8 bytes
  • created_at - 5 bytes
  • seller - 32 bytes
  • amount - 5 bytes
  • Total: ~50 bytes
  1. 每个月有250GB的新transaction内容
  • 每个transaction 50 bytes * 50 亿 transaction / 月
  • 每三年有9 TB 的新 transaction 内容
  • 假设最多的是新的 transaction,而不是更新过的已经存在的 transaction
  1. 2000 transaction / s
  2. 200 读请求 / s

方便的转换公式:

  • 每个月有 250万秒
  • 1 request / s = 250 万 request / 月
  • 40 request / s = 1 亿 request / 月
  • 400 request / s = 十亿 request / 月

2. 创建一个High Level设计

描述一个包括所有重要组件的 High Level 设计

High Level Design

3. 设计核心组件

Use Case: User 连接到 financial account
  1. Client 发送一个请求到 Web Server, 作为反向代理运行
  2. Web Server 转发 request 到 Account API 服务器
  3. Account API 服务器更新 SQL 数据库的 accounts 表(伴随着新记录的 account 信息)

accounts 表应该有如下结构:

id int NOT NULL AUTO_INCREMENT
created_at datetime NOT NULL
last_update datetime NOT NULL
account_url varchar(255) NOT NULL
account_login varchar(32) NOT NULL
account_password_hash char(64) NOT NULL
user_id int NOT NULL
PRIMARY KEY(id)
FOREIGN KEY(user_id) REFERENCES users(id)

我们可以创建一个 index 在 id,user_idcreated_at 去加速查询(登录时间代替扫描整张表)
,而且保持数据在内存里,从内存里面线性读取1MB数据需要花费 250 微妙,当从 SSD 中读取需要 倍,从磁盘中读取需要 80倍

我们可以使用 public REST API:

$ curl -X POST --data '{ "user_id": "foo", "account_url": "bar", \
    "account_login": "baz", "account_password": "qux" }' \
    https://mint.com/api/v1/account
Use Case: Service从账户获取transaction

我们想从账户中提取信息在如下的 case:

  • User 第一个 link Account
  • User 手动刷新 Account
  • 对于过去 30 天内一直处于活动状态的用户,每天自动生成

Data Flow:

  1. Client 发送一个 reuqest 给 Web Server
  2. Web Server 转发 请求到 Accounts API Server
  3. Account API service 放一个 job在 Queue里面,比如 Amazon SQS 和 Rabbit MQ
  • 提取 transaction 会需要一段时间,我们可能想要使用 queue 去异步的操作,尽管这会需要额外的复杂度
  1. Transaction Extraction Service 做如下的事:
  • 针对给定财政机构的账户,从 Queue 中拉取数据,并且提取 transaction,存储结果作为二进制文件进 Object Store
  • 使用 Category Service 去组织每个 transaction
  • 通过目录 使用 Budget Service 去计算每个月的花费
    • Budget Service使用Notification Service去让 User 知道他们是否靠近或超预算
  • 更新 SQL Database 里的 transactions 表伴随着格式化的 transaction
  • 更新 SQL Database 里的 monthly_spending table 的月均消费
  • 提示 User 交易已经通过Notification Service完成
    • 使用一个 Queue(not pictured) 去异步的发送 notifications

transactions Table 应该有以下的结构:

id int NOT NULL AUTO_INCREMENT
created_at datetime NOT NULL
seller varchar(32) NOT NULL
amount decimal NOT NULL
user_id int NOT NULL
PRIMARY KEY(id)
FOREIGN KEY(user_id) references users(id)

我们会创建一个 index 在 id,user_idm和 created_at

monthly_spending 表会有如下的结构:

id int NOT NULL AUTO_INCREMENT
month_year date NOT NULL
category varchar(32)
amount decimal NOT NULL
user_id int NOT NULL
PRIMARY KEY(id)
FOREIGN KEU(user_id) REFERENCES users(id)

我们将创建一个 index 在 iduser_id

Category Service

我们可以寻找一个 seller-to-category 目录,伴随着最流行的 sellers. 如果我们预估 50000 sellers 和评估每个 entry 会花费少于 255 bytes. 这个目录将只是需要 12 MB 的内存

class DefaultCategories(Enum):

    HOUSING = 0
    FOOD = 1
    GAS = 2
    SHOPPING = 3
    ...

seller_category_map = {}
seller_category_map['Exxon'] = DefaultCategories.GAS
seller_category_map['Target'] = DefaultCategories.SHOPPING

对于没有初始化寻找到的 map, 我们可以使用众包 effort, 通过评估手动目录股改我们的 User提供的数据。我们可以使用一个 heap 去快速的查找顶级手动覆盖每个 seller 在 O(1)时间内。

class Categorizer(object):

    def __init__(self, seller_category_map, seller_category_crowd_overrides_map):
        self.seller_category_map = seller_category_map
        self.seller_category_crowd_overrides_map = \
            seller_category_crowd_overrides_map

    def categorize(self, transaction):
        if transaction.seller in self.seller_category_map:
            return self.seller_category_map[transaction.seller]
        elif transaction.seller in self.seller_category_crowd_overrides_map:
            self.seller_category_map[transaction.seller] = \
                self.seller_category_crowd_overrides_map[transaction.seller].peek_min()
            return self.seller_category_map[transaction.seller]
        return None

Transaction 实现:

class Transaction(object):

    def __init__(self, created_at, seller, amount):
        self.created_at = created_at
        self.seller = seller
        self.amount = amount
Use Case: Service 推荐 budget

我们可以使用普遍的 budget 模板,用来分配目录总数基于income tiers. 使用这种方法,我们将不会存储 1 亿 budget 作为约束,只有这些被 User 覆盖掉,如果一个 user 覆盖掉一个 bug=dget 目录。 我们可以存储这个 覆盖量 在 表 budget_overrides

class Budget(object):

    def __init__(self, income):
        self.income = income
        self.categories_to_budget_map = self.create_budget_template()

    def create_budget_template(self):
        return {
            DefaultCategories.HOUSING: self.income * .4,
            DefaultCategories.FOOD: self.income * .2,
            DefaultCategories.GAS: self.income * .1,
            DefaultCategories.SHOPPING: self.income * .2,
            ...
        }

    def override_category_budget(self, category, amount):
        self.categories_to_budget_map[category] = amount

对于 Budget Service, 我们可以在 transactions 表里面运行 SQL Query去生成 monthly_spending 聚合表, monthly_spendging 表将可能有更少的行数,相比较于 50 亿的 transatcion. 自动 user 典型的有大量的 trasnactions 每个月。

作为一个替代,我们可以运行 MapReduce jobs在二进制的 trasnaction 文件中:

  1. 目录化每个 trasnaction
  2. 通过目录生成月综合消费

在 trasnaction 文件宏进行分析可以显式的减少数据库的负载。
我们可以调用 Budget Service 去重新进行分析User是否更新目录

样例log 文件格式:

user_id timestamp seller amount

MapReduce implementation:

class SpendingByCategory(MRJob):

    def __init__(self, categorizer):
        self.categorizer = categorizer
        self.current_year_month = calc_current_year_month()
        ...

    def calc_current_year_month(self):
        """Return the current year and month."""
        ...

    def extract_year_month(self, timestamp):
        """Return the year and month portions of the timestamp."""
        ...

    def handle_budget_notifications(self, key, total):
        """Call notification API if nearing or exceeded budget."""
        ...

    def mapper(self, _, line):
        """Parse each log line, extract and transform relevant lines.

        Argument line will be of the form:

        user_id   timestamp   seller  amount

        Using the categorizer to convert seller to category,
        emit key value pairs of the form:

        (user_id, 2016-01, shopping), 25
        (user_id, 2016-01, shopping), 100
        (user_id, 2016-01, gas), 50
        """
        user_id, timestamp, seller, amount = line.split('\t')
        category = self.categorizer.categorize(seller)
        period = self.extract_year_month(timestamp)
        if period == self.current_year_month:
            yield (user_id, period, category), amount

    def reducer(self, key, value):
        """Sum values for each key.

        (user_id, 2016-01, shopping), 125
        (user_id, 2016-01, gas), 50
        """
        total = sum(values)
        yield key, sum(values)

4. 扩展设计:

架构优化

高并发架构

我们可以添加一个额外的 Use Case: User 访问 summaries 和 transactions

User 状态,通过目录聚合,而且最近的 trasactions 被放在一个 Memory Cache, 比如 Redis 或者 Memcached.

  1. Client 发送一个读请求到 Web Server
  2. Web Server 转发请求到 Read API Server
  • 静态内容会被存储在 Object Store, 比如 S3, 被 缓存进 CDN
  1. Read API server 会做下面的事情:
  • 为内容Check Memery Cache
    • 如果 url 在内存中,发挥缓存内容
    • 否则
      • 如果 url 在 SQL 数据库中,拉取内容
        • 更新内容进缓存中

我们可以用数据仓库解决方案(如Amazon Redshift或Google BigQuery)创建一个单独的分析数据库,而不是将monthly_spending聚合表保存在SQL数据库中。

我们可能只想在数据库中存储一个月的交易数据,而将剩下的数据存储在数据仓库或对象存储中。像亚马逊S3这样的对象存储可以轻松处理每月250 GB新内容的限制。

为了解决每秒200个平均读取请求(峰值更高),流行内容的流量应由内存缓存而不是数据库处理。内存缓存对于处理分布不均的流量和流量尖峰也很有用。只要SQL读取副本没有因为复制写入而陷入困境,它们应该能够处理缓存缺失。

对于单个SQL写主从模式来说,每秒2000个平均事务写入(峰值更高)可能很难。我们可能需要采用额外的SQL扩展模式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/332206.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

小封装高稳定性振荡器 Sg2520egn / sg2520vgn, sg2520ehn / sg2520vhn

描述 随着物联网和ADAS等5G应用的实施,数据流量不断增长,网络基础设施变得比以往任何时候都更加重要。IT供应商一直在快速建设数据中心,并且对安装在数据中心内部/内部的光模块有很大的需求。此应用需要具有“小”,“低抖动”和“…

Redis分布式锁存在的问题以及解决方式

☆* o(≧▽≦)o *☆嗨~我是小奥🍹 📄📄📄个人博客:小奥的博客 📄📄📄CSDN:个人CSDN 📙📙📙Github:传送门 📅&a…

【前沿技术杂谈:智能对话的未来】深入比较ChatGPT与文心一言

【前沿技术杂谈:智能对话的未来】深入比较ChatGPT与文心一言 引言主体智能回复语言准确性知识库丰富度 深入分析:ChatGPT与文心一言的技术对比技术架构和算法数据处理和隐私用户界面和体验 应用场景分析未来展望技术进步的趋势潜在的挑战对社会的影响 结…

2018年认证杯SPSSPRO杯数学建模C题(第二阶段)机械零件加工过程中的位置识别全过程文档及程序

2018年认证杯SPSSPRO杯数学建模 基于轮廓提取与图像配准的零件定位问题研究 C题 机械零件加工过程中的位置识别 原题再现: 在工业制造自动生产线中,在装夹、包装等工序中需要根据图像处理利用计算机自动智能识别零件位置,并由机械手将零件…

JDBC编程详细教程与示例源码

版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl JDBC概述 为了在Java语言中提供对数据库访问的支持,Sun公司于1996年提供了一套访问数据库的标准Java类库JDBC。JDBC的全称是Java数据库连接(Java Database Conn…

怎么样的布局是符合可制造性的PCB布局?

满足可制造性、可装配性、可维修性要求,方便调试的时候于检测和返修,能够方便的拆卸器件: 1)极性器件的方向不要超过2种,最好都进行统一方向等要求,如图1-1所示; 图1-1 极性器件方向统一摆放 2…

CVE重要通用漏洞复现java php

在进行漏洞复现之前我们需要在linux虚拟机上进行docker的安装 我不喜欢win上安因为不知道为什么总是和我的vmware冲突 然后我的kali内核版本太低 我需要重新安装一个新的linux 并且配置网络 我相信这会话费我不少时间 查看版本 uname -a 需要5.5或以上的版本 看错了浪…

免费开源线上信息技术电子云书屋

1 概述 知命耳顺之际,时逢甲辰龙年到来,汇集半生研发积累和教育培训沉淀,以分布微服软件框架为基础,特别推出“线上电子云书屋”,陆续呈现编著的十余部信息技术教材和一些典型的软件架构平台,供给免费开源…

JVM-透彻理解字节码以及指令

一、字节码与指令概述 package ch13_bytecode;public class HelloWorld {public static void main(String[] args) {System.out.println("hello world");} }生成字节码: cafe babe 0000 0031 0022 0a00 0600 1409 0015 0016 0800 170a 0018 0019 0700 1a…

Docker(二)安装指南

作者主页: 正函数的个人主页 文章收录专栏: Docker 欢迎大家点赞 👍 收藏 ⭐ 加关注哦! 安装 Docker Docker 分为 stable test 和 nightly 三个更新频道。 官方网站上有各种环境下的 安装指南,这里主要介绍 Docker 在…

css-动画效果学习示例

阴影 x-轴 y-轴 模糊度 颜色 (正负值可以表示角度问题) 可以加多个阴影 内置阴影 transition 可以添加动画延迟效果 向z轴缩进,开启透视respective 触发旋转效果 学习来源 :动画属性_哔哩哔哩_bilibili

应用Dockerfile编写及部署使用

dockerfile内容规范: FROM mycentos-jdk:latest # 基础镜像 MAINTAINER # 镜像作者信息 姓名邮箱 RUN # 镜像构建的时候运行的命令 ADD # copy内容到容器(压缩包,自动解压) COPY # 类似…

C++:类与结构体的对比

2024年1月18日 内容来自The Cherno:C系列 -------------------------------------------------------------------------------------------------------------------------------- C中关于class与struct,几乎没有区别,只有一个关于“可见度”的区别…

element-ui的el-upload组件实现上传拖拽排序图片顺序(sortablejs)

<template><!-- 省略其他配置 --><el-upload ref"upload" :file-list.sync"fileList"></el-upload></template><script>import Sortable from sortablejs;export default {data() {return {fileList: []};},mounted()…

【React】组件生命周期、组件通信、setState

文章目录 React的组件化类组件render函数的返回值函数组件 认识生命周期生命周期解析生命周期函数不常用生命周期函数 认识组件间的通信父组件传递子组件 - 类组件和函数组件参数propTypes子组件传递父组件 React中的插槽&#xff08;slot&#xff09;children实现插槽props实现…

three.js 缓动算法.easing(渐入相机动画)

效果&#xff1a;淡入&#xff0c;靠近物体 代码&#xff1a; <template><div><el-container><el-main><div class"box-card-left"><div id"threejs" style"border: 1px solid red"></div><div c…

地平线旭日 X3 开发板上手体验

最近嫖到一块旭日X3开发板&#xff0c;借此熟悉地平线 AI 芯片旭日 X3 模型部署流程&#xff0c;以及算法工具链。这里基本是跟着官方的用户手册进行操作&#xff0c;其中也遇到一些奇怪的问题。 1 烧写系统 1.1 系统选择 旭日X3派开发板支持Ubuntu 20.04 Desktop、Server两…

【数据结构与算法】排序算法:冒泡排序,冒泡排序优化,选择排序、选择排序优化

目录 一、冒泡排序 1、冒泡排序思想 2、冒泡排序算法的性能分析 代码实现&#xff1a; 二、选择排序 1、选择排序思想 2、选择排序算法的性能分析 代码实现&#xff1a; 一、冒泡排序 1、冒泡排序思想 冒泡排序的基本思想是通过相邻元素之间的比较和交换来逐步将最大…

轮胎侧偏刚度线性插值方法

一、trucksim取数据 步骤一 步骤二 二、数据导入到matlab中 利用simulink的look up table模块 1是侧偏角&#xff1b;2是垂直载荷&#xff1b;输出是侧向力。 侧向力除以侧偏角就是实时的侧偏刚度。

unocss+iconify技术在vue项目中使用20000+的图标

安装依赖 npm i unocss iconify/json配置依赖 vue.config.js文件 uno.config.js文件 main.js文件 使用 <i class"i-fa:user"></i> <i class"i-fa:key"></i>class名是 i- 开头&#xff0c;跟库名:图标名&#xff0c;那都有什么库…
最新文章