標籤: 包裝設計

  • 大發現!國3汽車的PM2.5居然比國5低?

    大發現!國3汽車的PM2.5居然比國5低?

    接下來是國三的車。所以僅從pM2。5的指數上來看,老車的成績不一定差,新車的成績也不一定好,反而車況才是更重要的因素。而那些所謂的專家。其實之前其他網友測試汽車尾氣pM2。5指數得出驚人的結果。(汽車尾氣比空氣乾淨,聽着很扯,畢竟你不可能整天去吸尾氣),就有專家出來表明,汽車的排放導致的污染並非只是直接的pM排放污染,而是“二次反應”。

    一到冬季,pM指數和霧霾就成了熱門話題,這個時候汽車就會慣例被推上輿論焦點,近期國六排放標準的出台也進一步表明了相關部門的觀點—“霧霾的很大成因就是因為汽車的排放”。每次說到空氣污染提得最多的就是pM2.5指數。

    pM指數那麼多,為什麼偏偏是pM2.5?

    pM的英文全稱為particulate Matter (顆粒物),而数字2.5是指這種顆粒物的空氣動力學直徑(aerodynamic diameter)為2.5微米( pM10則是指粒徑等於、小於10微米的顆粒物)。粒徑越小、密度越低,顆粒沉降得越慢。如果再有一些外力引起空氣微小的擾動,這些細小的顆粒即使在無風的條件下,最終也很難沉降到地面上,它們會一直在空中“遊盪”,成為危害人體健康的罪魁禍首。

    而我們常說的pM2.5的濃度或其他污染氣體的濃度是指每立方米空氣中這種污染物的質量含量,它反映了空氣的污染程度,這個值越高,就代表空氣污染越嚴重。由於空氣中污染物的種類很多,如:可吸入顆粒物、二氧化硫、氮氧化物、一氧化碳及臭氧等,為了統一評估,我國的環保部門,將每一種污染物的濃度都換算成統一的空氣污染指數,然後對外發布。俗話說病從口入,那麼我們今天就從源頭-用儀器來測試一下汽車尾氣的pM2.5指數。

    在此之前我們先看下香煙的pM指數,直接爆表,所以能戒煙朋友趕緊戒煙了。

    接着測試一下國4的車。(所測汽車均為公司同事車輛)

    然後是國5的車!

    接下來是國三的車。

    所以僅從pM2.5的指數上來看,老車的成績不一定差,新車的成績也不一定好,反而車況才是更重要的因素。

    而那些所謂的專家。。。

    其實之前其他網友測試汽車尾氣pM2.5指數得出驚人的結果。。。

    (汽車尾氣比空氣乾淨,聽着很扯,畢竟你不可能整天去吸尾氣),就有專家出來表明,汽車的排放導致的污染並非只是直接的pM排放污染,而是“二次反應”。

    這樣一群吃瓜群眾就懵逼了,你是博士后,你說什麼都對,就像一直聽說F1產生的下壓力足以讓其貼在牆上跑,但是從來沒有見過,不相信這件事的人也找不到反駁的理由。

    不管汽車的排放是不是元兇(因為現在霧霾這個鍋汽車已經背定了),相關部門對新車排放的要求越來越高並沒錯,畢竟排放再少也是排放。假設之前國三標準滿分是100分,當年生產的汽車再優秀,也只能得100。

    現在國五標準出來了,直接就說滿分是150,100分不及格,也不給你重考的機會(限制進京、限制遷入等),這種把能夠達到要求的老車也一刀切那就有點想不通了,對於車迷來說最大的損失就是中國就永遠不會有老爺車文化,因為在相關部門眼中只有報廢車。

    最後通過數據計算可以知道廣州的車輛密度比北京還要高,但是空氣質量卻要好不少,所以愛吃烤鴨的小夥伴趕緊把烤鴨吃個夠,說不定哪天烤鴨也分黃標鴨!!!

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

    【其他文章推薦】

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

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

    ※超省錢租車方案

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

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

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

  • 為什麼朋友說他夢想的車是艾母雞?看完本文後終於懂了

    為什麼朋友說他夢想的車是艾母雞?看完本文後終於懂了

    說到奔馳許多人最初的印象都是寬、大、貴外加後排坐着一個人肥,牙黃,地中海的老闆似乎奔馳一向跟年輕、運動、操控這些詞扯不上什麼關係所以坊間也流傳這麼一個說法——甚至有一些“自信狂人”遇上大奔時會抑制不住腎上腺

    說到奔馳

    許多人最初的印象都是

    寬、大、貴

    外加後排坐着一個

    人肥,牙黃,地中海的老闆

    似乎奔馳一向跟

    年輕、運動、操控這些詞

    扯不上什麼關係

    所以坊間也流傳這麼一個說法——

    甚至有一些“自信狂人”

    遇上大奔時

    會抑制不住腎上腺本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

  • 我很驚訝!十幾萬的國產SUV竟有人幹得過眾泰?

    我很驚訝!十幾萬的國產SUV竟有人幹得過眾泰?

    對於我們汽車媒體來說,這是個很方面的功能。榮威RX5配備了車身穩定系統、定速巡航、一鍵啟動、電動駐車、前撞預警系統、陡坡緩降、電動後備廂、車道保持、道路限速識別、四驅系統鎖止等豐富配置。動力系統方面,RX5將搭載1。

    國內緊湊型SUV的熱度只增不減,2016年,榮威帶來了他們潛心打造,並得到馬雲的阿里技術支持的榮威RX5,主打互聯網概念。

    榮威RX5外觀走大氣沉穩范,中規中矩但又精緻的造型給人不錯的印象。前臉“展翼格柵”將兩側前大燈融貫一體,前大燈為矩陣式LED大燈,其由24顆LED光源組成,燈組內部還集成了“如意形”LED日間行車燈。矩陣式全LED大燈更顯科技感。

    一進車內最吸睛的當屬中控10.4英寸大屏。由於大屏幕的使用,一些傳統按鍵被取消,整體設計簡潔時尚,僅在屏幕下方保留了五個實體按鍵。同時RX5在內飾用料上也多處使用了軟質皮革包裹,凸顯質感。

    RX5配備了榮威和阿里聯合開發的Yun OS,功能較為強大,可以通過大數據為用戶提供個性化服務。還可以支持綁定航拍機和運動相機,旅途中拍攝的畫面可以呈現在中控的大屏幕上。對於我們汽車媒體來說,這是個很方面的功能。

    榮威RX5配備了車身穩定系統、定速巡航、一鍵啟動、電動駐車、前撞預警系統、陡坡緩降、電動後備廂、車道保持、道路限速識別、四驅系統鎖止等豐富配置。

    動力系統方面,RX5將搭載1.5T和2.0T兩款渦輪增壓發動機,其中1.5T發動機最大功率為169馬,峰值扭矩250N·m,與之匹配的是手動/TST 7速雙離合變速箱;2.0T發動機最大功率為220馬力,峰值扭矩350N·m,與之匹配的是TST 6速濕式雙離合變速箱。

    整車開起來動力表現不錯,車輛的轉向是比較精準的,底盤偏向柔軟和舒適,但是又保留了一些路感。很符合家用SUV得定位。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

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

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

  • .net core3.1 abp動態菜單和動態權限(思路) (二)

    .net core3.1 abp動態菜單和動態權限(思路) (二)

    ps:本文需要先把abp的源碼下載一份來下,跟着一起找實現,更容易懂

    在abp中,對於權限和菜單使用靜態來管理,菜單的加載是在登陸頁面的地方(具體是怎麼知道的,瀏覽器按F12,然後去sources中去找)

    這個/AbpScripts/GetScripts是獲取需要初始化的script,源自AbpScriptsController,GetScripts方法包括

    頁面加載時的鏈接是:http://localhost:62114/AbpScripts/GetScripts?v=637274153555501055

    _multiTenancyScriptManager //當前租戶初始化 對應報文的 abp.multiTenancy

    _sessionScriptManager //當前session初始化 對應報文的 abp.session
    _localizationScriptManager  //本地化的初始化 對應報文的 abp.localization
    _featuresScriptManager  //對應報文的 abp.features
    _authorizationScriptManager  //權限初始化  對應報文的 abp.auth
    _navigationScriptManager  //導航菜單初始化  對應報文的 abp.nav
    _settingScriptManager  //設置初始化  對應報文的 abp.setting
    _timingScriptManager  //對應報文的 abp.clock
    _customConfigScriptManager  //對應報文的 abp.custom

     

     

     

     

     好了,現在基本算是找到菜單和權限js獲取的地方了,一般系統裏面,權限是依賴於菜單和菜單按鈕的,所以我們先不管權限,先把菜單做成動態加載的

    從await _navigationScriptManager.GetScriptAsync()開始,一路F12,大概流程是

    (接口)INavigationScriptManager=>(接口實現)NavigationScriptManager=>(方法)GetScriptAsync=>(調用)await _userNavigationManager.GetMenusAsync=>
    (接口)IUserNavigationManager=>(接口實現)UserNavigationManager=>(方法)GetMenuAsync=>(調用)navigationManager.Menus=>
    (接口)INavigationManager=>(接口實現)NavigationManager=>(非靜態構造函數為Menus屬性賦值)NavigationManager

     到這裏之後基本就到底了,我們看看NavigationManager的內容

        internal class NavigationManager : INavigationManager, ISingletonDependency
        {
            public IDictionary<string, MenuDefinition> Menus { get; private set; }  //屬性
    
            public MenuDefinition MainMenu //屬性
            {
                get { return Menus["MainMenu"]; }
            }
    
            private readonly IIocResolver _iocResolver;  
            private readonly INavigationConfiguration _configuration;
    
            public NavigationManager(IIocResolver iocResolver, INavigationConfiguration configuration) //非靜態構造函數
            {
                _iocResolver = iocResolver;
                _configuration = configuration;
    
                Menus = new Dictionary<string, MenuDefinition>
                        {
                            {"MainMenu", new MenuDefinition("MainMenu", new LocalizableString("MainMenu", AbpConsts.LocalizationSourceName))}
                        };
            }
    
            public void Initialize()  //初始化方法
            {
                var context = new NavigationProviderContext(this);
    
                foreach (var providerType in _configuration.Providers)
                {
                    using (var provider = _iocResolver.ResolveAsDisposable<NavigationProvider>(providerType))
                    {
                        provider.Object.SetNavigation(context);  //中式英語翻譯一下,應該是設置導航
                    }
                }
            }
        }

    這個類裏面就只有屬性、需要注入的接口聲明、非靜態構造函數、初始化方法,我們到這裏需要關注的是Menus這個屬性,這個屬性似乎將會包含我們需要生成的菜單內容

    Menus = new Dictionary<string, MenuDefinition>
                        {
                            {"MainMenu", new MenuDefinition("MainMenu", new LocalizableString("MainMenu", AbpConsts.LocalizationSourceName))}
                        };

    這裡是對Menus的賦值,實例化了一個Dictionary,前面的不用看,主要是看標紅的這句話,從new LocalizableString(“MainMenu”, AbpConsts.LocalizationSourceName)裏面獲取到值

    好了現在基本找到地方了,我們不知道LocalizableString是什麼意思,但是我們可以百度一波

    ILocalizableString/LocalizableString:封裝需要被本地化的string的信息,並提供Localize方法(調用ILocalizationManager的GetString方法)返回本地化的string. SourceName指定其從那個本地化資源讀取本地化文本。

      LocalizableString(“Questions”, “”) 如果本地找不到資源,會報300

    大概的意思是通過new LocalizableString,我們可以在本地化來源為AbpConsts.LocalizationSourceName的string裏面尋找到Key為MainMenu的value(理解不對請噴)

     

    現在需要去找到那個地方對MainMenu進行了本地化操作,一般來說這個事情都是在程序加載的時候進行的,先對MainMenu進行讀取,保存到本地,然後在_navigationScriptManager讀取,傳輸給前台

    似乎不好找了,但是我們發現有一個類型MenuDefinition,F12一下,可以發現寶藏

    namespace Abp.Application.Navigation
    {
        /// <summary>
        /// Represents a navigation menu for an application.  //表示應用程序的導航菜單
    /// </summary>
        public class MenuDefinition : IHasMenuItemDefinitions
        {
            /// <summary>
            /// Unique name of the menu in the application. Required.  //應用程序中菜單的唯一名稱。 必須
            /// </summary>
            public string Name { get; private set; }
    
            /// <summary>
            /// Display name of the menu. Required.  //菜單显示名稱 必須
    /// </summary>
            public ILocalizableString DisplayName { get; set; }
    
            /// <summary>
            /// Can be used to store a custom object related to this menu. Optional.  //可用於存儲與此菜單相關的自定義對象
    /// </summary>
            public object CustomData { get; set; }
    
            /// <summary>
            /// Menu items (first level).   //菜單項(第一級)
    /// </summary>
            public List<MenuItemDefinition> Items { get; set; }
    
            /// <summary>
            /// Creates a new <see cref="MenuDefinition"/> object.
            /// </summary>
            /// <param name="name">Unique name of the menu</param>
            /// <param name="displayName">Display name of the menu</param>
            /// <param name="customData">Can be used to store a custom object related to this menu.</param>
            public MenuDefinition(string name, ILocalizableString displayName, object customData = null)
            {
                if (string.IsNullOrEmpty(name))
                {
                    throw new ArgumentNullException("name", "Menu name can not be empty or null.");
                }
    
                if (displayName == null)
                {
                    throw new ArgumentNullException("displayName", "Display name of the menu can not be null.");
                }
    
                Name = name;
                DisplayName = displayName;
                CustomData = customData;
    
                Items = new List<MenuItemDefinition>();
            }
    
            /// <summary>
            /// Adds a <see cref="MenuItemDefinition"/> to <see cref="Items"/>.
            /// </summary>
            /// <param name="menuItem"><see cref="MenuItemDefinition"/> to be added</param>
            /// <returns>This <see cref="MenuDefinition"/> object</returns>
            public MenuDefinition AddItem(MenuItemDefinition menuItem)
            {
                Items.Add(menuItem);
                return this;
            }
    
            /// <summary>
            /// Remove menu item with given name
            /// </summary>
            /// <param name="name"></param>
            public void RemoveItem(string name)
            {
                Items.RemoveAll(m => m.Name == name);
            }
        }
    }

    找到了菜單的類型了,那麼我們去找保存的地方就好找了,我們其實可以根據AddItem這個方法去找,去查看哪個地方引用了

    AddItem方法添加的是MenuItemDefinition類型的變量,那我們現在退出abp源碼,去我們的AbpLearn項目中去全局搜索一下

     

     

    看來是同一個AbpLearnNavigationProvider類裏面,雙擊過去看一下

     

        /// <summary>
        /// This class defines menus for the application.
        /// </summary>
        public class AbpLearnNavigationProvider : NavigationProvider
        {
            public override void SetNavigation(INavigationProviderContext context)
            {
                context.Manager.MainMenu
                    .AddItem(
                        new MenuItemDefinition(
                            PageNames.Home,
                            L("HomePage"),
                            url: "",
                            icon: "fas fa-home",
                            requiresAuthentication: true
                        )
                    ).AddItem(
                        new MenuItemDefinition(
                            PageNames.Tenants,
                            L("Tenants"),
                            url: "Tenants",
                            icon: "fas fa-building",
                            permissionDependency: new SimplePermissionDependency(PermissionNames.Pages_Tenants)
                        )
                    ).AddItem(
                        new MenuItemDefinition(
                            PageNames.Users,
                            L("Users"),
                            url: "Users",
                            icon: "fas fa-users",
                            permissionDependency: new SimplePermissionDependency(PermissionNames.Pages_Users)
                        )
                    ).AddItem(
                        new MenuItemDefinition(
                            PageNames.Roles,
                            L("Roles"),
                            url: "Roles",
                            icon: "fas fa-theater-masks",
                            permissionDependency: new SimplePermissionDependency(PermissionNames.Pages_Roles)
                                )
                    )
                    .AddItem(
                        new MenuItemDefinition(
                            PageNames.About,
                            L("About"),
                            url: "About",
                            icon: "fas fa-info-circle"
                        )
                    ).AddItem( // Menu items below is just for demonstration!
                        new MenuItemDefinition(
                            "MultiLevelMenu",
                            L("MultiLevelMenu"),
                            icon: "fas fa-circle"
                        ).AddItem(
                            new MenuItemDefinition(
                                "AspNetBoilerplate",
                                new FixedLocalizableString("ASP.NET Boilerplate"),
                                icon: "far fa-circle"
                            ).AddItem(
                                new MenuItemDefinition(
                                    "AspNetBoilerplateHome",
                                    new FixedLocalizableString("Home"),
                                    url: "https://aspnetboilerplate.com?ref=abptmpl",
                                    icon: "far fa-dot-circle"
                                )
                            ).AddItem(
                                new MenuItemDefinition(
                                    "AspNetBoilerplateTemplates",
                                    new FixedLocalizableString("Templates"),
                                    url: "https://aspnetboilerplate.com/Templates?ref=abptmpl",
                                    icon: "far fa-dot-circle"
                                )
                            ).AddItem(
                                new MenuItemDefinition(
                                    "AspNetBoilerplateSamples",
                                    new FixedLocalizableString("Samples"),
                                    url: "https://aspnetboilerplate.com/Samples?ref=abptmpl",
                                    icon: "far fa-dot-circle"
                                )
                            ).AddItem(
                                new MenuItemDefinition(
                                    "AspNetBoilerplateDocuments",
                                    new FixedLocalizableString("Documents"),
                                    url: "https://aspnetboilerplate.com/Pages/Documents?ref=abptmpl",
                                    icon: "far fa-dot-circle"
                                )
                            )
                        ).AddItem(
                            new MenuItemDefinition(
                                "AspNetZero",
                                new FixedLocalizableString("ASP.NET Zero"),
                                icon: "far fa-circle"
                            ).AddItem(
                                new MenuItemDefinition(
                                    "AspNetZeroHome",
                                    new FixedLocalizableString("Home"),
                                    url: "https://aspnetzero.com?ref=abptmpl",
                                    icon: "far fa-dot-circle"
                                )
                            ).AddItem(
                                new MenuItemDefinition(
                                    "AspNetZeroFeatures",
                                    new FixedLocalizableString("Features"),
                                    url: "https://aspnetzero.com/Features?ref=abptmpl",
                                    icon: "far fa-dot-circle"
                                )
                            ).AddItem(
                                new MenuItemDefinition(
                                    "AspNetZeroPricing",
                                    new FixedLocalizableString("Pricing"),
                                    url: "https://aspnetzero.com/Pricing?ref=abptmpl#pricing",
                                    icon: "far fa-dot-circle"
                                )
                            ).AddItem(
                                new MenuItemDefinition(
                                    "AspNetZeroFaq",
                                    new FixedLocalizableString("Faq"),
                                    url: "https://aspnetzero.com/Faq?ref=abptmpl",
                                    icon: "far fa-dot-circle"
                                )
                            ).AddItem(
                                new MenuItemDefinition(
                                    "AspNetZeroDocuments",
                                    new FixedLocalizableString("Documents"),
                                    url: "https://aspnetzero.com/Documents?ref=abptmpl",
                                    icon: "far fa-dot-circle"
                                )
                            )
                        )
                    );
            }
    
            private static ILocalizableString L(string name)
            {
                return new LocalizableString(name, AbpLearnConsts.LocalizationSourceName);
            }
        }

    好了,現在我們找到菜單定義的地方了,那麼我們如何去做動態菜單哪?

     

    首先我們想一下需要什麼樣的動態菜單?

    1.從數據庫加載,不從數據庫加載怎麼叫動態

    2.可以根據不同Host(管理者)和Tenant(租戶)加載不同的菜單,不可能管理者和租戶看到的菜單全是一個樣子的吧!

    3.可以根據不同的角色或者用戶加載不同的菜單(這個就牽扯到權限了,比如誰可以看到什麼,不可以看到什麼)

    4.權限、按鈕最好和菜單相綁定,這樣便於控制

    ……

     

    根據以上幾點,我們可以確定

    1.必須要在用戶登錄之後加載出來的菜單才能符合條件

    2.菜單需要建一個表(因為abp默認沒有單獨的菜單表),來進行存放

    3.字段需要包含:菜單名,菜單與權限對應的名稱(用於動態權限),菜單對應的Url,Icon,級聯父Id,是否啟用,排序,租戶Id

    4.需要對菜單進行編輯時,因為牽扯到多租戶,我們需要對多租戶定義一個標準的菜單,在添加租戶時,自動將標準菜單複製保存一份到新租戶中,所以我們需要對於菜單的進行區分,一般來說Host對應的數據行TenantId(int)都為null,我們可以將標準菜單的TenantId標為-1,已經分配保存的菜單TenantId為當前租戶Id,這樣便於區分和查詢

     

    好了,讓我們開始寫動態菜單吧

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

    【其他文章推薦】

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

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

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

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

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

  • Redis的內存和實現機制

    Redis的內存和實現機制

    1. Reids內存的劃分

    1. 數據 內存統計在used_memory中
    2. 進程本身運行需要內存 Redis主進程本身運行需要的內存佔用,代碼、常量池等
    3. 緩衝內存,客戶端緩衝區、複製積壓緩衝區、AOF緩衝區。有jemalloc分配內存,會統計在used_memory中
    4. 內存碎片 Redis在分配、回收物理內存過程中產生的。內存碎片不會統計在used_memory中。如果Redis服務器中的內存碎片已經很大,可以通過安全重啟的方式減小內存碎片:因為重啟之後,Redis重新從備份文件中讀取數據,在內存中進行重排,為每個數據重新選擇合適的內存單元,減小內存碎片。

    2. Redis的數據存儲的細節

    涉及到內存分配器jemalloc, 簡單動態字符串(SDS),5種值類型對象的內部編碼,redisObject,

    1. DictEntry: Redis 是key-value數據庫,因此對每個鍵值對都會有一個dictEntry,裏面存儲了指向Key和Value的指針;next指向下一個dictEntry,與本Key-Value無關
    2. Key: 並不是以字符串存儲,而是存儲在SDS結構中
    3. RedisObject: 5種值對象不是直接以對應的類型存儲的,而是被封裝為redisObject來存儲
    4. jemalloc: 無論是DictEntry對象,還是redisObject, SDS對象,都需要內存分配器

    2.1 Jemalloc

    redis 在編譯時便會指定內存分配器, 內存分配器可以是libc、jemalloc、tcmalloc

    jemalloc作為Redis的默認內存分配器,在減小內存碎片方面做的相對比較好。jemalloc在64位系統中,將內存空間劃分為小、大、巨大三個範圍;每個範圍內又劃分了許多小的內存塊單位;當Redis存儲數據時,會選擇大小最合適的內存塊進行存儲。

    2.2 RedisObject

    redis對象的類型,內部編碼,內存回收,共享對象等功能都需要RedisObject的支持

    typedef struct redisObject{
        unsigned type: 4;
        unsigned encoding: 4;
        unsigned lru: REDIS_LRU_BITS; /*lru time*/
        int refcount;
        void *ptr;
    } robj;
    
    • type 字段 佔4bit 目前有5中類型, REDIS_STRING, REDIS_LIST, REDIS_HASH, REDIS_SET, REDIS_ZSET。 當執行type命令時,便是通過讀取redisObject對象的type字段獲取對象類型

    • encoding 佔4bit (表示對象的內部編碼),對於redis支持的每種類型,都有至少兩種內部編碼。通過object encoding命令,可以查看對象採用的編碼方式

      • 對於字符串,有int, embstr, raw 三種編碼。
      • 對於列表, 有壓縮列表和雙端列表兩種編碼方式,如果列表中元素較少,redis傾向於使用壓縮列表進行存儲,因為壓縮列表內存佔用少,而且比雙端鏈表可以更快載入;當列表對象元素較多時,壓縮列表就會轉化為更適合存儲大量元素的雙端鏈表。
    • lru 不同版本佔用內存大小不一樣,4.0版本佔用24bit,2.6版本佔用22bit

      • 記錄的是對象最後一次被命令程序訪問的時間,通過對比lru時間和當前時間,可以計算某個對象的空轉時間,object idletime命令可以显示該空轉時間 秒級別,改命令並不會改變對象的lru值,lru值除了通過object idletime命令打印之外,還與Redis的內存回收有關係:如果Redis打開了maxmemory選項,且內存回收算法選擇的是volatile-lru或allkeys—lru,那麼當Redis內存佔用超過maxmemory指定的值時,Redis會優先選擇空轉時間最長的對象進行釋放。
    • refcount 共享對象 記錄對象的引用計數,協助內存回收,引用計數可以通過 object refcount命令查看

      • ​ 共享對象的具體實現
      • Redis共享對象目前只支持整數值的對象。實際上是對內存和CPU時間的衡量。共享對象雖然會降低內存消耗,但是判斷兩個對象是否相等時需要消耗時間的。,對於整數值,判斷操作複雜度為O(1);對於普通字符串,判斷複雜度為O(n);而對於哈希、列表、集合和有序集合,判斷的複雜度為O(n^2)。
      • 雖然共享對象只能是整數值的字符串對象,但是5種類型都可能使用共享對象(如哈希、列表等的元素可以使用)。reids服務器在初始化時,會創建10000個字符串對象,值分別是0-9999的整數值。10000這個数字可以通過調整參數REDIS_SHARED_INTEGERS(4.0中是OBJ_SHARED_INTEGERS)的值進行改變
    • ptr 指針指向具體的數據 如 set hello world ptr指向包含字符串world的SDS

    • RedisObject對象大小16字節 4bit+4bit+24bit+4Byte+8Byte=16Byte

    3. Redis內部數據結

    3.1 SDS 簡單動態字符串

    結構

    struct sdshdr {
    	int len;  // 記錄buf數組中已使用字節的數量 等於SDS所保存字符串的長度
        int free;  // 記錄buf數組中未使用的字節數量
        char buf[];
    };
    
    1. SDS結構 佔據的空間:free+len+buf(表示字符串結尾的空字符串), 其中buf=free+len+1. 則總長度為4+4+free+len+1=free+len+9

    2. 與C字符串的比較

      在C字符串的基礎上加入了free和len字段,優勢

      • 獲取字符串長度: SDS O(1), C字符串是O(n)
      • 緩衝區溢出:使用C字符串的API時,如果字符串長度增加(如strcat操作)而忘記重新分配內存,很容易造成緩衝區的溢出;而SDS由於記錄了長度,相應的API在可能造成緩衝區溢出時會自動重新分配內存,杜絕了緩衝區溢出。
      • 修改字符串內存的重分配:對於C字符串,如果要修改字符串,必須要重新分配內存(先釋放再申請),因為如果沒有重新分配,字符串長度增大時會造成內存溢出,字符串長度減小時會造成內存泄漏。對於SDS, 由於記錄了len和free,因此解除了字符串長度和空間數組長度之間的關聯,可以在此基礎上進行優化:空間預分配(分配內存時比實際需要的多)使得字符串長度增大時重新分配內存的概率減小。惰性空間釋放策略 惰性空間釋放用於優化 SDS 的字符串縮短操作: 當 SDS 的 API 需要縮短 SDS 保存的字符串時, 程序並不立即使用內存重分配來回收縮短后多出來的字節, 而是使用 free 屬性將這些字節的數量記錄起來, 並等待將來使用。
      • 二進制安全 C 字符串中的字符必須符合某種編碼(比如 ASCII), 並且除了字符串的末尾之外, 字符串裏面不能包含空字符, 否則最先被程序讀入的空字符將被誤認為是字符串結尾 —— 這些限制使得 C 字符串只能保存文本數據, 而不能保存像圖片、音頻、視頻、壓縮文件這樣的二進制數據。
        SDS 的 API 都是二進制安全的(binary-safe): 所有 SDS API 都會以處理二進制的方式來處理 SDS 存放在 buf 數組裡的數據, 程序不會對其中的數據做任何限制、過濾、或者假設 —— 數據在寫入時是什麼樣的, 它被讀取時就是什麼樣。

      總結:

      • Redis 的字符串表示為 sds ,而不是 C 字符串(以 \0 結尾的 char*)。

      • 對比 C 字符串,sds 有以下特性:
        – 可以高效地執行長度計算(strlen);
        – 可以高效地執行追加操作(append);
        – 二進制安全;

      • sds 會為追加 操作進行優化:加快追加操作的速度,並降低內存分配的次數,代價是多佔用了一些內存,而且這些內存不會被主動釋放。

    3.3 字典

    在Redis中的應用:

    1. 實現數據庫鍵空間(key space) Redis 是一個鍵值對數據庫,數據庫中的鍵值對就由典保存:每個數據庫都有一個與之相對應的字典,這個字典被稱之為鍵空間(key space。
    2. 用作Hash類型鍵的其中一種底層實現

    Redis 的 Hash 類型鍵使用以下兩種數據結構作為底層實現:

    1. 字典;
    2. 壓縮列表

    3.3.1 字典的底層實現

    實現字典的方法有很多種:

    • 最簡單的就是使用鏈表和數組,方式只適用於元素個數不多的情況
    • 兼顧高效和簡單性,使用哈希表
    • 追求更穩定的性能特徵,並且希望高效的實現排序操作,可以是用更為複雜的平衡樹

    Reids選擇了高效且實現簡單的哈希表作為字典的底層實現。

    /* dict.h/dict
    * 字典
    *
    * 每個字典使用兩個哈希表,用於實現漸進式 rehash
    */
    
    typedef struct dict {
        dictType *type;  // 特定於類型的處理函數
        void *privdata;  // 類型處理函數的私有數據
        dictht ht[2];   // 2個哈希表
        
        int rehashidx;  // 記錄rehash 進度的標誌, 值為-1  表示rehash未進行
        
        int iterators;   // 當前正在運作的安全迭代器數量
    } dict;
    

    注: dict類型使用了兩個指針分別指向兩個哈希表

    其中,0號哈希表(ht[0])是字典主要使用的哈希表,而 1號哈希表(ht[1])則只有對0號哈希表進行rehash時才使用。

    3.3.2 哈希表的實現

    /*哈希表*/
    typedef struct dictht {
        dictEntry **table;   // 哈希表節點指針數組(俗稱桶, bucket)
        unsigned long size;  //指針數組的大小
        unsigned long sizemask;   //指針數組的長度掩碼
        unsigned long used;   // 哈希表現有的節點數量
    }dictht;
    
    /*哈希表節點*/
    typedef struct dictEntry {
        void *key;
        union {
            void *val;
            uint64_t u64;
            int64_t s64;
        } v;
        
        // 鏈接後繼系節點
        struct dictEntry *next;
    } dictEntry;
    

    next 屬性指向另一個dictEntry結構, 多個dictEntry 可以通過next指針串連成鏈表dictht使用鏈地址法來處理鍵碰撞;當多個不同鍵擁有相同的哈希值時,哈希表用一個鏈表將這些鍵連接起來。

    3.3.3 哈希碰撞

    在哈希表實現中,當兩個不同的鍵擁有相同的哈希值時,我們稱這兩個鍵發生碰撞(collision),而哈希表實現必須想辦法對碰撞進行處理。字典哈希表所使用的碰撞解決方法被稱之為鏈地址法:這種方法使用鏈表將多個哈希值相同的節點串連在一起,從而解決衝突問題。

    假設現在有一個帶有三個節點的哈希表:

    對於一個新的鍵值對 key4 和 value4 ,如果 key4 的哈希值和 key1 的哈希值相同,那麼它們將在哈希表的 0 號索引上發生碰撞。

    3.2.4 添加新鍵值對時觸發rehash操作?

    對於使用鏈地址法來解決碰撞問題的哈希表 dictht 來說,哈希表的性能依賴於它的大小(size屬性)和它所保存的節點的數量(used 屬性)之間的比率:比率最好在1:1。

    4. 跳躍表

    跳躍表是一種隨機化數據結果,查找、添加、刪除操作都可以在對數期望時間下完成

    跳躍表目前在Redis的唯一作用就是作為有序集類型的底層數據結構之一

    Redis對跳躍表進行了修改包括:

    • score值可重複
    • 對比一個元素需要同時檢查它的score和member
    • 每個節點帶有高度為1層的後退指針,用於從表尾方向向表頭方向迭代

    Redis 為什麼用跳錶而不用平衡樹?

    4.1 skiplist與平衡樹、哈希表的比較

    • skiplist和各種平衡樹(如AVL、紅黑樹等)的元素是有序排列的,而哈希表不是有序的。因此,在哈希表上只能做單個key的查找,不適宜做範圍查找。所謂範圍查找,指的是查找那些大小在指定的兩個值之間的所有節點。
    • 在做範圍查找的時候,平衡樹比skiplist操作要複雜。在平衡樹上,我們找到指定範圍的小值之後,還需要以中序遍歷的順序繼續尋找其它不超過大值的節點。如果不對平衡樹進行一定的改造,這裏的中序遍歷並不容易實現。而在skiplist上進行範圍查找就非常簡單,只需要在找到小值之後,對第1層鏈表進行若干步的遍歷就可以實現。
    • 平衡樹的插入和刪除操作可能引發子樹的調整,邏輯複雜,而skiplist的插入和刪除只需要修改相鄰節點的指針,操作簡單又快速。
    • 從內存佔用上來說,skiplist比平衡樹更靈活一些。一般來說,平衡樹每個節點包含2個指針(分別指向左右子樹),而skiplist每個節點包含的指針數目平均為1/(1-p),具體取決於參數p的大小。如果像Redis里的實現一樣,取p=1/4,那麼平均每個節點包含1.33個指針,比平衡樹更有優勢。
    • 查找單個key,skiplist和平衡樹的時間複雜度都為O(log n),大體相當;而哈希表在保持較低的哈希值衝突概率的前提下,查找時間複雜度接近O(1),性能更高一些。所以我們平常使用的各種Map或dictionary結構,大都是基於哈希表實現的。
    • 從算法實現難度上來比較,skiplist比平衡樹要簡單得多。

    Redis的對象類型和內部編碼

    1. 字符串

    1.1 內部編碼

    • int 8個字節的長整型。字符串值是整型時,這個值使用long整型表示
    • embstr <=39字節的字符串。embstr與raw都使用redisObject和sds保存數據,區別在於,embstr的使用只分配一次內存空間(因此redisObject和sds是連續的),而raw需要分配兩次內存空間(分別為redisObject和sds分配空間)。因此與raw相比,embstr的好處在於創建時少分配一次空間,刪除時少釋放一次空間,以及對象的所有數據連在一起,尋找方便。而embstr的壞處也很明顯,如果字符串的長度增加需要重新分配內存時,整個redisObject和sds都需要重新分配空間,因此redis中的embstr實現為只讀。
    • raw: 大於39個字節的字符串

    1.2 編碼轉換

    新創建的字符串默認使用 REDIS_ENCODING_RAW 編碼,在將字符串作為鍵或者值保存進數據庫時,程序會嘗試將字符串轉為 REDIS_ENCODING_INT 編碼, 字符串的長度不超過512MB

    2. 列表

    創建新列表時Redis默認使用REDIS_ENCODING_ZIPLIST編碼,當一下任意一個條件滿足時,列表會被轉換成REDIS_ENCODING_LINKEDLIST編碼:

    • 試圖往列表新添加一個字符串值,且這個字符串的長度超過sever.list_max_ziplist_value(默認值是64)
    • ziplist 包含的節點超過server.list_max_ziplist_entries(默認的值為512)

    且編碼只可能由壓縮列錶轉化為雙端鏈表,一個列表可以存儲2^32-1個元素

    2.1 壓縮列表

    壓縮列表是Redis為了節約內存而開發的,由一系列特殊編碼的連續內存塊(而不是像雙端鏈表每個節點都是指針) 順序型數據結構;與雙端鏈表相比,壓縮列表可以節省內存空間,但是進行修改或增刪操作時,複雜度較高;因此當節點數量較少時,可以使用壓縮列表;但是節點數量多時,還是使用雙端鏈表划算。因為 ziplist 節約內存的性質,它被哈希鍵、列表鍵和有序集合鍵作為初始化的底層實現來使

    2.2 雙端鏈表

    typedef struct listNode {
        struct listNode *prev;  //前驅節點
        struct listNode *next;  // 後繼節點
        void *value;
    } listNode;
    
    typedef struct list {
        //表頭指針
        listNode *head;
        //表尾指針
        listNode *tail;
        unsigned long len; // 節點長度
        void *(*dup) (void *ptr);
        void (*freee)(void *ptr);
        int (*match) (void *ptr, void *key);
    }list;
    

    小結:

    作為Reids列表的底層實現之一; 作為通用數據結構,被其他功能模塊使用。

    • 節點帶有前驅和後繼指針,訪問前驅節點和後繼節點的複雜度為 O(1) ,並且對鏈表
      的迭代可以在從表頭到表尾和從表尾到表頭兩個方向進行;
    • 鏈錶帶有指向表頭和表尾的指針,因此對錶頭和表尾進行處理的複雜度為 O(1) ;
    • 鏈錶帶有記錄節點數量的屬性,所以可以在 O(1) 複雜度內返回鏈表的節點數量(長
      度);

    3. 哈希表

    • 當哈希表使用字典編碼時,程序將哈希表的鍵(key)保存為字典的鍵,將哈希表的值(value)保存為字典的值, 字典的鍵和值都是字符串對象

    • 壓縮列表編碼的哈希表

    • 編碼轉換

      默認使用ziplist編碼,當滿足以下條件時,自動切換為字典編碼

      • 哈希表中某個鍵或某個值的長度大於sever.hash_max_ziplist_value(默認值是64)
      • ziplist 包含的節點超過server.list_max_ziplist_entries(默認的值為512)

    4. 集合

    第一個添加到集合的元素,決定了創建集合時所使用的編碼:

    • 如果第一個元素可以表示為 long long 類型值(也即是,它是一個整數),那麼集合的初始編碼為 REDIS_ENCODING_INTSET 。
    • 否則,集合的初始編碼為 REDIS_ENCODING_HT 。

    4.1 內部編碼

    當使用 REDIS_ENCODING_HT 編碼時,集合將元素保存到字典的鍵裏面,而字典的值則統一設為 NULL

    如果一個集合使用 REDIS_ENCODING_INTSET 編碼, 當滿足以下條件的時候會轉成字典編碼

    • intset保存的整數值個數超過server.set_max_intset_entries 默認值為512
    • 試圖往集合中添加一個新的元素,這個元素不能被表示為long, long類型,類型不一樣的時候使用字典

    整數集合適用於集合所有元素都是整數且集合元素數量較小的時候,與哈希表相比,整數集合的優勢在於集中存儲,節省空間;同時,雖然對於元素的操作複雜度也由O(1)變為了O(n),但由於集合數量較少,因此操作的時間並沒有明顯劣勢。

    5 .有序集合

    有序集合與集合一樣,元素都不能重複;但與集合不同的是,有序集合中的元素是有順序的。與列表使用索引下標作為排序依據不同,有序集合為每個元素設置一個分數(score)作為排序依據

    5.1 內部編碼

    • 壓縮列表

    • 跳躍表(skiplist)

      跳躍表是一種有序數據結構,通過在每個節點中維持多個指向其他節點的指針,從而達到快速訪問節點的目的。除了跳躍表,實現有序數據結構的另一種典型實現是平衡樹;大多數情況下,跳躍表的效率可以和平衡樹媲美,且跳躍表實現比平衡樹簡單很多,因此redis中選用跳躍表代替平衡樹。跳躍表支持平均O(logN)、最壞O(N)的複雜點進行節點查找,並支持順序操作。Redis的跳躍表實現由zskiplist和zskiplistNode兩個結構組成:前者用於保存跳躍表信息(如頭結點、尾節點、長度等),後者用於表示跳躍表節點

    typedef struct zset {
        dict *dict;
        zskiplist *zsl;
    } zset;
    

    5.2 編碼轉換

    對於一個 REDIS_ENCODING_ZIPLIST 編碼的有序集,只要滿足以下任一條件,就將它轉換為REDIS_ENCODING_SKIPLIST 編碼

    • ziplist所保存的元素數量超過服務器屬性server.zset_max_ziplist_entries值 默認值是128
    • 新添加元素的member的長度大於服務器屬性server.zset_max_ziplist_value 默認值是64

    優化Redis 內存佔用

    1. 利用共享對象,可以減少對象的創建(同時減少了redisObject的創建),節省內存空間。目前redis中的共享對象只包括10000個整數(0-9999);可以通過調整REDIS_SHARED_INTEGERS參數提高共享對象的個數;例如將REDIS_SHARED_INTEGERS調整到20000,則0-19999之間的對象都可以共享。

      考慮這樣一種場景:論壇網站在redis中存儲了每個帖子的瀏覽數,而這些瀏覽數絕大多數分佈在0-20000之間,這時候通過適當增大REDIS_SHARED_INTEGERS參數,便可以利用共享對象節省內存空間

    內存碎片率

    mem_fragmentation_ratio=used_memory_rss (Redis進程佔據操作系統的內存(單位是字節))/ used_memory(Redis分配器分配的內存總量(單位是字節)).

    如果內存碎片率過高(jemalloc在1.03左右比較正常),說明內存碎片多,內存浪費嚴重;這時便可以考慮重啟redis服務,在內存中對數據進行重排,減少內存碎片。

    參考博文與書籍:

    1. 《redis設計與實現》
    2. Redis內存模型
    3. Redis 基礎操作 – 時間複雜度

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

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

  • 手動造輪子——基於.NetCore的RPC框架DotNetCoreRpc

    手動造輪子——基於.NetCore的RPC框架DotNetCoreRpc

    前言

        一直以來對內部服務間使用RPC的方式調用都比較贊同,因為內部間沒有這麼多限制,最簡單明了的方式就是最合適的方式。個人比較喜歡類似Dubbo的那種使用方式,採用和本地方法相同的方式,把接口層獨立出來作為服務契約,為服務端提供服務,客戶端也通過此契約調用服務。.Net平台上類似Dubbo這種相對比較完善的RPC框架還是比較少的,GRPC確實是一款非常優秀的RPC框架,能跨語言調用,但是每次還得編寫proto文件,個人感覺還是比較麻煩的。如今服務拆分,微服務架構比較盛行的潮流下,一個簡單實用的RPC框架確實可以提升很多開發效率。

    簡介

        隨着.Net Core逐漸成熟穩定,為我一直以來想實現的這個目標提供了便利的方式。於是利用閑暇時間本人手寫了一套基於Asp.Net Core的RPC框架,算是實現了一個自己的小目標。大致的實現方式,Server端依賴Asp.Net Core,採用的是中間件的方式攔截處理請求比較方便。Client端可以是任何可承載.Net Core的宿主程序。通信方式是HTTP協議,使用的是HttpClientFactory。至於為什麼使用HttpClientFactory,因為HttpClientFactory可以更輕鬆的實現服務發現,而且可以很好的集成Polly,很方便的實現,超時重試,熔斷降級這些,給開發過程中提供了很多便利。由於本人能力有限,基於這些便利,站在巨人的肩膀上,簡單的實現了一個RPC框架,項目託管在GitHub上https://github.com/softlgl/DotNetCoreRpc有興趣的可以自行查閱。

    開發環境

    • Visual Studio 2019
    • .Net Standard 2.1
    • Asp.Net Core 3.1.x

    使用方式

        打開Visual Studio先新建一個RPC契約接口層,這裏我起的名字叫IRpcService。然後新建一個Client層(可以是任何可承載.Net Core的宿主程序)叫ClientDemo,然後建立一個Server層(必須是Asp.Net Core項目)叫WebDemo,文末附本文Demo連接,建完這些之後項目結構如下:

    Client端配置

    Client端引入DotNetCoreRpc.Client包,並引入自定義的契約接口層

    <PackageReference Include="DotNetCoreRpc.Client" Version="1.0.2" />
    

    然後可以愉快的編碼了,大致編碼如下

    class Program
    {
        static void Main(string[] args)
        {
            IServiceCollection services = new ServiceCollection();
            //*註冊DotNetCoreRpcClient核心服務
            services.AddDotNetCoreRpcClient()
            //*通信是基於HTTP的,內部使用的HttpClientFactory,自行註冊即可
            .AddHttpClient("WebDemo", client => { client.BaseAddress = new Uri("http://localhost:13285/"); });
    
            IServiceProvider serviceProvider = services.BuildServiceProvider();
            //*獲取RpcClient使用這個類創建具體服務代理對象
            RpcClient rpcClient = serviceProvider.GetRequiredService<RpcClient>();
    
            //IPersonService是我引入的服務包interface,需要提供ServiceName,即AddHttpClient的名稱
            IPersonService personService = rpcClient.CreateClient<IPersonService>("WebDemo");
    
            PersonDto personDto = new PersonDto
            {
                Id = 1,
                Name = "yi念之間",
                Address = "中國",
                BirthDay = new DateTime(2000,12,12),
                IsMarried = true,
                Tel = 88888888888
            };
    
            bool addFlag = personService.Add(personDto);
            Console.WriteLine($"添加結果=[{addFlag}]");
    
            var person = personService.Get(personDto.Id);
            Console.WriteLine($"獲取person結果=[{person.ToJson()}]");
    
            var persons = personService.GetAll();
            Console.WriteLine($"獲取persons結果=[{persons.ToList().ToJson()}]");
    
            personService.Delete(person.Id);
            Console.WriteLine($"刪除完成");
    
            Console.ReadLine();
        }
    }
    

    到這裏Client端的代碼就編寫完成了

    Server端配置

    Client端引入DotNetCoreRpc.Client包,並引入自定義的契約接口層

    <PackageReference Include="DotNetCoreRpc.Server" Version="1.0.2" />
    

    然後編寫契約接口實現類,比如我的叫PersonService

    //實現契約接口IPersonService
    public class PersonService:IPersonService
    {
        private readonly ConcurrentDictionary<int, PersonDto> persons = new ConcurrentDictionary<int, PersonDto>();
        public bool Add(PersonDto person)
        {
            return persons.TryAdd(person.Id, person);
        }
    
        public void Delete(int id)
        {
            persons.Remove(id,out PersonDto person);
        }
    
        //自定義Filter
        [CacheFilter(CacheTime = 500)]
        public PersonDto Get(int id)
        {
            return persons.GetValueOrDefault(id);
        }
    
        //自定義Filter
        [CacheFilter(CacheTime = 300)]
        public IEnumerable<PersonDto> GetAll()
        {
            foreach (var item in persons)
            {
                yield return item.Value;
            }
        }
    }
    

    通過上面的代碼可以看出,我自定義了Filter,這裏的Filter並非Asp.Net Core框架定義的Filter,而是DotNetCoreRpc框架定義的Filter,自定義Filter的方式如下

    //*要繼承自抽象類RpcFilterAttribute
    public class CacheFilterAttribute: RpcFilterAttribute
    {
        public int CacheTime { get; set; }
    
        //*支持屬性注入,可以是public或者private
        //*這裏的FromServices並非Asp.Net Core命名空間下的,而是來自DotNetCoreRpc.Core命名空間
        [FromServices]
        private RedisConfigOptions RedisConfig { get; set; }
    
        [FromServices]
        public ILogger<CacheFilterAttribute> Logger { get; set; }
    
        public override async Task InvokeAsync(RpcContext context, RpcRequestDelegate next)
        {
            Logger.LogInformation($"CacheFilterAttribute Begin,CacheTime=[{CacheTime}],Class=[{context.TargetType.FullName}],Method=[{context.Method.Name}],Params=[{JsonConvert.SerializeObject(context.Parameters)}]");
            await next(context);
            Logger.LogInformation($"CacheFilterAttribute End,ReturnValue=[{JsonConvert.SerializeObject(context.ReturnValue)}]");
        }
    }
    

    以上代碼基本上完成了對服務端業務代碼的操作,接下來我們來看如何在Asp.Net Core中配置使用DotNetCoreRpc。打開Startup,配置如下代碼既可

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IPersonService, PersonService>()
            .AddSingleton(new RedisConfigOptions { Address = "127.0.0.1:6379", Db = 10 })
            //*註冊DotNetCoreRpcServer
            .AddDotNetCoreRpcServer(options => {
                //*確保添加的契約服務接口事先已經被註冊到DI容器中
    
                //添加契約接口
                //options.AddService<IPersonService>();
    
                //或添加契約接口名稱以xxx為結尾的
                //options.AddService("*Service");
    
                //或添加具體名稱為xxx的契約接口
                //options.AddService("IPersonService");
    
                //或掃描具體命名空間下的契約接口
                options.AddNameSpace("IRpcService");
    
                //可以添加全局過濾器,實現方式和CacheFilterAttribute一致
                options.AddFilter<LoggerFilterAttribute>();
            });
        }
    
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            //這一堆可以不要+1
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
    
            //添加DotNetCoreRpc中間件既可
            app.UseDotNetCoreRpc();
    
            //這一堆可以不要+2
            app.UseRouting();
    
            //這一堆可以不要+3
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Server Start!");
                });
            });
        }
    }
    

    以上就是Server端簡單的使用和配置,是不是感覺非常的Easy。附上可運行的Demo地址,具體編碼可查看Demo.

    總結

        能自己實現一套RPC框架是我近期以來的一個願望,現在可以說實現了。雖然看起來沒這麼高大上,但是整體還是符合RPC的思想。主要還是想自身實地的實踐一下,順便也希望能給大家提供一些簡單的思路。不是說我說得一定是對的,我講得可能很多是不對的,但是我說的東西都是我自身的體驗和思考,也許能給你帶來一秒鐘、半秒鐘的思考,亦或是哪怕你覺得我哪一句話說的有點道理,能引發你內心的感觸,這就是我做這件事的意義。最後,歡迎大家評論區或本項目GitHub下批評指導。

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

    【其他文章推薦】

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

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

    ※超省錢租車方案

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

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

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

  • 沒有國產主機,怎麼開發:交叉編譯和QEMU虛擬機

    沒有國產主機,怎麼開發:交叉編譯和QEMU虛擬機

    1. 背景

    近期國產化的趨勢越來越濃,包括國產操作系統、國產CPU等。時隔十多年,QQ for Linux也更新了。做為軟件開發人員,“有幸”也需要適配國產化。至於國產化的意義等就不在此討論。

    本文提到的國產主機主要是指使用國產CPU和操作系統的計算機,比如:操作系統是銀河麒麟,CPU是飛騰FT2000。如果需要做適配開發,起碼需要一台對應的主機吧。據說在國產化早期,有錢都難買到機器,需要特殊渠道申請購買。不過,現在購買還是比較方便的。

    通過客戶提供的正規正統的廠家詢價,着實嚇一跳,一台居然要一萬多!!而同等性能配置的windows-x86普通台式主機,才兩三千塊左右,相差有點大呀。本着能省就省的原則,上萬能的某寶看能不能淘一個。真得感謝馬爸爸和深圳華強北,5千多塊,突然感覺肉沒那麼痛了。

    其實完全可以理解,國產的批量肯定很小很小,價格必然是高的。對於不專門開發“國產軟件”的公司來說,買一台使用率比較低的機器不太值得。後面將介紹在沒有國產主機情況下,進行軟件開發的兩種替代方法:交叉編譯和QEMU虛擬機。

    2. 銀河麒麟是什麼

    銀河麒麟操作系統有服務器版本和桌面版本,本文使用的是桌面版本。具體細節看官方的介紹即可,就不做搬運工了。官方說的自主研發、安全可控都不是我們所關心的,我們只需要關心它的內核是什麼,會不會如網上所說根本就是個Ubutun,改個皮膚而已?!。

    先用VMware安裝個虛擬機試試吧,網上找了一個只有X86架構的鏡像包Kylin-4.0.2-desktop-sp2_Community-20171127-x86_64.iso,安裝過程略過,使用命令“uname -a”查一下。

    Linux wrgz-Lenovo 4.11.0-14-generic #20~16.04.1kord0k1-Ubuntu SMP Wed Oct 18 00:56:13 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
    

    看到Ubuntu就放心了,就當它是個Ubuntu Linux就行了。

    3. 飛騰FT2000又是什麼

    通俗點講它就是個CPU,再看看飛騰的官網上的描述。FT-2000系列芯片是基於飛騰片上并行系統(PSoC)體繫結構設計的通用微處理器,兼容ARMv8指令集,兼容支持ARM64和ARM32兩種執行模式。哦嚯,划個重點,簡單點看它就是一個ARMv8的64位CPU。

    划個不考試的重點:對於應用軟件開發者,簡單理解為是在ARMv8架構上的Ubuntu Linux上進行開發軟件;對於普通辦公者,則理解為是仿Windows的Linux系統。

    4. 交叉編譯

    本文提到的軟件開發,是使用C/C++開發無界面的應用軟件,實際上開發和測試都有是可以在Ubuntu上進行。但發布軟件則需要真機編譯或者交叉編譯才能運行。

    很幸運,在上飛騰官網時,發現了飛騰FT2000的技術文檔FT-2000+64Sv1.1.pdf,裏面有介紹到交叉編譯環境。

    • 安裝Ubuntu16.04(可安裝在虛擬機上或 X86電腦裸機上)
    • 安裝成功后,虛擬機 apt 源修改 修改/etc/apt/source.list 內容為如下:
    deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial main restricted
    deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-updates main restricted
    deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial universe
    deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-updates universe
    deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial multiverse
    deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-updates multiverse
    deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-backports main restricted universe > multiverse
    deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-security main restricted
    deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-security universe
    deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu/ xenial-security multiverse
    
    • 運行 apt-get update,再運行apt-get install gcc-aarch64-linux-gnu安裝
    • 使用命令aarch64-linux-gnu-gcc –v可以看到gcc版本號為gcc version 5.4.0 20160609

    有了交叉編譯器,編譯是很輕鬆的事。經後續測試,交叉編譯出來的程序,可以在國產真機上運行。

    5. QEMU虛擬機是什麼

    我們經常使用的虛擬機軟件是VMware,擺着這麼好的不用,為什麼選擇QEMU呢。這得從他們的區別說起。

    VMware重點於在一個硬件平台下運行多個操作系統,虛擬硬件平台與宿主硬件架構一致,也就是說虛擬機程序中的指令一般就是宿主CPU指令集,可以直接執行,因此一般速度上也就比較快。

    QEMU的特點是可以虛擬不同的硬件平台架構,比如在X86機器上虛擬出ARM架構的機器。許多基於ARM指令集的Android手機模擬器是基於Qemu的,很適合無真機情況下進行Android開發。當然執行ARM指令,需要轉換成X86指令才能在宿主機器上運行,這樣速度一般會慢點。

    由於本文提到的國產主機就是ARM架構的,VMware並不適用,而QEMU則符合要求。還有一個原因是QEMU支持Windows,只需要一個安裝包,安裝過程簡單,太香了。

    6. QEMU安裝銀河麒麟操作系統

    無獨有偶,鯤鵬處理器也是ARMv8指令集,在華為官網看到詳細的安裝過程,安裝細節可參考https://www.huaweicloud.com/kunpeng/software/qemu.html。

    下面只針對一些重點關注點做些說明。

    • 需要下載一個Arm64架構的麒麟桌面操作系統鏡像包,名字類似Kylin-4.0.2-desktop-sp3-xxxxxxx-arm64.iso。之所以重點提這點,是因為這種鏡像包在網上很難找。有想到用Arm64架構的Ubuntu鏡像包代替,才發現原來官方並沒有提供ARM桌面版的鏡像包(有ARM服務器版)。
    • 原來華為提供的安裝參數有些問題,包括網絡、鼠標、鍵盤參數。這些參數配置不對,會直接影響使用。

    QEMU有一個不太人性化的特點,就是沒有提供類似VMware的界面操作,只能通過命令操作,參數還特別多,網上的資料不多,官方文檔都有是英文的。下面給出三個重要的QEMU命令:創建、安裝、啟動。

    創建
    這個步驟就是創建一個預分配一個大文件,做為虛擬機的磁盤,我比較任性地分配了40G。

    c:\qemu\qemu-img.exe create D:\qemu\vm\kylin\hdd01.img 40G
    

    安裝

    c:\qemu\qemu-system-aarch64.exe -m 4096 -cpu cortex-a72 -smp 2,cores=2,threads=1,sockets=1 -M virt -bios D:\qemu\bios\QEMU_EFI.fd -net nic,model=pcnet -device nec-usb-xhci -device usb-kbd -device usb-mouse -device VGA -drive if=none,file=D:\software\kylin\Kylin-4.0.2-desktop-sp3-19122616.Z1-arm64.iso,id=cdrom,media=cdrom -device virtio-scsi-device -device scsi-cd,drive=cdrom -drive if=none,file=D:\qemu\vm\kylin\hdd01.img,id=hd0 -device virtio-blk-device,drive=hd0
    

    啟動

    c:\qemu\qemu-system-aarch64.exe -m 4096 -cpu cortex-a72 -smp 2,cores=2,threads=1,sockets=1 -M virt -bios D:\qemu\bios\QEMU_EFI.fd -net nic -net tap,ifname=tap0 -device nec-usb-xhci -device usb-kbd -device usb-mouse -device VGA -device virtio-scsi-device -drive if=none,file=D:\qemu\vm\kylin\hdd01.img,id=hd0 -device virtio-blk-device,drive=hd0
    

    安裝和啟動的命令參數差不多,統一說明它們的含義:

    參數 說明
    qemu-system-aarch64.exe 二進制文件,提供模擬aarch64架構的虛擬機進程
    -m 2048 分配2048MB內存
    -M virt 模擬成什麼服務器,我們一般選擇virt就可以了,他會自動選擇最高版本的virt
    -cpu cortex-a72 模擬成什麼CPU,其中cortex-a53\a57\a72都是ARMv8指令集的
    -smp 2,cores=2,threads=1,sockets=1 2個vCPU,這2個vCPU由qemu模擬出的一個插槽(socket)中的2個核心,每個核心支持一個超線程構成
    -bios xxx 指定bios bin所在的路徑
    -device xxx 添加一個設備,參數可重複
    -drive 添加一個驅動器,參數可重複
    -net 添加網絡設備

    QEMU虛擬機怎麼連網
    在Windows上使用qemu虛擬機,使虛擬機能連網,配置方法如下:

    • 在Windows主機上安裝TAP網卡驅動:可下載openvpn客戶端軟件,只安裝其中的TAP驅動;在網絡連接中,會看到一個新的虛擬網卡,屬性類似於TAP-Windows Adapter V9,將其名稱修改為tap0
    • 將虛擬網卡和Windows上真實網卡橋接:選中這兩塊網卡,右鍵,橋接。此時,Windows主機將不能連接互聯網,需要在網橋上配置IP地址和域名等信息,才能使Windows主機連接互聯網。
    • QEMU參數配置:在虛擬機啟動命令行添加以下參數–net nic -net tap,ifname=tap0;tap0為的虛擬網卡名。

    7. 總結

    國產操作系統的使用體驗已經好了很多,輕度辦公室還是可行的,但想替換Windows,太難了。
    QEMU可以虛擬不同的硬件平台架構,是個不錯的虛擬機軟件,而且開源,但在使用體驗方面還是差了一些。

    歡迎關注我的公眾號【林哥哥的編程札記】,謝謝!

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

  • 寫了個全局變量的bug,被同事們打臉!!!

    寫了個全局變量的bug,被同事們打臉!!!

    話說棧長前陣子寫了一個功能,測試 0 bug 就上線了,上線后也運行好好的,好多天都沒有人反饋bug,超爽。。

    不出問題還好,出問題就是大問題。。

    最近有個客戶反饋某些數據混亂問題,看代碼死活看不出什麼問題,很詭異,再仔細看代碼,原來是一個全局變量的問題,導致在併發情況下出現了線程不安全的問題,事後被同事們打臉!!!

    慎用全局變量,我在公司一直在強調,沒想到這麼低級的問題居然發生在自己身上,說起來真的慚愧啊。。

    最開始使用的是 Spring 注入對象的方式:

    @Autowired
    private Object object;
    

    因為 Spring 默認是單例,所以這樣寫是沒有問題的,後來隨着業務的發展,需要多個不同的業務實例,我改成了這種方式:

    @Setter
    private Object object;
    

    這個 @Setter 是 Lombok 的註解,用來生成 setters 方法,現在想起來,真是低級啊,同時操作的情況下,這個對象肯定會出現覆蓋的情況,從而導致上面說的問題。

    寫了一個這麼低級bug,我也不怕不好意思發出來,大家都謹記一下吧。

    另外,我再總結幾個慎用全局變量的場景:

    1、SimpleDateFormat

    SimpleDateFormat 禁止定義成 static 變量或者全局共享變量,因為它是線程不安全的,都被寫進阿里巴巴的《Java開發手冊》里了:

    為什麼說 SimpleDateFormat 不是線程安全的呢?

    來看下它的 format 方法源碼:

    可以看到 calendar 變量居然也是全局變量,多線程情況下就會存在設置臟變量的情況。

    所以,如果要用 SimpleDateFormat,就在每次用的時候都創建一個 SimpleDateFormat 對象,做到線程間隔離。

    2、資源連接

    資源連接包括數據庫連接、FTP連接、Redis連接等,這種也要慎用全局變量,一量使用全局變量,就會遇到以下問題:

    1)關閉連接的時候,就可能把別人正在操作的連接給關了,導致其他線程的業務中斷;

    2)因為是全局變量,創建的時候可能會創建多個實例,在關閉連接的時候,就可能只關閉了一個對象的連接,造成其他連接沒有被關閉,最後導致連接耗光系統不可用;

    3、数字運算

    這也是個很經典的問題了,如果要用多線程對一個数字進行累加等其他運算處理,千萬不要用全局基礎類型的變量,如下所示:

    private long count;
    

    多線程情況下,某個線程獲取到的值可能已經被其他線程修改了,最後得到的值就不準確了。

    當然,上面的示例可以通過加鎖的方式來解決,也可以使用全局的原子類(java.util.concurrent.atomic.Atom*)進行處理,比如:

    private AtomicInteger count = new AtomicInteger();
    

    注意,這種原子類使用全局變量就沒有線程安全的問題,它使用了 CAS 算法保證了數據一致性。

    不過,阿里推薦使用LongAdder,因為性能更好:

    java.util.concurrent.atomic.LongAdder

    4、全局session

    來看下面的例子:

    @Autowired
    protected HttpSession session;
    

    全局注入一個 Session 對象,在 Spring 中,這樣全局注入使用上面是默認沒問題的,包括 request, response 對象,都可以通過全局注入來獲取。

    這樣會存在線程安全性嗎?

    不會!

    使用這種方式,當 Bean 初始化時,Spring 並沒有注入真實對象,而是注入了一個代理對象,真正使用的時候通過該代理對象獲取真正的對象。

    並且,在注入此類對象時,Spring使用了線程局部變量(ThreadLocal),這就保證了 request/response/session 對象的線程安全性了。

    具體就不展開了,詳細的介紹及測試大家可以點擊這個鏈接查看這篇文章。

    既然是線程安全,但也得小心,如果我在方法中主動使 session 對象失效並重建了:

    session.invalidate();
    session = request.getSession();
    

    這樣,session對象就變成了真實對象了,不再是代理對象,就變成了文章最開始的時候我說的那種多線程安全問題了,如果線上出現 session 會話混亂,用戶 A 就可能看到用戶 B 的數據,你想想可不可怕?

    所以,即使可以這樣使用,也得千萬小心謹慎,最好是在方法級別使用這些對象。

    總結

    今天,棧長總結了一下我是怎麼寫出這個全局變量的低級 bug,也總結了下慎用全局變量的 4 種情況,相信大家多多少都遇到過類似的問題,希望能幫助大家少踩坑。

    全局變量雖好,但我們也得謹慎使用啊,一定要考慮是否引起多線程安全問題,不然會引起重大問題。

    你還遇到過哪些全局變量的問題,歡迎留言分享哦!

    推薦去我的博客閱讀更多:

    1.Java JVM、集合、多線程、新特性系列教程

    2.Spring MVC、Spring Boot、Spring Cloud 系列教程

    3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

    4.Java、後端、架構、阿里巴巴等大廠最新面試題

    覺得不錯,別忘了點贊+轉發哦!

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

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

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

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

  • 印尼醫療廢棄物流入河中 恐成病毒溫床

    摘錄自2020年9月1日自由時報報導

    綜合外媒報導,印尼大量的醫療廢棄物被堆積在坦格朗(Tangerang),當地的垃圾掩埋場5月時崩塌,大量的醫療垃圾流入西薩達河(Cisadane River)中。總長138公里的西薩達河是當地居民重要的水資源,許多民眾會在此處洗澡和洗衣服,河流恐成病毒溫床。

    印尼衛生部指出,3月到6月期間國內共產生1480噸醫療廢棄物。衛生部官員努拉里(Imran Agus Nurali)表示,已通過新的法規,其中包括醫療機構處理相關廢棄物的準則。

    印尼法律健康協會的流行病專家邁爾克(Mahesa Paranadipa Maikel)指出,如果這些醫療廢棄物散佈在河流附近的社區,則會污染當地水源且有傳播武漢肺炎的可能。

    污染治理
    國際新聞
    印尼
    醫療廢棄物
    水污染
    垃圾掩埋場
    動物與大環境變遷

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

    【其他文章推薦】

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

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

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

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

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

  • 北極圈驚見50公尺深「天坑」 專家解謎:甲烷噴炸

    北極圈驚見50公尺深「天坑」 專家解謎:甲烷噴炸

    摘錄自2020年9月1日自由時報報導

    綜合外媒報導,俄羅斯北極圈內凍原帶的夏天近年異常炎熱,導致不斷發生大規模爆炸,近日突然發生一起爆炸,導致炸出一個深約50公尺的超級大洞,當地居民議論紛紛。

    俄羅斯石油天然氣研究所(Russian Oil and Gas Research Institute)教授波哥亞夫蘭斯基(Vasily Bogoyavlensky)表示,爆炸可能是永凍土層慢慢融化後,原本冰封在泥土縫隙中的沼氣不斷釋放、噴發而成,其所引起的爆炸的巨大威力將土壤及冰塊拋到數百公尺外。但哥亞夫蘭斯基也補充,鑽探天然氣也有可能會引發爆炸。

    氣候變遷
    國際新聞
    俄羅斯
    永凍土
    凍原
    北極圈
    甲烷

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

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