7月零颱海水升溫 日本專家憂沖繩珊瑚白化

摘錄自2020年08月02日中央通訊社日本報導

日本觀測史上首度出現「7月零颱風」,沒想到也可能造成珊瑚大規模白化。日本專家指出,颱風不來就無法讓海水溫度下降,沖繩本島周邊有可能出現大範圍的珊瑚白化。

珊瑚適合生存在攝氏18度到28度的海水中,如果水溫維持高溫,對珊瑚生長來說不可或缺的褐蟲藻就會離開,然後造成珊瑚白化。日本氣象廳表示,沖繩周邊海域海面溫度在7月31日為30度。今年7月一個颱風都沒有生成,創下日本1951年開始觀測以來紀錄。颱風一方面雖然會帶來災情,但另一方面也有攪動海水、為夏天升高的水溫降溫的效果。

日本政府環境省石垣自然保護官辦公室7月調查石垣市的名藏灣,已確認部分珊瑚白化。自然保護官大嶽若緒說,期待颱風能適當地攪動海水降低海水溫度。

生物多樣性
物種保育
海洋
氣候變遷
國際新聞
日本
沖繩
珊瑚白化
颱風
海水升溫

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

【其他文章推薦】

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

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

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

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

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

聯合國報告:疫情恐加深東南亞地區不平等

摘錄自2020年7月30日中央社報導

聯合國今天(30日)發布COVID-19對東南亞的影響報告(Policy Brief: The Impact of COVID-19 on South-East Asia)指出,武漢肺炎疫情加深東南亞的不平等現象,東南亞國家的政府應該把重點放在疫情對社會經濟的影響。疫情對政治、經濟和健康的影響,重擊社會底層人民,使他們失業且更加貧窮。

報告指出,東南亞要從疫情中復甦有四個關鍵領域,包括打擊不平等、消弭數位落差、綠化經濟並保持人權和良好的治理。

報告也進一步強調,在面對疫情時,東南亞國家以及他們的領導人在保障人權以及良好治理上具有重要的角色。領導人們應該多利用以社區為中心的組織、促進包容及團結,並挺身而出反對歧視。

土地利用
國際新聞
東南亞國家
疫情下的社會衝突
貧窮

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

RocketMQ系列(五)廣播與延遲消息

今天要給大家介紹RocketMQ中的兩個功能,一個是“廣播”,這個功能是比較基礎的,幾乎所有的mq產品都是支持這個功能的;另外一個是“延遲消費”,這個應該算是RocketMQ的特色功能之一了吧。接下來,我們就分別看一下這兩個功能。

廣播

廣播是把消息發送給訂閱了這個主題的所有消費者。這個定義很清楚,但是這裏邊的知識點你都掌握了嗎?咱們接着說“廣播”的機會,把消費者這端的內容好好和大家說說。

  • 首先,消費者端的概念中,最大的應該是消費者組,一個消費者組中可以有多個消費者,這些消費者必須訂閱同一個Topic。
  • 那麼什麼算是一個消費者呢?我們在寫消費端程序時,看到了setConsumeThreadMax這個方法,設置消費者的線程數,難道一個線程就是一個消費者?錯!這裏的一個消費者是一個進程,你可以理解為ip+端口。如果在同一個應用中,你實例化了兩個消費者,這兩個消費者配置了相同的消費者組名稱,那麼應用程序啟動時會報錯的,這裏不給大家演示了,感興趣的小夥伴私下里試一下吧。
  • 同一個消息,可以被不同的消費者組同時消費。假設,我有兩個消費者組cg-1和cg-2,這兩個消費者組訂閱了同一個Topic,那麼這個Topic的消息會被cg-1和cg-2同時消費。那這是不是廣播呢?錯!當然不是廣播,廣播是同一個消費者組中的多個消費者都消費這個消息。如果配置的不是廣播,像前幾個章節中的那樣,一個消息只能被一個消費者組消費一次。

好了,說了這麼多,我們實驗一下吧,先把消費者配置成廣播,如下:

@Bean(name = "broadcast", initMethod = "start",destroyMethod = "shutdown")
public DefaultMQPushConsumer broadcast() throws MQClientException {
    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("broadcast");
    consumer.setNamesrvAddr("192.168.73.130:9876;192.168.73.131:9876;192.168.73.132:9876;");
    consumer.subscribe("cluster-topic","*");
    consumer.setMessageModel(MessageModel.BROADCASTING);
    consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
        for (MessageExt msg : msgs) {
            System.out.println(new String(msg.getBody()));
        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    });
    return consumer;
}

  • 其中,NameServer,訂閱的Topic都沒有變化。
  • 注意其中consumer.setMessageModel(MessageModel.BROADCASTING);這段代碼,設置消費者為廣播。咱們可以看一下,MessageModel枚舉中只有兩個值,BROADCASTINGCLUSTERING,默認為CLUSTERING

因為要測試廣播,所以我們要啟動多個消費者,還記得什麼是消費者嗎?對了,一個ip+端口算是一個消費者,在這裏我們啟動兩個應用,端口分別是8080和8081。發送端的程序不變,如下:

@Test
public void producerTest() throws Exception {

    for (int i = 0;i<5;i++) {
        MessageExt message = new MessageExt();
        message.setTopic("cluster-topic");
        message.setKeys("key-"+i);
        message.setBody(("this is simpleMQ,my NO is "+i+"---"+new Date()).getBytes());
        SendResult sendResult = defaultMQProducer.send(message);
        System.out.println("i=" + i);
        System.out.println("BrokerName:" + sendResult.getMessageQueue().getBrokerName());
    }
}

我們執行一下發送端的程序,日誌如下:

i=0
BrokerName:broker-a
i=1
BrokerName:broker-a
i=2
BrokerName:broker-b
i=3
BrokerName:broker-b
i=4
BrokerName:broker-b

再來看看8080端口的應用後台打印出來的日誌:

消費了5個消息,再看看8081的後台打印的日誌,

也消費了5個。兩個消費者同時消費了消息,這就是廣播。有的小夥伴可能會有疑問了,如果不設置廣播,會怎麼樣呢?私下里實驗一下吧,上面的程序中,只要把設置廣播的那段代碼註釋掉就可以了。運行的結果當然是只有一個消費者可以消費消息。

延遲消息

延遲消息是指消費者過了一個指定的時間后,才去消費這個消息。大家想象一個電商中場景,一個訂單超過30分鐘未支付,將自動取消。這個功能怎麼實現呢?一般情況下,都是寫一個定時任務,一分鐘掃描一下超過30分鐘未支付的訂單,如果有則被取消。這種方式由於每分鐘查詢一下訂單,一是時間不精確,二是查庫效率比較低。這個場景使用RocketMQ的延遲消息最合適不過了,我們看看怎麼發送延遲消息吧,發送端代碼如下:

@Test
public void producerTest() throws Exception {

    for (int i = 0;i<1;i++) {
        MessageExt message = new MessageExt();
        message.setTopic("cluster-topic");
        message.setKeys("key-"+i);
        message.setBody(("this is simpleMQ,my NO is "+i+"---"+new Date()).getBytes());
        message.setDelayTimeLevel(2);
        SendResult sendResult = defaultMQProducer.send(message);
        System.out.println("i=" + i);
        System.out.println("BrokerName:" + sendResult.getMessageQueue().getBrokerName());
    }
}
  • 我們只是增加了一句message.setDelayTimeLevel(2);
  • 為了方便,這次我們只發送一個消息。

setDelayTimeLevel是什麼意思,設置的是2,難道是2s后消費嗎?怎麼參數也沒有時間單位呢?如果我要自定義延遲時間怎麼辦?我相信很多小夥伴都有這樣的疑問,我也是帶着這樣的疑問查了很多資料,最後在RocketMQ的Github官網上看到了說明,

  • 在RocketMQ的源碼中,有一個MessageStoreConfig類,這個類中定義了延遲的時間,我們看一下,
// org/apache/rocketmq/store/config/MessageStoreConfig.java
private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
  • 我們在程序中設置的是2,那麼這個消息將在5s以後被消費。
  • 目前RocketMQ還不支持自定義延遲時間,延遲時間只能從上面的時間中選。如果你非要定義一個時間怎麼辦呢?RocketMQ是開源的,下載代碼,把上面的時間改一下,再打包部署,就OK了。

再看看消費端的代碼,

@Bean(name = "broadcast", initMethod = "start",destroyMethod = "shutdown")
public DefaultMQPushConsumer broadcast() throws MQClientException {
    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("broadcast");
    consumer.setNamesrvAddr("192.168.73.130:9876;192.168.73.131:9876;192.168.73.132:9876;");
    consumer.subscribe("cluster-topic","*");
    consumer.setMessageModel(MessageModel.BROADCASTING);
    consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
        for (MessageExt msg : msgs) {
            Date now = new Date();
            System.out.println("消費時間:"+now);

            Date msgTime = new Date();
            msgTime.setTime(msg.getBornTimestamp());
            System.out.println("消息生成時間:"+msgTime);

            System.out.println(new String(msg.getBody()));
        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    });
    return consumer;
}
  • 我們還是使用廣播的模式,沒有變。
  • 打印出了當前的時間,這個時間就是消費的時間。
  • 通過msg.getBornTimestamp()方法,獲得了消息的生成時間,也打印出來,看看是不是延遲5s。

啟動兩個消費者8080和8081,發送消息,再看看消費者的後台日誌,

消費時間:Thu Jun 11 14:45:53 CST 2020
消息生成時間:Thu Jun 11 14:45:48 CST 2020
this is simpleMQ,my NO is 0---Thu Jun 11 14:45:47 CST 2020

我們看到消費時間比生成時間晚5s,符合我們的預期。這個功能還是比較實用的,如果能夠自定義延遲時間就更好了。

總結

RocketMQ的這兩個知識點還是比較簡單的,大家要分清楚什麼是消費者組,什麼是消費者,什麼是消費者線程。另外就是延遲消息是不支持自定義的,大家可以在Github上看一下源碼。好了~今天就到這裏了。

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

【其他文章推薦】

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

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

※台北網頁設計公司全省服務真心推薦

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

※推薦評價好的iphone維修中心

曹工說JDK源碼(4)–抄了一小段ConcurrentHashMap的代碼,我解決了部分場景下的Redis緩存雪崩問題

曹工說JDK源碼(1)–ConcurrentHashMap,擴容前大家同在一個哈希桶,為啥擴容后,你去新數組的高位,我只能去低位?

曹工說JDK源碼(2)–ConcurrentHashMap的多線程擴容,說白了,就是分段取任務

曹工說JDK源碼(3)–ConcurrentHashMap,Hash算法優化、位運算揭秘

什麼是緩存雪崩

基本概念梳理

這個基本也是redis 面試的經典題目了,然而,網上不少博客對這個詞的定義都含糊不清,各執一詞。

主要有兩類說法:

  • 大量緩存key,由於設置了相同的過期時間,在某個時刻同時失效,導致此刻的查詢請求,全部湧向db,本來db的tps大概是幾千左右,結果湧入了幾十萬的請求,那db肯定直接就扛不住了

    這種說法下面,解決方案一般是,把過期時間增加一個隨機值,這樣,也就不會大批量的key同時失效了

  • 另外一種說法是,本來redis扛下了大部分的請求,但是,由於緩存所在的機器,發生了宕機。此時,緩存這台機器之間就連不上了,redis服務也掛了,此時,你的服務里,發現redis取不到,然後全都跑去查數據庫,那,就發生和前面一樣的情況了,請求全部湧向db,db無響應。

兩類說法,也不用覺得,這個對,那個不對,不過是一個技術名詞,當初發明這個詞的人,估計也沒想那麼多,結果傳播開來之後,就變成了現在這個樣子。

我們這裏主要採用下面那一種說法,因為下面這種說法,其實是已經包含了上面的情景。但是,下面這種場景,要複雜的多,因為redis此時就是一個完全不可信的東西了,你得想好,怎麼不讓它掛掉,那是不是應該部署sentinel、cluster集群?同時,持久化必須要開啟。

這樣呢,掛掉后,短暫的不可用之後,大概幾十s吧,緩存集群就恢復了,就又可用了。

同時,我們還得考慮,假設,現在redis掛了,我們代碼的降級策略是什麼?

大家發現redis掛了,首先,估計是會拋異常了,連接超時;拋了異常后,要直接拋到前端嗎?作為一個穩健的後端程序,那肯定是不行的,你redis掛了,數據庫又沒掛;好吧,那我們就大家一起去查數據庫。

結果,大量的查詢請求,就烏泱泱地跑去查庫了,然後,db卒。這個肯定不行。

所以,我們必須要控制的一點是,當發現某個key失效了,不是大家都去查庫,而是要進行 併發控制

什麼是併發控制?就是不能全部放過去查庫,只能放少部分,免得把脆弱的db打死。

併發控制,基本就是要爭奪去查庫的權利了,這一步,基本就是一個選舉的過程,可以通過搶鎖的方式,比如Reentrentlock,synchronized,cas也可以。

  1. 搶到鎖的線程,有資格去查庫,其他線程要麼被阻塞,要麼自旋

  2. 搶到鎖的線程,去查庫,查到數據后,將數據存放在某個地方,通知其他線程去取(如果其他線程被阻塞的話);或者,如果其他線程沒被阻塞,比如sleep 50ms,再去指定的地方拿數據那種,這種就不需要通知

    總之,如果其他線程要我們通知,我們就通知;不要我們通知,我們就不通知。

搶到鎖的線程,在構建緩存時,其他線程應該干什麼?

  1. 在while(true)里,sleep 50ms,然後再去取數據

    這種類似於忙等待,但是每次sleep一會,所以還不錯

  2. 將自己阻塞,等待搶到鎖的線程,構建完緩存后,來喚醒

  3. 在while(true)里,一直忙循環,期間一直檢查數據是否已經ok了,這種方案呢,要看裏面:檢查數據的操作,是否耗時;如果只是檢查jvm內存里的數據,那還好;否則的話,假設要去檢查redis的話,這種io比較耗時的操作的話,就不合適了,cpu會一直空轉。

