自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

高并發(fā)服務(wù)優(yōu)化篇:淺談數(shù)據(jù)庫(kù)連接池

運(yùn)維 數(shù)據(jù)庫(kù)運(yùn)維
連接池提供的獲取連接的能力,需要對(duì)"任務(wù)"唯一,即,只有當(dāng)某一線程完成了本次數(shù)據(jù)操作,將連接放回到連接池之后,其他線程才能夠再次獲取并使用。原因我們后面細(xì)說(shuō),先來(lái)親自測(cè)試一下。

 

本文轉(zhuǎn)載自微信公眾號(hào)「Coder的技術(shù)之路」,作者Coder的技術(shù)之路。轉(zhuǎn)載本文請(qǐng)聯(lián)系Coder的技術(shù)之路公眾號(hào)。

被N多大號(hào)轉(zhuǎn)載的一篇博客,引起了我的注意,說(shuō)的是數(shù)據(jù)庫(kù)連接池使用threadlocal的原因,文中結(jié)論如下圖所示。

 

姑且不談threadlocal的作用和工作原理,單說(shuō)數(shù)據(jù)庫(kù)連接池這個(gè)知識(shí)點(diǎn),猛地一看挺有理;仔細(xì)一看,怎么感覺(jué)不太對(duì)啊,同學(xué),這是什么虎狼之詞。

$ 實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)

個(gè)人理解,連接池提供的獲取連接的能力,需要對(duì)"任務(wù)"唯一,即,只有當(dāng)某一線程完成了本次數(shù)據(jù)操作,將連接放回到連接池之后,其他線程才能夠再次獲取并使用。原因我們后面細(xì)說(shuō),先來(lái)親自測(cè)試一下。

連接池選一個(gè)druid,設(shè)置連接池中只有一個(gè)connection,方便驗(yàn)證多線程應(yīng)對(duì)同一個(gè)connection的場(chǎng)景。

首先,將datasource共享資源傳入線程,采用datasource.getConnection()方式獲取連接 :

注:Runnable中故意不執(zhí)行connection.close

結(jié)果如上圖:只有一個(gè)線程可以正常執(zhí)行,由于沒(méi)有被關(guān)閉,其他線程都獲取連接失敗了。說(shuō)明,數(shù)據(jù)庫(kù)連接池的作用方式是某個(gè)線程任務(wù)"獨(dú)占"的。

$ 退一步來(lái)講

假設(shè)如同開(kāi)頭文章中描述的,用了一個(gè)功能不完備的連接池,讓多個(gè)線程拿到了同一個(gè)connection,那么,用threadlocal真的可以起到互不影響的作用么?

  1. //驗(yàn)證思路參考自:https://blog.csdn.net/sunbo94/article/details/79409298 
  2. //Connection設(shè)置 autoCommit=false 
  3. private static final ThreadLocal<Connection> connectionThreadLocal=new ThreadLocal<>(); 
  4.  
  5. private static class InnerRunner implements Runnable{ 
  6.    @Override 
  7.    public void run() { 
  8.        //其他代碼省略... 
  9.        String insertSql="insert into user(id,name) value("+RunnerIndex+","+RunnerIndex+")"
  10.        statement=connectionThreadLocal.get().createStatement(); 
  11.        statement.executeUpdate(insertSql); 
  12.        System.out.println(RunnerIndex+" is running"); 
  13.        //讓特定的線程執(zhí)行回滾,用來(lái)驗(yàn)證事務(wù)之間的影響 
  14.        if (RunnerIndex==3){ 
  15.           //模擬異常時(shí)耗時(shí)增加 
  16.           Thread.sleep(100); 
  17.           //從threadlocal里拿連接對(duì)象 
  18.           connectionThreadLocal.get().rollback(); 
  19.           System.out.println("3 rollback"); 
  20.         }else
  21.           //從threadlocal里拿連接對(duì)象 
  22.           connectionThreadLocal.get().commit(); 
  23.           System.out.println(RunnerIndex +" commit"); 
  24.        } 
  25.    } 

結(jié)果如下:

