亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專(zhuān)欄INFORMATION COLUMN

自定義注解@RedisLock分布式鎖用法及原理

oujie / 3336人閱讀

摘要:前言最近開(kāi)發(fā)公司的項(xiàng)目,遇到了分布式的場(chǎng)景,即,同一條數(shù)據(jù)可能被多臺(tái)服務(wù)器或者說(shuō)多個(gè)線程同時(shí)修改,此時(shí)可能會(huì)出現(xiàn)分布式事務(wù)的問(wèn)題,隨即封裝了分布式鎖的注解。

前言

最近開(kāi)發(fā)公司的項(xiàng)目,遇到了分布式的場(chǎng)景,即,同一條數(shù)據(jù)可能被多臺(tái)服務(wù)器或者說(shuō)多個(gè)線程同時(shí)修改,此時(shí)可能會(huì)出現(xiàn)分布式事務(wù)的問(wèn)題,隨即封裝了redis分布式鎖的注解。

場(chǎng)景分析

前提:我的銀行卡有0元錢(qián),現(xiàn)在有A,B兩個(gè)人,想分別給我轉(zhuǎn)10元錢(qián)
分析:
假如A,B通過(guò)讀數(shù)據(jù)庫(kù),同時(shí)發(fā)現(xiàn)我的余額是0,這時(shí),
線程A,會(huì)給我設(shè)置:
余額 = 10 + 0
線程B,會(huì)給我設(shè)置:
余額 = 10 + 0

最后,我的卡上收到了兩個(gè)人的轉(zhuǎn)賬,但是最后金額居然只有10元??!這是怎么回事?
其實(shí)原因就在于多個(gè)線程,對(duì)一條數(shù)據(jù)同時(shí)進(jìn)行了操作。如果我們可以設(shè)置一下,在修改的方法上面加一個(gè)鎖,每次修改之前,(A)先拿到這個(gè)鎖,再去做修改方法,此時(shí),其他(B)線程想要修改的時(shí)候,看到鎖已經(jīng)不再,需要等待鎖釋放,然后再去執(zhí)行,就保證了A,B先后依此執(zhí)行,數(shù)據(jù)依此累加就沒(méi)問(wèn)題了。

解決辦法

基于代碼的可移植性,我將分布式鎖做成了注解,大家如果有需要,可以直接將jar包拿過(guò)去做相應(yīng)的修改即可,jar包下載地址(鏈接:https://pan.baidu.com/s/1hBn-...
提取碼:1msl):

注解使用說(shuō)明:
1.在需要添加分布式鎖的方法上面加上@RedisLock
如果key不添加,則默認(rèn)鎖方法第一個(gè)參數(shù)param的id字段,如果需要指定鎖某個(gè)字段,則@RedisLock(key = "code")
2.如果方法沒(méi)有參數(shù),則不可使用RedisLock鎖

 @RedisLock
public void updateData( Data param){
    
}

下面詳細(xì)分析一下封裝的源碼:

先看一下項(xiàng)目結(jié)構(gòu)(總共就4個(gè)類(lèi)):

  //RedisLock注解類(lèi):沒(méi)什么好解釋的

  /**
 * Created by liuliang on 2018/10/15.
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisLock {
    //被鎖的數(shù)據(jù)的id
     String key() default "";

     //喚醒時(shí)間
     long acquireTimeout() default 6000L;

     //超時(shí)時(shí)間
     long timeout() default 6000L;
}
 //----------------------類(lèi)分割線---------------------
//RedisService 一個(gè)簡(jiǎn)單的操作redis的類(lèi),封裝了加鎖和釋放鎖的方法

/**
 * Created by liuliang on 2018/10/15.
 */
