日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
張開濤:超時(shí)與重試機(jī)制(2)

數(shù)據(jù)庫客戶端超時(shí)

站在用戶的角度思考問題,與客戶深入溝通,找到岑鞏網(wǎng)站設(shè)計(jì)與岑鞏網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類型包括:成都網(wǎng)站建設(shè)、網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、域名注冊(cè)、網(wǎng)頁空間、企業(yè)郵箱。業(yè)務(wù)覆蓋岑鞏地區(qū)。

在使用數(shù)據(jù)庫客戶端時(shí),我們會(huì)使用數(shù)據(jù)庫連接池,數(shù)據(jù)庫連接池可以進(jìn)行如下超時(shí)設(shè)置。

 
 
 
 
  1.     
  2.     
  3.  
  4.     
  5.     
  6.              value="connectTimeout=2000; socketTimeout=2000 "/>
  7.     
  8.     

● 網(wǎng)絡(luò)連接/讀超時(shí):使用connectionProperties配置Mysql超時(shí)時(shí)間,如果是Oracle則可以通過如下配置。

 
 
 
 
  1. value="oracle.net.CONNECT_TIMEOUT=2000;oracle.jdbc.ReadTimeout=2000"/>

● 默認(rèn)Statement超時(shí)時(shí)間,通過defaultQueryTimeout配置,單位是秒。

● 從連接池獲取連接的等待時(shí)間,通過maxWaitMillis配置。

● Statement超時(shí),如果使用ibatis,則可以通過如下方式配置Statement超時(shí)。

因此我們只需要如下配置。

 
 
 
 
  1. lazyLoadingEnabled="false"errorTracingEnabled="true" maxRequests="32"
  2. defaultStatementTimeout="2"/>

defaultStatementTimeout單位是秒,根據(jù)業(yè)務(wù)配置。如果數(shù)據(jù)庫連接池配置了,則此處可以不用配置。

如果想只設(shè)置某個(gè)Statement的超時(shí)時(shí)間,則可以考慮:

 
 
 
 

如上配置其實(shí)最終會(huì)調(diào)用Statement.setQueryTimeout方法設(shè)置Statement超時(shí)時(shí)間。

● 事務(wù)超時(shí)是總Statement超時(shí)設(shè)置,比如我們使用Spring管理事務(wù)的話,可以使用如下方式配置全局默認(rèn)的事務(wù)級(jí)別的超時(shí)時(shí)間。

 
 
 
 
  1.   
  2.   

這里我們分析下為什么說事務(wù)超時(shí)是Statement超時(shí)的總和,此處我們分析spring的DataSourceTransactionManager,首先開啟事務(wù)時(shí)會(huì)調(diào)用其doBegin方法。

 
 
 
 
  1. //先獲取@Transactional定義的timeout,如果沒有,則使用defaultTimeout
  2. int timeout =determineTimeout(definition);
  3. if (timeout !=TransactionDefinition.TIMEOUT_DEFAULT) { 
  4.    txObject.getConnectionHolder().setTimeoutInSeconds(timeout); 

其中determineTimeout用來獲取我們?cè)O(shè)置的事務(wù)超時(shí)時(shí)間,然后設(shè)置到ConnectionHolder對(duì)象上(其是ResourceHolder子類),接著看ResourceHolderSupport的setTimeoutInSeconds實(shí)現(xiàn)。

 
 
 
 
  1. public voidsetTimeoutInSeconds(int seconds) {
  2.     setTimeoutInMillis(seconds* 1000);
  3. }
  4.  
  5. public voidsetTimeoutInMillis(long millis) {
  6.     this.deadline = newDate(System.currentTimeMillis() + millis);  
  7. }