本文採用的方案

主線程構建緩存時,其他線程,在while(true)里,sleep 一定時間,然後再檢查數據是否ready。

說了這麼多,好像和題目里的concurrenthashmap沒啥關係,不,是有關係的,因為,這個思路,其實就是來自於concurrentHashMap。

ConcurrentHashMap中,是怎麼去初始化底層數組的

在我們用無參構造函數,去new一個ConcurrentHashMap時,此時還不會去創建底層數組,這個是一個小優化。什麼時候創建數組呢,是在我們第一次去put的時候。

put的時候,會調用putVal。

其中,putVal代碼如下:

    transient volatile Node<K,V>[] table;

	final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());
        int binCount = 0;
      	// 1
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
          	// 2
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
  • 1處,把field table,賦值給局部變量tab

  • 2處,如果tab為null,則進行initTable初始化

    這個2處,在多線程put的時候,是可能多個線程同時進來的。有併發問題。

我們接下來,看看initTable是怎麼解決這個問題的,畢竟,我們new數組,只new一次即可,new那麼多次,沒用,對性能有損耗。所以,這裏面肯定會多線程爭奪初始化權利的代碼。

	private transient volatile int sizeCtl;
	transient volatile Node<K,V>[] table;

	/**
     * Initializes table, using the size recorded in sizeCtl.
     */
    private final Node<K,V>[] initTable() {
        Node<K,V>[] tab;
      	int sc;
      	
      	// 0
        while ((tab = table) == null || tab.length == 0) {
          	// 1
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // lost initialization race; just spin
          	// 2
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                  	// 3
                    if ((tab = table) == null || tab.length == 0) {
                      	// 4
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        sc = n - (n >>> 2);
                    }
                } finally {
                  	// 5
                    sizeCtl = sc;
                }
                break;
              
            }// end if
          
        }// end while
        return tab;
    }
  • 1處,這裏把sizeCtl,賦值給局部變量sc。這裏的sizeCtl是一個很重要的field,當我們new完之後,默認這個字段,要麼為0,要麼為準備創建的底層數組的長度。

    這裏去判斷是否小於0,那肯定不滿足,小於0,會是什麼意思?當某個線程,搶到了這個initTable中的底層數組的創建權利時,就會把sizeCtl改為 -1。

    所以,這裏的意思是,看看是否已經有其他線程在初始化了,如果已經有了,則直接調用:

    Thread.yield();

    這個方法的意思是,暗示操作系統,自己準備放棄cpu;但操作系統,自有它自己的線程調度規則,所以,這個方法可能沒什麼效果;我們業務代碼,這裏一般可以修改為Thread.sleep。

    這個方法調用完成后,後續也沒有其他代碼,所以會直接跳轉到循環開始處(0處代碼),判斷table是否初始化ok了,如果沒有ok,則會繼續進來。

  • 2處,使用cas,如果此時,sizeCtl的值等於sc的值,就修改sizeCtl為 -1;如果成功,則返回true,進入3處

    否則,會跳轉到0處,繼續循環。

  • 3處,雖然搶到了控制權,但是這裏還是要再判斷一下,不然可能出現重複初始化,即,不加這一行,4處的代碼,會被重複執行

  • 4處開始,這裏去執行真正的初始化邏輯。

    // 
    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
    @SuppressWarnings("unchecked")
    // 1
    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
    // 2
    table = tab = nt;
    sc = n - (n >>> 2);
    

    這裏的1處,new數組;2處,賦值給field:table;此時,因為table 這個field是volatile修飾的,所以其他線程會馬上感知到。0處代碼就不會為true了,就不會繼續循環了。

  • 5處,修改sizeCtl為正數。

這裏說下,為啥要加3處的那個判斷。

現在,假設線程A,在初始化完成后,走到了5處,修改了sizeCtl為正數;而線程B,剛好執行1處代碼:

// 1
if ((sc = sizeCtl) < 0)

那肯定,1處就不滿足了;然後就會進到2處,cas修改成功,進行初始化。沒有3處判斷的話,就會重複初始化。

基於concurrentHashmap,實現我們的緩存雪崩方案

我這裏的方案,還是比較簡單那種,就是,n個線程同時爭奪構建緩存的權利;winner線程,構建緩存后,會把緩存設置到redis;其他線程則是一直在while(true)里sleep一段時間,然後檢查redis里的數據是否不為空。

這個方案中,redis掛了這種情況,是沒在考慮中的,但是一個方案,沒辦法立馬各方面全部到位,後續我再完善一下。

不考慮緩存雪崩的代碼

@Override
public Users getUser(long userId) {
    ValueOperations<String, Users> ops = redisTemplate.opsForValue();
  	// 1
    Users s = ops.get(String.valueOf(userId));
    if (s == null) {
        /**
         * 2 這裏要去查庫獲取值
         */
        Users users = getUsersFromDB(userId);
		// 3
        ops.set(String.valueOf(users.getUserId()),users);

        return users;
    }

    return s;
}

private Users getUsersFromDB(long userId) {
    Users users = new Users();

    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("spent 1s to get user from db");
    users.setUserId(userId);
    users.setUserName("zhangsan");

    return users;
}

直接看上面的1,2,3處。就是檢查、構建緩存,設置到緩存的過程。

考慮緩存雪崩的代碼

	// 1
	private volatile int initControl;

	@Override
    public Users getUser(long userId) {
        ValueOperations<String, Users> ops = redisTemplate.opsForValue();

        Users users;
        while (true) {
          	// 2
            users = ops.get(String.valueOf(userId));
            if (users != null) {
              	// 3 
                break;
            }
			
          	// 4
            int initControlLocal = initControl;
            /**
             * 5 如果已經有線程在進行獲取了,則直接放棄cpu
             */
            if (initControlLocal < 0) {
//                log.info("initControlLocal < 0,just yield and wait");
//                Thread.yield();
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    log.warn("e:{}", e);
                }
                continue;
            }


            /**
             * 6 爭奪控制權
             */
            boolean bGotChanceToInit = U.compareAndSwapInt(this,
                    INIT_CONTROL, initControlLocal, -1);
          	// 7
            if (bGotChanceToInit) {
                try {
                  	// 8
                    users = ops.get(String.valueOf(userId));
                    if (users == null) {
                        log.info("got change to init");
                        /**
                         * 9 這裏要去查庫獲取值
                         */
                        users = getUsersFromDB(userId);
                        ops.set(String.valueOf(users.getUserId()), users);
                        log.info("init over");
                    }
                } finally {
                  	// 10
                    initControl = 0;
                }

                break;
            }// end if (bGotChanceToInit)
        }// end while


        return users;
    }
  • 1處,定義了一個field,initControl;默認為0.線程們會去使用cas,修改為-1,成功的線程,即獲得初始化緩存的權利。

    注意,要定義為volatile,保證線程間的可見性

  • 2處,去redis獲取緩存,如果不為null,直接返回

  • 4處,如果沒取到緩存,則進入此處;此處,將field:initControl賦值給局部變量

  • 5處,判斷局部變量initControlLocal,是否小於0;小於0,說明已經有線程在進行初始化了,直接contine,繼續下一次循環

  • 6處,如果當前還沒有線程在初始化,則開始競爭初始化的權利,誰成功地用cas,修改field:initControl為-1,誰就獲得這個權利

  • 7處,如果當前線程獲得了權利,則進入8處,否則,會繼續下一次循環

  • 8處,再次去redis,獲取緩存,如果不為空,則進入9處

  • 9處,查庫,設置緩存

  • 10處,修改field:initControl為0,表示退出初始化