@Service
public class RedisService {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Resource(name = "stringRedisTemplate")
@Autowired
ValueOperations valOpsStr;

@Autowired
RedisTemplate redisTemplate;

@Resource(name = "redisTemplate")
ValueOperations valOpsObj;

public String getStr(String key) {
    return stringRedisTemplate.opsForValue().get(key);//獲取對(duì)應(yīng)key的value
    //        return valOpsStr.get(key);
}

public void setStr(String key, String val) {
    stringRedisTemplate.opsForValue().set(key,val,1800, TimeUnit.SECONDS);
    //        valOpsStr.set(key, val);
}

public void del(String key) {
    stringRedisTemplate.delete(key);
}


/**
 * 根據(jù)指定o獲取Object
 *
 * @param o
 * @return
 */
public Object getObj(Object o) {
    return valOpsObj.get(o);
}

/**
 *       * 設(shè)置obj緩存
 *       * @param o1
 *       * @param o2
 *
 */
public void setObj(Object o1, Object o2) {
    valOpsObj.set(o1, o2);
}

/**
 * 刪除Obj緩存
 *
 * @param o
 */
public void delObj(Object o) {
    redisTemplate.delete(o);
}


private static JedisPool pool = null;

static {
    JedisPoolConfig config = new JedisPoolConfig();
    // 設(shè)置最大連接數(shù)
    config.setMaxTotal(200);
    // 設(shè)置最大空閑數(shù)
    config.setMaxIdle(8);
    // 設(shè)置最大等待時(shí)間
    config.setMaxWaitMillis(1000 * 100);
    // 在borrow一個(gè)jedis實(shí)例時(shí),是否需要驗(yàn)證,若為true,則所有jedis實(shí)例均是可用的
    config.setTestOnBorrow(true);
    pool = new JedisPool(config, "127.0.0.1", 6379, 3000);
}


DistributedLock lock = new DistributedLock(pool);

/**
 * redis分布式加鎖
 * @param objectId
 * @param acquireTimeout
 * @param timeout
 */
public String redisLock(String objectId,Long acquireTimeout, Long timeout) {
    // 對(duì)key為id加鎖, 返回鎖的value值,供釋放鎖時(shí)候進(jìn)行判斷
    String lockValue = lock.lockWithTimeout(objectId, acquireTimeout, timeout);
    System.out.println(Thread.currentThread().getName() + "獲得了鎖");
    return lockValue;
}


/**
 * 釋放redis分布式鎖
 * @param objectId
 * @param lockValue
 */
public Boolean  releaseLock(String objectId,String lockValue){
    boolean b = lock.releaseLock(objectId, lockValue);
    System.out.println(Thread.currentThread().getName() + "釋放了鎖");
   return b;
}



