中國電動車電池不到位?通用汽車被迫延後投產

中國推出「雙積分制」,規定各大車廠必須出售一定比例的電動車。通用汽車(GM)亟欲在中國開賣電動車,不料卻傳出當地生產的電動車電池達不到 GM 要求,暴露出中國電池業的隱憂。

《華爾街日報》報導,GM 原定下個月內生產油電混合車「Buick Velite 6」,明年再推純電動車。然而,內情人士透露上市計畫已經延後,原因是中國萬向集團旗下 A123 Systems 生產的電池,內部測試時效能和安全未達 GM 標準。A123 Systems 在杭州設有工廠,供應中國市場所需。電動車電池是相當複雜的零件,無法輕易更換,Velite 6 生產時程恐怕會大延誤。

GM 原本打算使用韓廠 LG Chem 的電池,可是 2016 年中國規定車廠必須使用當局核可的電池供應商,名單上全部都是中國廠,沒有一家外國業者入列。車廠抱怨此種排外政策是中國保護主義的實例,當局剔除外國業者,獨厚本土廠商。電池諮詢機構 LIB-X Consulting 總裁 Thomas Barrera 表示,中國業者急就章發展電池技術,或許有品質和安全風險。中國電池價格低廉,相當有吸引力,但是這些電池上市前,也許沒經過必須的品質檢測。

中國的電動車政策讓外國車廠陷入困境,車商必須使用中國製電池打造電動車,卻又不能在品質上做出妥協,使用品質欠佳的電池,車輛有著火風險。儘管多數外國車廠在公開場合信心滿滿表示,有能力達到中國政府目標,2019 年電動車產出將占總產量的 3~4%,但是少有業者詳細說明要如何辦到。

(本文內容由 授權使用。 CC BY 2.0)

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

【其他文章推薦】

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

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

台達電開發 400kW 高功率電動車充電機,獲美專案經費

電源管理及散熱解決方案廠商台達電 30 日宣布獲美國能源部(Department of Energy,DOE)研發專案經費輔助,合作開發充電輸出功率可達 400kW 的高速電動車充電機(XFC),預期不到 10 分鐘的快速充電即可為未來電動車款提供 180 英里的行駛里程(約 288 公里)。此充電機將應用固態變壓器(SST)技術,其電網輸入至電動車輸出的能源轉換率將高達 96.5%,體積為現今直流充電機的一半,重量更僅有四分之一,同時設計高壓直流(HVDC)端口,以利與儲能及再生能源系統整合,建構智能微電網以減少充電站對市電電網的負擔。

台達電指出,此研究專案將由深具汽車行業知識及經驗的台達底特律團隊,及位於北卡羅來納州  Raleigh 的台達電力電子研究室(DPEL)主導研究開發,通用汽車、DTE Energy、維吉尼亞理工大學 CPES 研究中心、NextEnergy、密西根州能源局之能源辦公室以及底特律市永續發展辦公室也將共同參與。此共同研發計畫為期三年,經費總計 700 萬美元,美國能源部將輔助一半費用。

台達電美洲區總經理黃銘孝表示,公司非常榮幸能主導此項重要研究計畫,並與頂尖的研究人員及合作夥伴共同開發嶄新技術。透過利用固態變壓器技術,將能創造前所未有的充電速度和便利性,樹立電動車快速充電的產業標竿,同時協助美國能源部達成提升電動車普及率的策略目標。

台達電表示,400kW 高速充電機採用碳化矽(SiC)MOSFET 元件並導入創新的固態變壓器拓撲設計(Topology),替代利用低壓交流電的傳統工頻變壓器技術,將可直接轉換 4.8kV 或 13.2kV 的中壓交流電為電動車以高達 3C 充電率快速充電。未來高續航里程的電動車款,10 分鐘的充電時間即可提供一半的最高續航里程,以續航里程為 360 英里的電動車為例,10 分鐘的充電,就可行駛 180 英里。此外,與現今業界的直流快速充電機相比,其系統效率預期將提升 3.5% 達到 96.5%、同時減少一半的設備體積,重量更只有四分之一。 而內建高壓直流端口,可讓此充電機在微電網內運行,降低電動車快速充電對電網的影響。此研究專案的原型機將於 2020 年測試運行。

台達電也表示,除了電動車充電技術的提升,此研究專案的數據和成果將能幫助汽車製造商、相關技術提供者、城市政府、與電力公司更加了解電動車高速充電如何影響電力需量反應規劃,以及充電站如何整合可再生能源,以避免大量的高速充電對電網基礎設施造成壓力。

(本文內容由 授權使用。首圖來源:科技新報)

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

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

電動車前景看俏,全球電動車累計銷售量已突破 400 萬

彭博能源財經(BNEF)最新報告指出,全球電動自小客車累計銷售已突破 400 萬大關,雖然乍看之下只佔總汽車銷售量的一小部分,但與 2015 年的 100 萬輛相比,其成長速度可說是一日千里。

BNEF 指出,若把電動巴士計算在內,電動車銷售量早在 7 月初就抵達 400 萬,其中電動自小客車全球銷售量在 2018 年 6 月底來到 350 萬輛,電動巴士則為是 421,000 輛,總銷售量為 397 萬。

報告顯示,電動車銷售量從 100 萬到 200 萬輛僅花費 17 個月,更在短短 6 個月就從 300 萬增加到 400 萬輛,而隨著電動車技術進步與價格下滑,未來電動車發展將踩油門加速,全球電動車銷售量僅需 6 個月、在 2019 年 3 月就能突破 500 萬。

電動車銷售量與日俱增,電動車銷售佔比在中國、歐洲和北美等主要市場也不斷提升,以 2018 年第二季來說,電動車就分別占當地總銷售的 4%、2.3% 與 1.6%。中國市場則是全球電動車發展迅速的一大功臣,中國市場早在 2011 年就佔全球電動自小客車總銷售的 37%,更占電動巴士的 99%。

BNEF 指出,未來中國將佔全球電動車總銷售的 42%,歐洲與北美分別占 26% 與 25%,若特斯拉平價電動車款 Model 3 在北美的銷售行情一路上漲,北美電動車銷售量則會迅速超越歐洲,而這兩個市場的銷售量也將同時達到 130 萬輛。

報告也表示,2018 年底之前還會有幾款電動車上市,這將能進一步提升全球電動車銷售市場。BNEF 指出,Model 3 將於 2019 年中旬進入歐洲市場、中國的「雙積分制」也將在 2019 年生效,新型車款與政策都能推動歐洲與中國電動車買氣。

中國雙積分制規定各大車廠必須出售一定比例的環保車,其中分為「油耗積分」與「新能源積分」,若車廠生產越多汽油車,油耗積分就會隨之減少;生產越多高性能電動車,新能源積分就越多。車廠每年正負積分必須抵銷歸零,如果積分沒辦法歸零就不能販售新車。

在政策挹注之下,中國電動車發展將逐步加速。中國媒體也指出,中國政府預估新能源車產量可在 2020 年達到 200 萬輛,銷售佔比更會在 2025 年達到總汽車市場的 20%。

BNEF 先前預估,2025 年全球電動車累計銷售量將增加 10 倍、達到 1,100 萬輛,2020-2030 年電動車價格則可與傳統汽車相當,2030 年全球電動車銷售量有望突破 3,000 萬輛。

隨著氣候變遷與空氣污染加劇,各國開始意識到電動車開發的重要性、紛紛開始制定禁售汽柴油車時間表與路上零排放目標,未來電動車的發展還會再加快,說不定可提早突破 1,000 萬大關。

(首圖來源:。文/DaisyChuang)

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

【其他文章推薦】