大家可以看到,此處會(huì)設(shè)置一個(gè)deadline時(shí)間,用來判斷事務(wù)超時(shí)時(shí)間,那什么時(shí)候調(diào)用呢?首先檢查該類中的代碼,會(huì)發(fā)現(xiàn)。

 
 
 
 
  1. public int getTimeToLiveInSeconds() {
  2.     double diff = ((double) getTimeToLiveInMillis()) /1000;
  3.     int secs = (int) Math.ceil(diff);
  4.     checkTransactionTimeout(secs <= 0);
  5.     return secs;
  6. }
  7.  
  8. public long getTimeToLiveInMillis() throwsTransactionTimedOutException{
  9.     if (this.deadline == null) {
  10.         throw new IllegalStateException("No timeoutspecified for this resource holder");
  11.     }
  12.     long timeToLive = this.deadline.getTime() -System.currentTimeMillis();
  13.     checkTransactionTimeout(timeToLive <= 0);
  14.     return timeToLive;
  15. }
  16.  
  17. private void checkTransactionTimeout(booleandeadlineReached) throws TransactionTimedOutException {
  18.     if (deadlineReached) {
  19.         setRollbackOnly();
  20.        throw newTransactionTimedOutException("Transaction timed out: deadline was " +this.deadline);
  21.     }
  22. }

我們發(fā)現(xiàn)調(diào)用getTimeToLiveInSeconds和getTimeToLiveInMillis會(huì)檢查是否超時(shí),如果超時(shí)了,則標(biāo)記事務(wù)需回滾,并拋出TransactionTimedOutException異常進(jìn)行回滾。

DataSourceUtils.applyTransactionTimeout會(huì)調(diào)用DataSourceUtils. applyTimeout, DataSourceUtils.applyTimeout代碼如下。

 
 
 
 
  1. public static void applyTimeout(Statement stmt,DataSource dataSource, int timeout) throws SQLException {
  2.     ConnectionHolder holder =         (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
  3.     if (holder != null && holder.hasTimeout()){
  4.         // 計(jì)算剩余的事務(wù)超時(shí)時(shí)間覆蓋Statement超時(shí)
  5.        stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());
  6.     } else if (timeout > 0) {
  7.         //如果沒有配置事務(wù)超時(shí),則使用Statement超時(shí)
  8.        stmt.setQueryTimeout(timeout);
  9.     }
  10. }

在stmt.setQueryTimeout(holder.getTimeToLiveInSeconds())時(shí)會(huì)調(diào)用getTimeToLiveIn Seconds(),這會(huì)檢查事務(wù)是否超時(shí)。在JdbcTemplate中,執(zhí)行SQL之前,會(huì)調(diào)用其applyStatementSettings方法,其將調(diào)用DataSourceUtils.applyTimeout(stmt,getDataSource(), getQueryTimeout())設(shè)置超時(shí)時(shí)間。

此處有一個(gè)問題,如果設(shè)置了事務(wù)超時(shí),Statement級(jí)別的就不起作用了,整體會(huì)使用事務(wù)超時(shí)覆蓋Statement超時(shí)。

NoSQL客戶端超時(shí)

對(duì)于MongoDB,我們使用的是spring-data-mongodb客戶端,可以通過如下配置設(shè)置相關(guān)的超時(shí)時(shí)間。

 
 
 
 
  1.    
  2.            connections-per-host="${mongo.connectionsPerHost}"
  3.            threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}"
  4.            max-wait-time="${mongo.maxWaitTime}"
  5.            connect-timeout="${mongo.connectTimeout}"
  6.            socket-timeout="${mongo.socketTimeout}"
  7.            socket-keep-alive="${mongo.socketKeepAlive}"
  8.            auto-connect-retry="${mongo.autoConnectRetry}" />

我們?cè)?jīng)就遇到過因?yàn)椴辉O(shè)置mongodb客戶端timeout而導(dǎo)致服務(wù)響應(yīng)慢的情況。

對(duì)于Redis,我們使用的是Jedis客戶端,可以通過如下配置分配等待獲取連接池連接的超時(shí)時(shí)間和網(wǎng)絡(luò)連接/讀超時(shí)時(shí)間。

 
 
 
 
  1. PoolJedisConnectionFactory connectionFactory = new PoolJedisConnectionFactory();
  2. connectionFactory.setMaxWaitMillis(maxWaitMillis);
  3. connectionFactory.setTimeout(timeoutInMillis);