這裏的代碼,整體和hashmap中的initTable是一模一樣的。

如何測試

上面的方案,怎麼測試沒問題呢?我寫了一段測試代碼。

    ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 100,
            60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000), new RejectedExecutionHandler() 	{
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            log.info("discard:{}",r);
        }
    });
	
	@RequestMapping("/test.do")
    public void test() {
      	// 0
        iUsersService.deleteUser(111L);

        CyclicBarrier barrier = new CyclicBarrier(100);

        for (int i = 0; i < 100; i++) {

            executor.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        barrier.await();
                    } catch (InterruptedException | BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                    long start = System.currentTimeMillis();
                  	// 1
                    Users users = iUsersService.getUser(111L);
                    log.info("result:{},spent {} ms", users, System.currentTimeMillis() - start);
                }
            });
        }

    }

上面模擬100併發下,獲取緩存。

0處,把緩存刪了,模擬緩存失效

1處,調用方法,獲取緩存。

效果如下:

可以看到,只有一個線程拿到了初始化權利。

源碼位置

https://gitee.com/ckl111/all-simple-demo-in-work-1/tree/master/redis-cache-avalanche

總結

jdk的併發包,寫得真是有水平,大家仔細研究的話,必有收穫。

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

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

Spring系列.依賴注入配置

依賴注入的配置

Spring的依賴注入分為基於構造函數的依賴注入基於setter方法的依賴注入

基於構造函數的依賴注入

        <!-- 通過構造器參數索引方式依賴注入 -->
	<bean id="byIndex" class="cn.javass.spring.chapter3.HelloImpl3">
		<constructor-arg index="0" value="Hello World!"/>
		<constructor-arg index="1" value="1"/>
	</bean>
	<!-- 通過構造器參數類型方式依賴注入 -->
	<bean id="byType" class="cn.javass.spring.chapter3.HelloImpl3">
		<constructor-arg type="java.lang.String" value="Hello World!"/>
		<constructor-arg type="int" value="2"/>
	</bean>
	<!-- 通過構造器參數名稱方式依賴注入 -->
	<bean id="byName" class="cn.javass.spring.chapter3.HelloImpl3">
		<constructor-arg name="message" value="Hello World!"/>
		<constructor-arg name="index" value="3"/>
	</bean>
	<!-- 通過靜態的工廠方法注入 -->
	<bean id="byName" class="cn.javass.spring.chapter3.HelloImpl3" factory-method="getBean">
		<constructor-arg name="message" value="Hello World!"/>
		<constructor-arg name="index" value="3"/>
	</bean>

基於setter方法的依賴注入

    <bean class="...HelloImpl4">
	    <property name="message" value="Hello"/>
	    <property name="index" value="1"/>  //value中的值全部是字符串形式,如果轉換出錯會報異常
	</bean>
	<bean id="Hello2" class="com.csx.personal.web.services.HelloImpl2">
		<property name="msg" ref="message"/>  //msg屬性是一個類對象
	</bean>

循環依賴:創建Bean A需要Bean B,創建Bean B需要Bean C,創建Bean C需要Bean A 這樣就形成了循環依賴。 Spring的解決方案:Spring創建Bean的時候會維護一個池,在創建A的時候會去池中查找A是否在池子中,假如發現就拋出循環依賴異常。

避免依賴注入時的循環依賴:可以使用setter方式注入,不要使用構造器形式的注入。

依賴配置常見列子

常量值注入配置

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <!-- results in a setDriverClassName(String) call -->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
        <property name="username" value="root"/>
        <property name="password" value="masterkaoli"/>
    </bean>

注入其他Bean

這邊分別給出了一個引用當前容器和引用父容器中Bean的列子。

   <bean id="Hello2" class="com.csx.personal.web.services.HelloImpl2">
	<property name="msg">        //msg屬性是一個類對象
            <ref  bean="message"/>   //引用同一個容器中id="message"的Bean
       </property>
   </bean>
    
    <!-- 引用父容器中的Bean -->
    <!-- in the parent context -->
    <bean id="accountService" class="com.something.SimpleAccountService">
    <!-- insert dependencies as required as here -->
    </bean>
 
    <!-- in the child (descendant) context -->
    <bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
    </bean>

注入內部Bean

內部bean:這種bean一般只讓某個外部bean使用(和內部類相似),不讓容器中的其他Bean使用。

    <bean id="outer" class="...">
        <property name="target">
             <!-- this is the inner bean -->
            <bean class="com.example.Person"> 
                <property name="name" value="Fiona Apple"/>
                <property name="age" value="25"/>
            </bean>
        </property>
   </bean>

集合的注入

集合類的注入建議使用util命名空間

    <util:map id="myMap" key-type="java.lang.String" value-type="java.lang.String">
        <entry key="key1" value="chen"/>
        <entry key="key2" value="zhao"/>
    </util:map>

    <util:list id="myList" value-type="java.lang.String">
        <value>chen</value>
        <value>zhao</value>
    </util:list>
    
    <util:set id="mySet" value-type="java.lang.String" scope="singleton">
        <value>chen</value>
        <value>zhao</value>
    </util:set>
    
    <util:properties id="myProp" location="classpath:xx.properties"/>

null值和空字符串的注入

   <bean class="...HelloImpl4">
        <property name="message"><null/></property> //null值
        <property name="index" value=""/>  //空字符串
  </bean>

使用depends-on屬性

depends-on屬性用來指定bean的初始化順序。這個屬性只對scope是單列的bean生效

    <!--在實例化beanOne之前先實例化manager和accountDao這兩個bean-->
    <bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
	<property name="manager" ref="manager" />
    </bean>
    <bean id="manager" class="ManagerBean" />
    <bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

懶加載

bean的定義中有一個lazy-init這個屬性,用來設置單列bean在容器初始化后是否實例化這個bean。默認情況下容器是會實例化所有單例bean的,我們也建議這麼做,因為這樣能在容器初始化階段就發現bean配置是否正確。如果一個Bean按照下面的設置,lazy-init被設置為true那麼它不會被容器預初始化,只有在被使用的時候才被初始化。但是如果有一個單列類依賴了這個Bean,那麼這個被設置成懶加載的Bean還是會被預初始化。

   <bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>

如果想設置全局的單例Bean都不要預先初始化,那麼可以在xml中做如下設置:

    <beans default-lazy-init="true">
        <!-- no beans will be pre-instantiated... -->
    </beans>

Autowiring相關