※教你寫出一流的銷售文案?

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

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

Panasonic 歐洲 EV 零件傳大增產,擴產至 10 倍

日經新聞 1 日報導,因訂單以歐洲豪華車廠為中心呈現增加,故 Panasonic 計畫投下 100 億日圓資金,於 2023 年將歐洲電動車(EV)用零件產能擴增至現行的 10 倍以上水準。報導指出,Panasonic 所計畫增產的對象為 EV、插電式油電混合車(PHV)等電動化車款充電時所需要的車用充電器等產品,Panasonic 將擴增捷克工廠、斯洛伐克工廠的車用充電器等產品產能,搶攻需求看俏的電動化車款需求。

據報導,Panasonic 的車用充電器具備小型、高輸出等特性,有助於讓車用電池在更短的時間內完成充電,各家車廠預計於 2019 年以後開賣的新型 EV 已決定採用。

Panasonic 歐洲事業負責人 Laurent Abadie(Panasonic 常務執行幹部)接受日經新聞採訪時表示,「計畫將歐洲車用事業營收擴增至 2 倍以上水準」。Laurent Abadie 並透露,考慮祭出購併措施。

Panasonic 車用事業目前以車用電池為主,日美歐車廠所生產的約 70 款車種採用了 Panasonic 的電池產品,而 Panasonic 計畫將車用事業產品群多樣化,除了車用電池之外、也將對 EV 相關零件進行積極投資,目標在 2021 年度將車用事業營收擴增至 2.5 兆日圓、將較 2017 年度增加約 5 成。

Panasonic 於 7 月 31 日公布財報資料指出,因車用事業業績佳,提振上季(2018 年 4-6 月)合併營收較去年同期成長 7.7% 至 2 兆 87 億日圓、合併營益大增 19.1% 至 999.56 億日圓。Panasonic 預估 2018 年度(2018 年 4 月-2019 年 3 月)合併營收將成長 4.0% 至 8.3 兆日圓、合併營益將勁揚 11.7% 至 4,250 億日圓。

(本文內容由 授權使用。首圖來源: CC BY 2.0)

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

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧

RocketMQ系列(七)事務消息(數據庫|最終一致性)

終於到了今天了,終於要講RocketMQ最牛X的功能了,那就是事務消息。為什麼事務消息被吹的比較熱呢?近幾年微服務大行其道,整個系統被切成了多個服務,每個服務掌管着一個數據庫。那麼多個數據庫之間的數據一致性就成了問題,雖然有像XA這種強一致性事務的支持,但是這種強一致性在互聯網的應用中並不適合,人們還是更傾向於使用最終一致性的解決方案,在最終一致性的解決方案中,使用MQ保證各個系統之間的數據一致性又是首選。

RocketMQ為我們提供了事務消息的功能,它使得我們投放消息和其他的一些操作保持一個整體的原子性。比如:向數據庫中插入數據,再向MQ中投放消息,把這兩個動作作為一個原子性的操作。貌似其他的MQ是沒有這種功能的。

但是,縱觀全網,講RocketMQ事務消息的博文中,幾乎沒有結合數據庫的,都是直接投放消息,然後講解事務消息的幾個狀態,雖然講的也沒毛病,但是和項目中事務最終一致性的落地方案還相距甚遠。包括我自己在內,在項目中,服務化以後,用MQ保證事務的最終一致性,在網上一搜,根本沒有落地的方案,都是侃侃而談。於是,我寫下這篇博文,結合數據庫,來談一談RocketMQ的事務消息到底怎麼用。

基礎概念

要使用RocketMQ的事務消息,要實現一個TransactionListener的接口,這個接口中有兩個方法,如下:

/**
     * When send transactional prepare(half) message succeed, this method will be invoked to execute local transaction.
     *
     * @param msg Half(prepare) message
     * @param arg Custom business parameter
     * @return Transaction state
     */
LocalTransactionState executeLocalTransaction(final Message msg, final Object arg);

/**
     * When no response to prepare(half) message. broker will send check message to check the transaction status, and this
     * method will be invoked to get local transaction status.
     *
     * @param msg Check message
     * @return Transaction state
     */
LocalTransactionState checkLocalTransaction(final MessageExt msg);

RocketMQ的事務消息是基於兩階段提交實現的,也就是說消息有兩個狀態,prepared和commited。當消息執行完send方法后,進入的prepared狀態,進入prepared狀態以後,就要執行executeLocalTransaction方法,這個方法的返回值有3個,也決定着這個消息的命運,

  • COMMIT_MESSAGE:提交消息,這個消息由prepared狀態進入到commited狀態,消費者可以消費這個消息;
  • ROLLBACK_MESSAGE:回滾,這個消息將被刪除,消費者不能消費這個消息;
  • UNKNOW:未知,這個狀態有點意思,如果返回這個狀態,這個消息既不提交,也不回滾,還是保持prepared狀態,而最終決定這個消息命運的,是checkLocalTransaction這個方法。

當executeLocalTransaction方法返回UNKNOW以後,RocketMQ會每隔一段時間調用一次checkLocalTransaction,這個方法的返回值決定着這個消息的最終歸宿。那麼checkLocalTransaction這個方法多長時間調用一次呢?我們在BrokerConfig類中可以找到,

 /**
  * Transaction message check interval.
  */
@ImportantField
private long transactionCheckInterval = 60 * 1000;

這個值是在brokder.conf中配置的,默認值是60*1000,也就是1分鐘。那麼會檢查多少次呢?如果每次都返回UNKNOW,也不能無休止的檢查吧,

/**
 * The maximum number of times the message was checked, if exceed this value, this message will be discarded.
 */
@ImportantField
private int transactionCheckMax = 5;

這個是檢查的最大次數,超過這個次數,如果還返回UNKNOW,這個消息將被刪除。

事務消息中,TransactionListener這個最核心的概念介紹完后,我們看看代碼如何寫吧。

落地案例

我們在數據庫中有一張表,具體如下:

CREATE TABLE `s_term` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `term_year` year(4) NOT NULL ,
  `type` int(1) NOT NULL DEFAULT '1' ,
  PRIMARY KEY (`id`)
) 

字段的具體含義大家不用管,一會我們將向這張表中插入一條數據,並且向MQ中投放消息,這兩個動作是一個原子性的操作,要麼全成功,要麼全失敗。

我們先來看看事務消息的客戶端的配置,如下:

@Bean(name = "transactionProducer",initMethod = "start",destroyMethod = "shutdown")
public TransactionMQProducer transactionProducer() {
    TransactionMQProducer producer = new
        TransactionMQProducer("TransactionMQProducer");
    producer.setNamesrvAddr("192.168.73.130:9876;192.168.73.131:9876;192.168.73.132:9876;");
    producer.setTransactionListener(transactionListener());
    return producer;
}

@Bean
public TransactionListener transactionListener() {
    return new TransactionListenerImpl();
}

我們使用TransactionMQProducer生命生產者的客戶端,並且生產者組的名字叫做TransactionMQProducer,後面NameServer的地址沒有變化。最後就是設置了一個TransactionListener監聽器,這個監聽器的實現我們也定義了一個Bean,返回的是我們自定義的TransactionListenerImpl,我們看看裡邊怎麼寫的吧。

public class TransactionListenerImpl implements TransactionListener {
    @Autowired
    private TermMapper termMapper;

    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {

        Integer termId = (Integer)arg;
        Term term = termMapper.selectById(termId);
        System.out.println("executeLocalTransaction termId="+termId+" term:"+term);
        if (term != null) return COMMIT_MESSAGE;

        return LocalTransactionState.UNKNOW;
    }

