分類
發燒車訊

我很驚訝!十幾萬的國產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/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

分類
發燒車訊

【C#】AutoMapper 使用手冊

目錄

  • 1 入門例子
  • 2 註冊
    • 2.1 Profile
  • 3 配置
    • 3.1 命名約定
    • 3.2 配置可見性
    • 3.3 全局屬性/字段過濾
    • 3.4 識別前綴和後綴
    • 3.5 替換字符
  • 4 調用構造函數
  • 5 數組和列表映射
    • 5.1 處理空集合
    • 5.2 集合中的多態
  • 6 方法到屬性映射
  • 7 自定義映射
  • 8 扁平化映射
    • 8.1 IncludeMembers
  • 9 嵌套映射

本文基於 AutoMapper 9.0.0

AutoMapper 是一個對象-對象映射器,可以將一個對象映射到另一個對象。

官網地址:http://automapper.org/

官方文檔:https://docs.automapper.org/en/latest/

1 入門例子

public class Foo
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public class FooDto
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public void Map()
{
    var config = new MapperConfiguration(cfg => cfg.CreateMap<Foo, FooDto>());

    var mapper = config.CreateMapper();

    Foo foo = new Foo { ID = 1, Name = "Tom" };

    FooDto dto = mapper.Map<FooDto>(foo);
}

2 註冊

在使用 Map 方法之前,首先要告訴 AutoMapper 什麼類可以映射到什麼類。

var config = new MapperConfiguration(cfg => cfg.CreateMap<Foo, FooDto>());

每個 AppDomain 只能進行一次配置。這意味着放置配置代碼的最佳位置是在應用程序啟動中,例如 ASP.NET 應用程序的 Global.asax 文件。

從 9.0 開始 Mapper.Initialize 方法就不可用了。

2.1 Profile

Profile 是組織映射的另一種方式。新建一個類,繼承 Profile,並在構造函數中配置映射。

public class EmployeeProfile : Profile
{
    public EmployeeProfile()
    {
        CreateMap<Employee, EmployeeDto>();
    }
}

var config = new MapperConfiguration(cfg =>
{
    cfg.AddProfile<EmployeeProfile>();
});

Profile 內部的配置僅適用於 Profile 內部的映射。應用於根配置的配置適用於所有創建的映射。

AutoMapper 也可以在指定的程序集中掃描從 Profile 繼承的類,並將其添加到配置中。

var config = new MapperConfiguration(cfg =>
{
    // 掃描當前程序集
    cfg.AddMaps(System.AppDomain.CurrentDomain.GetAssemblies());
    
    // 也可以傳程序集名稱(dll 名稱)
    cfg.AddMaps("LibCoreTest");
});

3 配置

3.1 命名約定

默認情況下,AutoMapper 基於相同的字段名映射,並且是 不區分大小寫 的。

但有時,我們需要處理一些特殊的情況。

  • SourceMemberNamingConvention 表示源類型命名規則
  • DestinationMemberNamingConvention 表示目標類型命名規則

LowerUnderscoreNamingConventionPascalCaseNamingConvention 是 AutoMapper 提供的兩個命名規則。前者命名是小寫並包含下劃線,後者就是帕斯卡命名規則(每個單詞的首字母大寫)。

我的理解,如果源類型和目標類型分別採用了 蛇形命名法駝峰命名法,那麼就需要指定命名規則,使其能正確映射。

public class Foo
{
    public int Id { get; set; }

    public string MyName { get; set; }
}

public class FooDto
{
    public int ID { get; set; }

    public string My_Name { get; set; }
}

public void Map()
{
    var config = new MapperConfiguration(cfg =>
    {
        cfg.CreateMap<Foo, FooDto>();

        cfg.SourceMemberNamingConvention = new PascalCaseNamingConvention();
        cfg.DestinationMemberNamingConvention = new LowerUnderscoreNamingConvention();
    });

    var mapper = config.CreateMapper();

    Foo foo = new Foo { Id = 2, MyName = "Tom" };

    FooDto dto = mapper.Map<FooDto>(foo);
}

