東風計畫擴建新能源基地 但仍按“舊”規劃發展產品

據報導,東風汽車將在旗下東風乘用車公司擴建一處生產基地,用於新能源汽車製造。

實際上,東風的新能源乘用車產能暫時並不存在缺口。目前,東風乘用車武漢生產基地的年產能為16萬輛,東風乘用車的銷量僅為10萬輛。此外,東風乘用車在常州也建有生產基地,可用於新能源汽車生產。

對此,一位接近東風公司的知情人士表示,東風規劃的新能源汽車生產基地,可能將被用於新產品開發和生產。

仍按“舊”規劃發展產品

實際上,東風很早以前就開始電動汽車研發。其內部資料顯示,東風在“九五”規劃期間就開始電動車技術研發,2010年設立了9個整車項目平臺,包括混合動力和純電動,涉及乘用車和商用車。

據瞭解,主營商用車業務的東風股份2015年新能源商用車銷量達到5191輛,同比增長25倍。此外在商用車領域,東風還推出了純電動客車、物流車等產品,在主流市場均有佈局。

然而,與上汽、北汽等已推出多款新能源乘用車相比,東風在這一領域的反應略顯滯後。

2015年被視作新能源汽車發展元年,銷量開始成倍增長,各品牌相繼加入這一領域,然而,東風依然在按照多年前制定的既定產品路線發展。

據瞭解,目前東風自主品牌的新能源乘用車僅有E30、E30L、A60EV三款車型。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

特斯拉MODEL 3三月底發佈 預估售價約合人民幣22.77萬

據報導,特斯拉將於3月31日發佈全新MODEL 3車型,新車將於同期開始接受預訂,最快有望於2017年晚些時候交付客戶。新車的預估售價約為35000美元(約合人民幣22.77萬元)。

發佈當日,特斯拉將同期開始接受現場預訂,訂金為1000美元(約合人民幣6506元),次日(4月1日)起接受線上預訂,而新車實際交付將會於2017年晚些時候進行。根據消息,預售價為35000美元(約合人民幣22.77萬元)的MODEL 3作為入門車型,其電池續航里程或低於定位更高的MODEL S和MODEL X車型。即將發佈的MODEL 3車型為一款三廂轎車,但未來不排除有跨界版本出現的可能。

特斯拉CEO埃隆•馬斯克曾經表示,MODEL 3未來有望在中國投產,而價格預計只有MODEL S的一半。作為特斯拉的入門車型,MODEL 3將是一款肩負走量任務的產品,適合進行當地語系化生產。未來國產後,其價格可能會下降三分之一。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※評比南投搬家公司費用收費行情懶人包大公開

從代碼的視角深入淺出理解DevOps

對於DevOps的理解大家眾說紛紜,就連維基百科(Wikipedia)都沒有給出一個統一的定義。一般的解釋都是從字面上來理解,就是把開發(Development)和運維(Operations)整合到一起,來加速產品從啟動到上線的過程,並使之自動化。這個是對DevOps的廣義解釋,而且大多數人都是認可的。但這個解釋太寬泛了,幾乎包括了IT的所有內容,使之沒有太大意義。 而DevOps是近幾年才興起的(2014年才開始流行),它是對某種項目模式的描述,是有着其特定內涵的。任何項目都可以分成開發和運維兩個部分,而開發的一整套流程和工具在DevOps之前早就有了,並沒有改變。

DevOps真正改變的是運維。因此從運維的角度去理解DevOps,才能抓住它的本質。你可以把它理解為用開發的方式做運維(Operation as Development),這就是對它的狹義的理解。 開發的方式就是寫代碼,換句話說DevOps就是通過寫代碼來做運維。運維里一個非常流行的概念叫“Iac()” 基礎設施即代碼,也就是把運行環境的創建用代碼的形式來描述出來,通過運行代碼來創建環境。它是運維領域的一場革命,開創了現代運維技術,它也是DevOps的基石。但基礎設施創建只是運維的一部分,如果我們把這場革命繼續延伸到運維的各個領域,讓代碼覆蓋整個運維,那時就是代碼即運維(Operation as Code),這才是DevOps的精髓。

那麼從一個應用程序項目的角度看,什麼是DevOps呢?它就是把應用程序的代碼和運維的代碼都放到一個源程序庫中,並對它進行版本管理,這樣你就擁有了關於這個項目的所有信息,隨時可以部署這個程序(包括程序本身和它的運行環境),而且可以保證每次創建出來的程序的運行結果是一樣的(因為它的運行環境也是一樣的)。

運維即代碼(Operation as Code):

下面我們就把運維所做的事情一件一件拆分開,看看他們是怎麼用代碼來實現的。

運維的工作通常包括下面幾個方面:

  • 基礎設施:即程序運行環境的創建和維護。
  • 持續部署:部署應用程序,並使整個過程自動化。
  • 服務的健壯性:是指當服務的的運行環境出現了問題,例如網絡故障或服務過載或某些微服務宕機的情況下,程序仍能夠提供部分或大部分服務。
  • 運行監測:它既包含對程序的監測也包含對運行環境的監測。

基礎設施即代碼 (Infrastructure as code)

我們通過一個Go(別的語言也大同小異)微服務程序做例子來展示如何用代碼來創建基礎設施。程序本身的功能非常簡單,只是用SQL語句訪問數據庫中的數據,並寫入日誌。你可以簡單地把它分成兩層,後端數據訪問層和數據庫層。程序的部署環境是基於k8s的容器環境。在k8s中它被分成兩個服務。一個是後端程序服務,另一個是數據庫(用MySQL)服務。後端程序要調用數據庫服務,然後會把一些數據寫入日誌。

在這種新的模式下,運行環境的代碼和應用程序的代碼是存在一個源碼庫中的,這樣當你下載了源碼庫之後,你不但擁有了程序的所有源碼,而且也擁有了運行環境的源碼。這樣當要創建新的運行環境時,只要運行一遍代碼就能創建出整套的運行環境,而且每次創建出來的環境都是一致的。

上面就是這個Go程序的目錄結構,它裏面有一個目錄“script”是專門存放與運行環境相關的文件的,裏面的“kuburnetes”子目錄就是整個運行環境的代碼。除了“script”之外的其它目錄存有應用程序的代碼。這樣,與這個應用程序有關的信息都以代碼的方式保存在了一個源代碼庫。有了它之後,你可以隨時部署出一個相同的程序的運行環境,而且保證是一模一樣的。

“kubernetes”目錄下有兩個子目錄“backend”和“database”分別存放後端程序和數據庫的配置文件。它們內部的結構是類似的,都有三個“yaml”文件:

  • backend-deployment.yaml:部署配置文件,
  • backend-service.yaml:服務配置文件
  • backend-volume.yaml:持久卷配置文件.

另外還有一個“.sh”文件是它的運行命令,當你運行這個shell文件時,它就調用上面三個k8s配置文件來創建運行環境。

kubernetes目錄的最外層有兩個“yaml”文件“k8sdemo-config.yaml”和”k8sdemo-secret.yaml”,它們是用來創建k8s運行環境參數的,因為它們是被不同服務共享的,因此放在最外層。另外還有一個”k8sdemo.sh”文件是k8s命令文件,用來創建k8s對象。

這種源程序結構的一個好處就是使應用程序和它的運行環境能夠更好地集成。舉個例子,當你要修改服務的端口時,以前,你需要在運行環境和源碼里分別修改,但它是分別由開發和運維完成的,這很容易造成修改的不同步。當你把它們放在同一個源碼庫中,只需要修改一個地方,這樣就保證了應用程序和運行環境的一致性。

下面就是後端服務的k8s配置代碼:

apiVersion: v1
kind: Service
metadata:
  name: k8sdemo-backend-service
  labels:
    app: k8sdemo-backend
spec:
  type: NodePort
  selector:
    app: k8sdemo-backend
  ports:
    - protocol : TCP
      nodePort: 32080
      port: 80
      targetPort: 8080

由於篇幅的關係,這裏就不詳細解釋程序了,有興趣的請參見.

基礎設施可以分成兩個層面,一個就是上面講到的k8s層面,也就是容器層面,這個是跟應用程序緊密相關的。還有一個層面就是容器下面的支持層,也就是虛機層面,當然還包括網絡,負載均衡等設備或軟件。當你在阿里雲或華為雲上創建k8s之前,先要把這些構建好才行。它的部署也可以用代碼來完成。Terraform就是一款非常流行的用來完成創建的工具,它是被ThoughtWorks推薦的(詳見 ),它支持用代碼來創建虛機。

代碼如下:

resource "aws_instance" "example" {
  count         = 10
  ami           = "ami-v1"
  instance_type = "t2.micro"
}

但在這一層面,基礎設施的工作與應用程序的關聯並沒有那麼緊密,因此這部分的代碼沒有放在應用程序里,你可以單獨創建一個基礎設施的源碼項目,用來存放這部分代碼。

持續部署(Continuous Deployment)

