有一类业务场景没有固定的schema存储,但是有大量的数据行。这类业务的存储和检索在架构上如何实现?58个核心数据“帖”的架构实现技术细节,今天和大家聊一聊。一、背景描述及业务介绍1、58的核心数据是什么?58是一个信息平台,有很多垂直分类:招聘、房产、二手货、二手车、黄页等,每个分类下还有很多子分类。不管是什么品类,核心数据都是“帖子信息”。画外音:它看起来像一个大论坛吗?2、每一类信息的特点是什么?去过58的朋友可以很容易的了解这里的帖子信息:每个分类的属性差别很大,招聘帖和二手帖的属性完全不同。二手手机和二手家电的属性完全不同。目前大概有近万个属性;数据量巨大,达到百亿级别;每个属性都有查询需求,每个属性组合可能有组合查询需求。招聘需查职位/经验/薪资范围,二手手机颜色/价格/型号,二手冰箱/洗衣机/空调;吞吐量非常大,每秒几十万吞吐量;100亿数据量、10000个属性等属性组合查询,10万并发查询的技术问题怎么解决?一步步。2、易于思考的解决方案每个公司的发展都是一个从小到大的过程。抛开并发量和数据量不谈,我们先来看看:属性扩展性需求如何实现;多属性组合查询需求。画外音:公司成立初期,并发量和数据量都不大,首先要解决业务问题。1、如何满足业务的存储需求?一开始业务只有一个招聘类别,所以post表可能是这样设计的:tiezi(tid,uid,c1,c2,c3);2.如何满足属性之间的关系组合查询需求呢?最容易想到的就是通过组合索引来满足查询需求:index_1(c1,c2)index_2(c2,c3)index_3(c1,c3)3。随着业务的发展,增加了新的楼盘,如何解决分类和存储问题?可以增加几个属性来满足存储需求,所以post表变成:tiezi(tid,uid,c1,c2,c3,c10,c11,c12,c13);其中:c1,c2,c3为招聘类属性c10,c11,c12,c13为房地产类属性通过扩展属性,可以解决存储问题。4、如何满足查询要求?第一,跨业务属性一般没有组合查询需求。只需建立少量的组合索引,即可满足房地产类别的查询需求。画外音:我不敢想有多少索引可以涵盖所有的二属性查询和三属性查询。当业务越来越多的时候,你是不是发现玩不下去了?3.纵向拆分是一种思路。添加属性是一种扩展方式,添加表也是一种方式,垂直拆分也是一种常见的存储扩展方案。1、如何按业务垂直拆分?可以这样玩:tiezi_zhaopin(tid,uid,c1,c2,c3);tiezi_fangchan(tid,uid,c10,c11,c12,c13);在吞吐量巨大的情况下,垂直拆分会遇到哪些问题?这些表和相应的服务在不同的部门维护。似乎每个业务都有很强的灵活性和闭环研发。这恰恰是悲剧的开始:tid如何规范?如何标准化属性?如何通过uid查询(查询你发表过的所有帖子)?如何按时间查询(最新帖子)?扩散,有的使用mongo存储,有的使用mysql存储,还有一些自研存储;许多组件被反复开发;维护成本太高;……画外音:你想想,电商的商品列表不可能是类目一表。4、玩法58:三大中心服务第一:统一邮中心服务平台创业公司可能有多个品类,每个品类对异构数据的存储需求很多。无需纠结是分还是合:基础基础数据服务的统一是一个很好的做法。画外音:这是一个平台业务。如何统一存储不同类别、异构的数据?所有类别的通用属性统一存储;单个类别的特有属性、类别类型和通用属性存储在json中;更具体地说:tiezi(tid,uid,time,title,cate,subcate,xxid,ext);一些公共字段被提取出来单独存储;通过cate、subcate、xxid等定义ext的含义;使用ext存储不同业务线的个性化需求,比如:招聘岗位,ext为:{"job":"driver","salary":8000,"location":"bj"}和二手岗位,extis:{"type":"iphone","money":3500}post数据,100亿数据量,分为256个数据库,通过ext存储异构业务数据,使用mysql存储,在上面架设一个postcenter服务上层,使用memcache作为缓存,就是这样一个不复杂的架构解决了业务问题大问题。这是58的核心邮政中心服务IMC(InfoManagementCenter)。画外音:这个服务的底层存储在2016年全面切换到自研存储引擎,取代了mysql,但架构理念没有改变。解决了海量异构数据的存储问题,遇到的新问题是:每条记录的ext中的key需要重复存储,占用空间大,能否压缩存储;cateid不足以描述ext中的内容和类别有层次,深度不确定。ext是否可以自描述;可以随时添加属性以确保可扩展性;在解决了海量异构数据的存储问题之后,下一步就是解决品类可扩展性的问题。第二:统一类目属性服务每个业务有多少个属性,这些属性的含义是什么,值约束等,把它们耦合到post服务中显然是不合理的,那么怎么办呢?抽象出一个统一的类目,属性服务分别管理这些信息,帖子库ext字段中的jsonkey统一用数字表示,减少存储空间。画外音:post表只存储元信息,不考虑业务意义。如上图所示,json中的key不再是“salary”“location”“money”这样的长字符串,而是换成了数字1,2,3,4,这些数字是什么意思,属于哪个子类to,值校验约束统一存储在类别和属性服务中。画外音:category表存储业务信息和约束信息,与post表解耦。这张表解释了邮局服务中ext字段的数字键:1代表job,属于招聘类别下的100个子类别,取值必须是小于32的[a-z]字符;4代表type,属于二手分类下的200个子分类,取值必须是short;这样,ext扩展属性显示在原帖上:{"1":"driver","2":8000,"3":"bj"}{"4":"iphone","5":3500}key和value都是统一约束的。另外,如果ext中某个key的值不是常规校验值,而是枚举值,则需要一个限制该值的枚举表来校验:这个枚举校验表示key=4的属性(对应第二个-属性表中的手和手机类型字段),其取值不仅要通过“短类型”来验证,而且该值必须是一个固定的枚举值。{"4":"iphone","5":3500}这个ext不合法,key=4的value=iphone不合法,应该是枚举属性,合法的应该是:{"4":"5","5":3500}另外,类目属性服务还可以记录类目之间的层级关系:一级类目是招聘,房产,二手……二手类目是二手的二手家具,二手手机...二手手机下分为三个级别的分类:二手iphone,二手小米,二手三星...分类服务说明帖子数据,描述分类的层级关系,保证了每个分类属性的可扩展性,保证了每个属性值的合理性,这是58统一的另一个核心服务CMC(CategoryManagementCenter)。画外音:类目和属性服务是不是很像电商系统中的SKU延伸服务?品类层级对应电子商务中的品类层级;属性扩展对应电子商务中每个类目商品SKU的属性;枚举值标定勾选,对应属性的枚举值,如颜色:红、黄、蓝;通过类别服务,解决了键压缩、键描述、键扩展、值校验、类别层次等问题。还有这样一个问题没有解决:每个分类下的帖子属性不同,查询需求不同。如何解决100亿数据量、10000个属性的检索和联合检索需求?第三:当统一检索服务的数据量很大,对属性的查询需求不同,通过组合索引是不可能满足所有查询需求的。“外部索引,统一检索服务”是一种很常见的做法:数据库提供“postid”的查询需求;所有非“postid”的个性化搜索需求,统一使用外部索引。元数据和索引数据的操作如下:对帖子进行tid正向查询,直接访问帖子服务;修改post,post服务会通知检索服务,同时修改索引;对帖子进行复杂查询,使用检索服务满足需求。画外音:这个检索服务处理了58同城80%的请求(无论是PC还是APP,无论是首页、城市页、分类页、列表页、详情页,都会最终转化为搜索请求),它是58另一个统一的核心服务E-search,这个搜索引擎,是完全自主研发的。对于这个核心自研服务的搜索引擎架构,我简单说明一下:为了应对百亿级别的数据量,几十万级别的吞吐量,以及业务线中各种复杂复杂的搜索查询,可扩展性是设计重点:(1)统一的代理层,作为切入点,保证系统性能可以通过增加机器进行扩展;(2)统一的结果聚合层也可以保证系统性能可以通过增加机器来扩展;(3)search内核检索层、服务和索引数据部署在同一台机器上。服务启动时,可以将索引数据加载到内存中,请求访问时从内存中加载数据。访问速度非常快:索引数据为了满足数据容量的可扩展性,为了满足一条数据的性能可扩展性,同一条数据是冗余的。理论上,增加更多的机器可以无限扩大性能系统延迟,百亿级别的post检索,包括请求分离和组合,拉链交集,可以在10ms内从聚合层返回。画外音:入口层是用Java开发的,聚合层和检索层都是用C语言开发的。对于邮政业务来说,一致性并不是主要矛盾。易搜会定期全量重建索引,保证即使数据不一致也不会长期存在。五、总结文章写了很久,最后做一个简单的总结。面对百亿数据量、10000列属性、100000吞吐量的业务需求,元数据服务、属性服务、搜索服务可以解决的问题:一是解决存储问题,二是解决存储问题类别解耦问题,另一个是解决检索问题。任何复杂问题的解决方案都是循序渐进的。思路比结论更重要,希望大家有所收获。【本文为专栏作者《58神剑》原创稿件,转载请联系原作者】点此阅读更多该作者好文