3.2 配置可見性

默認情況下,AutoMapper 僅映射 public 成員,但其實它是可以映射到 private 屬性的。

var config = new MapperConfiguration(cfg =>
{
    cfg.ShouldMapProperty = p => p.GetMethod.IsPublic || p.SetMethod.IsPrivate;
    cfg.CreateMap<Source, Destination>();
});

需要注意的是,這裏屬性必須添加 private set,省略 set 是不行的。

3.3 全局屬性/字段過濾

默認情況下,AutoMapper 嘗試映射每個公共屬性/字段。以下配置將忽略字段映射。

var config = new MapperConfiguration(cfg =>
{
	cfg.ShouldMapField = fi => false;
});

3.4 識別前綴和後綴

var config = new MapperConfiguration(cfg =>
{
    cfg.RecognizePrefixes("My");
    cfg.RecognizePostfixes("My");
}

3.5 替換字符

var config = new MapperConfiguration(cfg =>
{
    cfg.ReplaceMemberName("Ä", "A");
});

這功能我們基本上用不上。

4 調用構造函數

有些類,屬性的 set 方法是私有的。

public class Commodity
{
    public string Name { get; set; }

    public int Price { get; set; }
}

public class CommodityDto
{
    public string Name { get; }

    public int Price { get; }

    public CommodityDto(string name, int price)
    {
        Name = name;
        Price = price * 2;
    }
}

AutoMapper 會自動找到相應的構造函數調用。如果在構造函數中對參數做一些改變的話,其改變會反應在映射結果中。如上例,映射后 Price 會乘 2。

禁用構造函數映射:

var config = new MapperConfiguration(cfg => cfg.DisableConstructorMapping());

禁用構造函數映射的話,目標類要有一個無參構造函數。

5 數組和列表映射

數組和列表的映射比較簡單,僅需配置元素類型,定義簡單類型如下:

public class Source
{
    public int Value { get; set; }
}

public class Destination
{
    public int Value { get; set; }
}

映射:

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Source, Destination>();
});
IMapper mapper = config.CreateMapper();

var sources = new[]
{
    new Source { Value = 5 },
    new Source { Value = 6 },
    new Source { Value = 7 }
};

IEnumerable<Destination> ienumerableDest = mapper.Map<Source[], IEnumerable<Destination>>(sources);
ICollection<Destination> icollectionDest = mapper.Map<Source[], ICollection<Destination>>(sources);
IList<Destination> ilistDest = mapper.Map<Source[], IList<Destination>>(sources);
List<Destination> listDest = mapper.Map<Source[], List<Destination>>(sources);
Destination[] arrayDest = mapper.Map<Source[], Destination[]>(sources);

具體來說,支持的源集合類型包括:

  • IEnumerable
  • IEnumerable
  • ICollection
  • ICollection
  • IList
  • IList
  • List
  • Arrays

映射到現有集合時,將首先清除目標集合。如果這不是你想要的,請查看AutoMapper.Collection。

5.1 處理空集合

映射集合屬性時,如果源值為 null,則 AutoMapper 會將目標字段映射為空集合,而不是 null。這與 Entity Framework 和 Framework Design Guidelines 的行為一致,認為 C# 引用,數組,List,Collection,Dictionary 和 IEnumerables 永遠不應該為 null

5.2 集合中的多態

這個官方的文檔不是很好理解。我重新舉個例子。實體類如下:

public class Employee
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public class Employee2 : Employee
{
    public string DeptName { get; set; }
}

public class EmployeeDto
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public class EmployeeDto2 : EmployeeDto
{
    public string DeptName { get; set; }
}

數組映射代碼如下:

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Employee, EmployeeDto>().Include<Employee2, EmployeeDto2>();
    cfg.CreateMap<Employee2, EmployeeDto2>();
});
IMapper mapper = config.CreateMapper();