	@Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        String termId = msg.getKeys();
        Term term = termMapper.selectById(Integer.parseInt(termId));
        System.out.println("checkLocalTransaction termId="+termId+" term:"+term);
        if (term != null) {
            System.out.println("checkLocalTransaction:COMMIT_MESSAGE");
            return COMMIT_MESSAGE;
        }
        System.out.println("checkLocalTransaction:ROLLBACK_MESSAGE");
        return ROLLBACK_MESSAGE;
    }
}

在這個類中,我們要實現executeLocalTransaction和checkLocalTransaction兩個方法,其中executeLocalTransaction是在執行完send方法后立刻執行的,裡邊我們根據term表的id去查詢,如果能夠查詢出結果,就commit,消費端可以消費這個消息,如果查詢不到,就返回一個UNKNOW,說明過一會會調用checkLocalTransaction再次檢查。在checkLocalTransaction方法中,我們同樣用termId去查詢,這次如果再查詢不到就直接回滾了。

好了,事務消息中最重要的兩個方法都已經實現了,我們再來看看service怎麼寫吧,

@Autowired
private TermMapper termMapper;
@Autowired
@Qualifier("transactionProducer")
private TransactionMQProducer producer;

@Transactional(rollbackFor = Exception.class)
public void sendTransactionMQ() throws Exception {
    Term term = new Term();
    term.setTermYear(2020);
    term.setType(1);
    int insert = termMapper.insert(term);

    Message message = new Message();
    message.setTopic("cluster-topic");
    message.setKeys(term.getId()+"");
    message.setBody(new String("this is transaction mq "+new Date()).getBytes());

    TransactionSendResult sendResult = producer
        .sendMessageInTransaction(message, term.getId());
    System.out.println("sendResult:"+sendResult.getLocalTransactionState() 
                       +" 時間:"+new Date());
}
  • 在sendTransactionMQ方法上,我們使用了@Transactional註解,那麼在這個方法中,發生任何的異常,數據庫事務都會回滾;
  • 然後,我們創建Term對象,向數據庫中插入Term;
  • 構建Mesaage的信息,將termId作為message的key;
  • 使用sendMessageInTransaction發送消息,傳入message和termId,這兩個參數和executeLocalTransaction方法的入參是對應的。

最後,我們在test方法中,調用sendTransactionMQ方法,如下:

@Test
public void sendTransactionMQ() throws InterruptedException {
    try {
        transactionService.sendTransactionMQ();
    } catch (Exception e) {
        e.printStackTrace();
    }

    Thread.sleep(600000);
}

整個生產端的代碼就是這些了,消費端的代碼沒有什麼變化,就不給大家貼出來了。接下來,我們把消費端的應用啟動起來,消費端的應用最好不要包含生產端的代碼,因為TransactionListener實例化以後,就會進行監聽,而我們在消費者端是不希望看到TransactionListener中的日誌的。

我們運行一下生產端的代碼,看看是什麼情況,日誌如下:

executeLocalTransaction termId=15 term:com.example.rocketmqdemo.entity.Term@4a3509b0
sendResult:COMMIT_MESSAGE 時間:Wed Jun 17 08:56:49 CST 2020
  • 我們看到,先執行的是executeLocalTransaction這個方法,termId打印出來了,發送的結果也出來了,是COMMIT_MESSAGE,那麼消費端是可以消費這個消息的;
  • 注意一下兩個日誌的順序,先執行的executeLocalTransaction,說明在執行sendMessageInTransaction時,就會調用監聽器中的executeLocalTransaction,它的返回值決定着這個消息是否真正的投放到隊列中;

再看看消費端的日誌,

msgs.size():1
this is transaction mq Wed Jun 17 08:56:49 CST 2020

消息被正常消費,沒有問題。那麼數據庫中有沒有termId=15的數據呢?我們看看吧,

數據是有的,插入數據也是成功的。

這樣使用就真的正確的嗎?我們改一下代碼看看,在service方法中拋個異常,讓數據庫的事務回滾,看看是什麼效果。改動代碼如下:

@Transactional(rollbackFor = Exception.class)
public void sendTransactionMQ() throws Exception {
    ……
    throw new Exception("數據庫事務異常");
}

拋出異常后,數據庫的事務會回滾,那麼MQ呢?我們再發送一個消息看看,

生產端的日誌如下:

executeLocalTransaction termId=16 term:com.example.rocketmqdemo.entity.Term@5d6b5d3d
sendResult:COMMIT_MESSAGE 時間:Wed Jun 17 09:07:15 CST 2020

java.lang.Exception: 數據庫事務異常
  • 從日誌中,我們可以看到,消息是投放成功的,termId=16,事務的返回狀態是COMMIT_MESSAGE;
  • 最後拋出了我們定義的異常,那麼數據庫中應該是不存在這條消息的啊;

我們先看看數據庫吧,

數據庫中並沒有termId=16的數據,那麼數據庫的事務是回滾了,而消息是投放成功的,並沒有保持原子性啊。那麼為什麼在執行executeLocalTransaction方法時,能夠查詢到termId=16的數據呢?還記得MySQL的事務隔離級別嗎?忘了的趕快複習一下吧。在事務提交前,我們是可以查詢到termId=16的數據的,所以消息提交了,看看消費端的情況,

msgs.size():1
this is transaction mq Wed Jun 17 09:07:15 CST 2020

消息也正常消費了,這明顯不符合我們的要求,我們如果在微服務之間使用這種方式保證數據的最終一致性,肯定會有大麻煩的。那我們該怎麼使用s呢?我們可以在executeLocalTransaction方法中,固定返回UNKNOW,數據插入數據庫成功也好,失敗也罷,我們都返回UNKNOW。那麼這個消息是否投放到隊列中,就由checkLocalTransaction決定了。checkLocalTransaction肯定在sendTransactionMQ后執行,而且和sendTransactionMQ不在同一事務中。我們改一下程序吧,

@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
    return LocalTransactionState.UNKNOW;
}

其他的地方不用改,我們再發送一下消息,

sendResult:UNKNOW 時間:Wed Jun 17 09:56:59 CST 2020
java.lang.Exception: 數據庫事務異常

checkLocalTransaction termId=18 term:null
checkLocalTransaction:ROLLBACK_MESSAGE
  • 事務消息發送的結果是UNKNOW,然後拋出異常,事務回滾;
  • checkLocalTransaction方法,查詢termId=18的數據,為null,消息再回滾;

又看了一下消費端,沒有日誌。數據庫中也沒有termId=18的數據,這才符合我們的預期,數據庫插入不成功,消息投放不成功。我們再把拋出異常的代碼註釋掉,看看能不能都成功。

@Transactional(rollbackFor = Exception.class)
public void sendTransactionMQ() throws Exception {
    ……
    //throw new Exception("數據庫事務異常");
}

再執行一下發送端程序,日誌如下:

sendResult:UNKNOW 時間:Wed Jun 17 10:02:57 CST 2020
checkLocalTransaction termId=19 term:com.example.rocketmqdemo.entity.Term@3b643475
checkLocalTransaction:COMMIT_MESSAGE
  • 發送結果返回UNKNOW;
  • checkLocalTransaction方法查詢termId=19的數據,能夠查到;
  • 返回COMMIT_MESSAGE,消息提交到隊列中;

先看看數據庫中的數據吧,

termId=19的數據入庫成功了,再看看消費端的日誌,

msgs.size():1
this is transaction mq Wed Jun 17 10:02:56 CST 2020

消費成功,這才符合我們的預期。數據插入數據庫成功,消息投放隊列成功,消費消息成功。

總結