 //----------------------類(lèi)分割線---------------------
/**
 * Created by liuliang on 2018/10/15.
 *
 * 分布式鎖的主要類(lèi),主要方法就是加鎖和釋放鎖
 *具體的邏輯在代碼注釋里面寫(xiě)的很清楚了
 */
@Slf4j
public class DistributedLock {
private final JedisPool jedisPool;

public DistributedLock(JedisPool jedisPool) {
    this.jedisPool = jedisPool;
}


/**
 * 加鎖
 * @param locaName  鎖的key
 * @param acquireTimeout  獲取超時(shí)時(shí)間
 * @param timeout   鎖的超時(shí)時(shí)間
 * @return 鎖標(biāo)識(shí)
 */
public String lockWithTimeout(String locaName,
                              long acquireTimeout, long timeout) {
    Jedis conn = null;
    String retIdentifier = null;
    try {
        // 獲取連接
        conn = jedisPool.getResource();
        // 隨機(jī)生成一個(gè)value
        String identifier = UUID.randomUUID().toString();
        // 鎖名,即key值
        String lockKey = "lock:" + locaName;
        // 超時(shí)時(shí)間,上鎖后超過(guò)此時(shí)間則自動(dòng)釋放鎖
        int lockExpire = (int)(timeout / 1000);

        // 獲取鎖的超時(shí)時(shí)間,超過(guò)這個(gè)時(shí)間則放棄獲取鎖
        long end = System.currentTimeMillis() + acquireTimeout;
        while (System.currentTimeMillis() < end) {
            log.info("lock...lock...");
            if (conn.setnx(lockKey, identifier) == 1) {
                log.info("==============lock success!=============");
                conn.expire(lockKey, lockExpire);
                // 返回value值,用于釋放鎖時(shí)間確認(rèn)
                retIdentifier = identifier;
                return retIdentifier;
            }
            // 返回-1代表key沒(méi)有設(shè)置超時(shí)時(shí)間,為key設(shè)置一個(gè)超時(shí)時(shí)間
            if (conn.ttl(lockKey) == -1) {
                conn.expire(lockKey, lockExpire);
            }

            try {
                //這里sleep 10ms是為了防止線程饑餓,各位可以思考一下為什么
                Thread.sleep(10);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    } catch (JedisException e) {
        e.printStackTrace();
    } finally {
        if (conn != null) {
            conn.close();
        }
    }
    return retIdentifier;
}


/**
 * 釋放鎖
 * @param lockName 鎖的key
 * @param identifier    釋放鎖的標(biāo)識(shí)
 * @return
 */
public boolean releaseLock(String lockName, String identifier) {
    Jedis conn = null;
    String lockKey = "lock:" + lockName;
    boolean retFlag = false;
    try {
        conn = jedisPool.getResource();
        while (true) {
            // 監(jiān)視lock,準(zhǔn)備開(kāi)始事務(wù)
            conn.watch(lockKey);
            //避免空指針
            String lockKeyValue = conn.get(lockKey)==null?"":conn.get(lockKey);
            // 通過(guò)前面返回的value值判斷是不是該鎖,若是該鎖,則刪除,釋放鎖
            if (lockKeyValue.equals(identifier)) {
                Transaction transaction = conn.multi();
                transaction.del(lockKey);
                List results = transaction.exec();
                if (results == null) {
                    continue;
                }
                log.info("==============unlock success!=============");
                retFlag = true;
            }
            conn.unwatch();
            break;
        }
    } catch (JedisException e) {
        e.printStackTrace();
    } finally {
        if (conn != null) {
            conn.close();
        }
    }
    return retFlag;
}
 //----------------------類(lèi)分割線---------------------
/**
 * Created by liuliang on 2018/10/16.
 這是一個(gè)攔截器,我們指定攔截RedisLock注解
 */
@Aspect
@Component
@Slf4j
public class RedisLockAop {

ThreadLocal beginTime = new ThreadLocal<>();
ThreadLocal objectId = new ThreadLocal<>();
ThreadLocal lockValue = new ThreadLocal<>();

@Autowired
private RedisService redisService;

@Pointcut("@annotation(redisLock)")
public void serviceStatistics(RedisLock redisLock) {
}

@Before("serviceStatistics(redisLock)")
public void doBefore(JoinPoint joinPoint, RedisLock redisLock) {
    // 記錄請(qǐng)求到達(dá)時(shí)間
    beginTime.set(System.currentTimeMillis());
    //注解所在方法名
    String methodName = joinPoint.getSignature().getName();
    //注解所在類(lèi)
    String className = joinPoint.getSignature().getDeclaringTypeName();
    //方法上的參數(shù)
    Object[] args = joinPoint.getArgs();
    String key = redisLock.key();
    if(ObjectUtils.isNullOrEmpty(args)){
        //方法的參數(shù)是空,生成永遠(yuǎn)不重復(fù)的uuid,相當(dāng)于不做控制
        key = methodName +  UUID.randomUUID().toString();
    }else {
        //取第一個(gè)參數(shù)指定字段,若沒(méi)有指定,則取id字段
        Object arg = args[0];
        log.info("arg:"+arg.toString());
        Map map = getKeyAndValue(arg);
        Object o = map.get(StringUtils.isEmpty(key) ? "id" : key);
        if(ObjectUtils.isNullOrEmpty(o)){
            //自定義異常,可以換成自己項(xiàng)目的異常
            throw new MallException(RespCode.REDIS_LOCK_KEY_NULL);
        }
        key = o.toString();
    }
    log.info("線程:"+Thread.currentThread().getName() + ", 已進(jìn)入方法:"+className+"."+methodName);
//        objectId.set(StringUtils.isEmpty(redisLock.key()) ? UserUtils.getCurrentUser().getId() : redisLock.key());
    objectId.set(key);
    String lock = redisService.redisLock(objectId.get(), redisLock.acquireTimeout(), redisLock.timeout());
    lockValue.set(lock);
    log.info("objectId:"+objectId.get()+",lockValue:"+lock +",已經(jīng)加鎖!");
}


@After("serviceStatistics(redisLock)")
public void doAfter(JoinPoint joinPoint,RedisLock redisLock) {
    String methodName = joinPoint.getSignature().getName();
    String className = joinPoint.getSignature().getDeclaringTypeName();
    redisService.releaseLock(objectId.get(),lockValue.get());
    log.info("objectId:"+objectId.get()+",lockValue:"+lockValue.get() +",已經(jīng)解鎖!");
    log.info("線程:"+Thread.currentThread().getName() + ", 已退出方法:"+className+"."+methodName+",耗時(shí):"+(System.currentTimeMillis() - beginTime.get() +" 毫秒!"));
}


//這是一個(gè)Object轉(zhuǎn)mapd的方法
public static Map getKeyAndValue(Object obj) {
    Map map = new HashMap();
    // 得到類(lèi)對(duì)象
    Class userCla = (Class) obj.getClass();
    /* 得到類(lèi)中的所有屬性集合 */
    Field[] fs = userCla.getDeclaredFields();
    for (int i = 0; i < fs.length; i++) {
        Field f = fs[i];
        f.setAccessible(true); // 設(shè)置些屬性是可以訪問(wèn)的
        Object val = new Object();
        try {
            val = f.get(obj);
            // 得到此屬性的值
            map.put(f.getName(), val);// 設(shè)置鍵值
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        /*
         * String type = f.getType().toString();//得到此屬性的類(lèi)型 if
         * (type.endsWith("String")) {
         * System.out.println(f.getType()+"	是String"); f.set(obj,"12") ;
         * //給屬性設(shè)值 }else if(type.endsWith("int") ||
         * type.endsWith("Integer")){
         * System.out.println(f.getType()+"	是int"); f.set(obj,12) ; //給屬性設(shè)值
         * }else{ System.out.println(f.getType()+"	"); }
         */

    }
    System.out.println("單個(gè)對(duì)象的所有鍵值==反射==" + map.toString());
    return map;
}

}

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/71742.html

相關(guān)文章

  • Redis詳解 - SpringBoot整合Redis,RedisTemplate和注解兩種方式的使

    摘要:和注解的方法返回值要一致刪除緩存在需要?jiǎng)h除緩存的方法上加注解,執(zhí)行完這個(gè)方法之后會(huì)將中對(duì)應(yīng)的記錄刪除。代表返回值,意思是當(dāng)返回碼不等于時(shí)不緩存,也就是等于時(shí)才緩存。返回值特定值如果被設(shè)置了如果沒(méi)有被設(shè)置例子自動(dòng)將對(duì)應(yīng)到并且返回原來(lái)對(duì)應(yīng)的。 本文主要講 Redis 的使用,如何與 SpringBoot 項(xiàng)目整合,如何使用注解方式和 RedisTemplate 方式實(shí)現(xiàn)緩存。最后會(huì)給一個(gè)用...

    SexySix 評(píng)論0 收藏0
  • 基于 Redis 的布式

    摘要:首先談到分布式鎖自然也就聯(lián)想到分布式應(yīng)用。如基于的唯一索引?;诘呐R時(shí)有序節(jié)點(diǎn)。這里主要基于進(jìn)行討論。該命令可以保證的原子性。所以最好的方式是在每次解鎖時(shí)都需要判斷鎖是否是自己的。總結(jié)至此一個(gè)基于的分布式鎖完成,但是依然有些問(wèn)題。 showImg(https://segmentfault.com/img/remote/1460000014128437?w=2048&h=1365); 前...

    fasss 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<