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粉丝列表、共同关注等;
     3、redis因为常驻内存,速度飞快,但是架不住内存贵啊,所以只能当cache,放少量常用的hot数据,其余大量数据还是要存磁盘,这就诞生了另一个常见的KV形式的NoSql数据库:cassandra、hbase、mongDB等;那么问题来了,哪些场景会产生大量KV数据、以至于内存都放不下、非常要放磁盘了?对于大型的互联网企业,存量用户动辄数亿、甚至数十亿(否则就不叫大型互联网企业了),每个用户肯定都有自己的用户画像(主要用于推荐等精准营销的场景);每个用户的画像标签少则几十上百,多则数千,按照平均一个用户需要1KB空间来存储所有的画像标签计算,数十亿的注册用户就需要TB级别的空间才能存储(比如15亿用户就需要约1.5TB的空间),很明显这个多数据全放内存是很贵的,大部分数据自然是要放磁盘的! 
  同理:像facebook、tencent、twitter、weibo这种social media类型的大厂,核心资产就是friends relationship关系链了,这也是典型的KV场景:自己的uid是key,friends好友的id组成value!TB级别的关系链数据,最终肯定是要放磁盘,这里以cassandra为例,介绍以下NoSql数据库的核心功能!
     (1)cassandra存储的数据从逻辑上分了三层:row_key、colume_key和value;插入数据的API为 insert(row_key, column_key, value);任何一条数据, 都包含上面三个部分;
  • 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如何选择

posted @ 2022-08-12 11:10  第七子007  阅读(398)  评论(0编辑  收藏  举报