互联网高并发问题
互联网高并发问题
一般的互联网应用,访问量非常大,最常见的是高并发问题。这是很多程序员都头疼的问题,但是问题终究要解决,所以我们一起看看吧。
高并发要解决的问题
- 同步
- 服务器能够接受请求能力
- 响应时间
- 防止单点故障和扩展
同步
说起同步,就是要加锁,而锁又分为先三种
- 代码层次上的,如java中的同步锁,典型的就是同步关键字synchronized(这里不讲,因为不适合分布式系统)
- 数据库层次上的,这些是分布式锁,如数据库的悲观锁、乐观锁;redis的分布式锁机制
- 第三方框架层次上的,如zookeeper
悲观锁
/* 本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。 */ select * from account where name=”Erica” for update
悲观锁的实现,往往依靠数据库提供的锁机制,如果是InnoDB,就会是行锁(也只有数据库层提供的锁机制才能 真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系 统不会修改数据)。
优点:实现分布式锁。
缺点:在事务执行过程中,锁住对应记录,如果事务执行时间比较长,这样锁住资源太久,太浪费了。
乐观锁
乐观锁主要是为了解决悲观锁的问题,不会在事务中锁住数据,只要加一个version,在update数据的时候version +1,然后保存到数据库。这样程序执行第二次update的方法时会返回false,因为version变了。
例如:
- 假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。
- 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。
- 在操作员 A 操作的过程中,操作员 B 也读入此用户信息( version=1 ),并 从其帐户余额中扣除 $20 ( $100-$20 )。
- 操作员 A 完成了修改工作,将数据版本号加一( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
- 操作员 B 完成了操作,也将版本号加一( version=2 )试图向数据库提交数据( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,数据库记录当前版本也为 2 ,要求现在保存数据的版本必须大于数据库现在版本,因此,操作员 B 的提交被驳回。
- 这样,就避免了操作员 B 用基于version=1 的旧数据修改的结果覆盖操作 员 A 的操作结果的可能。
优点:乐观锁机制避免了长事务中的数据库加锁开销(操作员 A和操作员 B 操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系 统整体性能表现。
缺点:需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局 限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户 余额更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在 系统设计阶段,我们应该充分考虑到这些情况出现的可能性。
优化方案:将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途 径,而不是将数据库表直接对外公开。
注意
Hibernate中同时实现了悲观锁和乐观锁。
//Hibernate悲观锁实现 String hqlStr ="from TUser as user where user.name='Erica'"; Query query = session.createQuery(hqlStr); query.setLockMode("user",LockMode.UPGRADE); // 加锁 List userList = query.list();// 执行查询,获取数据
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.xiaohao.test"> <!--乐观锁就靠optimistic-lock这个配置,并且表中有version这个字段--> <class name="User" table="user" optimistic-lock="version" > <id name="id"> <generator class="native" /> </id> <!--version标签必须跟在id标签后面--> <version column="version" name="version" /> <property name="userName"/> <property name="password"/> </class> </hibernate-mapping>
服务器能够承受请求的能力
首先,每个服务器能够承受的请求个数是有限的,太多的时候,服务器会出来不过来,一般单台服务器,能够承受400的并发量,但是这远远满足不了需求。
解决方法:
- 能使用静态页面的地方尽量使用
- 负载均衡
- 控制关键请求同时触发的量
使用静态页面代替动态页面
- 尽量减少动态页面(jsp、asp)的使用:这种方法就是减少jsp的使用,减少服务器对动态页面的解析,也减少启动服务时,占用jvm的内存。