調查顯示電動車市占率有望在2020年超過10%

據悉,2013年中國節能與新能源汽車產業發展高峰論壇於上周在北京舉行,中國汽車工程學會副秘書長張寧在壇於上表示,針對486位汽車工程師進行的《電動汽車技術進步和產業化前景調查問卷》顯示,電動汽車有望在2020年-2025年之間實現商業化。2020年,電動車在世界乘用車市場中的佔有率將達到10%-15%。

根據調查數據,目前影響電動汽車發展的主要瓶頸還是電池技術、成本和續駛裡程。數據還顯示,到2020年,燃油汽車仍擁有15%-25%的發展潛力。因此,從中國的現階段來講,應當不要排斥某種技術路線,還是應當用多種方式推動節能減排,包括電動汽車及燃料汽車。

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

【其他文章推薦】

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

※超省錢租車方案

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

萬向電動汽車獲得中國工信部資質認證

中國工信部今(22)日發布《車輛生產企業及產品公告》(第254批),公示了三家新增車輛生產企業的名單,公示時間為22日-28日。其中萬向電動汽車有限公司名列其中。

此前,萬向在電動汽車相關領域投入重資,雖已經初步形成車載電子系統、動力電池和電動汽車領等較完整的產業鏈,但在國內市場上,作為從事電動汽車相關業務的公司,仍缺少工信部認證的資質。

2013年1月,萬向成功收購美國A123,完善了電池產業鏈條,借全球知名企業品牌優勢,擴大了其在美國市場的銷售份額。

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

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

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

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

※超省錢租車方案

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

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

中國國務院批復新一輪新能源汽車推廣方案

中國科技部部長萬鋼上周在2013中國汽車產業發展(泰達)國際論壇上透露,國務院已正式批復新一輪的新能源汽車示范推廣方案,四部委正在制定實施細則,並將盡快正式啟動。「直接補貼到企業」將是新一輪補貼政策最大的變化。

新一輪補貼政策的內容主要有:以試點城市為核心,設立試點區域,擴大輻射范圍,加速區域電動汽車推廣;改善原有財政資金補貼方式,加快資金補貼落實力度;混合動力客車將向全國推廣;對充電站的建設進行財政支持。

另外,發改委、工信部、商務部等多部委對特斯拉總部進行了訪問考察。此次訪問或對特斯拉順利進入中國市場有促進作用。

中國國內電動汽車也在快速發展。以杭州為例,在國內率先啟用了可出租的純電動微公交,每小時20元(人民幣),一次充滿電可跑80-100公裏,每次充電大約耗時30分鐘。杭州市未來設想,每5-10公裏有1個立體車庫站點,建成綠色的微公交網點係統。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

Tesla執行長稱燃料電池不適合推廣 純電動車才有未來

據外媒本週二報導,美國電動跑車製造商特斯拉(Tesla)執行長Elon Musk本週在慕尼黑特斯拉展示中心表示,很多人都說純電動車根本沒有未來,但他認為,氫燃料電池基本上只是一種行銷的伎倆,氫是一種危險性頗高的氣體,比較適合用來推動火箭。

Elon Musk在演說中提到,旗下價格較低的大眾車種(可能會被命名為「Model E」)預料將在12-15個月內開發完成、2016年開賣,而休旅車「Model X」則會在明(2014)年問世。

此前,豐田汽車(Toyota) 董事長內山田武(Takeshi Uchiyamada)曾與9月30日在華盛頓特區經濟俱樂部(Economic Club of Washington, D.C.)發表演說後表示,Toyota之所以並未推出任何一款重量級純電動車,是因為該公司不認為這種產品會有市場。

目前全球燃料電池車的研發以日系車廠為軸心形成三大陣營,其中Toyota已表明計劃於2015年開賣燃料電池車。據日經新聞報導,預估2025年日本國內的燃料電池車普及數量將達200萬台。

另據日經新聞報導,日本政府也計劃於2015年度結束前,在國內整備100座氫燃料充填據點「氫氣站」,其他日本企業也紛紛著手進行相關技術的研發。為促進燃料電池車的普及,千代田化工建設(CHIYODA)計畫投下約300億日圓,於2015年在川崎市興建全球首座大規模氫燃料供應基地。

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

在運行時生成C# .NET類

​本文譯自​:​Generating C# .NET Classes at Runtime
作者:WedPort

在我的C#職業生涯中,有幾次我不得不在運行時生成新的類型。希望把它寫下來能幫助有相同應用需求的人。這也意味着我以後不必在查找相同問題的StackOverflow文章了。我最初是在.NET 4.6.2中這樣做的,但我已經更新到為.NET Core 3.0提供了示例。所有代碼都可以在我的GitHub上面找到。
GitHub:https://github.com/cheungt6/public/tree/master/ReflectionEmitClassGeneration

為什麼我需要在運行時生成類?

在運行時生產新類型的需求通常是由於運行時才知道類屬性,滿足性能要求以及需要在新類型中添加功能。當你嘗試這樣做的時候,你應該考慮的第一件事是:這是否真的是一個明智的解決方案。在深入思考之前,還有很多其他事情可以嘗試,問你自己這樣的問題:

  1. 我可以使用普通的類嗎
  2. 我可以使用Dictionary、Tuple或者對象數組(Array)?
  3. 我是否可以使用擴展對象
  4. 我確定我不能使用一個普通的類嗎?

如果你認為這仍然是必要的,請繼續閱讀下面的內容。

示例用例

作為一名開發人員,我將大量數據綁定到各種WPF Grids中。大多數時候屬性是固定的,我可以使用預定義的類。有時候,我不得不動態的構建網格,並且能夠在應用程序運行時更改數據。採取以下显示ID和一些財務數據的類(FTSE和CAC是指數,其屬性代表指數價格):

public class PriceHolderViewModel : ViewModelBase
{
    public long Id { get; set; }
    public decimal FTSE100 { get; set; }
    public decimal CAC40 { get; set; }
}

如果我們僅對其中的屬性感興趣,該類定義的非常棒。但是,如果要使用更多屬性擴展此類,則需要在代碼中添加它,重新編譯並在新版本中進行部署。

相反的,我們可以做的是跟蹤對象所需的屬性,並在運行時構建類。這將允許我們在需要是不斷的添加和刪除屬性,並使用反射來更新它們的值。

// Keep track of my properties
var _properties = new Dictionary<string, Type>(new[]{
   new KeyValuePair<string, Type>( "FTSE100", typeof(Decimal) ),
   new KeyValuePair<string, Type>( "CAC40", typeof(Decimal) ) });

創建你的類型

下面的示例向您展示了如何在運行時構建新類型。你需要使用**System.Reflection.Emit**庫來構造一個新的動態程序集,您的類將在其中創建,然後是模塊和類型。與舊的** .NET Framework**框架不同,在舊的版本中,你需要在當前程序的AppDomain中創建程序集 ,而在** .NET Core** 中,AppDomain不再可用。你將看到我使用GUID創建了一個新類型名稱,以便於跟蹤類型的版本。在以前,你不能創建具有相同名稱的兩個類型,但是現在似乎不是這樣了。

public Type GeneratedType { private set; get; }

private void Initialise()
{
    var newTypeName = Guid.NewGuid().ToString();
    var assemblyName = new AssemblyName(newTypeName);
    var dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
    var dynamicModule = dynamicAssembly.DefineDynamicModule("Main");
    var dynamicType = dynamicModule.DefineType(newTypeName,
            TypeAttributes.Public |
            TypeAttributes.Class |
            TypeAttributes.AutoClass |
            TypeAttributes.AnsiClass |
            TypeAttributes.BeforeFieldInit |
            TypeAttributes.AutoLayout,
            typeof(T));     // This is the type of class to derive from. Use null if there isn't one
    dynamicType.DefineDefaultConstructor(MethodAttributes.Public |
                                        MethodAttributes.SpecialName |
                                        MethodAttributes.RTSpecialName);
    foreach (var property in Properties)
        AddProperty(dynamicType, property.Key, property.Value);

    GeneratedType = dynamicType.CreateType();
}

在定義類型時,你可以提供一種類型,從中派生新的類型。如果你的基類具有要包含在新類型中的某些功能或屬性,這將非常有用。之前,我曾使用它在運行時擴展ViewModelSerializable類型。

在你創建了TypeBuilder后,你可以使用下面提供的代碼開始添加屬性。它創建了支持字段和所需的中間語言,以便通過GetterSetter訪問它們。為每個屬性完成此操作后,可以使用CreateType()創建類型的實例。

