Spring Boot從入門到實戰:整合通用Mapper簡化單表操作

數據庫訪問是web應用必不可少的部分。現今最常用的數據庫ORM框架有Hibernate與Mybatis,Hibernate貌似在傳統IT企業用的較多,而Mybatis則在互聯網企業應用較多。通用Mapper(https://github.com/abel533/Mapper) 是一個基於Mybatis,將單表的增刪改查通過通用方法實現,來減少SQL編寫的開源框架,且也有對應開源的mapper-spring-boot-starter提供。我們在此基礎上加了一些定製化的內容,以便達到更大程度的復用。

框架源碼地址:https://github.com/ronwxy/base-spring-boot (持續更新完善中,歡迎follow,star)
Demo源碼地址:https://github.com/ronwxy/springboot-demos/tree/master/springboot-tkmapper

在開源mapper-spring-boot-starter的基礎上,增加了如下內容:

  1. 針對MySQL數據庫與PostgreSQL數據庫添加了一些Java類型與數據庫類型的轉換處理類,如將List、Map類型與MySQL數據庫的json類型進行轉換處理
  2. 對Domain、Mapper、Service、Controller各層進行了封裝,將基本的增刪改查功能在各層通用化
  3. 提供了基於druid連接池的自動配置
  4. 其它一些調整,如默認映射複雜類型屬性(主要是List、Map類型,其它自定義類型需要自定義轉換處理類),將枚舉作為簡單類型處理
  5. 提供了一個parent項目,將一些常用的框架進行集成,實際項目可繼承parent簡化依賴配置(持續更新完善)

該框架可用於實際基於springboot的項目,只需簡單配置數據源,即可引入druid連接池及通用mapper的功能,以及各層基本的增刪改查方法。

如何使用?
下文給出使用步驟,可參考示例:https://github.com/ronwxy/springboot-demos/tree/master/springboot-tkmapper

1. 框架Maven部署安裝

下載框架源碼后,在項目根路徑下執行mvn clean install可安裝到本地maven庫。如果需要共享,且搭了Nexus私服,則在根路徑pom.xml文件中添加distributionManagement配置,指定Nexus倉庫分發地址,使用mvn clean deploy安裝到遠程maven倉庫,如

<distributionManagement>
     <repository>
         <id>nexus-releases</id>
         <url>
             http://ip:port/repository/maven-releases/
         </url>
     </repository>
     <snapshotRepository>
         <id>nexus-snapshots</id>
         <url>
             http://ip:port/repository/maven-snapshots/
         </url>
     </snapshotRepository>
 </distributionManagement>

 

上述指定的repository需要在maven的全部配置文件settings.xml中有對應賬號配置(id需要一一對應),如 

 <servers>
   <server>
     <id>nexus-snapshots</id>
     <username>admin</username>
     <password>xxx</password>
   </server>
<server>
     <id>nexus-releases</id>
     <username>admin</username>
     <password>xxx</password>
   </server>
 </servers>

 

2. pom.xml配置

項目中引入該數據庫框架有三種方式:

  1. 直接引入 cn.jboost.springboot:tkmapper-spring-boot-starter(沒有連接池)
  2. 直接引入 cn.jboost.springboot:druid-spring-boot-starter(druid連接池支持)
  3. 項目繼承 cn.jboost.springboot:spring-boot-parent(使用的是druid連接池)

三種方式的pom.xml配置如下

#第一種方式
<dependency>
   <groupId>cn.jboost.springboot</groupId>
   <artifactId>tkmapper-spring-boot-starter</artifactId>
   <version>1.2-SNAPSHOT</version>
</dependency>

#第二種方式
<dependency>
   <groupId>cn.jboost.springboot</groupId>
   <artifactId>druid-spring-boot-starter</artifactId>
   <version>1.2-SNAPSHOT</version>
</dependency>

#第三種方式
<parent>
   <groupId>cn.jboost.springboot</groupId>
   <artifactId>spring-boot-parent</artifactId>
   <version>1.2-SNAPSHOT</version>
   <relativePath/> <!-- lookup parent from repository -->
</parent>

 

根據情況引入mysql或postgresql的驅動依賴(其它數據庫暫未做類型轉換支持,未作測試)

 

3. 配置數據源

如果使用druid連接池,則在application.yml配置文件中,加入如下數據源配置(推薦)

spring:
  datasource:
    druid:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf-8
      username: root
      password:
      # 自定義配置
      initialSize: 2  # 初始化大小
      minIdle: 1   # 最小連接
      maxActive: 5 # 最大連接
      druidServletSettings:
        allow: 127.0.0.1
        deny:
        loginUsername: admin
        loginPassword: Passw0rd
        resetEnable: true
      druidFilterSettings:
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
      maxWait: 60000   # 配置獲取連接等待超時的時間
      timeBetweenEvictionRunsMillis: 60000 # 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒
      minEvictableIdleTimeMillis: 300000 # 配置一個連接在池中最小生存的時間,單位是毫秒
      validationQuery: SELECT 'x'
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true # 打開PSCache,並且指定每個連接上PSCache的大小
      maxPoolPreparedStatementPerConnectionSize: 20
      filters: stat #,wall(添加wall代碼里不能直接拼接sql,druid有sql注入校驗) # 配置監控統計攔截的filters,去掉后監控界面sql無法統計,'wall'用於防火牆
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
      useGlobalDataSourceStat: true # 合併多個DruidDataSource的監控數據

 

如果不使用連接池,則配置相對簡單,如下 

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf-8
    username: root
    password:
    driver-class-name: com.mysql.jdbc.Driver

 

4. 定義相應domain,mapper,service,controller各層對象 

以demo為例(demo數據庫腳本見resources/schema.sql),domain定義一個User類,

@Table(name = "user")
@Getter
@Setter
@ToString
public class User extends AutoIncrementKeyBaseDomain<Integer> {
    private String name;
    @ColumnType(jdbcType = JdbcType.CHAR)
    private Gender gender;
    private List<String> favor;
    private Map<String, String> address;

    public enum Gender{
        M,
        F
    }
}

 

需要添加@Table註解指定數據庫表名,可通過繼承AutoIncrementKeyBaseDomain來實現自增主鍵,或UUIDKeyBaseDomain來實現UUID主鍵,如果自定義其它類型主鍵,則繼承BaseDomain。 

該框架Service層通用方法實現BaseService只支持單列主鍵,不支持組合主鍵(也不建議使用組合主鍵)

框架默認對List、Map等複雜類型屬性會映射到mysql的json類型或postgresql的jsonb類型,如果某個屬性不需要映射,可添加@Transient註解;枚舉類型需添加@ColumnType指定jdbcType。

dao層定義UserMapper

@Repository
public interface UserMapper extends BaseMapper<User> {
}

 

BaseMapper默認實現了單表的增刪改查及批量插入等功能,如需定義複雜查詢,可在該接口中定義,然後通過mapper xml文件編寫實現。 

service層定義 UserService,繼承了BaseService的通用功能(具體可查看源碼),同樣可在該類中自定義方法

@Service
public class UserService extends BaseService<Integer, User> {

    @Transactional
    public void createWithTransaction(User user){
        create(user);
        //用於測試事務
        throw new RuntimeException("拋出異常,讓前面的數據庫操作回滾");
    }
}

 

controller層定義 UserController,繼承了BaseController的通用接口(具體可查看源碼) 

@RestController
@RequestMapping("/user")
public class UserController extends BaseController<Integer, User> {
}

 

如上,只需要定義各層對應的接口或類,繼承基礎接口或類,便完成了用戶基本的增刪改查功能,不需要寫一行具體的實現代碼。 

5. 測試、運行

  1. 示例中提供了兩個新建用戶的單元測試,參考SpringbootTkmapperApplicationTests

  2. 運行,在主類上直接運行,然後瀏覽器里打開 http://localhost:8080/user 則可列出單元測試中創建的用戶(其它接口參考BaseController實現)

6. 總結

本文介紹框架基於tk.mybatis:mapper-spring-boot-starter做了一些自定義擴展,以更大程度地實現復用。可用於實際項目開發,使用過程中如果遇到問題,可關注公眾號留言反饋。

我的個人博客地址:http://blog.jboost.cn
我的頭條空間: https://www.toutiao.com/c/user/5833678517/#mid=1636101215791112
我的github地址:https://github.com/ronwxy
我的微信公眾號:jboost-ksxy

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

歡迎關注我的微信公眾號,及時獲取最新分享

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

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

Trie|如何用字典樹實現搜索引擎的關鍵詞提示功能

Trie字典樹

Trie字典樹又稱前綴樹,顧名思義,是查詢前綴匹配的一種樹形數據結構

可以分為插入(創建) 和 查詢兩部分。參考地址極客時間

下圖為插入字符串的過程:

創建完成后,每個字符串最後一個字母標記為終結點(圖中显示為紅色)

下圖為查詢字符串:“her”的過程:綠色箭頭表示查詢路徑
我們將要查找的字符串分割成單個的字符 h,e,r,一個一個查詢

下圖為查詢字符串:“he”的過程:綠色箭頭表示查詢路徑
因為‘e’不是終結點,所以不能完全匹配上。

Trie字典樹的實現

1.首先是字典樹 數據結構定義的代碼實現

樹形結構,類比於二叉樹的存儲嘛,每個結點兩條分支(二叉樹);
而字典樹,每個節點可以最多有 26個分支(存儲英文字母)。

1-1二維數組存儲字母

int trie[MAX_NODE][26];//MAX_NODE表示結點數量,每個結點有26個字母結點
int k;

MAX_NODE表示結點數量,每個結點有26個字母結點
Trie[i][j]的值是0,表示trie樹中i號節點,並沒有一條連出去的邊滿足邊上的字符標識是字符集中第j個字符(從0開始);
trie[i][j]的值是正整數x表示trie樹中i號節點,有一條連出去的邊滿足邊上的字符標識是字符集中第j個字符,並且
這條邊的終點是x號節點。

1-2鏈表
我這裏用C++中的vector實現,

vector< pair<char, int> > trie[MAX_NODE];
int k;

也可以寫一個真正的鏈表,包含二元組字段<char,int>型的對應關係

1-3hash,

map<char, int> trie[MAX_NODE];

每次我們想找i號節點有沒有標識
是某個字符ch的邊時,只要看trie[i][ch]的值即可
但是實際上map時空複雜度的常數都比較大

2.插入 和 查詢 兩個函數的代碼實現

插入 查詢 實際上是類似的,就是從樹的根開始往下遍歷,

2-1插入:從樹的根開始往下遍歷,到達一個結點,沒有這個字母就插入到這個結點下,作為這個結點的子節點

基於二維數組結構的插入功能實現

代碼的第6~8行,一開始trie[][]被初始化為0,保證每個節點被創建出來時,都沒有子節點。K初
始化為1表示一開始只有1個節點,也就是0號節點根節點。Color是用來標記一個節點是不是終結
點。Color[i]=1標識i號節點是終結點。
第9~21行是插入函數insert(w),w是字符指針,實際上可以看作是一個字符串。
第11行是p從0號節點開始。
第12~19行是依次插入w的每一個字符。
第13行是計算w[i]是字符集第幾個字符,這裏我們假設字符集只包含26個小寫字母。
第14~17行是如果p沒有連出標識是w[i]的邊,那麼就創建一個。這裏新創建的節點一定就是k號節
點。所謂創建新節點實際上也沒什麼可創建的,新節點就是個編號。所以我們直接令trie[i][c]=k
即可,然後將k累加1,整個創建過程就完成了。
第18行是沿着標記着w[i]的邊移動到下一個節點。
最後第20行,是將最後到達的節點p標記為終結點。

2-2查詢:從樹的根開始往下遍歷,查看是否匹配上當前正在查的單詞
基於二維數組結構的查詢功能實現

第24行是從p=0也就是根節點開始。
第25~29行是枚舉s的每一個字符。
第26行是計算當前字符s[i]在字符集的序號。
第27行是判斷p節點有沒有連出標識s[i]字符的邊,如果沒有,說明現在無路可走,直接返回0;如
果有的話,
第28行就是移動到下一個節點。如果整個循環結束還沒有return 0,那就說明成功沿着s的每一個
字符到達了p節點。這時只要判斷p節點是不是終結點即可,也就是第30行的代

3.完整代碼C++版

public class Trie {
  private TrieNode root = new TrieNode('/'); // 存儲無意義字符

  // 往 Trie 樹中插入一個字符串
  public void insert(char[] text) {
    TrieNode p = root;
    for (int i = 0; i < text.length; ++i) {
      int index = text[i] - 'a';
      if (p.children[index] == null) {
        TrieNode newNode = new TrieNode(text[i]);
        p.children[index] = newNode;
      }
      p = p.children[index];
    }
    p.isEndingChar = true;
  }

  // 在 Trie 樹中查找一個字符串
  public boolean find(char[] pattern) {
    TrieNode p = root;
    for (int i = 0; i < pattern.length; ++i) {
      int index = pattern[i] - 'a';
      if (p.children[index] == null) {
        return false; // 不存在 pattern
      }
      p = p.children[index];
    }
    if (p.isEndingChar == false) return false; // 不能完全匹配,只是前綴
    else return true; // 找到 pattern
  }

  public class TrieNode {
    public char data;
    public TrieNode[] children = new TrieNode[26];
    public boolean isEndingChar = false;
    public TrieNode(char data) {
      this.data = data;
    }
  }
}

Trie字典樹的時間複雜度 與 缺點

插入的時間複雜度:O(N),N為所有待插入字符串的長度之和
查詢的時間複雜度:O(K),K為待查詢字符串的長度

占內存:如果用二維數組實現,每個節點就會額外需要 26*8=208 個字節
優化思路:將每個節點中的數組換成其他數據結構,比如有序數組(可以二分查找)、跳錶、散列表、紅黑樹等。

Trie變體,縮點優化:對只有一個子節點的節點,而且此節點不是一個串的結束節點,可以將此節點與子節點合併

Trie字典樹的實際應用

1.搜索引擎輸入框關鍵詞提示

因為字典樹是查找 “與前綴匹配的字符串”,又稱為前綴樹。
關鍵詞提示就是 查尋找前綴匹配的前綴合適關鍵詞,當然還有更複雜的關鍵詞排名問題,這裏不再展開。

2.自動補全功能,如:IDE編譯器自動補全,輸入法自動補全等

原理與搜索引擎類似。

3.敏感詞過濾系統

4.其它

Trie在面試與算法競賽中的例題

1.hihoCoder1014

hihoCoder1014

解題思路:Trie字典樹

首先我們把集合中的N個字符串都插入到trie中。
對於每一個查詢s我們在trie中查找s,如果查找過程中無路可走,那麼一定沒有以s為前綴的字符串。
如果最後停在一個節點p,那我們就要看看以p為根的子樹里一共有多少終結點。
終結點的數目就是答案。

但是如果我們每次都遍歷以P為根的子樹,那時間複雜度就太高了。解決的辦法是用空間換時間,我們增加一個數組intcnt[MAX_NODE]
cnt[i]記錄的是以i號節點為根的子樹中,有幾個終結點。
然後我們每次insert一個字符串的時候,順便就把沿途的節點的cnt值都+1。
這樣就不用每次遍歷以P為根的子樹,而是直接輸出cnt[P]即可。

代碼:

2.hihoCoder1107微軟面試題

hihoCoder1014

其實就是找一個節點p,滿足以p為根的子樹中的終結點不多於5個,同時以p的父節點為根的子樹中的終結點大於5個。
和上題一樣用cnt數組標記,之後dfs查找終結點的數目

3.Trie應用在整數xor異或值最大的題目

給定一個包含N個整數的集合S={A1, A2, A3, … AN}。然
後有M個詢問,每次詢問給定一個整數X,讓你找一個Ai使得Ai xor X的值最大。

首先我們知道一個整數可以用二進製表示成一個01串。比如3=(011)2, 5=(101)2, 4=(100)2……。
我們假設輸入的整數都在0~2^32-1之間,於是我們可以用一個長度是32位的01串表示一個整數。
然後對於給定的N個整數A1, A2, A3, … AN,我們把它們對應的01串都插入到一個trie中。注意這裏字符集只有0和1,所以整個trie是一棵二叉樹。

下面我們舉一個例子,為了描述方便,我們假設整數都在0~7之間,也就是可以用3位01串表示。
現在假設S={1, 2, 7},也就是說我們要在Trie中插入{001, 010, 111}:

這時假設我們要查詢x=4,也就是哪個數和4異或結果最大?4=(100)2,
我們的做法是在trie樹中,盡量與4的二進制位反着走。
比如4的第一位(最高位)是1,我們從0出發第一步就盡量沿着0走。因為我們要異或和最大,01相反才能異或值是1。
並且這一步是可以貪心的,也就是說如果有相反的邊,那麼我們一定沿着這條邊走。因為最高位異或得1的話,即便後面都是0, 10000…000也要比最高位是0,後面都是1的011111…111大。
所以我們第一步沿着標識是0的邊,移動到了1號節點;4第二位是0,所以我們沿着標識是1的邊移動到4號節點;
4的第三位是0,但是4號節點沒有標識是1的邊,所以我們也只好沿着標識是0的邊移動到5號節點。
已經到了終結點,所以5號節點對應的A2=(010)2=2就是我們要求的答案,A2 xor 4 = 6是最大的。

【精選推薦文章】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

ApplicationContextRunner如何簡化自動配置測試

 

1. 概覽

眾所周知,自動配置是Spring Boot的關鍵功能之一, 但測試自動配置可能會很棘手。

在以下部分中,我們將展示ApplicationContextRunner如何簡化自動配置測試。

2. 測試自動化配置方案

ApplicationContextRunner是一個實用程序類,它運行ApplicationContext並提供AssertJ樣式斷言。 最好用作測試類中的字段以便共享配置,然後我們在每個測試中進行自定義:

private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();

讓我們通過測試一些案例來展示它的魔力。

2.1. 測試Class Condition

在本節中,我們將測試一些使用@ConditionalOnClass和@ConditionalOnMissingClass 註解的自動配置類:

@Configuration
@ConditionalOnClass(ConditionalOnClassIntegrationTest.class)
protected static class ConditionalOnClassConfiguration {
    @Bean
    public String created() { return "This is created when ConditionalOnClassIntegrationTest is present on the classpath"; } } @Configuration @ConditionalOnMissingClass("com.baeldung.autoconfiguration.ConditionalOnClassIntegrationTest") protected static class ConditionalOnMissingClassConfiguration { @Bean public String missed() { return "This is missed when ConditionalOnClassIntegrationTest is present on the classpath"; } } 

我們想測試自動配置是否正確實例化或跳過createdmissing beans給定的預期條件。

  • ApplicationContextRunner為我們提供了withUserConfiguration方法,我們可以根據需要提供自動配置,以便為每個測試自定義ApplicationContext

  • run 方法將 ContextConsumer 作為將斷言應用於上下文的參數。 測試退出時,ApplicationContext將自動關閉:

@Test
public void whenDependentClassIsPresent_thenBeanCreated() {     this.contextRunner.withUserConfiguration(ConditionalOnClassConfiguration.class)       .run(context -> {         assertThat(context).hasBean("created");         assertThat(context.getBean("created"))           .isEqualTo("This is created when ConditionalOnClassIntegrationTest is present on the classpath");       }); }   @Test public void whenDependentClassIsPresent_thenBeanMissing() {     this.contextRunner.withUserConfiguration(ConditionalOnMissingClassConfiguration.class)         .run(context -> {             assertThat(context).doesNotHaveBean("missed");         }); } 

通過前面的示例,我們發現測試classpath上存在某個類的場景的簡單性。但是,當類不在classpath上時,我們如何測試相反的情況呢

這就是FilteredClassLoader發揮作用的地方。它用於在運行時過濾classpath上指定的類:

@Test
public void whenDependentClassIsNotPresent_thenBeanMissing() {     this.contextRunner.withUserConfiguration(ConditionalOnClassConfiguration.class)         .withClassLoader(new FilteredClassLoader(ConditionalOnClassIntegrationTest.class))         .run((context) -> {             assertThat(context).doesNotHaveBean("created");             assertThat(context).doesNotHaveBean(ConditionalOnClassIntegrationTest.class);         }); }   @Test public void whenDependentClassIsNotPresent_thenBeanCreated() {     this.contextRunner.withUserConfiguration(ConditionalOnMissingClassConfiguration.class)       .withClassLoader(new FilteredClassLoader(ConditionalOnClassIntegrationTest.class))       .run((context) -> {         assertThat(context).hasBean("missed");         assertThat(context).getBean("missed")           .isEqualTo("This is missed when ConditionalOnClassIntegrationTest is present on the classpath");         assertThat(context).doesNotHaveBean(ConditionalOnClassIntegrationTest.class);       }); } 

2.2. 測試 Bean Condition

我們剛剛測試了 @ConditionalOnClass 和 @ConditionalOnMissingClass 註解, 現在 讓我們看看使用@ConditionalOnBean和@ConditionalOnMissingBean註釋時的情況。

首先, 我們同樣需要 一些自動配置的類:

@Configuration
protected static class BasicConfiguration {
    @Bean
    public String created() {         return "This is always created";     } } @Configuration @ConditionalOnBean(name = "created") protected static class ConditionalOnBeanConfiguration {     @Bean     public String createOnBean() {         return "This is created when bean (name=created) is present";     } } @Configuration @ConditionalOnMissingBean(name = "created") protected static class ConditionalOnMissingBeanConfiguration {     @Bean     public String createOnMissingBean() {         return "This is created when bean (name=created) is missing";     } } 

然後,我們將像上一節一樣調用withUserConfiguration方法,然後發送我們的自定義配置類來測試自動配置是否在不同的條件下恰當地實例化bean或跳過createOnBeancreateOnMissingBean :

@Test
public void whenDependentBeanIsPresent_thenConditionalBeanCreated() {     this.contextRunner.withUserConfiguration(BasicConfiguration.class,       ConditionalOnBeanConfiguration.class)     // ommitted for brevity } @Test public void whenDependentBeanIsNotPresent_thenConditionalMissingBeanCreated() {     this.contextRunner.withUserConfiguration(ConditionalOnMissingBeanConfiguration.class)     // ommitted for brevity } 

2.3. 測試 Property Condition

在本節中,我們測試使用 @ConditionalOnPropertyannotations的自動配置類。

首先,我們需要這個測試的屬性:

com.baeldung.service=custom

然後,我們編寫嵌套的自動配置類,根據前面的屬性創建bean:

@Configuration
@TestPropertySource("classpath:ConditionalOnPropertyTest.properties") protected static class SimpleServiceConfiguration {     @Bean     @ConditionalOnProperty(name = "com.baeldung.service", havingValue = "default")     @ConditionalOnMissingBean     public DefaultService defaultService() {         return new DefaultService();     }     @Bean @ConditionalOnProperty(name = "com.baeldung.service", havingValue = "custom") @ConditionalOnMissingBean public CustomService customService() { return new CustomService(); } } 

現在,我們調用withPropertyValues方法來覆蓋每個測試中的屬性值:

@Test
public void whenGivenCustomPropertyValue_thenCustomServiceCreated() { this.contextRunner.withPropertyValues("com.baeldung.service=custom") .withUserConfiguration(SimpleServiceConfiguration.class) .run(context -> { assertThat(context).hasBean("customService"); SimpleService simpleService = context.getBean(CustomService.class); assertThat(simpleService.serve()).isEqualTo("Custom Service"); assertThat(context).doesNotHaveBean("defaultService"); }); } @Test public void whenGivenDefaultPropertyValue_thenDefaultServiceCreated() { this.contextRunner.withPropertyValues("com.baeldung.service=default") .withUserConfiguration(SimpleServiceConfiguration.class) .run(context -> { assertThat(context).hasBean("defaultService"); SimpleService simpleService = context.getBean(DefaultService.class); assertThat(simpleService.serve()).isEqualTo("Default Service"); assertThat(context).doesNotHaveBean("customService"); }); } 

3. 結論

總結一下, 這篇教程主要展示 如何使用ApplicationContextRunner運行帶有自定義的ApplicationContext並應用斷言.

我們在這裏介紹了最常用的場景,而不是列出如何自定義ApplicationContext 。

在此期間,請記住ApplicationConetxtRunner適用於非Web應用程序,因此請考慮WebApplicationContextRunner用於基於servlet的Web應用程序,ReactiveWebApplicationContextRunner用於響應式Web應用程序。

本文源代碼,請訪問GitHub。

原文:www.baeldung.com/spring-boot…

作者:baeldung

譯者:Leesen

 

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

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

為什麼阿里巴巴要求謹慎使用ArrayList中的subList方法

GitHub 3.7k Star 的Java工程師成神之路 ,不來了解一下嗎?

GitHub 3.7k Star 的Java工程師成神之路 ,真的不來了解一下嗎?

GitHub 3.7k Star 的Java工程師成神之路 ,真的確定不來了解一下嗎?

集合是Java開發日常開發中經常會使用到的。在之前的一些文章中,我們介紹過一些關於使用集合類應該注意的事項,如《為什麼阿里巴巴禁止在 foreach 循環里進行元素的 remove/add 操作》、《為什麼阿里巴巴建議集合初始化時,指定集合容量大小》等。

關於集合類,《阿里巴巴Java開發手冊》中其實還有另外一個規定:

本文就來分析一下為什麼會有如此建議?其背後的原理是什麼?

subList

subList是List接口中定義的一個方法,該方法主要用於返回一個集合中的一段、可以理解為截取一個集合中的部分元素,他的返回值也是一個List。

如以下代碼:

public static void main(String[] args) {
    List<String> names = new ArrayList<String>() {{
        add("Hollis");
        add("hollischuang");
        add("H");
    }};

    List subList = names.subList(0, 1);
    System.out.println(subList);
}

以上代碼輸出結果為:

[Hollis]

如果我們改動下代碼,將subList的返回值強轉成ArrayList試一下:

public static void main(String[] args) {
    List<String> names = new ArrayList<String>() {{
        add("Hollis");
        add("hollischuang");
        add("H");
    }};

    ArrayList subList = names.subList(0, 1);
    System.out.println(subList);
}

以上代碼將拋出異常:

java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList

不只是強轉成ArrayList會報錯,強轉成LinkedList、Vector等List的實現類同樣也都會報錯。

那麼,為什麼會發生這樣的報錯呢?我們接下來深入分析一下。

底層原理

首先,我們看下subList方法給我們返回的List到底是個什麼東西,這一點在JDK源碼中註釋是這樣說的:

Returns a view of the portion of this list between the specifiedfromIndex, inclusive, and toIndex, exclusive.

也就是說subList 返回是一個視圖,那麼什麼叫做視圖呢?

我們看下subList的源碼:

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

這個方法返回了一個SubList,這個類是ArrayList中的一個內部類。

SubList這個類中單獨定義了set、get、size、add、remove等方法。

當我們調用subList方法的時候,會通過調用SubList的構造函數創建一個SubList,那麼看下這個構造函數做了哪些事情:

SubList(AbstractList<E> parent,
            int offset, int fromIndex, int toIndex) {
    this.parent = parent;
    this.parentOffset = fromIndex;
    this.offset = offset + fromIndex;
    this.size = toIndex - fromIndex;
    this.modCount = ArrayList.this.modCount;
}

可以看到,這個構造函數中把原來的List以及該List中的部分屬性直接賦值給自己的一些屬性了。

也就是說,SubList並沒有重新創建一個List,而是直接引用了原有的List(返回了父類的視圖),只是指定了一下他要使用的元素的範圍而已(從fromIndex(包含),到toIndex(不包含))。

所以,為什麼不能講subList方法得到的集合直接轉換成ArrayList呢?因為SubList只是ArrayList的內部類,他們之間並沒有集成關係,故無法直接進行強制類型轉換。

視圖有什麼問題

前面通過查看源碼,我們知道,subList()方法並沒有重新創建一個ArrayList,而是返回了一個ArrayList的內部類——SubList。

這個SubList是ArrayList的一個視圖。

那麼,這個視圖又會帶來什麼問題呢?我們需要簡單寫幾段代碼看一下。

1、非結構性改變SubList

public static void main(String[] args) {
    List<String> sourceList = new ArrayList<String>() {{
        add("H");
        add("O");
        add("L");
        add("L");
        add("I");
        add("S");
    }};

    List subList = sourceList.subList(2, 5);

    System.out.println("sourceList : " + sourceList);
    System.out.println("sourceList.subList(2, 5) 得到List :");
    System.out.println("subList : " + subList);

    subList.set(1, "666");

    System.out.println("subList.set(3,666) 得到List :");
    System.out.println("subList : " + subList);
    System.out.println("sourceList : " + sourceList);

}

得到結果:

sourceList : [H, O, L, L, I, S]
sourceList.subList(2, 5) 得到List :
subList : [L, L, I]
subList.set(3,666) 得到List :
subList : [L, 666, I]
sourceList : [H, O, L, 666, I, S]

當我們嘗試通過set方法,改變subList中某個元素的值得時候,我們發現,原來的那個List中對應元素的值也發生了改變。

同理,如果我們使用同樣的方法,對sourceList中的某個元素進行修改,那麼subList中對應的值也會發生改變。讀者可以自行嘗試一下。

1、結構性改變SubList

public static void main(String[] args) {
    List<String> sourceList = new ArrayList<String>() {{
        add("H");
        add("O");
        add("L");
        add("L");
        add("I");
        add("S");
    }};

    List subList = sourceList.subList(2, 5);

    System.out.println("sourceList : " + sourceList);
    System.out.println("sourceList.subList(2, 5) 得到List :");
    System.out.println("subList : " + subList);

    subList.add("666");

    System.out.println("subList.add(666) 得到List :");
    System.out.println("subList : " + subList);
    System.out.println("sourceList : " + sourceList);

}

得到結果:

sourceList : [H, O, L, L, I, S]
sourceList.subList(2, 5) 得到List :
subList : [L, L, I]
subList.add(666) 得到List :
subList : [L, L, I, 666]
sourceList : [H, O, L, L, I, 666, S]

我們嘗試對subList的結構進行改變,即向其追加元素,那麼得到的結果是sourceList的結構也同樣發生了改變。

1、結構性改變原List

public static void main(String[] args) {
    List<String> sourceList = new ArrayList<String>() {{
        add("H");
        add("O");
        add("L");
        add("L");
        add("I");
        add("S");
    }};

    List subList = sourceList.subList(2, 5);

    System.out.println("sourceList : " + sourceList);
    System.out.println("sourceList.subList(2, 5) 得到List :");
    System.out.println("subList : " + subList);

    sourceList.add("666");

    System.out.println("sourceList.add(666) 得到List :");
    System.out.println("sourceList : " + sourceList);
    System.out.println("subList : " + subList);

}

得到結果:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1239)
    at java.util.ArrayList$SubList.listIterator(ArrayList.java:1099)
    at java.util.AbstractList.listIterator(AbstractList.java:299)
    at java.util.ArrayList$SubList.iterator(ArrayList.java:1095)
    at java.util.AbstractCollection.toString(AbstractCollection.java:454)
    at java.lang.String.valueOf(String.java:2994)
    at java.lang.StringBuilder.append(StringBuilder.java:131)
    at com.hollis.SubListTest.main(SubListTest.java:28)

我們嘗試對sourceList的結構進行改變,即向其追加元素,結果發現拋出了ConcurrentModificationException。關於這個異常,我們在《一不小心就踩坑的fail-fast是個什麼鬼?》中分析過,這裏原理相同,就不再贅述了。

小結

我們簡單總結一下,List的subList方法並沒有創建一個新的List,而是使用了原List的視圖,這個視圖使用內部類SubList表示。

所以,我們不能把subList方法返回的List強制轉換成ArrayList等類,因為他們之間沒有繼承關係。

另外,視圖和原List的修改還需要注意幾點,尤其是他們之間的相互影響:

1、對父(sourceList)子(subList)List做的非結構性修改(non-structural changes),都會影響到彼此。

2、對子List做結構性修改,操作同樣會反映到父List上。

3、對父List做結構性修改,會拋出異常ConcurrentModificationException。

所以,阿里巴巴Java開發手冊中有另外一條規定:

如何創建新的List

如果需要對subList作出修改,又不想動原list。那麼可以創建subList的一個拷貝:

subList = Lists.newArrayList(subList);
list.stream().skip(strart).limit(end).collect(Collectors.toList());

PS:最近,《阿里巴巴Java開發手冊》已經正式更名為《Java開發手冊》,併發布了新版本,增加了21條新規約,修改描述112處。

關注公眾號後台回復:手冊,即可獲取最新版Java開發手冊。

參考資料: https://www.jianshu.com/p/5854851240df https://www.cnblogs.com/ljdblog/p/6251387.html

【精選推薦文章】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

【機器學習】算法原理詳細推導與實現(二):邏輯回歸

【機器學習】算法原理詳細推導與實現(二):邏輯回歸

在上一篇算法中,線性回歸實際上是 連續型 的結果,即 \(y\in R\) ,而邏輯回歸的 \(y\) 是離散型,只能取兩個值 \(y\in \{0,1\}\),這可以用來處理一些分類的問題。

logistic函數

我們可能會遇到一些分類問題,例如想要劃分 鳶尾花 的種類,嘗試基於一些特徵來判斷鳶尾花的品種,或者判斷上一篇文章中的房子,在6個月之後能否被賣掉,答案是 或者 ,或者一封郵件是否是垃圾郵件。所以這裡是 \(x\) ,這裡是 \(y\) 在一個分類問題中,\(y\) 只能取兩個值0和1,這就是一個二元分類的問題,如下所示:

可以使用線性回歸對以上數值進行劃分,可以擬合出如下那麼一條線,用 \(y=0.5\) 作為臨界點,如果 \(x\) 在這個臨界點的右側,那麼 \(y\) 的值就是1,如果在臨界點的左側,那麼 \(y\) 的值就是0,所以確實會有一些人會這麼做,用線性回歸解決分類問題:

線性回歸解決分類問題,有時候它的效果很好,但是通常用線性回歸解決像這樣的分類問題會是一個很糟糕的主意,加入存在一個額外的訓練樣本 \(x=12\),如果現在對這個訓練集合做線性擬合,那麼可能擬合出來那麼一條直線:

這時候\(y\)的臨界點估計已經不太合適了,可以知道線性回歸對於分類問題來說,不是一個很好的方法。

假設 \(h_\theta(x) \in [0,1]\),當如果已知 \(y\in \{0,1\}\),那麼至少應該讓假設 \(h_\theta(x)\) 預測出來的值不會比1大太多,也不會比0小太多,所以一般不會選擇線性函數作為假設,而是會選擇一些稍微不同的函數圖像:

\[ g(z)=\frac{1}{1+e^{-z}} \]

\[ h_\theta(x)=g(\theta^Tx)=\frac{1}{1+e^{-\theta^Tx}} \]

\(g(z)\) 被稱為 sigmoid函數 ,也通常被稱為 logistic函數,它的函數圖像是:

\(z\) 變得非常小的時候,\(g(x)\) 會趨向於0,當\(z\)變得非常大的時候,\(g(x)\) 會趨向於1,它和縱軸相較於0.5。

邏輯回歸

那麼我們的假設\(h_\theta(x)\) 要嘗試估計 \(y\in \{0,1\}\) 的概率,即:

\[ P(y=1|x;\theta)=h_\theta(x) \]

\[ P(y=0|x;\theta)=1-h_\theta(x) \]

以上可以把兩個公式合併簡寫為(如果\(y=1\)那麼公式為\(h_\theta(x)\);如果\(y=0\)那麼公式為\(1-h_\theta(x)\)):

\[ P(y|x;\theta)=(h_\theta(x))^y(1-h_\theta(x))^{1-y} \]

如果對《概率論和數理統計》學得好的人不難看出,以上函數其實就是 伯努利分佈 的函數。

對於每一個假設值\(h_\theta(x)\),為了使每一次假設值更準確,即當 \(y=1\) 時估計函數 \(P(y=1|x;\theta)=h_\theta(x)\) 趨向於1,當\(y=0\) 時估計函數 \(P(y=0|x;\theta)=1-h_\theta(x)\) 趨向於0。則對於每一個\((x_i,y_i)\),參數 \(\theta\) 的似然估計 \(L(\theta)\)為:

\[ \begin{split} L(\theta)&=P(\vec{y}|X;\theta) \\ &=\prod_{i=1}^mP(y^{(i)}|x^{(i)};\theta) \\ &=\prod_{i=1}^m(h_\theta(x^{(i)}))^{y^{(i)}}(1-h_\theta(x^{(i)}))^{1-{y^{(i)}}} \end{split} \]

如果每一個\((x_i,y_i)\)都準確,即 \(P(y|x;\theta)\) 趨向於1,則應該使似然估計 \(L(\theta)\) 最大化,也就是轉化成熟悉的問題:求解 \(L(\theta)\) 的極大似然估計

為了調整參數 \(\theta\) 使似然估計 \(L(\theta)\) 最大化,推導如下(取 \(log\) 是為了去掉疊乘方便計算):

\[ \begin{split} l(\theta)&=logL(\theta) \\ &=\sum_{i=1}^m{y^{(i)}logh(x^{(i)})+(1-y^{(i)})log(1-h(x^{(i)}))} \end{split} \]

為了使這個函數最大,同樣可以使用前面學習過的梯度下降算法使對數似然估計最大化。之前學習的是要使誤差和 最小化,所以梯度下降的公式為:

\[ \theta:=\theta-\alpha\frac{\partial J(\theta)}{\partial\theta}=>\theta:=\theta-\alpha\nabla_\theta J(\theta) \]

而本次為了求解似然估計最大化,使用的是梯度上升:

\[ \theta:=\theta+\alpha\nabla_\theta l(\theta)=>\theta:=\theta+\alpha\frac{\partial l(\theta)}{\partial\theta} \]

對數似然性是和 \(\theta\) 有關,同樣的為了計算 梯度上升 最快的方向,要對上述公式求偏導得到極值,即是上升最快的方向:

\[ \begin{split} \frac{\partial l(\theta)}{\partial\theta_j}&=(y\frac{1}{g(\theta^Tx)}-(1-y)\frac{1}{1-g(\theta^Tx)})\frac{\partial}{\partial\theta_j}g(\theta^Tx) \\ &=(y\frac{1}{g(\theta^Tx)}-(1-y)\frac{1}{1-g(\theta^Tx)})g(\theta^Tx)(1-g(\theta^Tx))\frac{\partial}{\partial\theta_j}\theta^Tx \\ &=(y(1-g(\theta^Tx))-(1-y)g(\theta^Tx))x_j \\ &=(y-g(\theta^Tx))x_j \\ &=(y-h_{\theta}(x))x_j \end{split} \]

則對於 m 個樣本,則有:

\[ \frac{\partial l(\theta)}{\partial\theta_j}=\sum_{i=1}^m{(y-h_{\theta}(x))x_j} \]

\[ \theta_j:=\theta_j+\sum_{i=1}^m{(y^{(i)}-h_{\theta}(x^{(i)}))x^{(i)}_j} \]

所以總結來說:

邏輯回歸假設數據服從伯努利分佈,通過極大化似然函數的方法,運用梯度下降來求解參數,來達到將數據二分類的目的。

鳶尾花分類

為了劃分 鳶尾花 的種類,嘗試基於一些特徵來判斷鳶尾花的品種,選取100條鳶尾花數據集如下所示:

花萼長度(單位cm) 花萼寬度(單位cm) 種類
5.1 3.5 0
4.9 3.0 0
4.7 3.2 0
7.0 3.2 1
6.4 3.2 1

其中:

種類 含義
0 山鳶尾(setosa)
1 變色鳶尾(versicolor)
2 維吉尼亞鳶尾(virginica)

數據集的圖像分佈為:

計算損失函數:

# 損失函數
def computeCost(theta, X, y):
    theta = np.matrix(theta)
    X = np.matrix(X)
    y = np.matrix(y)
    first = np.multiply(-y, np.log(sigmoid(X * theta.T)))
    second = np.multiply((1 - y), np.log(1 - sigmoid(X * theta.T)))
    return np.sum(first - second) / (len(X))

梯度下降函數為:

# 梯度下降
def gradient(theta, X, y):
    theta = np.matrix(theta)
    X = np.matrix(X)
    y = np.matrix(y)

    parameters = int(theta.ravel().shape[1])
    grad = np.zeros(parameters)

    error = sigmoid(X * theta.T) - y

    for i in range(parameters):
        term = np.multiply(error, X[:, i])
        grad[i] = np.sum(term) / len(X)

    return grad

最終預測準確率為:

accuracy = 99%

結果分類的圖像為:

數據和代碼下載請關注公眾號【 TTyb 】,後台回復【 機器學習 】即可獲取:

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

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

Amzaon EC2虛擬化技術演進:從 Xen 到 Nitro

  今年2月,由光環新網運營的 AWS 中國(北京)區域和由西雲數據運營的 AWS 中國 (寧夏)區域發布新的實例類型,新的實例類型包括 C5、C5d、R5、R5d。除了這四種之外,在AWS國外部分區域還上線了最新的C5n。       這些新實例類型個個都具有鮮明的特徵,我簡單整理歸納如下:

  • C5實例:性價比顯著提升(與 C4 實例相比,C5 實例提供了更高的內存與 vCPU 比率,並且性價比提高了 25%,某些應用程序提高了 50% 以上),更大的實例大小(C5 實例新的更大的實例 c5.18xlarge提供了 72 個 vCPU 和 144 GiB 內存並提供了 25 Gbps 的網絡帶寬)。
  • C5d實例:基於本地 NVMe 的 SSD 磁盤將被物理連接到主機服務器,提供與C5實例的生命周期相耦合的塊級存儲。c5d.18xlarge 規格的實例支持2塊900GB的NVMe SSD作為本地存儲。
  • C5n實例:這是C5 系列的最新成員,其c5n.18xlarge規格可提供高達 100Gbps 的網絡吞吐量。
  • R5實例:其最大實例規格支持96 vCPU、768 GiB內存和25 Gbps 網絡帶寬。
  • R5d實例:R5d 實例與 R5 實例規格相同,它還包括高達 3.6 TB 的本地 NVMe 存儲。

這些實例類型之所以如此實力超群,我認為主要歸功於兩點:

  • 處理器升級

C5 實例配備 Intel Xeon Platinum 8000 系列 (Skylake-SP) 處理器,它發佈於2017/Q3,具有高達 3.4GHz 的穩定全核 Turbo CPU 時鐘速度,並使用 Intel Turbo Boost Technology 來允許單個核心睿頻高達3.5GHz。C5 實例為新的 Intel 高級矢量擴展 512 (AVX-512) 指令集提供了支持,與上一代 C4 實例相比,矢量和浮點計算性能提高最高可達2倍。
而發佈於2015年的C4 實例類型,配備Intel Xeon E5-2666 v3 (Haswell) 處理器。其時鐘頻率為2.9 GHz,配合Intel® Turbo Boost后最高可達3.5 GHz。

  • 採用了AWS Nitro 虛擬化平台

AWS Nitro 將是這篇文章的主角。本文會從它的發展歷程、架構、所創造的價值等方面進行分析和介紹,試圖總結出AWS上虛擬化基礎平台發展的脈絡。

AWS EC2虛擬化發展歷程

下錶總結了AWS曾經採用的虛擬化技術,以及這些技術之間的性能對比:

 

  • #1是全模擬技術。這種虛擬化方式能支持未修改的客戶機操作系統,但速度會嚴重下降。典型產品是VMware 在1986年發布的虛擬化產品。AWS 並沒有採用這種虛擬化技術,放在表格中只是為了做對比用。
  • #2 是基於Xen的半虛擬化技術(Paravirtualization,PV)。PV 要求修改客戶機內核和驅動。EC2第一個採用半虛擬化的實例類型是 m1.small。
  • #3 到 #6 是基於Xen和CPU硬件的全虛擬化技術(Hardware-assisted virtualization,HVM)。採用Xen HVM 技術的虛擬機運行在具有CPU和內存(VT-x)硬件虛擬化能力的處理器上,並使用半虛擬化驅動程序用於網絡和存儲設備。HVM 3.0 中尚未實現中斷和定時器半虛擬化,但在4.0中已有改善。
  • #7 和 #8 則是AWS Nitro技術,這是AWS 研發的一種新虛擬化平台。後面會有詳細介紹。

過去幾年中,Xen是AWS上虛擬化技術的主體,業已成為業界標準之一,已經非常成熟。那麼,為什麼AWS要從Xen 向 Nitro 發展呢?這得從Xen 的架構說起。

從上圖可以看出,Xen 實現了虛擬機的CPU 和內存虛擬化,但是虛擬機的I/O 訪問,包括網絡和存儲等,都是通過虛擬機中的前端模塊和 dom0 中的後端模塊通信,然後由dom0 中的後端模塊通過設備驅動實現的。這I/O路徑太長,這降低了I/O性能,而且dom0還會和業務虛擬機搶佔宿主機資源,很難實現管理虛機和業務虛機之間的平衡,以及避免抖動。

2013年,AWS 採用 Xen PV虛擬化技術的cr1.8xlarge 實例的架構如下圖所示:

這是嚴格意義上未採用Nitro技術的最後一個EC2型號。簡要說明:

  1. 圖中的硬件(Hardware),是運行虛擬機的物理服務器,採用了當時很強大的標準的10Gbps網卡,以及管理一些本地磁盤的存儲HBA卡。Hardware上既運行用戶的業務虛擬機,還運行Xen的dom0虛擬機。
  2. VMM採用Xen項目的PV模式。
  3. 圖中 Amzon Linux 代表Xen dom0,它負責訪問硬件,向虛擬機提供I/O 能力。

圖中 cr1.8xlarge 代表一個這種規格的虛擬機,它的本地存儲、EBS卷和VPC網絡訪問都是通過Xen管理的dom0 虛擬機實現的。

Nitro起源和發展

針對傳統虛擬化架構存在的問題,從2012年開始,AWS EC2虛擬化團隊就開始思考以下問題:

  1. 能做出比純軟件架構更好的hypervisor嗎?
  2. 設備模型本身很複雜,而且它會和業務虛擬機競爭CPU和系統資源,同時技術上它很難避免抖動發生
  3. hypervisor太重了,能將hypervisor 和它周邊的組件解耦嗎?

  從成立之日起,AWS就善於聽取客戶的呼聲和建議,並不斷進行迭代式改進,而不是大刀闊斧地從頭設計一個新架構。根據該原則,AWS團隊首先從最難的網絡部分着手,其位置就是上圖中的金黃色虛線框所示位置。從2013年開始,一些EC2實例類型開始支持網絡接口的硬件虛擬化:單根I/O虛擬化(SR-IOV),而第一個是2013年1月發布的C3,它首次採用了AWS增強型網絡(enhanced networking)。這最初是通過ixgbe驅動程序實現的,速度高達10 Gbps。   c3.8xlarge的架構如下圖所示:

c3.8xlarge的架構與cr1.8xlarge相比,在宿主機上增加了一塊新網卡,這塊網卡和原有的標準網絡通過一個迴環線(loopback cable)連接起來。虛機VPC網絡功能不再通過Xen 的dom0 實現,而是直接訪問宿主機上的這塊硬件網卡。C3 是AWS EC2 歷史上增長最快的幾個實例類型之一,它尤其以控制性能抖動和持續的網絡性能著稱。這可以看做Nitro思想的發源,那就是將軟件功能卸載到專有硬件上。

  下一個改進方向是EBS存儲訪問性能提升。   2015年,AWS推出了C4實例類型,它針對EBS卷使用了硬件虛擬化技術。c4.8xlarge的架構如下圖所示。仔細對比能發現,這個新架構與C3中的網絡架構改進有些不同。在虛擬機中,還保留了“前端-後端”這種Xen傳統架構,這是當時為了兼容性和穩妥新考慮,因為NVMe在當時來說還是一種非常新的技術。在宿主機上,採用了新收購的Annapurna Labs公司開發一種卡(下圖中黃色虛線框內),它能將遠端存儲以NMVe形式呈現給虛擬機。

這個改進的結果是,宿主機上的CPU被Xen佔用得少了,能更多地被虛機使用了。

  2016年5月發布的X1 是第一個支持ENA的實例類型。ENA是增強型網絡的最新實現,速度高達25 Gbps。ENA,全稱是Elastic Network Adapter,它正是Nitro項目的一部分,它是由Annapurna Labs公司開發的。

現在的ENA,能用於虛擬機和物理機,它以開源項目形式發布在github上。ENA 是AWS網絡虛擬化一關鍵技術,它使得虛擬機能夠繞過內核和用戶空間網絡處理程序,直接操作網卡硬件,這顯著提升了網絡效率。

從用戶使用角度,也許只是用了一個新網卡驅動。但是其底層採用了Annapurna Labs公司開發的定製網絡ASIC硬件卡。這是Nitro第一款真正的專用硬件卡。它不僅卸載了VPC網絡功能,還卸載了EBS 存儲網絡功能。因此這是一種完全的網絡負載卸載硬件。

 

下一步的優化方向在實例存儲上。2017年,AWS發布了存儲優化實例類型i3,它使用了SR-IOV和NVMe存儲驅動。這是AWS首次採用Annapurna Labs研發的Nitro存儲卡40202所管理的SSD磁盤,這些磁盤被直接映射給虛擬機,虛擬機通過NVME驅動來使用宿主機上的SSD磁盤。這能實現磁盤300萬以上的IOPS性能。Nitro 芯片負責包括磁盤監控、加密、QoS等職責。  

 

  顯然,到這時候為止,仍然剩下的問題只能是Xen 自身,以及它的管理功能部分了。Xen過於笨重,因為作為傳統 Hypervisor,它必須做很多事情 – 它必須保護物理硬件和 BIOS,它必須虛擬化 CPU,虛擬化存儲,虛擬化網絡,並提供豐富的管理功能。其管理性dom0虛擬機會搶佔業務虛機的系統資源。那到底能不能把Xen徹底替換掉呢?答案是肯定的,因為AWS在技術上從來沒讓人失望過。   2017年11月,AWS發布了C5 實例類型。它使用基於KVM的Nitro hypervisor 替換了Xen,hypervisor 軟件大大被簡化,Xen 所用的 dom0 也不需要了。其架構示意圖如下:

 

AWS Nitro 則重新構建了EC2虛擬化基礎架構。Nitro 系統將存儲、網絡和安全功能卸載(offload)到專用的硬件(Nitro卡)上,帶來的好處是虛擬化實例幾乎可以為客戶機操作系統提供主機的所有 CPU 和內存,同時Hypervisor 的功能也因此大大減弱。   Nitro 還被用到2017年發布的AWS 首個物理機實例類型 i3.metal中。下圖是i3.metal架構示意圖:

在i3.metal 中,Nitro 發揮了基礎性作用。它的安全芯片通過提供硬件保護和固件驗證功能為I3實例提供安全保障;它的各種卡,使得I3實例具備基於非易失性存儲器標準 (NVMe) SSD 的實例存儲,通過ENA支持高達 25Gbps 的聚合網絡帶寬。 

Nitro 架構

AWS Nitro 系統是模塊化組件的集合,可以使用廣泛的計算、存儲、內存和網絡選項來設計 EC2 實例,為新一代EC2實例提供動力。它包括三大部分:

 

Nitro 卡

 

這些Nitro 卡是硬件,插入到宿主機的PCIe卡槽中,採用SR-IOV 直通(passthrough)技術將這些卡呈現給實例。包括:

  • VPC Data Plane(用於VPC訪問的Nitro卡):本質上是一塊通過PCIe附加到宿主機上的一塊定製網卡,支持網絡封包和解包、安全組、限速器和路由等功能。實例通過ENA驅動和它通信。同時,該卡還帶有一些網絡加速功能。以限速器為例,每個Nitro支持的實例,不管它在哪個區域哪個數據中心哪個宿主機上,都會有一致的性能,這對分佈式應用非常重要。
  • EBS Data Plane(用於EBS卷訪問的Nitro卡):本質上是一塊通過PCIe附加到宿主機上的一塊定製卡。通過該卡,遠端存儲被以NVMe設備形式展現給實例,實例通過標準NVMe驅動程序訪問該卡。它首次被用在C4中。支持卷加密、存儲加速;支持I3裸機實例。
  • Instance Storage Data Plane(用於實例存儲訪問的Nitro卡):通過該卡,本地磁盤被以NVMe設備形式展現給實例,實例通過標準NVMe驅動程序訪問這些磁盤。支持加密、限速器和本地磁盤監控。

除了卡之外,Nitro 還提供卡控制器(Card Controller)。它提供API端點,負責協調所有Nitro卡、Nitro Hypervisor和Nitro安全芯片。它還利用Nitro安全芯片實現了Hardware Root Of Trust(硬件信任根),支持實例監控、計量和認證。它還為Nitro EBS卡實現了NVMe控制器。

Nitro 安全芯片

Nitro安全芯片整合到宿主機主板中,控制對所有非易失性存儲的訪問,持續監控和保護硬件資源,並在每次系統啟動時獨立驗證固件。

Nitro hypervisor

Nitro hypervisor位於極簡化的定製的Linux 內核中,基於KVM,帶有定製的VMM和小用戶空間應用。它只負責管理內存和CPU分配,將Nitro卡虛擬功能分配給實例,監控和計量硬件等,不再需要提供任何網絡功能。因此它只需執行虛擬機所需指令,快速而且簡單,在大多數工作負載中能提供接近裸機的性能。 Nitro 各組件之間的關係如下圖所示:

 

Nitro 帶來的豐富價值

更高網絡訪問性能

利用Nitro提供的新一代 Elastic Network Adapter (ENA) 和 NVM Express (NVMe) 技術,C5 實例提供了高達 25 Gbps 的網絡帶寬和更低延遲及抖動。2018年發布的更強大變體 C5n 實例,支持網絡帶寬高達 100 Gbps,用戶的仿真、內存緩存、數據湖以及其他通訊密集型應用運行得將比以往更好。   採用Nitro增強網絡功能后的網絡延遲對比:

(Series 1:cc2.8xlarge,2:c3.8xlarge,3:c4.8xlarge,4:c5.18xlarge,5:c5.18xlarge(採用ENAv2))

網絡和存儲帶寬對比:   (1:c3.8xlarge,2:c4.8xlarge,3:c5.18xlarge,4:c5n.18xlarge. Series1:網絡,Series2:存儲)

更高EBS和本地存儲訪問性能

Nitro 使得實例可通過物理方式連接到主機服務器的基於 NVMe 的本地 SSD 塊級存儲,以及將遠端存儲以NVMe設備的形式呈現給實例。 2019年3月,由Nitro支撐的新計算密集型 C5 和 C5d 實例已經在AWS 北京和寧夏區域推出。C5實例支持高達9Gbps 的專用 Amazon EBS 帶寬。而 C5d 最大實例規格則可使用兩塊900G的NVMe SSD。這些實例非常適合需要訪問高速、低延遲的本地存儲的應用程序。

更大實例大小和CPU內存比率

由Nitro支撐的C5實例,其實例的CPU和內存比率,由C4的1:1.875上升到1:2;實例的最大規格,從C4的36vCPU/60Gib內存,上升到72vCPU/144Gib內存。

更低虛擬化花銷

Nitro Hypervisor 是一款輕薄的靜態的虛擬機管理程序,可管理虛擬機的內存和CPU分配,並提供與大多數工作負載無法區分的性能。據Netflix公司Brendan Gregg 觀察,Nitro Hypervisor的性能損耗非常小,通常不到1%,他的結論是 Nitro提供的虛擬化性能接近裸設備。

更低Hypervisor抖動

有了Nitro后,就不再需要為存儲和網絡I/O再預留CPU和內存資源了。這不僅使得可以向EC2實例分配更多資源,為更大的實例規格提供了可能,還為實現一個簡單的輕量的hypervisor提供了可能,而這就為實現更低hypervisor抖動創造了條件。   下圖是一AWS 客戶在三種EC2實例上採用對延遲要求極低的一實時應用做的對比測試。藍色是C5,紅色是i3.metal,黃色是C4。SLA 是用於測試的實時應用所能忍受的最高延遲。

 

從上圖中的測試結果看,C5 相對裸機只有一點極小的附加開銷,而且性能非常平穩,幾乎沒有波動,能完全滿足應用的SLA需求。而C4則有相對較大的波動,只能大概滿足70的SLA。

更多實例類型

AWS發布了基於Nitro的實例存儲實例類型 C5d,M5d 和 R5d,提供低延遲高吞吐的基於NVMe的實例存儲。 AWS在2017 re:Invent上宣布了基於Nitro的AWS EC2 Bare Metal實例 I3.metal。它沒有性能開銷,能夠運行你喜歡的任何東西,比如Xen,KVM,容器,ESXi,FireCracker微虛機等;支持非虛擬化環境,支持容器環境,同時還能繼續使用比如EBS、ELB和VPC等基礎服務;支持比如SAP HANA和其它內存型應用。 AWS還基於Nitro發布了採用AMD EPYC處理器的系列實例R5,M5和T3,最高可降低10%成本。 AWS發布了基於Nitro的具有100Gbps網絡帶寬的實例類型C5n,這是運行HPC和分佈式機器學習負載的理想類型。 AWS發布了基於Nitro的採用AWS Graviton(基於ARM)處理器的實例類型A1,最高可降低45%成本。

更低價格、更高性價比

下錶显示了AWS 北京(BJS)和中衛(ZHY)區域的4代和5代EC2實例的價格比較,你可以看到實實在在的價格下降:  

 

目前,Nitro支撐的C5 實例提供了 EC2 產品系列中最佳的價格/計算性能比。與C4實例相比,其性價比提高了49% 。 與R4實例相比,由Nitro支撐的R5實例為每個vCPU提供額外5%的內存,且每 GiB 價格低50%。R5實例非常適用於高性能數據庫、分佈式內存緩存、內存數據庫和大數據分析等應用程序。

為更多性能優化提供了可能

對於需要深度定製化EC2 的用戶而言,Nitro 還帶了了另外的好處:對於EC2 更深入的監控和優化。在由Nitro支撐的C5實例中,你可以得到數百個PMC 計數器(Performance Monitoring Counters ,性能監控計數器)。作為對比,以前的實例類型中,你只能看到區區7個PMCs。更多的PMC計數器,為性能優化提供了更多可能。

小結

亞馬遜 AWS CTO 沃納·威格爾(Werner Vogels)曾經說過,“在亞馬遜 AWS,我們90%到95%的新項目都是基於客戶給我們的反饋,剩下5%也是從客戶角度出發所做得創新嘗試。”而Nitro 正是這種項目之一,它誕生於2013年,成年於2017年,現在還在不斷成長中。Nitro 正在作為AWS核心虛擬化架構平台,推動着AWS最核心的EC2產品家族不斷往更大(單實例的vCPU和內存更大)、更快(I/O速度更快)、更安全(採用Nitro安全芯片)、更穩定(Hypervisor抖動更低)、更多類型、更高性價比方向演進,支撐越來越多用戶越來越多的業務場景,創造着越來越大的業務價值。     主要參考文檔:

  1. AWS re:Invent 2018: Powering Next-Gen EC2 Instances: Deep Dive into the Nitro System (CMP303-R1)
  2. AWS re:Invent 2017: C5 Instances and the Evolution of Amazon EC2 Virtualization (CMP332)
  3. AWS re:Invent 2018: Deep Dive on Amazon EC2 Instances & Performance Optimization Best Practices (CMP307)
  4. AWS re:Invent 2018:Optimizing Network Performance for Amazon EC2 Instances (CMP308-R1) 

感謝您的閱讀,歡迎關注我的微信公眾號:

 

【精選推薦文章】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

反射機制 小小談

反射機制(Reflection)

何為反射

反射是在兩種物質分界面上改變傳播方向返回原來物質中的現象
反射是生物體對外界刺激做出應激行為的過程,根據產生的原因分為條件反射非條件反射等,典型的實驗案例包括巴甫洛夫的狗……
反射是一些面向對象程序設計語言提供的針對對象元數據(Metadata)的一種訪問機制

元……數據??什麼高深莫測的武功??

啊,誠然,一旦涉及到“元XXX”事情通常就開始變得無比抽象,以至於我不禁念叨起那句訣

太極生兩儀,兩儀生四象,四象生八卦……

不過元數據這個概念在數據庫里還是比較常見的,比如,某個關係型數據庫里有張表:

水果

編號 名字 數量
1 蘋果 6
2 香蕉 3
3 5
4 橘子 3
5 菠蘿 2

數據,就是存在表裡的一條一條的記錄,(1,蘋果,6),(3,梨,5)都是數據,那麼,元數據就是凌駕於這些數據之上的用於描述數據數據,對於這張表而言,也就是這張表的表頭(關係數據理論里稱之為關係模式):(編號,名稱,數據)

划重點
元數據(Metadata):用於描述數據的數據

好像有些明朗了,但那關面向對象什麼事呢

眾所周知,類(Class)是面向對象的一個重要概念,儘管,針對於數據庫來說,對象模型和關係模型是不同的概念(上文提到的是關係模型的一個例子),但是,對象模型中的對象和關係模型中的關係,其級別是等同的。

關係……又對象……越來越聽不懂了

好吧,我們先把關係放在一邊,我們只把上邊的東西看做一張表。

難道你就沒有把它改寫成如下形式的衝動嗎??

public class Fruit
{
    public int no;
    public string name;
    public int count;
    
    public Fruit(int no, string name, int count)
    {
        // ...
    }
}

好了,上面的類定義的語義就是

有這樣一類東西,我們稱呼這類東西為水果,結構如下……

那麼,這樣一來,我們就可以定義一個no為9,name叫做“西瓜”,count為5的一個對象,這個對象具有具體的數據。

而上面的類定義代碼,包含的就是這個類的元數據

說的再直白點吧

以人為例,數據注重的是這人的臉長啥樣,而元數據注重的是這人有沒有臉(好像不太對……)

好吧差不多了解了,但元數據和反射有什麼關係呢

反射是一些面向對象程序設計語言提供的針對對象元數據(Metadata)的一種訪問機制

本文一開始就說了,罰站20年

不過在此之前先解釋一件事,元數據在哪

任何一個面向對象的程序設計語言,其類類型都具備一個元數據的存儲,至少程序會使用這個元數據能夠動態地構造此類的對象。但不同的語言機制不同,比如C++這種的,因為直接和系統進行愉♂快的互♂動,因此元數據就直接使用系統的內存地址了,這種數據使用是很不直觀的,同時也不使用任何託管機製做後援(巨硬魔改的C++/CLI不在討論範圍內),因此這種貼近底層的語言不支持反射機制,雖然可以通過強行向程序代碼中通過工廠類模式強行注入可讀的元信息(方法參見這位大佬的文章)。

但是,正如前面所說的,如果元數據在託管編譯或解釋的狀態下會保留一份可讀的版本,這是提供給解釋器或者託管平台用的,當然,這種情況下語言一般會提供一個較為完善的元數據訪問機制,這就是反射。這類語言典型的代表就是C#(.NET託管)、Java(JVM虛擬機)、Python(解釋器提供)等。

那……反射是如何運作的呢??

反射嘛。那還不容易,拿個鏡子就可以了呀!
或者用羊角錘偷襲的方式砸膝蓋什麼的也是很容易的呀!
不過這麼說來,拿羊角錘偷襲鏡子豈不是更棒!!

正如之前所說,反射機制是對類的元數據的獲取和操縱,因此,一個重要的前提就是:

這個程序設計語言的運作機制當中,類的元數據必須是可見的,如果可讀的話那更好

只有當類的元數據是可見的,反射機制才有訪問它們的可能,但是元數據的可讀性會決定反射機制訪問它們的難易程度。

這裏補充一句,有人會說,在使用IDE或者代碼編輯器的時候,我們寫object.property這種訪問方式的時候編譯器不就直接告訴我們了么??
關於這一點,這裏暫時只說一個前提:

反射機制的實際動作是聚焦於運行時(Runtime)的。

在程序代碼編譯之前我們恣意地書寫這MyObject.id.hashCode.getFlush().balabala的時候,這是預編譯的過程,預編譯的時候當然這些元數據都是以字面形式給出的(因為你的代碼里寫了這個類的定義),你可以非常愉悅地Ctrl+C Ctrl+V或者享受着IntelliSense帶給你的N倍快樂,這個時候再談反射就沒什麼意義了,因此,反射機制訪問元數據都是在編譯后運行時發生的。

明明都是面向對象,為什麼偏偏C++不支持這個東西呢

以C++為例,這些元數據是否可見?答案是肯定的,那為什麼不支持反射機制呢,因為這些元數據是以指針的方式給出的,指針在已編譯的C++程序中的存在形式就是地址,說的再粗暴點,就是4或8字節的二進制數……
也就是說,在已經編譯完成的C++程序的眼裡,類的元數據已經變成二進制的地址碼了,如果某人在沒有源代碼的情況下想給這個項目寫一個反射機制,那麼他將不得不面對一大堆的:

0xb08dfe231a1c002e
0xb08dfe231bc128f6
0xb08dfe2417a90f5d
......

看到這些,他長舒了一口氣,優雅地點燃了一根香煙,然後毫不猶豫地戳到電腦屏幕上:

鬼知道這是什麼玩意啊!!

如果原項目加個殼、模板元編一下再做個混淆加密的話那更沒法看了,因此如果一定要實現反射機制,一般都是把反射機制直接囊括到項目開發過程當中(就像上面那位大佬的文章中提到的那樣,原項目的作者也是反射機制的構造者)。
這樣的話就會存在一個上上上個世紀汽車行業出現的問題:

這輛車的件無法用到另外一輛車上!
這個反射機制無法用到別的項目上!

當然,這樣說可能有些絕對,但以C++的方式實現一個能夠廣泛用於所有項目的反射機制應該是極端困難的。
上面大佬的文章當中,這個C++的項目要使用反射機制,是藉助工廠模式實現的,關於這些的實現方法,詳見大佬文章(當然我自己也沒完全看懂)

那託管語言又如何呢

C#、Java,這兩種語言都是託管代碼的(C#使用.NET進行託管,Java則交給了JVM虛擬機)。

與C++不同的是,他們並不直接接觸系統底層,而是通過中間代碼訪問底層的。

中間代碼由誰處理呢,C#是通過.NET提供的CLR,產生的中間語言是程序集,而Java靠的是JVM,其中間產物是class文件。
如果有幸使用一些IDE打開這兩個文件往裡窺探一遭的話,我們應當不難從中找到這些元數據的信息。

這就好像,一群孩子進了幼兒園,一個託管老師全程進行看護。

把拔碼麻區上辦,我區悠貳園吶

當然,託管老師肯定是知道孩子叫什麼名的,訪問他們自然也是很容易的。同理,託管環境(或虛擬環境)也是一樣的,因為銜接上下兩層,因此把底層的元數據和上層的可讀文本構造反射的橋樑是很容易辦到的,因此,C#和Java都提供了一套非常完善的反射庫,他們可以被用於使用這兩種語言寫的任意一個類當中。

好了,道理我都懂,但為什麼要反射呢?

反射能幹什麼呢

舉個最簡單的例子

我……我有一個夢想,我想要這樣一個函數,能夠返回Person類是否有我所說的方法,但是我不知道Person類里有什麼,比如我想問他有沒有Eat()方法,它返回true,我問他有沒有Fly()方法,它能返回false

好了,換作是你,你會怎麼實現這樣一個函數呢??

而反射機制恰恰做到了!
你提供給反射機制一個字符串形式的函數名,反射機制不僅可以得知這個函數是否存在,甚至能幫助你去執行這個函數(Invoke)。

什麼,你不好問它有沒有某個函數??好啊,反射機制甚至可以告訴你這個類都有哪些屬性哪些函數,繼承自誰,可見性如何,是否抽象等等。

那反射在什麼時候比較好用呢

上面那個例子其實就是一個經典的用途。

或者,我們可以考慮另外一個場景。

你寫了某個函數接受了一個抽象為Object的對象,你希望,如果Object的對象存在方法Grow則調用之,否則什麼也不做。

這個時候首先可以通過反射機制確定方法是否存在,但即便方法已經存在,我們是無法直接調用的,因為對象已經抽象為Object,而Object並不存在方法Grow,所以直接調用就洗洗睡了。

我們不能具象回來么??

如果我們知道類在抽象之前是什麼類型的時候,那當然可以具象化回來。
但是抽象雖然發生於編譯時或運行時(動態創建的對象),但具象類型的獲知卻是在編譯之前的代碼源文件,而且還有些時候你根本無法知道原類型,那也沒辦法拆箱。


這裏面我為了方便,也是想不出啥更好的詞
這裏我稱派生類基類的多態轉化為抽象
反過來的過程稱為具象

那我還怎麼調用Grow

反射機制可以獲取到完整的可用方法的列表,我們在列表中找到了Grow,存在形式為Method/MethodInfo對象或乾脆就是個字符串。

但無論是哪種,obj.Grow();是不可能了,好在反射機制連這件事都考慮在內了——Invoke調用!!

反射機制不僅知道你想要什麼方法,還可以幫助你調用這個方法,這個調用就通過一個叫做Invoke的方法完成。

不同語言對Invoke的定義不盡相同但功能上大同小異,通過Invoke調用某方法的過程實質上是轉調回調(或者是間接調用)。
間接調用比直接調用更加的強大靈活,但繞了遠路。

還有什麼比較宏大一點的應用么??

宏大一點……好吧,其實每一個磅礴的工程都是從一點一滴做起的。

一個很經典的案例,就是上文那篇大佬文章里的一個常用功能——序列化(Serialization)
雖然C#和Java本身就有可以用於序列化的一些結構和功能庫(Serializable接口之類的),但是有些時候我們對序列化機制如果有更高的可定製性要求的時候,我們往往傾向於自己構建一套心儀的序列化功能庫。

於是乎就有一個最簡單的問題擺在面前:

現在有Class1類的對象,還有Class2類的對象,還有Class3類的一些對象想要轉化成可解析的內容,以供發送或保存(當然這也就意味着,這些對象的所有屬性和狀態都要保存),但是這老大老二老三一家一個樣,屬性也各不相同,我又不想挨個單獨寫,那該怎麼辦呢??

現在有了反射機制,問題就很容易解決了。三胞胎嫌分起來麻煩??反射機制可以把他們安排的明明白白!!你可以向反射類提供一個完整的類名,反射機制就能保證給你這個類對應的可用屬性的列表,以及一整套處理方案(Get和Set),之後還不是想來啥來啥,美滋滋~~

當然,以上都是反射機制用途中小的不能再小的冰山一角,比如我還可以通過反射機制根據我的輸入創建我想要的類型的對象等等。

哇,反射這麼強大??我要滿地反射!!

冷靜點!任何事物都有多面性,反射也不例外,我們看看反射機制有什麼特點,它到底是否適合所有情形。

極致靈動(Flexi Frenzy) 稀有屬性

反射機制可以讓你的代碼非常靈活,以不變應萬變。
這也正是反射機制帶來的最大的好處。

未卜先知(Fortune Tell) 普通屬性

反射機制是在運行時起作用,當然,運行期間發生什麼,編譯之前是無法獲知的,反射就是處理這件事的。

效率捉急(Emaciated Efficiency) 糟糕屬性

反射機制最大的問題!

反射機制的效率是十分低下的,首先在運行時獲取元數據再轉化成可讀形式就不是一個很快的過程,而反射的Invoke調用是個不折不扣的間接調用。

不當地使用大量反射會導致程序效率的急劇下降。

代碼膨脹(Code Expansion)

顯然,用反射進行調用的代碼往往比直接調用寫起來複雜,所以除非你寫代碼是按行數計工資,否則能直接調用就不要反射。

健壯風險(Robustness Risk)

反射機制一般允許用戶傳入字符串……

然後就是萬劫不復深淵之伊始

這時候用戶傳的字符串就可以非常的五花八門了,就好像一個動物園裡,反射機制是一個可愛的小動物,而遊客開始不分青紅皂白地對它投各種食,良莠不齊,可是你的反射機制很脆弱,它可禁不起這折騰,吃到不好的東西就會生病罷工(拋異常,然後中止),因此你這當奶媽奶爸就要多操心,幫它收拾(捕獲),告訴他如何分辨食物(預先判斷)……

不過呢,有些時候引入反射機制恰恰就是出於健壯性的考慮……

如果我養的不是個反射機制而是一隻熊貓的話我會上天的!!

總結

反射是個強大的武器,但使用應多加謹慎!

以上

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

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

用象棋的思維趣說IT人的職業發展和錢途,在上海軟件行業,我見到的年入50萬的眾生相——我們該如何努力從而達到這個目標

    最近我花了不少功夫在學習象棋,也學習了王天一等高手的棋路,感覺IT人的職業和下棋一樣,往好了講,爭主動權爭實惠只爭朝夕,往壞了講,一步走錯得用多步來彌補,如果錯誤太大未必能彌補回來。在本文里,就用下棋這種比較輕鬆的話題來聊聊程序員該如何經營自己的職業。

1 對手是誰,如何算輸贏

    做任何事情都要明確對象和目標,下棋時,對手和目標都很明確,但在職業規劃里,對手是誰?如何算輸贏?

    對手從小了講,是自己,畢竟除山中賊易,除心中賊難,往大了講,是其它IT人。

    如何算輸贏?這有很多標準,往大了講,達到財務自由或者“掙到一個億”算贏,往中了講,有房有車有孩子,或者達到中產水平,往小了講,能在社會上立足,收入在平均水平以上。在本文里,不說過於遠大的目標,倒也不是遙不可及,而是再遠大的目標也是得靠腳踏實地,也不說“收入超平均水平”,畢竟這還是比較容易的,就算“達到中產水平”為贏。

    再具體點,如何算中產?我找了上海2018年中產的標準如下。

    1,有兩套及以上住房(不含和父母共有的),至少一套達非普標準;

    2,沒有房貸,或各項負債合計低於總資產30%。

    3,家庭年收入(不含父母)達50萬以上。

    之前本人寫過篇博文,在上海軟件行業,我見到的年入50萬的眾生相——我們該如何努力從而達到這個目標,年入50萬也算是中產吧。 

2 少走緩手棋

    一盤象棋有60個回合不算長,算下從大學畢業到退休,大致有30年吧,大概平攤下來,半年時間能算一個回合,也就是說,用半年的努力,自己職業上應該有顯著的效果。

    下棋時不怕局勢一般,就怕下緩手棋,高手之間過招,2步緩手足以輸棋,一般的人下棋,也經不起3步以上的緩手。

    那麼職業乃至人生的棋盤上,什麼叫緩手呢?

    1 在公司里得過且過,不主動追求技術進步,導致半年左右技術等方面沒進步。

    2 自己的技能已經得到提升,但出於安逸,沒有追求更高的工資或者更高級的職位。

    3 當工資收入達到一定水準后,不思進取,不積極探索新的掙錢渠道和掙錢模式。

    那麼在工作中,一旦下緩手棋會有什麼後果呢?

    1 半年不進步,看似沒什麼影響,但由奢入儉難,一旦得過且過的日子過慣了,再想上進就難了。

    2 如果一年不進步,技術馬上會落後於平均水平。

    3 一年半以上的不進步,就不說跳槽難了,這已經能導致在項目組裡日子難過(因為別人都在進步),更有甚者,如果公司動蕩,這批混日子而且技術不行的人,估計會第一批被淘汰,淘汰后甚至後面工作也難找。 

3 只爭朝夕,爭主動權更得靠效率

    和“少走緩手棋”相對的是“力爭主動權”。

    象棋里主動權直觀表現為,我可以從容調度子力進攻,能從容圍剿對方的子力從而得到子力優勢。在職業規劃里,主動權表現為,我不僅能從容應付當前的工作,而且由於我的技術以及能力在不斷提升,後繼我的技術發展方向和公司選型,我都掌握主動。從實惠角度來講,增加收入的主動權在自己手裡,在當前公司,我可以通過晉陞提升收入,我也可以通過跳槽提升收入,還可以通過各種額外手段提升收入。

    象棋里爭奪主動權靠積極和效率,所謂用最小的“度數”(棋子的移動步數)把子力放置到(給對方造成威脅最大的)積極位置。平時工作中,爭奪主動權的方式也是如此。

    怎麼算積極呢?列下我見到的比較積極的大牛事例。    

    1 態度端正,一天當两天用,在工作中,也絕無看網頁等做和工作無關事情的情況,有問題也是馬上確認積極解決,絕無拖沓。

    2 盡可能地利用各種時間碎片幹活或提升技術,比如我公司里的大牛中午休息時間也在看技術文章,博客園裡的一些大牛也是利用早上上班前或下班回家后的時間寫技術博客,總之積極地盡一切可能提升自己。

    3 從來不停止提升自己,比如我之前互聯網公司的一位大牛,Spring方面在業內非常出名,但平時也時一有時間就看Spring文章,一有可能就出書出視頻教程。

    而提升工作效率除了不斷總結之外,還得盡可能減少不必要的休閑娛樂時間,比如我之前互聯網公司的多位大牛,都是手機上沒有遊戲app,也沒聽說過追劇。

    知易行難,其實道理也很通俗,做起來並不容易,我的體會是,在開始階段稍微逼下自己,比如早起寫博客,工作時盡量不看無關事,平時積極些,可能最多一個月,當形成習慣時,想改也難。 

4 勢在子先,首先得提升自己的掙錢能力

    象棋里有寧失一子,不失一先的說法。如果把象棋中的形勢比作個人技能綜合能力(把握主動權的能力),那麼“子力”就好比掙到的錢。

    也就是說,能力比錢重要,一方面我們應該踏踏實實地提升能力,排除短期跳槽帶來的金錢誘惑,另一方面,在必要時,可以用錢買技能,比如在必要的時候買收費專欄里的知識,甚至參加培訓班。

    我見到的反面例子是,在之前的外企里,某人本身能力也就一般,但還在外面干兼職,這就屬於要錢不要技能,結果短時間內錢確實掙到了,但由於不注意提升能力,最後被迫離開了項目組。而正面的例子就比較多了,不少人在進項目組時能力一般,但平時通過看各種(免費和收費)資料,最後成為項目組內的棟樑。

    再說下應該重視哪些能力?

    1 對底層代碼的理解以及分析能力。

    2 能在短時間內全局掌握系統架構的能力。

    3 比較值錢的,比如大數據機器學習和分佈式的技能。

    4 帶團隊的能力,比如對外能和客戶方溝通,在公司內能和其它組協調扯皮,在組內能合理分派活,合理監管質量同時保證進度。 

5 優勢情況下更不能大意

   下棋時,我經常遇到如下的場景,我或者對手優勢很明顯,但由於一步走錯,局勢逆轉,對此象棋有“臨殺誤急“和“贏棋不鬧事”的說法。

    大家在工作中,這種情況也不能說沒,比如發布項目,什麼工作都準備好了,看似勝利在望,但在發布時,由於大意導致操作事務,最後出現產線問題, 

    象棋在沒贏前,任何走法都得反覆斟酌,同樣在發布項目等工作沒通過驗證前,也不能放鬆,這不僅得到主觀上態度端正,更得“未慮勝,先慮敗” ,盡量把可能導致失敗的因素都考慮全,並針對性地做預案或補償措施。

    我見過不少下棋的人,只會一廂情願,或者沉溺於自己的優勢中,或者主觀上忽視對方的反擊手段,這種人下棋贏了是運氣,輸了是必然,而且這樣下能力也不會提升。同樣,在做項目等工作時,寧可做好針對最壞情況的準備,比如發布時做好,甚至操練好回退預案,寧可用不到。這樣的話,一方面能確保勝局,另一方由於考慮周全,技能上一定也會有提升。 

6 取勝得靠進攻手段,IT人掙錢也得積極

    象棋中取勝一般有兩種方法,一種是確保自己不犯錯然後坐等對方走錯,另一種是積極主動擴大主動權,積優勢為勝勢。

    對應IT人掙錢方式,一種是干好手頭的活,確保不出問題,然後循例升職和加工資,另一種則是積極擴大自己的品牌,不斷探索新的掙錢方式,相比之下,後者更加積極,掙錢效率應該也優於前者。

    正如特級大師的擴大先手和進攻手段得靠慢慢積累,IT人掙錢的方式也得從小做起。比如小到通過寫博客積累,然後可以開專欄出視頻教程,再大點可以通過各種渠道找些項目,再大些可以做自己得產品。

    說起來容易做起來難,但一旦有積極想法了,而不是坐等工資等穩定收入,其它不敢說,至少自己的格局能不斷變廣,掙錢的機會也會越來越多。 

7 逆勢中不能自暴自棄,只要努力,就有翻盤可能

    下棋時不可能老是贏,有不少時候局面會很難看,只能苦苦支撐。相比之下,工作中也不可能一帆風順,比如工作氛圍不好比較壓抑,有996工作壓力大,或者錢少,這種時候絕不能自暴自棄。

    特級大師之間,出現翻盤的案例也不少,更何況業餘的,比如有10盤逆勢棋,如果自暴自棄亂下,估計最多一兩盤能下翻盤,但如果咬牙堅持每步都下好,翻盤的可能性就大很多。

    對應的在工作中,哪怕情況再差,每天也得儘力做好自己的事,必要時更得提升自己的能力,指不定哪天就時來運轉了,或者這個公司沒機會,由於自己一刻也沒放鬆反而在不斷提升自己的實力,在下家公司里就能春風得意了。

8 總結:人生棋局裡,一時輸贏不能代表今後的輸贏

     大家經常能看到,某人前幾年由於混日子,活得沒指望,但突然想明白了,只用了半年就完成了逆轉,也就是說,雖然一步走錯需要用更大的代價來彌補,但一時的輸贏絕不能代表一世的得失。所以,在任何時候都得積極向上力爭主動。本文也算戲說,大家在一笑之餘如果感到有意思,或者有收穫,請幫忙推薦下本文,謝謝大家。

版權說明:

    有不少網友轉載和想要轉載我的博文,本人感到十分榮幸,這也是本人不斷寫博文的動力。關於本文的版權有如下統一的說明,抱歉就不逐一回復了。

    1 本文可轉載,無需告知,轉載時請用鏈接的方式,給出原文出處,別簡單地通過文本方式給出,同時寫明原作者是hsm_computer。

    2 在轉載時,請原文轉載 ,謝絕洗稿。否則本人保留追究法律責任的權利。

【精選推薦文章】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

附004.Kubernetes Dashboard簡介及使用

一 Kubernetes dashboard簡介

1.1 Web UI簡介

dashboard是基於Web的Kubernetes用戶界面。可以使用dashboard將容器化應用程序部署到Kubernetes集群,對容器化應用程序進行故障排除,以及管理集群資源。可以使用dashboard來概述群集上運行的應用程序,以及創建或修改單個Kubernetes資源(例如部署、任務、守護進程等)。可以使用部署嚮導擴展部署,啟動滾動更新,重新啟動Pod或部署新應用程序。
dashboard還提供有關群集中Kubernetes資源狀態以及可能發生的任何錯誤的信息。

二 dashboard部署

2.1 下載yaml

  1 [root@master ~]# mkdir dashboard					#建議將yaml存儲到本地
  2 [root@master ~]# cd dashboard/
  3 [root@master dashboard]# wget https://raw.githubusercontent.com/kubernetes/dashboard/master/aio/deploy/recommended/kubernetes-dashboard.yaml

 

2.2 修改為國內源

  1 [root@master ~]# cd dashboard/
  2 [root@master dashboard]# vi kubernetes-dashboard.yaml
  3 ……
  4 image: mirrorgooglecontainers/kubernetes-dashboard-amd64:v1.10.1
  5 ……

 
提示:將yaml文件中的image字段修改為mirrorgooglecontainers/kubernetes-dashboard-amd64:v1.10.1。

2.3 安裝

  1 [root@master dashboard]# kubectl apply -f kubernetes-dashboard.yaml
  2 [root@master ~]# kubectl get pod --all-namespaces -o wide| grep kubernetes-dashboard			#確認驗證
  3 kube-system	kubernetes-dashboard-68ddcc97fc-c5thv	0/1	Running	0	30s	<none>	node2	<none>	<none>

 

三 dashboard訪問方式

3.1 訪問方式概述

安裝dashboard后,需要為用戶配置對群集資源的訪問控制。從版本1.7開始,dashboard不再具有默認授予的完全管理員權限。默認所有權限都被撤銷,並且只授予了使Dashboard工作所需的最小權限。
提示:本說明僅針對使用Dashboard 1.7及更高版本的用戶。如果確信dashboard需要授予管理員權限,可參考:《附006.Kubernetes身份認證》
通常,其他應用程序不應直接訪問dashboard。
dashboard有以下三種訪問方式:
kubectl proxy:只能在localhost上訪問。訪問地址:http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/
NodePort:編輯 kubernetes-dashboard.yaml文件中,將 type: ClusterIP 改為 type: NodePort,確認dashboard運行在哪個節點后。訪問地址:https://<node-ip>:<nodePort>
apiserver:需要在瀏覽器中安裝用戶證書。訪問地址: https://<master-ip>:<apiserver-port>/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/
提示:kubectl proxy方式不推薦,建議使用有效證書來建立安全的HTTPS連接。

3.2 kubectl proxy

  1 [root@master ~]# kubectl proxy
  2 [root@master ~]# curl http://127.0.0.1:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/	#訪問

 
提示:建議通過後端形式,並且允許所有主機訪問的方式:

  1 [root@master ~]# nohup kubectl proxy --address='0.0.0.0' --accept-hosts='^*$' &

3.3 NodePort

NodePort訪問儀錶板的方式僅建議用於單節點設置中的Kubernetes環境。

  1 [root@master ~]# kubectl -n kube-system edit service kubernetes-dashboard
  2 ……
  3    type: NodePort
  4 ……
  5 #將type: ClusterIP  修改為 type: NodePort。

 
提示:以上操作也可通過以下命令一步完成:

  1 [root@master ~]# kubectl get pods --namespace=kube-system | grep dashboard
  2 kubernetes-dashboard-68ddcc97fc-c5thv   1/1     Running   0          3h14m
  3 [root@master ~]# kubectl describe pod kubernetes-dashboard-68ddcc97fc-c5thv --namespace=kube-system | grep Node
  4 Node:               node2/172.24.8.73

 
測試訪問:
瀏覽器訪問:http://172.24.8.73:30343/
提示:若部署的Kubernetes集群為多節點集群,需要通過以上方式查找dashboard所在的node節點,若為單節點集群,則直接訪問http://<master>:<port>即可。

3.4 apiserver

若Kubernetes API服務器公開並可從外部訪問,可瀏覽器直接訪問:https://172.24.8.71:6443/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/
注意:apiserver默認情況下使用system:anonymous用戶進行訪問,因此沒有權限打開相應資源。只有在選擇在瀏覽器中安裝用戶證書時,才能使用這種訪​​問儀錶板的方式。
NodePort和apiserver都需要配置相應的認證才可訪問,確定某種方式方式后需要配置認證類型。

3.5 Ingress

dashboard也可以使用ingress進行資源公開。
可參考:https://kubernetes.io/docs/concepts/services-networking/ingress/

四 dashboard驗證方式

瀏覽器訪問的時候需要加載相應證書,部署完成后默認已經創建相應證書,可參考附01進行導出。由於Kubernetes默認證書可能過期導致無法訪問dashboard,本實驗在已成功部署Kubernetes後手動重新創建證書。

4.1 創建證書

  1 [root@master ~]# mkdir /etc/kubernetes/dash_pki
  2 [root@master ~]# cd /etc/kubernetes/dash_pki/
  3 [root@master dash_pki]# openssl genrsa -out ca.key 2048				#生成一個 2048 bit 的 ca.key
  4 [root@master dash_pki]# openssl req -x509 -new -nodes -key ca.key -subj "/CN=172.24.8.71" -days 10000 -out ca.crt	                #根據 ca.key 生成一個 ca.crt(使用 -days 設置證書的有效時間)
  5 [root@master dash_pki]# openssl genrsa -out server.key 2048			        #生成一個 2048 bit 的 server.key
  6 [root@master dash_pki]# openssl req -new -key server.key -subj "/CN=172.24.8.71" -out server.csr			                #根據 server.key 生成一個 server.csr
  7 [root@master dash_pki]# openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 10000	#根據 ca.key、ca.crt 和 server.csr 生成 server.crt
  8 subject=/CN=172.24.8.71
  9 Getting CA Private Key
 10 [root@master dash_pki]# openssl x509  -noout -text -in ./server.crt		        #查看證書

 

4.2 修改默認證書配置

  1 [root@master ~]# cd dashboard/
  2 [root@master dashboard]# kubectl delete -f kubernetes-dashboard.yaml		#刪除使用默認證書所創建的dashboard
  3 [root@master dashboard]# ll /etc/kubernetes/dash_pki/
  4 [root@master dashboard]# kubectl create secret generic kubernetes-dashboard-certs --from-file="/etc/kubernetes/dash_pki/server.crt,/etc/kubernetes/dash_pki/server.key" -n kube-system	#掛載新證書到dashboard
  5 [root@master dashboard]# kubectl get secret kubernetes-dashboard-certs -n kube-system -o yaml	#查看新證書

 

4.3 重新部署dashboard

  1 [root@master dashboard]# kubectl apply -f kubernetes-dashboard.yaml
  2 [root@master dashboard]# kubectl get pods --namespace=kube-system | grep dashboard		#確認驗證

 

4.4 導入證書

將server.crt導入IE瀏覽器,導入操作略。

4.5 訪問測試

本試驗基於apiserver訪問方式+Kubeconfig身份驗證進行登錄。
通過apiserver形式訪問:https://172.24.8.71:6443/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/
提示:dashboard登錄整個流程可參考:https://www.cnadn.net/post/2613.htm
提示:apiserver方式見3.4,Kubeconfig驗證方式見《附006.Kubernetes身份認證》中的3.5。  

附001:導出當前Kubernetes證書

[root@master ~]# grep ‘client-certificate-data’ ~/.kube/config | head -n 1 | awk ‘{print $2}’ | base64 -d >> kubecfg.crt
[root@master ~]# grep ‘client-key-data’ ~/.kube/config | head -n 1 | awk ‘{print $2}’ | base64 -d >> kubecfg.key
[root@master ~]# openssl pkcs12 -export -clcerts -inkey kubecfg.key -in kubecfg.crt -out k8s.crt -name “kubernetes-client”
Enter Export Password:[x120952576]
Verifying – Enter Export Password:[x120952576]
使用相應的密碼,將k8s.crt導入IE瀏覽器,導入操作略。    
我的博客即將同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=20stclch1nfo4

【精選推薦文章】

智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

帶您來看台北網站建置台北網頁設計,各種案例分享

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

20年研發管理經驗談(十一)

本文繼20年研發管理經驗談(十)。

此文是對我個人測試思想的一個總結,由於經驗不夠,知識淺薄,如果有什麼不合理的地方請一笑了之。

一、面向對象的概念
  所謂的面向對象是軟件開發的一種重要的思維方式,是把軟件開發過程中出現的事物,用一個個的對像來分析.一般一張數據表可以封裝為一個對像。用個形象的比喻:我們現在要做一張桌子,首先我們考慮到的是我們要做的是什麼?是桌子;桌子是用來干什麼的呢?是用來吃飯、喝茶、看書、打麻將的;然後就要考慮桌子由哪些部分組成?由桌面和桌腿來組成;接着我們需要考慮我們採用什麼材料呢?紙?不行…那可什麼都幹不成,OK,用木頭;接着就可以開始把組成桌子的組件做為對象開始分析–桌面如何做是用刀砍的還是用刨子刨呢?桌腿又如何做…
  一套完整的方法成形了就可以具體實現了,在做的過程中桌面要做多大,桌腿要做多長都要事先考慮到是不是要留出接口,這些就是我們給組成桌子的組件賦予的屬性。OK,現在可以做出具體的實物了,做好實物組件(對象)以後就要將做好的桌面桌腿進行組裝,由於我們事先考慮好了組件的屬性,考慮到了必須預留接口,因此我們可以很輕易的組合成功,桌子做出來了。以上就是面向對象的思想的一個簡要的比喻

  了解面向對象必須了解的幾個名詞:對象、方法、屬性、繼承、多態。

 

二、遊戲測試
  遊戲測試是整個軟件測試行業中比較特殊的一部份,他有着大多數軟件測試的共性,也具備自身的特性,而相對於許多通用軟件的測試來說,遊戲測試所具備的特性是非常明顯的。現在就簡要的說說上面提到的共性和特性。
共性:
1、測試的目的就是為了盡可能的發現軟件存在和潛在的問題。
2、測試都是需要測試人員按照產品行為描述來實施。產品行為描述可以是書面的規格說明書、需求文檔、產品文件、或是用戶手冊、源代碼、或是工作的可執行程序。
3、每一種測試都需要產品運行於真實的或是模擬環境之下。
4、每一種測試都要求以系統方法展示產品功能, 以證明測試結果是否有效, 以及發現其中出錯的原因, 從而讓程序人員進行改進。
總之,軟件測試就是對產品進行盡可能的全面檢查,盡可能的發掘bug,提高軟件質量,從而為企業創造利潤。

特性:
  網絡遊戲世界從某種意義上說是另一個人類社會,只是人們在網絡遊戲世界中進行着在被允許的範圍內的活動,包括了修鍊、交流、合作、經商、欺詐、情感、衝突等等。而在遊戲製作時這些進行這些行為的部分就是一個個完整的功能,我們在進行測試的時候,需要考慮的不僅僅是能否實現功能,要考慮更多的是人們在進行操作時會如何做,可能有多少種做法,這些做法應該有什麼樣的響應,哪些做法是被禁止的,在進行了被禁止的操作后應該有什麼的響應。因此這裏就是涉及到了遊戲世界的測試方法:
1、遊戲情節的測試,主要指遊戲世界中的任務系統的組成, 有人也稱為遊戲世界的事件驅動, 我喜歡稱為遊戲情感世界的測試。
2、遊戲世界的平衡測試,主要表現在經濟平衡,能力平衡( 包含技能, 屬性等等),保證遊戲世界競爭公平。
3、遊戲文化的測試,比如整個遊戲世界的風格, 是中國文化主導,還是日韓風格等等,大到遊戲整體,小到N P C( 遊戲世界人物) 對話, 比如一個書生,他的對話就必需斯文, 不可以用江湖語言。

以上陳述中關於遊戲特性的部分概念是曾在金山公司的測試人陳衛俊提出來過的,在此引用。

 

三、如何用面向對象的思想進行測試
  上面了解了面向對象的概念以及遊戲測試和通用軟件測試的區別以後我們可以進入正題了—如何用面向對象的思想進行遊戲測試?
  首先,和所有通用軟件以及硬件產品一樣,我們的遊戲是一個產品,是一個存在的實體,因此,我們把這個”實體”當做一個大的對象開始分析,整個遊戲由哪些部分構成,而構成整個遊戲的大的部分又由哪些組件構成,認真分析完這些以後就可以着手進行測試了,注意,這裏說”可以進行測試了”意思不是馬上就能進入測試,聽我慢慢道來。 
  ”工欲善其事,必先利其器”—某位高人說的,我們做測試也是一樣,分析完畢后,我們要做的還是分析 ^_^ 不過這裏的分析和之前的分析有點點區別,這裏我們需要分析的是具體功能的關鍵測試點和風險點,測試不能盲目,打蛇要打七寸…..在這裏我們就是把某個具體的功能作為一個對象,我們要分析組成這個功能的是哪些因素,一共有哪些測試點,哪些測試點是關鍵點,哪些是高風險點,一一列舉出來,這樣我們就一目瞭然了,然後就是我們打算採用何種方式來進行測試,這裏就是方法了.測試的方式可能有很多種(比如在不同的操作系統下進行測試等),因此我們也需要一一列舉,此外我們需要分析的還有測試過程中我們需要用到的具體測試手法、具體的數值、特定的環境等等這些就是屬性,當然這些我們也必須整理出來。
  將以上提到的對象、方法、屬性整理成文檔就是我們測試時所必須的測試用例了。當然,還是老話,測試用例的優劣是以覆蓋面來評判的,這裏就需要經驗了,簡單說就是靠累積以及學習。
  OK,測試用例我們完成了,剩下的就是實施測試了,實施測試時個人覺得一定要按照用例的描述去執行,如果在測試過程中覺得用例不完善可以先更新用例再進行測試,一定不要先測試再補用例!!
  接下來就是測試報告,報告中包含的應該有所有測試點的簡述,包括了通過測試的部分和存在bug的部分。bug管理是很重要的一環,在這裏不詳述。
  關於測試流程在這裏就不做具體說明,在這裏希望闡述的是一種測試的思想,個人覺得測試除了要有紮實的相關基礎知識以便更深入的了解產品以外,更重要的是測試思想,具備了完善的測試思想才能有計劃的完成每一步測試,從而提高測試的效率,保證測試產出的質量,也更好的保證產品的質量。面向對象是一種思想,用面向對象的思想來組織、計劃、實施測試工作,能讓我們在測試工作中有很強的目的性,他能清楚地告訴我們今天要做什麼,明天要做什麼,我們要做的是哪些,說回遊戲測試,遊戲開發是一個迭帶的開發模式,因此測試工作往往會有很大的隨機性,因此當我們接到一個新功能時,首先要明確我們要測得這個功能是做什麼的,有什麼用,這個功能怎麼使用。OK,我們了解了這個功能是什麼,能做什麼就可以開始細化分析了:這個功能共由哪些子功能組成,這些子功能是否有自己的子功能點,一層層的分析下去,然後就是從最底層的功能點分析:這個功能什麼情況下要發揮其功效,發揮其功效的因素有哪些,怎麼樣去發揮具體的功效,該功能有沒有相應的容錯機制,這些就是我們的詳細測試點和測試手法。然後向上一層一層分析,一直到最頂層就是我們的功能完整的測試方針。這樣我們就把面向對象的思想完全用到了測試中。當然,在分析的過程中我們必須考慮到,與遊戲情節、遊戲風格、遊戲平衡、玩家的易用性是否衝突等等因素,適時地給策劃提出正確的建議。

  以上陳述的種種,無非是想將面向對象的思想用到測試中的好處列舉出來,或許經驗淺薄說的有些蒼白,但是我堅信一點,測試是一種思想,是一種絕對不亞於開發思想的學問,要想做好測試就需要具備良好的測試思想,或者良好的測試思想不是一天两天能夠形成的但是相信只要把測試當做一種職業,當作一種事業來做,把自己真正當成保證產品質量的最後一道關卡,成為一個BT(BestTester)就指日可待了!

軟件測試用例的認識誤區

  軟件測試用例是為了有效發現軟件缺陷而編寫的包含測試目的、測試步驟、期望測試結果的特定集合。正確認識和設計軟件測試用例可以提高軟件測試的有效性,便於測試質量的度量,增強測試過程的可管理性。

  在實際軟件項目測試過程中,由於對軟件測試用例的作用和設計方法的理解不同,測試人員(特別是剛從事軟件測試的新人)對軟件測試用例存在不少錯誤的認識,給實際軟件測試帶來了負面影響,本文對這些認識誤區進行列舉和剖析。

 

 

誤區之一:測試輸入數據設計方法等同於測試用例設計方法

  現在一些測試書籍和文章中講到軟件測試用例的設計方法,經常有這樣的表述:測試用例的設計方法包括:等價類、邊界值、因果圖、錯誤推測法、場景設計法等。這種表述是很片面的,這些方法只是軟件功能測試用例設計中如何確定測試輸入數據的方法,而不是測試用例設計的全部內容。

  這種認識的不良影響可能會使不少人認為測試用例設計就是如何確定測試的輸入數據,從而掩蓋了測試用例設計內容的豐富性和技術的複雜性。如果測試用例設計人員把這種認識拿來要求自己,則害了自己;拿來教人,則害了別人;拿來指導測試,則害了測試團隊。聽起來似乎是“小題大做”,但是絕不是“危言聳聽”。

  無疑,對於軟件功能測試和性能測試,確定測試的輸入數據很重要,它決定了測試的有效性和測試的效率。但是,測試用例中輸入數據的確定方法只是測試用例設計方法的一個子集,除了確定測試輸入數據之外,測試用例的設計還包括如何根據測試需求、設計規格說明等文檔確定測試用例的設計策略、設計用例的表示方法和組織管理形式等問題。

  在設計測試用例時,需要綜合考慮被測軟件的功能、特性、組成元素、開發階段(里程碑)、測試用例組織方法(是否採用測試用例的數據庫管理)等內容。具體到設計每個測試用例而言,可以根據被測模塊的最小目標,確定測試用例的測試目標;根據用戶使用環境確定測試環境;根據被測軟件的複雜程度和測試用例執行人員的技能確定測試用例的步驟;根據軟件需求文檔和設計規格說明確定期望的測試用例執行結果。

 

 

誤區之二:強調測試用例設計得越詳細越好

  在確定測試用例設計目標時,一些項目管理人員強調測試用例“越詳細越好”。具體表現在兩個方面:盡可能設計足夠多的設計用例,測試用例的數量閱讀越好;測試用例盡可能包括測試執行的詳細步驟,達到“任何一個人都可以根據測試用例執行測試”,追求測試用例越詳細越好。

  這種做法和觀點最大的危害就是耗費了很多的測試用例設計時間和資源,可能等到測試用例設計、評審完成后,留給實際執行測試的時間所剩無幾了。因為當前軟件公司的項目團隊在規劃測試階段,分配給測試的時間和人力資源是有限的,而軟件項目的成功要堅持“質量、時間、成本”的最佳平衡,沒有足夠多的測試執行時間,就無法發現更多的軟件缺陷,測試質量更無從談起了。

  編寫測試用例的根本目的是有效地找出軟件可能存在的缺陷,為了達到這個目的,需要分析被測試軟件的特徵,運用有效的測試用例設計方法,盡量使用較少的測試用例,同時滿足合理的測試需求覆蓋,從而達到“少花時間多辦事”的效果。

  測試用例中的測試步驟需要詳細到什麼程度,主要取決於測試用例的“最終用戶”(即執行這些測試用例的人員),以及測試用例執行人員的技能和產品熟悉程度。如果編寫測試用例的人員也是測試用例執行人員,或者測試用例的執行人員深刻了解被測軟件,測試用例就沒有必要太詳細。而如果是測試新人執行測試用例,或者軟件測試外包給獨立的第三方公司,那麼測試用例的執行步驟最好足夠詳細。

 

誤區之三:追求測試用例設計“一步到位”

  現在軟件公司都意識到了測試用例設計的重要性了,但是一些人認為設計測試用例是一次性投入,測試用例設計一次就“萬事大吉”了,片面追求測試設計的“一步到位”。

這種認識造成的危害性使設計出的測試用例缺乏實用性,或者誤導測試用例執行人員,誤報很多不是軟件缺陷的“Bug”,這樣的測試用例在測試執行過程中“形同虛設”,難免淪為“垃圾文檔”的地步。

  “唯一不變的是變化”。任何軟件項目的開發過程都處於不斷變化過程中,用戶可能對軟件的功能提出新需求,設計規格說明相應地更新,軟件代碼不斷細化。設計軟件測試用例與軟件開發設計并行進行,必須根據軟件設計的變化,對軟件測試用例進行內容的調整,數量的增減,增加一些針對軟件新增功能的測試用例,刪除一些不再適用的測試用例,修改那些模塊代碼更新了的測試用例。

  軟件測試用例設計只是測試用例管理的一個過程,除此之外,還要對其進行評審、更新、維護,以便提高測試用例的“新鮮度”,保證“可用性”。因此,軟件測試用例也要堅持“與時俱進”的原則。

 

 

誤區之四:讓測試新人設計測試用例

  在與測試同行交流的過程中,不少剛參加測試工作的測試新人經常詢問的一個問題是:“怎麼才能設計好測試用例?”。因為他(她)們以前從來沒有設計過測試用例,面對大型的被測試軟件感到“老虎吃天,無從下口”。

  讓測試新人設計測試用例是一種高風險的測試組織方式,它帶來的不利後果是設計出的測試用例對軟件功能和特性的測試覆蓋性不高,編寫效率低,審查和修改時間長,可重用性差。

  軟件測試用例設計是軟件測試的中高級技能,不是每個人(尤其是測試新人)都可以編寫的,測試用例編寫者不僅要掌握軟件測試的技術和流程,而且要對被測軟件的設計、功能規格說明、用戶試用場景以及程序/模塊的結構都有比較透徹的理解。

  因此,實際測試過程中,通常安排經驗豐富的測試人員進行測試用例設計,測試新人可以從執行測試用例開始,隨着項目進度的不斷進展,測試人員的測試技術和對被測軟件的不斷熟悉,可以積累測試用例的設計經驗,編寫測試用例。

 

 

 

【精選推薦文章】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"