OpenStack4j 多線程編程session問題(一)

背景

項(xiàng)目中使用了 OpenStack4j github鏈接 來調(diào)用 OpenStack 接口震捣,最近負(fù)責(zé)實(shí)現(xiàn)一個(gè)“自動(dòng)化構(gòu)建上百個(gè)虛擬節(jié)點(diǎn)”功能笋颤,覺得單線程模式下一個(gè)個(gè)創(chuàng)虛擬機(jī)太慢了祸憋,考慮使用多線程的方式創(chuàng)建虛擬機(jī)虐急,創(chuàng)建過程中遇到了如下異常:

# org.openstack4j.api.exceptions.OS4JException: 
# Unable to retrieve current session. 
# Please verify thread has a current session available.

從字面上比庄,是說我當(dāng)前的線程沒有對(duì)應(yīng)的會(huì)話(session)唉工,那么這個(gè)會(huì)話到底是怎么回事呢研乒?如何用多線程實(shí)現(xiàn)對(duì) OpenStack 的并發(fā)請求呢?下面就來說一說

錯(cuò)誤還原

使用 OpenStack4j 創(chuàng)建虛擬機(jī)的代碼很簡單淋硝,分兩步走雹熬,第一步宽菜,建立一個(gè) client,第二步竿报,用 client 獲取 compute 服務(wù)并創(chuàng)建虛擬機(jī)铅乡,代碼示范如下:

第一步,建立一個(gè) client

OSClientV3 os = OSFactory.builderV3()
                .endpoint("http://<fqdn>:5000/v3")
                .credentials("admin", "secret", Identifier.byId("user domain id"))
                .scopeToProject(Identifier.byId("project id"))
                .authenticate());

第二步烈菌,獲取 compute服務(wù)并創(chuàng)建虛擬機(jī)阵幸,這里的server就是創(chuàng)建的虛擬機(jī):

ServerCreate serverCreate = Builders.server()
                .name(name)
                .addSecurityGroup(securityGroup)
                .flavor(flavorId)
                .image(imageId)
                .networks(networks)
                .keypairName(keypairName)
                .build();
Server server = os.compute().servers().boot(serverCreate);

那么用多線程創(chuàng)建虛擬機(jī)的代碼結(jié)構(gòu)就大致如下,當(dāng)然芽世,只是簡單的示范代碼:

OSClientV3 os = OSFactory.builderV3()
                .endpoint("http://<fqdn>:5000/v3")
                .credentials("admin", "secret", Identifier.byId("user domain id"))
                .scopeToProject(Identifier.byId("project id"))
                .authenticate());

//用線程池管理線程
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);

for(int i = 0; i < n; i++) {
    executorService.submit(new createMission(os));
}

executorService.shutdown();
//創(chuàng)建一個(gè)任務(wù)類挚赊,繼承線程
class CreateMission extends Thread {

    OSClientV3 os;
     
    public CreateMission(OSClientV3 os) {
        this.os = os;   
    }

    @Override
    public void run() {
        //創(chuàng)建serverCreate對(duì)象,這里省略代碼
        Server server = os.compute().servers().boot(serverCreate);
    }
}

如果這么寫的話济瓢,就報(bào)錯(cuò)啦荠割,遇到上文所述的 session 異常:

# org.openstack4j.api.exceptions.OS4JException: 
# Unable to retrieve current session. 
# Please verify thread has a current session available.

為了解決這個(gè)錯(cuò)誤,我們就得知道 session 是什么旺矾,為什么在多線程下會(huì)出錯(cuò)

Session

下面結(jié)合 OpenStack4j 源碼來分析一下這個(gè) session

說簡單也簡單涨共,從 client 的創(chuàng)建說起:

OSClientV3 os = OSFactory.builderV3()
                .endpoint("http://<fqdn>:5000/v3")
                .credentials("admin", "secret", Identifier.byId("user domain id"))
                .scopeToProject(Identifier.byId("project id"))
                .authenticate());

如上所示,client 是用 OSFactorybuilderV3 實(shí)現(xiàn)的宠漩,返回了一個(gè)ClientV3 對(duì)象:

package org.openstack4j.openstack;
public abstract class OSFactory<T extends OSFactory<T>> {
    ……
    public static V3 builderV3() {
        return new ClientV3();
    }
}

在創(chuàng)建 client 的一系列調(diào)用中举反, clientV3 對(duì)象最后調(diào)用了一個(gè)方法 authenticate()