Jedis在建立Socket時(shí)通過如下代碼設(shè)置超時(shí)。

 
 
 
 
  1. this.socket.connect(new InetSocketAddress(this.host, this.port),this. timeout);
  2. this.socket.setSoTimeout(this.timeout);

可以在JVM啟動(dòng)時(shí)通過添加-Dsun.net.client.defaultConnectTimeout=60000-Dsun.net.client.defaultReadTimeout=60000來配置默認(rèn)全局的Socket連接/讀超時(shí)。即如Httpclient、JDBC等,如果沒有配置socket超時(shí),則默認(rèn)會(huì)使用該超時(shí)。

業(yè)務(wù)超時(shí)

任務(wù)型:比如,訂單超時(shí)未支付取消,超時(shí)活動(dòng)自動(dòng)關(guān)閉等,這屬于任務(wù)型超時(shí),可以通過Worker定期掃描數(shù)據(jù)庫修改狀態(tài)即可。還有如有時(shí)候需要調(diào)用的遠(yuǎn)程服務(wù)超時(shí)了(比如,用戶注冊(cè)成功后,需要給用戶發(fā)放優(yōu)惠券),可以考慮使用隊(duì)列或者暫時(shí)記錄到本地稍后重試。

服務(wù)調(diào)用型:比如,某個(gè)服務(wù)的全局超時(shí)時(shí)間為500ms,但我們有多處服務(wù)調(diào)用,每處的服務(wù)調(diào)用超時(shí)時(shí)間可能不一樣,此時(shí),可以簡(jiǎn)單地使用Future來解決問題,通過如Future.get(3000,TimeUnit.MILLISECONDS)來設(shè)置超時(shí)。

前端Ajax超時(shí)

我們使用jQuery來進(jìn)行Ajax請(qǐng)求,可以在請(qǐng)求時(shí)帶上timeout參數(shù)設(shè)置超時(shí)時(shí)間。

 
 
 
 
  1. $.ajax({
  2.     url:"http://ins.jd.com:9090/test",
  3.     dataType:"jsonp",
  4.     jsonp:"test",
  5.     jsonpCallback:"test",
  6.     timeout:2000,
  7.     success:function(result,status,xhr) {
  8.        //success
  9.     },
  10.     error: function(result,status,xhr){
  11.         if(status== 'timeout') {
  12.             //timeout
  13.         }
  14.     }
  15. });

當(dāng)進(jìn)行跨域JSONP請(qǐng)求時(shí),使用jQuery 1.4.x版本時(shí),IE9、Chrome 52、Firefox 49測(cè)試 JSONP時(shí),請(qǐng)求在超時(shí)后不能被取消,即使客戶端超時(shí)了,該腳本也將一直運(yùn)行;使用jQuery1.5.2時(shí)超時(shí)是起作用了,但是,發(fā)出去的請(qǐng)求是沒有取消的(請(qǐng)求還處于執(zhí)行狀態(tài))。

如還有一種辦法來進(jìn)行超時(shí)重試,通過setTimeout進(jìn)行超時(shí)重試,比如,京東首頁的某個(gè)異步接口,其中一個(gè)域名(A機(jī)房)超時(shí)了,想超時(shí)后通過另一個(gè)域名(B機(jī)房)重新獲取數(shù)據(jù),代碼如下所示。

 
 
 
 
  1. var id = setTimeout(retryCallback, 5000);
  2. $.ajax({
  3.    dataType: 'jsonp',
  4.     success:function() {
  5.        clearTimeout(id);
  6.         ...
  7.     }
  8. });

除了客戶端設(shè)置超時(shí)外,服務(wù)端也一定要配置合理的超時(shí)時(shí)間。

總結(jié)

本文主要介紹了如何在Web應(yīng)用訪問的整個(gè)鏈路上進(jìn)行超時(shí)時(shí)間設(shè)置。通過配置合理的超時(shí)時(shí)間,防止出現(xiàn)某服務(wù)的依賴服務(wù)超時(shí)時(shí)間太長(zhǎng)而響應(yīng)慢,以致自己響應(yīng)慢甚至崩潰。

