目录
业务图
用户管理
业务难点
1. 如何确定用户注册信息的真实性
2. 面对亿级用户量
3. 支持多种登录方式会造成读请求扩散,需要解决用户定位问题
4. 高并发场景下缓存穿透问题需要有效解决,避免数据库压力过大
5. 明文存储用户敏感信息会造成安全隐患,需要对关键数据加密
项目效果
编辑
编辑
编辑
编辑
数据库设计
会员相关核心数据库表
乘车人数据表
列车数据表
订单数据表
项目结构
业务图
大体上分为会员服务、订单服务、购票服务、支付服务、网关服务
用户管理
12306 铁路购票系统中,存在两类用户,分别是:会员(即当前账户登录用户)以及乘车
人。
会员支持在系统中自行注册,需要注册者提供用户名、密码、证件类型、证件号、真实姓
名、手机号、邮箱以及旅客类型。
其中,用户名和证件号码全局唯一,不允许注册者重复使用。
会员登录系统时,支持用户名/邮箱/手机号码三种登录方式,搭配密码完成系统用户登录
行为。
一个注册会员可以添加多个乘车人。添加乘车人时需要填写真实姓名、身份证、手机号
等,新增乘车人需要通过实名认证审核,审核通过方可成功。
会员选择交通工具(火车、高铁等)进行买票时,可以选择多个乘车人进行购票。
会员可以通过已支付订单查看所有订单信息,乘车人也可通过订单标签页中本人车票查看
自己或其他会员购买为自己购买的订单信息。
业务难点
1. 如何确定用户注册信息的真实性
当用户在 12306 网站注册新账号或者为自己的账号添加新的乘车人时,系统需要确保用
户提交的各项信息是真实准确的,而不是虚假的。
这其中就涉及到一个很重要的概念——用户信息的认证。对用户提交的信息进行认证,就是
要验证信息的真实性,确认这确实是本人自愿提交的真实信息,不是其他人冒充的。
12306 中的信息准确性校验主要包括:
1.注册会员时,校验用户名、证件号、手机号是否匹配,确保账号信息真实有效。
2.添加乘车人时,校验乘车人姓名、身份证号是否真实匹配,并校验手机号可用性。
2. 面对亿级用户量
鉴于会员和乘车人数据规模都已超过 10 亿级别,远超出单机 MySQL 数据库的处理能
力,所以需使用分库分表或分布式数据库来支撑海量数据。
分库分表后续又会涉及:
选择分库还是分表,还是选择分库分表?基于什么考虑?
选择哪个字段作为分片键?选择单个分片键还是复合分片键?
如何在老业务上平滑上线分库分表?出现问题如何快速回滚?
拆分后出现单表数据量过大,如何继续扩容?扩单表还是整体扩?
3. 支持多种登录方式会造成读请求扩散,需要解决用户定位问题
在具体的登录环节,由于系统支持会员使用用户名、手机号以及邮箱等多种方式进行
登录,这就存在一个比较棘手的问题:
由于登录时无法确定用户的分片键,使得系统无法直接锁定用户的数据位于哪个数据库或
者哪张表中。为了找到用户的数据,只能对全部的数据库和表进行扫描查询,这就造成了
所谓的“读请求扩散”问题。
也就是说,原本读请求可以直接定位到某个数据库某张表,现在却要多处查询,无疑大大
增加了系统的查询负载。
一旦出现了读请求扩散问题,势必会导致用户的登录请求响应时间变长,严重的话还可能
造成登录超时。
4. 高并发场景下缓存穿透问题需要有效解决,避免数据库压力过大
在高并发的会员注册场景下,可能会出现缓存穿透问题。主要原因可能是:
用户注册时,需要验证用户名是否已存在,这通常需要查询数据库。如果缓存中没有该用户名,就会去数据库查询,如果数据库中也没有,就可以判断该用户名可用。
在高并发的情况下,可能有大量的新用户同时注册,输入的用户名极有可能都不存在
于数据库中。这将导致大量的缓存不存在,都去查询数据库,造成数据库压力剧增。
且这些查询数据库的 Key 都不会被缓存,因为数据库中没有,不会写入缓存。那么这
些 Key 对应的 Null 值也不会被缓存,造成每次请求都查不到缓存,直接查询数据
库。这样就形成了缓存穿透情况。
5. 明文存储用户敏感信息会造成安全隐患,需要对关键数据加密
在 12306 系统中,存在较多的敏感信息,比如会员或者乘车人的姓名、手机号、邮箱、
证件号码以及住址。
在数据库中将手机号或身份证号等敏感个人信息存明文会带来以下安全问题:
1.明文存储可能会直接泄露用户隐私,一旦数据库被攻击或数据泄露,个人敏感信息就
会被完全暴露。
2.明文存储提高了内部人员滥用数据的风险,恶意查询和使用个人敏感信息的成本很
低。
项目效果
个人信息页面
首页查询车票
乘车人页面
订单页面
车票页面
数据库设计
会员相关核心数据库表
t_user 会员数据表:存储会员账号、密码、证件号、邮箱、手机号等信息
t_user_mail 会员邮箱数据表:存储会员邮箱和用户名的关系
t_user_phone 会员手机号数据表:存储会员手机号和用户名的关系
虽然手机号和邮箱在用户表中已经有了,但因为我们对用户表进行了分库分表行为,分片键使用的 username 用户名进行拆分。分库分表中间件ShardingSphere 会通过分片键 username 用户名来确定数据在哪个库中的哪个表。所以,分库分表后,每次增删改查都需要带上分片键用户名。不然的话,查询会请求所有库的所有用户表,新增、修改和删除会直接报错。
用户相关扩展功能表如下:
t_user_reuse 用户名可复用表:存储已被注销的可用用户名
t_user_deletion:用户证件号注销表:存储被注销过得证件号记录数据
t_user_deletion 用户证件号注销表,用来解决用户恶意注销 12306 账号的问题。假设,一个用户用一个证件号反复注册并注销,是不是应该把他拉入黑名单。因为用户名每次注册都可以使用新的,所以说我们只能针对证件号当做依据。目前系统的规则是,如果同一个证件号注销超过5 次,就拉入黑名单不再支持注册。
乘车人数据表
t_passenger:乘车人数据表
一个用户可以多名乘车人,可以用来买票时选择多人购票。用户表和乘车人表之间通过用户名进行关联,一对多的关联关系。
列车数据表
t_train:列车表,存储每天生成的行驶列车数据。
t_carriage:列车车厢表,存储每趟列车对应的车厢数据,包括车厢类型。
t_train_station:列车站点表,存储列车行驶站点顺序表。
t_train_station_relation:列车站点关联表,存储列车行驶站点关联关系表。这是一个会把所有车站都会关联起来的数据集合,存储的数据模型如下所示:北京南->天津西,北京南->沧州西,北京南->xxx,北京南->句容西。
t_train_station_price:列车站点价格表,存储列车站点关联关系不同座位价格表。价格表在 t_train_station_relation 关系表的基础上,加入了两个字段,一个是价格,一个是座位类型。因为列车行驶路线中,不同的站点间隔费用不一样,而且不同的座位费用也不一样。通过价格表整体区分出来。
订单数据表
t_order:订单主表,用户购买的单次车票,就对应一个订单。但是有可能一个订单中会有多个
乘车人,所以还会有订单明细表。
t_order_item:订单明细表,一个订单可能有多个乘车人,多个乘车人就对应多个订单明细。
t_order_item_passenger:订单明细乘车人表,因为订单表和订单明细表分库分表规则所致,
乘车人无法查看本人车票订单,所以创建了这个关联表。通过证件号关联订单。
项目结构
12306项目的后端+前端项目,其中包含了不同的组件、服务和测试集合等。
checkstyle
: 代码格式检查组件,包括规则配置和忽略配置。console-vue
: 12306前端控制台项目,包括相关文件和目录。dependencies
: 后端项目全局依赖版本控制的pom.xml
文件。format
: 后端项目格式化组件,包括格式化规则配置和开源协议头格式化。frameworks
: 基础架构组件库,包括顶层抽象基础组件、业务相关组件、缓存组件、公共工具包、项目规约、数据库持久层、设计模式、分布式ID、幂等组件、日志打印、Web相关组件。resources
: 项目数据库初始化及其他资源,包括数据初始化和数据库初始化。services
: 后端项目集合,包括聚合模式服务、网关服务、订单服务、支付服务、购票服务、用户服务。tests
: 单元测试集合,包括通用单元测试。pom.xml
: Maven项目的配置文件。LICENSE
: 开源许可证文件。mvnw
: Maven Wrapper文件。
├── checkstyle || -- # 代码格式检查组件
│ ├── 12306_checkstyle.xml || -- # 代码格式检查组件规则配置
│ └── 12306_checkstyle_suppression.xml || -- # 忽略代码格式检查组件规则配
置
├── console-vue || -- # 12306 前端控制台项目
│ ├── README.md
│ ├── babel.config.js
│ ├── jsconfig.json
│ ├── node_modules
│ ├── package.json
│ ├── public
│ ├── src
│ ├── vue.config.js
│ └── yarn.lock
├── dependencies || -- # 12306 后端项目全局依赖版本控制
│ └── pom.xml
├── format || -- # 12306 后端项目格式化组件
│ ├── 12306_spotless_formatter.xml || -- # 12306 后端项目格式化组件规则
配置
│ └── license-header || -- # 12306 后端项目开源协议头格式化
├── frameworks || -- # 12306 基础架构组件库
│ ├── base || -- # 12306 顶层抽象基础组件
│ ├── bizs || -- # 12306 业务相关基础组件,比如用户上下文等
│ ├── cache || -- # 12306 缓存基础组件
│ ├── common || -- # 12306 公共工具包组件
│ ├── convention || -- # 12306 项目规约组件
│ ├── database || -- # 12306 数据库持久层组件
│ ├── designpattern || -- # 12306 设计模式抽象基础组件
│ ├── distributedid || -- # 12306 分布式 ID 基础组件
│ ├── idempotent || -- # 12306 幂等基础组件,包括 HTTP 及不同消息队列实现
│ ├── log || -- # 12306 日志打印基础组件库
│ └── web || -- # 12306 Web 相关基础组件库
│ ├── pom.xml
├── resources || -- # 12306 项目数据库初始化及其它
│ ├── data || -- # 12306 数据库数据初始化
│ └── db || -- # 12306 数据库初始化
├── services || -- # 12306 后端项目集合
│ ├── aggregation-service || -- # 12306 SpringBoot 聚合模式服务
│ ├── gateway-service || -- # 12306 网关服务
│ ├── order-service || -- # 12306 订单服务
│ ├── pay-service || -- # 12306 支付服务
│ ├── ticket-service || -- # 12306 购票服务
│ └── user-service || -- # 12306 用户服务
│ ├── pom.xml
└── tests || -- # 12306 单元测试集合
│ ├── general || -- # 12306 通用单元测试
└── pom.xml
├── LICENSE
├── mvnw