var employees = new[]
{
    new Employee { ID = 1, Name = "Tom" },
    new Employee2 { ID = 2, Name = "Jerry", DeptName = "R & D" }
};

var dto = mapper.Map<Employee[], EmployeeDto[]>(employees);

可以看到,映射后,dto 中兩個元素的類型,一個是 EmployeeDto,一個是 EmployeeDto2,即實現了父類映射到父類,子類映射到子類。

如果去掉 Include 方法,則映射后 dto 中兩個元素的類型均為 EmployeeDto

6 方法到屬性映射

AutoMapper 不僅能實現屬性到屬性映射,還可以實現方法到屬性的映射,並且不需要任何配置,方法名可以和屬性名一致,也可以帶有 Get 前綴。

例如下例的 Employee.GetFullName() 方法,可以映射到 EmployeeDto.FullName 屬性。

public class Employee
{
    public int ID { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string GetFullName()
    {
        return $"{FirstName} {LastName}";
    }
}

public class EmployeeDto
{
    public int ID { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string FullName { get; set; }
}

7 自定義映射

當源類型與目標類型名稱不一致時,或者需要對源數據做一些轉換時,可以用自定義映射。

public class Employee
{
    public int ID { get; set; }

    public string Name { get; set; }

    public DateTime JoinTime { get; set; }
}

public class EmployeeDto
{
    public int EmployeeID { get; set; }

    public string EmployeeName { get; set; }

    public int JoinYear { get; set; }
}

如上例,IDEmployeeID 屬性名不同,JoinTimeJoinYear 不僅屬性名不同,屬性類型也不同。

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Employee, EmployeeDto>()
        .ForMember("EmployeeID", opt => opt.MapFrom(src => src.ID))
        .ForMember(dest => dest.EmployeeName, opt => opt.MapFrom(src => src.Name))
        .ForMember(dest => dest.JoinYear, opt => opt.MapFrom(src => src.JoinTime.Year));
});

8 扁平化映射

對象-對象映射的常見用法之一是將複雜的對象模型並將其展平為更簡單的模型。

public class Employee
{
    public int ID { get; set; }

    public string Name { get; set; }

    public Department Department { get; set; }
}

public class Department
{
    public int ID { get; set; }

    public string Name { get; set; }
}

public class EmployeeDto
{
    public int ID { get; set; }

    public string Name { get; set; }

    public int DepartmentID { get; set; }

    public string DepartmentName { get; set; }
}

如果目標類型上的屬性,與源類型的屬性、方法都對應不上,則 AutoMapper 會將目標成員名按駝峰法拆解成單個單詞,再進行匹配。例如上例中,EmployeeDto.DepartmentID 就對應到了 Employee.Department.ID

8.1 IncludeMembers

如果屬性命名不符合上述的規則,而是像下面這樣:

public class Employee
{
    public int ID { get; set; }

    public string Name { get; set; }

    public Department Department { get; set; }
}

public class Department
{
    public int DepartmentID { get; set; }

    public string DepartmentName { get; set; }
}

public class EmployeeDto
{
    public int ID { get; set; }

    public string Name { get; set; }

    public int DepartmentID { get; set; }

    public string DepartmentName { get; set; }
}

Department 類中的屬性名,直接跟 EmployeeDto 類中的屬性名一致,則可以使用 IncludeMembers 方法指定。

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Employee, EmployeeDto>().IncludeMembers(e => e.Department);
    cfg.CreateMap<Department, EmployeeDto>();
});

9 嵌套映射

有時,我們可能不需要展平。看如下例子:

public class Employee
{
    public int ID { get; set; }

    public string Name { get; set; }

    public int Age { get; set; }

    public Department Department { get; set; }
}

public class Department
{
    public int ID { get; set; }

    public string Name { get; set; }

    public string Heads { get; set; }
}

public class EmployeeDto
{
    public int ID { get; set; }