事務消息最重要的就是TransactionListener接口的實現,我們要理解executeLocalTransaction和checkLocalTransaction這兩個方法是干什麼用的,以及它們的執行時間。再一個就是和數據庫事務的結合,數據庫事務的隔離級別大家要知道。把上面這幾點掌握了,就可以靈活的使用RocketMQ的事務消息了。

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

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

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

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

LeetCode 78,面試常用小技巧,通過二進制獲得所有子集

本文始發於個人公眾號:TechFlow,原創不易,求個關注

今天是LeetCode專題第47篇文章,我們一起來看下LeetCode的第78題Subsets(子集)。

這題的官方難度是Medium,點贊3489,反對79,通過率59.9%。從這個數據我們也可以看得出來,這是一道難度不是很大,但是質量很高的題。的確,在這道題的解法當中,你會學到一種新的技巧。

廢話不多說,我們先來看題意。

題意

這題的題意非常簡單,和上一題有的一拼,基本上從標題就能猜到題目的意思。給定一個沒有重複元素的int型數組,要求返回所有的子集,要求子集當中沒有重複項,每一項當中也沒有重複的元素。

樣例

Input: nums = [1,2,3]
Output:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

照搬上題

剛拿到手可能有點蒙,但是稍微想一下就會發現,這一題和上題非常接近,兩者唯一的不同就是,子集沒有數量的限制,從空集開始,一直到它本身結束,不論多少個元素都可以。而上一題要求的是有數量限制的,也就是說上一題我們求的其實是限定了k個元素的子集。

想明白這點就簡單了,顯然我們可以復用上一題的算法,我們來遍歷這個k,從0到n,就可以獲得所有的子集了。只要你上一題做出來了,那麼這題幾乎沒有任何難度。如果你沒有看過上一題的文章的話,可以通過傳送門回顧一下:

LeetCode 77,組合挑戰,你能想出不用遞歸的解法嗎?

我們直接來看代碼:

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        # 上一題求解k個組合的解法
        def combine(n, k, ret):
            window = list(range(1, k+1)) + [n+1]
            j = 0
            
            while j < k:
                cur = []
                for i in range(k):
                    cur.append(nums[window[i] - 1])
                ret.append(cur[:])
                
                j = 0
                while j < k and window[j+1] == window[j] + 1:
                    window[j] = j + 1
                    j += 1
                window[j] += 1
                
        # 手動添加空集
        ret = [[]]
        n = len(nums)
        # 遍歷k從1到n
        for i in range(1, n+1):
            combine(n, i, ret)
        return ret

二進制組合

照搬上一題的解法固然是可行的,但是這麼做完全沒有必要,也得不到任何收穫。所以我們應該想一下新的解法。

既然這道題讓我們求的是所有的子集,那麼我們可以從子集的特點入手。我們之前學過,一個含有n個元素的子集的數量是。這個很容易想明白,因為n個元素,每個元素都有兩個狀態,選或者不選。並且這n個元素互相獨立,也就是說某個元素選或者不選並不會影響其他的元素,所以我們可以知道一共會有種可能。

我們也可以從組合數入手,我們令所有子集的數量為S,那麼根據上面我們用組合求解的解法,可以得到:

兩者的結果是一樣的,說明這個結論一定是正確的。

不知道大家看到n個元素,每個元素有兩個取值有什麼想法,如果做過的題目數量夠多的話,應該能很快聯想到二進制。因為在二進制當中,每一個二進制位就只有0和1兩種取值。那麼我們就可以用n位的二進制數來表示n個元素集合取捨的狀態。n位二進制數的取值範圍是,所以我們用一重循環去遍歷它,就相當於一重循環遍歷了整個集合所有的狀態。

這種技巧我們也曾經在動態規劃狀態壓縮的文章當中提到過,並且在很多題目當中都會用到。所以建議大家可以了解一下,說不定什麼時候面試就用上了。

根據這個技巧, 我們來實現代碼就非常簡單了。

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        ret = []
        n = len(nums)
        # 遍歷所有的狀態
        # 1左移n位相當於2的n次方
        for s in range(1 << n):
            cur = []
            # 通過位運算找到每一位是0還是1
            for i in range(n):
                # 判斷s狀態在2的i次方上,也就是第i位上是0還是1
                if s & (1 << i):
                    cur.append(nums[i])
            ret.append(cur[:])
            
        return ret

從代碼來看明顯比上面的解法短得多,實際上運行的速度也更快,因為我們去掉了所有多餘的操作,我們遍歷的每一個狀態都是正確的,也不用考慮重複元素的問題。

總結

不知道大家看完文章都有一些什麼感悟,可能第一種感悟就是LeetCode應該按照順序刷吧XD。

的確如此,LeetCode出題人出題都是有套路的,往往出了一道題之後,為了提升題目數量(湊提數),都會在之前題目的基礎上做變形,變成一道新題。所以如果你按照順序刷題的話,會很明顯地發現這一點。如果你從這個角度出發去思考的話,不但能理解題目之間的聯繫,還能揣摩出出題人的用意,這也是一件很有趣的事情。

如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

本文使用 mdnice 排版

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

【其他文章推薦】

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

JDK動態代理

在《springAOP之代理模式》中說了代理模式,包含靜態代理和動態代理,在動態代理模式中又分為JDK動態代理和CGlib動態代理,今天重點來看JDK動態代理。

一、概述

說到JDK動態代理就必須想到JDK動態代理要求有一個統一的接口,那為什麼要有接口,下面會說到,下面看我的接口類,

package cn.com.jdk.proxy;

public interface Subject {

    void sayHello(String a);
}

接口類很簡單就是一個簡單的方法定義。下面看實際的接口的實現類SubjectImpl,

package cn.com.jdk.proxy;

public class SubjectImpl implements Subject {

    @Override
    public void sayHello(String a) {
        // TODO Auto-generated method stub

        System.out.println("hello:"+a);
    }

}

實現類簡單的事項了Subject接口,進行了打印操作。下面看代理類

package cn.com.jdk.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class JDKProxy implements InvocationHandler {
    private SubjectImpl si;
    //此屬性不用管
    private String a;
/**
 * proxy  JDK動態生成的代理類的實例
 * method 目標方法的Method對象     Class.forName("cn.com.jdk.proxy.Subject").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
 * args   目標方法的參數                       new Object[] { paramString }
 */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("before");
        //使用反射的放式調用si(被代理類)目標方法
        Object o=method.invoke(si, args);
        System.out.println("after");
        return o;
    }
    public JDKProxy(SubjectImpl si,String a) {
        this.si=si;
        this.a=a;
    }

}

上面是代理類的實現,在代理類中含義被代理類的一個引用,且提供了響應的構造方法。下面具體的使用,

package cn.com.jdk.proxy;

import java.lang.reflect.Proxy;

public class ProxyTest {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //進行此項設置,可以在項目的com/sun/proxy目錄下找到JDK動態生成的代理類的字節碼文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        SubjectImpl si=new SubjectImpl();
        
        Subject subject=(Subject)Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), new JDKProxy(si,"111"));
        subject.sayHello("tom");

    }

}

上面是使用的代碼,通過Proxy類的newProxyInstance方法獲得一個Subject的實例,調用sayHello方法,下面看執行結果

before
hello:tom
after

可以看到執行了sayHello方法,且打印了before和after,這不正是代理類中invoke方法的執行嗎,看下面

很神奇的一件事,我們不光調用了sayHello方法,實現了打印,而且在加入了自己的打印方法,這不正是AOP的增強功能嗎。這一切是怎麼發生的那,下面細細說來。

二、詳述