package org.openstack4j.openstack.client;
public abstract class OSClientBuilder {
    ……
    public static class ClientV3 extends OSClientBuilder {
        ……
        public OSClientV3 authenticate() throws AuthenticationException {
            if (this.tokenId != null && this.tokenId.length() > 0) {
                return this.scope != null ? (OSClientV3)OSAuthenticator.invoke(new KeystoneAuth(this.tokenId, this.scope), this.endpoint, this.perspective, this.config, this.provider) : (OSClientV3)OSAuthenticator.invoke(new KeystoneAuth(this.tokenId), this.endpoint, this.perspective, this.config, this.provider);
            } else {
                return this.user != null && this.user.length() > 0 ? (OSClientV3)OSAuthenticator.invoke(new KeystoneAuth(this.user, this.password, this.domain, this.scope), this.endpoint, this.perspective, this.config, this.provider) : (OSClientV3)OSAuthenticator.invoke(new KeystoneAuth(this.scope, Type.TOKENLESS), this.endpoint, this.perspective, this.config, this.provider);
            }
        }
    }
}

要注意 return 語句里調(diào)用了 OSAuthenticator.invoke 方法,這里面的實(shí)現(xiàn)如下:

package org.openstack4j.openstack.internal;
public class OSAuthenticator {
    ……
    public static OSClient invoke(KeystoneAuth auth, String endpoint, Facing perspective, Config config,
            CloudProvider provider) {
        SessionInfo info = new SessionInfo(endpoint, perspective, false, provider);
        return authenticateV3(auth, info, config);
    }
    
    private static OSClientV3 authenticateV3(KeystoneAuth auth, SessionInfo info, Config config) {
        ……
        if (!info.reLinkToExistingSession) {
            OSClientSessionV3 v3 = OSClientSessionV3.createSession(token, info.perspective, info.provider, config);
            v3.reqId = reqId;
            return v3;
        }

        OSClientSessionV3 current = (OSClientSessionV3) OSClientSessionV3.getCurrent();
        current.token = token;
       
        current.reqId = reqId;
        return current;
    }
}

注意看最后扒吁,這個(gè)方法返回了一個(gè) OSClientSessionV3 的對(duì)象火鼻!所以說我們用的 client,其實(shí)就可以視為一個(gè) session

以上介紹了那么多看似繁瑣的調(diào)用雕崩,就是為了讓我們形成這樣一個(gè)理解:

OpenStack4j 里魁索,創(chuàng)建的 client 可以被理解成創(chuàng)建了一次會(huì)話(session),以后的調(diào)用盼铁,都是基于此次會(huì)話的

最后看一下session類:

/**
 * A client which has been identified. Any calls spawned from this session will
 * automatically utilize the original authentication that was successfully
 * validated and authorized
 *
 * @author Jeremy Unruh
 */
public abstract class OSClientSession {
    private static final ThreadLocal<OSClientSession> sessions = new ThreadLocal<OSClientSession>();
    
    ……
    public static class OSClientSessionV3 extends OSClientSession<OSClientSessionV3, OSClientV3> {
        ……
    }
}

注意看一開始定義的 ThreadLocal<OSClientSession> sessions

謎底揭曉了粗蔚,OSClientSessionThreadLocal<OSClientSession> 為每個(gè)線程維護(hù)了一個(gè) session(也就是client)

每次使用client調(diào)用某服務(wù)的時(shí)候,最后會(huì)調(diào)用到這里:

package org.openstack4j.openstack.internal;
public class BaseOpenStackService {
    ……
    @SuppressWarnings("rawtypes")
    private <R> Invocation<R> builder(Class<R> returnType, String path, HttpMethod method) {
        OSClientSession ses = OSClientSession.getCurrent();
        if (ses == null) {
            throw new OS4JException(
                    "Unable to retrieve current session. Please verify thread has a current session available.");
        }
        RequestBuilder<R> req = HttpRequest.builder(returnType).endpointTokenProvider(ses).config(ses.getConfig())
                .method(method).path(path);
        Map headers = ses.getHeaders();
        if (headers != null && headers.size() > 0){
            return new Invocation<R>(req, serviceType, endpointFunc).headers(headers);
        }else{ 
            return new Invocation<R>(req, serviceType, endpointFunc);
        }
    }
}

上圖里饶火,著重看這段代碼:

OSClientSession ses = OSClientSession.getCurrent();
if (ses == null) {
    throw new OS4JException(
            "Unable to retrieve current session. Please verify thread has a current session available.");
}

回顧一下之前多線程創(chuàng)建虛擬機(jī)的代碼鹏控,我們在主線程里創(chuàng)建了 client (也可理解為會(huì)話),在另外的線程里試圖用這個(gè)會(huì)話調(diào)用 OpenStack 的服務(wù)肤寝,但這個(gè)會(huì)話是用 ThreadLocal 維護(hù)的当辐,只屬于主線程,另外的線程當(dāng)然取不到這個(gè)會(huì)話鲤看,于是就報(bào)錯(cuò)啦

于是要使用多線程并發(fā)調(diào)用 OpenStack 的話缘揪,需要在每個(gè)線程里都創(chuàng)建一個(gè)會(huì)話,再進(jìn)行調(diào)用,代碼框架大概如下:

//用線程池管理線程
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);

for(int i = 0; i < n; i++) {
    executorService.submit(new createMission());
}

executorService.shutdown();
//創(chuàng)建一個(gè)任務(wù)類找筝,繼承線程
class CreateMission extends Thread {
     
    @Override
    public void run() {
        OSClientV3 os = OSFactory.builderV3()
                .endpoint("http://<fqdn>:5000/v3")
                .credentials("admin", "secret", Identifier.byId("user domain id"))
                .scopeToProject(Identifier.byId("project id"))
                .authenticate());
        //創(chuàng)建serverCreate對(duì)象蹈垢,這里省略代碼
        Server server = os.compute().servers().boot(serverCreate);
    }
}

但是!讓我們假設(shè)一個(gè)場景袖裕,如果我要?jiǎng)?chuàng)建500個(gè)虛擬機(jī)耘婚,我用一個(gè)線程池,維護(hù)了若干個(gè)線程陆赋。每次,一個(gè)線程執(zhí)行一次創(chuàng)建虛擬機(jī)的任務(wù)嚷闭,就要?jiǎng)?chuàng)建一次會(huì)話攒岛,那么創(chuàng)建500個(gè)虛擬機(jī),就需要?jiǎng)?chuàng)建500個(gè)會(huì)話胞锰!

這樣對(duì)OpenStack服務(wù)器的壓力是不是太大了灾锯?如果要?jiǎng)?chuàng)建1000個(gè),1萬個(gè)虛擬機(jī)呢嗅榕?

既然我們用了線程池顺饮,復(fù)用了線程,我們是不是應(yīng)該想辦法把會(huì)話也復(fù)用一下凌那?

這時(shí)候兼雄,就想到了利用 ThreadLocal,至于怎么用帽蝶,下一篇再說了赦肋。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市励稳,隨后出現(xiàn)的幾起案子佃乘,更是在濱河造成了極大的恐慌,老刑警劉巖驹尼,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趣避,死亡現(xiàn)場離奇詭異,居然都是意外死亡新翎,警方通過查閱死者的電腦和手機(jī)程帕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來地啰,“玉大人骆捧,你說我怎么就攤上這事∷枵溃” “怎么了敛苇?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我枫攀,道長括饶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任来涨,我火速辦了婚禮图焰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蹦掐。我一直安慰自己技羔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布卧抗。 她就那樣靜靜地躺著藤滥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪社裆。 梳的紋絲不亂的頭發(fā)上拙绊,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音泳秀,去河邊找鬼标沪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛嗜傅,可吹牛的內(nèi)容都是我干的金句。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼吕嘀,長吁一口氣:“原來是場噩夢啊……” “哼趴梢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起币他,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤坞靶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蝴悉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體彰阴,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年拍冠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尿这。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡庆杜,死狀恐怖射众,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情晃财,我是刑警寧澤叨橱,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響罗洗,放射性物質(zhì)發(fā)生泄漏愉舔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一伙菜、第九天 我趴在偏房一處隱蔽的房頂上張望轩缤。 院中可真熱鬧,春花似錦贩绕、人聲如沸火的。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽馏鹤。三九已至,卻和暖如春踊淳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背陕靠。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工迂尝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人剪芥。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓垄开,卻偏偏與公主長得像,于是被迫代替她去往敵國和親税肪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子溉躲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • ??一個(gè)任務(wù)通常就是一個(gè)程序,每個(gè)運(yùn)行中的程序就是一個(gè)進(jìn)程益兄。當(dāng)一個(gè)程序運(yùn)行時(shí)锻梳,內(nèi)部可能包含了多個(gè)順序執(zhí)行流,每個(gè)順...
    OmaiMoon閱讀 1,662評(píng)論 0 12
  • 在一個(gè)方法內(nèi)部定義的變量都存儲(chǔ)在棧中净捅,當(dāng)這個(gè)函數(shù)運(yùn)行結(jié)束后疑枯,其對(duì)應(yīng)的棧就會(huì)被回收,此時(shí)蛔六,在其方法體中定義的變量將不...
    Y了個(gè)J閱讀 4,413評(píng)論 1 14
  • 整理自:Java面試題全集(上)https://blog.csdn.net/jackfrued/article/d...
    在水之天閱讀 3,102評(píng)論 1 24
  • ORA-00001: 違反唯一約束條件 (.) 錯(cuò)誤說明:當(dāng)在唯一索引所對(duì)應(yīng)的列上鍵入重復(fù)值時(shí)荆永,會(huì)觸發(fā)此異常。 O...
    我想起個(gè)好名字閱讀 5,176評(píng)論 0 9
  • 有點(diǎn)不好看国章,請勿噴~ 那時(shí)候年少稚嫩 難免有點(diǎn)心有余力不足嘿嘿
    那么蕭閱讀 599評(píng)論 0 1