只要是線程3的statement.executeUpdate 語(yǔ)句運(yùn)行在前,而事務(wù)回滾語(yǔ)句執(zhí)行在某個(gè)commit之后,就會(huì)出現(xiàn)問(wèn)題,即需要回滾的數(shù)據(jù)被提交的情況。

如下圖,3的insert結(jié)果確實(shí)沒(méi)有被回滾,而是出現(xiàn)在了表中:

所以,對(duì)于知識(shí),大家不能盲目的接收,建議抱些懷疑的態(tài)度,還是有必要的。

$ 話說(shuō)回來(lái),為什么threadlocal對(duì)同一個(gè)數(shù)據(jù)庫(kù)連接不起作用呢?

Connection是什么?

connection可以當(dāng)成是服務(wù)器和數(shù)據(jù)庫(kù)的一個(gè)會(huì)話,而statemant用來(lái)在會(huì)話的上下文中執(zhí)行sql以及返回結(jié)果。一個(gè)connection可以包含多個(gè)statement;然而在兩者中間,還有一個(gè)事務(wù)(Translation)的概念,事務(wù)用來(lái)保證其內(nèi)部的語(yǔ)句,要么都執(zhí)行,要么都不執(zhí)行,如果autoCommit被開(kāi)啟,則默認(rèn)是一個(gè)語(yǔ)句一個(gè)事務(wù)。

往簡(jiǎn)單點(diǎn)說(shuō),connection是一種共享資源,更簡(jiǎn)單一點(diǎn),它是一個(gè)共享變量,在被連接池創(chuàng)建之后,在內(nèi)存中的地址是唯一的一個(gè)變量。

ThreadLocal能存共享變量么?

存肯定能存,但不建議,因?yàn)閷onnection set進(jìn)ThreadLocalMap,也其實(shí)是保存一個(gè)內(nèi)存對(duì)象的地址引用而已,真正使用的時(shí)候,還是唯一的那個(gè)對(duì)象在起作用。

ThreadLocal最常用的功能,是為了避免層層傳遞而提供了對(duì)象保存和獲取方法。

高中學(xué)數(shù)學(xué)的時(shí)候曾經(jīng)有過(guò)一個(gè)技巧,叫證難則反,在這里也適用。我們反過(guò)來(lái)想,如果用threadlocal的副本拷貝能實(shí)現(xiàn)connection的隔離,那豈不是只要一個(gè)connection就可以了?實(shí)時(shí)上呢,數(shù)據(jù)庫(kù)連接常常會(huì)出現(xiàn)不夠用的情況,結(jié)論就顯而易見(jiàn)了~

$ 話又說(shuō)回來(lái),threadLocal想要完成數(shù)據(jù)庫(kù)連接隔離的功能,需要怎么做呢?

如果非要用ThreadLocal實(shí)現(xiàn)這個(gè)連接隔離的功能,那么,只能是為每個(gè)線程創(chuàng)建新的連接,然后保存在Threadlocal中,這樣,每個(gè)線程在自己的生命周期范圍內(nèi)只會(huì)使用這個(gè)連接,即可實(shí)現(xiàn)線程隔離。

$ 話又又說(shuō)回來(lái),druid、zadl等一眾數(shù)據(jù)庫(kù)連接池是怎么進(jìn)行連接的管理工作的呢?

最大連接數(shù)為1的druid連接池原理概覽:

  • druid維護(hù)一個(gè)數(shù)組來(lái)存放連接
  • 同時(shí)維護(hù)了多個(gè)變量來(lái)檢測(cè)連接池的狀態(tài),其中poolingCount用來(lái)表示池中連接的數(shù)量
  • 當(dāng)有線程來(lái)獲取連接時(shí),需要先加鎖,對(duì)數(shù)量進(jìn)行減一操作。
  • 當(dāng)獲取連接時(shí)發(fā)現(xiàn)數(shù)量為0 ,則返回為空
  • 當(dāng)連接關(guān)閉時(shí),會(huì)將連接資源放回?cái)?shù)組,并對(duì)數(shù)量做加一操作。