上面,我們又複習了JDK動態代理的內容,以及演示了如何使用JDK動態代理,下面我們要看這是怎麼實現的,先從測試的下面這段代碼說起,也是最重要的代碼,JDK動態代理的精華都在這句代碼里,

Subject subject=(Subject)Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), new JDKProxy(si,"111"));

這句代碼是調用了Proxy類的newProxyInstance方法,此方法的入參如下,

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

 一共三個參數,一個是ClassLoader,這裏傳入的是被代理對象的類加載器;一個是Class,這裏傳入的是被代理對象所實現的接口;一個是InvocationHandler,這裏傳入的是代理類,代理類實現了InvocationHandler接口。

 1、newProxyInstance方法

下面看newProxyInstance方法的定義,

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
          //1、使用代理類的類加載器和其所實現的接口,動態生成代理類
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            //2、返回JDK生成的代理類的構造方法,該構造方法的參數為
            //  InvocationHandler
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
//3、返回該構造方法的一個實例,也就是使用InvocationHandler為參數的構造方法利用反射的機制返回一個實例。
return cons.newInstance(new Object[]{h}); } catch (IllegalAccessException|InstantiationException e) { throw new InternalError(e.toString(), e); } catch (InvocationTargetException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new InternalError(t.toString(), t); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } }

該方法中有三步比較重要,上面的註釋已經標出。

1.1、getProxyClass0(loader, intfs)方法

該方法便是上面的第一步,這一步的作用是JDK返回一個代理類的實例,方法上的註釋如下,

/*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

註釋直譯過來是查找或者生成指定的代理類,這裡有兩層意思,一個是查找,第二個是生成,由此可以想到這個方法中應該有緩存,下面看方法的具體定義,

/**
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     */
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

這個方法很簡單,判斷了接口的數量,大於65535便拋異常,接口的數量大於65535的可能性不大。最後調用了proxyClassCache的get方法,首先看proxyClassCache,從字面上理解是代理類的緩存,看其定義,

/**
     * a cache of proxy classes
     */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

是一個WeakCache對象實例,看下該構造方法,

/**
     * Construct an instance of {@code WeakCache}
     *
     * @param subKeyFactory a function mapping a pair of
     *                      {@code (key, parameter) -> sub-key}
     * @param valueFactory  a function mapping a pair of
     *                      {@code (key, parameter) -> value}
     * @throws NullPointerException if {@code subKeyFactory} or
     *                              {@code valueFactory} is null.
     */
    public WeakCache(BiFunction<K, P, ?> subKeyFactory,
                     BiFunction<K, P, V> valueFactory) {
        this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
        this.valueFactory = Objects.requireNonNull(valueFactory);
    }

看了該類的構造方法后,回到proxyClassCache.get(loader, interfaces)方法的調用,我們已經知道proxyClassCache是WeakCache的一個實例,那麼get方法如下,

 /**
     * Look-up the value through the cache. This always evaluates the
     * {@code subKeyFactory} function and optionally evaluates
     * {@code valueFactory} function if there is no entry in the cache for given
     * pair of (key, subKey) or the entry has already been cleared.
     *
     * @param key       possibly null key
     * @param parameter parameter used together with key to create sub-key and
     *                  value (should not be null)
     * @return the cached value (never null)
     * @throws NullPointerException if {@code parameter} passed in or
     *                              {@code sub-key} calculated by
     *                              {@code subKeyFactory} or {@code value}
     *                              calculated by {@code valueFactory} is null.
     */
    public V get(K key, P parameter) {
        Objects.requireNonNull(parameter);

        expungeStaleEntries();

        Object cacheKey = CacheKey.valueOf(key, refQueue);

        // lazily install the 2nd level valuesMap for the particular cacheKey
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
        if (valuesMap == null) {
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }

        // create subKey and retrieve the possible Supplier<V> stored by that
        // subKey from valuesMap
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;

        while (true) {
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue<V> instance
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            // else no supplier in cache
            // or a supplier that returned null (could be a cleared CacheValue
            // or a Factory that wasn't successful in installing the CacheValue)

            // lazily construct a Factory
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }

            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    // successfully installed Factory
                    supplier = factory;
                }
                // else retry with winning supplier
            } else {
                if (valuesMap.replace(subKey, supplier, factory)) {
                    // successfully replaced
                    // cleared CacheEntry / unsuccessful Factory
                    // with our Factory
                    supplier = factory;
                } else {
                    // retry with current supplier
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }

 上面是WeakCache的get方法,這個方法暫時不作說明,後面會詳細介紹WeakCache類,請參見《JDK動態代理之WeakCache 》。這裏只需記住該get方法會返回一個代理類的實例即可。那麼此代理類是如何定義的那?

1.1.1、$Proxy0.class代理類

這個代理類是JDK動態生成的,其命名規則為以“$”開頭+Proxy+“從0開始的序列”。上面在測試的時候,我們加入了下面這行代碼,

//進行此項設置,可以在項目的com/sun/proxy目錄下找到JDK動態生成的代理類的字節碼文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

註釋中寫到可以生成代理類的字節碼文件,下面是使用反編譯工具過來的java代碼,

package com.sun.proxy;

import cn.com.jdk.proxy.Subject;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy
  implements Subject
{
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m0;
 //參數為InvocationHandler的構造方法
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
   //調用父類Proxy的構造方法,在父類的構造方法中會初始化h屬性
    super(paramInvocationHandler);
  }

  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }
//實現的Subject的sayHello方法
  public final void sayHello(String paramString)
    throws 
  {
    try
    {
      //調用h的invoke方法,這裏的h指的是實現了InvocationHandler的類
      //調用其中的invoke方法,在本例中是調用JDKProxy類中的invoke方
      //
      this.h.invoke(this, m3, new Object[] { paramString });
      return;
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("cn.com.jdk.proxy.Subject").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
    }
    throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
  }
}

上面是反編譯過來的JDK生成的代理類的代碼,包含了一個使用InvocationHandler作為參數的構造方法,以及實現了Subject接口的sayHello方法。上面註釋中寫到該構造方法調用了其父類Proxy的構造方法,下面看其父類Proxy的構造方法,

protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }

把InvocationHandler的值賦給了h,h的定義如下,

protected InvocationHandler h;

那麼在生成的代理類中自然會繼承該屬性,所以在代理類中的sayHello中使用下面的方法調用,

public final void sayHello(String paramString)
    throws 
  {
    try
    {
      this.h.invoke(this, m3, new Object[] { paramString });
      return;
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

上面的this.h便是其父類的h屬性。在上面的this.h.invoke中的m3是怎麼來的那,看下面,

 static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("cn.com.jdk.proxy.Subject").getMethod("sayHello", new Class[] { Class.forName("java.lang.String") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
    }
    throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
  }

在該類的靜態代碼塊中給出了4個屬性。

1.2、getConstructor(constructorParams)方法

在上面的getProxyClass0方法中我們知道該方法會返回一個JDK生成代理類的Class對象,此類的定義便是上面的$Proxy0.class類。其定義在上面已經分析過。getConstructor方法要返回一個以constructorParams為參數的構造方法,

@CallerSensitive
    public Constructor<T> getConstructor(Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        return getConstructor0(parameterTypes, Member.PUBLIC);
    }

調用了getConstuctor0方法返回一個public的構造方法,

private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
                                        int which) throws NoSuchMethodException
    {
        Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
        for (Constructor<T> constructor : constructors) {
            if (arrayContentsEq(parameterTypes,
                                constructor.getParameterTypes())) {
                return getReflectionFactory().copyConstructor(constructor);
            }
        }
        throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));
    }

上面的方法會返回一個public的構造方法。

