起因

在接口设计之初,没有考虑到用户访问量的问题,造成在大量并发请求的情况下,造成数据库的负载压力过于庞大(接口每2分钟上报一次,且活跃用户有20W以上),有造成数据库瓶颈的危险

目标

在保证原有接口正常的情况下,尽可能的减少数据库查询、更新、插入的SQL运行次数,以减轻数据库负载

设计

日志表

原有日志表采用请求一次就插入一条数据,现改成队列的形式,达到插入条件后,则采用批量插入的形式,提高执行效率

数据统计表

原:

请求一次,判断是否为已存在数据,若存在则进行更新操作,若不存在则进行插入操作

现:

1、判断是否存在此数据;查询缓存,若缓存不存在,则进行数据库查询,若数据库不存在,则写入插入队列,并将此信息存储到Redis的集合中,实现去重操作,下一次若有一样的数据,则添加到更新队列中

2、先执行批量插入队列,执行成功后,将Redis中的相关集合清空,并且执行更新操作,更新完成则清除队列数据【此处有两种实现方式,会写入到踩过的坑中说明】

相关查询表(用户信息、关联ID信息等等)

原:

每次请求都去查询一次数据库表中的数据

现:

引入Redis缓存,因此处只需要判断是否存在,所以采用Redis的位图bitMap数据结构,存储相关数据

【位图比string数据结构更加节约资源,相关的位图介绍,可以点击此链接查看:位图介绍

测试

用自带的ab进行压测,具体相关的教程可以点击此处

最终结果

在并发1000、请求1000的情况下,SQL执行条数减少了四分之三

踩过的坑

1、批量更新使用update when then 语句时,一定要注意更新的数据是是否正常

##例如:使用下面的语句,会有怎样的情况发生呢?
UPDATE table_name SET a=CASE id WHEN 797 THEN 123 WHEN 273 THEN 456 END,b=CASE id WHEN 797 THEN b+138 END,c=CASE id WHEN 273 THEN c+123 END WHERE id IN ('797','273')

##执行这条SQL的时候,会出现其他的字段被置为0,例如:表里面还有d、e、f,而且里面是有相关数据的,用了上面的SQL后,会造成其他的d、e、f的数据被清空

2、当高并发场景下时,一定要注意原子性

(1)加分布式锁,用Redis实现

(2)队列数据取出的同时,马上删除此部分数据,若执行成功,则丢弃此数据集,若执行失败,则用代码实现将数据回滚回原队列中

3、无论是什么缓存,都必须加上过期时间,防止不消费的情况下,缓存中的数据一直保留,占用服务器资源

4、使用Redis的scan方法的同时,必须注意游标的问题,详情可点击此处

$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);

5、做相关优化的时候,一定要多重测试;例如:自测、测试服测、上线前奏测、线上测等等