部署應用程序是運維的一項重要工作。隨着商業競爭的加劇,要求更快的程序更新,從原來的的幾周部署一次,到後來的一天部署十幾次甚至幾十次。這樣手工部署就完全不能滿足需要,於是就要把整個流程自動化,這就是持續部署。

管線(pipeline)是一個很重要的概念,它用來描述持續部署的整個步驟和流程。Jenkin是一款非常流行的持續集成和部署工具,它提出了“管線即代碼”(“Pipeline as code”,詳見)。就是把持續部署的管線也作為程序源碼的一部分,和應用程序一起管理起來,讓它有着和應用程序一樣的版本和複審流程。

下面我們就通過一個具體例子來說明他是怎樣實現的。這個例子用的是和上面一樣的程序。先來看一下程序的目錄結構。

與持續部署相關的文件都在“script”目錄下,他被分成兩部分,一個是“cd”子目錄,它存有Jenkins的管線,另一個是“Kubernetes”下的“jenkins”子目錄,它存有Jenkinsde的k8s配置文件。你如果仔細看一下的話會發現它裏面的文件和前面講到的後端程序和數據庫的k8s配置文件很相似,有了它,你就可以在k8s里創建出Jenkins的運行環境。

這裏我把Jenkins的k8s配置文件也放在應用程序里了,但實際上它是應該放在前面提到的基礎設施項目源碼里。因為Jenkins是被應用程序共享的,而不是屬於單獨的一個應用。這裏為了說明方便放在一起了,真正用的時候要把它抽取出來。

下面就是管線的代碼:

def POD_LABEL = "k8sdemopod-${UUID.randomUUID().toString()}"
podTemplate(label: POD_LABEL, cloud: 'kubernetes', containers: [
    containerTemplate(name: 'modified-jenkins', image: 'jfeng45/modified-jenkins:1.0', ttyEnabled: true, command: 'cat')
  ],
  volumes: [
     hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
  ]) {

    node(POD_LABEL) {
       def kubBackendDirectory = "/script/kubernetes/backend"
       stage('Checkout') {
            container('modified-jenkins') {
                sh 'echo get source from github'
                git 'https://github.com/jfeng45/k8sdemo'
            }
          }
       stage('Build image') {
            def imageName = "jfeng45/jenkins-k8sdemo:${env.BUILD_NUMBER}"
            def dockerDirectory = "${kubBackendDirectory}/docker/Dockerfile-k8sdemo-backend"
             container('modified-jenkins') {
               withCredentials([[$class: 'UsernamePasswordMultiBinding',
                 credentialsId: 'dockerhub',
                 usernameVariable: 'DOCKER_HUB_USER',
                 passwordVariable: 'DOCKER_HUB_PASSWORD']]) {
                 sh """
                   docker login -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD}
                   docker build -f ${WORKSPACE}${dockerDirectory} -t ${imageName} .
                   docker push ${imageName}
                   """
               }
             }
           }
       stage('Deploy') {
           container('modified-jenkins') {
               sh "kubectl apply -f ${WORKSPACE}${kubBackendDirectory}/backend-deployment.yaml"
               sh "kubectl apply -f ${WORKSPACE}${kubBackendDirectory}/backend-service.yaml"
             }
       }
    }
}

由於篇幅的關係,這裏不詳細解釋。如果有興趣並想了解如何運行Jenkins來完成持續部署,請參閱 .

這裏我用的是Jenkins軟件,它另外還有一個子項目叫Jenkins-x,是專門為k8s環境量身打造的,它的主要功能是能夠幫助你自動生成管線代碼(你需要回答他的一些問題)。如果你不想自己寫代碼,那麼你可以試一下它,詳情請見。

服務的韌性(Service Resilience as Code)

又叫服務的健壯性,這部分不像前面兩個部分有着公認的名字,英文叫“Service Resilience”,翻譯成中文就五花八門了,我覺得叫服務的韌性比較合適。

“Service Resilience”是指當服務的的運行環境出現了問題,例如網絡故障或服務過載或某些微服務宕機的情況下,程序仍能夠提供部分或大部分服務,這時我們就說服務的韌性很強。它是衡量服務質量的一個重要指標。

這部分的功能包括下面幾個部分:

  • 服務超時 (Timeout)
  • 服務重試 (Retry)
  • 服務限流(Rate Limiting)
  • 熔斷器 (Circuit Breaker)
  • 故障注入(Fault Injection)
  • 艙壁隔離技術(Bulkhead)

這部分與前面兩個部分略有不同,前面兩個部分都是典型的運維任務,而這部分以前是應用程序的一部分,只是這些年才慢慢開始轉移到運維的。

最開始的時候,這些功能都是和程序的業務邏輯混在一起,對業務邏輯的侵入很大,後來,大家開始把這部分邏輯抽取出來,劃分成單獨的一部分。下面通過一個具體的例子(Go微服務程序)來講解:

上圖是程序的目錄結構,它分為客戶端(client)和服務端(server),它們的內部結構是類似的。“middleware”包是實現服務韌性功能的包。 “service”包是業務邏輯包,在服務端就是微服務的實現函數,在客戶端就是調用服務端的函數。在客戶端(client)下的“middleware”包中包含四個文件並實現了三個功能:服務超時,服務重試和熔斷器。“clientMiddleware.go”是總入口。在服務端(server)下的“middleware”包中包含了兩個文件並實現了一個功能,服務限流。“serverMiddleware.go”是總入口。

注意,這裏的服務韌性的功能是完全從業務邏輯中抽出來了,對業務邏輯沒有任何侵入,它是在一個單獨的包(middleware)里實現的。這裏用的是修飾模式。有關程序實現的詳情,請參閱。

上面講的是用程序來實現這些功能,但從本質上來講這些功能不應該屬於應用程序,而是應該由基礎設施來完成。現在公認的看法是,服務網格(Service Mesh)是完成這些功能的最佳方案。使用服務網格的方式和k8s類似,也是創建配置文件,然後通過運行配置文件來建立服務網格的運行環境。我們這裏用Istio來舉例說明,Istio是一款非常流行的服務網格軟件。

上圖就是下載的Istio軟件的目錄,“bookinfo”是它的一個示例程序,在這個例子里,它展示了多種使用Istio的方式,其中就有如何實現服務韌性的方法。詳情請參見.

運行監測 (Monitoring or Observability)

運行監測是運維的一項重要內容,它通常包含如下幾個方面的內容:

  • 日誌(logging): 記錄的是程序運行過程中的信息
  • 跟蹤(tracing): 記錄的是與一條請求相關的信息,特別是請求的與時間有關的信息。
  • 指標(metrics): 與上面的離散的信息不同,這裏記錄的是可累加的信息,一般是按照時間軸進行累加。

我們經常稱之為觀測的三個支柱(Three Pillars of Observabilty),有一篇很不錯的講解它們之間關係的文章,詳情請見””。

這部分的內容可能會有些爭議。因為前幾個部分都是清清楚楚的運維工作,即使服務韌性, 雖然以前是開發的工作,但現在也已經一直公認是運維的事,而且它們的代碼都能很乾凈的摘出來。但運行監測不一樣,雖然主要工作還是由運維來完成,但它的代碼與業務邏輯代碼混在一起,很難摘得清楚。

日誌:

這部分的代碼都是在應用程序里,但日誌的採集,匯總,分析和展示都是由運維來完成。它的代表就是著名的ELK系列。採用DevOps之後,這裏面的變化不大,頂多就是採集代理(Agent)更好地和服務網格或k8s進行集成,使之變得更為容易。

分佈式跟蹤:

這部分有點像服務韌性,開始的時候是由程序完成,慢慢地把它變成單獨的部分與應用程序隔離開,最終大部分的工作交由服務網格來完成。但它又與服務韌性不太相同。服務韌性可以和應用程序做一個非常乾淨的切割,而分佈式跟蹤取決於跟蹤的顆粒度。如果僅是服務之間的跟蹤,就一點問題都沒有,完全可以由服務網格來完成。但如果是服務內部的跟蹤,服務網格就無能為力了,還是要由程序代碼來完成,就像日誌一樣。但我覺得服務之間的跟蹤是投入產出比最高的,大多數情況下有它就足夠了,不必需要服務內部的跟蹤。

詳細情況請參見

Metrics:

這部分觀測的是累加信息。大多數情況下,只要安裝好工具,就能採集數據進行分析。最流行的工具是Prometheus. 你不需要寫代碼來獲取數據,不過你如果想要快速地找到需要的信息,k8s的配置還是要和Prometheus的設置相匹配,因此你需要做一些詳細的設計。詳細情況請參見。

當然你如果有一些更細緻的監測需求,Prometheus不能直接滿足。這時需要在應用程序里插入一些Prometheus的監測代碼來滿足你的需要。

其他工作

是不是還有其他運維工作被漏掉了?

持續集成(Continious Integration)

很多人都把持續集成(CI)算作DevOps的重要組成部分,那是因為他用的是廣義的定義。按照狹義的理解,DevOps只包括運維的內容。持續集成(CI)與持續部署(CD)有着明顯的不同,持續集成是開發的工作,而持續部署是運維的工作,下圖展示了它們的差異。