回到最初的調用,我們看getConstructor方法的參數是constructorParams,此屬性定義如下,

/** parameter types of a proxy class constructor */
    private static final Class<?>[] constructorParams =
        { InvocationHandler.class };

是一個Class數組,其類型為InvocationHandler。這樣便可以知道是通過代理類的Class對象返回其構造方法cons。有了構造方法下面便是通過構造方法生成實例。

1.3、cons.newInstance(new Object[]{h})方法

此方法便是通過構造方法返回一個代理類的實例。

 

上面分析了Proxy的newProxyInstance方法,此方法最終會返回一個代理類的實例,會經過下面幾個步驟,

從上面的步驟,我們知道在獲得代理類的構造方法時,是獲得其參數為InvocationHandler的構造方法,所以肯定要實現InvocationHandler接口,在本例中便是JDKProxy類,這個類實現了這個接口。值開篇我們講到JDK動態代理必須要有統一的接口,從上面的步驟中我們知道在生成代理類的Class對象時使用了兩個參數,一個ClassLoader,另一個是接口,這裏就是為什麼要有統一的接口,因為在生成代理類的Class對象中需要接口,所以被代理類必須要有一個接口。

2、方法調用

這裏的方法調用,便是對應使用方法中的下面這行代碼,

subject.sayHello("tom");

在上面的分析中獲得了一個代理類的實例,即下面這行代碼,

Subject subject=(Subject)Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(), new JDKProxy(si,"111"));

通過使用被代理類的類加載器、被代理類所實現的接口、實現了InvocationHandler接口的類的實例三個參數,返回了一個代理類的實例。上面已經詳細分析過。此代理類的實例繼承了Proxy,實現了Subject接口。其sayHello方法如下,

public final void sayHello(String paramString)
    throws 
  {
    try
    {
      this.h.invoke(this, m3, new Object[] { paramString });
      return;
    }
    catch (RuntimeException localRuntimeException)
    {
      throw localRuntimeException;
    }
    catch (Throwable localThrowable)
    {
    }
    throw new UndeclaredThrowableException(localThrowable);
  }

上面已經分析過,this.h是InvocationHandler的實例,這裏便是new JDKProxy(si,”111″),m3是m3 = Class.forName(“cn.com.jdk.proxy.Subject”).getMethod(“sayHello”, new Class[] { Class.forName(“java.lang.String”) });下面看JDKProxy中的invoke方法,

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("before");
        //使用反射的放式調用目標方法
        Object o=method.invoke(si, args);
        System.out.println("after");
        return o;
    }

此方法的三個參數分別為代理類的實例、Method對象(sayHello),調用sayHello時的參數,所以要調用被代理類的sayHello方法,需要這樣寫:method.invoke(si,args),即調用被代理類(SubjectImpl)的sayHello方法,參數為args(tom)。下面是一個簡單的方法調用過程,

三、總結

本文分析了JDK動態代理的簡單使用方法及背後的原理,有不當之處歡迎指正,感謝!

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

【其他文章推薦】

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

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

Probius:一個功能強大的自定義任務系統

斷更的這些日子,我又折騰了一個輪子,文末參考源碼

大約在一年半以前寫過一篇文章『探秘varian:優雅的發布部署程序』,裡邊有講到我們採用類似lego的模塊化方式來構建CICD的流程,雖能滿足我們的需求,但終究需要編寫代碼,使用成本有點高,不夠友好。近段時間終於下定決心將其重構,只為帶來更好的使用體驗,於是便有了這個項目Probius

Probius為遊戲星際爭霸里的角色,是一隻充滿好奇心的星靈探測機,取此名字的意思也是希望用戶能夠在這個系統中充分發揮想象,藉助此系統實現各種自定義的功能,覆蓋更多的運維場景

設計思路

Probius由三個關鍵詞構成:命令、模板、任務

命令:為系統中的最小粒度,可以是一個具體的linux命令,或者是一個腳本都可以

模板:模板為一組命令的集合

任務:模板為靜態的定義,而任務就是模板的執行,執行一個任務實際上就是去執行了一個模板內的所有命令

整體思想跟varian一樣,但不同的是可以僅僅通過web端的配置,就能實現各種各樣的功能,下邊具體介紹下如何配置的

頁面配置

新建命令,在這個頁面可以創建命令或者腳本

如果是單純的命令,直接在命令輸入框填寫即可,如果是需要執行腳本,則點擊腳本之後,會額外多出一個腳本輸入框,填寫要執行的腳本

理論上不限制腳本的類型,可以是shell、python或者go之類的,前提是系統上有腳本的運行環境,當命令或者腳本有參數的時候可以在參數列寫上參數名稱,然後在最終執行任務的時候需要傳遞具體參數的值過來

在命令執行完成后,會根據命令的返回狀態也就是$?的值來判斷命令是否執行成功,當$?為0是表示執行成功,否則表示執行失敗,如果是執行的腳本時,需要在腳本最後明確腳本返回狀態,shell腳本可以在腳本執行成功時通過exit指定退出狀態,例如

ls /ops-coffee.cn &&\
exit 0 ||\
exit 2

而對於python腳本則可以藉助sys.exit這樣寫

import sys

if 'www' in 'ops-coffee.cn':
  sys.exit(0)
else:
  sys.exit(3)

其他語言類似

模板的創建分為幾步,先創建一個模板

然後給模板添加任務

主要為選擇任務、確定執行順序、選擇執行主機以及執行用戶,添加完成后可以在模板詳情頁面看到關聯的命令

模板定義了一個完整的任務流程,定義完成后就可以執行任務了,執行任務界面寫的比較簡單

這界面主要給運維人員使用,定義任務名稱、所要執行的模板ID、以及參數,支持定時執行或者周期執行,只需要加上crontab參數即可,除了可以立即執行任務外,還可以將次任務保存為常用任務,後續在常用任務頁面可以直接執行

這個功能主要方便其他非運維人員使用本系統,同時也支持針對任務設置權限,可以將權限設置給某個用戶組,那麼則只有這個組內的成員可以看到並執行任務了

任務執行后可以通過任務歷史查看任務執行詳情,在這個頁面可以清晰的看到任務執行到了哪一步,是成功還是失敗

可以點擊每一步任務後邊的日誌查看實時日誌輸出

寫在最後

如果你用過我們開源的一站式DevOps平台CODO的話,會發現這個系統跟CODO的TASK模塊非常像,是的沒錯,這個設計與CODO的TASK如出一轍,但開源的CODO任務模塊要更加強大,例如支持分組執行、支持任務重做、支持人工干預等等

TASK的源碼在這裏:https://github.com/opendevops-cn/codo-task,感興趣可以自行閱讀部署,需要注意的是CODO為微服務架構,單獨安裝TASK是無法正常運行的,具體部署方法參考官方文檔

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

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

.Net Core基礎的健康檢查

前言

健康檢查能查看我們的應用程序當前是否是一個健康的運行狀態。微軟已經給我們提供了健康檢查輪子,只需要簡單的配置就能完成服務的狀態檢查。一起來實現一個最簡單的健康檢查吧。

開始

  • 新建一個空的webApi項目。 並引用Microsoft.Extensions.Diagnostics.HealthChecks 包。並在ConfigureServicesConfigure中加入相關配置
public void ConfigureServices(IServiceCollection services)
{
    //健康檢查服務
    services.AddHealthChecks();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    //加入中間件
    app.UseHealthChecks("/healthChecks");
}

最簡單的檢查就完成了,我們測試一下。

返Healthy,表示服務正常。

自定義拓展