當我們要往一個bean的某個屬性里注入另外一個bean,我們會使用property +ref標籤的形式。但是對於大型項目,假設有一個bean A被多個bean引用注入,如果A的id因為某種原因修改了,那麼所有引用了A的bean的ref標籤內容都得修改,這時候如果使用autowire=”byType”,那麼引用了A的bean就完全不用修改了。

 <!--autowire的用法如下,對某個Bean配置autowire模式 -->
 <!--和給Bean的屬性添加@AutoWired註解的效果一致-->
 <bean id="auto" class="example.autoBean" autowire="byType"/>

autowire的幾種模式:

  • no模式:也是默認模式,這種模式下不會進行自動屬性注入,需要我們自己通過value或者ref屬性進行配置;
  • byName模式:通過屬性的名稱自動裝配,Spring會在容器中查找名稱與bean屬性名稱一致的bean,並自動注入到bean屬性中。當然bean的屬性需要有setter方法。例如:bean A有個屬性master,master的setter方法就是setMaster,A設置了autowire=”byName”,那麼Spring就會在容器中查找名為master的bean通過setMaster方法注入到A中;
  • byType:通過類型自動裝配(注入)。Spring會在容器中查找類(Class)與bean屬性類一致的bean,並自動注入到bean屬性中,如果容器中包含多個這個類型的bean,Spring將拋出異常。如果沒有找到這個類型的bean,那麼注入動作將不會執行;
  • constructor:類似於byType,但是是通過構造函數的參數類型來匹配。假設bean A有構造函數A(B b, C c),那麼Spring會在容器中查找類型為B和C的bean通過構造函數A(B b, C c)注入到A中。與byType一樣,如果存在多個bean類型為B或者C,則會拋出異常。但時與byType不同的是,如果在容器中找不到匹配的類的bean,將拋出異常,因為Spring無法調用構造函數實例化這個bean;
  • default : 採用父級標籤(即beans標籤的default-autowire屬性)的配置。

需要注意的是上面這5中方式注入都需要我們提供相應的setter方法,通過@Autowired的方式不需要提供相應的setter方法。

自動裝配的缺點

  • 屬性和構造函數參數設置中的顯式依賴項會覆蓋自動裝配;

將Bean排除自動裝配

如果按照下面的方式配置了Bean,那麼這個Bean將不會作為自動裝配的候選Bean。但是autowire-candidate自會對byType形式的自動注入生效,如果我們是通過byName的形式進行自動注入,那麼還是能注入這個Bean。

    <bean id="auto" class="example.autoBean" autowire="byType" autowire-candidate="false"/>

如果我們只想讓某些Bean作為自動裝配的候選Bean,那麼可以進行全局設置。如果做了下面的配置,那麼只有id為bean1和bean2的Bean才會成為自動裝配的候選Bean。同時default-autowire-candidates的值支持正則表達式形式。但是強烈建議不要配置這個選項的值,使用默認的配置就行。

    <beans default-autowire-candidates="bean1,bean2">
    </beans>

方法注入(單例依賴原型Bean)

我們在配置bean的時候首先要考慮這個bean是要配置成單例模式還是其他模式。 在我們的應用中,絕大多數類都是單例類。當單例類引用單例類,或者原型類引用原型類、單例類時,我們只要像普通的配置方式就行了。但是當一個單例類引用原型類時,就會出現問題。這種情況可以使用下面的方式進行注入。

   @Service
   @Scope("prototype")
   public class MyService1 {
public void service() {
   System.out.println(this.toString() + ":id");
}
	}
	
	@Service
public abstract class MyService {

    private MyService1 service1;
    public void useService(){
     service1 = createService1();
     service1.service();
    }

    @Lookup("myService1")
    public abstract MyService1 createService1();

}
	
//也可以這樣配置bean
<bean id="serviceC" class="com.csx.demo.springdemo.service.ServiceC">
    <lookup-method bean="serviceD" name="createService"/>
</bean>
<bean id="serviceD" class="com.csx.demo.springdemo.service.ServiceD"     scope="prototype"/>

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

【其他文章推薦】

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

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

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

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

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

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

WinForm通用自動更新AutoUpdater項目實戰

目前我們做的上位機項目還是以Winform為主,在實際應用過程中,可能還會出現一些細節的修改。對於這種情況,如果上位機帶有自動更新功能,我們只需要將更新后的應用程序打包放在指定的路徑下,可以讓用戶自己來進行更新使用,會大大增加項目的便捷性。

 

01.自動更新整體思路

今天給大家介紹一下如何基於C#實現WinForm自動更新的一種方式,這種方式長期應用在項目中,提供了很多幫助,也節約了大量的時間成本,並且也使用在CMSPro軟件中,整體流程如下圖所示:

圖表 1自動更新流程

 

02.實現說明

通過上圖,可以發現這種方式是基於打包文件的方式實現的,好處在於整體打包下載,即使中途出現網絡中斷也不會有任何影響,當然相比於那種單個文件更新的方式,可能每次耗時會多一些,但是由於更新並不是一個頻繁操作的過程,這個時間是可以接受的。

(1)首先對於項目是否啟用自動更新,是通過配置的方式實現的,在實際開發中,可以使用手動更新和自動更新兩種方式,當啟用自動更新時,每次啟動應用程序都會與服務器版本號做下比較,判斷是否執行自動更新的流程。

圖表 2自動更新界面

 

(2)對於手動更新,可以通過點擊,彈出一個手動更新窗體,如下圖所示:

圖表 3手動更新界面

 

(3)對於服務器路徑、本地版本號等信息都是通過本地配置文件存儲的,因此本地需要有一個LocalVersion的配置文件,具體用什麼形式,可以自由選擇,Ini、Txt、Xml、Json都可以,如下圖所示:

圖表 4本地配置文件參考

 

(4)服務器側也會有一個配置文件,形式自由選擇,應該包含以下信息:當前服務器版本號、最新版本的程序包、該版本是否更新、該版本更新內容等信息,同時如果有新版本,應該將新版本的文件放到指定路徑下,保證最新版本包的這個路徑是有效路徑。

圖表 5服務器配置文件參考

 

(5)上位機通過將服務器的最新版本號與本地的版本號做對比,如果服務器的版本號較大,說明服務器有更新版本,因此,會根據最新版本包的地址進行下載,這裏採用的是zip文件,下載過程根據網絡及實際情況可能會耗時,因此上位機側應該做個進度條,讓用戶知道下載的進度情況,同時對於每一步的狀態也應該通過圖標的方式來進行显示,讓用戶明確更新的進度情況。

圖表 6自動更新流程

 

(6)更新完成后,系統會自動重啟新的應用程序,可以看到軟件從之前的5.3.5版本升級到最新的6.0.0版本。

圖表 7更新結果

03.整體總結

本文主要工控上位機進行自動更新的流程做了一個整體介紹,主要是介紹流程為主,給大家分享一下實現的整體思路,畢竟每個人的實現方式都可能有所不同,大家也可以在此基礎上增加一個新的功能,給自己的上位機軟件增加一點特色的同時,也給自己提供了便捷一下升級的過程的話,可以通過關注本公眾號:dotNet工控上位機,併發送關鍵詞:CMSPro,下載之後安裝運行,便會直接進入版本升級的過程。

公眾號:thinger_swj

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

【其他文章推薦】

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

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

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

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