*上述只是druid連接池的極簡(jiǎn)版流程敘述,實(shí)際上,還有連接池空等待、滿通知、活躍數(shù)、異常數(shù)等的復(fù)雜判斷。*有興趣的同學(xué)可以看下源碼。

zdal的連接池管理源碼一覽:

  1. public class InternalManagedConnectionPool{ 
  2.    //最大連接數(shù) 
  3.    private final int  maxSize; 
  4.    //用來(lái)存放連接的鏈表 
  5.    private final ArrayList connectionListeners; 
  6.    //內(nèi)部的信號(hào)量,用來(lái)控制允許獲取資源的線程總數(shù) 
  7.    private final InternalSemaphore  permits; 
  8.    //正在使用的連接數(shù)  
  9.    private volatile int  maxUsedConnections = 0; 
  10.  
  11.    protected InternalManagedConnectionPool(...){ 
  12.      //構(gòu)造函數(shù)中,初始化了連接池大小和信號(hào)量大小 
  13.      connectionListeners = new ArrayList(this.maxSize); 
  14.       permits = new InternalSemaphore(this.maxSize); 
  15.  } 

getConnection()方法:

  1. //獲取連接 
  2.  public ConnectionListener getConnection(){ 
  3.     //信號(hào)量嘗試獲取許可 
  4.    if (permits.tryAcquire(poolParams.blockingTimeout, TimeUnit.MILLISECONDS)) { 
  5.          ConnectionListener cl = null
  6.          do { 
  7.          //加鎖資源池 
  8.          synchronized (connectionListeners) { 
  9.  
  10.            if (connectionListeners.size() > 0) { 
  11.                 //獲取list的最后一個(gè) 
  12.                 cl = (ConnectionListener) connectionListeners.remove(connectionListeners.size() - 1); 
  13.                      
  14.                 //最大連接數(shù) 減去 正在工作的信號(hào)量  
  15.                 int size = (maxSize - permits.availablePermits()); 
  16.                 if (size > maxUsedConnections){ 
  17.                      maxUsedConnections = size
  18.                 } 
  19.             } 
  20.            } 
  21.         if (cl != null) { 
  22.          return cl; 
  23.          } 
  24.       }while(connectionListeners.size() > 0); 
  25.  
  26.       //OK, 在連接池中找不到正在工作的連接了. 那就創(chuàng)建個(gè)新的 
  27.       createNewConnection(){...} 
  28.  
  29.   }else
  30.    if (this.maxSize == this.maxUsedConnections) { 
  31.          throw new ResourceException( 
  32.          "數(shù)據(jù)源最大連接數(shù)已滿,并且在超時(shí)時(shí)間范圍內(nèi)沒(méi)有新的連接釋放,poolName = " 
  33.          + poolName 
  34.          + " blocking timeout=" 
  35.          + poolParams.blockingTimeout + 
  36.          "(ms)"); 
  37.   } 
  38.  } 

這里把內(nèi)部連接池的管理類的關(guān)鍵屬性和連接獲取方法流量進(jìn)行了簡(jiǎn)化,連接歸還就不弄了,大同小異,仔細(xì)看,我們看到了什么

  • volatile 標(biāo)識(shí)的maxUsedConnections用來(lái)完成線程間數(shù)據(jù)可見(jiàn)
  • 隸屬于AQS系列的Semaphone,用來(lái)控制共享資源并發(fā)訪問(wèn)量。

都是些常見(jiàn)的八股文,不過(guò)組合起來(lái)可就了不得~

$ 話又又又說(shuō)回來(lái),在druid、zdal中,threadlocal的作用體現(xiàn)在哪里呢?

我們知道,誠(chéng)如druid、zdal等優(yōu)秀的中間件,可不止是數(shù)據(jù)庫(kù)連接池這一個(gè)作用,阿里數(shù)據(jù)庫(kù)中間件zdal源碼解析 文中也有提及。

那么,ThreadLocal能在這里扮演什么角色呢?

就以zdal為例,因?yàn)榘⒗锏臄?shù)據(jù)庫(kù)規(guī)?;径挤浅4?,但又有一套完備的數(shù)據(jù)庫(kù)庫(kù)表拆分規(guī)范,因此,分庫(kù)鍵、分表鍵、主鍵、虛擬表名等在設(shè)計(jì)和存儲(chǔ)時(shí)需要遵循規(guī)范,而zdal中的解析操作,也需要與之相匹配。

