去年做了一个优化类项目,当时接收时,基本上oracle-db io已经被这个应用占据了79%的load,随着业务发展,DB响应已经到了无法忍受的地步。

       具体场景为:实时监听买卖订单消息,订单保存45天,这些订单会每条都更加用户维度进行分析,分析规则比较复杂,基本有单笔订单,该用户近45天订单全量分析,分析完后给出该订单是否为真假,如果真的订单并且未结束,再汇总出总金额,需要实时计算。

      当时高峰期每秒进来订单量在300多,总计每天订单增长数维持在1KW,45天大体订单在4.5个亿。

      当时查询性能基本上已经慢到一个用户如果订单量>1000基本上响应时间在5秒以上。后续又来的业务需求,订单状态会发生变化,用户订单也时刻在发生变化,那么我们需要的金额就会时刻在变化,一旦变化就力度非常大,就需要预警。

      怎么办?a.新需求

                  b.每天访问量越来越大,性能。

                  c.公司搞大型活动订单高峰期预计比平时高5倍。

     

      拆库是必然的,而且业务发展非常快,至少预估3年可扩展。

      拆库成本很高,数据迁移,停机。。。。

      半年后如果再次性能存在问题,再搞一次拆库实在不方便,而且还需要评估一些公司大型活动的冲击。

能否有种可以支持半年然后,可以无缝扩容一次,1年后再扩容一次。。。。

  一.部署图

Mysql

    应用服务器:主要包括

a. 业务逻辑处理

b. 数据存取路由

c. 数据存取处理

    业务逻辑处理、数据存取处理两层基本实现与传统的处理没有区别,下面详细阐述数据  路由的实现


 

 二. 数据存取路由

a) 规则算法

这里采用的是针对用户(UserId) 的hash 值取余数(Key , 传统的规则算法是当前需要分n 个库,分母就是n ,本专利采用小于n * (2t )的最大质数为分母,t 的值为你预估的无缝成倍扩容次数; 譬如当前需要划分16 个库能支撑应用性能,假设取t=8 ,那16 * (28 )=4096. 也就是说无缝扩容至少可以支持到4096 个库,4093 为最大的小于16 * (28 )=4096

b) 路由映射关系

Key-- 数据库实例)的映射关系(映射关系可以通过集中式缓存+

Key 值:规则计算后的值;

数据库实例别名:每个数据库实例的一个唯一标识;

数据库实例参数:进行存取数据时需要连接数据库的参数;

读写模式:该映射关系是读写模式(WR), 还是只读模式(R) :如果是WR

UserID :针对特殊用户的特殊映射关系。

譬如按上面例子进行的规则算法,该映射关系为:

  Key 值在(1*28 )>key>=(0*28 ) Instance1 ;

          Key 值在(2*28 )>key>= (1*28 ) Instance2 ;

          Key 值在( n*28 ) >key>=((n-1)*28 ) 映射到 Instance n ;

          Key 值在( 16*28 ) >key>= ( 15*28 ) 映射到 Instance16

   

Key 值

数据库实例别名

数据库实例参数

读写模式

UserID

0

Instance1

{ ip:XXX,port:8080 ….}

WR

 

1

Instance1

{ ip:XXX,port:8080 ….}

WR

 

….

Instance1

{ ip:XXX,port:8080 ….}

WR

 

255

Instance1

{ ip:XXX,port:8080 ….}

WR

 

256

Instance2

{ ip:YYY,port:8080 ….}

WR

 

257

Instance2

{ ip:YYY,port:8080 ….}

WR

 

….

Instance2

{ ip:YYY,port:8080 ….}

WR

 

1877

Instance8

{ ip:MMM,port:8080 ….}

WR

 

…..

…..

WR

 

4095

Instance16

{ ip:ZZZ,port:8080 ….}

WR

 


     

    假设A用户根据规则计算得出其Key=257,
    当用户A有数据进行存储时,
    根据映射关系(读写模式为WR)找到对应的数据库实例为Instance2,新增数据将存储到Instance2数据库;
    当用户A有数据需要修改或者查询时,根据映射关系(模式为WR,R)找到对应的数据库实例为Instance2,(如果有多个映射关系,循环各个映射关系),查询或者修改对应的数据库。


 三.再次扩容

情况1 :譬如 Instance1 (其对应Key 值为0-255

增加key 值0-127 的映射关系,将其映射到 Instance17 读写模式为WR

将原来的0-127 的映射关系读写模式改为R

key 值在0-127 的用户数据将被路由到 Instance17 中 , 数据查询以及修改将会同时对 Instance1 ,Instance17 进行同时操作;

 

KEY 值

数据库实例别名

数据库实例参数

读写模式

UserID

0

Instance1

{ ip:XXX,port:8080 ….}

R

 

0

Instance17

{ ip:XXX,port:8080 ….}

WR

 

1

Instance1

{ ip:XXX,port:8080 ….}

R

 

1

Instance17

{ ip:XXX,port:8080 ….}

WR

 

….

Instance1

{ ip:XXX,port:8080 ….}

WR

 

255

Instance1

{ ip:XXX,port:8080 ….}

WR

 

256

Instance2

{ ip:YYY,port:8080 ….}

WR

 

….

Instance2

{ ip:YYY,port:8080 ….}

WR

 

1877

Instance8

{ ip:MMM,port:8080 ….}

WR

 

…..

…..

WR

 

4095

Instance16

{ ip:ZZZ,port:8080 ….}

WR

 

 

情况2 :当需要再次扩容一台 Instance18 机器时 , 只需要根据上述步骤将0-63 的Key

情况3 :当需要针对一些特殊用户进行单独处理时 , 譬如用户A( Key=257 ) ,用户B

增加两条记录;

由于存储映射关系匹配到该用户存在特殊映射关系时,以该用户映射关系优先 , 故加入配置以后,用户A ,B

 

KEY 值

数据库实例别名

数据库实例参数

读写模式

UserID

0

Instance1

{ ip:XXX,port:8080 ….}

WR

 

1

Instance1

{ ip:XXX,port:8080 ….}

WR

 

….

Instance1

{ ip:XXX,port:8080 ….}

WR

 

255

Instance1

{ ip:XXX,port:8080 ….}

WR

 

256

Instance2

{ ip:YYY,port:8080 ….}

WR

 

257

Instance2

{ ip:YYY,port:8080 ….}

WR

 

257

Instance19

{ ip:YYY,port:8080 ….}

WR

用户A

….

Instance2

{ ip:YYY,port:8080 ….}

WR

 

1877

Instance8

{ ip:MMM,port:8080 ….}

WR

 

1877

Instance19

{ ip:MMM,port:8080 ….}

WR

用户B

…..

…..

WR

 

4095

Instance16

{ ip:ZZZ,port:8080 ….}

WR

 

1 扩容1 年后,1 年前的老数据需要归档,如果 Instance1 中Key 值在0-127 数据全部归档后,0-127

通过简单修改路由映射关系,解决了传统的扩容情况造成的各种问题。