HealthChecks提供了一個IHealthCheck接口,這個接口只有一個CheckHealthAsync方法,我們只需要實現這個接口就可以實現我們需要的各種自定義的檢查項目。CheckHealthAsync返回一個HealthCheckResult的枚舉代表健康檢查的幾種狀態,分別是異常,降級,健康。

public enum HealthStatus
{
    Unhealthy = 0,
    Degraded = 1,
    Healthy = 2,
}

實現接口,返回不健康狀態。

public class SqlHealthChecks : IHealthCheck
{
    public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken())
    {
        if (1 == 1)
        {
            return Task.FromResult(HealthCheckResult.Unhealthy());
        }
    }
}

ConfigureServices中添加自定義的檢查,AddCheck可以添加你自定的健康檢查服務,

public void ConfigureServices(IServiceCollection services)
{
    //健康檢查服務
    services.AddHealthChecks().AddCheck<SqlHealthChecks>("key");
}

測試可以發現返回的為不健康的應用

自定義返回值

我們可以利用HealthCheckOptions來實現健康檢查的自定義返回內容.

private static Task WriteResponse(HttpContext context, HealthReport healthReport)
{
    context.Response.ContentType = "application/json";
    var result = JsonHelper.SerializeObject(new
    {
        code = context.Response.StatusCode,
        errors = healthReport.Entries.Select(e => new
        {
            key = e.Key,
            value = e.Value.Status.ToString()
        })
    });

    return context.Response.WriteAsync(result);
}


public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime appLifetime)
{

    app.UseHealthChecks("/healthChecks", new HealthCheckOptions{ResponseWriter = WriteResponse});

}

測試返回效果

引入Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore 可以為DbContext進行檢查


public void ConfigureServices(IServiceCollection services)
{
    services.AddHealthChecks().AddCheck<SqlHealthChecks>("key").AddDbContextCheck<DbContext>("DbContext");
}

健康檢查UI

引入AspNetCore.HealthChecks.UI並在ConfigureServicesConfigure中加入相應的配置

public void ConfigureServices(IServiceCollection services)  
{  
    services.AddHealthChecksUI();  
}  

public void Configure(IApplicationBuilder app, IHostingEnvironment env)  
{  
    app.UseHealthChecksUI();  
}  

appsetting,json文件中加入配置

{
  "HealthChecksUI": {
    "HealthChecks": [
      {
        "Name": "HealthCheck",
        "Uri": "https://localhost:5000/healthCheck"
      }
    ],
    "EvaluationTimeinSeconds": 10,
    "MinimumSecondsBetweenFailureNotifications": 60
  }
}

啟動項目並指向/healthchecks-ui。

擴展包

開源社區已經有很多現有的優秀的擴展包我們可以直接引用

AspNetCore.HealthChecks.Npgsql
AspNetCore.HealthChecks.Redis
AspNetCore.HealthChecks.AzureStorage
AspNetCore.HealthChecks.AzureServiceBus
AspNetCore.HealthChecks.MySql
AspNetCore.HealthChecks.DocumentDb
AspNetCore.HealthChecks.SqLite
AspNetCore.HealthChecks.Kafka
AspNetCore.HealthChecks.RabbitMQ
AspNetCore.HealthChecks.IdSvr
AspNetCore.HealthChecks.DynamoDB
AspNetCore.HealthChecks.Oracle
AspNetCore.HealthChecks.Uris
AspNetCore.HealthChecks.System
AspNetCore.HealthChecks.Network
AspNetCore.HealthChecks.SqlServer
AspNetCore.HealthChecks.MongoDb

參考文章

  • 微軟官方文檔
  • 社區
  • 源碼理解HealthCheck

總結

實現了一個最簡單的健康檢查功能,可以在這個基礎上進行自定義的擴展和開發。

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

【其他文章推薦】

※教你寫出一流的銷售文案?

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

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

Jmeter(十一) – 從入門到精通 – JMeter邏輯控制器 – 下篇(詳解教程)

1.簡介

Jmeter官網對邏輯控制器的解釋是:“Logic Controllers determine the order in which Samplers are processed.”。

意思是說,邏輯控制器可以控制採樣器(samplers)的執行順序。由此可知,控制器需要和採樣器一起使用,否則控制器就沒有什麼意義了。放在控制器下面的所有的採樣器都會當做一個整體,執行時也會一起被執行。

JMeter邏輯控制器可以對元件的執行邏輯進行控制,除僅一次控制器外,其他可以嵌套別的種類的邏輯控制器。

2.邏輯控制器分類

JMeter中的Logic Controller分為兩類:
(1)控制測試計劃執行過程中節點的邏輯執行順序,如:Loop Controller、If Controller等;
(2)對測試計劃中的腳本進行分組、方便JMeter統計執行結果以及進行腳本的運行時控制等,如:Throughput Controller、Transaction Controller。

3.預覽邏輯控制器 

首先我們來看一下JMeter的邏輯控制器,路徑:線程組(用戶)->添加->邏輯控制器(Logic Controller);我們可以清楚地看到JMeter5中共有17個邏輯控制器,如下圖所示:

如果上圖您看得不是很清楚的話,宏哥總結了一個思維導圖,關於JMeter5的邏輯控制器類型,如下圖所示: 

 通過以上的了解,我們對邏輯控制器有了一個大致的了解和認識。下面宏哥就給小夥伴或則童鞋們分享講解一些通常在工作中會用到的邏輯控制器。 

4.常用邏輯控制器詳解

  這一小節,宏哥就由上而下地詳細地講解一下常用的邏輯控制器。

4.1Interleave Controller

交替控制器,顧名思義是:互相交替,其節點下的取樣器交替執行。根據被控制器觸發執行次數,去依次執行控制器下的子節點<邏輯控制器、採樣器>。被觸發執行可以由線程組的線程數、循環次數、邏輯控制器觸發。

1、我們先來看看這個Interleave Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 > 交替控制器,如下圖所示: 

2、關鍵參數說明如下:

Name:名稱,可以隨意設置,甚至為空;

Comments:註釋,可隨意設置,可以為空;

Ignore sub-controller blocks:忽略子控制器,即子控制器失效,由交替控制器接管。

勾選后,會無視節點下的所有控制器<交替控制器、隨機控制器例外>,將每個取樣器作為一個單獨字節點執行
不勾選忽略子控制器,交替執行時,節點下次一級每個取樣器、邏輯控制器都認為是一個單獨子節點來交替執行。

Interleave across threads: 勾選此項,則交替控制器下的請求將應用至所有線程和循環中迭代。如有四個請求,三個線程,兩輪循環,那麼第一輪三個線程分別運行請求1,請求2,請求3,第二輪循環的三個線程運行請求4,請求1,請求2。

允許跨線程交替執行,勾選后,當線程組線程數大於1時,當前線程首次執行會根據線程數順序進行交替,後續執行按自己所屬線程的上一個次的執行的位置交替,如: 交替控制器下由A B C D  E 5個接口, 設置線程組 線程數3個,循環4次,則最終執行結果為  線程1執行 A B C D 線程2執行 B C D E 線程3執行 C D E A 。

 4.1.1簡單實例

1、首先在交替控制器下添加3個取樣器 訪問博客園首頁、訪問北京宏哥的博客園首頁和訪問北京宏哥的JMeter系列文章,線程組下添加一個取樣器 訪問度娘,與交替控制器同層級,線程組設置循環次數為2,如下圖所示:

2、配置好以後,運行JMeter,然後查看結果樹(循環兩次,每次只執行交替控制器里一個取樣器),如下圖所示:

4.1.2複雜實例

宏哥這裏講解的複雜使用,就是將交替控制器嵌套使用,來看看執行結果,從而更進一步的理解和學習交替控制器。

