system desing 系统设计(一): 数据库sql和NoSql的选择
1、我们现在用的手机、笔记本、pc等,任何功能都不再是单机,都要和后端的server通信,而通信的本质就是交换数据,重要的数据最终都会存放在server端。那么问题来了,每个用户无时无刻都在产生大量数据,这些数据在server端都是怎么存放的了? 存放好后,怎么才能快速地增删改查、做crud了?
按照数据存放形态划分,主要分一下3种:
- unstructured data 非结构化数据:指的是文件类,包括但不限于音视频、各种文档、日志等,通常存放于各种文件系统File System。
- 这类数据没有“规则”,适合存放在OS的本地磁盘。
- 如果有分布式distribute、冗余备份redundant backp等需求,可以考虑HDFS、对象存储cos/oss、Amazon的S3等!
- structed data 结构化数据: 这类数据非常规整,整齐划一,适合存放在excel类的表格型table数据库中。
- 常见的关系型数据库有mysql、sql server、Oracle、db2等,支持事务transaction;
- 大数据类的分布式数据库有hive、pg、mpp等,不支持tramsaction;
- 简而言之:能存放在excel的数据,也能存放在这些数据库中;
- 因为增删改查这种规则数据长用的就是SQL语句,所以这类也成为sql数据库
- semi-structed data 半结构化数据:这类数据相比上一种结构化数据,也是一行一行的,但是没那么规整,主要是KV结构的数据,比如key是uid,value是各种属性,包括但不限于name、age、height、weight、friends等!
- key是固定长度的,但是value的长度可变,相比于上面的sql数据库,下面的这种明显扩展性extensibility更好!
- 平时写代码时,这种结构的数据常用hashmap存储,但hashmap的数据都放在内存memory,一旦断电,数据全丢,为能持久化保存这种KV数据,最终肯定是要存放在硬盘disck上的,所以衍生出了很多著名的KV半结构化数据库: hbase、redis、mongoDB、Cassandra等!因为存放的数据没那么“规整”(毕竟value时可变的嘛),所以又被成为NoSql数据库!
2、(1)sql数据库很容易理解,平时excel怎么存,sql数据库就怎么存。另外,从功能上讲部分还支持transaction,我个人认为这是sql数据库最大的优点了,所以mysql、sql server、oracle等被大量用在了OLTP场景,最典型的就是交易系统!除了交易系统,sql数据库还被广泛用于“规整”数据的存储,典型如电商的商品、用户、订单等数据,比如:
表和表之间通过各种id、primary key做join来关联数据!总结:sql数据库存放的都是规整的数据,excel怎么存数据,sql数据就怎么存数据;
(2)除了规整的sql数据库,还有另一种不是那么规整的KV数据存储,最常见的就是redis、hbase、mongDB、cassandra等;redis因为是内存数据库,用的是跳表skip list,所以速度明显比数据存放在磁盘的数据库快很多,这里先简单介绍一下redis的核心功能!
众所周知,计算机体系存储数据的速度依次排序:cpu register、L1 cache、L2 cache、L3 cache、memory、hard disk;内存和磁盘(开发人员能直接控制的存储设备)速度对比如下:
类型
|
每秒读写次数
|
随机读写延迟
|
访问带宽
|
内存
|
千万级
|
80-100ns
|
5GB
|
SSD 盘
|
35000
|
0.1-02ms
|
100~300MB
|
机械盘
|
100 左右
|
10ms
|
100MB 左右
|
为了快速读写数据,redis选择了把数据存放在memory,而不是磁盘;利用速度快的特点,redis特别适用于在线i计算等实时场景:
- 缓存系统cache:经常被用到的“热hot”数据完全可以放在内存中,而不是每次都从磁盘读写!
- 比如weibo、twitter某些大V刚发的tweets
- 又比如电商常见的秒杀:如果短时间内所有的流量都直接打到数据库,用户体验会非常差,原因很简单:
- 因为是多线程,所以用户访问数据库的时候肯定要加锁,导致数据库本身的读写效率低,严重降低QPS;
- 数据还在磁盘,IO的效率比内存差远了
- 计数器counter:因为redis本身是线程安全的,完全可以在多线程的情况下做计数器,比如电商中商品的数量,尤其是上述的秒杀场景。使用redis后整体的架构如下:
大部分流量都在redis被响应了,真正到最后mysql数据库的流量会很少;如果秒杀的数量特别多,mysql的流量还是很大,可以在redis后面加个消息队列MessageQueue,比如kafka,让mysql从kafka取数据,达到了削锋peak clipping的效果!
- bloomfilter:使用redis实现bloomfilter的功能,用来过滤重复的url、垃圾邮件等;
除了速度快,redis的另一个特点就是value的长度和格式多种多样,并不局限于string!各种数据结构列举如下:
常见的数据存储结构都有了,比如sets of strings就可以用来在电商秒杀、单用户购买限量上使用,如下:
比如把秒杀商品的id、秒杀id等作为key,已经秒杀到商品用户的uid写入value的sets集合;后续收到用户的秒杀请求时,先到sets来看看是否已经存在了,不存在说明还没购买,再继续走购买的流程!因为上述这些value的特性,redis的又拓展了如下的试用场景:
- messageQueue:比如把消息都放在lists、sets里面,由于是多线程安全的,完全可以让多线程同时读取消息;
- ranking list 排行榜:把数据放在sorted sets of strings,redis自带了value的ranking功能
- social media 社交网络:flowings关注列表、followers粉丝列表、共同关注等;
- row_key:又称为 Hash Key 或 Partition Key;Cassandra 会根据这个 key 算一个 hash 值,然后决定整条数据存储在哪个partition(目标就是分布式、多态机器存储数据);
- colume_key: 这个key决定了数据存放在partition的哪个位置;用户可以指定 column_key 按照什么排序,因为Cassandra 支持这样的“范围查询”(这点比redis、memcache强);查询数据的 query(row_key, column_start, column_end)中的colume_key可以是复合值, 如 timestamp + user_id,这样就可以根据timestamp排序了;
- value:就是用户想存放的数据了。对于object类的数据,可以先serialize序列化后再存放!
(2)回到应用上:在社交场景,这种KV数据库是很适合用来存放关系链数据的,比如用户好友的关系链就可以这样存:
如果把value中的timestamp放到colume_key,还可以根据时间先后顺序对好友关系链做排序,看看谁是我的第一个好友,哈哈!同理,还有用户自己发送的tweet也可以按照如下方式存储:
检索的时候可以根据create_at来划定范围,找到tweet_id;再根据tweet_id在redis等缓存中找tweet_data;如果缓存没有,再在cassandra中查找,效率会更高!
(3)具体到tweet场景,最基础的表存储结构如下:
- User Table:存放用户属性,因为结构是固定的,可以用sql数据库存储
- friendship Table:因为好友关系是可以变化的,所以需要用NoSql数据库存储
- tweet Table:用户发送tweet也是动态变化的,所以也需要用NoSql数据库存储
在取tweet时,方式有pull和push两种;pull简单,就是用户登陆后,先从friendship table拿到好友id,再根据id去tweet table拿tweet,整个流程如下:
从流程图可以看出,登陆后要两次读取数据库存放在磁盘的数据,所以为了提速,可以适当把部分好友的id和tweet放内存cache;至于放cache的标准,可以是时间顺序,比如时间近的放内存;也可以根据好友的影响力来决定,比如weibo、facebook、twitter的大V明星用户,他们的id、tweet放内存做缓存!与pull对应的还有另一种取tweet的方式,就是push,原理也简单:用户发布tweet后,肯定第一时间写入tweet table;然后后台程序再去friendship table找到该用户的followers,给所有的followers主动push这条tweets(专业名词叫fanout)!整个流程如下:
这么做好处是用户登陆后可以直接从news feed table拿最新的tweet,只需要读一次DB,效率明显比上面的pull(如果不考虑cache,需要读2次DB)高一些!步骤4和5也可以异步离线执行,不影响在线实时的效果!那么最关键的问题来了,这个news fees table的结构到底是啥了? 如下:核心字段就两个,分别是onwer_id和tweet_id;用户登陆时,直接一条sql语句就能查到所有的tweet!
目前了解到的:facebook用的pull,twitter用的pull,wechat朋友圈用的push,知道为啥么?
最后做个总结,不同数据库大致的QPS数量级如下(单机4C8G的配置),供参考:
- MySQL / PosgreSQL 等 SQL 数据库的性能:约 1k QPS
- MongoDB / Cassandra 等 硬盘型NoSQL 数据库的性能:约 10k QPS
- Redis / Memcached 等 内存型NoSQL 数据库的性能: 100k ~ 1m QPS
用户的QPS直接决定了选用哪中数据库来存放数据!
2022.9.2更新:最近几天成都出现疫情,全市每天接近2000w人次做核酸,但是核酸系统经常崩溃,导致很多市民排几个小时的队,怨声载道!网传是用了mysql搞了张超大宽表:
貌似单表还过亿了,而且没有sharding,哎,承建方要出来背锅了.......
参考:
1、https://www.bilibili.com/video/BV1Za411Y7rz?p=6&spm_id_from=pageDriver&vd_source=241a5bcb1c13e6828e519dd1f78f35b2 存储系统设计
2、https://www.bilibili.com/video/BV1Za411Y7rz?p=2&vd_source=241a5bcb1c13e6828e519dd1f78f35b2 sql和nosql数据库的选择
3、https://blog.51cto.com/u_12302929/3386774 sql和nosql如何选择