目前很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们:“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足其中两项”。所以,很多系统在设计之初就要对这三者进行取舍。在互联网领域的绝大多数的场景中,都需要牺牲系统的强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间在用户的可接受范围之内即可。
我们为了保证数据的最终一致性,需要很多技术方案来支持,比如分布式事务、分布式锁等。针对分布式锁的实现,目前主要有三种实现方案,一是基于数据库(oracle、mysql)实现分布式锁;二是基于缓存(redis、memcached)实现分布式锁;三是基于Zookeeper实现分布式锁。下面实现的是基于Redis单机版实现的分布式锁,适合并发量不是很大的中小型项目。
一、实现分布式锁所需要的pom依赖
5.0.0.RELEASE
1.8.11
2.9.0
1.6.0.RELEASE
org.springframework
spring-orm
${spring.version}
org.springframework
spring-webmvc
${spring.version}
org.aspectj
aspectjweaver
${aspectj.version}
redis.clients
jedis
${jedis.version}
org.springframework.data
spring-data-redis
${spring.data.redis.version}
二、添加spring测试文件applicationContext-test-redis.xml
此文件添加目录为:src/main/resources/spring/applicationContext-test-redis.xml
三、添加测试类 JedisTest
@Component
public class JedisTest {
@Test
public void example1() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/applicationContext-test-redis.xml");
JedisConnectionFactory jedisConnectionFactory = (JedisConnectionFactory) context.getBean("jedisConnectionFactory");
Field jedisField = ReflectionUtils.findField(JedisConnection.class, "jedis");
ReflectionUtils.makeAccessible(jedisField);
System.out.println(jedisConnectionFactory.getConnection());
Jedis jedis = (Jedis) ReflectionUtils.getField(jedisField, jedisConnectionFactory.getConnection());
String result = jedis.set("test-key", "Hello world-", "NX", "EX", 100);
System.out.println(result);
//代码执行后,返回字符串”OK”或者”null”,表示是否设值成功。
}
}
测试成功后就可以在项目中进行代码部署了,示例如下:
四、实现分布式的工具类
import redis.clients.jedis.Jedis;
import java.util.Collections;
/**
* author Alex
* date 2018/01/22
* description 一个用于获取redis分布式锁的工具类,适用于单机部署的redis
* Redis如果是多机部署的,那么可以尝试使用Redisson实现分布式锁,这是Redis官方提供的Java组件
* Redisson的github链接:https://github.com/redisson/redisson
* Redisson的使用示例链接:https://blog.csdn.net/u014042066/article/details/72778440
*/
public class RedisDistributedLockUtil {
private static final String LOCK_SUCCESS = "OK";
private static final Long RELEASE_SUCCESS = 1L;
//意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作
private static final String SET_IF_NOT_EXIST = "NX";
//意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定
private static final String SET_WITH_EXPIRE_TIME = "EX";
/**
* 获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁的keyName
* @param requestId 请求标识,可以使用UUID,为了确认在分布式环境下是哪个客户端加的锁,同时也用于解锁
* @param expireTime 过期时间,单位(秒)
* @return 是否获取成功
*/
public static boolean getDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
//加锁
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
/**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁的keyName
* @param requestId 请求标识,使用加锁时生成的UUID
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
//Lua脚本代码,KEYS[1]使用lockKey参数,ARGV[1]使用requestId参数
//Lua脚本含义:首先获取锁的value值,看是否与请求的标识相匹配,如果匹配则删除锁,否则就返回0
//Lua脚本代码在执行时将会被当作一个命令来执行,并且直到eval命令执行完成,才会执行其他命令,它具有原子性
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//eval()方法是将Lua代码交给Redis服务端执行
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
}
五、实现分布式锁的伪代码
@Autowired
private JedisConnectionFactory jedisConnectionFactory;
public void handleService() throws Exception {
Jedis jedis = null;
try {
//通过反射获取Jedis连接对象
Field jedisField = ReflectionUtils.findField(JedisConnection.class, "jedis");
//将该字段设置为可访问,主要是针对private字段
ReflectionUtils.makeAccessible(jedisField);
jedis = (Jedis) ReflectionUtils.getField(jedisField, jedisConnectionFactory.getConnection());
//获取请求客户端标识
String requestId = UUID.randomUUID().toString();
//设置超时时间
long timeOut = System.currentTimeMillis() + 3 * 1000;
//更新缓存数据之前先获取Redis分布式锁,如果获取到Redis分布式锁则进行更新,否则就循环获取锁直到超时
while (true) {
//获取分布式锁并设置锁的自动过期时间为2秒
boolean distributedLock = RedisDistributedLockUtil.getDistributedLock(
jedis, RedisKeyNameEnum.TEL_TIP_DISTRIBUTED_LOCK.getFactValue(), requestId, 2);
if (distributedLock) {
//进行业务处理....
//处理结束后要释放锁
boolean b = RedisDistributedLockUtil.releaseDistributedLock(
jedis, RedisKeyNameEnum.TEL_TIP_DISTRIBUTED_LOCK.getFactValue(), requestId);
if (b) {
System.out.println("成功释放Redis分布式锁!");
} else {
System.out.println("释放Redis分布式锁失败,等待锁自动超时.........");
}
//业务处理结束后要中止循环
break;
} else {
//超时还未获取到锁则中止循环
if (System.currentTimeMillis() > timeOut) {
System.out.println("获取Redis分布式锁超时,可能是连接异常,循环中止..........");
break;
}
System.out.println("获取Redis分布式锁失败,休眠100毫秒再重新尝试获取锁,休眠中..........");
//获取分布式锁失败休眠100毫秒再重新获取锁
Thread.sleep(100);
}
}
} finally {
//异常抛给上层应用去处理,这里只进行关闭资源的操作处理
if (null != jedis) {
jedis.close();
}
}
}
六、项目中配置Redis的缓存管理器
七、进行redis业务操作的工具类RedisUtil
package com.mengfei.redis;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class RedisUtil {
private RedisTemplate redisTemplate;
public void setRedisTemplate(RedisTemplate redisTemplate) {
RedisSerializer redisSerializer = new JdkSerializationRedisSerializer();
RedisSerializer redisSerializerString = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializerString);
redisTemplate.setValueSerializer(redisSerializer);
this.redisTemplate = redisTemplate;
}
public RedisTemplate getRedisTemplate() {
return redisTemplate;
}
//=============================common============================
public Set getKeys(String key){
if(key == null || "".equals(key)){
return null;
}
Set r = redisTemplate.keys(key);
return r;
}
/**
* 批量获取redis string
* @param keys
* @return
*/
public List batchGetString(Collection keys ){
if(CollectionUtils.isEmpty(keys)){
return null;
}
RedisSerializer serializer = redisTemplate.getStringSerializer();
List r =redisTemplate.executePipelined(new RedisCallback() {
public Object doInRedis(RedisConnection connection) throws DataAccessException {
byte[][] rawKeys = new byte[keys.size()][];
Iterator it = keys.iterator() ;
int i = 0;
while(it.hasNext()){
String key = it.next();
// return jdkSerializer.deserialize(connection.get(serializer.serialize(key)));
// connection.rPop(serializer.serialize(key));
rawKeys[i++] = serializer.serialize(key);
connection.get(serializer.serialize(key));
}
// connection.mGet(rawKeys);
return null;
}
});
return r;
}
/**
* 批量插入 redis string
* @param list
*/
public boolean batchSetString(List> list) {
boolean result = redisTemplate.execute(new RedisCallback() {
public Boolean doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer serializer = redisTemplate.getStringSerializer();
for (Map map : list) {
Set key = map.keySet();
Iterator it = key.iterator();
while(it.hasNext()) {
String inner = it.next();
byte[] innerKey = serializer.serialize(inner);
byte[] name = serializer.serialize(map.get(inner));
connection.setNX(innerKey, name);
}
}
return true;
}
}, false, true);
return result;
}
/**
* 批量插入 redis string
* @param list
*/
public boolean batchSetHash(List> list,String key) {
boolean result = redisTemplate.execute(new RedisCallback() {
public Boolean doInRedis(RedisConnection connection)
throws DataAccessException {
RedisSerializer serializer = redisTemplate.getStringSerializer();
byte[] keyByte = serializer.serialize(key);
for (Map map : list) {
Set key = map.keySet();
Iterator it = key.iterator();
while(it.hasNext()) {
String inner = it.next();
byte[] innerKey = serializer.serialize(inner);
byte[] name = serializer.serialize(map.get(inner));
connection.hSet(keyByte, innerKey, name);
}
}
return true;
}
}, false, true);
return result;
}
/**
* get cache
*
* @param field
* @return
*/
public byte[] getByte(String field) {
byte[] result = redisTemplate.execute((RedisCallback)
connection -> connection.get(field.getBytes()));
if (result == null) {
return null;
}
return result;
}
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key,long time){
try {
if(time>0){
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key){
return redisTemplate.getExpire(key,TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key){
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String ... key){
if(key!=null&&key.length>0){
if(key.length==1){
redisTemplate.delete(key[0]);
}else{
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
//============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key){
return key==null?null:redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key,Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key,Object value,long time){
try {
if(time>0){
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
}else{
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @return
*/
public long incr(String key, long delta){
if(delta<0){
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @return
*/
public long decr(String key, long delta){
if(delta<0){
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
//================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key,String item){
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map hmget(String key){
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map map){
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map map, long time){
try {
redisTemplate.opsForHash().putAll(key, map);
if(time>0){
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key,String item,Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key,String item,Object value,long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if(time>0){
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item){
redisTemplate.opsForHash().delete(key,item);
}
/**
* 判断hash表中是否有该项的值
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item){
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item,double by){
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item,double by){
return redisTemplate.opsForHash().increment(key, item,-by);
}
//============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
* @return
*/
public Set sGet(String key){
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key,Object value){
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object...values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key,long time,Object...values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if(time>0) expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
* @param key 键
* @return
*/
public long sGetSetSize(String key){
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object ...values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//===============================list=================================
/**
* 获取list缓存的内容
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List lGet(String key,long start, long end){
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
* @param key 键
* @return
*/
public long lGetListSize(String key){
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;
index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key,long index){
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
*
* 功能描述: 值左入列
*
* @param:
* @return:
* @auther: Tian
* @date: 2018/9/20 14:50
*/
public boolean lLPush(String key, Object value) {
try {
redisTemplate.opsForList().leftPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
*
* 功能描述: 值左入列 并设置超时时间
*
* @param:
* @return:
* @auther: Tian
* @date: 2018/9/20 14:51
*/
public boolean lLPush(String key, Object value, long time) {
try {
redisTemplate.opsForList().leftPush(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
*
* 功能描述: 左入列
*
* @param:
* @return:
* @auther: Tian
* @date: 2018/9/20 14:47
*/
public boolean lLPush(String key, List value) {
try {
redisTemplate.opsForList().leftPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
*
* 功能描述: 左入列 带超时时间
*
* @param:
* @return:
* @auther: Tian
* @date: 2018/9/20 14:48
*/
public boolean lLPush(String key, List value, long time) {
try {
redisTemplate.opsForList().leftPushAll(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
*
* 功能描述: 右出队列
*
* @param: [key, value, time]
* @return: boolean
* @auther: Tian
* @date: 2018/9/20 14:52
*/
public Object lRPop(String key) {
try {
return redisTemplate.opsForList().rightPop(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
*
* 功能描述: 右出队列
*
* @param: [key, value, time]
* @return: boolean
* @auther: Tian
* @date: 2018/9/20 14:52
*/
public Object lLPop(String key) {
try {
return redisTemplate.opsForList().leftPop(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据索引修改list中的某条数据
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index,Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key,long count,Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
以上示例仅供参考,如有不对的地方,欢迎指正!
参考:
1、实现基于Redis分布式锁的正确姿势
2、如何从Spring RedisTemplate中获得Jedis实例