如圖所示,整個流程是這樣的:
程序員從源碼庫(Source Control)中下載源代碼,編寫程序,完成后提交代碼到源碼庫,持續集成(Continuous Integration)工具從源碼庫中下載源代碼,編譯源代碼,然後提交到運行庫(Repository),然後持續交付(Continuous Delivery)工具從運行庫(Repository)中下載代碼,生成發布版本,併發布到不同的運行環境(例如DEV,QA,UAT, PROD)。

圖中,左邊的部分是持續集成,它主要跟開發和程序員有關;右邊的部分是持續部署,它主要跟測試和運維有關。持續交付(Continuous Delivery)又叫持續部署(Continuous Deployment),它們如果細分的話還是有一點區別的,但我們這裏不分得那麼細,統稱為持續部署

我並沒有把持續集成放到DevOps裏面,因為本文用的是狹義的解釋,也就是只包含運維的部分。

結論

本文從代碼的視角詮釋了對DevOps的理解,DevOps的精髓就是用寫代碼的方式來做運維,並對運維的各個部分給出了具體的實例,希望能對想採用DevOps的朋友有所幫助。DevOps對開發和運維的改變都是巨大的,尤其是對運維。在不久的將來,就沒有開發和運維之分了,只有一個工作,就是寫代碼,當然也許會細分成開發碼農和運維碼農。運維的工作都是通過寫代碼來完成。應用程序里不但包括業務邏輯的代碼,也包括運維的代碼,它們會被同時存儲在一個源碼庫中。

源碼庫

完整源碼的github鏈接:

索引:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

Java基礎系列6:深入理解Java異常體系

該系列博文會告訴你如何從入門到進階,一步步地學習Java基礎知識,並上手進行實戰,接着了解每個Java知識點背後的實現原理,更完整地了解整個Java技術體系,形成自己的知識框架。

 

前言:

Java的基本理念是“結構不佳的代碼不能運行”。

“異常”這個詞有“我對此感到意外”的意思。問題出現了,你也許不清楚該如何處理,但你的確知道不應該置之不理;你要停下來,看看是不是有別人或在別的地方,能夠處理這個問題。只是在當前的環境中還沒有足夠的信息來解決這個問題,所以就把這個問題提交到一個更高級別的環境中,在這裏將作出正確的決定。

使用異常所帶來的另一個相當明顯的好處是,它往往能夠降低錯誤處理代碼的複雜度。如果不使用異常,那麼就必須檢查特定的錯誤,並在程序中的許多地方去處理它。而如果使用異常,那就不必在方法調用處進行檢查,因為異常機制將保證能夠捕獲這個錯誤。並且,只需在一個地方處理錯誤,即所謂的異常處理程序中。這種方式不僅節省代碼,而且把“描述在正常執行過程中做什麼事”的代碼和“出了問題怎麼辦”的代碼相分離。總之,與以前的錯誤處理方法相比,異常機制使代碼的閱讀、編寫和調試工作更加井井有條。

 

異常概述:

現在我們需要編寫一個五子棋程序,當用戶輸入下期坐標時,程序要判斷用戶輸入是否合法,如果保證程序有較好的容錯性,將會有如下的代碼(偽代碼):

if(用戶輸入包含除逗號之外的其他非数字字符)
{
	alert 坐標只能是數值
	goto retry
}
else if(用戶輸入不包含逗號) {
	alert 應使用逗號分隔兩個坐標值
	goto retry
}

else if(用戶輸入坐標值超出了有效範圍) {
	alert 用戶輸入坐標應位於棋盤坐標之內
	goto retry
}

else if(用戶輸入的坐標已有棋子)
{
	alert 只能在沒有棋子的地方下棋
	goto retry
}
else {
	.....
}

  

上面代碼還未涉及任何有效處理,只是考慮了4種可能的錯誤,代碼就已經急劇增加了。但實際上,上面考慮的4種情形還遠未考慮到所有的可能情形(事實上,世界上的意外是不可窮舉的),程序可能發生的異常情況總是大於程序員所能考慮的意外情況。

對於上面的錯誤處理機制,主要有以下兩個缺點:

  • 無法窮舉所有的異常情況。因為人類知識的限制,異常情況總比可以考慮到的情況多,總有“漏網之魚”的異常情況,所以程序總是不夠健壯。
  • 錯誤處理代碼和業務實現代碼混雜。這種錯誤處理和業務實現混雜的代碼嚴重影響程序的可讀性,會增加程序維護的難度。

我們希望有這樣一種處理機制:

if(用戶輸入的數據不合法){
    .....
}else{
   處理邏輯
   .....  
}

  

上面偽碼提供了一個非常強大的“if塊”——程序不管輸入錯誤的原因是什麼,只要用戶輸入不滿足要求,程序就一次處理所有的錯誤。這種處理方法的好處是,使得錯誤處理代碼變得更有條理,只需在一個地方處理錯誤。

這就需要用到java異常了。

 

異常是程序中的一些錯誤,但並不是所有的錯誤都是異常,並且錯誤有時候是可以避免的。

比如說,你的代碼少了一個分號,那麼運行出來結果是提示是錯誤java.lang.Error;如果你用System.out.println(11/0),那麼你是因為你用0做了除數,會拋出java.lang.ArithmeticException的異常。

異常發生的原因有很多,通常包含以下幾大類:

  • 用戶輸入了非法數據。
  • 要打開的文件不存在。
  • 網絡通信時連接中斷,或者JVM內存溢出。

 

Java中的異常有以下三種類型:

檢查異常:最具代表的檢查性異常是用戶錯誤或問題引起的異常,這是程序員無法預見的。例如要打開一個不存在文件時,一個異常就發生了,這些異常在編譯時不能被簡單地忽略。

運行異常:運行時異常是可能被程序員避免的異常。與檢查性異常相反,運行時異常可以在編譯時被忽略。

錯誤:錯誤不是異常,而是脫離程序員控制的問題。錯誤在代碼中通常被忽略。例如,當棧溢出時,一個錯誤就發生了,它們在編譯也檢查不到的。

 

異常的體繫結構:

我們先來統觀以下Java的異常體繫結構圖:

 

 

Java的異常被分為兩大類:Checked異常和Runtime異常(運行時異常)。所有的RuntimeException類及其子類的實例被稱為Runtime異常;不是RuntimeException類及其子類的異常實例則被稱為Checked異常。

只有Java語言提供了Checked異常,其他語言都沒有提供Checked異常。Java認為Checked異常都是可以被處理(修復)的異常,所以Java程序必須顯式處理Checked異常。如果程序沒有處理Checked異常,該程序在編譯時就會發生錯誤,無法通過編譯。

 

Throwable

Java異常的頂級類,所有的類都繼承自Throwable

Error:

Error錯誤,一般是指與虛擬機相關的問題,如系統崩潰、虛擬機錯誤、動態鏈接失敗等,這種錯誤無法恢復或不可能捕獲,將導致應用程序中斷。通常應用程序無法處理這些錯誤,因此應用程序不應該試圖使用catch 塊來捕獲Error對象。
在定義該方法時,也無須在其throws子句中聲明該方法可能拋出Error及其任何子類。

 

Exception:

Exception中異常主要分為兩大類,運行時異常和檢查異常。常見的運行時異常有ArrayIndexOutOfBoundsException(數組下標越界)、NullPointerException(空指針異常)、ArithmeticException(算術異常)、ClassNotFoundException(類型找不到),這些異常時非檢查異常,程序可以選擇處理,也可以不處理,編譯器編譯時期並不會報錯。這些異常一般是由於程序邏輯錯誤引起的,所以建議程序員還是處理一下。除運行時異常外的所有異常我們都稱為非運行時異常,也是必須處理的異常,如果不出來,程序編譯會報錯。

 

Error和Exception的區別:

ErrorException的區別:Error通常是災難性的致命的錯誤,是程序無法控制和處理的,當出現這些異常時,Java虛擬機(JVM)一般會選擇終止線程;Exception通常情況下是可以被程序處理的,並且在程序中應該盡可能的去處理這些異常。

 

檢查異常:必須處理的異常

除了RuntimeException及其子類以外,其他的Exception類及其子類都屬於檢查異常,當程序中可能出現這類異常,要麼使用try-catch語句進行捕獲,要麼用throws子句拋出,否則編譯無法通過。

 

非檢查異常:可以不處理

包括RuntimeException及其子類和Error

不受檢查異常為編譯器不要求強制處理的異常,檢查異常則是編譯器要求必須處置的異常。

 

異常處理機制:

Java的異常處理機制可以讓程序具有極好的容錯性,讓程序更加健壯。當程序運行出現意外情形時,系統會自動生成一個Exception對象來通知程序,從而實現將“業務功能實現代碼”和“錯誤處理代碼”分離,提供更好的可讀性。