[computer graphics]簡單光照模型(Phong和Blinn-Phong)和明暗處理

簡單光照模型(Phong和Blinn-Phong)和明暗處理

支持點光源和平行光,是一種簡單光照模型,它將光照分解成了三個部分,分別為

  • 漫反射
  • 鏡面反射
  • 環境光

如圖所示,是一個簡單的幾何模型。

  • \(L\)是光源方向
  • \(N\)是法線方向
  • \(R\)是反射方向
  • \(V\)是視線方向
  • \(H\)\(L\)\(V\)的平分
  • 所有向量都是單位向量

理想漫反射

當光源來自一個方向時,漫反射均勻地向各個方向傳播,與視點無關,是由物體表面粗糙不平引起的,漫反射的空間分佈是均勻的,也就是說不論從哪個方向看去,同一個點的漫反射光強都是一樣的。物體上的點\(P\),法向量為\(N\),入射光強度為\(I_p\)\(L\)\(P\)指向光源的方向。如果所有所有的向量都是單位向量,那麼有

\[I_d = I_pK_d\cdot(L\cdot N) \]

其中\(K_d=(K_{dr},K_{dg},K_{db})\)這三個分量分別是RGB三原色的漫反射係數,可以反應物體的顏色。同樣的\(I_p=(I_r,I_g,I_b)\)可以通過分量來設置光源的顏色。

鏡面反射

對於理想鏡面,反射光集中在一個方向,並遵守反射定律。對於一般的光滑表面,反射光則集中在一個範圍內,且反射定律決定的方向光強最大。所以從不同位置觀察到的鏡面反射光強不同。鏡面反射光可表示為

\[I_s = I_pK_s(R\cdot V)^{n} \]

\(R \cdot V\)計算的是反射方向和視線方向的夾角,夾角越小,強度越大。\(n\)是反射指數,反應了物體的表面的光滑程度,一般1-2000。\(n\)越大約光滑,因為n越大,例如2000,那麼當夾角很小時,例如很接近1,如0.9,但是經過2000乘方,就變得很小了,這意味着只有無限接近反射方向,才能看到高光,其他方向不行,這就表示物體很光滑。反過來,\(n\)很小那麼移動一點角度,也能看到衰弱的高光,所以光斑會比較明顯。

在鏡面反射模型中,最終要的是計算R的方向,\(R\)可以通過入射方向和法線方向計算出來

因為這裏的向量都是單位向量,只有方向不一致

\[\begin{aligned} ||L||\cos\theta &= ||M||=\cos\theta\\ &M和N的方向一致\\ R &= -L+2M \\ &=2N\cos\theta-L\\ &=2N\cdot(N\cdot L)-L \end{aligned} \]

高光區域只反映光源的顏色,漫反射才能設定物體的顏色。

環境光

光源間接對物體施加的明暗影響,在物體和環境之間多次反射。在簡單光照模型中進行了簡化,通常用一個常數來模擬環境光

\[I = I_aK_a \]

\(I_a\)是環境光強,\(K_a\)為物體對環境光的反射係數。

Phong模型

\[I = I_aK_a +I_pK_d\cdot(L\cdot N)+I_pK_s(R\cdot V)^{n} \]

Phong模型是上述三種因素的疊加,其中\(R\)的計算比較費時,需要對每一點計算一次\(R\)的值。

Blinn-Phong模型

由於Phong模型計算較為耗時,後來提出了一種對Phong模型的修改,Blinn-Phong模型。
假設:

  1. 光源在無窮遠處,光線的方向L為常數(這就意味着,對物體上所有點來說,光線的方向都是一致的,正常情況應該是光源到點的向量,每個點的光照方向都不一致)
  2. 視點在無窮遠處,視線的防線V為常數(這個同理)
  3. 此模型針對高光部分進行了修改,\(R\cdot V\)的計算用\(H\cdot N\)近似,其中\(H=(L+V)/||L+V||\),也就是\(L\)\(V\)的平分向量。當\(V\)接近\(R\)的時候,\(H\)也接近\(N\),符號高光的規律。對於所有點,\(H\)只需計算一次。

所以Blinn-Phong模型的可以表示成:

\[I = I_aK_a +I_pK_d\cdot(L\cdot N)+I_pK_s(H\cdot N)^{n} \]

(圖片中應該採用了明暗處理,不僅是光照模型)

明暗處理

如今的物體大多數用多邊形表示,一個多邊形的法線方向一致,因此一個多邊形內部的像素相同,而在鄰接出可能會有突變,感覺不連續。為了讓過度平滑,基本思想是:對多邊形的頂點計算合適的光強度,在內部進行均勻插值。其中有兩種主要的做法:

  • 計算物體表面多邊形頂點的光強,然後插值,求多邊形內部光強。
  • 對內部點的法向量進行插值,而頂點的法向量用相鄰多邊形的法向量的平均值得到。

Gouraud明暗處理(雙線性光強插值)

基本算法

  1. 計算多邊形頂點的平均法向量
  2. 用Phong模型計算頂點的平均強度
  3. 插值計算離散邊上的各點光強
  4. 插值計算多邊形區域內的各點光強

計算速度比簡單光照模型有了很大的提高,解決了顏色突變問題,但是鏡面反射效果不理想。

Phong明暗處理(雙線性法向量插值)

和Gouraud方法基本類似,只不過是對法向量插值。多邊形頂點的法向量用相鄰多邊形的法向量的平均值。而內部每個點都要計算法向量,用頂點的法向量插值得到。
這種做法效果好,可以產生正確的高光,但是計算量很大。

  • [1]維基百科
  • [2]計算機圖形學基礎教程 胡事民

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

【其他文章推薦】

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

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

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

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

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

大話性能測試系列(1)- 性能測試的基本概念

如果你對性能測試感興趣,但是又不熟悉理論知識,可以看下面的系列文章

https://www.cnblogs.com/poloyy/category/1620792.html

 

學習前的認知

我們在學習性能測試之前,需要有個新的認識:性能測試,不再是像功能測試一樣單純的找 Bug,而是去找性能指標

 

轉變思維

  • 在做功能測試、自動化測試的時候,我們基本都是依託界面進行測試,也稱 GUI 測試,我們的目的就是為了跑通功能、程序,並成功找到 Bug
  • 但在做性能測試的時候,我們大部分是 headless 模式(所謂的:無頭,無界面模式),目的不再是單純的為了找到 Bug,而是要分析性能指標等等(後續講到)

 

性能測試的時間一般會比自動化、功能測試長,為啥?

  • 因為性能測試的步驟跟自動化、功能測試的步驟不一樣,比如說前期的準備(了解系統,環境搭建),後期的壓力測試(7*24h)等等
  • 在後面,我們通過講述性能測試步驟來仔細了解

 

性能測試一定要工具,手工不行嗎?

  • 性能測試是模擬系統在被很多很多用戶同時使用時,系統能不能正常使用和提供服務
  • 重點:很多很多用戶
  • 功能測試:一個人點點點就知道功能通不通,有沒有 Bug 了
  • 性能測試:用手工的話,可以模擬幾個、十幾個用戶,但是當需要模擬上千萬個用戶時,手工又怎麼模擬數據量多的場景呢?
  • 類比,吃飯場景:一個人可以吃好幾碗,但是叫你吃幾百碗是不可能的
  • 結論:工具就可以模擬大數據量的場景,可以做到人做不到的事情

 