private static void AddProperty(TypeBuilder typeBuilder, string propertyName, Type propertyType)
{
    var fieldBuilder = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
    var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
    
    var getMethod = typeBuilder.DefineMethod("get_" + propertyName,
        MethodAttributes.Public |
        MethodAttributes.SpecialName |
        MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
    var getMethodIL = getMethod.GetILGenerator();
    getMethodIL.Emit(OpCodes.Ldarg_0);
    getMethodIL.Emit(OpCodes.Ldfld, fieldBuilder);
    getMethodIL.Emit(OpCodes.Ret);

    var setMethod = typeBuilder.DefineMethod("set_" + propertyName,
          MethodAttributes.Public |
          MethodAttributes.SpecialName |
          MethodAttributes.HideBySig,
          null, new[] { propertyType });
    var setMethodIL = setMethod.GetILGenerator();
    Label modifyProperty = setMethodIL.DefineLabel();
    Label exitSet = setMethodIL.DefineLabel();

    setMethodIL.MarkLabel(modifyProperty);
    setMethodIL.Emit(OpCodes.Ldarg_0);
    setMethodIL.Emit(OpCodes.Ldarg_1);
    setMethodIL.Emit(OpCodes.Stfld, fieldBuilder);
    setMethodIL.Emit(OpCodes.Nop);
    setMethodIL.MarkLabel(exitSet);
    setMethodIL.Emit(OpCodes.Ret);

    propertyBuilder.SetGetMethod(getMethod);
    propertyBuilder.SetSetMethod(setMethod);
}

有了類型后,就很容易通過使用Activator.CreateInstance()來創建它的實例。但是,你希望能夠更改已創建的屬性的值,為了做到這一點,你可以再次使用反射來獲取propertyInfos並提取Set方法。一旦有了這些屬性,電影它們類設置屬性值就相對簡單了。

foreach (var property in Properties)
{
    var propertyInfo = GeneratedType.GetProperty(property.Key);
    var setMethod = propertyInfo.GetSetMethod();
    setMethod.Invoke(objectInstance, new[] { propertyValue });
}

現在,您可以在運行時使用自定義屬性來創建自己的類型,並具有更新其值的功能,一切就緒。 我發現的唯一障礙是創建一個可以存儲新類型實例的列表。 WPF中的DataGrid傾向於只讀取List的常規參數類型的屬性。 這意味着即使您使用新屬性擴展了基類,使用AutoGenerateProperties也只能看到基類中的屬性。 解決方案是使用生成的類型顯式創建一個新的List。 我在下面提供了如何執行此操作的示例:

var listGenericType = typeof(List<>);
var list = listGenericType.MakeGenericType(GeneratedType);
var constructor = list.GetConstructor(new Type[] { });
var newList = (IList)constructor.Invoke(new object[] { });
foreach (var value in values)
    newList.Add(value);

結論

我已經在GitHub中創建了一個示例應用程序。它包含一個UI來幫助您調試和理解運行時新類型的創建,以及如何更新值。如果您有任何問題或意見,請隨時與我們聯繫。

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

【其他文章推薦】

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

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

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

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

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

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

一個線上問題的思考:Eureka註冊中心集群如何實現客戶端請求負載及故障轉移?

前言

先拋一個問題給我聰明的讀者,如果你們使用微服務SpringCloud-Netflix進行業務開發,那麼線上註冊中心肯定也是用了集群部署,問題來了:

你了解Eureka註冊中心集群如何實現客戶端請求負載及故障轉移嗎?

可以先思考一分鐘,我希望你能夠帶着問題來閱讀此篇文章,也希望你看完文章後會有所收穫!

背景

前段時間線上Sentry平台報警,多個業務服務在和註冊中心交互時,例如續約註冊表增量拉取等都報了Request execution failed with message : Connection refused 的警告:

緊接着又看到 Request execution succeeded on retry #2 的日誌。

看到這裏,表明我們的服務在嘗試兩次重連后和註冊中心交互正常了。

一切都顯得那麼有驚無險,這裏報Connection refused 是註冊中心網絡抖動導致的,接着觸發了我們服務的重連,重連成功后一切又恢復正常。

這次的報警雖然沒有對我們線上業務造成影響,並且也在第一時間恢復了正常,但作為一個愛思考的小火雞,我很好奇這背後的一系列邏輯:Eureka註冊中心集群如何實現客戶端請求負載及故障轉移?

註冊中心集群負載測試

線上註冊中心是由三台機器組成的集群,都是4c8g的配置,業務端配置註冊中心地址如下(這裏的peer來代替具體的ip地址):

eureka.client.serviceUrl.defaultZone=http://peer1:8080/eureka/,http://peer2:8080/eureka/,http://peer3:8080/eureka/

我們可以寫了一個Demo進行測試:

註冊中心集群負載測試

1、本地通過修改EurekaServer服務的端口號來模擬註冊中心集群部署,分別以87618762兩個端口進行啟動
2、啟動客戶端SeviceA,配置註冊中心地址為:http://localhost:8761/eureka,http://localhost:8762/eureka

3、啟動SeviceA時在發送註冊請求的地方打斷點:AbstractJerseyEurekaHttpClient.register(),如下圖所示:

這裏看到請求註冊中心時,連接的是8761這個端口的服務。

4、更改ServiceA中註冊中心的配置:http://localhost:8762/eureka,http://localhost:8761/eureka
5、重新啟動SeviceA然後查看端口,如下圖所示:

此時看到請求註冊中心是,連接的是8762這個端口的服務。

註冊中心故障轉移測試

以兩個端口分別啟動EurekaServer服務,再啟動一個客戶端ServiceA。啟動成功后,關閉一個8761端口對應的服務,查看此時客戶端是否會自動遷移請求到8762端口對應的服務:

1、以87618762兩個端口號啟動EurekaServer
2、啟動ServiceA,配置註冊中心地址為:http://localhost:8761/eureka,http://localhost:8762/eureka
3、啟動成功后,關閉8761端口的EurekaServer
4、在EurekaClient發送心跳請求的地方打上斷點:AbstractJerseyEurekaHttpClient.sendHeartBeat()
5、查看斷點處數據,第一次請求的EurekaServer8761端口的服務,因為該服務已經關閉,所以返回的responsenull

6、第二次會重新請求8762端口的服務,返回的response為狀態為200,故障轉移成功,如下圖:

思考

通過這兩個測試Demo,我以為EurekaClient每次都會取defaultZone配置的第一個host作為請求EurekaServer的請求的地址,如果該節點故障時,會自動切換配置中的下一個EurekaServer進行重新請求。

那麼疑問來了,EurekaClient每次請求真的是以配置的defaultZone配置的第一個服務節點作為請求的嗎?這似乎也太弱了!!?

EurekaServer集群不就成了偽集群!!?除了客戶端配置的第一個節點,其它註冊中心的節點都只能作為備份和故障轉移來使用!!?

真相是這樣嗎?NO!我們眼見也不一定為實,源碼面前毫無秘密!

翠花,上乾貨!

客戶端請求負載原理

原理圖解

還是先上結論,負載原理如圖所示:

這裡會以EurekaClient端的IP作為隨機的種子,然後隨機打亂serverList,例如我們在商品服務(192.168.10.56)中配置的註冊中心集群地址為:peer1,peer2,peer3,打亂后的地址可能變成peer3,peer2,peer1

用戶服務(192.168.22.31)中配置的註冊中心集群地址為:peer1,peer2,peer3,打亂后的地址可能變成peer2,peer1,peer3

EurekaClient每次請求serverList中的第一個服務,從而達到負載的目的。

代碼實現

我們直接看最底層負載代碼的實現,具體代碼在
com.netflix.discovery.shared.resolver.ResolverUtils.randomize() 中:

這裏面random 是通過我們EurekaClient端的ipv4做為隨機的種子,生成一個重新排序的serverList,也就是對應代碼中的randomList,所以每個EurekaClient獲取到的serverList順序可能不同,在使用過程中,取列表的第一個元素作為serverhost,從而達到負載的目的。

思考

原來代碼是通過EurekaClientIP進行負載的,所以剛才通過DEMO程序結果就能解釋的通了,因為我們做實驗都是用的同一個IP,所以每次都是會訪問同一個Server節點。

既然說到了負載,這裏肯定會有另一個疑問:

通過IP進行的負載均衡,每次請求都會均勻分散到每一個Server節點嗎?

比如第一次訪問Peer1,第二次訪問Peer2,第三次訪問Peer3,第四次繼續訪問Peer1等,循環往複……

我們可以繼續做個試驗,假如我們有10000個EurekaClient節點,3個EurekaServer節點。

Client節點的IP區間為:192.168.0.0 ~ 192.168.255.255,這裏面共覆蓋6w多個ip段,測試代碼如下:

/**
 * 模擬註冊中心集群負載,驗證負載散列算法
 *
 *  @author 一枝花算不算浪漫
 *  @date 2020/6/21 23:36
 */
public class EurekaClusterLoadBalanceTest {

    public static void main(String[] args) {
        testEurekaClusterBalance();
    }

    /**
     * 模擬ip段測試註冊中心負載集群
     */
    private static void testEurekaClusterBalance() {
        int ipLoopSize = 65000;
        String ipFormat = "192.168.%s.%s";
        TreeMap<String, Integer> ipMap = Maps.newTreeMap();
        int netIndex = 0;
        int lastIndex = 0;
        for (int i = 0; i < ipLoopSize; i++) {
            if (lastIndex == 256) {
                netIndex += 1;
                lastIndex = 0;
            }

            String ip = String.format(ipFormat, netIndex, lastIndex);
            randomize(ip, ipMap);
            System.out.println("IP: " + ip);
            lastIndex += 1;
        }

        printIpResult(ipMap, ipLoopSize);
    }

    /**
     * 模擬指定ip地址獲取對應註冊中心負載
     */
    private static void randomize(String eurekaClientIp, TreeMap<String, Integer> ipMap) {
        List<String> eurekaServerUrlList = Lists.newArrayList();
        eurekaServerUrlList.add("http://peer1:8080/eureka/");
        eurekaServerUrlList.add("http://peer2:8080/eureka/");
        eurekaServerUrlList.add("http://peer3:8080/eureka/");

        List<String> randomList = new ArrayList<>(eurekaServerUrlList);
        Random random = new Random(eurekaClientIp.hashCode());
        int last = randomList.size() - 1;
        for (int i = 0; i < last; i++) {
            int pos = random.nextInt(randomList.size() - i);
            if (pos != i) {
                Collections.swap(randomList, i, pos);
            }
        }

        for (String eurekaHost : randomList) {
            int ipCount = ipMap.get(eurekaHost) == null ? 0 : ipMap.get(eurekaHost);
            ipMap.put(eurekaHost, ipCount + 1);
            break;
        }
    }

    private static void printIpResult(TreeMap<String, Integer> ipMap, int totalCount) {
        for (Map.Entry<String, Integer> entry : ipMap.entrySet()) {
            Integer count = entry.getValue();
            BigDecimal rate = new BigDecimal(count).divide(new BigDecimal(totalCount), 2, BigDecimal.ROUND_HALF_UP);
            System.out.println(entry.getKey() + ":" + count + ":" + rate.multiply(new BigDecimal(100)).setScale(0, BigDecimal.ROUND_HALF_UP) + "%");
        }
    }
}

負載測試結果如下:

可以看到第二個機器會有50%的請求,最後一台機器只有17%的請求,負載的情況並不是很均勻,我認為通過IP負載並不是一個好的方案。

還記得我們之前講過Ribbon默認的輪詢算法RoundRobinRule,【一起學源碼-微服務】Ribbon 源碼四:進一步探究Ribbon的IRule和IPing 。

這種算法就是一個很好的散列算法,可以保證每次請求都很均勻,原理如下圖:

故障轉移原理

原理圖解

還是先上結論,如下圖:

我們的serverList按照client端的ip進行重排序后,每次都會請求第一個元素作為和Server端交互的host,如果請求失敗,會嘗試請求serverList列表中的第二個元素繼續請求,這次請求成功后,會將此次請求的host放到全局的一個變量中保存起來,下次client端再次請求 就會直接使用這個host

這裏最多會重試請求兩次。

代碼實現

直接看底層交互的代碼,位置在
com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient.execute() 中:

我們來分析下這個代碼:

  1. 第101行,獲取client上次成功server端的host,如果有值則直接使用這個host
  2. 第105行,getHostCandidates()是獲取client端配置的serverList數據,且通過ip進行重排序的列表
  3. 第114行,candidateHosts.get(endpointIdx++),初始endpointIdx=0,獲取列表中第1個元素作為host請求
  4. 第120行,獲取返回的response結果,如果返回的狀態碼是200,則將此次請求的host設置到全局的delegate變量中
  5. 第133行,執行到這裏說明第120行執行的response返回的狀態碼不是200,也就是執行失敗,將全局變量delegate中的數據清空
  6. 再次循環第一步,此時endpointIdx=1,獲取列表中的第二個元素作為host請求
  7. 依次執行,第100行的循環條件numberOfRetries=3,最多重試2次就會跳出循環

我們還可以第123和129行,這也正是我們業務拋出來的日誌信息,所有的一切都對應上了。

總結

感謝你看到這裏,相信你已經清楚了開頭提問的問題。

上面已經分析完了Eureka集群下Client端請求時負載均衡的選擇以及集群故障時自動重試請求的實現原理。

如果還有不懂的問題,可以添加我的微信或者給我公眾號留言,我會單獨和你討論交流。

本文首發自:一枝花算不算浪漫 公眾號,如若轉載請在文章開頭標明出處,如需開白可直接公眾號回復即可。

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

【其他文章推薦】

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

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

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

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

說說TCP的三次握手和四次揮手

一、傳輸控制協議TCP簡介

1.1 簡介

TCP(Transmission Control Protocol) 傳輸控制協議,是一種 面向連接的、可靠的、基於字節流的傳輸層 通信協議。

TCP是一種面向連接(連接導向)的、可靠的基於字節流的傳輸層通信協議。TCP將用戶數據打包成報文段,它發送后啟動一個定時器,另一端收到的數據進行確認、對失序的數據重新排序、丟棄重複數據。

TCP把連接作為最基本的對象,每一條TCP連接都有兩個端點,這種端點我們叫作套接字(socket),將端口號拼接到IP地址即構成了套接字,例如 192.1.1.6:50030

1.2 特點

  • 面向連接的、可靠的、基於字節流的 傳輸層 通信協議
  • 將應用層的數據流分割成文段併發送給目標節點的TCP層
  • 數據包都有序號,對方收到則發送ACK確認,未收到則重傳
  • 使用校驗和來檢驗數據在傳輸過程中是否有誤

二、TCP報文頭

1、源端口(Source Port)/ 目的端口(Destination Port):他們各佔2個字節,標示該段報文來自哪裡(源端口)以及要傳給哪個上層協議或應用程序(目的端口)。進行tcp通信時,一般client是通過系統自動選擇的臨時端口號,而服務器一般是使用知名服務端口號或者自己指定的端口號(比如DNS協議對應端口53,HTTP協議對應80)

2、序號(Sequence Number):佔據四個字節,TCP是面向字節流的,TCP連接中傳送的字節流中的每個字節都按順序編號,例如如一段報文的序號字段值是107,而攜帶的數據共有100個字段,如果有下一個報文過來,那麼序號就從207(100+107)開始,整個要傳送的字節流的起始序號必須要在連接建立時設置。首部中的序號字段值指的是本報文段所發送的數據的第一個字節的序號

3、確認序號(Acknowledgment Number):4個字節,是期望收到對方下一個報文段的第一個數據字節的序號,若確認號=N,則表明:到序號N-1為止的所有數據都已正確收到,例如:B收到A發送過來的報文,其序列號字段是301,而數據長度是200字節,這表明了B正確的收到了A到序號500(301+200-1)為止的數據,因此B希望收到A的下一個數據序號是501,於是B在發送給A的確認報文段中,會把ACK確認號設置為501

4、數據偏移(Offset):4個字節。指出TCP報文段的數據起始處距離報文段的起始處有多遠,這個字段實際上是指出TCP報文段的首部長度。由於首部中還有長度不確定的選項字段,因此數據偏移字段是必要的。單位是32位字,也就是4字節,4位二進制最大表示15,所以數據偏移也就是TCP首部最大60字節

5、保留(Reserved):6個字節。保留域

6、TCP Flags:控制位,由八個標誌位組成,每個標誌位表示控制的功能,我們主要來介紹TCP Flags中常用的六個,

  • URG(緊急指針標誌):當URG=1時,表明緊急指針字段有效。它告訴系統此報文段中有緊急數據,應儘快傳送(相當於高優先級的數據),而不要按原來的排隊順序來傳送。例如,已經發送了很長的一個程序在主機上運行。但後來發現了一些問題,需要取消該程序的運行。因此用戶從鍵盤發出中斷命令。如果不使用緊急數據,那麼這兩個字符將存儲在接收TCP的緩存末尾。只有在所有的數據被處理完畢后這兩個字符才被交付接收方的應用進程。這樣做就浪費了許多時間

  • ACK(確認序號標誌):當ACK=1時確認號字段有效。當ACK=0時,確認號無效。TCP規定,在連接建立后所有的傳送的報文段都必須把ACK置1

  • PSH(push標誌):當兩個應用進程進行交互式的通信時,有時在一端的應用進程希望在鍵入一個命令后立即就能收到對方的響應。在這種情況下,TCP就可以使用推送操作。這時,發送方TCP把PSH置1,並立即創建一個報文段發送出去。接收方TCP收到PSH=1的報文段,就儘快地交付接收應用進程,而不再等到整個緩存都填滿了後向上交付

  • RST(重置連接標誌):TCP連接中出現嚴重差錯(如由於主機崩潰或其他原因),必須釋放連接,然後再重新建立運輸連接,可以用來拒絕一個非法的報文段或拒絕打開一個連接

  • SYN(同步序號,用於建立連接過程):在連接建立時用來同步序號。當SYN=1而ACK=0時,表明這是一個連接請求報文段。對方若同意建立連接,則應在相應的報文段中使用SYN=1和ACK=1。因此,SYN置為1就表示這是一個連接請求或連接接受保溫。

  • FIN(finish標誌,用於釋放連接):當FIN=1時,表明此報文段的發送方的數據已發送完畢,並要求釋放運輸連接

7、窗口(Window)是TCP流量控制的一個手段。這裏說的窗口,指的是接收通告窗口(Receiver Window,RWND)。它告訴對方本端的TCP接收緩衝區還能容納多少字節的數據,這樣就可以控制發送數據的速度

8、檢驗和(Checksum):檢驗範圍包括首部和數據兩部分,由發送端填充,接收端對TCP報文段執行CRC算法以檢驗TCP報文段在傳輸過程中是否損壞。這也是TCP可靠傳輸的一個重要保障

9、緊急指針(Urgent Pointer):緊急指針僅在URG=1時才有意義,它指出本報文段中的緊急數據的字節數(緊急數據結束后就是普通數據)。因此,緊急指針指出了緊急數據的末尾在報文段中的位置。當所有緊急數據都處理完時,TCP就告訴應用程序恢復到正常操作。值得注意的是,即使窗口為零時也可發送緊急數據。

10、TCP可選項(TCP Options):長度可變,最長可達40字節。當沒有使用“選項”時,TCP的首部長度是20字節。

三、TCP的三次握手

所謂三次握手(Three-Way Handshake)即建立TCP連接,就是指建立一個TCP連接時,需要客戶端和服務端總共發送3個包以確認連接的建立。在socket編程中,這一過程由客戶端執行connect來觸發,整個流程如下圖所示:

在TCP/IP協議中,TCP協議提供可靠的連接服務,採用三次握手建立一個連接。

第一次握手: 建立連接時,客戶端發送SYN包(syn=j)到服務器,並進入SYN_SEND狀態,等待服務器確認,SYN:同步序列編號(Synchronize Sequence Numbers)。

第二次握手: 服務器收到 SYN 包,必須確認客戶的 SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;

第三次握手: 客戶端收到服務器的SYN + ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED(TCP連接成功)狀態,完成三次握手。

3.1 為什麼需要三次握手才能建立連接

  • 為了初始化Sequence Number 的初始值,實現可靠數據傳輸, TCP 協議的通信雙方, 都必須維護一個序列號, 以標識發送出去的數據包中, 哪些是已經被對方收到的。 三次握手的過程即是通信雙方相互告知序列號起始值, 並確認對方已經收到了序列號起始值的必經步驟
  • 如果只是兩次握手, 至多只有連接發起方的起始序列號能被確認, 另一方選擇的序列號則得不到確認

3.2 首次握手的隱患——SYN超時

一、問題起因分析:
  1. 服務器收到客戶端的SYN,回復SYN和ACK的時候未收到ACK確認
  2. 服務器不斷重試直至超時,Linux默認等待63秒才斷開連接;(重複5次【不包括第一次】,從1秒開始,每次重試都翻倍:1+2+4+8+16+32=63秒)
二、針對SYN Flood的防護措施:
  1. SYN隊列滿后,通過tcp_syncookies參數會發SYN cookie【源端口+目標端口+時間戳組成】
  2. 若為正常連接則Client會回發SYN Cookie,直接建立連接;

3.3 保活機制:

當我們建立連接后,Client出現故障怎麼辦?

  1. 向對方發送保活探測報文,如果未收到響應則繼續發送;
  2. 嘗試次數達到保活探測數仍未收到相應則中斷連接;

四、TCP的四次揮手

所謂四次揮手(Four-Way Wavehand)即終止TCP連接,就是指斷開一個TCP連接時,需要客戶端和服務端總共發送4個包以確認連接的斷開。在socket編程中,這一過程由客戶端或服務端任一方執行close來觸發,整個流程如下圖所示:

由於TCP連接時全雙工的,因此,每個方向都必須要單獨進行關閉,這一原則是當一方完成數據發送任務后,發送一個FIN來終止這一方向的連接,收到一個FIN只是意味着這一方向上沒有數據流動了,即不會再收到數據了,但是在這個TCP連接上仍然能夠發送數據,直到這一方向也發送了FIN。首先進行關閉的一方將執行主動關閉,而另一方則執行被動關閉。

  • 第一次揮手: Client發送一個FIN,用來關閉Client到Server的數據傳送,Client進入FIN_WAIT_1狀態
  • 第二次揮手: Server收到FIN后,發送一個ACK給Client,確認序號為收到序號+1(與SYN相同,一個FIN佔用一個序號),Server進入CLOSE_WAIT狀態
  • 第三次揮手: Server發送一個FIN,用來關閉Server到Client的數據傳送,Server進入LAST_ACK狀態
  • 第四次揮手: Client收到FIN后,Client進入TIME_WAIT狀態,接着發送一個ACK給Server,確認序號為收到序號+1,Server進入CLOSED狀態,完成四次揮手
一、為什麼會有TIME_WAIT狀態

客戶端連接在收到服務器的結束報文段之後,不會直接進入CLOSED狀態,而是轉移到TIME_WAIT狀態。在這個狀態,客戶端連接要等待一段長為2MSL,即兩倍的報文段最大生存時間,才能完全關閉,其原因主要有兩點:

  • 確保有足夠的時間放對方收到ACK包
  • 避免新舊連接混淆
二、為什麼需要四次握手才能斷開連接

因為TCP連接是全雙工的網絡協議,允許同時通信的雙方同時進行數據的收發,同樣也允許收發兩個方向的連接被獨立關閉,以避免client數據發送完畢,向server發送FIN關閉連接,而server還有發送到client的數據沒有發送完畢的情況。所以關閉TCP連接需要進行四次握手,每次關閉一個方向上的連接需要FIN和ACK兩次握手,發送發和接收方都需要FIN報文和ACK報文

三、服務器出現大量CLOSE_WAIT狀態的原因

是由於對方關閉socket連接,我方忙於讀或寫,沒有及時關閉連接

當客戶端因為某種原因先於服務端發出了FIN信號,就會導致服務端被動關閉,若服務端不主動關閉socket發FIN給Client,此時服務端Socket會處於CLOSE_WAIT狀態(而不是LAST_ACK狀態)。通常來說,一個CLOSE_WAIT會維持至少2個小時的時間(系統默認超時時間的是7200秒,也就是2小時)。如果服務端程序因某個原因導致系統造成一堆CLOSE_WAIT消耗資源,那麼通常是等不到釋放那一刻,系統就已崩潰

解決:
1、檢查代碼,特別是釋放資源的代碼
2、檢查配置,特別是處理請求的線程配置

Linux的檢查代碼:netstat -n | awk '/^tcp/{++S[$NF]}END{for(a in S) print a,S[a]}'

五、總結

到這裏TCP的三次握手四次揮手就講完了,好久都沒有寫技術文章了,寫了一下,感覺還挺好的,上面是博主的認識,有寫的不好的地方,大家可以在評論區討論或者提問,博主看到了會第一時間回復大家,最近也準備開始面試了,先好好準備一下,希望今年可以找到心滿意足的工作,也希望今年面試的小夥伴們都有一個好的office,大家一起加油,我是牧小農,我喂自己帶鹽,大家加油。

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

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

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

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

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

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.1Runtime Controller

運行控制器用來控制其子元件的執行時長。市場單位是秒。

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

2、關鍵參數說明如下:

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

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

Runtime:默認為1,去掉1則默認為0,此時不執行其節點下的元件。 與線程組中的調度器的持續時間 效果一致。不填 或 0,不會執行樣例

4.1.1Runtime控制器控制其下取樣器執行2s

1、創建測試計劃,設置 Runtime 控制器的運行時間 為 2,線程組設置默認不變,如下圖所示:

Runtime 控制器設置

線程組設置

2、配置好以後,運行JMeter,然後查看結果樹,如下圖所示:

4.1.2使用線程組中的調度器控制樣例運行3s

1、創建測試計劃,設置 Runtime 控制器的運行時間 為 2,線程組設置運行時間3,如下圖所示:

線程組設置

Runtime 控制器設置

2、配置好以後,運行JMeter,然後查看結果樹,如下圖所示:

線程組設置3,Runtime控制器設置2,但是運行時間是2s。所以從上邊的運行時間得出結論:如果線程組中設置了持續時間,Runtime 控制器也設置了 運行時間,那麼會優先於線程組中的設置。

4.2Simple Controller

Simple Controller用來指定了一個執行單元,它不改變元件的執行順序。在它下邊還可以嵌套其他控制器。簡單控制器可以編輯只有名稱和註釋。就像他的名字一樣,簡單,可以理解為一個文件夾,就是分組用的,沒有其他特殊功能,但相比不添加簡單控制器,區別在於簡單控制器可以被模塊控制器所引用。其作用就是分組,比如QQ好友列表,可分為家人、同學、等。一般是請求較多,需要分組時採用。

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

2、關鍵參數說明如下:

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

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

4.2.1簡單實例

1、創建測試計劃,線程組設置循環10,如下圖所示:

2、配置好以後,運行JMeter,然後查看結果樹,如下圖所示:

4.3Throughput Controller

用來控制其下元件的執行次數,並無控制吞吐量的功能,想要控制吞吐量可以使用Constant Throughput Timer,後邊會講解到。吞吐量控制器有兩種模式:Total Executions:設置運行次數與Percent Executions:設置運行比例(1~100之間)。

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

2、關鍵參數說明如下:

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

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

Total Executions:執行百分比(1-100);

percent Executions:執行數量;

Throughput:根據上邊選擇的方式填寫,百分比為0~100;

Per User:線程數,當選Total Executions時,是線程數;當選percent Executions時,是線程數*循環次數。

4.3.1不勾選Per User

1、線程組中設置 線程數量 2,循環次數 10,吞吐量控制器 設置 Total Executions,吞吐量設置為 2,其下添加一個取樣器,如下圖所示:

2、配置好以後,運行JMeter,然後查看結果樹(執行了2次),如下圖所示:

3、現在將 吞吐量控制器 設置為百分比的控制方式,吞吐量設置為:50%,如下圖所示:

4、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了10次,計算方式:10=吞吐量50% * 循環次數10 * 線程數 2),如下圖所示:

4.3.2勾選Per User

1、線程組中設置 線程數量 2,循環次數 10,吞吐量控制器 設置 Total Executions,吞吐量設置為 2,其下添加一個取樣器,勾選Per User,如下圖所示:

線程組設置

吞吐量控制器

2、配置好以後,點擊“保存”,運行JMeter,然後查看結果樹(總共執行了4次,其中吞吐量設置為2,執行2次,線程設置為2,執行2次,總共4次),函數 __threadNum 只是簡單地返回當前線程的編號,如下圖所示:

3、現在將 吞吐量控制器 設置為百分比的控制方式,吞吐量設置為:50,如下圖所示:

4、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了10次,計算方式:10=吞吐量50% * 循環次數10 * 線程數 2),如下圖所示:

綜上所述:

勾選Per User:

1.線程數*循環次數>=線程數*吞吐量時,Total Executions模式的執行次數=線程數*吞吐量。

2.線程數*循環次數<線程數*吞吐量時,Total Executions模式的執行次數=當線程數*循環次數。

不勾選Per User:

1.線程數*循環次數<=吞吐量時,Total Executions模式的執行次數=線程數*循環次數。

2.線程數*循環次數>吞吐量時,Total Executions模式的執行次數=吞吐量。

l Percent Executions:設置運行比例(1~100之間),單位為%