    public string Name { get; set; }

    public DepartmentDto Department { get; set; }
}

public class DepartmentDto
{
    public int ID { get; set; }

    public string Name { get; set; }
}

我們要將 Employee 映射到 EmployeeDto,並且將 Department 映射到 DepartmentDto

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Employee, EmployeeDto>();
    cfg.CreateMap<Department, DepartmentDto>();
});

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

分類
發燒車訊

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

前言

    一直以來對內部服務間使用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虛擬機

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,被同事們打臉!!!

話說棧長前陣子寫了一個功能,測試 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年8月31日中央社報導

南非為防止武漢肺炎(COVID-19)擴大進行各階段封鎖,知名的克魯格國家公園住宿小屋因長時間無人入住,部分屋舍遭動物佔領與破壞,園方已展開維修,準備重新逐步開放。

武漢肺炎爆發後,南非自3月26日進入最嚴格的第5級全國封鎖,幾個月來因疫情變化逐漸開放,8月18日起改善第2級,國家公園開放旅遊觀光。

新聞網站「時報即時消息」(TimesLIVE)今(31日)引述南非國家公園管理處執行長姆克特尼(Fundisile Mketeni)表示,克魯格國家公園(Kruger National Park)準備重新開放面臨許多挑戰,特別是清潔與維修工作。疫情封鎖期間,部分住宿小屋被靈長類動物、松鼠與蝙蝠佔領和破壞。

姆克特尼指出,已迅速維修,預計9月份會有旅遊高峰。

國際新聞
南非
防疫
國家公園

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

分類
發燒車訊

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

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

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

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

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

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

分類
發燒車訊

全球冰川湖增53% 冰川融化形成湖泊水量增 恐釀洪災

摘錄自2020年9月1日聯合報報導

由於氣候暖化導致冰川融化,自1990年起,全球冰川湖的數量已增加53%。影像顯示,冰川湖數量在1990年至2018年間增長53%,使湖泊覆蓋地球的面積擴大約51%。根據調查,有1萬4394個冰湖散布在近9000平方公里的地球表面上。

科學家分析來自NASA和谷歌地球引擎的衛星影像數據,評估冰川融化後所形成的湖泊如何受到全球暖化和其他過程影響。根據這些數據,研究人員估計,全球冰川湖水量在1990年至2018年間增加48%,目前總計約達156.5立方公里。

對於世界上許多最貧窮的人來說,冰川湖是重要的淡水資源,尤其是在亞洲山區和南美部分地區。但大量冰融水注入湖中會導致下游地區面臨洪水威脅,能摧毀村莊、沖刷道路、破壞管線和其他基礎設施。

氣候變遷
國際新聞
全球
冰川融化
全球暖化
洪水

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

分類
發燒車訊

日貨輪觸礁漏油毀生態 模里西斯擬求償9.9億

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

日本大型貨輪「若潮號」(WAKASHIO)7月在模里西斯近海觸礁,超過千噸燃油外洩造成當地生態浩劫,模里西斯政府計畫求償13.4億模里西斯盧比(約新台幣9.95億元)。另外,協助清理漏油的拖船昨晚(31日)撞上駁船,造成2死2失蹤。

綜合外媒報導,日本商船三井集團營運的巴拿馬籍大型散貨輪「若潮號」7月25日觸礁時,載有約3800噸燃油,據悉,有1000多噸燃油外洩海上,被污染的沿海地區已禁止捕魚,模里西斯政府也在8月7日宣布進入環境緊急狀態,擔心瀕物種恐受到影響。

根據模里西斯政府文件顯示的擬議計畫,當局將向日方求償13.4億模里西斯盧比,這筆錢將用來支持受到漏油影響的當地漁民社區,其中12億盧比用於建造100艘漁船、970萬盧比用於為475名漁民和60名船長提供培訓。

污染治理
國際新聞
模里西斯
燃油外洩
漏油事件
觸礁

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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