有朋友和我说,他的程序遇到了瓶颈,程序中带有算法,但是没有事务,仅是查询情况下,应该如何提高并发数量呢??
首先,我要说的是,先纠正绝大多数人的思维。并发与并行不是一回事!!!并行,指同一时间多个事件同时发生。并发,是指在某个时间间隔中,有多个事件发生,不一定同时发生。
于是我仔细思考了一下,因为当时并没有思绪,需要思考的时间!!!
应该先从瓶颈的地方开始,需要测试工具,来测试瓶颈的具体原因。到底是查询慢问题,还是代码的问题。因为数据库也有自身的优化策略。Java也有优化策略。
先说说,我们常规处理并发的解决方案:
1.动态页面静态化。
2.制作数据库散列表,即分库分表。
3.增加缓存。
4.增加镜像。
5.部署集群。
6.负载均衡。
7.异步读取,异步编程。
8.创建线程池和自定义连接池,将数据持久化。
9.把一件事,拆成若干件小事,启用线程,为每个线程分配一定的事做,多个线程同时进行把该事件搞定再合并。
以上是我个人工作中常用的处理方案。这些都是不花钱的。条件允许,请选择CDN,这是收费的,你要找服务商来商讨。还有,可以采用GCDN的加速方式,网上有讲解,但是这两点都是要消费的。
如果以上觉得不可行,因为数据库最优,代码最优,那么怎么办????没关系,还有一个至关重要的事情,就是你的Web容器!!!你是tomcat也好Jboss也罢,weblogic也没关系。他们都有自己的调优方式。
接下来我要举实例说明了。
你应该用过淘宝吧??也应该看过它里面有个功能是定时抢宝贝的,有个倒计时,时间一到,大家疯抢。这种功能必然会涉及高并发处理。
我在重复一遍,并发与并行是两回事。它俩的区别请自行百度。回到问题中来,多个人同时抢一个东西,意味着只有一个人能拿到。有没有一种可能,大家一起上前哄抢,然后把它撕碎了,一人分一点???肯定不行,事务的ACID原则不许与你那么做。所以当某个人抢到的时候,就必然要加锁。锁,无非是两种,一个是程序的锁,Java中锁有很多种,例如synchronized,ReenTrantLock等;另一种是数据库中的锁。
至于怎么选择,我来建议一下:如果你是简单的程序,单机部署,建议用程序的锁。如果是分布式部署,建议用数据库的锁。
为啥这么说呢?因为单机情况下,用户必然不多,即使用同步锁,让请求进行排队等待,消耗的时间也不会太多,这种代价还是能承受的。当你使用分布式方案的时候,每一台应用服务器都会有自己的事务,每个请求都是相互独立的,不在同一事务中,就无法保证数据的一致性,这种情况下,程序加锁已经不起作用了。所以我们必须要用数据库的加锁机制,就是大家所说的悲观锁与乐观锁。
说到悲观锁,实际上就是for update,MyAsim只支持表级锁,InnerDB支持行级锁。 举个例子吧:
select * from 表 where 某=某 for update。先不看for update,就看前面的查询,显然意思是根据某个字段查询当前表的所有数据,加上for update 后,意味着你查的这些数据都会被锁上,那么这时候有人更改这些数据的某一条可以吗??不可以。那什么时候可以呢?必须要等到你锁定的那个表的事务完全提交完成后。如果你那个事务是个线程而且要等待另一个线程完成后才能执行,一旦出现死锁,那么就会永久等待,那可就完蛋了,这就是重大事故啊!所以,实际开发中很少使用悲观锁。
那么是什么又是乐观锁呢?相比之下也是很简单。
在数据库增加一个字段‘version’默认值为1,每次涉及事务的时候,都给它加1,举个例子:
数据中的某条数据的version值为5,说明被更改过4次。现在我再次更改,那我就要先获得当前的的version值,需要先查询,发现是5,Ok,然后我提交,把它变成6。注意,场景变了,高并发情况下。我又去更改,先获取值为6,当我提交更改时应该变成7对不对?但是我却发现它已经变成10了,说明了什么,是不是说,在我正要更改时,已经有4个人提前完成了更改,那是不是就不符合我的要求了,此时怎么办,是不是应该回滚啊。那这个SQL应该怎么写啊?
首先要把自动提交设为手动,setAutoCommit(false),注意啊,这是在同一个事务里执行的,
begin:
int currentVersion = select version from 表 where id = 某;此时查出当前版本号。
int result = update 表 set a=某,b=某,c=某,version=currentVersion +1 where id = 某 and version < currentVersion + 1。执行后返回结果。
if(result > 0){
大于零,说明成功了对不对。是不是就应该提交啊。
commit();
}else{
否则小于零,说明失败了对不对,那是不是就应该回滚啊。
rollback();
}
end;
这就是乐观锁,即使在高并发的情况下,也是可以互不妨碍的。有人说:他提交了一次失败了,就需要再次提交,假如很多人同时访问同一条数据,同时更改,那么总是会有最后的人拿到旧的版本号,必然他会失败,这样用户体验性是不是会很差?这种问题确实存在,但也要分情况而定。如果是定时抢,那么就不涉及这问题,全凭运气了,难不成你拍在最后了,还指望能抢到?所以业务的不同,解决方案也会有所不同,但是,这个方案绝对能解决现有的绝大部分需求。而且这种方案现实生活中也是绝对能承受的。
现在有人和我说,那么春运购买火车票呢?全国各地好多的窗口售卖点,假如A窗口的卖票人,点一下开票,失败,又点,又失败,再点,发现票卖没了,那是不是就尴尬了?
那我们怎么解决这个问题?这就需要进行代码优化了,还是拿上面的例子举例:
begin:
boolean isSuccess = false;
while(!isSuccess){
int currentVersion = select version from 表 where id = 某;此时查出当前版本号。
isSuccess= update 表 set a=某,b=某,c=某,version=currentVersion +1 where id = 某 and version < currentVersion + 1。执行后返回结果。
注:居然有人和我说,更新应该返回int类型,怎么会返回布尔呢?我回答:三目运算。statement.executeUpdate() > 0 ? true : false
都怨我,我真的没想过这种问题还有人问。
if(isSuccess){
说明成功了对不对。是不是就应该提交啊。
commit();
}else{
否则,说明失败了对不对,那是不是就应该回滚啊。
rollback();
}
}
end;
看到了吧,是不是不同的场景应对方法不同啊,但是都能解决问题。以上就是卖票人只点击一次,就不断去循环更改,只要有一次能成功就退出更改。解决了问题。