不管Per User是否勾選,按Percent Executions模式的執行次數都不受Per User影響,Percent Executions模式的執行次數=線程數*循環次數*吞吐量%。(循環次數=線程組循環次數*循環控制器循環次數)

l Per User:勾選該項的話則按虛擬用戶數(線程數)來計算執行次數,不勾選則按所有虛擬用戶數來計算執行次數

測試計劃

序號 線程數 循環次數 模式 Throughput Per User 執行次數
1 2 10 Percent 50 Y 10
2 2 10 Percent 50 N 10
3 2 10 Total 7 Y 14
4 2 10 Total 7 N 7
5 2 2 Total 7 Y 4
6 2 2 Total 7 N 4

下面說明一下這6個場景:
(1)序號1和2場景,Per User 對總執行次數沒有影響。
(2)序號3場景,Per User勾選,每個虛擬用戶(線程)執行7次,共執行14次。
(3)序號4場景,Per User不勾選,則所有虛擬用戶執行7次。
(4)序號5場景,Per User勾選,每個虛擬用戶(線程)執行7次,共執行14次,由於Thread Group計劃循環次數是4(2線程*2循環)次,所以最多只能執行4次。
(5)序號6場景,Per User不勾選,所有虛擬用戶執行7次,由於Thread Group計劃循環次數是4(2線程*2循環)次,所以最多只能執行4次。

