解决web环境中并发问题3种可选方案
2012-09-25 13:30
429 查看
造成并发问题的根源:多个session更改同一条数据,造成数据的不一致性;例如A,B同时查询出1000元,A从账户中取走800元,更新;此时由于B查询出的是1000,他也从账户中取走800,更新,最后账户里面得到B更新的数据,200,账户余额错误!
1.在程序中利用线程同步机制解决并发:相当于让线程排队进入一段事务,保证数据的完整性;
同样的道理,也可以使用synchronized代码块,例如:
或者,在addOrUpdateAcl方法上添加synchronized关键字,原理都是一样的;但是在addOrUpdateAcl方法中不能添加使用代码块,因为在这个程序中,事务是由spring管理的,事务的边界在addOrUpdateAcl方法上,如果在这个方法中添加代码块,并不能保证这个事务完整的结束;
此种方案优势很明显,解决方案相当简单,依靠java自身语言机制,保证一个事务完整的结束;但缺点同样很致命:1.只部署在单台服务器上,如果是多台服务器分布式的话,根本没有办法用程序的方式控制事务;2.排队等待时间比较长,排队本身就限制了并发的效率问题,如果一个事务跨度比较大,严重影响并发效率;
2.从数据库的角度控制并发:
a).使用mysql的排他锁,select * for update;对应hibernate中的悲观锁,但此时必须借助异常处理,使用悲观所,很容易造成数据库端死锁,让程序多尝试几次;
在加载权限时,需要添加hibernate UPGREADE悲观锁机制加载:
这个方案优势很明显了,多台服务器并发不存在问题了,因为数据库端使用了排他锁(mysql);但缺点是目前对数据库的性能有一定的影响;
b) 使用乐观锁:此时需要在权限实体上添加一个字段,version,这个version可为timestamp,int等类型;同时需要在配置文件中声明;
//Acl实体:
Acl.cfg.xml配置文件:
这个方案应该是比较满意的方案了,数据库端在更新时才添加锁,不会像悲观锁那样在查询的时候就加锁了,缩短了事务跨度,提高了并发,hibernate仅在更新时才对比数据时间戳,同时能在多台服务器上部署;
1.在程序中利用线程同步机制解决并发:相当于让线程排队进入一段事务,保证数据的完整性;
private static ReentrantLock lock = new ReentrantLock();//可重入锁,必须静态,全局的 //此方法必须考虑解决并发问题;很经典 public String grantOrRevoke() throws Exception{ /** * 在程序中解决此处权限分配并发的方案:(方案一) * 1.在Action里面添加锁,排队进入addOrUpdateAcl方法,保证一个事务结束,不会出现并发的问题;需要注意的问题是ReentrantLock必须是类变量, * 不能是实例变量,因为Action是prototype类型的 * 2.在addOrUpdateAcl方法添加synchronized关键字,将其声明为线程安全的,这这个事务也能保证完整性;此处需要注意的是:不能在addOrUpdateAcl * 方法里面添加锁,如果在里面添加锁,则不能保证一个事务完整结束 */ /** * 感叹一下:如果在只有一台服务器的情况下,不需要考虑多台web服务器同时对数据库并发,这个解决方案比较简单,完全不需要对数据库进行额外的事务控制,不需要添加排他锁,共享锁之类的【hibernate中类似于悲观锁,乐观锁】;测试并发的时候很完美 */ //1.可重入锁 lock.lock();//保证每次只有一个线程进入,这样就保证了事务的完整性;很安全;此时数据库可以不添加锁的机制 try{ serviceFactory.createAcl().addOrUpdateAcl(acl.getRole().getId(), acl.getModule().getId(), permission, permit); }finally{ lock.unlock(); } return null; }
同样的道理,也可以使用synchronized代码块,例如:
private static Object syn_block_lock = new Object(); public String grantOrRevoke() throws Exception{ synchronized(syn_block_lock){ serviceFactory.createAcl().addOrUpdateAcl(acl.getRole().getId(), acl.getModule().getId(), permission, permit); } return null; }
或者,在addOrUpdateAcl方法上添加synchronized关键字,原理都是一样的;但是在addOrUpdateAcl方法中不能添加使用代码块,因为在这个程序中,事务是由spring管理的,事务的边界在addOrUpdateAcl方法上,如果在这个方法中添加代码块,并不能保证这个事务完整的结束;
此种方案优势很明显,解决方案相当简单,依靠java自身语言机制,保证一个事务完整的结束;但缺点同样很致命:1.只部署在单台服务器上,如果是多台服务器分布式的话,根本没有办法用程序的方式控制事务;2.排队等待时间比较长,排队本身就限制了并发的效率问题,如果一个事务跨度比较大,严重影响并发效率;
2.从数据库的角度控制并发:
a).使用mysql的排他锁,select * for update;对应hibernate中的悲观锁,但此时必须借助异常处理,使用悲观所,很容易造成数据库端死锁,让程序多尝试几次;
public static final int ERROR_TRY_COUNT = 5;//出错尝试次数 //此方法必须考虑解决并发问题;很经典 public String grantOrRevoke() throws Exception{ log.debug("ajax invoked grantOrRevoke ...在数据库端使用可排它锁(对应hibernate悲观锁)方案2解决并发问题(数据库方案)_2"); /** * 在数据库端控制并发解决方案: * 1.使用数据库的排他锁,select * for update,这个语句在查询时就对数据进行添加锁,其他事务必须等这个事务结束了才能访问数据;对应hibernate的悲观锁;同时增加role_id,module_id共同约束条件;使用排他锁很容易造成死锁,所以应该 * 对错误进行处理,增加尝试次数; 由于在addOrUpdateAcl的方法中如果出错,则根据spring的事务管理机制,这个事务会回滚,所以这个事务回滚了;然后再次进行尝试,直到成功;超过三次尝试后,权限分配失败 * 2.使用乐观锁解决,配合服务器程序和数据库的记录时间戳,每次更新时检查当前时间戳是否和数据库的时间戳相同,如果不相同,则重新查询最新记录,再次更新 */ int errorCount = 0;//出错三次后,不再尝试 //必须加锁,保证一条记录对应四个权限 //1.使用hibernate悲观锁解决;查询的时候使用加锁查询;select * for update do{ try{ serviceFactory.createAcl().addOrUpdateAcl(acl.getRole().getId(), acl.getModule().getId(), permission, permit); break;//执行成功,则跳出 }catch(Exception e){ log.debug("添加或者修改失败,再次尝试:errorCount=" + errorCount); errorCount ++; Thread.currentThread().sleep(1);//休息1毫秒,如果不添加这个,失败的次数多了 } }while(errorCount < ERROR_TRY_COUNT);//继续尝试更新 log.error("分配失败!"); return null; }
在加载权限时,需要添加hibernate UPGREADE悲观锁机制加载:
@Override public Acl findByRoleIdAndModuleId(int roleId, int moduleId, boolean addLock) { //这个地方必须防止并发,必须使用锁机制 Session session = sessionFactory.getCurrentSession(); Query q = session.createQuery("from " + entityClass.getSimpleName() + " o where o.role.id =:roleId and o.module.id =:moduleId"); q.setInteger("roleId", roleId); q.setInteger("moduleId", moduleId); if(addLock){ q.setLockMode("o", LockMode.PESSIMISTIC_WRITE); //hibernate4替换了hibernate3的 LockMode.UPGRADE } return (Acl) q.uniqueResult(); }
这个方案优势很明显了,多台服务器并发不存在问题了,因为数据库端使用了排他锁(mysql);但缺点是目前对数据库的性能有一定的影响;
b) 使用乐观锁:此时需要在权限实体上添加一个字段,version,这个version可为timestamp,int等类型;同时需要在配置文件中声明;
//Acl实体:
package com.train.admin.acl.bean; import java.io.Serializable; import java.sql.Timestamp; import java.util.Date; import com.train.admin.module.bean.Module; import com.train.admin.role.bean.Role; public class Acl implements Serializable { public static final int PERMISSION_C = 0;//添加 public static final int PERMISSION_R = 1;//查询 public static final int PERMISSION_U = 2;//更新 public static final int PERMISSION_D = 3;//删除 private int id; private Role role; private Module module; private int aclState; private Timestamp version = new Timestamp((new Date()).getTime()); /** * @param permission 权限标识符, CRUD * @param permit 是否允许;true允许,false不允许 */ public void setPermission(int permission, boolean permit){ int temp = 1; temp = temp << permission; if(permit){//授予权限 aclState = aclState | temp;//或 }else{//去掉权限 aclState = aclState ^ temp;//异或 } } /** * @param permission 权限标识符,CRUD * @return 是否拥有对应权限 */ public boolean hasPermission(int permission){ int temp = 1; aclState = aclState >> permission; temp = aclState & temp; return temp == 1 ? true : false; }//省略get,set方法}
Acl.cfg.xml配置文件:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- Mapping file autogenerated by MyEclipse Persistence Tools --> <hibernate-mapping> <class name="com.train.admin.acl.bean.Acl" table="t_acl" optimistic-lock="version" catalog="train_ticket"> <id name="id" type="java.lang.Integer"> <column name="id" /> <generator class="native"></generator> </id> <timestamp name="version" column="version"></timestamp><!--必须在ID属性后面--> <many-to-one name="role" class="com.train.admin.role.bean.Role" fetch="select"> <column name="role_id" not-null="true" /> </many-to-one> <many-to-one name="module" class="com.train.admin.module.bean.Module" fetch="select"> <column name="module_id" not-null="true" /> </many-to-one> <property name="aclState" type="java.lang.Integer"> <column name="acl_state" not-null="true" /> </property> </class> </hibernate-mapping>处理代码和悲观所基本不变,添加错误处理机制;
public static final int ERROR_TRY_COUNT = 5;//出错尝试次数 //此方法必须考虑解决并发问题;很经典 public String grantOrRevoke() throws Exception{ log.debug("ajax invoked grantOrRevoke ...在数据库端使用可排它锁(对应hibernate悲观锁)方案2解决并发问题(数据库方案)_2"); /** * 在数据库端控制并发解决方案: * 1.使用数据库的排他锁,select * for update,这个语句在查询时就对数据进行添加锁,其他事务必须等这个事务结束了才能访问数据;对应hibernate的悲观锁;同时增加role_id,module_id共同约束条件;使用排他锁很容易造成死锁,所以应该 * 对错误进行处理,增加尝试次数; 由于在addOrUpdateAcl的方法中如果出错,则根据spring的事务管理机制,这个事务会回滚,所以这个事务回滚了;然后再次进行尝试,直到成功;超过三次尝试后,权限分配失败 * 2.使用乐观锁解决,配合服务器程序和数据库的记录时间戳,每次更新时检查当前时间戳是否和数据库的时间戳相同,如果不相同,则重新查询最新记录,再次更新 */ int errorCount = 0;//出错三次后,不再尝试 //必须加锁,保证一条记录对应四个权限 //2.使用hibernate乐观锁实现,需要添加一个字段,version,用来标识hibernate更新时对比的时间戳 do{ try{ serviceFactory.createAcl().addOrUpdateAcl(acl.getRole().getId(), acl.getModule().getId(), permission, permit); break;//执行成功,则跳出 }catch(Exception e){ log.debug("添加或者修改失败,再次尝试:errorCount=" + errorCount); errorCount ++; Thread.currentThread().sleep(5);//休息5毫秒,如果不添加这个,失败的次数多了 } }while(errorCount < ERROR_TRY_COUNT);//继续尝试更新 return null; }
这个方案应该是比较满意的方案了,数据库端在更新时才添加锁,不会像悲观锁那样在查询的时候就加锁了,缩短了事务跨度,提高了并发,hibernate仅在更新时才对比数据时间戳,同时能在多台服务器上部署;
相关文章推荐
- Web网站缓存文件并发问题解决方案
- Web网站缓存文件并发问题解决方案
- Web网站缓存:Web网站缓存文件的并发问题解决方案
- 应用并发问题解决方案
- 第二篇、倾力总结40条常见的移动端Web页面问题解决方案
- Selenium Webdriver学习记录(一):环境搭建(Java+Maven+Eclipse+Selenium3.x)+第一个测试demo+部分问题解决
- 倾力总结40条常见的移动端Web页面问题解决方案
- 启动64位IDEA2016版本需要Java JDK 1.8版本,开发环境JDK 1.7问题解决方案(新增linux下配置)
- HBase环境搭建60010端口无法访问问题解决方案
- vue-cli开发环境跨域问题解决方案
- Excel在.Net 环境下Web方式下驻留内存问题的解决
- MySQL中SELECT+UPDATE处理并发更新问题解决方案分享
- 40条常见的移动端Web页面问题解决方案
- web.xml中taglib标签报错问题解决方案
- 【服务器搭建环境配置】mysql安装 问题解决方案
- redis下并发问题解决方案
- WebSphere Application Server 更新web.xml的问题以解决有些应用在tomcat环境下可以跑,更新到was上失效的问题
- iOS5 XCode4.2环境下iPhone3G和3GS调试问题解决方案
- MySQL中SELECT+UPDATE处理并发更新问题解决方案分享
- MySQL中SELECT+UPDATE处理并发更新问题解决方案分享