看到这样一篇文章如下:
以前为部门内部开发过一个定时器程序,这个定时器很简单,就是配置quartz,来实现定时调用配置的url功能。最近为了防止定时器所在的服务器由于特殊原因挂掉,需要对定时器做多机部署。那么如果按照原来的方式进行部署,就会遇到 在一定的间隔时间内,可能出现多次重复调用的问题。为了解决这个问题,我就借助了redis的分布式锁功能。
redis分布式锁参考 : http://www.jeffkit.info/2011/07/1000/
具体原理如下:
定时器到时间被触发,程序开始先争取一个redis锁。
如果获得锁,就设置锁的超时时间为到下次定时器触发的时间。
然后执行定时器任务。后来的定时器也来尝试获得redis锁,当然,这个锁已不能获取了,而且超时时间在未来,所以就放弃这次任务调用。
定时器到时间再次被触发,然后尝试获得锁,由于锁的超时时间为定时任务的时间间隔,当前时间正好大于或等于超时时间,所以,程序可以顺利的获得锁,并重置超时时间。
。。。。。。。不断的循环调用,判断
在此之间测试循环间隔时间最小单位为1s最好,如果小于1s的调用,由于使用redis会有10几毫秒的运算耗费,因此不能保证在1s以下的时间间隔比较均匀.
为了能保证定时触发时,能获得redis锁,可以设置锁的超时时间为间隔时间-10ms。这样就判断超时时间 now > timeoutTime = true。
补充:
只要多个服务器时间差别不大,基本不会有重复的问题。唯一担心的就是redis,这个挂了,就全挂了。
因此,如果要考虑更全面,需要对redis点单再做集群。就看是否有必要了。
我也遇到了相同问题:
公司两台服务器部署应用,然后使用redis锁,不过使用的是spring-data-redis的哈希表的操作。
//加锁
public boolean addJobLock(final String jobName) {
return getRedisTemplate().execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
String newJobName = JOB_PREFIX + jobName;
final String key = "ALL" + ID_SPLIT + "ALL";
if (((Jedis) connection.getNativeConnection()).hlen(newJobName) == 0L) {
final String keyValue = String.valueOf(System.currentTimeMillis());
return JedisUtil.isSuccess(((Jedis) connection.getNativeConnection()).hset(newJobName, key,
keyValue));
} else {
// 如果存在key,则先取出来,然后看里面的值
long keyTime = getHKeyLong(newJobName, key);
/**
* 如果超时,并重新插入
*/
if ((keyTime + DEFAULT_TIMEOUT) < System.currentTimeMillis()) {
final String keyValue = String.valueOf(System.currentTimeMillis());
((Jedis) connection.getNativeConnection()).hset(newJobName, key, keyValue);
return true;
} else {
return false;
}
}
}
});
}
<pre name="code" class="java"> /**
* 获取Job的有效期
*/
private long getHKeyLong(final String jobName, String key) {
String keyValue = getRedisTemplate().execute(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
String newJobName = JOB_PREFIX + jobName;
final String key = "ALL" + ID_SPLIT + "ALL";
return ((Jedis) connection.getNativeConnection()).hget(newJobName, key);
}
});
if (StringUtils.isNotBlank(keyValue)) {
return Long.parseLong(keyValue);
} else {
return 0L;
}
}
//释放锁
public boolean releaseJobLock(final String jobName) {
return getRedisTemplate().execute(new RedisCallback<Boolean>() {
String newJobName = JOB_PREFIX + jobName;
final String key = "ALL" + ID_SPLIT + "ALL";
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
return JedisUtil.isSuccess(((Jedis) connection.getNativeConnection()).hdel(newJobName, key));
}
});
}
分别在业务开始加锁和释放锁就可以了。
spring下spring-data-redis配置:
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}"/>
<property name="maxActive" value="${redis.maxActive}"/>
</bean>
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="usePool" value="true"/>
<property name="hostName" value="${redis.hostName}"/>
<property name="port" value="${redis.port}"/>
<property name="database" value="${redis.dbIndex}"/>
<property name="poolConfig" ref="jedisPoolConfig"/>
<property name="timeout" value="${redis.timeout}"/>
</bean>
<!-- redis template definition -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory"/>
</bean>