4.4Module Controller

模塊控制器可以快速的切換腳本,不用來回的新建,方便腳本調試。 

可以理解為引用、調用的意思,執行內容為Module To Run種所選的內容,引用範圍為當前測試計劃內的測試片段、邏輯控制器<模塊控制器除外>
被引用的邏輯控制器、測試片段可以為禁用狀態,被引用后仍然會被執行。
可以將模塊控制器與包括控制器一起學習比較,模塊控制器是從內部文件中引用,引用上相對比較靈活,可以只引用部分測試片段或模塊內容,包括控制器是從外部文件引用,只能引用整個測試片段的內容。
注意:被應用的模塊位置不可隨意變更,變更後會執行時出現提示引用失敗
找到目標元素:快速查找與跳轉的作用,點擊後會立即跳轉到所選的邏輯控制器的內容詳情

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

2、關鍵參數說明如下:

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

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

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

4.4.1實例

1、創建測試計劃,添加兩個測試片段,並且在每個測試片段下添加一個取樣器,然後,添加線程組,再添加模塊控制器,最後添加查看結果樹,如下圖所示:

2、配置模塊控制器,選擇第一個測試片段,如下圖所示:

3、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了第1個測試片段的取樣器),如下圖所示:

4、配置模塊控制器,選擇第二個測試片段,如下圖所示: 

5、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了第2個測試片段的取樣器),如下圖所示:

4.5Switch Controller

Switch Controller:開關控制器,通過其下樣例順序數值或名稱 控制執行某一個樣例。

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

2、關鍵參數說明如下:

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

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

Switch Value:指定請求的索引或者名稱,索引從0開始,如果沒有賦值,或者索引超過請求個數的話就執行第0個請求。可以是数字,也可以是字符,為字符時匹配取樣器名稱,如果匹配不上就會默認並找取樣器名稱為default的取樣器,如果沒有則不運行。

4.5.1數值

數值:表示將執行其下第 數值+1個取樣器,例如:填1,將執行第2個取樣器;填0或者不填,將執行第1個取樣器;數值超出其下取樣器數目時,執行第1個取樣器。

1、創建一個測試計劃,設置線程組和Switch控制器,如下圖所示:

線程組

Switch控制器

2、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了第3<數值+1>個取樣器),如下圖所示:

3、修改Switch控制器的數值為0或者不填,如下圖所示:

4、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了第1<數值為0或者不填,執行第1個取樣器>個取樣器),如下圖所示:

4.5.2字符

1、創建一個測試計劃,設置線程組和Switch控制器(直接使用取樣器名字),如下圖所示:

線程組

Switch控制器

2、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了使用名字的取樣器),如下圖所示:

5.小結

好了,今天關於邏輯控制器的上篇就講解到這裏,這一篇主要介紹了 Runtime Controller 、 Simple Controller 、Throughput ControllerModule Controller 和  Switch Controller

 

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

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

 

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

基於領域驅動設計(DDD)超輕量級快速開發架構(二)動態linq查詢的實現方式

-之動態查詢,查詢邏輯封裝復用

基於領域驅動設計(DDD)超輕量級快速開發架構詳細介紹請看

https://www.cnblogs.com/neozhu/p/13174234.html

需求

  1. 配合EasyUI datagird filter實現多字段(任意字段)的篩選
  2. 根據業務需求篩選特定的狀態或條件,如:查看結案的訂單,最近30天的訂單,查看屬於我的訂單.等等,這些邏輯是固定也是可以被重用,但又不想每次寫相同的條件,那麼下面我會給我的解決方案.

需求1隻是一個偷懶的實現方式,因為datagrid自帶這個功能,但又不想根據具體的需求來畫查詢條件,如果需求必須要再datagrid上面做一塊查詢條件的輸入那目前只能在前端自己手工添加,在組織後傳入後台,暫時不在這裏討論

需求2可能不太好解釋,看完代碼就自然理解為什麼要這麼做了,這麼做的好處有哪些

具體實現的方式

 

 默認情況下 datagrid 有幾列就可以對這幾列進行篩選,對於日期型的字段會採用between,選擇2個時間之間進行篩選,数字類型會提供大於小於等符號選擇,可以自行嘗試,其原理是datagrid 會根據datagrid 頭部輸入的值生成一個Json字符串發送後台請求數據

JSON:格式