java異常關鍵字:

  • try – 用於監聽。try后緊跟一個花括號括起來的代碼塊(花括號不可省略),簡稱try塊,它裏面放置可能引發異常的代碼,當try語句塊內發生異常時,異常就被拋出。【監控區域】
  • catch – 用於捕獲異常。catch后對應異常類型和一個代碼塊,用於處理try塊發生對應類型的異常。【異常處理程序】
  • finally – 用於清理資源,finally語句塊總是會被執行。 多個catch塊后還可以跟一個finally塊,finally塊用於回收在try塊里打開的物理資源(如數據庫連接、網絡連接和磁盤文件)。只有finally塊執行完成之後,才會回來執行try或者catch塊中的return或者throw語句,如果finally中使用了return或者throw等終止方法的語句,則就不會跳回執行,直接停止。【使用finally進行清理】
  • throw – 用於拋出一個實際的異常。throw可以單獨作為語句使用,拋出一個具體的異常對象。【拋出異常】
  • throws –用在方法簽名中,用於聲明該方法可能拋出的異常。【異常說明】

 

1、使用try…catch捕獲異常:

語法格式:

try{
   //業務實現代碼
   ...
}catch(Exception e){
  //異常處理代碼
  ...
}

  

如果執行try塊里的業務邏輯代碼時出現異常,系統自動生成一個異常對象,該異常對象被提交給Java運行時環境,這個過程被稱為拋出(throw)異常。

當Java運行時環境收到異常對象時,會尋找能處理該異常對象的catch塊,如果找到合適的catch塊,則把該異常對象交給該catch塊處理,這個過程被稱為捕獲(catch)異常;如果Java運行時環境找不到捕獲異常的catch塊,則運行時環境終止Java程序也將退出。

下面看幾個簡單的異常捕獲的例子:

例1:

public class DivTest {
	
	public static void main(String[] args) {
		try {
			int a=Integer.parseInt(args[0]);
			int b=Integer.parseInt(args[1]);
			int c=a/b;
			System.out.println("您輸入的兩個數相除的結果是"+c);
		}catch(IndexOutOfBoundsException e) {
			System.out.println("數組越界,運行時參數不夠");
		}catch(NumberFormatException e) {
			System.out.println("数字格式異常");
		}catch(ArithmeticException e) {
			System.out.println("算術異常");
		}catch(Exception e) {
			System.out.println("未知異常");
		}
	}

}

  

  • 如果運行該程序時輸入的參數不夠,將會發生數組越界異常,Java運行時將調用IndexOutOfBoundsException對應的catch塊處理該異常。
  • 如果運行該程序時輸入的參數不是数字,而是字母,將發生数字格式異常,Java運行時將調用NumberFormatException 對應的catch塊處理該異常。
  • 如果運行該程序時輸入的第二個參數是0,將發生除0異常,Java運行時將調用ArithmeticException對應的catch塊處理該異常。
  • 如果程序運行時出現其他異常,該異常對象總是Exception類或其子類的實例,Java運行時將調用Exception對應的catch塊處理該異常。

上面程序中的三種異常是我們在編程中經常遇見的,讀者應該掌握這些異常。

例2:

import java.util.Date;


public class Test {
	
	public static void main(String[] args) {
		Date d=null;
		try {
			System.out.println(d.after(new Date()));
		}catch(NullPointerException e) {
			System.out.println("空指針異常");
		}catch(Exception e) {
			System.out.println("未知異常");
		}
	}

}

  

上面程序針對NullPointerException異常提供了專門的異常處理塊。上面程序調用一個null對象的after0方法,這將引發NullPointerException異常(當試圖調用一個null對象的實例方法或實例變量時,就會引發NullPointerException異常),Java 運行時將會調用NullPointerException對應的catch塊來處理該異常;如果程序遇到其他異常,Java運行時將會調用最後的catch塊來處理異常。

catch塊處理異常遵循着:先小后大,即先子類後父類。正如在前面程序所看到的,程序總是把對應Exception類的catch塊放在最後,這是為什麼呢?讀者可能明白原因:如果把Exception類對應的catch塊排在其他catch塊的前面,Java運行時將直接進入該catch塊(因為所有的異常對象都是Exception或其子類的實例),而排在它後面的catch塊將永遠也不會獲得執行的機會。

 

多異常捕獲:

在Java7之前,每個catch塊只能捕獲一個異常,Java7之後,每個catch塊可以捕獲多種類型的異常。

上面的例1可以改成如下代碼實現:

public class Test {
	
	public static void main(String[] args) {
		try {
			int a=Integer.parseInt(args[0]);
			int b=Integer.parseInt(args[1]);
			int c=a/b;
			System.out.println("您輸入的兩個數相除的結果是"+c);
		}catch(IndexOutOfBoundsException|NumberFormatException|ArithmeticException e) {
			System.out.println("數組越界,数字格式異常,算術異常");
		}catch(Exception e) {
			System.out.println("未知異常");
		}
	}

}

  

2、使用throws聲明拋出異常:

使用throws聲明拋出異常的思路是,當前方法不知道如何處理這種類型的異常,該異常應該由上一級調用者處理;如果main方法也不知道如何處理這種類型的異常,也可以使用throws聲明拋出異常,該異常將交給JVM處理。JVM對異常的處理方法是,打印異常的跟蹤棧信息,並中止程序運行,這就是前面程序在遇到異常后自動結束的原因。

import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class Test {
	
	public static void main(String[] args) throws FileNotFoundException {
		FileInputStream fis=new FileInputStream("a.txt");
	}

}

  

上面程序聲明不處理IOException異常,將該異常交給JVM處理,所以程序一旦遇到該異常,JVM就會打印該異常的跟蹤棧信息,並結束程序。運行上面程序,會看到如下圖所示的運行結果。

 

 

3、使用throw拋出異常:

public class Test {
	
	public static void main(String[] args) throws Exception {
		try {
			int a=Integer.parseInt(args[0]);
			int b=Integer.parseInt(args[1]);
			int c=a/b;
			if(b==0) {
				throw new Exception("除數不能為0");
			}
			System.out.println("您輸入的兩個數相除的結果是"+c);
		}catch(Exception e) {
			System.out.println("未知異常");
		}
	}

}

  

上面程序中粗體字代碼使用throw語句來自行拋出異常。當Java運行時接收到開發者自行拋出的異常時,同樣會中止當前的執行流,跳到該異常對應的catch塊,由該catch塊來處理該異常。也就是說,不管是系統自動拋出的異常,還是程序員手動拋出的異常,Java運行時環境對異常的處理沒有任何差別。

 

4、訪問異常信息:

如果程序需要在catch塊中訪問異常對象的相關信息,則可以通過訪問catch塊的后異常形參來獲得。當Java運行時決定調用某個catch塊來處理該異常對象時,會將異常對象賦給catch塊后的異常參數,程序即可通過該參數來獲得異常的相關信息。

所有的異常對象都包括如下幾個常用的方法:

getMessage():返回該異常信息的跟蹤棧信息輸出到標準錯誤輸出

printStackTrace():將該異常的跟蹤棧信息輸出到標準錯誤輸出。

printStackTrace(PrintStream s):將該異常的跟蹤棧信息輸出到指定輸出流。

getStackTrace():返回該異常的跟蹤棧信息。

 

5、使用finally回收資源:

有些時候,程序在try塊里打開了一些物理資源(例如數據庫連接、網絡連接和磁盤文件),這些物理資源都必須显示回收。

在哪裡回收這些物理資源呢?在try塊里回收?還是在catch塊中進行回收?假設程序在try塊里進行資源回收,根據圖10.1所示的異常捕獲流程—一如果try塊的某條語句引起了異常,該語句后的其他語句通常不會獲得執行的機會,這將導致位於該語句之後的資源回收語句得不到執行。如果在catch塊里進行資源回收,但catch塊完全有可能得不到執行,這將導致不能及時回收這些物理資源。

為了保證一定能回收try塊中打開的物理資源,異常處理機制提供了finally塊。不管try塊中的代碼是否出現異常,也不管哪一個catch塊被執行,甚至在try塊或catch塊中執行了return語句,finally塊總會被執行。完整的Java異常處理語法結構如下:

try{
  //業務實現代碼
  ...
}catch(SubException e){
  //異常處理塊
  ...
}catch(SubException e2){
   //異常處理塊
   ...
}finally{
   //資源回收
   ...
}

  

例如:

public class Test {
	
	public static void main(String[] args) throws Exception {
		try {
			int a=10;
			int b=0;
			int c=a/b;
		}catch(Exception e) {
			System.out.println("未知異常");
		}finally {
			System.out.println("資源回收");
		}
	}

}

  

結果:

未知異常
資源回收

  

 

總結:

 

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

mysql 排它鎖之行鎖、間隙鎖、后碼鎖

MySQL InnoDB支持三種行鎖定

  • 行鎖(Record Lock):鎖直接加在索引記錄上面,鎖住的是key。

  • 間隙鎖(Gap Lock):鎖定索引記錄間隙,確保索引記錄的間隙不變。間隙鎖是針對事務隔離級別為可重複讀或以上級別而設計的。

  • 后碼鎖(Next-Key Lock):行鎖和間隙鎖組合起來就叫Next-Key Lock。

