看到這樣一篇文章如下:
以前爲部門內部開發過一個定時器程序,這個定時器很簡單,就是配置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>