RedisClient.java 12.5 KB
Newer Older
苗卫卫 committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
package com.starcharge.base.redis;

import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * Redis工具包
 * 依赖Spring自带的RedisTemplate,可根据配置更换Redis客户端Lettuce/Jedis
 *
 * @author kevin
 */
@Component
public class RedisClient {

	@Resource
	private RedisTemplate<String, Object> redisTemplate;

	private static final Logger logger = LoggerFactory.getLogger(RedisClient.class);

	private static final String REDIS_LOCK_PREFIX = "tmp:lock:";

	private static final String LOCK_LUA = "if redis.call('get',KEYS[1]) then return 0 else " +
			"if redis.call('set',KEYS[1],ARGV[1]) then if redis.call('expire',KEYS[1],ARGV[2]) " +
			"then return 1 else return 0 end else return 0 end end";

	private static final String RELEASE_LOCK_LUA = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

	@SuppressWarnings("unchecked")
	public <T> RedisTemplate<String, T> getRedisTemplate(Class<T> clz) {
		return (RedisTemplate<String, T>) redisTemplate;
	}

	/*********************************key-value*************************************/
	
	public void put(final String key, final Object value) {
		put(key, value, null);
	}

	/**
	 * 移除key后重新放值
	 * 
	 * @param key
	 * @param value
	 */
	public void removePut(final String key, final Object value) {

		remove(key);

		put(key, value, null);
	}