默認情況下,InnoDB工作在可重複讀隔離級別下,並且會以Next-Key Lock的方式對數據行進行加鎖,這樣可以有效防止幻讀的發生。Next-Key Lock是行鎖和間隙鎖的組合,當InnoDB掃描索引記錄的時候,會首先對索引記錄加上行鎖(Record Lock),再對索引記錄兩邊的間隙加上間隙鎖(Gap Lock)。加上間隙鎖之後,其他事務就不能在這個間隙修改或者插入記錄。

行鎖(Record Lock)

  • 當需要對表中的某條數據進行寫操作(insert、update、delete、select for update)時,需要先獲取記錄的排他鎖(X鎖),這個就稱為行鎖。
create table x(`id` int, `num` int, index `idx_id` (`id`));
insert into x values(1, 1), (2, 2);

-- 事務A
START TRANSACTION;
update x set id = 1 where id = 1;

-- 事務B
-- 如果事務A沒有commit,id=1的記錄拿不到X鎖,將出現等待
START TRANSACTION;
update x set id = 1 where id = 1;

-- 事務C
-- id=2的記錄可以拿到X鎖,不會出現等待
START TRANSACTION;
update x set id = 2 where id = 2;
  • 針對InnoDB RR隔離級別,上述SQL示例展示了行鎖的特點:“鎖定特定行不允許進行修改”,但行鎖是基於表索引的,如果where條件中用的是num字段(非索引列)將產生不一樣的現象:
-- 事務A
START TRANSACTION;
update x set num = 1 where num = 1;

-- 事務B
-- 由於事務A中num字段上沒有索引將產生表鎖,導致整張表的寫操作都會出現等待
START TRANSACTION;
update x set num = 1 where num = 1;

-- 事務C
-- 同理,會出現等待
START TRANSACTION;
update x set num = 2 where num = 2;

-- 事務D
-- 等待
START TRANSACTION;
insert into x values(3, 3);

Gap鎖(Gap Lock)

在MySQL中select稱為快照讀,不需要鎖,而insert、update、delete、select for update則稱為當前讀,需要給數據加鎖,幻讀中的“讀”即是針對當前讀。

RR事務隔離級別允許存在幻讀,但InnoDB RR級別卻通過Gap鎖避免了幻讀

產生間隙鎖的條件(RR事務隔離級別下)

  • 使用普通索引鎖定
  • 使用多列唯一索引
  • 使用唯一索引鎖定多行記錄

唯一索引的間隙鎖

測試環境

MySQL,InnoDB,默認的隔離級別(RR)

數據表