filterRules: [
{field:field,op:op,value:value},
{field:field,op:op,value:value},
] 
  • 通常的做法是一個一個判斷加條件
  1 var filters = JsonConvert.DeserializeObject<IEnumerable<filterRule>>(filterRules); 
  2 foreach (var rule in filters)
  3         {
  4           if (rule.field == "Id" && !string.IsNullOrEmpty(rule.value) && rule.value.IsInt())
  5           {
  6             var val = Convert.ToInt32(rule.value);
  7             switch (rule.op)
  8             {
  9               case "equal":
 10                 this.And(x => x.Id == val);
 11                 break;
 12               case "notequal":
 13                 this.And(x => x.Id != val);
 14                 break;
 15               case "less":
 16                 this.And(x => x.Id < val);
 17                 break;
 18               case "lessorequal":
 19                 this.And(x => x.Id <= val);
 20                 break;
 21               case "greater":
 22                 this.And(x => x.Id > val);
 23                 break;
 24               case "greaterorequal":
 25                 this.And(x => x.Id >= val);
 26                 break;
 27               default:
 28                 this.And(x => x.Id == val);
 29                 break;
 30             }
 31           }
 32           if (rule.field == "Name" && !string.IsNullOrEmpty(rule.value))
 33           {
 34             this.And(x => x.Name.Contains(rule.value));
 35           }
 36           if (rule.field == "Code" && !string.IsNullOrEmpty(rule.value))
 37           {
 38             this.And(x => x.Code.Contains(rule.value));
 39           }
 40 
 41           if (rule.field == "Address" && !string.IsNullOrEmpty(rule.value))
 42           {
 43             this.And(x => x.Address.Contains(rule.value));
 44           }
 45 
 46           if (rule.field == "Contect" && !string.IsNullOrEmpty(rule.value))
 47           {
 48             this.And(x => x.Contect.Contains(rule.value));
 49           }
 50 
 51           if (rule.field == "PhoneNumber" && !string.IsNullOrEmpty(rule.value))
 52           {
 53             this.And(x => x.PhoneNumber.Contains(rule.value));
 54           }
 55 
 56           if (rule.field == "RegisterDate" && !string.IsNullOrEmpty(rule.value))
 57           {
 58             if (rule.op == "between")
 59             {
 60               var datearray = rule.value.Split(new char[] { '-' });
 61               var start = Convert.ToDateTime(datearray[0]);
 62               var end = Convert.ToDateTime(datearray[1]);
 63 
 64               this.And(x => SqlFunctions.DateDiff("d", start, x.RegisterDate) >= 0);
 65               this.And(x => SqlFunctions.DateDiff("d", end, x.RegisterDate) <= 0);
 66             }
 67           }
 68           if (rule.field == "CreatedDate" && !string.IsNullOrEmpty(rule.value))
 69           {
 70             if (rule.op == "between")
 71             {
 72               var datearray = rule.value.Split(new char[] { '-' });
 73               var start = Convert.ToDateTime(datearray[0]);
 74               var end = Convert.ToDateTime(datearray[1]);
 75 
 76               this.And(x => SqlFunctions.DateDiff("d", start, x.CreatedDate) >= 0);
 77               this.And(x => SqlFunctions.DateDiff("d", end, x.CreatedDate) <= 0);
 78             }
 79           }
 80 
 81 
 82           if (rule.field == "CreatedBy" && !string.IsNullOrEmpty(rule.value))
 83           {
 84             this.And(x => x.CreatedBy.Contains(rule.value));
 85           }
 86 
 87          if (rule.field == "LastModifiedDate" && !string.IsNullOrEmpty(rule.value))
 88           {
 89             if (rule.op == "between")
 90             {
 91               var datearray = rule.value.Split(new char[] { '-' });
 92               var start = Convert.ToDateTime(datearray[0]);
 93               var end = Convert.ToDateTime(datearray[1]);
 94 
 95               this.And(x => SqlFunctions.DateDiff("d", start, x.LastModifiedDate) >= 0);
 96               this.And(x => SqlFunctions.DateDiff("d", end, x.LastModifiedDate) <= 0);
 97             }
 98           }
 99 
100           if (rule.field == "LastModifiedBy" && !string.IsNullOrEmpty(rule.value))
101           {
102             this.And(x => x.LastModifiedBy.Contains(rule.value));
103           }
104 
105         }

View Code

  • 新的做法是動態根據field,op,value生成一個linq 表達式,不用再做繁瑣的判斷,這塊代碼也可以被其它項目使用,非常好用
namespace SmartAdmin
{
 
  public static class PredicateBuilder
  {

    public static Expression<Func<T, bool>> FromFilter<T>(string filtergroup) {
      Expression<Func<T, bool>> any = x => true;
      if (!string.IsNullOrEmpty(filtergroup))
      {
        var filters = JsonSerializer.Deserialize<filter[]>(filtergroup);

          foreach (var filter in filters)
          {
            if (Enum.TryParse(filter.op, out OperationExpression op) && !string.IsNullOrEmpty(filter.value))
            {
              var expression = GetCriteriaWhere<T>(filter.field, op, filter.value);
              any = any.And(expression);
            }
          }
      }

      return any;
    }

    #region -- Public methods --
    public static Expression<Func<T, bool>> GetCriteriaWhere<T>(Expression<Func<T, object>> e, OperationExpression selectedOperator, object fieldValue)
    {
      var name = GetOperand<T>(e);
      return GetCriteriaWhere<T>(name, selectedOperator, fieldValue);
    }

    public static Expression<Func<T, bool>> GetCriteriaWhere<T, T2>(Expression<Func<T, object>> e, OperationExpression selectedOperator, object fieldValue)
    {
      var name = GetOperand<T>(e);
      return GetCriteriaWhere<T, T2>(name, selectedOperator, fieldValue);
    }