	/**
	 * 塞入string
	 * 
	 * @param key
	 * @param value
	 * @param expire
	 */
	public void put(final String key, final Object value, final Integer expire) {
		redisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS);
	}

	public <T> T get(final String key, Class<T> clz) {
		return get(key, clz, null);
	}

	public String get(final String key) {
		return get(key, String.class, null);
	}

	@SuppressWarnings("unchecked")
	public <T> T get(final String key, Class<T> clz, final Integer expire) {

		if (expire != null) {
			redisTemplate.expire(key, expire, TimeUnit.SECONDS);
		}

		return (T) redisTemplate.opsForValue().get(key);
	}
	
	@SuppressWarnings("unchecked")
	public <T> List<T> multiGet(final List<String> keys, Class<T> clz) {
		return (List<T>)redisTemplate.opsForValue().multiGet(keys);
	}
	
	/*********************************key-hash*************************************/
	
	public void putHash(final String key, final Object hashKey, final Object value) {
		putHash(key, hashKey, value, null);
	}
	
	public void putHash(final String key, final Object hashKey, final Object value, final Integer expire) {
		redisTemplate.opsForHash().put(key, hashKey, value);

		if (expire != null) {
			redisTemplate.expire(key, expire, TimeUnit.SECONDS);
		}
	}
	
	public void putHash(final String key, Map<String, Object> mapValue) {
		putHash(key, mapValue, null);
	}
	
	public void putHash(final String key, Map<String, Object> mapValue, final Integer expire) {
		redisTemplate.opsForHash().putAll(key, mapValue);

		if (expire != null) {
			redisTemplate.expire(key, expire, TimeUnit.SECONDS);
		}
	}
	
	public void deleteHashKey(final String key, Object... hashKeys) {
		redisTemplate.opsForHash().delete(key, hashKeys);
	}
	
	
	/*********************************key-set (无序,不重复)*************************************/
	public void putSet(final String key, final Object value) {
		if(value instanceof Set) {
			putSet(key, ((Set)value).toArray());
		}else {
			putSet(key, new Object[] {value});
		}
	}
	
	public void putSet(final String key, final Object... values) {
		redisTemplate.opsForSet().add(key, values);
	}

	@SuppressWarnings("unchecked")
	public <T> T getSet(final String key, Class<T> clz) {
		return (T)redisTemplate.opsForSet().pop(key);
	}
	

	@SuppressWarnings("unchecked")
	public <T> List<T> members(final String key, Class<T> clz, final Integer expire) {

		if (expire != null) {
			redisTemplate.expire(key, expire, TimeUnit.SECONDS);
		}

		return (List<T>)redisTemplate.opsForSet().members(key);
	}
	
	/*********************************key-list (有序,可重复)*************************************/
	
	public void rightPush(final String key, final Object value) {
		redisTemplate.opsForList().rightPush(key, value);
	}
	
	public void rightPushAll(final String key, final List values) {
		redisTemplate.opsForList().rightPushAll(key, values);
	}
	
	public void rightPushAll(final String key, final Object... values) {
		redisTemplate.opsForList().rightPushAll(key, values);
	}
	
	public void leftPush(final String key, final Object value) {
		redisTemplate.opsForList().leftPush(key, value);
	}
	
	@SuppressWarnings("unchecked")
	public <T> T leftPop(final String key, Class<T> clz) {
		return (T)redisTemplate.opsForList().leftPop(key);
	}
	
	@SuppressWarnings("unchecked")
	public <T> T rightPop(final String key, Class<T> clz) {
		return (T)redisTemplate.opsForList().rightPop(key);
	}
	

	/**
	 * 得到key数据结构未list的数据,带分页支持
	 */
	public <T> List<T> findList(String key, Class<T> clz, Integer startRow, Long endRow) {
		ListOperations<String, T> operations = getRedisTemplate(clz).opsForList();
		return operations.range(key, startRow - 1, endRow - 1);

	}
	
	public Long getListTotal(String key) {
		return getRedisTemplate(Long.class).opsForList().size(key);
	}

	public void expire(String key, long expire) {
		redisTemplate.expire(key, expire, TimeUnit.SECONDS);
	}
	
	/**
	 * 删除某个key
	 * 
	 * @param key
	 */
	public void remove(String key) {
		redisTemplate.delete(key);
	}

	public void del(String key) {
		remove(key);
	}

	/**
	 * 模糊删除key
	 * 
	 * @param key
	 */
	public void clear(String key) {

		Set<String> keys = redisTemplate.keys(key);
		if (!keys.isEmpty()) {
			redisTemplate.delete(keys);
		}
	}

	public Long getAtomicLong(String key) {
		return getAtomicLong(key, 1);
	}
	
	public Long getAtomicLong(String key, long delta) {
		return getRedisTemplate(Long.class).boundValueOps(key).increment(delta);
	}

	public Long getAtomicLong(String key, String hashKey) {
		return getAtomicLong(key, hashKey, 1);
	}
	
	public Long getAtomicLong(String key, String hashKey, long delta) {
		return getRedisTemplate(Long.class).boundHashOps(key).increment(hashKey, delta);
	}

	/**
	 * 获取redis锁,默认重试1s
	 * @param lockId
	 * @param requestId
	 * @param expire
	 * @return
	 */
	public Boolean getLockRetry(String lockId, String requestId, long expire) {
		return getLockRetry(lockId, requestId, expire, 1000);
	}

	/**
	 * 获取redis锁,获取失败则等待200~300毫秒进行重试,可配置超时时间
	 * @param lockId
	 * @param requestId
	 * @param expire
	 * @param retryTimeout 重试超时时间,根据资源的消耗进行配置
	 * @return
	 */
	public Boolean getLockRetry(String lockId, String requestId, long expire, long retryTimeout) {
		return getLockRetry(lockId, requestId, expire, retryTimeout, 200+ RandomUtils.nextInt(0, 100));
	}

	/**
	 * 重试获取锁
	 * @param lockId
	 * @param requestId
	 * @param expire
	 * @param retryTimeout	重试时长
	 * @param retryInterval	重试时间间隔
	 * @return
	 */
	public Boolean getLockRetry(String lockId, String requestId, long expire, long retryTimeout, long retryInterval) {
		//请求时间
		long requestTime=System.currentTimeMillis();
		while(System.currentTimeMillis()-requestTime < retryTimeout){
			if(lock(lockId, requestId, expire)) {
				return Boolean.TRUE;
			}

			try {
				TimeUnit.MILLISECONDS.sleep(retryInterval);
			} catch (InterruptedException e) {
				e.printStackTrace();
				logger.error("线程等待出现异常"+e.getMessage(), e);
			}
		}
		return Boolean.FALSE;
	}

	/**
	 * 利用redis进行锁
	 * boolean result = redisTemplate.boundValueOps(key).setIfAbsent(0);
	 * if (result) {
	 *		redisTemplate.expire(key, expire, TimeUnit.SECONDS);
	 * }
	 * 上面的方式不具有原子性,系统异常时会导致加锁了但是未设置失效时间,导致死锁
	 *
	 * 在没有锁值传入的情况下,最好返回定义的锁值,便于后期释放锁
	 * 推荐{@link RedisClient#lock(String, String, long)} 或者 {@link RedisClient#lock(String, long)}
	 */
	public boolean getLock(String lockId, long expire) {
		return redisTemplate.boundValueOps(getLockKey(lockId)).setIfAbsent(0, expire, TimeUnit.SECONDS);
	}

	/**
	 * 当前方法仅适用自动释放锁的情况
	 * @param lockId
	 * @param expire
	 * @return
	 */
	public boolean getLockAutoRelease(String lockId, long expire) {
		return StringUtils.isNotBlank(lock(lockId, expire));
	}

	public String getLockKey(String lockId) {
		return REDIS_LOCK_PREFIX + lockId;
	}

	/**
	 * 获取锁,返回锁值
	 * @param lockId
	 * @param expire
	 * @return
	 */
	public String lock(String lockId, final long expire) {
		String value=UUID.randomUUID().toString();
		if(lock(lockId, value, expire)){
			return value;
		}
		return null;
	}

	/**
	 * 获取锁
	 * @param lockId	锁的key
	 * @param requestId UUID,用于释放当前线程的锁
	 * @param expire
	 * @return
	 */
	public Boolean lock(String lockId, final String requestId, final long expire) {
		final String key = getLockKey(lockId);

		return redisTemplate.execute(new RedisCallback<Boolean>() {
			@Override
			public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
				try {
					return connection.set(key.getBytes("UTF-8"), requestId.getBytes("UTF-8"), Expiration.seconds(expire), RedisStringCommands.SetOption.SET_IF_ABSENT);
				}catch (Exception e) {
					logger.error("获取redis锁出现异常", e);
				}
				return false;
			}
		});
	}

	/**
	 * 如果当前线程的锁已经释放/失效,拿到的锁值可能不是当前线程存储的值
	 * @param lockId
	 * @return
	 */
	public String getLockValue(String lockId) {
		final String key = getLockKey(lockId);
		return redisTemplate.execute(new RedisCallback<String>() {
			@Override
			public String doInRedis(RedisConnection connection) throws DataAccessException {
				try {
					return new String(connection.get(key.getBytes("UTF-8")),"UTF-8");
				}catch (Exception e) {
					logger.error("获取redis锁requestId出现异常", e);
				}
				return null;
			}
		});
	}

	/**
	 * 释放锁,有可能因为持锁之后方法执行时间大于锁的有效期,此时有可能已经被另外一个线程持有锁,所以不能直接删除
	 * 需要判断加锁的requestId与当前锁的requestId是否一致,一致则释放
	 * @param lockId
	 * @param requestId 获取锁成功后调用getLockRequestId获取requestId,释放锁时传入requestId
	 * @return
	 */
	public boolean releaseLock(String lockId, String requestId) {
		if (StringUtils.isBlank(lockId) || StringUtils.isBlank(requestId)) {
			return false;
		}
		final String key = getLockKey(lockId);
        try {
            // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
            // spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本
        	RedisCallback<Boolean> callback = (connection) -> {
        		try {
        			return connection.eval(RELEASE_LOCK_LUA.getBytes("UTF-8"), ReturnType.BOOLEAN, 1, key.getBytes("UTF-8"),  requestId.getBytes("UTF-8"));
        		}catch (Exception e) {
        			logger.error("执行释放锁出现异常", e);
					return Boolean.FALSE;
				}
        	};
        	return redisTemplate.execute(callback);
            
        } catch (Exception e) {
            logger.error("release lock occured an exception", e);
        }
        return false;
    }
	
	public boolean isLock(String lockId) {
		final String key = getLockKey(lockId);
		return redisTemplate.execute(new RedisCallback<Boolean>() {
			@Override
			public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
				try {
					return connection.get(key.getBytes("UTF-8"))==null;
				}catch (Exception e) {

				}
				return false;
			}
		});
	}
}