CREATE TABLE `test` (
  `id` int(1) NOT NULL AUTO_INCREMENT,
  `name` varchar(8) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

數據

INSERT INTO `test` VALUES ('1', '小羅');
INSERT INTO `test` VALUES ('5', '小黃');
INSERT INTO `test` VALUES ('7', '小明');
INSERT INTO `test` VALUES ('11', '小紅');

以上數據,會生成隱藏間隙

(-infinity, 1]
(1, 5]
(5, 7]
(7, 11]
(11, +infinity]

只使用記錄鎖,不會產生間隙鎖

/* 開啟事務1 */
BEGIN;
/* 查詢 id = 5 的數據並加記錄鎖 */
SELECT * FROM `test` WHERE `id` = 5 FOR UPDATE;
/* 延遲30秒執行,防止鎖釋放 */
SELECT SLEEP(30);

-- 注意:以下的語句不是放在一個事務中執行,而是分開多次執行,每次事務中只有一條添加語句

/* 事務2插入一條 name = '小張' 的數據 */
INSERT INTO `test` (`id`, `name`) VALUES (4, '小張'); # 正常執行

/* 事務3插入一條 name = '小張' 的數據 */
INSERT INTO `test` (`id`, `name`) VALUES (8, '小東'); # 正常執行

/* 提交事務1,釋放事務1的鎖 */
COMMIT;

以上,由於主鍵是唯一索引,而且是只使用一個索引查詢,並且只鎖定一條記錄,所以,只會對 id = 5 的數據加上記錄鎖,而不會產生間隙鎖。

產生間隙鎖

/* 開啟事務1 */
BEGIN;
/* 查詢 id 在 7 - 11 範圍的數據並加記錄鎖 */
SELECT * FROM `test` WHERE `id` BETWEEN 5 AND 7 FOR UPDATE;
/* 延遲30秒執行,防止鎖釋放 */
SELECT SLEEP(30);

-- 注意:以下的語句不是放在一個事務中執行,而是分開多次執行,每次事務中只有一條添加語句

/* 事務2插入一條 id = 3,name = '小張1' 的數據 */
INSERT INTO `test` (`id`, `name`) VALUES (3, '小張1'); # 正常執行

/* 事務3插入一條 id = 4,name = '小白' 的數據 */
INSERT INTO `test` (`id`, `name`) VALUES (4, '小白'); # 正常執行

/* 事務4插入一條 id = 6,name = '小東' 的數據 */
INSERT INTO `test` (`id`, `name`) VALUES (6, '小東'); # 阻塞

/* 事務5插入一條 id = 8, name = '大羅' 的數據 */
INSERT INTO `test` (`id`, `name`) VALUES (8, '大羅'); # 阻塞

/* 事務6插入一條 id = 9, name = '大東' 的數據 */
INSERT INTO `test` (`id`, `name`) VALUES (9, '大東'); # 阻塞

/* 事務7插入一條 id = 11, name = '李西' 的數據 */
INSERT INTO `test` (`id`, `name`) VALUES (11, '李西'); # 阻塞

/* 事務8插入一條 id = 12, name = '張三' 的數據 */
INSERT INTO `test` (`id`, `name`) VALUES (12, '張三'); # 正常執行

/* 提交事務1,釋放事務1的鎖 */
COMMIT;

從上面我們可以看到,(5, 7]、(7, 11] 這兩個區間,都不可插入數據,其它區間,都可以正常插入數據。所以當我們給 (5, 7] 這個區間加鎖的時候,會鎖住 (5, 7]、(7, 11] 這兩個區間。

鎖住不存在的數據

/* 開啟事務1 */
BEGIN;
/* 查詢 id = 3 這一條不存在的數據並加記錄鎖 */
SELECT * FROM `test` WHERE `id` = 3 FOR UPDATE;
/* 延遲30秒執行,防止鎖釋放 */
SELECT SLEEP(30);

-- 注意:以下的語句不是放在一個事務中執行,而是分開多次執行,每次事務中只有一條添加語句

/* 事務2插入一條 id = 3,name = '小張1' 的數據 */
INSERT INTO `test` (`id`, `name`) VALUES (2, '小張1'); # 阻塞

/* 事務3插入一條 id = 4,name = '小白' 的數據 */
INSERT INTO `test` (`id`, `name`) VALUES (4, '小白'); # 阻塞

/* 事務4插入一條 id = 6,name = '小東' 的數據 */
INSERT INTO `test` (`id`, `name`) VALUES (6, '小東'); # 正常執行

/* 事務5插入一條 id = 8, name = '大羅' 的數據 */
INSERT INTO `test` (`id`, `name`) VALUES (8, '大羅'); # 正常執行

/* 提交事務1,釋放事務1的鎖 */
COMMIT;

我們可以看出,指定查詢某一條記錄時,如果這條記錄不存在,會產生間隙鎖

結論

  • 對於指定查詢某一條記錄的加鎖語句,如果該記錄不存在,會產生記錄鎖和間隙鎖,如果記錄存在,則只會產生記錄鎖,如:WHERE id = 5 FOR UPDATE;
  • 對於查找某一範圍內的查詢語句,會產生間隙鎖,如:WHERE id BETWEEN 5 AND 7 FOR UPDATE;

普通索引的間隙鎖

數據準備

創建 test1 表:

  • 注意:number 不是唯一值
CREATE TABLE `test1` (
  `id` int(1) NOT NULL AUTO_INCREMENT,
  `number` int(1) NOT NULL COMMENT '数字',
  PRIMARY KEY (`id`),
  KEY `number` (`number`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

id 是主鍵,number上建立了一個普通索引。先加一些數據:

INSERT INTO `test1` VALUES (1, 1);
INSERT INTO `test1` VALUES (5, 3);
INSERT INTO `test1` VALUES (7, 8);
INSERT INTO `test1` VALUES (11, 12);

test1表中 number 索引存在的隱藏間隙:

(-infinity, 1]
(1, 3]
(3, 8]
(8, 12]
(12, +infinity]

執行以下的事務(事務1最後提交)

/* 開啟事務1 */
BEGIN;
/* 查詢 number = 5 的數據並加記錄鎖 */
SELECT * FROM `test1` WHERE `number` = 3 FOR UPDATE;
/* 延遲30秒執行,防止鎖釋放 */
SELECT SLEEP(30);

-- 注意:以下的語句不是放在一個事務中執行,而是分開多次執行,每次事務中只有一條添加語句

/* 事務2插入一條 number = 0 的數據 */
INSERT INTO `test1` (`number`) VALUES (0); -- 正常執行

/* 事務3插入一條 number = 1 的數據 */
INSERT INTO `test1` (`number`) VALUES (1); -- 被阻塞

/* 事務4插入一條 number = 2 的數據 */
INSERT INTO `test1` (`number`) VALUES (2); -- 被阻塞

/* 事務5插入一條 number = 4 的數據 */
INSERT INTO `test1` (`number`) VALUES (4); -- 被阻塞

/* 事務6插入一條 number = 8 的數據 */
INSERT INTO `test1` (`number`) VALUES (8); -- 正常執行

/* 事務7插入一條 number = 9 的數據 */
INSERT INTO `test1` (`number`) VALUES (9); -- 正常執行

/* 事務8插入一條 number = 10 的數據 */
INSERT INTO `test1` (`number`) VALUES (10); -- 正常執行

/* 提交事務1 */
COMMIT;

這裏可以看到,number (1 – 8) 的間隙中,插入語句都被阻塞了,而不在這個範圍內的語句,正常執行,這就是因為有間隙鎖的原因。

加深對間隙鎖的理解

將數據還原成初始化的那樣

/* 開啟事務1 */
BEGIN;
/* 查詢 number = 5 的數據並加記錄鎖 */
SELECT * FROM `test1` WHERE `number` = 3 FOR UPDATE;
/* 延遲30秒執行,防止鎖釋放 */
SELECT SLEEP(30);

/* 事務1插入一條 id = 2, number = 1 的數據 */
INSERT INTO `test1` (`id`, `number`) VALUES (2, 1); -- 阻塞

/* 事務2插入一條 id = 3, number = 2 的數據 */
INSERT INTO `test1` (`id`, `number`) VALUES (3, 2); -- 阻塞

/* 事務3插入一條 id = 6, number = 8 的數據 */
INSERT INTO `test1` (`id`, `number`) VALUES (6, 8); -- 阻塞

/* 事務4插入一條 id = 8, number = 8 的數據 */
INSERT INTO `test1` (`id`, `number`) VALUES (8, 8); -- 正常執行

/* 事務5插入一條 id = 9, number = 9 的數據 */
INSERT INTO `test1` (`id`, `number`) VALUES (9, 9); -- 正常執行

/* 事務6插入一條 id = 10, number = 12 的數據 */
INSERT INTO `test1` (`id`, `number`) VALUES (10, 12); -- 正常執行

/* 事務7修改 id = 11, number = 12 的數據 */
UPDATE `test1` SET `number` = 5 WHERE `id` = 11 AND `number` = 12; -- 阻塞

/* 提交事務1 */
COMMIT;

這裡有一個奇怪的現象:

事務3添加 id = 6,number = 8 的數據,給阻塞了;
事務4添加 id = 8,number = 8 的數據,正常執行了。
事務7將 id = 11,number = 12 的數據修改為 id = 11, number = 5的操作,給阻塞了;

這是為什麼呢?我們來看看下邊的圖

從圖中可以看出,當 number 相同時,會根據主鍵 id 來排序,所以:

事務3添加的 id = 6,number = 8,這條數據是在 (3, 8) 的區間裡邊,所以會被阻塞;
事務4添加的 id = 8,number = 8,這條數據則是在(8, 12)區間裡邊,所以不會被阻塞;
事務7的修改語句相當於在 (3, 8) 的區間裡邊插入一條數據,所以也被阻塞了。

結論

  • 在普通索引列上,不管是何種查詢,只要加鎖,都會產生間隙鎖,這跟唯一索引不一樣
  • 在普通索引跟唯一索引中,數據間隙的分析,數據行是優先根據普通索引排序,再根據唯一索引排序

后碼鎖(Next-key Locks)

后碼鎖是記錄鎖與間隙鎖的組合,它的封鎖範圍,既包含索引記錄,又包含索引區間。

注:Next-key Lock的主要目的,也是為了避免幻讀(Phantom Read)。如果把事務的隔離級別降級為RC,Next-key Lock則也會失效。

總結

  • 記錄鎖、間隙鎖、后碼鎖,都屬於排它鎖;
  • 記錄鎖就是鎖住一行記錄;
  • 間隙鎖只有在事務隔離級別 RR 中才會產生;
  • 唯一索引只有鎖住多條記錄或者一條不存在的記錄的時候,才會產生間隙鎖,指定給某條存在的記錄加鎖的時候,只會加記錄鎖,不會產生間隙鎖;
  • 普通索引不管是鎖住單條,還是多條記錄,都會產生間隙鎖;
  • 間隙鎖會封鎖該條記錄相鄰兩個鍵之間的空白區域,防止其它事務在這個區域內插入、修改、刪除數據,這是為了防止出現 幻讀 現象;
  • 普通索引的間隙,優先以普通索引排序,然後再根據主鍵索引排序;
  • 事務級別是RC(讀已提交)級別的話,間隙鎖將會失效。

資料

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※評比南投搬家公司費用收費行情懶人包大公開

澳洲野火失控 聖誕期間消防員仍備戰

整理:劉妙慈 (環境資訊中心實習編輯)

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

枯萎病摧毀突尼西亞海綿 漁民生計飽受威脅

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

圈養不人道 海洋世界取消虎鯨表演

摘錄自2019年12月25日東網報導

外形搶眼的虎鯨素來是水族館明星,惟圈養做法引來不合人道的質疑。美國佛羅里達州「奧蘭多海洋世界」(SeaWorld Orlando)近日宣布,明年元旦將取締虎鯨表演節目,改由介紹虎鯨的參觀項目取而代之。

虎鯨節目「一個海洋」自2011年推出,是一個主題圍繞保育的23分鐘表演,亦是海洋世界首個沒有訓練員在水中參與的項目。該節目下周二(31日)除夕將上演最後一場,其後會改為舉行名為「邂逅虎鯨」的參觀項目。

海洋世界的動物學團隊主管多爾德(Chris Dold)則介紹,新節目將向遊客解釋虎鯨在海洋生態中的角色,以及保護其棲息地的重要性等;又指節目具教育價值,與公園致力推廣動物救援及保護的信念相符。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※評比南投搬家公司費用收費行情懶人包大公開

5種常見Bean映射工具的性能比對

本文由 JavaGuide 翻譯自 https://www.baeldung.com/java-performance-mapping-frameworks 。轉載請註明原文地址以及翻譯作者。

1. 介紹

創建由多個層組成的大型 Java 應用程序需要使用多種領域模型,如持久化模型、領域模型或者所謂的 DTO。為不同的應用程序層使用多個模型將要求我們提供 bean 之間的映射方法。手動執行此操作可以快速創建大量樣板代碼並消耗大量時間。幸運的是,Java 有多個對象映射框架。在本教程中,我們將比較最流行的 Java 映射框架的性能。

綜合日常使用情況和相關測試數據,個人感覺 MapStruct、ModelMapper 這兩個 Bean 映射框架是最佳選擇。

2. 常見 Bean 映射框架概覽

2.1. Dozer

Dozer 是一個映射框架,它使用遞歸將數據從一個對象複製到另一個對象。框架不僅能夠在 bean 之間複製屬性,還能夠在不同類型之間自動轉換。

要使用 Dozer 框架,我們需要添加這樣的依賴到我們的項目:

<dependency>
    <groupId>net.sf.dozer</groupId>
    <artifactId>dozer</artifactId>
    <version>5.5.1</version>
</dependency>

更多關於 Dozer 的內容可以在官方文檔中找到: http://dozer.sourceforge.net/documentation/gettingstarted.html ,或者你也可以閱讀這篇文章:https://www.baeldung.com/dozer 。

2.2. Orika

Orika 是一個 bean 到 bean 的映射框架,它遞歸地將數據從一個對象複製到另一個對象。

Orika 的工作原理與 Dozer 相似。兩者之間的主要區別是 Orika 使用字節碼生成。這允許以最小的開銷生成更快的映射器。

要使用 Orika 框架,我們需要添加這樣的依賴到我們的項目:

<dependency>
    <groupId>ma.glasnost.orika</groupId>
    <artifactId>orika-core</artifactId>
    <version>1.5.2</version>
</dependency>

更多關於 Orika 的內容可以在官方文檔中找到:https://orika-mapper.github.io/orika-docs/,或者你也可以閱讀這篇文章:https://www.baeldung.com/orika-mapping。

2.3. MapStruct

MapStruct 是一個自動生成 bean mapper 類的代碼生成器。MapStruct 還能夠在不同的數據類型之間進行轉換。Github 地址:https://github.com/mapstruct/mapstruct。

要使用 MapStruct 框架,我們需要添加這樣的依賴到我們的項目:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.2.0.Final</version>
</dependency>

更多關於 MapStruct 的內容可以在官方文檔中找到:https://mapstruct.org/,或者你也可以閱讀這篇文章:https://www.baeldung.com/mapstruct。

要使用 MapStruct 框架,我們需要添加這樣的依賴到我們的項目:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.2.0.Final</version>
</dependency>

2.4. ModelMapper

ModelMapper 是一個旨在簡化對象映射的框架,它根據約定確定對象之間的映射方式。它提供了類型安全的和重構安全的 API。

更多關於 ModelMapper 的內容可以在官方文檔中找到:http://modelmapper.org/ 。

要使用 ModelMapper 框架,我們需要添加這樣的依賴到我們的項目:

<dependency>
  <groupId>org.modelmapper</groupId>
  <artifactId>modelmapper</artifactId>
  <version>1.1.0</version>
</dependency>

2.5. JMapper

JMapper 是一個映射框架,旨在提供易於使用的、高性能的 Java bean 之間的映射。該框架旨在使用註釋和關係映射應用 DRY 原則。該框架允許不同的配置方式:基於註釋、XML 或基於 api。

更多關於 JMapper 的內容可以在官方文檔中找到:https://github.com/jmapper-framework/jmapper-core/wiki。

要使用 JMapper 框架,我們需要添加這樣的依賴到我們的項目:

<dependency>
    <groupId>com.googlecode.jmapper-framework</groupId>
    <artifactId>jmapper-core</artifactId>
    <version>1.6.0.1</version>
</dependency>

3.測試模型

為了能夠正確地測試映射,我們需要有一個源和目標模型。我們已經創建了兩個測試模型。

第一個是一個只有一個字符串字段的簡單 POJO,它允許我們在更簡單的情況下比較框架,並檢查如果我們使用更複雜的 bean 是否會發生任何變化。

簡單的源模型如下:

public class SourceCode {
    String code;
    // getter and setter
}

它的目標也很相似:

public class DestinationCode {
    String code;
    // getter and setter
}

源 bean 的實際示例如下:

public class SourceOrder {
    private String orderFinishDate;
    private PaymentType paymentType;
    private Discount discount;
    private DeliveryData deliveryData;
    private User orderingUser;
    private List<Product> orderedProducts;
    private Shop offeringShop;
    private int orderId;
    private OrderStatus status;
    private LocalDate orderDate;
    // standard getters and setters
}

目標類如下圖所示:

public class Order {
    private User orderingUser;
    private List<Product> orderedProducts;
    private OrderStatus orderStatus;
    private LocalDate orderDate;
    private LocalDate orderFinishDate;
    private PaymentType paymentType;
    private Discount discount;
    private int shopId;
    private DeliveryData deliveryData;
    private Shop offeringShop;
    // standard getters and setters
}

整個模型結構可以在這裏找到:https://github.com/eugenp/tutorials/tree/master/performance-tests/src/main/java/com/baeldung/performancetests/model/source。

4. 轉換器

為了簡化測試設置的設計,我們創建了如下所示的轉換器接口:

public interface Converter {
    Order convert(SourceOrder sourceOrder);
    DestinationCode convert(SourceCode sourceCode);
}

我們所有的自定義映射器都將實現這個接口。

4.1. OrikaConverter

Orika 支持完整的 API 實現,這大大簡化了 mapper 的創建:

public class OrikaConverter implements Converter{
    private MapperFacade mapperFacade;

    public OrikaConverter() {
        MapperFactory mapperFactory = new DefaultMapperFactory
          .Builder().build();

        mapperFactory.classMap(Order.class, SourceOrder.class)
          .field("orderStatus", "status").byDefault().register();
        mapperFacade = mapperFactory.getMapperFacade();
    }

    @Override
    public Order convert(SourceOrder sourceOrder) {
        return mapperFacade.map(sourceOrder, Order.class);
    }

    @Override
    public DestinationCode convert(SourceCode sourceCode) {
        return mapperFacade.map(sourceCode, DestinationCode.class);
    }
}

4.2. DozerConverter

Dozer 需要 XML 映射文件,有以下幾個部分:

<mappings xmlns="http://dozer.sourceforge.net"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://dozer.sourceforge.net
  http://dozer.sourceforge.net/schema/beanmapping.xsd">

    <mapping>
        <class-a>com.baeldung.performancetests.model.source.SourceOrder</class-a>
        <class-b>com.baeldung.performancetests.model.destination.Order</class-b>
        <field>
            <a>status</a>
            <b>orderStatus</b>
        </field>
    </mapping>
    <mapping>
        <class-a>com.baeldung.performancetests.model.source.SourceCode</class-a>
        <class-b>com.baeldung.performancetests.model.destination.DestinationCode</class-b>
    </mapping>
</mappings>

定義了 XML 映射后,我們可以從代碼中使用它:

public class DozerConverter implements Converter {
    private final Mapper mapper;

    public DozerConverter() {
        DozerBeanMapper mapper = new DozerBeanMapper();
        mapper.addMapping(
          DozerConverter.class.getResourceAsStream("/dozer-mapping.xml"));
        this.mapper = mapper;
    }

    @Override
    public Order convert(SourceOrder sourceOrder) {
        return mapper.map(sourceOrder,Order.class);
    }

    @Override
    public DestinationCode convert(SourceCode sourceCode) {
        return mapper.map(sourceCode, DestinationCode.class);
    }
}

4.3. MapStructConverter

Map 結構的定義非常簡單,因為它完全基於代碼生成:

@Mapper
public interface MapStructConverter extends Converter {
    MapStructConverter MAPPER = Mappers.getMapper(MapStructConverter.class);

    @Mapping(source = "status", target = "orderStatus")
    @Override
    Order convert(SourceOrder sourceOrder);

    @Override
    DestinationCode convert(SourceCode sourceCode);
}

4.4. JMapperConverter

JMapperConverter 需要做更多的工作。接口實現后:

public class JMapperConverter implements Converter {
    JMapper realLifeMapper;
    JMapper simpleMapper;

    public JMapperConverter() {
        JMapperAPI api = new JMapperAPI()
          .add(JMapperAPI.mappedClass(Order.class));
        realLifeMapper = new JMapper(Order.class, SourceOrder.class, api);
        JMapperAPI simpleApi = new JMapperAPI()
          .add(JMapperAPI.mappedClass(DestinationCode.class));
        simpleMapper = new JMapper(
          DestinationCode.class, SourceCode.class, simpleApi);
    }

    @Override
    public Order convert(SourceOrder sourceOrder) {
        return (Order) realLifeMapper.getDestination(sourceOrder);
    }

    @Override
    public DestinationCode convert(SourceCode sourceCode) {
        return (DestinationCode) simpleMapper.getDestination(sourceCode);
    }
}

我們還需要向目標類的每個字段添加@JMap註釋。此外,JMapper 不能在 enum 類型之間轉換,它需要我們創建自定義映射函數:

@JMapConversion(from = "paymentType", to = "paymentType")
public PaymentType conversion(com.baeldung.performancetests.model.source.PaymentType type) {
    PaymentType paymentType = null;
    switch(type) {
        case CARD:
            paymentType = PaymentType.CARD;
            break;

        case CASH:
            paymentType = PaymentType.CASH;
            break;

        case TRANSFER:
            paymentType = PaymentType.TRANSFER;
            break;
    }
    return paymentType;
}

4.5. ModelMapperConverter

ModelMapperConverter 只需要提供我們想要映射的類:

public class ModelMapperConverter implements Converter {
    private ModelMapper modelMapper;

    public ModelMapperConverter() {
        modelMapper = new ModelMapper();
    }

    @Override
    public Order convert(SourceOrder sourceOrder) {
       return modelMapper.map(sourceOrder, Order.class);
    }

    @Override
    public DestinationCode convert(SourceCode sourceCode) {
        return modelMapper.map(sourceCode, DestinationCode.class);
    }
}

5. 簡單的模型測試

對於性能測試,我們可以使用 Java Microbenchmark Harness,關於如何使用它的更多信息可以在 這篇文章:https://www.baeldung.com/java-microbenchmark-harness 中找到。

我們為每個轉換器創建了一個單獨的基準測試,並將基準測試模式指定為 Mode.All。

5.1. 平均時間

對於平均運行時間,JMH 返回以下結果(越少越好):

這個基準測試清楚地表明,MapStruct 和 JMapper 都有最佳的平均工作時間。

5.2. 吞吐量

在這種模式下,基準測試返回每秒的操作數。我們收到以下結果(越多越好):

在吞吐量模式中,MapStruct 是測試框架中最快的,JMapper 緊隨其後。

5.3. SingleShotTime

這種模式允許測量單個操作從開始到結束的時間。基準給出了以下結果(越少越好):

這裏,我們看到 JMapper 返回的結果比 MapStruct 好得多。

5.4. 採樣時間

這種模式允許對每個操作的時間進行採樣。三個不同百分位數的結果如下:

所有的基準測試都表明,根據場景的不同,MapStruct 和 JMapper 都是不錯的選擇,儘管 MapStruct 對 SingleShotTime 給出的結果要差得多。

6. 真實模型測試

對於性能測試,我們可以使用 Java Microbenchmark Harness,關於如何使用它的更多信息可以在 這篇文章:https://www.baeldung.com/java-microbenchmark-harness 中找到。

我們為每個轉換器創建了一個單獨的基準測試,並將基準測試模式指定為 Mode.All。

6.1. 平均時間

JMH 返回以下平均運行時間結果(越少越好):

該基準清楚地表明,MapStruct 和 JMapper 均具有最佳的平均工作時間。

6.2. 吞吐量

在這種模式下,基準測試返回每秒的操作數。我們收到以下結果(越多越好):

在吞吐量模式中,MapStruct 是測試框架中最快的,JMapper 緊隨其後。

6.3. SingleShotTime

這種模式允許測量單個操作從開始到結束的時間。基準給出了以下結果(越少越好):

6.4. 採樣時間

這種模式允許對每個操作的時間進行採樣。三個不同百分位數的結果如下:

儘管簡單示例和實際示例的確切結果明顯不同,但是它們的趨勢相同。在哪種算法最快和哪種算法最慢方面,兩個示例都給出了相似的結果。

6.5. 結論

根據我們在本節中執行的真實模型測試,我們可以看出,最佳性能顯然屬於 MapStruct。在相同的測試中,我們看到 Dozer 始終位於結果表的底部。

7. 總結

在這篇文章中,我們已經進行了五個流行的 Java Bean 映射框架性能測試:ModelMapper MapStruct Orika ,Dozer, JMapper。

示例代碼地址:https://github.com/eugenp/tutorials/tree/master/performance-tests。

開源項目推薦

作者的其他開源項目推薦:

  1. :【Java學習+面試指南】 一份涵蓋大部分Java程序員所需要掌握的核心知識。
  2. : 適合新手入門以及有經驗的開發人員查閱的 Spring Boot 教程(業餘時間維護中,歡迎一起維護)。
  3. : 我覺得技術人員應該有的一些好習慣!
  4. :從零入門 !Spring Security With JWT(含權限驗證)後端部分代碼。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

RocketMQ ACL使用指南

目錄

@(本節目錄)

1、什麼是ACL?

ACL是access control list的簡稱,俗稱訪問控制列表。訪問控制,基本上會涉及到用戶、資源、權限、角色等概念,那在RocketMQ中上述會對應哪些對象呢?

  • 用戶
    用戶是訪問控制的基礎要素,也不難理解,RocketMQ ACL必然也會引入用戶的概念,即支持用戶名、密碼。
  • 資源
    資源,需要保護的對象,在RocketMQ中,消息發送涉及的Topic、消息消費涉及的消費組,應該進行保護,故可以抽象成資源。
  • 權限
    針對資源,能進行的操作,
  • 角色
    RocketMQ中,只定義兩種角色:是否是管理員。

另外,RocketMQ還支持按照客戶端IP進行白名單設置。

2、ACL基本流程圖

在講解如何使用ACL之前,我們先簡單看一下RocketMQ ACL的請求流程:

對於上述具體的實現,將在後續文章中重點講解,本文的目的只是希望給讀者一個大概的了解。

3、如何配置ACL

3.1 acl配置文件

acl默認的配置文件名:plain_acl.yml,需要放在${ROCKETMQ_HOME}/store/config目錄下。下面對其配置項一一介紹。

3.1.1 globalWhiteRemoteAddresses

全局白名單,其類型為數組,即支持多個配置。其支持的配置格式如下:


  • 表示不設置白名單,該條規則默認返回false。
  • “*”
    表示全部匹配,該條規則直接返回true,將會阻斷其他規則的判斷,請慎重使用。
  • 192.168.0.{100,101}
    多地址配置模式,ip地址的最後一組,使用{},大括號中多個ip地址,用英文逗號(,)隔開。
  • 192.168.1.100,192.168.2.100
    直接使用,分隔,配置多個ip地址。
  • 192.168..或192.168.100-200.10-20
    每個IP段使用 “*” 或”-“表示範圍。

3.1.2 accounts

配置用戶信息,該類型為數組類型。擁有accessKey、secretKey、whiteRemoteAddress、admin、defaultTopicPerm、defaultGroupPerm、topicPerms、groupPerms子元素。

3.1.2.1 accessKey

登錄用戶名,長度必須大於6個字符。

3.1.2.2 secretKey

登錄密碼。長度必須大於6個字符。

3.1.2.3 whiteRemoteAddress

用戶級別的IP地址白名單。其類型為一個字符串,其配置規則與globalWhiteRemoteAddresses,但只能配置一條規則。

3.1.2.4 admin

boolean類型,設置是否是admin。如下權限只有admin=true時才有權限執行。

  • UPDATE_AND_CREATE_TOPIC
    更新或創建主題。
  • UPDATE_BROKER_CONFIG
    更新Broker配置。
  • DELETE_TOPIC_IN_BROKER
    刪除主題。
  • UPDATE_AND_CREATE_SUBSCRIPTIONGROUP
    更新或創建訂閱組信息。
  • DELETE_SUBSCRIPTIONGROUP
    刪除訂閱組信息。
3.1.2.5 defaultTopicPerm

默認topic權限。該值默認為DENY(拒絕)。

3.1.2.6 defaultGroupPerm

默認消費組權限,該值默認為DENY(拒絕),建議值為SUB。

3.1.2.7 topicPerms

設置topic的權限。其類型為數組,其可選擇值在下節介紹。

3.1.2.8 groupPerms

設置消費組的權限。其類型為數組,其可選擇值在下節介紹。可以為每一消費組配置不一樣的權限。

3.2 RocketMQ ACL權限可選值

  • DENY
    拒絕。
  • PUB
    擁有發送權限。
  • SUB
    擁有訂閱權限。

3.3、權限驗證流程

上面定義了全局白名單、用戶級別的白名單,用戶級別的權限,為了更好的配置ACL權限規則,下面給出權限匹配邏輯。

4、使用示例

4.1 Broker端安裝

首先,需要在broker.conf文件中,增加參數aclEnable=true。並拷貝distribution/conf/plain_acl.yml文件到${ROCKETMQ_HOME}/conf目錄。

broker.conf的配置文件如下:

brokerClusterName = DefaultCluster
brokerName = broker-b
brokerId = 0
deleteWhen = 04
fileReservedTime = 48
brokerRole = ASYNC_MASTER
flushDiskType = ASYNC_FLUSH
listenPort=10915
storePathRootDir=E:/SH2019/tmp/rocketmq_home/rocketmq4.5MB/store
storePathCommitLog=E:/SH2019/tmp/rocketmq_home/rocketmq4.5MB/store/commitlog
namesrvAddr=127.0.0.1:9876
autoCreateTopicEnable=false
aclEnable=true

plain_acl.yml文件內容如下:

globalWhiteRemoteAddresses:

accounts:
- accessKey: RocketMQ
  secretKey: 12345678
  whiteRemoteAddress:
  admin: false
  defaultTopicPerm: DENY
  defaultGroupPerm: SUB
  topicPerms:
  - TopicTest=PUB
  groupPerms:
  # the group should convert to retry topic
  - oms_consumer_group=DENY

- accessKey: admin
  secretKey: 12345678
  whiteRemoteAddress:
  # if it is admin, it could access all resources
  admin: true

從上面的配置可知,用戶RocketMQ只能發送TopicTest的消息,其他topic無權限發送;拒絕oms_consumer_group消費組的消息消費,其他消費組默認可消費。

4.2 消息發送端示例

public class AclProducer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name", getAclRPCHook());
        producer.setNamesrvAddr("127.0.0.1:9876");
        producer.start();
        for (int i = 0; i < 1; i++) {
            try {
                Message msg = new Message("TopicTest3" ,"TagA" , ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
                SendResult sendResult = producer.send(msg);
                System.out.printf("%s%n", sendResult);
            } catch (Exception e) {
                e.printStackTrace();
                Thread.sleep(1000);
            }
        }
        producer.shutdown();
    }

    static RPCHook getAclRPCHook() {
        return new AclClientRPCHook(new SessionCredentials("rocketmq","12345678"));
    }
}

運行效果如圖所示:

4.3 消息消費端示例

public class AclConsumer {

    public static void main(String[] args) throws InterruptedException, MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_4", getAclRPCHook(),new AllocateMessageQueueAveragely());
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe("TopicTest", "*");
        consumer.setNamesrvAddr("127.0.0.1:9876");
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                ConsumeConcurrentlyContext context) {
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs);
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        consumer.start();
        System.out.printf("Consumer Started.%n");
    }

    static RPCHook getAclRPCHook() {
        return new AclClientRPCHook(new SessionCredentials("rocketmq","12345678"));
    }
}

發現並不沒有消費消息,符合預期。

關於RocketMQ ACL的使用就介紹到這裏了,下一篇將介紹RocketMQ ACL實現原理。

推薦閱讀:
1、

2、

3、

4、

作者介紹:
丁威,《RocketMQ技術內幕》作者,RocketMQ 社區佈道師,公眾號: 維護者,目前已陸續發表源碼分析Java集合、Java 併發包(JUC)、Netty、Mycat、Dubbo、RocketMQ、Mybatis等源碼專欄。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!