    public static Expression<Func<T, bool>> GetCriteriaWhere<T>(string fieldName, OperationExpression selectedOperator, object fieldValue)
    {
      var props = TypeDescriptor.GetProperties(typeof(T));
      var prop = GetProperty(props, fieldName, true);
      var parameter = Expression.Parameter(typeof(T));
      var expressionParameter = GetMemberExpression<T>(parameter, fieldName);
      if (prop != null && fieldValue != null)
      {
       
        BinaryExpression body = null;
        switch (selectedOperator)
        {
          case OperationExpression.equal:
            body = Expression.Equal(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType)?? prop.PropertyType), prop.PropertyType));
            return Expression.Lambda<Func<T, bool>>(body, parameter);
          case OperationExpression.notequal:
            body = Expression.NotEqual(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
            return Expression.Lambda<Func<T, bool>>(body, parameter);
          case OperationExpression.less:
            body = Expression.LessThan(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
            return Expression.Lambda<Func<T, bool>>(body, parameter);
          case OperationExpression.lessorequal:
            body = Expression.LessThanOrEqual(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
            return Expression.Lambda<Func<T, bool>>(body, parameter);
          case OperationExpression.greater:
            body = Expression.GreaterThan(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
            return Expression.Lambda<Func<T, bool>>(body, parameter);
          case OperationExpression.greaterorequal:
            body = Expression.GreaterThanOrEqual(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
            return Expression.Lambda<Func<T, bool>>(body, parameter);
          case OperationExpression.contains:
            var contains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
            var bodyLike = Expression.Call(expressionParameter, contains, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
            return Expression.Lambda<Func<T, bool>>(bodyLike, parameter);
          case OperationExpression.endwith:
            var endswith = typeof(string).GetMethod("EndsWith",new[] { typeof(string) });
            var bodyendwith = Expression.Call(expressionParameter, endswith, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
            return Expression.Lambda<Func<T, bool>>(bodyendwith, parameter);
          case OperationExpression.beginwith:
            var startswith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
            var bodystartswith = Expression.Call(expressionParameter, startswith, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
            return Expression.Lambda<Func<T, bool>>(bodystartswith, parameter);
          case OperationExpression.includes:
            return Includes<T>(fieldValue, parameter, expressionParameter, prop.PropertyType);
          case OperationExpression.between:
            return Between<T>(fieldValue, parameter, expressionParameter, prop.PropertyType);
          default:
            throw new Exception("Not implement Operation");
        }
      }
      else
      {
        Expression<Func<T, bool>> filter = x => true;
        return filter;
      }
    }

    public static Expression<Func<T, bool>> GetCriteriaWhere<T, T2>(string fieldName, OperationExpression selectedOperator, object fieldValue)
    {


      var props = TypeDescriptor.GetProperties(typeof(T));
      var prop = GetProperty(props, fieldName, true);

      var parameter = Expression.Parameter(typeof(T));
      var expressionParameter = GetMemberExpression<T>(parameter, fieldName);

      if (prop != null && fieldValue != null)
      {
        switch (selectedOperator)
        {
          case OperationExpression.any:
            return Any<T, T2>(fieldValue, parameter, expressionParameter);

          default:
            throw new Exception("Not implement Operation");
        }
      }
      else
      {
        Expression<Func<T, bool>> filter = x => true;
        return filter;
      }
    }



    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr, Expression<Func<T, bool>> or)
    {
      if (expr == null)
      {
        return or;
      }

      return Expression.Lambda<Func<T, bool>>(Expression.OrElse(new SwapVisitor(expr.Parameters[0], or.Parameters[0]).Visit(expr.Body), or.Body), or.Parameters);
    }

    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr, Expression<Func<T, bool>> and)
    {
      if (expr == null)
      {
        return and;
      }

      return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(new SwapVisitor(expr.Parameters[0], and.Parameters[0]).Visit(expr.Body), and.Body), and.Parameters);
    }

    #endregion
    #region -- Private methods --

    private static string GetOperand<T>(Expression<Func<T, object>> exp)
    {
      if (!( exp.Body is MemberExpression body ))
      {
        var ubody = (UnaryExpression)exp.Body;
        body = ubody.Operand as MemberExpression;
      }

      var operand = body.ToString();

      return operand.Substring(2);

    }

    private static MemberExpression GetMemberExpression<T>(ParameterExpression parameter, string propName)
    {
      if (string.IsNullOrEmpty(propName))
      {
        return null;
      }

      var propertiesName = propName.Split('.');
      if (propertiesName.Count() == 2)
      {
        return Expression.Property(Expression.Property(parameter, propertiesName[0]), propertiesName[1]);
      }

      return Expression.Property(parameter, propName);
    }

    private static Expression<Func<T, bool>> Includes<T>(object fieldValue, ParameterExpression parameterExpression, MemberExpression memberExpression ,Type type)
    {
      var safetype= Nullable.GetUnderlyingType(type) ?? type;

      switch (safetype.Name.ToLower())
      {
        case  "string":
          var strlist = (IEnumerable<string>)fieldValue;
          if (strlist == null || strlist.Count() == 0)
          {
            return x => true;
          }
          var strmethod = typeof(List<string>).GetMethod("Contains", new Type[] { typeof(string) });
          var strcallexp = Expression.Call(Expression.Constant(strlist.ToList()), strmethod, memberExpression);
          return Expression.Lambda<Func<T, bool>>(strcallexp, parameterExpression);
        case "int32":
          var intlist = (IEnumerable<int>)fieldValue;
          if (intlist == null || intlist.Count() == 0)
          {
            return x => true;
          }
          var intmethod = typeof(List<int>).GetMethod("Contains", new Type[] { typeof(int) });
          var intcallexp = Expression.Call(Expression.Constant(intlist.ToList()), intmethod, memberExpression);
          return Expression.Lambda<Func<T, bool>>(intcallexp, parameterExpression);
        case "float":
          var floatlist = (IEnumerable<float>)fieldValue;
          if (floatlist == null || floatlist.Count() == 0)
          {
            return x => true;
          }
          var floatmethod = typeof(List<int>).GetMethod("Contains", new Type[] { typeof(int) });
          var floatcallexp = Expression.Call(Expression.Constant(floatlist.ToList()), floatmethod, memberExpression);
          return Expression.Lambda<Func<T, bool>>(floatcallexp, parameterExpression);
        default:
          return x => true;
      }
      
    }
    private static Expression<Func<T, bool>> Between<T>(object fieldValue, ParameterExpression parameterExpression, MemberExpression memberExpression, Type type)
    {
      
      var safetype = Nullable.GetUnderlyingType(type) ?? type;
      switch (safetype.Name.ToLower())
      {
        case "datetime":
          var datearray = ( (string)fieldValue ).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
          var start = Convert.ToDateTime(datearray[0] + " 00:00:00", CultureInfo.CurrentCulture);
          var end = Convert.ToDateTime(datearray[1] + " 23:59:59", CultureInfo.CurrentCulture);
          var greater = Expression.GreaterThan(memberExpression, Expression.Constant(start, type));
          var less = Expression.LessThan(memberExpression, Expression.Constant(end, type));
          return Expression.Lambda<Func<T, bool>>(greater, parameterExpression)
            .And(Expression.Lambda<Func<T, bool>>(less, parameterExpression));
        case "int":
        case "int32":
          var intarray = ( (string)fieldValue ).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
          var min = Convert.ToInt32(intarray[0] , CultureInfo.CurrentCulture);
          var max = Convert.ToInt32(intarray[1], CultureInfo.CurrentCulture);
          var maxthen = Expression.GreaterThan(memberExpression, Expression.Constant(min, type));
          var minthen = Expression.LessThan(memberExpression, Expression.Constant(max, type));
          return Expression.Lambda<Func<T, bool>>(maxthen, parameterExpression)
            .And(Expression.Lambda<Func<T, bool>>(minthen, parameterExpression));
        case "decimal":
          var decarray = ( (string)fieldValue ).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
          var dmin = Convert.ToDecimal(decarray[0], CultureInfo.CurrentCulture);
          var dmax = Convert.ToDecimal(decarray[1], CultureInfo.CurrentCulture);
          var dmaxthen = Expression.GreaterThan(memberExpression, Expression.Constant(dmin, type));
          var dminthen = Expression.LessThan(memberExpression, Expression.Constant(dmax, type));
          return Expression.Lambda<Func<T, bool>>(dmaxthen, parameterExpression)
            .And(Expression.Lambda<Func<T, bool>>(dminthen, parameterExpression));
        case "float":
          var farray = ((string)fieldValue).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
          var fmin = Convert.ToDecimal(farray[0], CultureInfo.CurrentCulture);
          var fmax = Convert.ToDecimal(farray[1], CultureInfo.CurrentCulture);
          var fmaxthen = Expression.GreaterThan(memberExpression, Expression.Constant(fmin, type));
          var fminthen = Expression.LessThan(memberExpression, Expression.Constant(fmax, type));
          return Expression.Lambda<Func<T, bool>>(fmaxthen, parameterExpression)
            .And(Expression.Lambda<Func<T, bool>>(fminthen, parameterExpression));
        case "string":
          var strarray = ( (string)fieldValue ).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
          var smin = strarray[0];
          var smax = strarray[1];
        
          var strmethod = typeof(string).GetMethod("Contains");
          var mm = Expression.Call(memberExpression, strmethod, Expression.Constant(smin, type));
          var nn = Expression.Call(memberExpression, strmethod, Expression.Constant(smax, type));


          return Expression.Lambda<Func<T, bool>>(mm, parameterExpression)
            .Or(Expression.Lambda<Func<T, bool>>(nn, parameterExpression));
        default:
          return x => true;
      }

    }



    private static Expression<Func<T, bool>> Any<T, T2>(object fieldValue, ParameterExpression parameterExpression, MemberExpression memberExpression)
    {
      var lambda = (Expression<Func<T2, bool>>)fieldValue;
      var anyMethod = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
      .First(m => m.Name == "Any" && m.GetParameters().Count() == 2).MakeGenericMethod(typeof(T2));

      var body = Expression.Call(anyMethod, memberExpression, lambda);

      return Expression.Lambda<Func<T, bool>>(body, parameterExpression);
    }

    private static PropertyDescriptor GetProperty(PropertyDescriptorCollection props, string fieldName, bool ignoreCase)
    {
      if (!fieldName.Contains('.'))
      {
        return props.Find(fieldName, ignoreCase);
      }

      var fieldNameProperty = fieldName.Split('.');
      return props.Find(fieldNameProperty[0], ignoreCase).GetChildProperties().Find(fieldNameProperty[1], ignoreCase);

    }
    #endregion
  }

  internal class SwapVisitor : ExpressionVisitor
  {
    private readonly Expression from, to;
    public SwapVisitor(Expression from, Expression to)
    {
      this.from = from;
      this.to = to;
    }
    public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
  }
  public enum OperationExpression
  {
    equal,
    notequal,
    less,
    lessorequal,
    greater,
    greaterorequal,
    contains,
    beginwith,
    endwith,
    includes,
    between,
    any
  }
}

View Code

 1 public async Task<JsonResult> GetData(int page = 1, int rows = 10, string sort = "Id", string order = "asc", string filterRules = "")
 2     {
 3       try
 4       {
 5         var filters = PredicateBuilder.FromFilter<Company>(filterRules);
 6         var total = await this.companyService
 7                              .Query(filters)
 8                              .AsNoTracking()
 9                              .CountAsync()
10                               ;
11         var pagerows = (await this.companyService
12                              .Query(filters)
13                               .AsNoTracking()
14                            .OrderBy(n => n.OrderBy(sort, order))
15                            .Skip(page - 1).Take(rows)
16                            .SelectAsync())
17                            .Select(n => new
18                            {
19                              Id = n.Id,
20                              Name = n.Name,
21                              Code = n.Code,
22                              Address = n.Address,
23                              Contect = n.Contect,
24                              PhoneNumber = n.PhoneNumber,
25                              RegisterDate = n.RegisterDate.ToString("yyyy-MM-dd HH:mm:ss")
26                            }).ToList();
27         var pagelist = new { total = total, rows = pagerows };
28         return Json(pagelist);
29       }
30       catch(Exception e) {
31         throw e;
32         }
33 
34     }

配合使用的代碼

