分布式锁组件
业务需求
很多业务上都有对同一个资源并发进行读写访问的场景,又要保证单次访问的正确性和多次访问的一致性,这就要求对资源进行加锁。同时在服务部署可以水平伸缩的情况下,无法做到在单JVM中的加锁控制,所以需要有一个分布式的系统来统一进行加解锁处理。
解决方案
iuap的分布式锁组件是利用Zookeeper的强一致特性,通过Zookeeper来提供分布式锁的能力,解决了在多JVM中对共享资源加锁解锁的需求,并通过集群方式保证整个系统的高可用。本组件实现了对共享资源的互斥加解锁,在同一线程内的重复加锁,以及在业务结束后通过拦截触发的自动解锁功能等。
功能说明
- 支持对共享资源的互斥加解锁;
- 支持线程内可重复加锁功能;
- 支持批量加解锁功能;
- 支持线程结束自动解锁功能。
锁的逻辑实现的流程图如下:
使用说明
配置和使用方式
1:在属性文件中,配置分布式锁组件使用的Zookeeper的连接url和方式,支持单机方式、集群方式
#配置锁组件连接zookeeper的url
zklock.url=127.0.0.1:2181
#zookeeper集群方式配置示例
#zklock.url=20.12.6.58:2181,20.12.6.59:2181,20.12.6.60:2181
#锁存活最大时间,单位秒,如果不配置,不强制删除,如果配置,加锁失败且已有的锁存活时间大于此值,强制删除
zklock.maxlocktime=3600
2:Spring的配置文件中,声明iuap-lock中连接池建立的配置信息的bean,根据项目需要调整参数值
在web.xml中引入iuap-lock对应的spring配置文件,也可以将内部的配置合并到其他的spring配置文件中。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:/applicationContext-lock.xml
</param-value>
</context-param>
主要是配置了连接池的信息,具体配置如下:
<bean id="zkPoolConfig" class="org.apache.commons.pool2.impl.GenericObjectPoolConfig">
<property name="maxTotal" value="100"/>
<property name="maxIdle" value="20"/>
<property name="maxWaitMillis" value="60000"/>
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<property name="numTestsPerEvictionRun" value="-1"/>
<property name="minEvictableIdleTimeMillis" value="30000"/>
</bean>
3:工程中引入对iuap-lock组件的依赖
<dependency>
<groupId>com.yonyou.iuap</groupId>
<artifactId>iuap-lock</artifactId>
<version>${iuap.modules.version}</version>
</dependency>
${iuap.modules.version} 为平台在maven私服上发布的组件的version。
4:初始化连接池
在应用启动时,需要初始化连接池,可以在web.xml中定义listener,在listener中实现初始化,示例如下:
public void contextInitialized(ServletContextEvent event) {
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(event.getServletContext());
ContextHolder.setContext(wac);
GenericObjectPoolConfig config = (GenericObjectPoolConfig)ContextHolder.getContext().getBean("zkPoolConfig");
ZkPool.initPool(config);
}
5:代码中调用组件提供的API,操作分布式锁的加锁和解锁,完成业务
@Test
public void testDisLockCodeExample() throws InterruptedException {
String lockpath = "iuapdislockpath_test";
boolean islocked = false;
try {
islocked = DistributedLock.lock(lockpath);
if(islocked){
System.out.println("加锁2成功,业务处理!");
}
} catch (Exception e) {
System.out.println("加锁2操作异常!");
} finally {
if(islocked){
DistributedLock.unlock(lockpath);
System.out.println("解锁2成功!");
}
}
}
6:Web项目中,可以配置过滤器方式,防止忘记解锁(不强制)
<filter>
<filter-name>lockfilter</filter-name>
<filter-class>com.yonyou.iuap.lock.filter.DistributedLockFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>lockfilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意:url-pattern要按照自己加锁业务的请求路径进行映射,不要使用/*,以免拦截范围过大。
7:更多API操作和配置方式,请参考分布式锁对应的示例工程(DevTool/examples/example_iuap_lock)
API接口
加锁操作
- 功能描述
对敏感、关键的业务数据或资源进行加锁操作,加锁后可以进行业务操作,过程中不会有其他请求加锁成功。
调用方式
boolean islocked = DistributedLock.lock(lockpath);
参数说明
参数 | 类型 | 说明 |
---|---|---|
lockpath | String | 需要锁定资源的唯一标识 |
- 返回值说明
类型 | 说明 |
---|---|
boolean | 加锁成功或者失败的标识 |
解锁操作
- 功能描述
对加锁后的资源进行解锁操作。
调用方式
DistributedLock.unlock(lockpath);
参数说明
参数 | 类型 | 说明 |
---|---|---|
lockpath | String | 需要解锁资源的唯一标识 |
批量加锁操作
- 功能描述
对多个资源同时加锁,批量加锁要么全部加锁成功,要么全部加锁失败。
调用方式
boolean islocked = DistributedLock.lock(locks);
参数说明
参数 | 类型 | 说明 |
---|---|---|
locks | String[] | 需要锁定资源的唯一标识数组 |
- 返回值说明
类型 | 说明 |
---|---|
boolean | 批量加锁成功或者失败的标识 |
批量解锁操作
- 功能描述
对加锁后的资源进行批量解锁操作。
调用方式
DistributedLock.unlock(locks);
参数说明
参数 | 类型 | 说明 |
---|---|---|
locks | String[] | 需要批量解锁资源的唯一标识数组 |