客戶端和服務(wù)端都應(yīng)該設(shè)置超時(shí)時(shí)間,而且客戶端根據(jù)場(chǎng)景可以設(shè)置比服務(wù)端更長(zhǎng)的超時(shí)時(shí)間。如果存在多級(jí)依賴關(guān)系,如A調(diào)用B,B調(diào)用C,則超時(shí)設(shè)置應(yīng)該是A>B>C,否則可能會(huì)一直重試,引起DDoS攻擊效果。不過最終如何選擇還是要看場(chǎng)景,有時(shí)候客戶端設(shè)置的就是要比服務(wù)端的超時(shí)時(shí)間短,通過在服務(wù)端實(shí)施限流/降級(jí)等手段防止DDoS攻擊。

超時(shí)之后應(yīng)該有相應(yīng)的策略來處理,常見的策略有重試(等一會(huì)兒再試、嘗試其他分組服務(wù)、嘗試其他機(jī)房服務(wù),重試算法可考慮使用如指數(shù)退避算法)、摘掉不存活節(jié)點(diǎn)(負(fù)載均衡/分布式緩存場(chǎng)景下)、托底(返回歷史數(shù)據(jù)/靜態(tài)數(shù)據(jù)/緩存數(shù)據(jù))、等待頁或者錯(cuò)誤頁。

對(duì)于非冪等寫服務(wù)應(yīng)避免重試,或者可以考慮提前生成唯一流水號(hào)來保證寫服務(wù)操作通過判斷流水號(hào)來實(shí)現(xiàn)冪等操作。

在進(jìn)行數(shù)據(jù)庫/緩存服務(wù)器操作時(shí),記得經(jīng)常檢查慢查詢,慢查詢通常是引起服務(wù)出問題的罪魁禍?zhǔn)?。也要考慮在超時(shí)嚴(yán)重時(shí),直接將該服務(wù)降級(jí),待該服務(wù)修復(fù)后再取消降級(jí)。

對(duì)于有負(fù)載均衡的中間件請(qǐng)考慮配置心跳/存活檢查,而不是惰性檢查。

超時(shí)重試必然導(dǎo)致請(qǐng)求響應(yīng)時(shí)間增加,最壞情況下的響應(yīng)時(shí)間=重試次數(shù)×單次超時(shí)時(shí)間,這很可能嚴(yán)重影響到用戶體驗(yàn),導(dǎo)致用戶會(huì)不斷刷新頁面來重復(fù)請(qǐng)求,最后導(dǎo)致服務(wù)接收的請(qǐng)求太多而掛掉,因此除了控制單次超時(shí)時(shí)間,也要控制好用戶能忍受的最壞超時(shí)時(shí)間。

超時(shí)時(shí)間太短會(huì)導(dǎo)致服務(wù)調(diào)用成功率降低,超時(shí)時(shí)間太長(zhǎng)又導(dǎo)致本應(yīng)成功的調(diào)用卻失敗了,這也要根據(jù)實(shí)際場(chǎng)景來選擇最適合當(dāng)前業(yè)務(wù)的,甚至是程序動(dòng)態(tài)自動(dòng)計(jì)算超時(shí)時(shí)間。比如商品詳情頁的庫存狀態(tài)服務(wù),可以設(shè)置較短的超時(shí)時(shí)間,當(dāng)超時(shí)時(shí)降級(jí)返回有貨,而結(jié)算頁服務(wù)就需要設(shè)置稍微長(zhǎng)一些的超時(shí)時(shí)間保證確實(shí)有貨。

在實(shí)際開發(fā)中,不要輕視超時(shí)時(shí)間,很多重大事故都是因?yàn)槌瑫r(shí)時(shí)間不合理導(dǎo)致的,設(shè)置超時(shí)時(shí)間一定是只有好處沒有壞處的,請(qǐng)立即Review你的代碼吧。

【本文是專欄作者“張開濤”的原創(chuàng)文章,作者微信公眾號(hào):開濤的博客( kaitao-1234567)】


文章題目:張開濤:超時(shí)與重試機(jī)制(2)
當(dāng)前鏈接:http://www.5511xx.com/article/cohspgg.html