  • 對於固定查詢邏輯的封裝和復用,當然除了復用還可以明顯的提高代碼的可讀性.
public class OrderSalesQuery : QueryObject<Order>
{
    public decimal Amount { get; set; }
    public string Country { get; set; }
    public DateTime FromDate { get; set; }
    public DateTime ToDate { get; set; }

    public override Expression<Func<Order, bool>> Query()
    {
        return (x => 
            x.OrderDetails.Sum(y => y.UnitPrice) > Amount &&
            x.OrderDate >= FromDate &&
            x.OrderDate <= ToDate &&
            x.ShipCountry == Country);
    }
}

查看訂單的銷售情況,條件 金額,國家,日期

var orderRepository = new Repository<Order>(this);
    
var orders = orderRepository
    .Query(new OrderSalesQuery(){ 
        Amount = 100, 
        Country = "USA",
        FromDate = DateTime.Parse("01/01/1996"), 
        ToDate = DateTime.Parse("12/31/1996" )
    })
    .Select();

調用查詢方法

public class CustomerLogisticsQuery : QueryObject<Customer>
{
    public CustomerLogisticsQuery FromCountry(string country)
    {
        Add(x => x.Country == country);
        return this;
    }

    public CustomerLogisticsQuery LivesInCity(string city)
    {   
        Add(x => x.City == city);
        return this;
    }
}

客戶查詢 根據國家和城市查詢

public class CustomerSalesQuery : QueryObject<Customer>
{
    public CustomerSalesQuery WithPurchasesMoreThan(decimal amount)
    {
        Add(x => x.Orders
            .SelectMany(y => y.OrderDetails)
            .Sum(z => z.UnitPrice * z.Quantity) > amount);

        return this;
    }

    public CustomerSalesQuery WithQuantitiesMoreThan(decimal quantity)
    {
        Add(x => x.Orders
            .SelectMany(y => y.OrderDetails)
            .Sum(z => z.Quantity) > quantity);

        return this;
    }
}

客戶的銷售情況,金額和數量

var customerRepository = new Repository<Customer>(this);

var query1 = new CustomerLogisticsQuery()
    .LivesInCity("London");

var query2 = new CustomerSalesQuery()
    .WithPurchasesMoreThan(100)
    .WithQuantitiesMoreThan(10);

customerRepository
    .Query(query1.And(query2))
    .Select()
    .Dump();

復用上面的定義的查詢方法

以上這些都是改項目提供的方法,非常的好用

 

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

【其他文章推薦】

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

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

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

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

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

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

三文搞懂學會Docker容器技術(下)

接着上面一篇:三文搞懂學會Docker容器技術(上)

                         三文搞懂學會Docker容器技術(中)

7,Docker容器目錄掛載

  7.1 簡介

容器目錄掛載:

我們可以在創建容器的時候,將宿主機的目錄與容器內的目錄進行映射,這樣我們就可以實現宿主機和容器目錄的雙向數據自動同步;

  7.2 作用

前面學過cp命令來實現數據傳遞,這種方式比較麻煩;

我們通過容器目錄掛載,能夠輕鬆實現代碼上傳,配置修改,日誌同步等需求;

  7.3 實現

語法:

docker run -it -v  /宿主機目錄:/容器目錄 鏡像名

多目錄掛載

docker run -it -v /宿主機目錄:/容器目錄 -v /宿主機目錄2:/容器目錄2  鏡像名

注意:

如果你同步的是多級目錄,可能會出現權限不足的提示;

這是因為Centos7中的安全模塊selinux把權限禁掉了,我們需要添加  –privileged=true 來解決掛載的目錄沒有權限的問題;

  7.4 掛載目錄只讀

docker run -it -v  /宿主機目錄:/容器目錄:ro 鏡像名

 

8,Docker遷移與備份

  8.1 概述

我們開發的時候,經常自定義鏡像,然後commit提交成鏡像到本地倉庫,但是我們發布到客戶服務器的時候,可以用前面講得搞到hub官方,或者阿里雲,但是有些機密性的項目,是禁止公網存儲的,所以我們只能通過docker鏡像備份和遷移實現;

  8.2 實現

備份鏡像:

docker save -o 備份鏡像的名稱  源鏡像名稱:tag版本

 docker save -o mytomcat7.1.tar java1234/tomcat7:7.1

 

恢復鏡像:

docker load -i 鏡像文件

docker load -i mytomcat7.1.tar

 

9,DockerFile詳解

  9.1 DockerFile簡介

Dockerfile是由一系列命令和參數構成的腳本,這些命令應用於操作系統(centos或者Ubuntu)基礎鏡像並最終創建的一個新鏡像;

我們前面講過的用手工的方式,修改配置文件,或者添加,刪除文件目錄的方式,來構建一種新鏡像;這種手工方式麻煩,容易出錯,而且不能復用;

我們這裏講Dockerfile,用腳本方式來構建自動化,可復用的,高效率的創建鏡像方式,是企業級開發的首選方式;

 

再軟件系統開發生命周期中,採用Dockerfile來構建鏡像;

1、對於開發人員:可以為開發團隊提供一個完全一致的開發環境;

2、對於測試人員:可以直接拿開發時所構建的鏡像或者通過Dockerfile文件構建一個新的鏡像開始工作;

3、對於運維人員:在部署時,可以實現應用的無縫移植。

  9.2 DockerFile常用指令

FROM image_name:tag 定義了使用哪個基礎鏡像啟動構建流程
MAINTAINER user_info 聲明鏡像維護者信息
LABEL key value 鏡像描述元信息(可以寫多條)
ENV key value 設置環境變量(可以寫多條)
RUN command 構建鏡像時需要運行的命令(可以寫多條)
WORKDIR path_dir 設置終端默認登錄進來的工作目錄
EXPOSE port 當前容器對外暴露出的端口
ADD source_dir/file dest_dir/file 將宿主機的文件複製到容器內,如果是一個壓縮文件,將會在複製后自動解壓
COPY source_dir/file dest_dir/file 和ADD相似,但是如果有壓縮文件是不能解壓
VOLUME 創建一個可以從本地主機或其他容器掛載的掛載點,一般用來存放數據庫和需要保持的數據等
CMD 指定容器啟動時要運行的命令,假如有多個CMD,最後一個生效
ENTRYPOINT 指定容器啟動時要運行的命令
ONBUILD 當構建一個被繼承的Dockerfile時運行的命令,父鏡像在被子鏡像繼承後父鏡像的onbuild被觸發。可以把ONBUID理解為一個觸發器。

 

10,Docker私有倉庫

  10.1 簡介

Docker私有倉庫主要是企業內部用來存放鏡像的倉庫,相對官方倉庫以及阿里雲倉庫,具有更高的保密安全級別;

  10.2 私有倉庫搭建

第一步:拉取私有倉庫鏡像 (私有倉庫程序本身就是一個鏡像)

docker pull registry

第二步:啟動私有倉庫容器

docker run -di –name=myRegistry -p 5000:5000 registry

第三步:測試

http://192.168.1.112:5000/v2/_catalog

看到這個 說明啟動OK。因為倉庫里還沒有鏡像,所以就是空的;

第四步:etc/docker 修改daemon.json,讓docker信任私有倉庫地址

“insecure-registries”: [“192.168.1.112:5000”]

 

第五步:修改配置后重啟docker;

 systemctl restart docker

  10.3 私有倉庫測試

第一步:標記此鏡像為私有倉庫的鏡像

docker tag tomcat:7 192.168.1.112:5000/mytomcat7

第二步:上傳鏡像到私有倉庫

docker push 192.168.1.112:5000/mytomcat7

此時私有倉庫里已經有了這個鏡像;

第三步:刪除192.168.1.112:5000/mytomcat7本地倉庫鏡像

docker rmi -f 192.168.1.112:5000/mytomcat7

第四步:從私有倉庫拉取192.168.1.112:5000/mytomcat7鏡像,並運行;

docker run -it -p 8080:8080 192.168.1.112:5000/mytomcat7

第五步:瀏覽器運行 http://192.168.1.112:8080測試

 

——————————————————————————————————————————

作者: java1234_小鋒

出處:https://www.cnblogs.com/java688/p/13174647.html

版權:本站使用「CC BY 4.0」創作共享協議,轉載請在文章明顯位置註明作者及出處。

——————————————————————————————————————————

 

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

【其他文章推薦】

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

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

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

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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