<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Spring事務(wù)失效場景

      一、Spring事務(wù)失效場景

      1.1 前言

      身為Java開發(fā)工程師,相信大家對Spring種事務(wù)的使用并不陌生。但是你可能只停留在基礎(chǔ)的使用層面上,在遇到一些比較特殊的場景,事務(wù)可能沒有生效,直接在生產(chǎn)上暴露了,這可能就會導(dǎo)致比較嚴(yán)重的生產(chǎn)事故。今天,我們就簡單來說一下Spring事務(wù)的原理,然后總結(jié)出對應(yīng)的解決方案。

      • 聲明式事務(wù)是Spring 功能中最爽之一,可是有些時(shí)候,我們在使用聲明式事務(wù)并為生效,這是為什么呢?
      • 再次就聊聊聲明式事務(wù)的幾種失效場景。本文將會從以下兩個(gè)方面來說一下事務(wù)為什么會失效?

      1.2 Spring 事務(wù)原理

      大家還記得在JDBC中是如何操作事務(wù)的嗎?偽代碼可能如下:

      //Get database connection
      Connection connection = DriverManager.getConnection();
      //Set autoCommit is false
      connection.setAutoCommit(false);
      //use sql to operate database
      .........
      //Commit or rollback
      connection.commit()/connection.rollback
      
      connection.close();

      需要再各個(gè)業(yè)務(wù)中編寫代碼如 commit() 、close() 來控制事務(wù)。

      但是 Spring 不樂意這么干了,這樣對業(yè)務(wù)代碼侵入性太大了,所有就用一個(gè)事務(wù)注解 @Transaction 來控制事務(wù),底層實(shí)現(xiàn)是基于切面編程 AOP 實(shí)現(xiàn)的,而Spring 中實(shí)現(xiàn) AOP 機(jī)制采用的動態(tài)代理,具體分為 JDK 動態(tài)代理和 CGLib 動態(tài)代理兩種模式。

      1

      1. Spring 的 bean 的初始化過程中,發(fā)現(xiàn)方法有 @Transaction 注解,就需要對相應(yīng)的 Bean 進(jìn)行代理,生成代理對象。
      2. 然后再方法調(diào)用的時(shí)候,會執(zhí)行切面的邏輯,而這里切賣你的邏輯中就包含了開啟事務(wù),提交事務(wù)或者回滾事務(wù)等邏輯。

      另外注意一點(diǎn)的是,Spring 本身不實(shí)現(xiàn)事務(wù),底層還是依賴于數(shù)據(jù)庫的事務(wù)。沒有數(shù)據(jù)庫事務(wù)的支持,Spring 事務(wù)是不會生效的。

      1.3 Spring 事務(wù)失效場景

      1.3.1 拋出檢查異常

      比如你的事務(wù)控制代碼如下:

      @Transactional
      public void transactionTest() throws IOException{
          User user = new User();
          UserService.insert(user);
          throw new IOException();
      }

      如果 @Transactional 沒有特別指定,Spring只會在遇到運(yùn)行時(shí)異常RuntimeException或者error時(shí)進(jìn)行回滾,而 IOException 等檢查異常不會影響回滾。

      public boolean rollbackOn(Throwable ex) {
        	return (ex instanceof RuntimeException || ex instanceof  Error);
      }

      解決方案:

      知道原因后,解決方法也很簡單。配置rollbackFor 屬性,例如:

       @Transactional(rollbackFor = Exception.class)
      1.3.2 業(yè)務(wù)方法本身捕獲了異常
      @Transactional(rollbackFor = Exception.class)
      public void transactionTest() {
          try {
              User user = new User();
              UserService.insert(user);
              int i = 1 / 0;
          }catch (Exception e) {
              e.printStackTrace();
          }
      }

      這種場景下,事務(wù)失敗的原因也很簡單,Spring 是否進(jìn)行回滾時(shí)根據(jù)你是否拋出異常決定的,所以如果你自己捕獲了異常,Spring也無能為力。

      看了上面的代碼,你可能認(rèn)為這么簡單的問題你不可能犯這么愚蠢的錯(cuò)誤,但是我想告訴你的是,我身邊幾乎一半的人都被這一幕困擾過。

      寫業(yè)務(wù)代碼的時(shí)候,代碼可能比較復(fù)雜,嵌套的方法很多。如果你不小心,很可能會觸發(fā)此問題。舉一個(gè)非常簡單的例子,假設(shè)你有一個(gè)審計(jì)功能。每個(gè)方法執(zhí)行后,審計(jì)結(jié)果保存在數(shù)據(jù)庫中,那么代碼可能會這樣寫。

      @Service
      public class TransactionService {
      
          @Transactional(rollbackFor = Exception.class)
          public void transactionTest() throws IOException {
              User user = new User();
              UserService.insert(user);
              throw new IOException();
      
          }
      }
      
      @Component
      public class AuditAspect {
      
      	@Autowired
      	private auditService auditService;
      
          @Around(value = "execution (* com.alvin.*.*(..))")
          public Object around(ProceedingJoinPoint pjp) {
              try {
                  Audit audit = new Audit();
                  Signature signature = pjp.getSignature();
                  MethodSignature methodSignature = (MethodSignature) signature;
                  String[] strings = methodSignature.getParameterNames();
                  audit.setMethod(signature.getName());
                  audit.setParameters(strings);
                  Object proceed = pjp.proceed();
                  audit.success(true);
                  return proceed;
              } catch (Throwable e) {
                  log.error("{}", e);
                  audit.success(false);
              }
              
              auditService.save(audit);
              return null;
          }
      
      }

      上面的示例中,事務(wù)將失敗。原因是Spring 的事務(wù)切面 優(yōu)先級最低,所以如果異常被切面捕獲,Spring自然不能正常處理事務(wù),因?yàn)槭聞?wù)管理器無法捕獲異常。

      解決方案

      雖然我們知道在處理使唔使業(yè)務(wù)代碼不能自己捕獲異常,但是只要代碼變得復(fù)雜,我們就很可能再次出錯(cuò),所以我們在處理事務(wù)的時(shí)候要小心,還是不要使用聲明式事務(wù),并使用編程式事務(wù):

      transactionTemplate.execute()
      1.3.3 同一類的方法調(diào)用
      @Service
      public class DefaultTransactionService implement Service {
      
          public void saveUser() throws Exception {
              //do something
              doInsert();
          }
      
          @Transactional(rollbackFor = Exception.class)
          public void doInsert() throws IOException {
              User user = new User();
              UserService.insert(user);
              throw new IOException();
      
          }
      }

      這也是一個(gè)容易出錯(cuò)的場景。事務(wù)失敗的原因也很簡單,因?yàn)镾pring的事務(wù)管理功能是通過動態(tài)代理實(shí)現(xiàn)的,而Spring默認(rèn)使用 JDK 動態(tài)代理,而 JDK 動態(tài)代理采用接口實(shí)現(xiàn)的方式,通過反射調(diào)用目標(biāo)類。簡單理解,就是 saveUser() 方法中調(diào)用 this.doInsert() ,這里的this 是被真實(shí)對象,所以會直接走 doInsert 的業(yè)務(wù)邏輯,而不走切面邏輯,所以事務(wù)失敗。

      解決方案

      方案一:解決方法可以是直接在啟動類中添加 @Transcational 注解 saveUser() 
      方案二:@EnableAspectJAutoProxy(exposeProxy = true)在啟動類中添加,會由Cglib代理實(shí)現(xiàn)。
      1.3.4 方法使用 final 或 static 關(guān)鍵字

      如果Spring 使用了 Cglib 代理實(shí)現(xiàn) (比如你的代理類沒有實(shí)現(xiàn)接口),而你的業(yè)務(wù)方法敲好使用了 final 或者 static 關(guān)鍵字,那么事務(wù)也會失敗。更具體的說,它應(yīng)該拋出異常,因?yàn)?Cglib 使用字節(jié)碼增強(qiáng)技術(shù)生成被代理類的子類并重寫代理類的方法來實(shí)現(xiàn)代理。如果被代理的方法使用 final 或 static 關(guān)鍵字,則子類不能重寫被代理的方法。

      如果 Spring 使用 JDK 動態(tài)代理實(shí)現(xiàn),JDK動態(tài)代理是基于接口實(shí)現(xiàn)的,那么 final 和 static 修飾的方法也就無法被代理。

      總而言之,方法連代理都沒有,那么肯定無法實(shí)現(xiàn)事務(wù)回滾了。

      解決方案

      想辦法去掉 final 或者 static 關(guān)鍵字
      1.3.5 方法不是 public

      如果方法不是 public,Spring 事務(wù)也會失敗,因?yàn)?spring 的事務(wù)管理源碼 AbstractFallbackTransactionAttributeSource中有判斷computeTransactionAttribute()。如果目標(biāo)方法不是公共的,則TransactionAttribute返回null

      // Don't allow no-public methods as required.
      if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
        return null;
      }

      解決方案:

      將當(dāng)前方法訪問級別更改為 public 
      1.3.6 錯(cuò)誤使用傳播機(jī)制

      Spring 事務(wù)的傳播機(jī)制是指在多個(gè)事務(wù)方法互相調(diào)用時(shí),確定事務(wù)應(yīng)該如何傳播的策略。Spring 提供了 7 種事務(wù)傳播機(jī)制:

      1. REQUIRED
      2. SUPPORT
      3. MANDATORY
      4. REQUIRES_NEW
      5. NOT_SUPPORTED
      6. NEVER
      7. NESTED

      如果不知道這些傳播策略的原理,很可能會導(dǎo)致交易失敗。

      @Service
      public class TransactionService {
      
      
          @Autowired
          private UserMapper userMapper;
      
          @Autowired
          private AddressMapper addressMapper;
      
      
          @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
          public  void doInsert(User user,Address address) throws Exception {
              //do something
              userMapper.insert(user);
              saveAddress(address);
          }
      
          @Transactional(propagation = Propagation.REQUIRES_NEW)
          public  void saveAddress(Address address) {
              //do something
              addressMapper.insert(address);
          }
      }

      在上面的例子中,如果用戶插入失敗,不會導(dǎo)致 saveAddress() 回滾,因?yàn)檫@里使用的傳播是 REQUIRES_NEW,傳播機(jī)制 REQUIRES_NEW 的原理是如果當(dāng)前方法中沒有事務(wù),就會創(chuàng)建一個(gè)新的事物。如果一個(gè)事物已經(jīng)存在,則當(dāng)前事務(wù)將被掛起,并創(chuàng)建一個(gè)新事務(wù)。在當(dāng)前事務(wù)完成之前,不會提交父事務(wù)。如果父事務(wù)發(fā)生異常,則不影響子事務(wù)的提交。

      事務(wù)的傳播機(jī)制說明如下:

      • REQUIRED:如果當(dāng)前上下文中存在事務(wù),那么加入該事務(wù),如果不存在事務(wù),創(chuàng)建一個(gè)事務(wù),這是默認(rèn)的傳播屬性值。
      • SUPPORT:如果當(dāng)前上下文存在事務(wù),則支持事務(wù)加入事務(wù),如果不存在事務(wù),則使用非事務(wù)的方式執(zhí)行。
      • MANDATORY:如果當(dāng)前上下文中存在事務(wù),并且同時(shí)將上下文中的事務(wù)掛起,執(zhí)行當(dāng)前新建事務(wù)完成以后,上下文事務(wù)回復(fù)在執(zhí)行。
      • NOT_SUPPORTED 如果當(dāng)前上下文存在事務(wù),則掛起當(dāng)前事務(wù),然后新的方法在沒有事務(wù)的環(huán)境中執(zhí)行。
      • NEVER 如果當(dāng)前上下文中存在書屋,則拋出異常,否則在無事務(wù)環(huán)境上執(zhí)行代碼。
      • NESTED 如果當(dāng)前上下文中存在是我,則嵌套是我執(zhí)行,如果不存在事務(wù),則新建事務(wù)。

      解決方案

      將事務(wù)傳播策略更改為默認(rèn)值 REQUIRED, REQUIRED 原理是如果當(dāng)前有一個(gè)事務(wù)被添加到一個(gè)事務(wù)中,如果沒有,則創(chuàng)建一個(gè)新事物,父事務(wù)和被調(diào)用的事務(wù)在同一個(gè)事務(wù)中。即時(shí)被調(diào)用的異常被捕獲,整個(gè)事務(wù)仍然會被回滾。
      1.3.7 沒有被Spring管理
      // @Service
      public class OrderServiceImpl implements OrderService {
          @Transactional
          public void updateOrder(Order order) {
              // update order
          }
      }

      如果此時(shí)把 @Server 注解注釋掉,這個(gè)類就不會被加載成一個(gè) Bean ,那這個(gè)類就不會被 Spring 管理了,事務(wù)自然就失效了。

      解決方案

      需要保證每個(gè)事物注解的每個(gè)Bean被Spring管理。
      1.3.8 多線程
      @Service
      public class UserService {
      
          @Autowired
          private UserMapper userMapper;
          @Autowired
          private RoleService roleService;
      
          @Transactional
          public void add(UserModel userModel) throws Exception {
      
              userMapper.insertUser(userModel);
              new Thread(() -> {
                   try {
                       test();
                   } catch (Exception e) {
                      roleService.doOtherThing();
                   }
              }).start();
          }
      }
      
      @Service
      public class RoleService {
      
          @Transactional
          public void doOtherThing() {
               try {
                   int i = 1/0;
                   System.out.println("保存role表數(shù)據(jù)");
               }catch (Exception e) {
                  throw new RuntimeException();
              }
          }
      }

      我們可以看到實(shí)物方法add中,調(diào)用了實(shí)物方法 doOtherThing ,但是事務(wù)方法 doOtherThing 是在另外一個(gè)線程中被調(diào)用的。

      這樣會導(dǎo)致兩個(gè)方法不再同一個(gè)線程中,獲取到的數(shù)據(jù)庫連接不一樣,從而使兩個(gè)不同的事務(wù)。如果想 doOtherThing 方法中拋出異常, add 方法也回滾時(shí)不可能的。

      我們說的同一個(gè)事物,其實(shí)是指同一個(gè)數(shù)據(jù)庫連接,只有擁有同一個(gè)數(shù)據(jù)庫連接才能同時(shí)提交和回滾。如果在不同的線程,拿到的數(shù)據(jù)庫連接肯定時(shí)不一樣的,所以是不同的事務(wù)。

      解決方案

      這里就有點(diǎn)分布式事務(wù)的感覺了,盡量還是保證在同一個(gè)事物中處理。
      1.3.9 總結(jié)

      以上總結(jié)了 Spring 中事務(wù)實(shí)現(xiàn)的原理,同時(shí)列舉了 8 重 Spring 事務(wù)失敗的場景,相信很多人都遇到過,失敗的原理也有詳細(xì)說明。希望大家對 Spring 事務(wù)有一個(gè)新的認(rèn)識。

      二、JDK動態(tài)代理和CGLIB動態(tài)代理

      2.1 什么是代理模式

      代理模式(Proxy Pattern)給某一個(gè)對象提供一個(gè)代理,并用代理對象控制原對象的引用。代理對象再客戶端和目標(biāo)對象之間起到中介作用。

      代理模式是常用的結(jié)構(gòu)型設(shè)計(jì)模式之一,當(dāng)直接訪問某些對象存在問題時(shí)可以通過一個(gè)代理對象來間接訪問,為了保證客戶端使用的透明性,所訪問的真實(shí)對象與代理對象需要實(shí)現(xiàn)相同的接口。代理模式屬于結(jié)構(gòu)型設(shè)計(jì)模式,屬于GOF23設(shè)計(jì)模式

      代理模式可以分為靜態(tài)代理和動態(tài)代理兩種類型,而動態(tài)代理中又分為 JDK 動態(tài)代理和 CGLIB 代理兩種。

      2

      代理模式包含如下角色

      1. Subject(抽象主體角色)抽象主體角色聲明了真實(shí)主體和代理主體的共同接口,這樣依賴在任何使用真實(shí)主體的地方都可以使用代理主體。客戶端需要針對抽象主體角色進(jìn)行編程。
      2. Proxy(代理主體角色)代理主體角色內(nèi)部包含對真實(shí)主體的引用,從而可以在任何時(shí)候操作真是主體對象。在代理主體角色中提供一個(gè)與真實(shí)主體角色相同的接口,以便在任何時(shí)候都可以代替真實(shí)主體。代理主體角色還可以控制對真實(shí)主體的使用,負(fù)責(zé)在需要的時(shí)候創(chuàng)建和刪除真是主體對象,并對真實(shí)主體對象的使用加以約束。代理角色通常在客戶端調(diào)用所引用的真實(shí)主體操作之前或之后還需要執(zhí)行其他操作,而不僅僅是單純的調(diào)用真是主體對象中的操作。
      3. RealSubject(真是主體角色)真是主體角色定義了代理角色所代表的真實(shí)對象,在真實(shí)主體角色中實(shí)現(xiàn)了真實(shí)的業(yè)務(wù)操作,客戶端可以通過代理主體角色間接調(diào)用真是主體角色重定義的方法。

      2.2 代理模式的優(yōu)點(diǎn)

      • 代理模式能將代理對象與真實(shí)被調(diào)用的目標(biāo)對象分離。
      • 一定程度上降低了系統(tǒng)柜的耦合度,擴(kuò)展性好。
      • 可以起到保護(hù)目標(biāo)對象的作用。
      • 可以對目標(biāo)對象的功能增強(qiáng)

      2.3 代理模式的缺點(diǎn)

      • 代理模式會造成系統(tǒng)設(shè)計(jì)中類的數(shù)量增加。
      • 在客戶端和目標(biāo)對象增加一個(gè)代理對象,會造成請求處理速度變慢。

      2.4 JDK動態(tài)代理

      在java的動態(tài)代理機(jī)制中,有兩個(gè)重要的類或接口,一個(gè)是 InvocationHandler(Interface)、另外一個(gè)則是Prox(Class),這個(gè)類和接口是實(shí)現(xiàn)我們動態(tài)代理所必須用到的。

      2.4.1 InvocationHandler

      每一個(gè)動態(tài)代理類都必須要實(shí)現(xiàn) InvocationHandler 這個(gè)接口,并且每個(gè)代理類的實(shí)例都關(guān)聯(lián)了一個(gè)handler,當(dāng)我們通過代理對象調(diào)用一個(gè)方法的時(shí)候,這個(gè)方法的調(diào)用就會被轉(zhuǎn)發(fā)為由 InvocationHandler 這個(gè)接口的 invoke 方法來進(jìn)行調(diào)用。

      InvocationHandler 這個(gè)接口的唯一一個(gè)方法 invoke 方法:

      Object invoke(Object proxy, Method method, Object[] args) throws Throwable

      這個(gè)方法一共接受三個(gè)參數(shù),那么這三個(gè)參數(shù)分別代表如下:

      • proxy :指代 JDK 動態(tài)生成的最終代理對象。
      • method :指代的是我們所要調(diào)用真是對象的某個(gè)方法的 Method 對象。
      • args : 指代的是調(diào)用真實(shí)對象某個(gè)方法時(shí)接受的參數(shù)。
      2.4.2 Proxy

      Proxy 這個(gè)類的作用就是來動態(tài)創(chuàng)建一個(gè)代理對象的類,它提供了許多的方法,但是我們用的最多的就是 newProxyInstance 這個(gè)方法:

      public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,  InvocationHandler handler)  throws IllegalArgumentException

      這個(gè)方法的作用就是得到一個(gè)動態(tài)的代理對象,其接收三個(gè)參數(shù),我們來看看這三個(gè)參數(shù)所代表的含義:

      • loader :ClassLoader對象,定義了由那些 ClassLoader 來對生成的代理對象進(jìn)行加載,即代理類的類加載器。
      • interfaces : Interface 對象的數(shù)組,表示的是我將要給我需要代理的對象提供一組什么接口,如果我提供了一組接口給它,那么這個(gè)代理對象就宣稱實(shí)現(xiàn)了該接口(多態(tài)),這樣我就能調(diào)用這組接口中的方法了。
      • Handler :InvocationHandler 對象,表示的是當(dāng)我這個(gè)動態(tài)代理對象在調(diào)用方法的時(shí)候,會關(guān)聯(lián)到哪一個(gè) InvocationHandler 對象上。

      所以我們所說的 DynamicProxy (動態(tài)代理類)是這樣一種class : 它是在運(yùn)行時(shí)生成的 class ,在生成它時(shí)你必須提供一組 interface 給它,然后改 class 就宣稱它實(shí)現(xiàn)了這些 interface。這個(gè) DynamicProxy 其實(shí)就是一個(gè) Proxy,它不會做實(shí)質(zhì)性的工作,在生成它的實(shí)例時(shí)你必須提供一個(gè) handler,由它接管實(shí)際的工作。

      2.4.3 JDK 動態(tài)代理實(shí)例
      1. 創(chuàng)建接口類
      public interface HelloInterface {
          void sayHello();
      }
      1. 創(chuàng)建被代理類,實(shí)現(xiàn)接口
      /**
       * 被代理類
       */
      public class HelloImpl implements HelloInterface{
          @Override
          public void sayHello() {
              System.out.println("hello");
          }
      }
      1. 創(chuàng)建InvocationHandler實(shí)現(xiàn)類
      /**
       * 每次生成動態(tài)代理類對象時(shí)都需要指定一個(gè)實(shí)現(xiàn)了InvocationHandler接口的調(diào)用處理器對象
       */
      public class ProxyHandler implements InvocationHandler{
          private Object subject; // 這個(gè)就是我們要代理的真實(shí)對象,也就是真正執(zhí)行業(yè)務(wù)邏輯的類
          public ProxyHandler(Object subject) {// 通過構(gòu)造方法傳入這個(gè)被代理對象
              this.subject = subject;
          }
          /**
           *當(dāng)代理對象調(diào)用真實(shí)對象的方法時(shí),其會自動的跳轉(zhuǎn)到代理對象關(guān)聯(lián)的handler對象的invoke方法來進(jìn)行調(diào)用
           */
          @Override
          public Object invoke(Object obj, Method method, Object[] objs)
                  throws Throwable {
              Object result = null;
              System.out.println("可以在調(diào)用實(shí)際方法前做一些事情");
              System.out.println("當(dāng)前調(diào)用的方法是" + method.getName());
              result = method.invoke(subject, objs);// 需要指定被代理對象和傳入?yún)?shù)
              System.out.println(method.getName() + "方法的返回值是" + result);
              System.out.println("可以在調(diào)用實(shí)際方法后做一些事情");
              System.out.println("------------------------");
              return result;// 返回method方法執(zhí)行后的返回值
          }
      }
      1. 測試
      public class Mytest {
          public static void main(String[] args) {
              //第一步:創(chuàng)建被代理對象
              HelloImpl hello = new HelloImpl();
              //第二步:創(chuàng)建handler,傳入真實(shí)對象
              ProxyHandler handler = new ProxyHandler(hello);
              //第三步:創(chuàng)建代理對象,傳入類加載器、接口、handler
              HelloInterface helloProxy = (HelloInterface) Proxy.newProxyInstance(
                      HelloInterface.class.getClassLoader(), 
                      new Class[]{HelloInterface.class}, handler);
              //第四步:調(diào)用方法
              helloProxy.sayHello();
          }
      }
      1. 結(jié)果
      可以在調(diào)用實(shí)際方法前做一些事情
      當(dāng)前調(diào)用的方法是sayHello
      hello
      sayHello方法的返回值是null
      可以在調(diào)用實(shí)際方法后做一些事情
      ------------------------
      2.4.4 JDK 動態(tài)代理步驟

      JDK 動態(tài)代理分為以下幾步:

      1. 拿到被代理對象的引用,并且通過反射獲取到它的所有的接口。
      2. 通過 JDK Proxy 類重新生成一個(gè)新的類,同時(shí)新的類要實(shí)現(xiàn)被代理類所實(shí)現(xiàn)的所有的接口。
      3. 動態(tài)生成 Java 代碼,把新加的業(yè)務(wù)邏輯方法由一定的邏輯代碼去調(diào)用。
      4. 編譯新生成的 Java 代碼 .class。
      5. 將新生成的Class文件重新加載到JVM中運(yùn)行。

      所以說 JDK 動態(tài)代理的核心是通過重寫被代理對象所實(shí)現(xiàn)的接口中的方法重新生成代理類來實(shí)現(xiàn)的,那么加入被代理對象沒有實(shí)現(xiàn)接口呢?那么這時(shí)候就需要 CGLIB 動態(tài)代理了。

      2.5 CGLIB 動態(tài)代理

      JDK 動態(tài)代理是通過重寫被代理對象實(shí)現(xiàn)的接口中的方法來實(shí)現(xiàn),而CGLIB是通過集成部誒代理對象來實(shí)現(xiàn)和 JDK 動態(tài)代理需要實(shí)現(xiàn)指定接口一樣,CGLIB 也要求代理對象必須要實(shí)現(xiàn) MethodInterceptor 接口,并重寫其唯一的方法 intercept 。

      CGLib 采用了非常底層的字節(jié)碼技術(shù),其原理是通過字節(jié)碼技術(shù)為一個(gè)類創(chuàng)建子類,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,順勢植入橫切邏輯。(利用ASM開源包,對代理對象類的 class 文件加載進(jìn)來,通過修改其字節(jié)碼生成子類來處理)。

      注意 :因?yàn)镃GLIB 是童工集成目標(biāo)類來重寫其方法來實(shí)現(xiàn)的,故而如果是 final 和 private 方法則無法被重寫,也就無法被代理。

      <dependency>
        <groupId>cglib</groupId>
      	<artifactId>cglib-nodep</artifactId>
      	<version>2.2</version>
      </dependency>
      2.5.1 CGLIB 核心類
      1. net.sf.cglib.proxy.Enhancer :主要增強(qiáng)類,通過字節(jié)碼技術(shù)動態(tài)創(chuàng)建委托類的子類實(shí)例;

        Enhancer 可能是 CGLIB 中最常用的一個(gè)類,和Java1.3 動態(tài)代理中引入的Proxy類差不多。和Proxy不同的是,Enhancer 技能夠代理普通的 class,也能夠代理接口。Enhancer 創(chuàng)建一個(gè)被代理對象的子類并且攔截所有的方法調(diào)用(包括從 Object 中繼承的 toString 和 hashCode 方法)。Enhancer 不能夠攔截 final 方法,例如 Object.getClass() 方法,這是由于 Java final 方法語義決定的。基于同樣的道理,Enhancer 也不能對 final 類進(jìn)行 代理操作。這也是 Hibernate 為什么不能持久化 final class 的原因。

      2. net.sf.cglib.proxy.MethodInterceptor :常用的方法攔截器接口,需要實(shí)現(xiàn) intercept 方法,實(shí)現(xiàn)具體攔截處理:

      public java.lang.Object intercept(java.lang.Object obj,
                                         java.lang.reflect.Method method,
                                         java.lang.Object[] args,
                                         MethodProxy proxy) throws java.lang.Throwable{}
      • obj : 動態(tài)生成的代理對象。

      • method : 實(shí)際調(diào)用的方法。

      • args : 調(diào)用方法入?yún)ⅰ?/p>

      • net.sf.cglib.proxy.MethodProxy :java Method類的代理類,可以實(shí)現(xiàn)委托類對象的方法的調(diào)用;常用方法:methodProxy.invokeSuper(proxy, args);在攔截方法內(nèi)可以調(diào)用多次。

      2.5.2 CGLIB 代理實(shí)例
      1. 創(chuàng)建被代理類
      public class SayHello {
        public void say() {
          System.out.println("hello")
        }
      }
      1. 創(chuàng)建代理類
      /**
       *代理類 
       */
      public class ProxyCglib implements MethodInterceptor{
           private Enhancer enhancer = new Enhancer();  
           public Object getProxy(Class clazz){  
                //設(shè)置需要?jiǎng)?chuàng)建子類的類  
                enhancer.setSuperclass(clazz);  
                enhancer.setCallback(this);  
                //通過字節(jié)碼技術(shù)動態(tài)創(chuàng)建子類實(shí)例  
                return enhancer.create();  
           }  
      
           //實(shí)現(xiàn)MethodInterceptor接口方法  
           public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {  
                System.out.println("可以在調(diào)用實(shí)際方法前做一些事情");  
                //通過代理類調(diào)用父類中的方法  
                Object result = proxy.invokeSuper(obj, args);  
                System.out.println("可以在調(diào)用實(shí)際方法后做一些事情");  
                return result;  
           } 
      }
      1. 測試
      public class Mytest {
      
          public static void main(String[] args) {
                ProxyCglib proxy = new ProxyCglib();  
                //通過生成子類的方式創(chuàng)建代理類  
                SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class);  
                proxyImp.say();  
          }
      }
      1. 結(jié)果
      可以在調(diào)用實(shí)際方法前做一些事情
      hello
      可以在調(diào)用實(shí)際方法后做一些事情
      2.5.3 CGLIB 動態(tài)代理實(shí)現(xiàn)分析

      CGLIB 動態(tài)代理采用了 FastClass 機(jī)制,其分別為代理類和被代理類各生成一個(gè)FastClass,這個(gè) FastClass 類會為代理類或被代理類的方法分配一個(gè) index(int類型)。這個(gè)index 當(dāng)作一個(gè)入?yún)ⅲ現(xiàn)astClass 就可以直接定位要調(diào)用的方法直接進(jìn)行調(diào)用,這樣省去了反射調(diào)用,所以調(diào)用效率比 JDK 動態(tài)代理通過反射調(diào)用更高。

      但是我們看上面的源碼也可以明顯看到,JDK 動態(tài)代理只生成一個(gè)文件,而 CGLIB 生成了三個(gè)文件,所以生成代理對象的過程會更復(fù)雜。

      2.6 JDK 和 CGLIB 動態(tài)代理對比

      JDK 動態(tài)代理是實(shí)現(xiàn)了唄代理對象啊所實(shí)現(xiàn)的接口,CGLIB 是繼承了被代理對象。JDK 和 CGLIB 都是在運(yùn)行期生成字節(jié)碼,JDK 是直接寫 Class 字節(jié)碼,CGLib 使用 ASM 框架寫 Class 字節(jié)碼,Cglib 代理實(shí)現(xiàn)更為復(fù)雜,生成代理類的效率比 JDK 代理低。

      JDK 調(diào)用代理方法,是通過反射機(jī)制調(diào)用,CGLIB 是通過 FastClass 機(jī)制直接調(diào)用方法,CGLIB 執(zhí)行效率更高。

      2.6.1 原理區(qū)別:

      java 動態(tài)代理是利用反射機(jī)制生成一個(gè)實(shí)現(xiàn)代理接口的匿名類,在調(diào)用具體方法前調(diào)用 InvocationHandler 來處理。核心是實(shí)現(xiàn) InvocationHandler 接口,使用 invoke() 方法進(jìn)行面向切面的處理,調(diào)用相應(yīng)的通知。

      1. 如果目標(biāo)對象實(shí)現(xiàn)了接口,默認(rèn)情況下會采用 JDK 的動態(tài)代理實(shí)現(xiàn) AOP。
      2. 如果目標(biāo)對象實(shí)現(xiàn)了接口,可以強(qiáng)制使用 CGLIB 實(shí)現(xiàn) AOP。
      3. 如果目標(biāo)對象沒有實(shí)現(xiàn)了接口,必須采用 CGLIB 庫,spring 會自動在 JDK 動態(tài)代理和 CGLIB 之間轉(zhuǎn)換
      2.6.2 性能區(qū)別:
      1. CGLib底層采用ASM字節(jié)碼生成框架,使用字節(jié)碼技術(shù)生成代理類,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能對聲明為final的方法進(jìn)行代理,因?yàn)镃GLib原理是動態(tài)生成被代理類的子類。

      2. 在jdk6、jdk7、jdk8逐步對JDK動態(tài)代理優(yōu)化之后,在調(diào)用次數(shù)較少的情況下,JDK代理效率高于CGLIB代理效率,只有當(dāng)進(jìn)行大量調(diào)用的時(shí)候,jdk6和jdk7比CGLIB代理效率低一點(diǎn),但是到j(luò)dk8的時(shí)候,jdk代理效率高于CGLIB代理。

      2.6.3 各自局限
      1. JDK的動態(tài)代理機(jī)制只能代理實(shí)現(xiàn)了接口的類,而不能實(shí)現(xiàn)接口的類就不能實(shí)現(xiàn)JDK的動態(tài)代理。

      2. cglib是針對類來實(shí)現(xiàn)代理的,他的原理是對指定的目標(biāo)類生成一個(gè)子類,并覆蓋其中方法實(shí)現(xiàn)增強(qiáng),但因?yàn)椴捎玫氖抢^承,所以不能對final修飾的類進(jìn)行代理。

      類型 機(jī)制 回調(diào)方式 適用場景 效率
      JDK動態(tài)代理 委托機(jī)制,代理類和目標(biāo)類都實(shí)現(xiàn)了同樣的接口,InvocationHandler持有目標(biāo)類,代理類委托InvocationHandler去調(diào)用目標(biāo)類的原始方法 反射 目標(biāo)類是接口類 效率瓶頸在反射調(diào)用稍慢
      CGLIB動態(tài)代理 繼承機(jī)制,代理類繼承了目標(biāo)類并重寫了目標(biāo)方法,通過回調(diào)函數(shù)MethodInterceptor調(diào)用父類方法執(zhí)行原始邏輯 通過FastClass方法索引調(diào)用 非接口類、非final類,非final方法 第一次調(diào)用因?yàn)橐啥鄠€(gè)Class對象,比JDK方式慢。多次調(diào)用因?yàn)橛蟹椒ㄋ饕确瓷淇欤绻椒ㄟ^多,switch case過多其效率還需測試

      2.7 靜態(tài)代理和動態(tài)代理的區(qū)別

      靜態(tài)代理只能通過手動完成代理操作,如果被代理類增加新的方法,代理類需要同步新增,違背開閉原則。

      動態(tài)代理采用在運(yùn)行時(shí)動態(tài)生成代碼的方式,取消了對被代理類的擴(kuò)展限制,遵循開閉原則。

      若動態(tài)代理要對目標(biāo)類的增強(qiáng)邏輯擴(kuò)展,結(jié)合策略模式,只需要新增策略類便可完成,無需修改代理類的代碼。

      三、Redlock :Redis 集群分布式鎖

      3.1 前言

      分布式鎖是一種非常有用的技術(shù)手段。實(shí)現(xiàn)高效的分布式鎖有三個(gè)屬性需要考慮。

      1. 安全屬性 : 互斥,不管什么時(shí)候,只有一個(gè)客戶端持有鎖。
      2. 效率屬性A :不會死鎖。
      3. 效率屬性B :容錯(cuò),只要大多數(shù) redis 節(jié)點(diǎn)能夠正常工作,客戶端都能獲取和釋放鎖。

      3.2 普通版:單機(jī) redis 分布式鎖

      Redis 分布式鎖大部分人都會想到 :setnx + lua 或者 set + lua ,加上過期時(shí)間,大多數(shù)都是使用的下面的 keyset 方法,具體實(shí)現(xiàn)過程這里就不再贅述。

      • 實(shí)現(xiàn)比較輕,大多數(shù)能夠滿足需求;因?yàn)槭菃螜C(jī)單實(shí)例部署,如果 redis 服務(wù)宕機(jī),那么所有需要獲取分布式鎖的地方均無法獲取鎖,將全部阻塞,需要做好降級處理。
      • 當(dāng)鎖過期后,執(zhí)行任務(wù)的進(jìn)程還沒有執(zhí)行完,但是鎖因?yàn)樽詣舆^期已經(jīng)解鎖,可能被其他進(jìn)程重新加鎖,這就造成多個(gè)金策會給你同時(shí)獲取到了鎖,這需要額外的方案來解決這種問題。
      • 在集群模式時(shí)有復(fù)制延遲,以及主節(jié)點(diǎn)宕機(jī),會造成鎖丟失或者鎖延遲現(xiàn)象。

      事實(shí)上這類鎖最大的缺點(diǎn)就是它加鎖時(shí)之作用在一個(gè) Redis 節(jié)點(diǎn)上,即時(shí) Redis 通過 sentinel 保證高可用,如果這個(gè) master 節(jié)點(diǎn)由于某些原因發(fā)生了主從切換,那么就會出現(xiàn) 鎖丟失的情況:

      • 在 Redis 的 master 節(jié)點(diǎn)上拿到了鎖;
      • 但是這個(gè)加鎖的 key 還沒有同步到 slave 節(jié)點(diǎn);
      • master 故障,發(fā)生故障轉(zhuǎn)移, slave 節(jié)點(diǎn)升級為 master 節(jié)點(diǎn);
      • 導(dǎo)致鎖丟失。

      正因?yàn)槿绱耍琑edis 作者 基于分布式環(huán)境下提出一種更為高級的分布式鎖的實(shí)現(xiàn)方式 : Redlock ;Redlock 也是 Redis 所有分布式鎖實(shí)現(xiàn)分布式鎖實(shí)現(xiàn)方式中唯一能讓面試官高潮的方式。

      基于單 Redis 節(jié)點(diǎn)的分布式鎖的算法就描述完了。這里面有好幾個(gè)問題需要重點(diǎn)分析一下。

      • 首先第一個(gè)問題,這個(gè)鎖必須要設(shè)置一個(gè)過期時(shí)間。否則的話,當(dāng)一個(gè)客戶端獲取鎖成功之后,加入它崩潰了,或者由于發(fā)生了網(wǎng)路分割(network partition)導(dǎo)致它在無法和 Redis 節(jié)點(diǎn)通信了,那么它就會一直持有這個(gè) 鎖,而其它客戶端永遠(yuǎn)無法獲得鎖了。為了解決這個(gè)問題,redis作者把這個(gè)過期時(shí)間成為所的有效時(shí)間(lock validity time)。獲的鎖的客戶端必須在這個(gè)時(shí)間之內(nèi)完成對共享資源的訪問。
      • 第二個(gè)問題,第一步獲取鎖的操作,不少文章把它實(shí)現(xiàn)成了兩個(gè) Redis 命令:
      SETNX resource_name my_random_value
      EXPIRE resource_name 30

      雖然這兩個(gè)命令和前面算法描述中的一個(gè) set 命令執(zhí)行效果相同,但卻不是原子的。如果客戶端在執(zhí)行完 SETNX 后崩潰了,那么就沒有機(jī)會執(zhí)行 EXPIRE了,導(dǎo)致它一直持有這個(gè)鎖。

      • 第三個(gè)問題:也是作者指出的,設(shè)置一個(gè)隨機(jī)字符串 my_random_value 是很有必要的,它保證了一個(gè)客戶端釋放的鎖必須是自己持有的那個(gè)鎖。加入獲取鎖時(shí) SET 的不是一個(gè)隨機(jī)字符串,而是一個(gè)固定值,那么可能會發(fā)生下面的執(zhí)行序列:

        1. 客戶端A 獲取鎖成功。
        2. 客戶端A在某個(gè)操作上阻塞了很長時(shí)間。
        3. 過期時(shí)間到了,鎖自動釋放了。
        4. 客戶端B獲取到了對應(yīng)同一個(gè)資源的鎖。
        5. 客戶端A從阻塞中回復(fù)過來,釋放掉了客戶端B 持有的鎖。
        6. 之后,客戶端B 在訪問共享資源的時(shí)候,它沒有鎖為它提供保護(hù)了。
      • 第四個(gè)問題:釋放鎖的操作必須是使用Lua腳本實(shí)現(xiàn),釋放鎖其實(shí)包含三步操作 : "GET" ,判斷和 "DEL" ,用Lua腳本來實(shí)現(xiàn)能保證各三步的原子性。否則,如果把這三步操作方法哦客戶端邏輯中執(zhí)行的話,就有可能發(fā)生與前面第三個(gè)問題類似的執(zhí)行序列:

        1. 客戶端1獲取鎖成功。
        2. 客戶端1訪問共享資源。
        3. 客戶端1為了釋放鎖,先執(zhí)行’GET’操作獲取隨機(jī)字符串的值。
        4. 客戶端1判斷隨機(jī)字符串的值,與預(yù)期的值相等。
        5. 客戶端1由于某個(gè)原因阻塞住了很長時(shí)間。
        6. 過期時(shí)間到了,鎖自動釋放了。
        7. 客戶端2獲取到了對應(yīng)同一個(gè)資源的鎖。
        8. 客戶端1從阻塞中恢復(fù)過來,執(zhí)行DEL操縱,釋放掉了客戶端2持有的鎖。
        9. 實(shí)際上,在上述第三個(gè)問題和第四個(gè)問題的分析中,如果不是客戶端阻塞住了,而是出現(xiàn)了大的網(wǎng)絡(luò)延遲,也有可能導(dǎo)致類似的執(zhí)行序列發(fā)生。

      前面的問題,只要實(shí)現(xiàn)分布式鎖的時(shí)候加以注意,就都能夠被正確的處理。但除此之外,還有一個(gè)問題,就是由 failover【故障切換】 引起的卻是基于單Redis界定的分布式鎖無法解決的。正是這個(gè)問題催生了 Redlock 的出現(xiàn)。

      3.3 為什么基于failover【故障切換】的方案不夠好

      為了理解我們想要提高的到底是什么,我們先看下當(dāng)前大多數(shù)基于Redis的分布式鎖三方庫的現(xiàn)狀。 用Redis來實(shí)現(xiàn)分布式鎖最簡單的方式就是在實(shí)例里創(chuàng)建一個(gè)鍵值,創(chuàng)建出來的鍵值一般都是有一個(gè)超時(shí)時(shí)間的(這個(gè)是Redis自帶的超時(shí)特性),所以每個(gè)鎖最終都會釋放。

      而當(dāng)一個(gè)客戶端想要釋放鎖時(shí),它只需要?jiǎng)h除這個(gè)鍵值即可。 表面來看,這個(gè)方法似乎很管用,但是這里存在一個(gè)問題:在我們的系統(tǒng)架構(gòu)里存在一個(gè)單點(diǎn)故障,如果Redis的master節(jié)點(diǎn)宕機(jī)了怎么辦呢?有人可能會說:加一個(gè)slave節(jié)點(diǎn)!在master宕機(jī)時(shí)用slave就行了!但是其實(shí)這個(gè)方案明顯是不可行的,因?yàn)檫@種方案無法保證第1個(gè)安全互斥屬性,因?yàn)镽edis的復(fù)制是異步的。 總的來說,這個(gè)方案里有一個(gè)明顯的競爭條件(race condition),舉例來說:

      1、客戶端A在master節(jié)點(diǎn)拿到了鎖。

      2、master節(jié)點(diǎn)在把A創(chuàng)建的key寫入slave之前宕機(jī)了。

      3、slave變成了master節(jié)點(diǎn)

      4、B也得到了和A還持有的相同的鎖(因?yàn)樵瓉淼膕lave里還沒有A持有鎖的信息)

      當(dāng)然,在某些特殊場景下,前面提到的這個(gè)方案則完全沒有問題,比如在宕機(jī)期間,多個(gè)客戶端允許同時(shí)都持有鎖,如果你可以容忍這個(gè)問題的話,那用這個(gè)基于復(fù)制的方案就完全沒有問題,否則的話我們還是建議你采用這篇文章里接下來要描述的方案。

      3.4 集群分布式鎖

      在redis集群模式下創(chuàng)建鎖和解鎖的方案,用到的 redis 命令依然和普通模式一樣,唯一不同的在于集群模式下的數(shù)據(jù)清理方式,基本命令如下:

      SET key value [EX seconds] [PX milliseconds] [NX|XX]
      Set the string value of a key
      SET 指令可以將字符串的value和key綁定在一起。
      EX seconds:設(shè)置key的過時(shí)時(shí)間,單位為秒。
      PX milliseconds:設(shè)置key的過期時(shí)間,單位為毫秒。
      NX:(if Not eXist)只有鍵key不存在的時(shí)候才會設(shè)置key的值
      XX:只有鍵key存在的時(shí)候才會設(shè)置key的值
      
      NX通常用于實(shí)現(xiàn)鎖機(jī)制,X

      lua腳本

      // 獲取鎖(unique_value可以是UUID等)
      SET key_name unique_value NX PX 30000
      // 釋放鎖(lua腳本中,一定要比較value,防止誤解鎖)
      if redis.call("get",KEYS[1]) == ARGV[1] then
       return redis.call("del",KEYS[1])
      else
      return 0
      end

      3.5 Redlock 實(shí)現(xiàn)

      Redlock 算法大概是這樣的:

      在 Redis 的分布式環(huán)境中,我們假設(shè)最孤立現(xiàn)象(最苛刻環(huán)境下):有 N 個(gè) Redis Master 。這些節(jié)點(diǎn)完全互相獨(dú)立(正常集群不會做成這么孤立),不存在主從復(fù)制或者其他集群卸掉機(jī)制。我們確保將在 N 個(gè)實(shí)例上使用與 Redis 單實(shí)例下相同方法獲取和釋放鎖。現(xiàn)在我們假設(shè)有 5 個(gè) Redis Master 節(jié)點(diǎn),同時(shí)我們需要在 5 臺服務(wù)器上賣弄運(yùn)行這些 Redis 實(shí)例,這樣保證他們不會同時(shí)宕機(jī)。

      3

      這里把上圖中的各個(gè)redis主從連線去掉,就變成各個(gè)獨(dú)立的集群了(實(shí)現(xiàn)孤立場景:集群之間掉線不通等極端情況)

      為了取到鎖,客戶端應(yīng)該執(zhí)行以下操作:

      • 獲取當(dāng)前Unix時(shí)間,以毫秒為單位。

      • 依次嘗試從5個(gè)實(shí)例,使用相同的key和具有唯一性的value(例如UUID)獲取鎖。當(dāng)向Redis請求獲取鎖時(shí),客戶端應(yīng)該設(shè)置一個(gè)網(wǎng)絡(luò)連接和響應(yīng)超時(shí)時(shí)間,這個(gè)超時(shí)時(shí)間應(yīng)該小于鎖的失效時(shí)間。例如你的鎖自動失效時(shí)間為10秒,則超時(shí)時(shí)間應(yīng)該在5-50毫秒之間。這樣可以避免服務(wù)器端Redis已經(jīng)掛掉的情況下,客戶端還在死死地等待響應(yīng)結(jié)果。如果服務(wù)器端沒有在規(guī)定時(shí)間內(nèi)響應(yīng),客戶端應(yīng)該盡快嘗試去另外一個(gè)Redis實(shí)例請求獲取鎖。

      • 客戶端使用當(dāng)前時(shí)間減去開始獲取鎖時(shí)間(步驟1記錄的時(shí)間)就得到獲取鎖使用的時(shí)間。當(dāng)且僅當(dāng)從大多數(shù)(N/2+1,這里是3個(gè)節(jié)點(diǎn))的Redis節(jié)點(diǎn)都取到鎖,并且使用的時(shí)間小于鎖失效時(shí)間時(shí),鎖才算獲取成功。

        // 計(jì)算方式如下
        1. 當(dāng)前時(shí)間 -  獲取鎖的時(shí)間 = 鎖使用時(shí)間
        2. N/2 + 1 三個(gè)Redis節(jié)點(diǎn)都獲取到鎖
        3. 鎖使用時(shí)間 < 鎖失效時(shí)間 (才認(rèn)為獲取到鎖)
      • 如果取到了鎖,key的真正有效時(shí)間等于有效時(shí)間減去獲取鎖所使用的時(shí)間(步驟3計(jì)算的結(jié)果)。

      • 如果因?yàn)槟承┰颍?strong>獲取鎖失敗(沒有在至少N/2+1個(gè)Redis實(shí)例取到鎖或者取鎖時(shí)間已經(jīng)超過了有效時(shí)間),客戶端應(yīng)該在所有的Redis實(shí)例上進(jìn)行解鎖(即便某些Redis實(shí)例根本就沒有加鎖成功,防止某些節(jié)點(diǎn)獲取到鎖但是客戶端沒有得到響應(yīng)而導(dǎo)致接下來的一段時(shí)間不能被重新獲取鎖)。

      3.5.1實(shí)現(xiàn)代碼
      1. POM依賴
      <!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
      <dependency>
       <groupId>org.redisson</groupId>
       <artifactId>redisson</artifactId>
       <version>3.3.2</version>
      </dependency>
      Config config = new Config();
      
      config.useSentinelServers().addSentinelAddress("127.0.0.1:6369","127.0.0.1:6379", "127.0.0.1:6389")
       .setMasterName("masterName")
       .setPassword("password").setDatabase(0);
      
      RedissonClient redissonClient = Redisson.create(config);
      // 還可以getFairLock(), getReadWriteLock()
      RLock redLock = redissonClient.getLock("REDLOCK_KEY");
      boolean isLock;
      try {
       isLock = redLock.tryLock();//使用默認(rèn)方式獲取:默認(rèn)租約時(shí)間(leaseTime)是LOCKEXPIRATIONINTERVAL_SECONDS,即30s
       // 因?yàn)樯弦恍蝎@取到了,這里獲取不到:如果獲取到了就500ms, 就認(rèn)為獲取鎖失敗。10000ms即10s是鎖失效時(shí)間。
       isLock = redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS);
       if (isLock) { //如果獲取成功
       //TODO if get lock success, do something;
      
       }
      } catch (Exception e) {
      
      } finally {
       // 無論如何, 最后都要解鎖
       redLock.unlock();
      }
      1. key對應(yīng)唯一值

      防止誤刪除鎖:實(shí)現(xiàn)分布式鎖的一個(gè)非常重要的點(diǎn)就是set的value要具有唯一性,redisson的value是怎樣保證value的唯一性呢? 答案是UUID+threadId。入口在redissonClient.getLock(“REDLOCK_KEY”),源碼在Redisson.java和RedissonLock.java中:

      1. 獲取鎖

      設(shè)置過期時(shí)間:代碼為redLock.tryLock()或者redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS),兩者的最終核心源碼都是下面這段代碼,只不過前者獲取鎖的默認(rèn)租約時(shí)間(leaseTime)是LOCKEXPIRATIONINTERVAL_SECONDS,即30s:

      posted @ 2025-05-26 14:35  CharyGao  閱讀(98)  評論(0)    收藏  舉報(bào)
      主站蜘蛛池模板: 欧美日本中文| 亚洲国产精品自产在线播放| 亚洲av永久无码精品天堂久久| 偷炮少妇宾馆半推半就激情| 国产精品一亚洲av日韩| 国内精品无码一区二区三区| 岛国最新亚洲伦理成人| 欧美日本在线一区二区三区| 日本亚洲欧洲无免费码在线| 潮喷无码正在播放| 亚洲综合一区二区三区视频 | 国产精品中文字幕在线| 国产女同疯狂作爱系列| 国产中文字幕久久黄色片| 精品国产免费一区二区三区香蕉 | 日本一区二区精品色超碰| 欧美成人精品三级在线观看| 天堂国产一区二区三区四区不卡 | 精品国产一区二区三区av性色| 亚洲日韩国产精品第一页一区| 精品日本乱一区二区三区| 一区二区三区无码视频免费福利| 色悠悠国产精品免费观看| 亚洲AV天天做在线观看| 国产成人免费午夜在线观看| 深夜免费av在线观看| 国产又爽又大又黄a片| 在线免费观看毛片av| 亚洲高清免费在线观看| 国产精品www夜色视频| 亚洲AV无码精品色午夜果冻| 公喝错春药让我高潮| 99久久夜色精品国产亚洲| 国产欧美精品aaaaaa片| 亚洲嫩模一区二区三区 | 国产在线中文字幕精品| 日韩大片看一区二区三区| 欧美性猛交xxxx乱大交丰满| 蜜芽久久人人超碰爱香蕉| 久久国产精品老女人| 男人和女人高潮做爰视频|