這個(gè)解析工作是相對(duì)復(fù)雜且繁重的,然而,針對(duì)同一用戶的操作,通常庫(kù)表的路由是相對(duì)固定的,因此,當(dāng)我們解析過(guò)一次sql,通過(guò)各個(gè)字段和配置規(guī)則,計(jì)算出了庫(kù)表路由,那么,可以直接put進(jìn)線程上下文,供本次請(qǐng)求的后續(xù)數(shù)據(jù)庫(kù)操作使用。

  1. public Object parse(...){ 
  2.     SimpleCondition simpleCondition = new SimpleCondition(); 
  3.     simpleCondition.setVirtualTableName("user"); 
  4.     simpleCondition.put("age", 10); 
  5.     ThreadLocalMap.put(ThreadLocalString.ROUTE_CONDITION, simpleCondition); 
  6.  
  7. public void 后續(xù)操作(){ 
  8.    RouteCondition rc = (RouteCondition) ThreadLocalMap.get(ThreadLocalString.ROUTE_CONDITION); 
  9.     
  10.     if (rc != null) { 
  11.         //不走解析SQL,由ThreadLocal傳入的指定對(duì)象(RouteCondition),決定庫(kù)表目的地 
  12.        metaData = sqlDispatcher.getDBAndTables(rc); 
  13.     } else { 
  14.        // 通過(guò)解析SQL來(lái)分庫(kù)分表 
  15.        try { 
  16.           metaData = sqlDispatcher.getDBAndTables(originalSql, parameters); 
  17.        } catch (ZdalCheckedExcption e) { 
  18.           throw new SQLException(e.getMessage()); 
  19.        } 
  20.   } 

這個(gè)也正好是對(duì)前面ThreadLocal正確使用方法的補(bǔ)充。

起因是對(duì)一篇文章敘述產(chǎn)生疑問(wèn),通過(guò)簡(jiǎn)單的驗(yàn)證,證實(shí)了自己的想法,然后又從幾個(gè)方面對(duì)數(shù)據(jù)庫(kù)連接和threadlocal進(jìn)行了擴(kuò)展,以上,大家如果發(fā)現(xiàn)有任何問(wèn)題,歡迎留言幫忙指正和補(bǔ)充。

 

責(zé)任編輯:武曉燕 來(lái)源: Coder的技術(shù)之路
相關(guān)推薦

2009-06-24 07:53:47

Hibernate數(shù)據(jù)

2009-07-03 17:37:54

JSP數(shù)據(jù)庫(kù)

2010-03-18 15:09:15

python數(shù)據(jù)庫(kù)連接

2009-06-16 09:25:31

JBoss配置

2017-06-22 14:13:07

PythonMySQLpymysqlpool

2019-11-27 10:31:51

數(shù)據(jù)庫(kù)連接池內(nèi)存

2025-04-18 08:54:30

2020-04-30 14:38:51

數(shù)據(jù)庫(kù)連接池線程

2018-10-10 14:27:34

數(shù)據(jù)庫(kù)連接池MySQL

2021-08-12 06:52:01

.NET數(shù)據(jù)庫(kù)連接池

2025-03-21 06:20:00

連接池系統(tǒng)數(shù)據(jù)庫(kù)

2011-07-29 15:11:42

WeblogicOracle數(shù)據(jù)庫(kù)連接

2010-03-18 14:55:17

Python數(shù)據(jù)庫(kù)連接

2009-07-29 09:33:14

ASP.NET數(shù)據(jù)庫(kù)連

2018-01-03 14:32:32

2009-07-17 13:32:49

JDBC數(shù)據(jù)庫(kù)

2011-05-19 09:53:33

數(shù)據(jù)庫(kù)連接池

2025-01-16 10:30:49

2009-06-26 14:41:48

ADO.NET

2009-08-10 17:34:42

C#數(shù)據(jù)庫(kù)連接池
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)