1、創建一個父交替控制器:北京宏爸,其下兩個子交替控制器:北京宏哥 北京宏弟,子交替控制器下面分別添加2個取樣器:訪問度娘  訪問博客園首頁,設置線程組循環次數10,如下圖所示:

2、配置好以後,運行JMeter,然後查看結果樹( 從結果可以看出,先交替子控制器的樣例,再交替父控制器下的樣例。大家明白了吧),如下圖所示:

4.1.3忽略子控制器塊

  在交替控制器的設置界面,有這樣一個選項,是否忽略子控制器,所以這裏一般也是交替控制器作為父級控制器時使用的選項,這裏的子控制器一般指非交替控制器的其他控制器 (如果子控制器也是交替控制器,該項實際和交替控制器的嵌套效果一樣了)

1、下面,我們在交替器下添加一個循環控制器,設置循環次數 2,線程組循環次數設置為 3,設置交替器 勾選 忽略子控制器,如下圖所示:

循環控制器:

線程組:

交替控制器:

2、 配置好以後,運行JMeter,然後查看結果樹( 從結果可以看出,循環控制器沒有執行2次,只執行了1次),如下圖所示:

3、下面,我們再把交替控制器中 忽略子控制器 去掉勾選,其他設置不變,如下圖所示:

4、配置好以後,點擊“保存”,運行JMeter,然後查看結果樹( 從結果可以看出,循環控制器執行2次,然後再執行 訪問度娘-哥弟 取樣器這樣交替執行了3次),如下圖所示:

綜上所述:以控制器為1個小單元,交替執行

4.2Once Only Controller

在每個線程內,該控制器下的內容只會被執行一遍,無論循環多少次,都只執行一遍。<嵌套在循環控制器之內時是個例外,每個線程組循環都會被執行一遍>。

此控制器通常用於控制需要登錄的請求,測試過程中,我們往往都只需要登錄一次,獲取到對應的登錄信息后即可執行後續相關的請求,而不是每執行一個請求都登錄一次,如將login請求放入僅一次控制器,則在線程組循環運行期間,不論循環次數設置為多少次,login請求都將僅在第一次執行時運行

 1、我們先來看看這個Once Only Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 > 僅一次控制器,如下圖所示:

2、關鍵參數說明如下:

Name:名稱,可以隨意設置,甚至為空;

Comments:註釋,可隨意設置,可以為空。

4.2.1實例

宏哥這裏以博客園發布文章為例,說一下測試場景:正常邏輯是我們需要一次登錄博客園然後多次發布文章;而不是發布一次文章就需要登錄一次博客園。以此為例添加測試腳本。

1、按照上邊的測試場景,宏哥添加測試腳本,如下圖所示:

 2、配置好以後,點擊“保存”,運行JMeter,然後查看結果樹( 從結果可以看出,一次登錄博客園然後多次發布文章;而不是發布一次文章就需要登錄一次博客園),如下圖所示:

4.2.2紅色字體實戰舉例 

<嵌套在循環控制器之內時是個例外,每個線程組循環都會被執行一遍>。

1、保持上邊的測試樹結構,然後將 僅一次控制器 用鼠標拖到 循環控制器 裡邊,如下圖所示:

 2、配置好以後,點擊“保存”,運行JMeter,然後查看結果樹( 從結果可以看出,宏哥設置了3個線程,每個線程都登錄一次博客園),如下圖所示:

4.3Random Controller

隨機控制器節點下的元件隨機運行,與交替控制器不一樣的是節點下的元件運行順序不定。

 1、我們先來看看這個Random Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 >  隨機控制器,如下圖所示:

2、關鍵參數說明如下:

Name:名稱,可以隨意設置,甚至為空;

Comments:註釋,可隨意設置,可以為空;

Ignore sub-controller blocks:忽略子控制器,即子控制器失效,由隨機控制器接管,類似交替控制器。

4.3.1簡單實例 

1、創建測試計劃,隨機控制下添加三個請求,控制器外一個請求,線程4個;如下圖所示:

2、配置好以後,點擊“保存”,運行JMeter,然後查看結果樹( 從結果可以看出,宏哥設置了4個線程,每個線程都要訪問一次北京宏哥的Jmeter系列文章,但是控制器下邊的取樣器的訪問卻是隨機訪問一個),如下圖所示:

4.3.2隨機嵌套循環-不忽略子控制器

1、按照小標題的內容,創建測試計劃,如下圖所示:

2、配置好以後,點擊“保存”,運行JMeter,然後查看結果樹( 從結果可以看出,宏哥設置了3個線程,隨機選擇隨機控制器下的兩個循環控制器),如下圖所示:

4.3.3隨機嵌套循環-忽略子控制器

1、按照小標題的內容,創建測試計劃,如下圖所示:

2、配置好以後,點擊“保存”,運行JMeter,然後查看結果樹( 從結果可以看出,宏哥設置了3個線程,循環控制器也失效了,每次都隨機選擇一個取樣器執行),如下圖所示:

4.3.4隨機嵌套交替-忽略子控制器

1、按照小標題的內容,創建測試計劃,如下圖所示:

2、配置好以後,點擊“保存”,運行JMeter,然後查看結果樹( 從結果可以看出,宏哥設置了10次循環,交替控制器也失效了,每次都隨機選擇一個取樣器執行),如下圖所示:

4.4Random Order Controller

隨機順序控制器其節點下的原件隨機執行,不過每個元件只執行一次。

當控制器被觸發時,將控制器下的所有子節點順序打亂執行一遍,執行一遍;執行一遍,不是執行一個。

注意:是將子節點的順序打亂,而非請求的順序打亂,子節點可以是其他邏輯控制器。

隨機控制器與隨機順序控制器名字十分接近,但兩者還是有着明顯的區別,可參考  上邊介紹的隨機控制器。

隨機控制器為每次只執行節點下的一個子節點,隨機順序控制器是將節點下的所有子節點都正常執行,只是將執行順序打亂

1、我們先來看看這個Random Order Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 > 隨機順序控制器,如下圖所示: 

2、關鍵參數說明如下:

Name:名稱,可以隨意設置,甚至為空;

Comments:註釋,可隨意設置,可以為空。

4.4.1實例

1、創建測試計劃,如下圖所示: 

2、配置好以後,點擊“保存”,運行JMeter,然後查看結果樹( 從結果可以看出,宏哥設置了3次循環,每次循環把所有的子節點都執行了),如下圖所示: 

4.5Recording Controller

其錄製控制器,顧名思義是錄製的時候會用到。實際上它是一個位置,當我們用JMeter代理進行錄製時,錄製的腳本默認放在此控制器的節點下面。沒有實際的邏輯作用,我們用簡單控制器也可以代替它。由於這個沒有用到過,這裏宏哥就不做詳細介紹了,如果後期用到的話,宏哥會單獨寫一篇關於錄製控制器的文章給小夥伴或童鞋們來答疑解惑。

1、我們先來看看這個Recording Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 > 錄製控制器,如下圖所示: 

2、關鍵參數說明如下:

Name:名稱,可以隨意設置,甚至為空;

Comments:註釋,可隨意設置,可以為空;

Forever:勾選上這一項表示一直循環下去。

5.小結

 

 好了,今天關於邏輯控制器的上篇就講解到這裏,這一篇主要介紹了 Interleave ControllerOnce Only ControllerRandom Controller  Random Order ControllerRecording Controller

 

您的肯定就是我進步的動力。如果你感覺還不錯,就請鼓勵一下吧!記得隨手點波  推薦  不要忘記哦!!!

別忘了點 推薦 留下您來過的痕迹

 

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

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

FB行銷專家,教你從零開始的技巧