亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java擴(kuò)展Nginx之共享內(nèi)存

 更新時間:2023年07月17日 08:56:04   作者:程序員欣宸  
這篇文章主要介紹了Java擴(kuò)展Nginx之共享內(nèi)存的相關(guān)資料,需要的朋友可以參考下

本篇概覽

作為《Java擴(kuò)展Nginx》系列的第七篇,咱們來了解一個實用工具共享內(nèi)存,正式開始之前先來看一個問題 在一臺電腦上,nginx開啟了多個worker,如下圖,如果此時我們用了nginx-clojure,就相當(dāng)于有了四個jvm進(jìn)程,彼此相互獨(dú)立,對于同一個url的多次請求,可能被那四個jvm中的任何一個處理:

現(xiàn)在有個需求:統(tǒng)計某個url被訪問的總次數(shù),該怎么做呢?在java內(nèi)存中用全局變量肯定不行,因為有四個jvm進(jìn)程都在響應(yīng)請求,你存到哪個上面都不行 聰明的您應(yīng)該想到了redis,確實,用redis可以解決此類問題,但如果不涉及多個服務(wù)器,而只是單機(jī)的nginx,還可以考慮nginx-clojure提供的另一個簡單方案:共享內(nèi)存,如下圖,一臺電腦上,不同進(jìn)程操作同一塊內(nèi)存區(qū)域,訪問總數(shù)放入這個內(nèi)存區(qū)域即可:

相比redis,共享內(nèi)存的好處也是顯而易見的: redis是額外部署的服務(wù),共享內(nèi)存不需要額外部署服務(wù) redis請求走網(wǎng)絡(luò),共享內(nèi)存不用走網(wǎng)絡(luò)

所以,單機(jī)版nginx如果遇到多個worker的數(shù)據(jù)同步問題,可以考慮共享內(nèi)存方案,這也是咱們今天實戰(zhàn)的主要內(nèi)容:在使用nginx-clojure進(jìn)行java開發(fā)時,用共享內(nèi)存在多個worker之間同步數(shù)據(jù)

本文由以下內(nèi)容組成:

先在java內(nèi)存中保存計數(shù),放在多worker環(huán)境中運(yùn)行,驗證計數(shù)不準(zhǔn)的問題確實存在 用nginx-clojure提供的Shared Map解決問題

用堆內(nèi)存保存計數(shù)

寫一個content handler,代碼如下,用UUID來表明worker身份,用requestCount記錄請求總數(shù),每處理一次請求就加一:

package com.bolingcavalry.sharedmap;

import nginx.clojure.java.ArrayMap;
import nginx.clojure.java.NginxJavaRingHandler;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import static nginx.clojure.MiniConstants.CONTENT_TYPE;
import static nginx.clojure.MiniConstants.NGX_HTTP_OK;

public class HeapSaveCounter implements NginxJavaRingHandler {

    /**
     * 通過UUID來表明當(dāng)前jvm進(jìn)程的身份
     */
    private String tag = UUID.randomUUID().toString();

    private int requestCount = 1;

    @Override
    public Object[] invoke(Map<String, Object> map) throws IOException {

        String body = "From "
                    + tag
                    + ", total request count [ "
                    + requestCount++
                    + "]";

        return new Object[] {
                NGX_HTTP_OK, //http status 200
                ArrayMap.create(CONTENT_TYPE, "text/plain"), //headers map
                body
        };
    }
}

修改nginx.conf的worker_processes配置,改為auto,則根據(jù)電腦CPU核數(shù)自動設(shè)置worker數(shù)量:

worker_processes  auto;

nginx增加一個location配置,服務(wù)類是剛才寫的HeapSaveCounter:

location /heapbasedcounter {
	content_handler_type 'java';
    content_handler_name 'com.bolingcavalry.sharedmap.HeapSaveCounter';
}

編譯構(gòu)建部署,再啟動nginx,先看jvm進(jìn)程有幾個,如下可見,除了jps自身之外有8個jvm進(jìn)程,等于電腦的CPU核數(shù),和設(shè)置的worker_processes是符合的:

(base) willdeMBP:~ will$ jps
4944
4945
4946
4947
4948
4949
4950
4968 Jps
4943

先用Safari瀏覽器訪問/heapbasedcounter,第一次收到的響應(yīng)如下圖,總數(shù)是1:

刷新頁面,UUID不變,總數(shù)變成2,這意味著兩次請求到了同一個worker的JVM上:

改用Chrome瀏覽器,訪問同樣的地址,如下圖,這次UUID變了,證明請求是另一個worker的jvm處理的,總數(shù)變成了1:

至此,問題得到證明:多個worker的時候,用jvm的類的成員變量保存的計數(shù)只是各worker的情況,不是整個nginx的總數(shù)

接下來看如何用共享內(nèi)存解決此類問題

關(guān)于共享內(nèi)存

nginx-clojure提供的共享內(nèi)存有兩種:Tiny Map和Hash Map,它們都是key&value類型的存儲,鍵和值均可以是這四種類型:int,long,String, byte array Tiny Map和Hash Map的區(qū)別,用下表來對比展示,可見主要是量化的限制以及使用內(nèi)存的多少:

特性Tiny MapHash Map
鍵數(shù)量2^31=2.14Billions64位系統(tǒng):2^63
32位系統(tǒng):2^31
使用內(nèi)存上限64位系統(tǒng):4G
32位系統(tǒng):2G
受限于操作系統(tǒng)
單個鍵的大小16M受限于操作系統(tǒng)
單個值的大小64位系統(tǒng):4G
32位系統(tǒng):2G
受限于操作系統(tǒng)
entry對象自身所用內(nèi)存24 byte64位系統(tǒng):40 byte
32位系統(tǒng):28 byte

您可以基于上述區(qū)別來選自使用Tiny Map和Hash Map,就本文的實戰(zhàn)而言,使用Tiny Map就夠用了 接下來進(jìn)入實戰(zhàn)

使用共享內(nèi)存

使用共享內(nèi)存一共分為兩步,如下圖,先配置再使用:

現(xiàn)在nginx.conf中增加一個http配置項shared_map,指定了共享內(nèi)存的名稱是uri_access_counters:

# 增加一個共享內(nèi)存的初始化分配,類型tiny,空間1M,鍵數(shù)量8K
shared_map uri_access_counters  tinymap?space=1m&entries=8096;

然后寫一個新的content handler,該handler在收到請求時,會在共享內(nèi)存中更新請求次數(shù),總的代碼如下,有幾處要重點(diǎn)注意的地方,稍后會提到:

package com.bolingcavalry.sharedmap;

import nginx.clojure.java.ArrayMap;
import nginx.clojure.java.NginxJavaRingHandler;
import nginx.clojure.util.NginxSharedHashMap;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import static nginx.clojure.MiniConstants.CONTENT_TYPE;
import static nginx.clojure.MiniConstants.NGX_HTTP_OK;

public class SharedMapSaveCounter implements NginxJavaRingHandler {

    /**
     * 通過UUID來表明當(dāng)前jvm進(jìn)程的身份
     */
    private String tag = UUID.randomUUID().toString();

    private NginxSharedHashMap smap = NginxSharedHashMap.build("uri_access_counters");

    @Override
    public Object[] invoke(Map<String, Object> map) throws IOException {
        String uri = (String)map.get("uri");

        // 嘗試在共享內(nèi)存中新建key,并將其值初始化為1,
        // 如果初始化成功,返回值就是0,
        // 如果返回值不是0,表示共享內(nèi)存中該key已經(jīng)存在
        int rlt = smap.putIntIfAbsent(uri, 1);

        // 如果rlt不等于0,表示這個key在調(diào)用putIntIfAbsent之前已經(jīng)在共享內(nèi)存中存在了,
        // 此時要做的就是加一,
        // 如果relt等于0,就把rlt改成1,表示訪問總數(shù)已經(jīng)等于1了
        if (0==rlt) {
            rlt++;
        } else {
            // 原子性加一,這樣并發(fā)的時候也會順序執(zhí)行
            rlt = smap.atomicAddInt(uri, 1);
            rlt++;
        }

        // 返回的body內(nèi)容,要體現(xiàn)出JVM的身份,以及share map中的計數(shù)
        String body = "From "
                + tag
                + ", total request count [ "
                + rlt
                + "]";

        return new Object[] {
                NGX_HTTP_OK, //http status 200
                ArrayMap.create(CONTENT_TYPE, "text/plain"), //headers map
                body
        };
    }
}

上述代碼已經(jīng)添加了詳細(xì)注釋,相信您一眼就看懂了,我這里挑幾個重點(diǎn)說明一下:

寫上述代碼時要牢一件事:這段代碼可能運(yùn)行在高并發(fā)場景,既同一時刻,不同進(jìn)程不同線程都在執(zhí)行這段代碼

NginxSharedHashMap類是ConcurrentMap的子類,所以是線程安全的,我們更多考慮應(yīng)該注意跨進(jìn)程讀寫時的同步問題,例如接下來要提到的第三和第四點(diǎn),都是多個進(jìn)程同時執(zhí)行此段代碼時要考慮的同步問題

putIntIfAbsent和redis的setnx類似,可以當(dāng)做跨進(jìn)程的分布式鎖來使用,只有指定的key不存在的時候才會設(shè)置成功,此時返回0,如果返回值不等于0,表示共享內(nèi)存中已經(jīng)存在此key了

atomicAddInt確保了原子性,多進(jìn)程并發(fā)的時候,用此方法累加可以確保計算準(zhǔn)確(如果我們自己寫代碼,先讀取,再累加,再寫入,就會遇到并發(fā)的覆蓋問題)

關(guān)于那個atomicAddInt方法,咱們回憶一下java的AtomicInteger類,其incrementAndGet方法在多線程同時調(diào)用的場景,也能計算準(zhǔn)確,那是因為里面用了CAS來確保的,那么nginx-clojure這里呢?我很好奇的去探尋了一下該方法的實現(xiàn),這是一段C代碼,最后沒看到CAS有關(guān)的循環(huán),只看到一段最簡單的累加,如下圖:

很明顯,上圖的代碼,在多進(jìn)程同時執(zhí)行時,是會出現(xiàn)數(shù)據(jù)覆蓋的問題的,如此只有兩種可能性了,第一種:即便是多個worker存在,執(zhí)行底層共享內(nèi)存操作的進(jìn)程也只有一個

第二種:欣宸的C語言水平不行,根本沒看懂JVM調(diào)用C的邏輯,自我感覺這種可能性很大:如果C語言水平可以,欣宸就用C去做nginx擴(kuò)展了,沒必要來研究nginx-clojure呀?。ㄈ绻炊舜硕未a的調(diào)用邏輯,還望您指點(diǎn)欣宸一二,謝謝啦)

編碼完成,在nginx.conf上配置一個location,用SharedMapSaveCounter作為content handler:

location /sharedmapbasedcounter {
    content_handler_type 'java';
 	content_handler_name 'com.bolingcavalry.sharedmap.SharedMapSaveCounter';
}

編譯構(gòu)建部署,重啟nginx

先用Safari瀏覽器訪問/sharedmapbasedcounter,第一次收到的響應(yīng)如下圖,總數(shù)是1:

刷新頁面,UUID發(fā)生變化,證明這次請求到了另一個worker,總數(shù)也變成2,這意味著共享內(nèi)存生效了,不同進(jìn)程使用同一個變量來計算數(shù)據(jù):

改用Chrome瀏覽器,訪問同樣的地址,如下圖,UUID再次變化,證明請求是第三個worker的jvm處理的,但是訪問次數(shù)始終正確:

實戰(zhàn)完成,前面的代碼中只用了兩個API操作共享內(nèi)存,學(xué)到的知識點(diǎn)有限,接下來做一些適當(dāng)?shù)难由鞂W(xué)習(xí)

一點(diǎn)延伸

剛才曾提到NginxSharedHashMap是ConcurrentMap的子類,那些常用的put和get方法,在ConcurrentMap中是在操作當(dāng)前進(jìn)程的堆內(nèi)存,如果NginxSharedHashMap直接使用父類的這些方法,豈不是與共享內(nèi)存無關(guān)了?

帶著這個疑問,去看NginxSharedHashMap的源碼,如下圖,真相大白:get、put這些常用方法,都被重寫了,紅框中的nget和nputNumber都是native方法,都是在操作共享內(nèi)存:

至此,nginx-clojure的共享內(nèi)存學(xué)習(xí)完成,高并發(fā)場景下跨進(jìn)程同步數(shù)據(jù)又多了個輕量級方案,至于用它還是用redis,相信聰明的您心中已有定論 源碼下載 《Java擴(kuò)展Nginx》的完整源碼可在GitHub下載到,地址和鏈接信息如下表所示(https://github.com/zq2599/blog_demos):

名稱鏈接備注
項目主頁https://github.com/zq2599/blog_demos該項目在GitHub上的主頁
git倉庫地址(https)https://github.com/zq2599/blog_demos.git該項目源碼的倉庫地址,https協(xié)議
git倉庫地址(ssh)git@github.com:zq2599/blog_demos.git該項目源碼的倉庫地址,ssh協(xié)議

這個git項目中有多個文件夾,本篇的源碼在nginx-clojure-tutorials文件夾下的shared-map-demo子工程中,如下圖紅框所示:

本篇涉及到nginx.conf的修改,完整的參考在此:https://raw.githubusercontent.com/zq2599/blog_demos/master/nginx-clojure-tutorials/files/nginx.conf

到此這篇關(guān)于Java擴(kuò)展Nginx之共享內(nèi)存的文章就介紹到這了,更多相關(guān)Java擴(kuò)展Nginx 共享內(nèi)存內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java?Collection接口中的常用方法總結(jié)

    Java?Collection接口中的常用方法總結(jié)

    這篇文章將大概用代碼案例簡單總結(jié)一下?Collection?接口中的一些方法,我們會以他的實現(xiàn)類?Arraylist?為例創(chuàng)建對象??煲黄饋砜纯窗?/div> 2022-12-12
  • Java數(shù)據(jù)結(jié)構(gòu)之常見排序算法(下)

    Java數(shù)據(jù)結(jié)構(gòu)之常見排序算法(下)

    這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之常見排序算法(下),與之相對有(上),想了解的朋友可以去本網(wǎng)站掃搜,在這兩篇文章里涵蓋關(guān)于八大排序算法的所有內(nèi)容,需要的朋友可以參考下
    2023-01-01
  • Spring整合Junit的使用詳解

    Spring整合Junit的使用詳解

    這篇文章主要介紹了Spring整合Junit的使用詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02
  • Java文件IO操作教程之DirectIO的意義

    Java文件IO操作教程之DirectIO的意義

    這篇文章主要給大家介紹了關(guān)于Java文件IO操作教程之DirectIO的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • Java實現(xiàn)打字游戲

    Java實現(xiàn)打字游戲

    這篇文章主要為大家詳細(xì)介紹了Java實現(xiàn)打字游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-08-08
  • Java實現(xiàn)級聯(lián)下拉結(jié)構(gòu)的示例代碼

    Java實現(xiàn)級聯(lián)下拉結(jié)構(gòu)的示例代碼

    在開發(fā)過程中,會遇到很多的實體需要將查出的數(shù)據(jù)處理為下拉或者級聯(lián)下拉的結(jié)構(gòu),提供給前端進(jìn)行展示。本文為大家介紹了java封裝下拉和級聯(lián)下拉的通用工具類,需要的可以參考一下
    2022-06-06
  • Restful API中的錯誤處理方法

    Restful API中的錯誤處理方法

    這篇文章主要給大家介紹了關(guān)于Restful API中錯誤處理方法的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • 解決PageHelper的上下文問題導(dǎo)致SQL查詢結(jié)果不正確

    解決PageHelper的上下文問題導(dǎo)致SQL查詢結(jié)果不正確

    主要介紹了PageHelper在使用過程中出現(xiàn)的分頁上下文問題,并分析了可能的原因和解決方案,主要解決方案包括每次分頁查詢后調(diào)用`PageHelper.clearPage()`清理分頁上下文,確保每次查詢前正確調(diào)用`startPage`,以及避免在條件判斷未執(zhí)行SQL時影響后續(xù)查詢
    2024-12-12
  • 利用5分鐘快速搭建一個springboot項目的全過程

    利用5分鐘快速搭建一個springboot項目的全過程

    Spring Boot的監(jiān)控能夠使開發(fā)者更好地掌控應(yīng)用程序的運(yùn)行狀態(tài),下面這篇文章主要給大家介紹了關(guān)于如何利用5分鐘快速搭建一個springboot項目的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-05-05
  • Java 反射機(jī)制的實例詳解

    Java 反射機(jī)制的實例詳解

    這篇文章主要介紹了Java 反射機(jī)制的實例詳解的相關(guān)資料,希望通過本文能幫助到大家,讓大家理解掌握反射機(jī)制,需要的朋友可以參考下
    2017-10-10

最新評論