工程背景介绍:
我们开发了一个万能接口,用户通过这个接口中传入数据,我们拿到数据进行复杂的逻辑处理然后再将数据各种匹配展示分发等操作,处理的流程相当庞大,接口中我们只保留了接收数据和返回一个本次请求的id的操作,其余操作都是异步到其他程序中处理的。
返回id的操作是需要和数据库进行两次连接,一次读库得到最新的id 然后把id更新到数据库。
项目出现问题:
我们以为自己的程序就像上图中的那样运行,一次请求,读库,写库,返回id,其余异步处理。但是没有考虑高并发,强压力写的性能问题,在高并发下,多个接口线程同事访问数据库,这样的情况会出现并发同步的问题,当然这点我们是考虑到了 ,使用线程锁可避免数据的幻读,重复读等。可一旦这样大量的接口线程堆积,很快服务器cpu将扛不住发生宕机!
那如果不试用线程同步锁呢,很明显不只是数据的错乱问题将发生,数据库在极大线程的访问压力下也将抗不住,cpu使用率达到85%!程序面临着瘫痪的风险。
解决方式:
1.数据库集群?
好处:增加数据库服务器,压力随之分摊到几个服务器中,减小数据库压力
坏处:硬件成本大 数据库主从问题 哈希一致性问题 单点故障问题
2.接口服务器集群
好处:访问压力被分摊到几个服务器中 线程的堆积问题得到有效解决
坏处:硬件成本大 单点故障问题 nignx负载均衡服务器的搭建 操作复杂
以上两种方案都是增加硬件成本,加大了开发难度,难以真正实施
终极解决方案:读写分离的应用!
好处:硬件成本不变 释放了数据库压力 再也不用担心数据库和服务器压力了
坏处:无
实现思路:
1.对读库操作解耦合,增加缓存服务ecache(也可以是其它如redis),读不再直接面对数据库,只有缓存中没有数据时才把数据库中的数据读写到缓存之中,这样数据库的读压力完全被释放!
2.对写数据库进行异步操作,在更新数据库操作中增加消息中间件rabbitmq(也可以是其它如activemq,spring对前者支持较好),我们将更新后的id写入到rabbitmq中,单线程消费mq到数据库,由于此时跟新后的id已经返回给客户,写库的操作并不需要高时效性。这样数据库的写压力也完全被释放!
这个时候我们来看看整个操作,读写均不再直接面对数据库,但是数据库中的数据依然没有错乱,缓存的操作速度极快,接口服务器也不再有很多的线程挤压,我们在硬件成本不变的情况下解决了数据库和服务器压力过大,可以说是一种完美的解决方案
至此,压力的问题成功解决,当然问题的解决并非是一撮而就的,这里还有着一些其它的问题,比如分布式事务的解决,这里不再一一赘述,后面我们找机会仔聊聊这些分布式服务的经典问题!