大數據量測試是性能測試嗎?

大數據量測試

簡單理解:一個接口返回的數據比較多(假設:不使用分頁,把所有數據同時返回)

 

結論

  • 返回大數據量的接口的響應時間會變長
  • 這麼大的數據量,我們需要考慮:網絡傳輸數據、服務器查詢這些數據、服務器處理這些數據等等分別需要多少時間
  • 這已經跟響應時間掛鈎,所以已經屬於性能測試的範圍,但不歸納於性能分析範圍

 

大數據測試是性能測試嗎?

大數據測試的功能屬於功能測試哦

 

性能測試過程發現問題需要立即提交嗎?

在性能測試過程中發現一些問題,假設定位到某一段代碼有問題,可以截圖提交 Bug 給開發,但這並不是我們性能測試的最終目的,最終目的是找出性能指標

 

有哪些性能指標?

  • 比如說響應時間:10個人、100個人 、1000個人 、10000個人向服務器發起請求,服務器響應請求的平均響應時間是多少,這就是一個指標
  • 又好比TPS:服務器在當前的配置下,不同用戶數發起請求,服務器的 TPS 處理能力是多少,這也是一個指標
  • 後續詳細介紹

 

性能測試中發現的 Bug 

  • 性能測試過程中發現的 Bug 屬於一個衍生品,並不是最終得到的結果
  • 但像功能測試,最終目的就是為了找出 Bug

 

關於這個問題的總結

  • 做性能測試,當數據量變大后,會出現連接超時、連接拒絕、500、502異常問題;在性能測試中,這些異常問題基本都會出現的,但不會去立即提 Bug
  • 對於性能測試工程師,我們要做的是分析為什麼在當前數據量下會出現連接超時、連接拒絕,響應時間超時、服務器異常等異常問題
  • 這就需要我們去分析性能瓶頸,並不會單獨去某個異常問題出現在哪裡,而是分析為什麼會出現這個異常問題,分析的是服務器或者是代碼,而不是讓開發人員馬上來修復這些異常問題

 

我們常說的壓測是指壓力測試嗎?

  • 並不是,而是指負載測試,一般都是為了找出系統的最大負載量
  • 就好像你老闆說:你去壓測下,看看系統能支撐多少用戶同時訪問我們的系統

 

什麼是性能測試?

狹義理解

  • 通過工具,找出或獲得系統在不同工況下的性能指標值
  • 性能測試過程中,重點是找出性能指標,而不再是找出 Bug,
  • 性能測試的產出絕對不只是 Bug

 

場景類比

跑步100米,用時多少?運動員的心跳、步伐頻率是多少?

  1. 跑步100米:業務場景
  2. 用時多少:響應時間
  3. 運動員的心跳、步伐:性能指標值

性能指標值和響應時間是否滿足當前業務場景的最低要求(合格線)

 

什麼時候能找出性能指標值

假設當前有一個業務

電商系統,下單業務,目前還不知道系統支持多少人同時下單,那麼我們需要找到服務器能正常支持多少人同時下單

 

性能測試初始階段(第一次做)

  • 先把基礎的性能指標值找出來(第一次性能測試也叫做基準測試)
  • 比如:100個人同時下單系統正常,但120個人同時下單就會出現部分請求的響應時間超長,連接異常
  • 那麼100-120範圍內的某個值就是當前服務器能達到的性能指標值(基準值)

 

版本迭代,進行第二次做性能測試,重新跑一遍之前的性能腳本

  • 又會得到一些性能指標值,對比上個版本的性能指標值,看是否有優化(性能變化)
  • 假設這個時候120個人同時下單是正常的,150個人才有異常,那麼接口已經有優化了

 

假設公司是從0開始做性能測試

  • 第一階段:做好性能測試,得到性能指標值
  • 第二階段:假設性能比之前差,哪些性能指標值不滿足預期值,就需要分析是哪裡有問題

 

廣義理解

  • 只要與服務器性能指標相關的測試都屬於性能測試
  • 比如:響應時間、併發用戶數、服務器處理能力、吞吐量等性能指標
  • 負載測試、壓力測試、容量測試、可靠性測試都屬於性能測試
  • 通常嘴巴上說的做性能測試就是廣義的性能測試,它包括了很多內容,並不只是針對某一個測試類型

 

“官方”解釋

以下含義來源高老的解釋,比較“官方”的術語

  1. 性能測試針對系統的性能指標,建立性能測試模型
  2. 制定性能測試方案
  3. 制定監控策略
  4. 在場景條件下執行性能場景
  5. 分析判斷性能瓶頸並調優
  6. 最終得出性能結果來評估系統的性能指標是否滿足既定值

其實也算是一個簡潔描述的性測試流程了

 

注意

  • 性能測試不像自動化測試那樣很多東西大家都是公認的,性能測試沒有一套標準的知識體系,只能說是相似的
  • 基本每個人都有自己的一套知識體系,就好像高老也會說他給性能測試的定義很大可能會被轟炸一樣
  • 只要屬於自己的知識體系建立起來了,那麼就能助力你正確的完成性能測試
  • 不用太過糾結於哪個人對性能測試概念的解釋是最準確的

目前博主是正在學習性能測試的小白一枚,希望通過通俗簡單的術語來學懂性能測試,打造屬於自己的知識體系,歡迎大家進群與我溝通(870155189)

 

 什麼是負載測試?

概念

  • 逐步增加系統負載,測試系統性能變化,並最終確定系統所能承受的最大負載量
  • 通俗理解:看看你幾斤幾兩

 

如何增加負載

通過增加“用戶數”,就是常說的併發數

 

場景類比

天平秤,稱東西的時候,需要逐步加砝碼,最終達到砝碼和物品重量的平衡點,因為它不可能一下子就達到平衡點【好比不可能一下子找到系統能承受的最大負載量】

  • 稱東西:業務場景
  • 加砝碼:逐步加壓
  • 達到平衡點:找到最大負載量

 

實際場景

  • 有一個業務,增加到40個人的時候,服務器還能正常使用,沒有異常
  • 當你增加到50個人的時候,服務器已經開始有異常了,那麼就能確定40-50之間某個值就是系統所能承受的最大負載量【出現性能拐點,找到了服務器性能瓶頸的範圍值】
  • 最後減小加壓梯度(比如:從40個人開始每次增加1個人、2個人),確認最大負載量【確認性能拐點】

 

服務器又有哪些可能會出現的異常呢

  • 響應時間超長:正常服務器處理請求時間是 1s,但現在變成3s – 5s
  • 服務報錯:無法同時正常響應多個請求
  • 服務器宕機:系統完全用不了

 

什麼是壓力測試?

概念

  • 在較大的性能壓力下,持續運行一個比較長的時間,看看系統服務是否正常及系統資源的利用率情況
  • 通俗理解:鴨梨山大!
  • 關鍵字:較大壓力 + 較長時間
  • 注意:不是滿負荷壓力哦

 

