整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          帶你了解一款Java中超好用的工具類庫(kù) Hutool

          帶你了解一款Java中超好用的工具類庫(kù) Hutool

          、背景

          在 GitHub 上見到過很多開源的自動(dòng)化框架內(nèi)都自帶了很多 Util 工具類,我們自己在開發(fā)自動(dòng)化框架也必然需要用到工具類庫(kù),那么這樣就會(huì)帶來一些問題:

          • API的學(xué)習(xí)成本
          • 重復(fù)造輪子
          • 封裝不完善帶來的bug

          那么,有沒有比較好的通用輪子讓我們直接使用呢?當(dāng)然有,今天我們來介紹一下工具類庫(kù)—Hutool

          二、Hutool 簡(jiǎn)介

          Hutool是一個(gè)小而全的Java工具類庫(kù),通過靜態(tài)方法封裝,降低相關(guān)API的學(xué)習(xí)成本,提高工作效率,使Java擁有函數(shù)式語言般的優(yōu)雅,讓Java語言也可以“甜甜的”。 Hutool中的工具方法來自于每個(gè)用戶的精雕細(xì)琢,它涵蓋了Java開發(fā)底層代碼中的方方面面,它既是大型項(xiàng)目開發(fā)中解決小問題的利器,也是小型項(xiàng)目中的效率擔(dān)當(dāng); Hutool是項(xiàng)目中“util”包友好的替代,它節(jié)省了開發(fā)人員對(duì)項(xiàng)目中公用類和公用工具方法的封裝時(shí)間,使開發(fā)專注于業(yè)務(wù),同時(shí)可以最大限度的避免封裝不完善帶來的bug。

          以上是摘自官網(wǎng)的介紹,如果我們有需要用到某些工具方法的時(shí)候,不妨在Hutool里面找找。

          官網(wǎng)地址:https://github.com/looly/hutool

          三、Hutool 包含組件

          一個(gè)Java基礎(chǔ)工具類,對(duì)文件、流、加密解密、轉(zhuǎn)碼、正則、線程、XML等JDK方法進(jìn)行封裝,組成各種Util工具類,同時(shí)提供以下組件:



          可以根據(jù)需求對(duì)每個(gè)模塊單獨(dú)引入,也可以通過引入hutool-all方式引入所有模塊。

          四、安裝

          1、Maven

          在項(xiàng)目的pom.xml的dependencies中加入以下內(nèi)容:

          <dependency>
              <groupId>cn.hutool</groupId>
              <artifactId>hutool-all</artifactId>
              <version>5.4.2</version>
          </dependency>

          2、Gradle

          compile 'cn.hutool:hutool-all:5.4.2'

          3、非Maven項(xiàng)目

          點(diǎn)擊以下任一鏈接,下載hutool-all-X.X.X.jar即可:

          • Maven中央庫(kù)1
          • Maven中央庫(kù)2

          注意 Hutool 5.x支持 JDK8+,對(duì) Android 平臺(tái)沒有測(cè)試,不能保證所有工具類或工具方法可用。 如果你的項(xiàng)目使用 JDK7,請(qǐng)使用 Hutool 4.x 版本

          五、常用工具類

          1、Convert

          類型轉(zhuǎn)換工具類,用于各種類型數(shù)據(jù)的轉(zhuǎn)換。

          @Test(description="Convert使用:類型轉(zhuǎn)換工具類")
          public void covert() {
           int a=1;
           String aStr=Convert.toStr(a);
           //轉(zhuǎn)換為指定類型數(shù)組
           String[] b={"1", "2", "3", "4"};
           Integer[] bArr=Convert.toIntArray(b);
           log.info(bArr.toString());
          
           //轉(zhuǎn)換為日期對(duì)象
           String dateStr="2020-09-17";
           Date date=Convert.toDate(dateStr);
           log.info(date.toString());
          
           //轉(zhuǎn)換為列表
           String[] strArr={"a", "b", "c", "d"};
           List<String> strList=Convert.toList(String.class, strArr);
           log.info(strList.toString());
          }
          

          運(yùn)行結(jié)果:

          [Ljava.lang.Integer;@4c0884e8
          Thu Sep 17 00:00:00 CST 2020
          [a, b, c, d]

          2、DateUtil

          日期時(shí)間工具類,定義了一些常用的日期時(shí)間操作方法。

          @Test(description="DateUtil使用:日期時(shí)間工具")
          public void dateUtil() {
           //Date、long、Calendar之間的相互轉(zhuǎn)換
           //當(dāng)前時(shí)間
           Date date=DateUtil.date();
           log.info(date.toString());
           //Calendar轉(zhuǎn)Date
           date=DateUtil.date(Calendar.getInstance());
           //時(shí)間戳轉(zhuǎn)Date
           date=DateUtil.date(System.currentTimeMillis());
           //自動(dòng)識(shí)別格式轉(zhuǎn)換
           String dateStr="2020-09-17";
           date=DateUtil.parse(dateStr);
           //自定義格式化轉(zhuǎn)換
           date=DateUtil.parse(dateStr, "yyyy-MM-dd");
           //格式化輸出日期
           String format=DateUtil.format(date, "yyyy-MM-dd");
           log.info(format.toString());
           //獲得年的部分
           int year=DateUtil.year(date);
           //獲得月份,從0開始計(jì)數(shù)
           int month=DateUtil.month(date);
           //獲取某天的開始、結(jié)束時(shí)間
           Date beginOfDay=DateUtil.beginOfDay(date);
           Date endOfDay=DateUtil.endOfDay(date);
           //計(jì)算偏移后的日期時(shí)間
           Date newDate=DateUtil.offset(date, DateField.DAY_OF_MONTH, 2);
           //計(jì)算日期時(shí)間之間的偏移量
           long betweenDay=DateUtil.between(date, newDate, DateUnit.DAY);
          }

          運(yùn)行結(jié)果:

          2020-09-17 18:42:22
          2020-09-17

          3、StrUtil

          字符串工具類,定義了一些常用的字符串操作方法。

          @Test(description="StrUtil使用:字符串工具")
          public void strUtil() {
           //判斷是否為空字符串
           String str="test";
           StrUtil.isEmpty(str);
           StrUtil.isNotEmpty(str);
           //去除字符串的前后綴
           StrUtil.removeSuffix("a.jpg", ".jpg");
           StrUtil.removePrefix("a.jpg", "a.");
           //格式化字符串
           String template="這只是個(gè)占位符:{}";
           String str2=StrUtil.format(template, "我是占位符");
           log.info("/strUtil format:{}", str2);
          }

          運(yùn)行結(jié)果:

          /strUtil format:這只是個(gè)占位符:我是占位符

          4、ClassPathResource

          獲取 classPath 下的文件,在 Tomcat 等容器下,classPath一般是 WEB-INF/classes

          @Test(description="ClassPath單一資源訪問類:在classPath下查找文件")
          public void classPath() throws IOException {
           //獲取定義在src/main/resources文件夾中的配置文件
           ClassPathResource resource=new ClassPathResource("generator.properties");
           Properties properties=new Properties();
           properties.load(resource.getStream());
           log.info("/classPath:{}", properties);
          }

          運(yùn)行結(jié)果:

          /classPath:{jdbc.userId=root, jdbc.password=root, jdbc.driverClass=com.mysql.cj.jdbc.Driver, jdbc.connectionURL=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai}

          5、ReflectUtil

          Java反射工具類,可用于反射獲取類的方法及創(chuàng)建對(duì)象。

          @Test(description="ReflectUtil使用:Java反射工具類")
          public void reflectUtil() {
           //獲取某個(gè)類的所有方法
           Method[] methods=ReflectUtil.getMethods(Dog.class);
           //獲取某個(gè)類的指定方法
           Method method=ReflectUtil.getMethod(Dog.class, "getName");
           //使用反射來創(chuàng)建對(duì)象
           Dog dog=ReflectUtil.newInstance(Dog.class);
           //反射執(zhí)行對(duì)象的方法
           ReflectUtil.invoke(dog, "setName","大黃");
           log.info(dog.getName());
          }

          Dog

          Data
          @Builder
          @NoArgsConstructor
          @AllArgsConstructor
          public class Dog {
           private String name;
           private Float weight;
          }

          運(yùn)行結(jié)果:

          大黃

          6、NumberUtil

          數(shù)字處理工具類,可用于各種類型數(shù)字的加減乘除操作及判斷類型。

          @Test(description="NumberUtil使用:數(shù)字處理工具類")
          public void numberUtil() {
           double n1=1.234;
           double n2=1.234;
           double result;
           //對(duì)float、double、BigDecimal做加減乘除操作
           result=NumberUtil.add(n1, n2);
           result=NumberUtil.sub(n1, n2);
           result=NumberUtil.mul(n1, n2);
           result=NumberUtil.div(n1, n2);
           //保留兩位小數(shù)
           BigDecimal roundNum=NumberUtil.round(n1, 2);
           String n3="1.234";
           //判斷是否為數(shù)字、整數(shù)、浮點(diǎn)數(shù)
           NumberUtil.isNumber(n3);
           NumberUtil.isInteger(n3);
           NumberUtil.isDouble(n3);
          }

          7、BeanUtil

          JavaBean的工具類,可用于Map與JavaBean對(duì)象的互相轉(zhuǎn)換以及對(duì)象屬性的拷貝。

          @Test(description="BeanUtil使用:JavaBean的工具類")
          public void beanUtil() {
           Dog dog=new Dog();
           dog.setName("大黃");
           dog.setWeight(5.14f);
          
           //Bean轉(zhuǎn)Map
           Map<String, Object> map=BeanUtil.beanToMap(dog);
           log.info("beanUtil bean to map:{}", map);
           //Map轉(zhuǎn)Bean
           Dog mapDog=BeanUtil.mapToBean(map, Dog.class, false);
           log.info("beanUtil map to bean:{}", mapDog);
           //Bean屬性拷貝
           Dog copyDog=new Dog();
           BeanUtil.copyProperties(dog, copyDog);
           log.info("beanUtil copy properties:{}", copyDog);
          }

          運(yùn)行結(jié)果:

          beanUtil bean to map:{name=大黃, weight=5.14}
          beanUtil map to bean:Dog(name=大黃, weight=5.14)
          beanUtil copy properties:Dog(name=大黃, weight=5.14)

          8、CollUtil

          集合操作的工具類,定義了一些常用的集合操作。

          @Test(description="CollUtil使用:集合工具類")
          public void collUtil() {
           //數(shù)組轉(zhuǎn)換為列表
           String[] array=new String[]{"a", "b", "c", "d", "e"};
           List<String> list=CollUtil.newArrayList(array);
           //join:數(shù)組轉(zhuǎn)字符串時(shí)添加連接符號(hào)
           String joinStr=CollUtil.join(list, ",");
           log.info("collUtil join:{}", joinStr);
           //將以連接符號(hào)分隔的字符串再轉(zhuǎn)換為列表
           List<String> splitList=StrUtil.split(joinStr, ',');
           log.info("collUtil split:{}", splitList);
           //創(chuàng)建新的Map、Set、List
           HashMap<Object, Object> newMap=CollUtil.newHashMap();
           HashSet<Object> newHashSet=CollUtil.newHashSet();
           ArrayList<Object> newList=CollUtil.newArrayList();
           //判斷列表是否為空
           CollUtil.isEmpty(list);
           CollUtil.isNotEmpty(list);
          }

          運(yùn)行結(jié)果:

          collUtil join:a,b,c,d,e
          collUtil split:[a, b, c, d, e]
          

          9、MapUtil

          Map操作工具類,可用于創(chuàng)建 Map 對(duì)象及判斷 Map 是否為空。

          @Test(description="MapUtil使用:Map工具類")
          public void mapUtil() {
           //將多個(gè)鍵值對(duì)加入到Map中
           Map<Object, Object> map=MapUtil.of(new String[][]{
             {"key1", "value1"},
             {"key2", "value2"},
             {"key3", "value3"}
           });
           //判斷Map是否為空
           MapUtil.isEmpty(map);
           MapUtil.isNotEmpty(map);
          }

          10、SecureUtil

          加密解密工具類,可用于 MD5 加密。

          @Test(description="SecureUtil使用:加密解密工具類")
          public void secureUtil() {
           //MD5加密
           String str="123456";
           String md5Str=SecureUtil.md5(str);
           log.info("secureUtil md5:{}", md5Str);
          }
          

          運(yùn)行結(jié)果:

          secureUtil md5:e10adc3949ba59abbe56e057f20f883e

          11、CaptchaUtil

          驗(yàn)證碼工具類,可用于生成圖形驗(yàn)證碼。

          @Test(description="CaptchaUtil使用:圖形驗(yàn)證碼")
          public void captchaUtil(HttpServletRequest request, HttpServletResponse response) {
           //生成驗(yàn)證碼圖片
           LineCaptcha lineCaptcha=CaptchaUtil.createLineCaptcha(200, 100);
           try {
            request.getSession().setAttribute("CAPTCHA_KEY", lineCaptcha.getCode());
            response.setContentType("image/png");//告訴瀏覽器輸出內(nèi)容為圖片
            response.setHeader("Pragma", "No-cache");//禁止瀏覽器緩存
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expire", 0);
            lineCaptcha.write(response.getOutputStream());
           } catch (IOException e) {
            e.printStackTrace();
           }
          }

          12、Validator

          字段驗(yàn)證器,驗(yàn)證給定字符串是否滿足指定條件,一般用在表單字段驗(yàn)證里。

          @Test(description="Validator使用:字段驗(yàn)證器")
          public void validator() {
           //判斷是否為郵箱地址
           boolean result=Validator.isEmail("zuozewei@hotmail.com");
           log.info("Validator isEmail:{}", result);
           //判斷是否為手機(jī)號(hào)碼
           result=Validator.isMobile("18911111111");
           log.info("Validator isMobile:{}", result);
           //判斷是否為IPV4地址
           result=Validator.isIpv4("192.168.3.101");
           log.info("Validator isIpv4:{}", result);
           //判斷是否為漢字
           result=Validator.isChinese("你好");
           log.info("Validator isChinese:{}", result);
           //判斷是否為身份證號(hào)碼(18位中國(guó))
           result=Validator.isCitizenId("123456");
           log.info("Validator isCitizenId:{}", result);
           //判斷是否為URL
           result=Validator.isUrl("http://www.7d.com");
           log.info("Validator isUrl:{}", result);
           //判斷是否為生日
           result=Validator.isBirthday("2020-09-17");
           log.info("Validator isBirthday:{}", result);
          }

          運(yùn)行結(jié)果:

          Validator isEmail:true
          Validator isMobile:true
          Validator isIpv4:true
          Validator isChinese:true
          Validator isCitizenId:false
          Validator isUrl:true
          Validator isBirthday:true

          13、JSONUtil

          JSON 解析工具類,針對(duì) JSONObject 和 JSONArray 的靜態(tài)快捷方法集合。

          @Test(description="JSONUtil使用:JSON解析工具類")
          public void jsonUtil() {
           Dog dog=new Dog();
           dog.setName("大黃");
           dog.setWeight(5.14f);
          
           //對(duì)象轉(zhuǎn)化為JSON字符串
           String jsonStr=JSONUtil.parse(dog).toString();
           log.info("jsonUtil parse:{}", jsonStr);
           //JSON字符串轉(zhuǎn)化為對(duì)象
           Dog dogBean=JSONUtil.toBean(jsonStr, Dog.class);
           log.info("jsonUtil toBean:{}", dogBean);
           List<Dog> dogList=new ArrayList<>();
           dogList.add(dog);
           String jsonListStr=JSONUtil.parse(dogList).toString();
           //JSON字符串轉(zhuǎn)化為列表
           dogList=JSONUtil.toList(new JSONArray(jsonListStr), Dog.class);
           log.info("jsonUtil toList:{}", dogList);
          }
          

          運(yùn)行結(jié)果:

          jsonUtil parse:{"weight":5.14,"name":"大黃"}
          jsonUtil toBean:Dog(name=大黃, weight=5.14)
          jsonUtil toList:[Dog(name=大黃, weight=5.14)]

          14、RandomUtil

          隨機(jī)工具類,RandomUtil 主要針對(duì) JDK 中 Random 對(duì)象做封裝。

          @Test(description="RandomUtil使用:隨機(jī)工具類")
          public void randomUtil() {
           int result;
           String uuid;
           //獲得指定范圍內(nèi)的隨機(jī)數(shù)
           result=RandomUtil.randomInt(1, 100);
           log.info("randomInt:{}",StrUtil.toString(result));
           //獲得隨機(jī)UUID
           uuid=RandomUtil.randomUUID();
           log.info("randomUUID:{}", uuid);
          }

          運(yùn)行結(jié)果:

          randomInt:9
          randomUUID:8aec5890-72ab-4d72-a37d-d36acba83b58

          15、DigestUtil

          摘要算法工具類,支持常見摘要算法 MD2、MD5、SHA-1、SHA-256、SHA-384、SHA-512等。

          @Test(description="DigestUtil使用:摘要算法工具類")
          public void digestUtil() {
           String password="123456";
           //計(jì)算MD5摘要值,并轉(zhuǎn)為16進(jìn)制字符串
           String result=DigestUtil.md5Hex(password);
           log.info("DigestUtil md5Hex:{}", result);
           //計(jì)算SHA-256摘要值,并轉(zhuǎn)為16進(jìn)制字符串
           result=DigestUtil.sha256Hex(password);
           log.info("DigestUtil sha256Hex:{}", result);
           //生成Bcrypt加密后的密文,并校驗(yàn)
           String hashPwd=DigestUtil.bcrypt(password);
           boolean check=DigestUtil.bcryptCheck(password,hashPwd);
           log.info("DigestUtil bcryptCheck:{}", check);
          }
          

          運(yùn)行結(jié)果:

          DigestUtil md5Hex:e10adc3949ba59abbe56e057f20f883e
          DigestUtil sha256Hex:8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92
          DigestUtil bcryptCheck:true

          16、HttpUtil

          Http客戶端工具類,應(yīng)對(duì)簡(jiǎn)單場(chǎng)景下Http請(qǐng)求的工具類封裝,此工具封裝了HttpRequest對(duì)象常用操作,可以保證在一個(gè)方法之內(nèi)完成Http請(qǐng)求。

          此模塊基于JDK的HttpUrlConnection封裝完成,完整支持https、代理和文件上傳。

          @Test(description="HttpUtil使用:Http請(qǐng)求工具類")
          public void httpUtil() {
           String response=HttpUtil.get("http://example.com/");
           log.info("HttpUtil get:{}", response);
          }

          運(yùn)行結(jié)果:

          2020-09-17 18:48:27.328  INFO 12004 --- [           main] com.zuozewei.demo.example.example        : HttpUtil get:<!doctype html>
          <html>
          <head>
              <title>Example Domain</title>
          
              <meta charset="utf-8" />
              <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
              <meta name="viewport" content="width=device-width, initial-scale=1" />
              <style type="text/css">
              body {
                  background-color: #f0f0f2;
                  margin: 0;
                  padding: 0;
                  font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
                  
              }
              div {
                  width: 600px;
                  margin: 5em auto;
                  padding: 2em;
                  background-color: #fdfdff;
                  border-radius: 0.5em;
                  box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
              }
              a:link, a:visited {
                  color: #38488f;
                  text-decoration: none;
              }
              @media (max-width: 700px) {
                  div {
                      margin: 0 auto;
                      width: auto;
                  }
              }
              </style>    
          </head>
          
          <body>
          <div>
              <h1>Example Domain</h1>
              <p>This domain is for use in illustrative examples in documents. You may use this
              domain in literature without prior coordination or asking for permission.</p>
              <p><a href="https://www.iana.org/domains/example">More information...</a></p>
          </div>
          </body>
          </html>

          17、其他工具類

          Hutool中的工具類很多,可以參考:https://www.hutool.cn/

          六、小結(jié)

          測(cè)試開發(fā)過程中要善于半開源,半代碼的方式,節(jié)省開發(fā)時(shí)間,合理利用輪子,提高工作效率。


          文章源碼:

          • https://github.com/zuozewei/blog-example/tree/master/Java-api-test/11-tools/springboot-hutool-demo


          參考資料:

          • [1]:https://www.hutool.cn/docs

          家好,很高興又見面了,我是姜茶的編程筆記,我們一起學(xué)習(xí)前端相關(guān)領(lǐng)域技術(shù),共同進(jìn)步,也歡迎大家關(guān)注、點(diǎn)贊、收藏、轉(zhuǎn)發(fā),您的支持是我不斷創(chuàng)作的動(dòng)力

          在 JavaScript 中,Array.prototype.map 是一個(gè)常用的方法,它對(duì)數(shù)組的每個(gè)元素調(diào)用提供的函數(shù),并返回一個(gè)新數(shù)組,包含函數(shù)的返回值。為了更好地理解其內(nèi)部機(jī)制,今天我們將從零開始,實(shí)現(xiàn)一個(gè)自定義的 map 方法,并詳細(xì)解析其實(shí)現(xiàn)步驟。

          實(shí)現(xiàn)自定義 map 方法

          首先,我們創(chuàng)建一個(gè) myMap 方法,將其添加到 Array.prototype 上。這個(gè)方法接受兩個(gè)參數(shù):一個(gè)回調(diào)函數(shù) callback 和一個(gè)可選的 thisArg 參數(shù)。callback 函數(shù)將對(duì)數(shù)組的每個(gè)元素進(jìn)行操作,thisArg 是執(zhí)行回調(diào)函數(shù)時(shí)的 this 值。

          Array.prototype.myMap=function(callback, thisArg) {
            // 將傳入的數(shù)組轉(zhuǎn)換為對(duì)象
            const O=Object(this);
          
            // 獲取數(shù)組的長(zhǎng)度
            const len=O.length >>> 0;
          
            // 如果 callback 不是函數(shù),則拋出 TypeError
            if (typeof callback !=='function') {
              throw new TypeError(callback + ' is not a function');
            }
          
            // 創(chuàng)建一個(gè)新的數(shù)組,用于存儲(chǔ)映射結(jié)果
            const result=new Array(len);
          
            // 遍歷數(shù)組并應(yīng)用回調(diào)函數(shù)
            for (let i=0; i < len; i++) {
              // 只有當(dāng)元素在數(shù)組中存在時(shí)才進(jìn)行映射
              if (i in O) {
                // 調(diào)用回調(diào)函數(shù),傳遞 thisArg 作為 this 值,并傳遞當(dāng)前元素、索引、數(shù)組本身
                result[i]=callback.call(thisArg, O[i], i, O);
              }
            }
          
            return result;
          };
          

          讓我們逐步解析這個(gè)實(shí)現(xiàn)。

          1. 將傳入的數(shù)組轉(zhuǎn)換為對(duì)象

          我們首先將 this 轉(zhuǎn)換為一個(gè)對(duì)象,以便在方法中使用:

          const O=Object(this);
          

          2. 獲取數(shù)組的長(zhǎng)度

          使用 >>> 0 操作符將長(zhǎng)度轉(zhuǎn)換為無符號(hào)整數(shù),確保其為正整數(shù):

          const len=O.length >>> 0;
          

          3. 檢查回調(diào)函數(shù)

          如果 callback 不是函數(shù),則拋出一個(gè) TypeError

          if (typeof callback !=='function') {
            throw new TypeError(callback + ' is not a function');
          }
          

          4. 創(chuàng)建結(jié)果數(shù)組

          我們創(chuàng)建一個(gè)與原數(shù)組長(zhǎng)度相同的新數(shù)組,用于存儲(chǔ)映射結(jié)果:

          const result=new Array(len);
          

          5. 遍歷數(shù)組并應(yīng)用回調(diào)函數(shù)

          使用 for 循環(huán)遍歷數(shù)組的每個(gè)索引。如果當(dāng)前索引存在于數(shù)組中,則調(diào)用回調(diào)函數(shù),并將結(jié)果存儲(chǔ)在 result 數(shù)組中:

          for (let i=0; i < len; i++) {
            if (i in O) {
              result[i]=callback.call(thisArg, O[i], i, O);
            }
          }
          

          6. 返回結(jié)果數(shù)組

          最后,我們返回存儲(chǔ)映射結(jié)果的 result 數(shù)組:

          return result;
          

          示例使用

          下面是一個(gè)示例,展示如何使用自定義的 myMap 方法:

          const numbers=[1, 2, 3, 4, 5];
          const doubled=numbers.myMap(x=> x * 2);
          
          console.log(doubled); // 輸出 [2, 4, 6, 8, 10]
          

          在這個(gè)示例中,我們將 numbers 數(shù)組中的每個(gè)元素都乘以 2,并將結(jié)果存儲(chǔ)在 doubled 數(shù)組中。結(jié)果輸出為 [2, 4, 6, 8, 10],這與內(nèi)置的 Array.prototype.map 方法的行為一致。

          結(jié)論

          通過實(shí)現(xiàn)自定義的 map 方法,我們深入理解了 JavaScript 中數(shù)組的操作方式。希望你可以更好地掌握 map 方法的內(nèi)部機(jī)制,并提升你的 JavaScript 編程技巧。

          最后

          如果你有任何問題或建議,歡迎在評(píng)論區(qū)留言交流!祝你編程愉快!

          . 背景

          隨著公司業(yè)務(wù)的不斷擴(kuò)張,用戶流量在不斷提升,研發(fā)體系的規(guī)模和復(fù)雜性也隨之增加。線上服務(wù)的穩(wěn)定性也越來越重要,服務(wù)性能問題,以及容量問題也越發(fā)明顯。

          因此有必要搭建一個(gè)有效壓測(cè)系統(tǒng),提供安全、高效、真實(shí)的線上全鏈路壓測(cè)服務(wù),為線上服務(wù)保駕護(hù)航。

          關(guān)于全鏈路壓測(cè)的建設(shè),業(yè)界已經(jīng)有了非常多文章,但是涉及到具體的技術(shù)實(shí)現(xiàn)方面,卻很少介紹。本文想從全鏈路壓測(cè)系統(tǒng),從設(shè)計(jì)到落地整個(gè)實(shí)踐過程,來詳細(xì)介紹下全鏈路壓測(cè)系統(tǒng)是具體是如何設(shè)計(jì),以及如何落地的。希望能從技術(shù)落地實(shí)踐的角度,給同行業(yè)的同學(xué)一些參考和啟發(fā)。

          2. 解決方案

          2.1 業(yè)內(nèi)實(shí)踐

          全鏈路壓測(cè)在業(yè)內(nèi)已經(jīng)有了廣泛的實(shí)踐,如阿里的 Amazon、PTS[1][2],美團(tuán)的 Quake[3][4],京東的的 ForceBOT[5],高德的 TestPG[6]等等,都為我們提供豐富的實(shí)踐經(jīng)驗(yàn),和大量?jī)?yōu)秀的技術(shù)方案。我們廣泛吸收了各大互聯(lián)網(wǎng)公司的全鏈路壓測(cè)建設(shè)經(jīng)驗(yàn),并基于字節(jié)跳動(dòng)業(yè)務(wù)需求,設(shè)計(jì)開發(fā)了一個(gè)全鏈路壓測(cè)系統(tǒng) Rhino。

          2.1 架構(gòu)圖

          Rhino 平臺(tái)作為公司級(jí)的全鏈路壓測(cè)平臺(tái),它的目標(biāo)是對(duì)全公司所有業(yè)務(wù),提供單服務(wù)、全鏈路,安全可靠、真實(shí)、高效的壓測(cè),來幫助業(yè)務(wù)高效便捷的完成性能測(cè)試任務(wù),更精確評(píng)估線上服務(wù)性能&容量方面風(fēng)險(xiǎn)。

          因此在 Rhino 平臺(tái)設(shè)計(jì)之初,我們就定下以下目標(biāo):

          • 安全:所有壓測(cè)都是在線上完成的,所以理論上所有的壓測(cè)對(duì)線上用戶都是有損的。壓測(cè)平臺(tái)將從服務(wù)狀態(tài),以及壓測(cè)數(shù)據(jù)兩方面去保證壓測(cè)的安全性。
          • 高效:較少壓測(cè)腳本編寫成本,數(shù)據(jù)構(gòu)造和壓測(cè)監(jiān)控成本,盡量自動(dòng)化完成壓測(cè)過程的各個(gè)階段。
          • 準(zhǔn)確:精確的壓力控制,準(zhǔn)確的鏈路壓測(cè)監(jiān)控,精確的壓測(cè)報(bào)告結(jié)果,以及性能&容量數(shù)據(jù)。
          • 高覆蓋:需要支撐公司內(nèi)不同的業(yè)務(wù)線的壓測(cè)需求,如搜索,廣告,電商,教育,游戲等等。

          Rhino 是一個(gè)分布式全鏈路壓測(cè)系統(tǒng),可以通過水平擴(kuò)展,來實(shí)現(xiàn)模擬海量用戶真實(shí)的業(yè)務(wù)操作場(chǎng)景,對(duì)線上各種業(yè)務(wù)進(jìn)行全方位的性能測(cè)試。它主要分為控制中心(Rhino Master)模塊,壓測(cè)鏈路服務(wù)模塊,監(jiān)控系統(tǒng)模塊,壓測(cè)引擎模塊,如圖。(每一個(gè)模塊都是由多個(gè)微服務(wù)來完成的。如下圖每個(gè)實(shí)線圖都代表一個(gè)微服務(wù)或多個(gè)微服務(wù))。

          3. 核心功能介紹

          • 搭建全鏈路壓測(cè)平臺(tái),最核心主要有:數(shù)據(jù)構(gòu)造、壓測(cè)隔離、鏈路治理、任務(wù)調(diào)度、壓測(cè)熔斷、壓測(cè)引擎、壓測(cè)監(jiān)控等。下面我們將從這些方面詳細(xì)介紹下,在 Rhino 平臺(tái)中是如何設(shè)計(jì)和實(shí)現(xiàn)的。

          3.1 數(shù)據(jù)構(gòu)造

          壓測(cè)過程中數(shù)據(jù)構(gòu)造是最重要,也是最為復(fù)雜的環(huán)節(jié)。壓測(cè)數(shù)據(jù)的建模,直接影響了壓測(cè)結(jié)果的準(zhǔn)確性。

          • 對(duì)于服務(wù)性能缺陷掃描,性能調(diào)優(yōu),以及新上線服務(wù),推薦構(gòu)造 Fake 數(shù)據(jù),來壓測(cè)指定路徑。
          • 對(duì)于線上容量規(guī)劃,性能能力驗(yàn)證,以及性能 Diff,推薦使用線上真實(shí)流量,使壓測(cè)結(jié)果更貼近真實(shí)情況。
          • 對(duì)于涉及到用戶賬號(hào),用戶登錄態(tài)保持的情況,推薦使用壓測(cè)專屬測(cè)試賬號(hào),避免影響線上真實(shí)用戶。

          基礎(chǔ)數(shù)據(jù)構(gòu)造

          為了高效的構(gòu)造特定的 Fake 壓測(cè)數(shù)據(jù),Rhino 壓測(cè)平臺(tái)提供大量數(shù)據(jù)構(gòu)造方式:

          • CSV 文件:按列分割數(shù)據(jù),字段名取 CSV 文件第一行。數(shù)據(jù)讀取方式是按行遞增循環(huán)。如果一個(gè)壓測(cè)任務(wù)會(huì)拆分成多個(gè) Job,那么數(shù)據(jù)文件也會(huì)拆分,避免 Job 之間的數(shù)據(jù)重復(fù)。
          • 自增:變量類型均為數(shù)字類型。每次發(fā)壓時(shí)+1,到最大值后從最小值循環(huán)使用。
          • 隨機(jī):變量類型均為數(shù)字類型,每次發(fā)壓時(shí)隨機(jī)生成。
          • 常量:Constant,可自定義為任意值。

          壓測(cè)賬號(hào)

          在壓測(cè)過程中,有些壓測(cè)請(qǐng)求需要進(jìn)行登錄,并保持會(huì)話;此外在很多壓測(cè)請(qǐng)求中涉及到用戶賬號(hào)信息 UserID,DeviceID 等數(shù)據(jù)。用戶賬號(hào)的構(gòu)造問題,一直是壓測(cè)過程中非常棘手的問題。Rhino 平臺(tái)打通的用戶中心,設(shè)置了壓測(cè)專屬的賬號(hào)服務(wù),完美地解決了壓測(cè)過程中的登錄態(tài),以及測(cè)試賬號(hào)等問題。具體流程和使用界面,如下圖。

          3.2 壓測(cè)隔離

          壓測(cè)隔離中需要解決的壓測(cè)流量隔離,以及壓測(cè)數(shù)據(jù)的隔離。

          壓測(cè)流量隔離,主要是通過構(gòu)建壓測(cè)環(huán)境來解決,如線下壓測(cè)環(huán)境,或泳道化/Set 化建設(shè),將壓測(cè)流量與線上流程完全隔離。優(yōu)點(diǎn)是壓測(cè)流量與線上流量完全隔離,不會(huì)影響到線上用戶。缺點(diǎn):機(jī)器資源及維護(hù)成本高,且壓測(cè)結(jié)果需要經(jīng)過一定的換算,才能得線上容量,結(jié)果準(zhǔn)確性存在一定的問題。目前公司內(nèi)壓測(cè)都是在線上集群上完成的,線上泳道化正在建設(shè)中。

          壓測(cè)數(shù)據(jù)隔離,主要是通過對(duì)壓測(cè)流量進(jìn)行染色,讓線上服務(wù)能識(shí)別哪些是壓測(cè)流量,哪些是正常流量,然后對(duì)壓測(cè)流量進(jìn)行特殊處理,以達(dá)到數(shù)據(jù)隔離的目的。目前 Rhino 平臺(tái)整體壓測(cè)隔離框架如圖。

          壓測(cè)標(biāo)記

          壓測(cè)標(biāo)記就是最常見的壓測(cè)流量染色的方式。

          • 對(duì)于 RPC 協(xié)議,會(huì)在請(qǐng)求的頭部中增加一個(gè) Key:Value 的字段作為壓測(cè)標(biāo)記。
          • 對(duì)于 HTTP 和其他協(xié)議,會(huì)在請(qǐng)求頭,自動(dòng)注入一個(gè) Stress 標(biāo)記(Key-Value) 。
          • 壓測(cè)標(biāo)記 Key:Value,其中 key 是固定的 Stress_Tag 值,但是每個(gè)壓測(cè)任務(wù)都有唯一的 Stress_Value 值,主要用于解決壓測(cè)數(shù)據(jù)沖突,以及性能問題定位。

          壓測(cè)標(biāo)記透?jìng)?/strong>

          目前公司內(nèi)各個(gè)基礎(chǔ)組件、存儲(chǔ)組件,以及 RPC 框架都已經(jīng)支持了壓測(cè)標(biāo)記的透?jìng)鳌F湓硎菍簻y(cè)標(biāo)記的 KV 值存入 Context 中,然后在所有下游請(qǐng)求中都帶上該 Context,下游服務(wù)可以根據(jù) Context 中壓測(cè)標(biāo)記完成對(duì)壓測(cè)流量的處理。在實(shí)際業(yè)務(wù)中,代碼改造也非常簡(jiǎn)單,只需要透?jìng)?Context 即可。

          Golang 服務(wù): 將壓測(cè)標(biāo)記寫入 Context 中。

          Python 服務(wù):利用 threading.local()存儲(chǔ)線程 Context。

          Java 服務(wù):利用 ThreadLocal 存儲(chǔ)線程 Context。

          壓測(cè)開關(guān)

          為了解決線上壓測(cè)安全問題,我們還引入了壓測(cè)開關(guān)組件。

          • 每個(gè)服務(wù)每個(gè)集群,都有一個(gè)壓測(cè)開關(guān)。只有打開壓測(cè)開關(guān)時(shí),壓測(cè)流量才能流入到服務(wù)內(nèi),否則就會(huì)被底層微服務(wù)框架直接拒絕,業(yè)務(wù)層無感知。
          • 在每個(gè) IDC 區(qū)域,都會(huì)有一個(gè)全局的壓測(cè)總開關(guān)。只有打開了這個(gè)全局壓測(cè)開關(guān),壓測(cè)流量才被允許在這個(gè) IDC 內(nèi)流轉(zhuǎn)。
          • 當(dāng)線上出現(xiàn)壓測(cè)問題,除了從源頭關(guān)閉壓測(cè)流量以外,關(guān)閉目標(biāo)服務(wù)的壓測(cè)開關(guān),也能立即阻斷壓測(cè)流量。

          壓測(cè)數(shù)據(jù)隔離

          線上壓測(cè)中,最復(fù)雜的問題就是壓測(cè)鏈路中涉及到寫操作,如何避免污染線上數(shù)據(jù),并且能保證壓測(cè)請(qǐng)求保持和線上相同的請(qǐng)求路徑。業(yè)界有很多解決方案,常見的有影子表,影子庫(kù),以及數(shù)據(jù)偏移,如圖[7]。

          Rhino 平臺(tái)針對(duì)不同存儲(chǔ),有不同的解決方案:

          • MySQL、MongoDB:影子表。SDK 判斷是否是壓測(cè)流量,若是則根據(jù)配置映射至新表名。配置策略有兩種,一是讀寫影子表,二是讀線上表、寫影子表。
          • Redis:Redis Key 加上 Stress 前綴。如 Stress_Tag=Valuex,那么讀寫 Redis 的 Key=Valuex_Key。這樣可以解決多個(gè)壓測(cè)任務(wù)數(shù)據(jù)沖突的問題。壓測(cè)結(jié)束后,只需要對(duì) Prefix=Valuex 做清除或過期操作即可。
          • MQ:對(duì)于消息隊(duì)列,Rhino 平臺(tái)有兩種策略。一是直接丟棄,然后針對(duì)消息隊(duì)列的性能,單獨(dú)進(jìn)行壓測(cè);二是在 Header 中透?jìng)鲏簻y(cè)標(biāo)記,Consumer 根據(jù)壓測(cè)標(biāo)記和業(yè)務(wù)需求,再做特殊處理。默認(rèn)走丟棄策略,業(yè)務(wù)方可根據(jù)需求進(jìn)行配置。
          • 其他存儲(chǔ),如 ES,ClickHouse 等,都有壓測(cè)集群。壓測(cè)時(shí),會(huì)將壓測(cè)請(qǐng)求打到指定的壓測(cè)集群中。

          服務(wù)壓測(cè)改造

          在壓測(cè)之前,需要對(duì)服務(wù)進(jìn)行壓測(cè)驗(yàn)證。對(duì)于不滿足壓測(cè)要求(即壓測(cè)數(shù)據(jù)隔離)的服務(wù),需要進(jìn)行壓測(cè)改造。

          1. 壓測(cè)驗(yàn)證:對(duì)于存儲(chǔ)服務(wù),在不打開壓測(cè)開關(guān)的前提下,通過壓測(cè)請(qǐng)求,發(fā)送讀寫操作都是會(huì)被拒絕。如果沒有拒絕,說明在操作存儲(chǔ)服務(wù)時(shí),沒有帶上壓測(cè) Context,需要進(jìn)行改造。
          2. 壓測(cè)改造:壓測(cè)改造是線上全鏈路壓測(cè)推進(jìn)中非常關(guān)鍵,而又非常困難的一個(gè)環(huán)節(jié)。對(duì)于已經(jīng)上線的服務(wù),壓測(cè)改造還極有可能會(huì)引入新的 BUG,所以經(jīng)常推動(dòng)起來比較困難。因此為了解決這些問題,Rhino 平臺(tái)有以下幾個(gè)解決方案:

          a. 盡量減少代碼改動(dòng),并給出完整的指導(dǎo)手冊(cè)及代碼示例,減少 RD 的工作量,降低代碼錯(cuò)誤的可能性

          b. 提供簡(jiǎn)單便捷的線上線下 HTTP&RPC 的壓測(cè)請(qǐng)求 Debug 工具,方便代碼改動(dòng)的驗(yàn)證

          c. 對(duì)于新項(xiàng)目,在項(xiàng)目開始初期,就將壓測(cè)改造加入項(xiàng)目開發(fā)規(guī)范中,減少后期的代碼改動(dòng)

          3.3 鏈路治理

          鏈路梳理

          請(qǐng)求調(diào)用鏈,對(duì)于線上壓測(cè)是非常重要的:

          • 提供清晰壓測(cè)流量地圖,并提供完整的鏈路監(jiān)控。
          • 完成服務(wù)依賴的梳理,檢測(cè)壓測(cè)所依賴的服務(wù)/中臺(tái)是否具備壓測(cè)的條件,是否需要壓測(cè)改造。
          • 鏈接壓測(cè)開關(guān)管理,壓測(cè)上下游周知等。

          Rhino 平臺(tái)通過公司的流式日志系統(tǒng)來完成調(diào)用鏈檢索的。一個(gè)服務(wù)在被請(qǐng)求或者請(qǐng)求下游時(shí),都會(huì)透?jìng)饕粋€(gè) LogID。RPC 框架會(huì)打印調(diào)用鏈日志(包括 RPC 日志-調(diào)用者日志,Access 日志-被調(diào)用者日志),所有日志中都會(huì)包含這個(gè) LogID。通過 LogID 將一個(gè)請(qǐng)求所經(jīng)過的所有服務(wù)日志串起來,就完成調(diào)用鏈檢索。

          Rhino 平臺(tái)在公司流式日志系統(tǒng)提供的鏈路梳理功能基礎(chǔ)上,進(jìn)行了進(jìn)一步優(yōu)化,以滿足壓測(cè)需要:

          • 自動(dòng)梳理:由于公司采用微服務(wù)架構(gòu),每個(gè)請(qǐng)求背后的調(diào)用鏈路及其復(fù)雜,單純靠人工維護(hù)是無法完成的。 用戶只需要提供請(qǐng)求中 LogID,Rhino 平臺(tái)就能快速梳理出該請(qǐng)求經(jīng)過的服務(wù)節(jié)點(diǎn),如圖。
          • 實(shí)時(shí)梳理:由于線上服務(wù)不斷在變化,上線下線新增等,因此同一個(gè)請(qǐng)求的調(diào)用鏈也是不斷變化的。Rhino 平臺(tái)建議一般使用 1 個(gè)小時(shí)內(nèi)的 LogID 進(jìn)行梳理。
          • 多調(diào)鏈路合并:同一個(gè)接口,不同參數(shù)下的調(diào)用鏈?zhǔn)遣槐M相同的。Rhino 平臺(tái)會(huì)將多個(gè) LogID 梳理的結(jié)果自動(dòng)進(jìn)行合并,來補(bǔ)全調(diào)用鏈,保證鏈路梳理結(jié)果的準(zhǔn)確性和完整性。

          測(cè)周知

          雖然 Rhino 平臺(tái)對(duì)于壓測(cè)有很多的安全保障措施,但是對(duì)于大型壓測(cè),保證信息的通暢流通也是非常重要的。因此在壓測(cè)周知方面,Rhino 平臺(tái)也提供了很多解決方案:

          • 一鍵拉群:梳理完鏈路后,在壓測(cè)前可以一鍵拉群,將鏈路中上下游服務(wù)的 Owner 拉到同一個(gè)群里,同步壓測(cè)信息。
          • 壓測(cè)周知:每個(gè)壓測(cè)開始執(zhí)行時(shí),都會(huì)向壓測(cè)周知群里推送消息,如壓測(cè) QPS,壓測(cè)時(shí)長(zhǎng)等信息。

          • 壓測(cè)事件:在壓測(cè)開始執(zhí)行時(shí),Rhino 平臺(tái)還會(huì)向目標(biāo)服務(wù)的事件隊(duì)列中發(fā)送一個(gè)壓測(cè)事件,方便快速評(píng)估/定位穩(wěn)定性問題是否是壓測(cè)導(dǎo)致,減少 RD 線上問題排查的干擾。

          壓測(cè)開關(guān)管理

          在壓測(cè)之前,需要開啟整體鏈路的壓測(cè)開關(guān)的,否則壓測(cè)流量就會(huì)被服務(wù)拒絕,導(dǎo)致壓測(cè)失敗。

          • 一鍵開啟:在壓測(cè)執(zhí)行之前,Rhino 平臺(tái)可以一鍵開啟鏈接上所有節(jié)點(diǎn)的壓測(cè)開關(guān)。
          • 壓測(cè)開關(guān)開啟周知:壓測(cè)開關(guān)開啟時(shí),Rhino 平臺(tái)會(huì)自動(dòng)給對(duì)應(yīng)服務(wù) Owner 推送相關(guān)信息,確保服務(wù) Owner 了解相關(guān)壓測(cè)信息,上游會(huì)有壓測(cè)流量會(huì)經(jīng)過其服務(wù)。
          • 靜默關(guān)閉:壓測(cè)開關(guān)到期后,Rhino 會(huì)自動(dòng)靜默關(guān)閉壓測(cè)開關(guān),以保證線上服務(wù)的安全。

          服務(wù) Mock

          對(duì)于調(diào)用鏈中不能壓測(cè)的服務(wù)(敏感服務(wù)),或者第三方服務(wù),為了壓測(cè)請(qǐng)求的完整性,就需要對(duì)這些服務(wù)進(jìn)行 Mock。業(yè)界通用的 Mock 方案有:

          1. 修改業(yè)務(wù)代碼,修改服務(wù)調(diào)用為空轉(zhuǎn)代碼。優(yōu)點(diǎn):實(shí)現(xiàn)成本低。 缺點(diǎn):返回值固定,代碼&業(yè)務(wù)入侵高,推動(dòng)困難。如要 Mock 位置比較靠下游,超出部門覆蓋業(yè)務(wù)范圍,推動(dòng)就非常麻煩。
          2. 通用 Mock 服務(wù)。通用 MockServer,會(huì)根據(jù)不同用戶配置不同 Mock 規(guī)則,執(zhí)行對(duì)應(yīng)的響應(yīng)延時(shí),并返回對(duì)應(yīng)響應(yīng)數(shù)據(jù)。優(yōu)點(diǎn):無代碼入侵,業(yè)務(wù)方無感知。 缺點(diǎn):實(shí)現(xiàn)成本高。

          由于字節(jié)整個(gè)公司都采用微服務(wù)架構(gòu),導(dǎo)致一次壓測(cè)涉及鏈路都比較長(zhǎng),快速無業(yè)務(wù)入侵的 Mock

          方式成為了首選。Rhino 平臺(tái)是通過公司 Service Mesh 和 ByteMock 系統(tǒng)來實(shí)現(xiàn)了高效的,對(duì)業(yè)務(wù)透明的服務(wù) Mock。

          壓測(cè)執(zhí)行前,Rhino 平臺(tái)需要向 Service Mesh 注冊(cè)染色轉(zhuǎn)發(fā)規(guī)則,并向 Mock 服務(wù)注冊(cè) Mock 規(guī)則。然后在壓測(cè)流量中注入 Mock 染色標(biāo)記,才能完成服務(wù) Mock:

          1. 基于 Service Mesh 的染色流量轉(zhuǎn)發(fā)。首先需要在壓測(cè)流量中注入轉(zhuǎn)發(fā)染色標(biāo)記,并在 Service Mesh 中注冊(cè)對(duì)應(yīng)的轉(zhuǎn)發(fā)規(guī)則。Service Mesh 檢測(cè)到染色流量后,就會(huì)將其轉(zhuǎn)發(fā)到指定的 Mock Server 上,如圖。
          2. 基于 Mock Server 的請(qǐng)求規(guī)則匹配。首先在 Mock Server 上注冊(cè) Mock 規(guī)則,以及匹配的 Response 和響應(yīng)時(shí)延。當(dāng) Mock Server 接收到請(qǐng)求后,會(huì)根據(jù)規(guī)則進(jìn)行響應(yīng),如圖。

          3.4 發(fā)壓模式

          最小調(diào)度單元

          Rhino 平臺(tái)中,壓測(cè) Agent 就是一個(gè)最小調(diào)度單元。一次壓測(cè)任務(wù),通常會(huì)拆分成多個(gè)子 Job,然后下發(fā)到多個(gè) Agent 上來完成。

          • 最小化容器部署,減少資源浪費(fèi)。壓測(cè)對(duì)機(jī)器資源消耗是非常高的,通常 CPU &Memory 的使用率都在 80%以上。但是沒有壓測(cè)執(zhí)行時(shí)間內(nèi),機(jī)器資源使用率<5%。如果長(zhǎng)期占用大量的資源,將會(huì)對(duì)機(jī)器資源造成極大的浪費(fèi)。壓測(cè) Agent 都采用容器化部署,并且每個(gè)容器的資源規(guī)格也盡可能小,這樣既能滿足日常壓測(cè)需求,也不會(huì)占用太多的機(jī)器資源。
          • 獨(dú)占 Agent,增加壓測(cè)執(zhí)行穩(wěn)定性:?jiǎn)蝹€(gè)容器內(nèi)只啟動(dòng)一個(gè) Agent 進(jìn)程,單個(gè) Agent 同時(shí)只能被一個(gè)壓測(cè)任務(wù)占用,避免多任務(wù)多進(jìn)程的干擾和資源競(jìng)爭(zhēng),增加壓測(cè)的穩(wěn)定性。
          • 動(dòng)態(tài)擴(kuò)容,支撐海量 QPS 發(fā)壓:壓測(cè)高峰期,Rhino 平臺(tái)會(huì)臨時(shí)申請(qǐng)機(jī)器資源,快速擴(kuò)容,完成海量 QPS 的支撐。壓測(cè)完成后,會(huì)立即釋放機(jī)器資源,減少資源浪費(fèi)。

          2020 年春節(jié)搶紅包壓測(cè)中,Rhino 臨時(shí)擴(kuò)容在 4000+個(gè)實(shí)例,支撐了單次 3kw+QPS 的壓測(cè),但日常 Rhino 平臺(tái)只部署了 100+個(gè)實(shí)例,就能滿足日常壓測(cè)需求。

          智能壓力調(diào)節(jié)

          • 動(dòng)態(tài)分配壓測(cè) Agent:在壓測(cè)過程,經(jīng)常出現(xiàn)壓測(cè) Agent 的 CPU/Memory 使用率過高(>90%),導(dǎo)致壓力上不去,達(dá)不到目標(biāo) QPS;或者壓測(cè)延時(shí)過高,壓測(cè)結(jié)果不準(zhǔn)確的問題。Rhino 平臺(tái)在發(fā)壓的過程中,會(huì)實(shí)時(shí)監(jiān)控每個(gè)壓測(cè) Agent 的 CPU/Memory 使用率,當(dāng)超過閾值時(shí)(>90%),會(huì)動(dòng)態(tài)分配額外的 Agent,以降低每個(gè) Agent 的負(fù)載,保證壓測(cè)的穩(wěn)定性。
          • 智能調(diào)節(jié)壓力:在壓測(cè)過程,通常需要不斷的調(diào)節(jié) QPS 大小,以達(dá)到性能壓測(cè)目標(biāo)。這過程非常耗費(fèi)精力和時(shí)間。Rhino 平臺(tái),可以根據(jù)壓測(cè)任務(wù)設(shè)定的性能指標(biāo),智能調(diào)節(jié) QPS 大小,當(dāng)達(dá)到壓測(cè)目標(biāo)后,會(huì)自動(dòng)熔斷,停止壓測(cè)。

          壓測(cè)鏈路模擬

          Rhino 平臺(tái)默認(rèn)將全鏈路壓測(cè)分為公網(wǎng)壓測(cè)和內(nèi)網(wǎng)壓測(cè)。公網(wǎng)壓測(cè)主要 IDC 網(wǎng)絡(luò)帶寬,延時(shí),IDC 網(wǎng)關(guān)新建連接、轉(zhuǎn)發(fā)等能力;內(nèi)網(wǎng)壓測(cè),主要是壓測(cè)目標(biāo)服務(wù),目標(biāo)集群的 性能,容量等。

          • 對(duì)于內(nèi)網(wǎng)壓測(cè),默認(rèn)都要求同 IDC 內(nèi)發(fā)壓,減少網(wǎng)絡(luò)延時(shí)的干擾。
          • 對(duì)于公網(wǎng)壓測(cè),Rhino 平臺(tái)在公司 CDN 節(jié)點(diǎn)上都有部署 Agent 節(jié)點(diǎn),利用了 CDN 節(jié)點(diǎn)剩余計(jì)算能力,完成了公網(wǎng)壓測(cè)能力的建設(shè)。

          同城多機(jī)房,異地多機(jī)房

          Rhino 平臺(tái)在各個(gè) IDC 都有部署 Agent 集群。各個(gè) IDC 內(nèi)服務(wù)的壓測(cè),默認(rèn)會(huì)就近選擇壓測(cè) Agent,來減少網(wǎng)絡(luò)延時(shí)對(duì)壓測(cè)結(jié)果的干擾,使得壓測(cè)結(jié)果更精準(zhǔn),壓測(cè)問題定位更簡(jiǎn)單。

          邊緣計(jì)算節(jié)點(diǎn) Agent

          除了多機(jī)房部署之外,Rhino 平臺(tái)還在邊緣計(jì)算節(jié)點(diǎn)上也部署了壓測(cè) Agent,來模擬各種不同地域不同運(yùn)營(yíng)商的流量請(qǐng)求,確保流量來源,流量分布更貼近真實(shí)情況。在 Rhino 平臺(tái)上可以選擇不同地域不同運(yùn)營(yíng)商,從全國(guó)各個(gè)地區(qū)發(fā)起壓測(cè)流量。

          3.5 壓測(cè)熔斷

          為了應(yīng)對(duì)線上壓測(cè)風(fēng)險(xiǎn),Rhino 平臺(tái)提供兩種熔斷方式,來應(yīng)對(duì)壓測(cè)過程中的突發(fā)事件,來降低對(duì)線上服務(wù)造成的影響。

          基于告警監(jiān)控的熔斷

          每個(gè)壓測(cè)任務(wù),都可以關(guān)聯(lián)調(diào)用鏈中任意服務(wù)的告警規(guī)則。在壓測(cè)任務(wù)執(zhí)行過程,Rhino 平臺(tái)會(huì)主動(dòng)監(jiān)聽告警服務(wù)。 當(dāng)調(diào)用鏈中有服務(wù)出現(xiàn)了告警,會(huì)立即停止壓測(cè)。對(duì)于沒有關(guān)聯(lián)的告警,Rhino 平臺(tái)也會(huì)記錄下來,便于壓測(cè)問題定位。

          基于 Metric 的熔斷

          自定義監(jiān)控指標(biāo)及閾值,到達(dá)閾值后,也會(huì)自動(dòng)停止壓測(cè)。目前支持 CPU、Memory、 上游穩(wěn)定性、錯(cuò)誤日志,以及其他自定義指標(biāo)。

          此外,除了 Rhino 平臺(tái)自身提供的熔斷機(jī)制以外,公司服務(wù)治理架構(gòu)也提供了很多額外的熔斷機(jī)制,如壓測(cè)開關(guān),一鍵切斷壓測(cè)流量;過載保護(hù),服務(wù)過載時(shí)自動(dòng)丟棄壓測(cè)流量。

          3.6 任務(wù)模型

          HTTP 任務(wù)

          對(duì)于 HTTP 協(xié)議,參考了 Postman,全部可視化操作,保證所有人都能上手操作,極大降低了壓測(cè)的使用門檻和成本。

          RPC 任務(wù)

          對(duì)于 RPC 任務(wù),Rhino 也自動(dòng)完成了對(duì) IDL 的解析,然后轉(zhuǎn)換成 JSON 格式,便于用戶參數(shù)化處理。

          自定義-Go Plugin

          對(duì)于非 HTTP/RPC 的協(xié)議,以及有復(fù)雜邏輯的壓測(cè)任務(wù),Rhino 平臺(tái)也提供了完善的解決方案——Go Plugin。

          Go Plugin 提供了一種方式,通過在主程序和共享庫(kù)直接定義一系列的約定或者接口,就可以動(dòng)態(tài)加載其他人編譯的 Go 語言共享對(duì)象,使得主程序可以在編譯后動(dòng)態(tài)加載共享庫(kù),實(shí)現(xiàn)熱插拔的插件系統(tǒng)。此外主程序和共享庫(kù)的開發(fā)者不需要共享代碼,只要雙方的約定不變,修改共享庫(kù)后也不再需要重新編譯主程序。

          用戶只要根據(jù)規(guī)范要求,實(shí)現(xiàn)一段發(fā)壓業(yè)務(wù)邏輯代碼即可。Rhino 平臺(tái)可以自動(dòng)拉取代碼,觸發(fā)編譯。并將編譯后的插件 SO 文件分發(fā)到多個(gè)壓測(cè) Agent。 Agent 動(dòng)態(tài)加載 SO 文件,并發(fā)運(yùn)行起來,就可以達(dá)到壓測(cè)的目的。此外,Rhino 還針對(duì)常見 Go Plugin 壓測(cè)場(chǎng)景,建立了壓測(cè)代碼示例代碼庫(kù)。對(duì)于壓測(cè)新手,簡(jiǎn)單修改下業(yè)務(wù)邏輯代碼,就可以完成壓測(cè)了。這樣就解決了非常見協(xié)議,以及復(fù)雜壓測(cè)場(chǎng)景等的壓測(cè)問題。

          3.7 壓測(cè)引擎

          單 Agent 多引擎

          壓測(cè)調(diào)度的最小單元是壓測(cè) Agent,但是實(shí)際每個(gè) Agent 中有掛載多種壓測(cè)引擎的,來支撐不同的壓測(cè)場(chǎng)景。Rhino 平臺(tái)在壓測(cè)數(shù)據(jù)和壓測(cè)引擎之間增加了一個(gè)壓測(cè)引擎適配層,實(shí)現(xiàn)了壓測(cè)數(shù)據(jù)與壓測(cè)引擎的解耦。壓測(cè)引擎適配層,會(huì)根據(jù)選擇不同的壓測(cè)引擎,生成不同 Schema 的壓測(cè)數(shù)據(jù),啟用不同的引擎來完成壓測(cè),而這些對(duì)用戶是透明的。

          壓測(cè)引擎

          在壓測(cè)引擎上,我們有開源的壓測(cè)引擎,也有自研的壓測(cè)引擎。

          開源壓測(cè)引擎的優(yōu)點(diǎn)是維護(hù)人多,功能比較豐富,穩(wěn)定且性能好,缺點(diǎn)就是輸入格式固定,定制難度大。此外 Agent 與開源壓測(cè)引擎之間通常是不同進(jìn)程,進(jìn)程通信也存在比較大的問題,不容易控制。

          自研壓測(cè)引擎,優(yōu)點(diǎn)是和 Agent 通常運(yùn)行在單進(jìn)程內(nèi),比較容易控制;缺點(diǎn)可能就是性能稍微差一些。但是 Golang 天然支持高并發(fā),因此自研和開源之間的性能差距并不明顯。

          • HTTP 協(xié)議:默認(rèn) Gatling ,單機(jī)發(fā)壓性能非常好,遠(yuǎn)超于 Jmeter。對(duì)于智能壓測(cè),或動(dòng)態(tài)調(diào)節(jié)的情況,會(huì)切換到自研壓測(cè)引擎上。
          • RPC 協(xié)議:自研引擎,主要利用 Golang 協(xié)程+RPC 連接池,來完成高并發(fā)壓測(cè)。
          • GoPlugin 協(xié)議:自研引擎,利用 Golang Plugin 可動(dòng)態(tài)裝載的特性,自動(dòng)裝載自定義壓測(cè)插件,來完成壓測(cè)。

          3.8 壓測(cè)監(jiān)控

          客戶端監(jiān)控

          由于公司監(jiān)控系統(tǒng),最小時(shí)間粒度是 30s,30s 內(nèi)的數(shù)據(jù)會(huì)聚合成一個(gè)點(diǎn)。這個(gè)時(shí)間粒度對(duì)于壓測(cè)來說是比較難以接受的。因此,Rhino 平臺(tái)自己搭建了一套客戶端監(jiān)控系統(tǒng)。

          • 每個(gè) Request 都會(huì)以請(qǐng)求開始時(shí)間為基準(zhǔn)打一個(gè)點(diǎn)。
          • 單個(gè) Agent 內(nèi),會(huì)將相同任務(wù)相同接口,1s 內(nèi)的打點(diǎn)數(shù)據(jù)在本地做一次匯總,上報(bào)到 Kafka 中。
          • 監(jiān)控服務(wù)會(huì)消費(fèi) Kafka 中的打點(diǎn)數(shù)據(jù),將多個(gè) Agent 上報(bào)的數(shù)據(jù)進(jìn)行再次匯總,然后寫入數(shù)據(jù)庫(kù)中。
          • 前端監(jiān)控報(bào)表會(huì)實(shí)時(shí)拉取數(shù)據(jù)庫(kù)中監(jiān)控匯總數(shù)據(jù),繪制實(shí)時(shí)監(jiān)控曲線
          • 在監(jiān)控?cái)?shù)據(jù)匯總流程中,對(duì)于請(qǐng)求響應(yīng)時(shí)間的 PCT99 計(jì)算,是比較難處理的:目前 Rhino 平臺(tái)采用的 T-Digest 算法來計(jì)算 1 秒內(nèi)的 PCT99整個(gè)時(shí)間段內(nèi)的 PCT99 的計(jì)算,則是以 PCT & AGV 的方式聚合。即單位時(shí)間內(nèi)通過 T-Digest 計(jì)算 PCT99;整個(gè)時(shí)間段內(nèi)的 PCT99,則是對(duì)所有點(diǎn)的 PCT99 取平均值。整體計(jì)算方案已與公司服務(wù)端監(jiān)控算法對(duì)齊,目的是減少客戶端監(jiān)控與服務(wù)端監(jiān)控之間的 Gap,減少壓測(cè)結(jié)果分析的干擾因素。

          服務(wù)端監(jiān)控

          服務(wù)端監(jiān)控,直接接入了公司 Metric 系統(tǒng)。

          • 在壓測(cè)過程中,Rhino 平臺(tái)會(huì)提供整條鏈路上所有節(jié)點(diǎn)核心指標(biāo)的監(jiān)控大盤,并高亮顯示可能存在風(fēng)險(xiǎn)的節(jié)點(diǎn),來提供實(shí)時(shí)預(yù)警。
          • 對(duì)于每個(gè)節(jié)點(diǎn)也都提供了實(shí)時(shí)的,詳細(xì)的監(jiān)控曲線圖。
          • 對(duì)于每個(gè)節(jié)點(diǎn)默認(rèn)提供 CPU、Memory、QPS 和 Error_Rate 等核心監(jiān)控指標(biāo),用戶可以在 Rhino 平臺(tái)上修改監(jiān)控配置,增加其他自定義監(jiān)控指標(biāo)。

          性能 Profile

          在壓測(cè)過程中,Rhino 平臺(tái)還可以實(shí)時(shí)采集目標(biāo)服務(wù)進(jìn)程的性能 Profile,并通過火焰圖的方式展示出來,方便用戶進(jìn)行性能問題分析和優(yōu)化,如圖。

          4. 壓測(cè)實(shí)踐

          Rhino 壓測(cè)平臺(tái)是一個(gè)面向全字節(jié)跳動(dòng)公司的,為了所有研發(fā)同學(xué)提供的一站式全鏈路壓測(cè)的平臺(tái)。Rhino 平臺(tái)的研發(fā)團(tuán)隊(duì),不僅負(fù)責(zé) Rhino 平臺(tái)的研發(fā)任務(wù),還會(huì)配合 QA&RD 來完成公司大型項(xiàng)目,重點(diǎn)業(yè)務(wù)的性能壓測(cè)工作。

          4.1 重大項(xiàng)目支撐

          公司內(nèi)重大項(xiàng)目的壓測(cè),Rhino 平臺(tái)都會(huì)積極參與,全力支撐的。其中,比較典型的項(xiàng)目有抖音春晚,西瓜百萬英雄,春節(jié)紅包雨等活動(dòng)。

          其中字節(jié)春節(jié)紅包雨活動(dòng),完成是由 Rhino 團(tuán)隊(duì)來負(fù)責(zé)和完成的。字節(jié)春節(jié)紅包雨活動(dòng)是在春節(jié)期間,所有字節(jié)客戶端發(fā)起的,諸如抽卡分現(xiàn)金,紅包錦鯉,紅包雨等一系列的超大規(guī)模的紅包引流活動(dòng)。其流量規(guī)模巨大,流量突發(fā)性強(qiáng),業(yè)務(wù)邏輯和網(wǎng)絡(luò)架構(gòu)復(fù)雜度高等等,都對(duì) Rhino 平臺(tái)提出不小的挑戰(zhàn)。

          在春節(jié)紅包雨活動(dòng)中,所有用戶流量都經(jīng)過運(yùn)營(yíng)商專線接入到網(wǎng)絡(luò)邊緣的匯聚機(jī)房,然后經(jīng)過過濾和驗(yàn)證后,再轉(zhuǎn)發(fā)到核心機(jī)房。其中各個(gè) IDC 互為備份,其具體流量路線如圖。在這里,不僅要驗(yàn)證后端各服務(wù)是否能承載預(yù)期流程,還要驗(yàn)證各個(gè)專線帶寬,各個(gè)網(wǎng)關(guān)帶寬及轉(zhuǎn)發(fā)能力,各 IDC 承載能力以及之間帶寬等等。

          為此,我們將整個(gè)壓測(cè)拆分成多個(gè)階段,來簡(jiǎn)化壓測(cè)復(fù)雜性,也降低壓測(cè)問題定位的難度:

          • 通過撥測(cè)/CDN 壓測(cè)來分別驗(yàn)證各個(gè)匯聚機(jī)房的承載能力,帶寬,以及網(wǎng)關(guān)性能。
          • 在各個(gè)匯聚機(jī)房部署壓測(cè) Agent,來模擬用戶流量分布,來壓測(cè)部署在核心機(jī)房的后端服務(wù)性能。
          • 單接口單實(shí)例壓測(cè),單接口單機(jī)房壓測(cè),場(chǎng)景化全鏈路單機(jī)房壓測(cè),場(chǎng)景化全鏈路全資源壓測(cè),分階段來驗(yàn)證后端服務(wù)性能。
          • 最后會(huì)通過全網(wǎng)撥測(cè),來模擬真實(shí)春節(jié)紅包雨高峰期流量,整體驗(yàn)證全系統(tǒng)性能。

          在這些大型項(xiàng)目的支撐中,Rhino 團(tuán)隊(duì)不僅學(xué)到了大量的業(yè)務(wù)和架構(gòu)設(shè)計(jì)知識(shí),還了解到業(yè)務(wù)研發(fā)同學(xué)如何看待壓測(cè),如何使用平臺(tái),幫助我們發(fā)現(xiàn)更多平臺(tái)的問題,促進(jìn)平臺(tái)不斷迭代優(yōu)化。

          4.2 日常壓測(cè)任務(wù)支撐

          日常壓測(cè)支撐,也是 Rhino 平臺(tái)非常重要的一項(xiàng)任務(wù)。對(duì)于日常壓測(cè)中遇到的各種問題,我們采用了各種方案來解決:

          • 專人 Oncall 值周,一對(duì)一指導(dǎo)。
          • 詳細(xì)完善的壓測(cè)知識(shí)庫(kù),不僅介紹了平臺(tái)如何使用,還包括壓測(cè)如何改造,壓測(cè)方案如何制定,壓測(cè)問題如何定位。
          • 完善的性能培訓(xùn)體系:定期開展性能測(cè)試相關(guān)分享,并對(duì)于 QA&RD 團(tuán)隊(duì),也會(huì)開展專業(yè)的壓測(cè)培訓(xùn)。

          4.3 線上流量調(diào)度

          Rhino 平臺(tái)還實(shí)現(xiàn)了線上流量的定期調(diào)度,以達(dá)到線上實(shí)例自動(dòng)壓測(cè)的目的[8]:

          • 將線上流量逐步調(diào)度到目標(biāo)實(shí)例上,來測(cè)試服務(wù)實(shí)例性能極限,并給出實(shí)例性能 Profile,分析出實(shí)例性能瓶頸。
          • 通過長(zhǎng)期的流量調(diào)度,來觀察服務(wù)實(shí)例性能變化,以監(jiān)控服務(wù)性能的變化趨勢(shì)。
          • 通過不同資源水位下的實(shí)例性能,來預(yù)估出整個(gè)集群容量。完成對(duì)服務(wù)容量預(yù)估,以及線上風(fēng)險(xiǎn)評(píng)估。
          • 基于泳道化的流量調(diào)度,可以精確的預(yù)估服務(wù)集群容量。

          其具體實(shí)現(xiàn)方案如下:

          • 修改負(fù)載均衡中目標(biāo)實(shí)例的權(quán)重 Weight 值,逐步調(diào)大該 Weight 值,將更多流量集中打到目標(biāo)實(shí)例,直到達(dá)到設(shè)置的停止閾值。

          目前已經(jīng)有 500+微服務(wù)接入,每天定時(shí)執(zhí)行流量調(diào)度,來監(jiān)控線上服務(wù)性能變化趨勢(shì),如下圖。

          4.4 常態(tài)化壓測(cè)

          Rhino 平臺(tái)目前還在公司內(nèi)推行常態(tài)化壓測(cè),通過周期定時(shí)化的自動(dòng)化全鏈路壓測(cè),來實(shí)現(xiàn)以下目標(biāo):

          • 實(shí)時(shí)監(jiān)控線上服務(wù)集群容量,防止服務(wù)性能劣化。
          • 實(shí)時(shí)監(jiān)控線上鏈路容量,防止鏈路性能劣化。

          目前 Rhino 平臺(tái)上的常態(tài)化壓測(cè),會(huì)周期定時(shí),以無人值守的方式,自動(dòng)執(zhí)行壓測(cè)任務(wù),并推送壓測(cè)結(jié)果。在壓測(cè)執(zhí)行過程中,會(huì)根據(jù)調(diào)用鏈自動(dòng)完成壓測(cè)開關(guān)開啟,發(fā)起壓測(cè)流量。實(shí)時(shí)監(jiān)控服務(wù)性能指標(biāo),并根據(jù) Metric 及告警監(jiān)控,自動(dòng)完成壓測(cè)熔斷,以保證壓測(cè)安全。

          目前已經(jīng)有多個(gè)業(yè)務(wù)方接入常態(tài)化壓測(cè),以此保證線上服務(wù)的穩(wěn)定性。

          4.5 DevOps 流水線中的壓測(cè)

          服務(wù)在上線時(shí),都會(huì)經(jīng)過預(yù)發(fā)布,線上小流量灰度,線上全量發(fā)布。在這個(gè)過程中,我們可以通過線上測(cè)試 Case 以及灰度發(fā)布,來攔截服務(wù)線上功能缺陷。但是對(duì)于性能缺陷的攔截,卻不夠有效。

          從線上故障跟蹤系統(tǒng)里就可以發(fā)現(xiàn),由于上線前沒有做好性能壓測(cè),很多性能缺陷都逃逸到了線上。

          為了攔截各種性能缺陷,Rhino 平臺(tái)完成了 DevOps 平臺(tái)的打通。將壓測(cè)服務(wù)在 DevOps 平臺(tái)上注冊(cè)成一個(gè)原子服務(wù) ,研發(fā)人員可以將壓測(cè)節(jié)點(diǎn)編排在任意流水線的任意位置,實(shí)現(xiàn)上線前的例行壓測(cè)。DevOps 流水線中的壓測(cè),不僅可以幫助 RD 發(fā)現(xiàn)代碼中的性能問題,還能與性能基線進(jìn)行 Diff,來發(fā)現(xiàn)代碼性能變壞的味道。

          5. 總結(jié)與展望

          5.1 總結(jié)

          Rhino 壓測(cè)平臺(tái)從立項(xiàng)到現(xiàn)在,不到兩年的時(shí)間內(nèi),其發(fā)展已經(jīng)初具規(guī)模,如圖(每月壓測(cè)執(zhí)行統(tǒng)計(jì))。這個(gè)期間,非常非常感謝公司內(nèi)所有合作團(tuán)隊(duì),尤其是架構(gòu)團(tuán)隊(duì),中臺(tái)團(tuán)隊(duì)對(duì)壓測(cè)平臺(tái)的支撐,沒有他們的支撐,全鏈路壓測(cè)建設(shè)是難以完成的。

          5.2 未來發(fā)展

          業(yè)務(wù)深層次定制化

          通用壓測(cè)平臺(tái)已經(jīng)初步搭建完成,基本上能滿足業(yè)務(wù)線日常壓測(cè)需求。但在日常壓測(cè)支撐過程中,發(fā)現(xiàn)不同業(yè)務(wù)線在壓測(cè)時(shí),但是仍然有大量的前置和后繼工作需要人工來完成。

          如何更進(jìn)一步降低業(yè)務(wù)方壓測(cè)改造的成本,如何減少壓測(cè)環(huán)境數(shù)據(jù)預(yù)置成本,如何快速完成壓測(cè)數(shù)據(jù)清理,如何快速定位出性能問題等等,Rhino 壓測(cè)平臺(tái)后續(xù)將更進(jìn)一步深入業(yè)務(wù),與各大業(yè)務(wù)方開展更深入的合作,提供更深度的業(yè)務(wù)定制,為研發(fā)提效,助力業(yè)務(wù)線發(fā)展。

          壓測(cè)與容量規(guī)劃

          業(yè)務(wù)目前資源是否充足,其具體容量是多少;按照目前業(yè)務(wù)增長(zhǎng),其機(jī)器資源還能支撐多久?

          目前服務(wù)資源利用如何,是否可以優(yōu)化,如何更進(jìn)一步提升資源利用率,降低機(jī)器資源成本?

          某大型活動(dòng),需要申請(qǐng)多少資源?是否不需要壓測(cè),或者自動(dòng)化利用線上流量數(shù)據(jù),或者利用日常壓測(cè)數(shù)據(jù),就可以給出上述問題的結(jié)論?

          壓測(cè)與 SRE

          如何保證服務(wù)穩(wěn)定性,如何監(jiān)控服務(wù)性能劣化并及時(shí)預(yù)警,限流、超時(shí)、重試以及熔斷等服務(wù)治理措施配置是否合理?以及如何配合混沌測(cè)試進(jìn)行容災(zāi)演練,保證服務(wù)穩(wěn)定性等等,這些 Rhino 平臺(tái)都會(huì)做更進(jìn)一步探索。

          6. 招聘

          目前 Rhino 團(tuán)隊(duì)還非常小,非常缺少性能測(cè)試以及后端開發(fā)相關(guān)的研發(fā)工程師,歡迎感興趣的同學(xué)來加入。簡(jiǎn)歷投遞郵箱: tech@bytedance.com ;郵件標(biāo)題: 姓名 - 工作年限 - Rhino 。

          參考文獻(xiàn)

          [1] http://jm.taobao.org/2017/03/30/20170330/

          [2] https://testerhome.com/topics/19493

          [3] https://tech.meituan.com/2018/09/27/quake-introduction.html

          [4] https://tech.meituan.com/2019/02/14/full-link-pressure-test-automation.html

          [5] https://www.open-open.com/lib/view/open1484317425690.html

          [6] https://www.infoq.cn/article/NvfJekpvU154pwlsCTLW

          [7] https://tech.bytedance.net/articles/3199

          [8] https://www.usenix.org/conference/osdi16/technical-sessions/presentation/veeraraghavan

          更多分享

          Fastbot:行進(jìn)中的智能 Monkey

          品質(zhì)優(yōu)化 - 圖文詳情頁(yè)秒開實(shí)踐

          Android Camera 內(nèi)存問題剖析

          字節(jié)跳動(dòng)自研線上引流回放系統(tǒng)的架構(gòu)演進(jìn)


          歡迎關(guān)注「字節(jié)跳動(dòng)技術(shù)團(tuán)隊(duì)」


          主站蜘蛛池模板: 亚洲视频一区二区三区四区| 杨幂AV污网站在线一区二区| 亚洲另类无码一区二区三区| 91在线一区二区| 国产拳头交一区二区| 无码av不卡一区二区三区| 日本免费电影一区| 亚洲国产视频一区| 熟女少妇丰满一区二区| 中文字幕精品一区二区精品 | 国精产品999一区二区三区有限| 精品国产区一区二区三区在线观看| 成人中文字幕一区二区三区| 在线观看国产一区亚洲bd| 国产精品一区二区三区99| 国产色欲AV一区二区三区| 色狠狠一区二区三区香蕉| 亚洲永久无码3D动漫一区| 亚洲一区二区三区AV无码| 成人精品一区二区三区不卡免费看| 狠狠色婷婷久久一区二区三区| 国产亚洲无线码一区二区 | 国产精品视频一区二区三区无码| 乱色精品无码一区二区国产盗| 亚洲国产精品无码第一区二区三区| 天天躁日日躁狠狠躁一区| 精品成人av一区二区三区| 好爽毛片一区二区三区四| 久久久久99人妻一区二区三区| 麻豆一区二区三区蜜桃免费| 99久久精品午夜一区二区| 国产激情一区二区三区成人91 | 国产伦精品一区二区三区免费下载| 麻豆国产一区二区在线观看| 国产AV午夜精品一区二区入口 | 国产AV午夜精品一区二区入口 | 好吊妞视频一区二区| 国产综合一区二区| 极品少妇伦理一区二区| 免费高清av一区二区三区| 无码精品人妻一区二区三区中 |