場景類比

問:大家什麼時候會覺得工作壓力大?

答:996、007;因為你不會覺得955壓力山大吧

結論:所以在我們日常工作中,長時間工作強度高,才會覺得壓力大;如果你一周就加班一天也說壓力大…(那就別干這一行了)

 

壓力測試用來幹嘛的

測試系統的穩定性

 

類比

工作壓力大,你還能堅持下去(那穩定性肯定好吧),那如果你很快就離職了(那穩定性肯定差,都宕機罷工了)

 

什麼時候會做壓力測試

  • 生產環境下,系統隔三差五的出現不穩定的情況
  • 這個時候,就需要通過壓力測試去測試系統的穩定性情況

 

啥情況算不穩定?穩定性差?

隔三差五的出現下面的情況

  • 服務異常:響應錯誤、響應時間超時等
  • 服務器出現異常:宕機

 

怎麼分析是服務異常還是服務器異常 

  • 如果所有請求都是一片紅,應用程序發送的所有請求都報紅,就是服務器出現了異常
  • 如果有些請求偶爾成功響應,偶爾又失敗,則是服務異常,出現不穩定的情況

 

如何取壓力值

  • 在負載測試中,我們確認了系統所能承受的最大負載量
  • 壓力值 < 最大負載量,一般取80%左右

 

靈魂拷問

負載測試一般時間比較短,壓力測試時間比較長,持續運行時間短就能正常使用,但持續運行時間長就可能崩掉了,這是什麼原因呢?

 

場景類比

  • 栗子一:電腦保持開機狀態很長時間,會逐漸變卡,因為內存的東西會越來越多,得不到有效的回收, 就會越來越卡
  • 栗子二:當你經常工作壓力很大,且你的心理所能承受的壓力逐漸達到最大值時,你就可能會選擇離職

 

總結

壓力測試長時間運行,可能會逐漸增加系統的內存佔用空間,若得不到有效的內存回收,當達到內存最大值時,系統就會崩掉

 

壓力測試持續運行時間要多久?

  • 標準性能測試裏面,一般是7*24小時,或者是它的倍數
  • 但是實際工作中,並不會這麼久,否則成本太高
  • 所以會以小時為單位,比如:4個小時、8個小時…晚上下班之後做,第二天早上上班看結果

 

先負載測試還是壓力測試?

  • 先負載測試
  • 負載測試可以找到服務器性能瓶頸的範圍值,若生產環境中系統穩定性較差,再做壓力測試
  • 所以壓力測試是可做可不做的

 

什麼是可靠性測試?

概念

  • 在給定的一定的業務壓力下,持續運行一段時間,查看系統是否穩定
  • 關鍵字:是否穩定,一定業務壓力
  • 注意:不是較大壓力哦

 

業務場景栗子

電商秒殺場景,幾十個商品幾十萬個人同時秒殺搶購

 

如何理解可靠性測試

  1. 編寫性能腳本:假設一秒內有一萬個人同時發起請求
  2. 有壓力嗎?,一萬個人同時發起請求
  3. 但是持續時間,不像壓力測試一樣需要持續一段時間
  4. 目的是為了驗證當這麼多人同時發起請求時,成功秒殺的用戶能否繼續完成後續下單付款等操作【一定業務壓力下,系統是否穩定運行】

 

什麼是容量測試?

概念

  • 在一定的軟、硬件條件下,在數據庫不同數據量級數據量的情況下,對系統中讀/寫比較多的業務進行測試,從而獲得不同數據量級下的性能指標值
  • 關鍵字:不同數據量級

 

數據庫數據量對性能測試結果有沒有影響?

肯定有

  • 比如數據庫已經有幾百條數據和幾百萬條數據,查詢的速度肯定不一樣,所以肯定會影響性能測試結果
  • 數據量級的差異,會影響TPS、響應時間、網絡等

 

場景類比

從一袋米中找一個綠豆,和一碗米中找一個綠豆,找的時間肯定是千差萬別的

 

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

加拿大最後完整冰棚崩塌 面積縮 43% 、分裂出冰山面積比曼哈頓大

摘錄自2020年8月11日立場新聞報導

今年7月的最後兩日,加拿大北極群島中最北的埃爾斯米爾島(Ellesmere Island)米爾恩冰棚(Milne Ice Shelf)崩塌,使加拿大最後一個完整的冰棚面積減少了 43%,該冰塊其後漂入北冰洋,進一步分成兩大塊,並被歐洲太空總署哥白尼計劃哨兵衛星所拍攝到。

塌下的冰棚約有 80 平方公里,比 60 平方公里的曼哈頓更大。根據加拿大極冰局,高於正常的空氣溫度、離岸風和冰棚前的開揚水域,都是造成冰棚破裂的原因。由於冰棚破裂,北極最後一個已知的棚外湖 (epishelf lake) 可能消失。

冰棚可像活塞一樣,減緩與阻擋冰蓋與融冰水流入海洋的速度,有助於限制全球海水水位上升。另外,冰棚崩塌,造成的冰山可能會危害當地的航運業。

棚外湖是困於浮在海水上冰棚的淡水水體。當米爾恩冰棚崩塌時,可能將棚外湖以及其所含的淡水送進北冰洋,影響其鹹度,不過專家不確定其影響程度,情況將取決於米爾恩冰棚剩餘部分的完整性。

氣候變遷
國際新聞
加拿大
北極
冰棚
冰山融化

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

【其他文章推薦】

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

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

※台北網頁設計公司全省服務真心推薦

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

※推薦評價好的iphone維修中心

印度空氣污染嚴重致或致八成蜜蜂死亡 糧食安全將受重大威脅

摘錄自2020年8月11日立場新聞報導

蜜蜂可能比人類更強烈地感受到空氣污染帶來的影響。最新刊於《美國國家科學院期刊》(PNAS) 的報告指,即使是輕微的空氣污染也會殺死 80% 的亞洲大蜜蜂(Apis Dorsata) 。學者表示這是首次提供全面證據,證明昆蟲對空氣中的懸浮粒子特別敏感。

印度多個城市都高居全球空氣污染最嚴重名單,不僅是 13.5 億人的家鄉,亦是亞洲大蜜蜂的棲息地。這種蜜蜂是南亞主要的花粉傳播媒介。芬蘭奧盧大學行為生態學家 Olli Loukola 指出,據估計如沒有昆蟲授粉, 53% 芒果會消失,單計印度的出口就因此損失約 8,600 萬美元。

研究發現,在污染更嚴重地點採集的蜜蜂更有可能出現含砷和鉛等有毒重金屬的顆粒。從污染最嚴重地區捕獲的 80% 蜜蜂在採樣後 1 天內死亡,是校園區蜜蜂死亡量的兩倍。團隊也發現,被有毒灰塵覆蓋的蜜蜂接觸花朵採蜜的次數僅是校園區蜜蜂的一半,因此可能減少有花植物成功授粉的機會。

生物多樣性
污染治理
國際新聞
印度
懸浮粒子
糧食安全
蜜蜂
授粉生物
空氣污染

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

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案