天天看點

【性能測試】記一次性能測試

目錄

一.測試環境介紹

二.本次測試參數介紹

三.解碼程式介紹

四.HDFS測試

五.本地測試

六.CPU測試

七.pod負載問題

總結:

公司建立了一個全局的kubernetes叢集,伺服器在泰國,使用rancher2.5.5搭建的。因為裡面配置的資源很大,但是還沒有正式的應用部署在上面,上司要求部署幾個app上去跑跑資源。決定下來将解碼程式放上去跑跑,解碼程式是将工廠測試資料的檔案解碼出來,一個serial numner檔案一般有240張表,全部解出寫入存儲目錄,一個serial number一張表為一個目錄,先不壓縮,直接寫txt檔案。

一.測試環境介紹

Rancher版本2.5.1

節點配置

master 3 cpu 48core memrory 125G
node 8 cpu 16core memrory 48G

二.本次測試參數介紹

測試資料量
sn數量:1780sn
寫入資料大小:95G
寫入格式:txt

測試參數
Cpu:pod的cpu參數(基本測試了2core和4core的情況)
Memrory: Pod的memrory參數(基本測試了8G和12G的情況)
Concurrent Thread: sn的并發數,請求數,同時有多少個sn在請求服務(1,16,24,32,48,65,80)
Table write Thread: 每個sn會解碼出240張表左右,此參數為并發多少個table同時寫存儲(1,16,50)
Pod number: 服務端程式負載均衡的Pod數量(6,8,12,16,20)

           

三.解碼程式介紹

服務端使用springboot打包,并使用docker打成鏡像

服務端程式代碼部分,傳入本次reqeustId和serial num相關的資訊。解碼分三步,第一步根據sn檔案位址去下載下傳sn檔案,第二步解碼sn檔案成一個hashmap,key是table name,value是一個List<String>,第三步将hashmap寫入存儲。每一步記錄處理時間,傳回到client。

/**
 *解碼程式,三步
 *1.download檔案
 *2.解碼
 *3.寫入存儲
 */
@PostMapping(value = "/parseFileBySerialModelWriteAllTable")
    public String parseFileBySerialModelWriteAllTable(@RequestParam(name = "requestId") String requestId,
    									@RequestParam(name = "serialNum") String serialNum,
	    								@RequestParam(name = "host") String host,
							    		@RequestParam(name = "remotePath") String remotePath,
							    		@RequestParam(name = "fileName") String fileName,
							    		@RequestParam(name = "type") String type,
							    		@RequestParam(name = "fileType") String fileType,
							    		@RequestParam(name = "tableAlias") String tableAlias,
							    		@RequestParam(name = "tableThread") String tableThread) throws Exception {
		logger.info("parseFileBySerialModelWriteAllTable in");
		
		String errorInf = "";
		long downloadTime = 0;
		long parseTime = 0;
		long writeTableTime = 0;
		int tableSize = 0;
		
		try {
			long t1 = System.currentTimeMillis();
			
			DownloadHelper downloadHelper = new DownloadHelper();
			byte[] bytes7 = downloadHelper.download(host, remotePath, fileName, Integer.parseInt(type), fileType);//下載下傳檔案==================================
			
			downloadTime = System.currentTimeMillis() - t1;
			t1 = System.currentTimeMillis();
			
			logger.info("download end");
			/*InputStream in = new FileInputStream(remotePath);
			byte[] bytes7 = IOReader.read(name, in, fileType);*/
			if(bytes7 == null || bytes7.length == 0) {
				throw new Exception("download empty !" + serialNum);
			}
			
			
			ParserBasic parser = new ParserBasic() {
				@Override
				public boolean include(Object tableCode) {
					if(tableAlias == null || "".equals(tableAlias))
						return true;
					else if(tableAlias.equals(tableCode.toString()))
						return true;
					else
						return false;
				}
				
				@Override
				public String getTableRow(Map<String, String> tbStates, String tableRow) {
	//				System.out.println("TEST_DATE = " + tbStates.get(SysCnt.TEST_DATE));
					return tbStates.get(SysCnt.SEQ) + "," +
						tbStates.get(SysCnt.TEST_TIME) + ","+ 
						tbStates.get(SysCnt.SPC_ID) + ","+ 
						tbStates.get(SysCnt.TEST_SEQ_EVENT) + ","+ 
						tbStates.get(SysCnt.STATE_NAME) + ","+ 
						tbStates.get(SysCnt.OCCURRENCE) + "," + 
						StringUtil.removeBlank(tableRow) + ENTER;
				}
				
				@Override
				public String getMapKey(Map<String, String> tbStates, int globalTableId) throws Exception {
					return String.valueOf(globalTableId);
				}
				
			};
			
			parser.setParseDefine(true);
			
			Map<String, List<String>> tables = (fileType.equals(SysCnt.FILE_TYPE_BTR)) ? parser.parser(bytes7): parser.parser_csv(bytes7);//解碼程式===========================
			tableSize = tables.size();
			
			if(tables == null || tables.keySet().size() == 0) {
				throw new Exception("parser table empty !" + serialNum);
			}
			
			parseTime = System.currentTimeMillis() - t1;
			t1 = System.currentTimeMillis();
			
			String writePath = requestId + "/" + serialNum;
			parseService.writeTableData(writePath, tables,Integer.parseInt(tableThread));//寫入資料=================================
			
			writeTableTime = System.currentTimeMillis() - t1;
			
			parser.emptyParser();
			logger.info("parseFileBySerialModel tables length:"+tables.keySet().size());
			
			logger.info("parseFileBySerialModel =============================================" + serialNum);
		}catch(Exception e) {
			logger.error(e.getMessage(),e);
			errorInf = e.getMessage();
		}
		
		if(!"".equals(errorInf)) {
			return "{success: 0,error:\""+errorInf+"\",IP:"+InetAddress.getLocalHost().getHostAddress()+",downloadTime:"+downloadTime+",parseTime:"+parseTime+",tableSize:"+tableSize+",writeTableTime:"+writeTableTime+"}";
		}else {
			return "{success: 1,error:\"\",IP:"+InetAddress.getLocalHost().getHostAddress()+",downloadTime:"+downloadTime+",parseTime:"+parseTime+",tableSize:"+tableSize+",writeTableTime:"+writeTableTime+"}";
		}
		
	}
           
/**
 *寫資料,兩種方式
 *1.寫入hdfs
 *2.寫本地
 *
 */
public void writeTableData(String writePath,Map<String, List<String>> tables,int tableThread) throws InterruptedException {
		
		Set<String> tableKey = tables.keySet();
		
		ExecutorService executeService = Executors.newFixedThreadPool(tableThread);
		
		Iterator<String> tableNameList = tableKey.iterator();
		while(tableNameList.hasNext()) {
			String tableName = tableNameList.next();
			
			String path = comPath + writePath + "/" + tableName + "/";
			String input = list2line(tables.get(tableName));
			
			//setTableIndex();
			
			executeService.execute(new Runnable() {
				public void run(){
					try {
						write2Hdfs(path,input);
						
					} catch (Exception e) {
						System.out.println("iii");
						e.printStackTrace();
					}
				}
			});
			
		}
		
		executeService.shutdown();
		//System.out.println("=============write tables threads end!");  
		while(true){  
           if(executeService.isTerminated()){  
                System.out.println("=============write tables end!");  
                break;  
            }  
            Thread.sleep(1000);    
        }
		
	}
	
    /**
     *處理lsit<string>
     */
	public static String list2line(List<String> list) {
		StringBuilder sb = new StringBuilder(1024 * 1024);
		for (String s : list) {
			sb.append(s).append("\n");
		}
		return sb.toString();
	}
	

    /**
     *寫hdfs方法
     */
	public static void write2Hdfs(String location, String input) throws IOException {
		FileSystem fs = ServiceSupport.getFileSystem();
		//System.out.println("fs address:"+fs.toString());
		FSDataOutputStream fsdos = fs.create(new Path(location + "data.txt"));
		fsdos.write(input.getBytes());
		System.out.println("write path success:"+location);
		fsdos.flush();
		fsdos.close();
		fsdos = null;
		//fs.close();
	}
	
    /**
     *寫本地方法
     */
	public static void write2local(String location, String input) throws Exception {
		File file = new File(location + "data.txt");
		if(!file.getParentFile().exists()) {
			file.getParentFile().mkdirs();
		}
		
		file.createNewFile();
		
		byte bt[] = new byte[1024];
		bt = input.getBytes();
		try {
			FileOutputStream in = new FileOutputStream(file);
			try {
				in.write(bt, 0, bt.length);
			} catch (IOException e) {
				throw e;
			} finally {
				in.close();
			}
		} catch (FileNotFoundException e) {
			throw e;
		}
		
	}
           

注:為保證能支援多種case,服務端程式寫了多個restful api,如下

/parseFileBySerialModelWriteAllTable:寫全表,每個表一個檔案,寫入hdfs
/parseFileBySerialModelWriteSplitTable:一定數量表做成一個輸入寫一個檔案,寫入hdfs
/parseFileBySerialModelWriteOneFile:所有表寫一個檔案,寫入hdfs
/parseFileBySerialModelNotWrite:隻解碼,不寫入任何存儲

/parseFileBySerialModelWriteAllTableLocal:寫全表,每個表一個檔案,寫入本地存儲
/parseFileBySerialModelWriteAllTableLocalOneFile:所有表寫一個檔案,寫入本地存儲
           

----------------------------------------分割線-------------------------------------------------------------------------------

用戶端程式

/**
 *擷取sn的資訊,控制并發數,調用服務端
 *此處使用的測試資料是89個sn的資料,并使用循環,将89個sn的資料模拟成20倍1780個sn
 */
public void callParseBySameSn(String path) throws Exception {
		
		System.out.println("parse start:12G,16POD,4core,50Thread,1780sn,write one file for all table");
		System.out.println("=============getSnByInputPath start !");
		List<String> snList = parseService.get89Sn();//擷取89個sn
		/*String model1 = "WAF2LZPK,9";
		List<String> snList = new ArrayList<>();
		snList.add(model1);*/
		
		System.out.println("=============getSnByInputPath end !");
		
		List<SnModel> models = parseService.getSnModelFile(snList);//擷取89sn資料的檔案位址
		System.out.println("=============getSnModelFile end !");
		
		//String requestId = UUID.randomUUID() + "";
		
		long t1 = System.currentTimeMillis();
		ExecutorService service = Executors.newFixedThreadPool(24);//并發數,代表有多少個sn同時調用解碼程式
		String majorDir = "M_"+getLocalTimeFormat("yyyyMMddHHmmssSSS");//每次調用的唯一辨別,會作為目錄傳入服務端
		
		ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();
		
		for(int loop=0;loop<20;loop++){
			
			String dateMill = getLocalTimeFormat("yyyyMMddHHmmssSSS");
			String requestId = majorDir + "/" + dateMill;//唯一辨別補充,因為循環20次,每次生成一個不同的目錄。相當于有20個目錄,存儲了同一個份資料(89sn的解碼資料)
			
			for (int i = 0; i < models.size(); i++) {

				/*String[] mod = snList.get(i).split(",");
				String serialNum = mod[0];
				String transSeq = mod[1];
				String host = mod[4];
				String remotePath = mod[3];;
				String fileName = mod[2];;
				String type = "0";
				String fileType = mod[6];
				String tableAlias = "626";
				//String tableAlias = "P250_ERROR_RATE_BY_ZONE";*/
				
				
				String serialNum = models.get(i).getSerialNum()+"_"+models.get(i).getTransSeq();
				String transSeq = models.get(i).getTransSeq();
				
				String orginHost = models.get(i).getHost();
				String orginPath = "";
				if(orginHost.indexOf(".") == -1){
					String hostName = setHost(new File(orginHost+"/").getPath().replace("\\", "/"));
					orginHost = hostName+".wux.chin.seagate.com";
					
					String location = new File(hostName+ "/" + models.get(i).getPath() + "/" + models.get(i).getFileName()).getPath().replace("\\", "/");
					orginPath = location;
				}else{
					String location = new File(models.get(i).getHost() + "/" + models.get(i).getPath() + "/" + models.get(i).getFileName()).getPath().replace("\\", "/");
					orginHost = setHost(location);
					orginPath = setPath(location, orginHost);
				}
				
				String host = orginHost;
				String remotePath = orginPath;
				
				/*String location = new File(models.get(i).getHost() + "/" + models.get(i).getPath() + "/" + models.get(i).getFileName()).getPath().replace("\\", "/");
				String host = setHost(location);
				String remotePath = setPath(location,host);*/
				
				
				String fileName = models.get(i).getFileName();
				String type = "0";
				String fileType = models.get(i).getFileType();
				String tableAlias = "";
				//String tableAlias = "P250_ERROR_RATE_BY_ZONE";

				service.submit(new Runnable() {

					@Override
					public void run() {
						try {
                            //調用服務端,此處的最後兩個參數說明,16是解碼後寫會有240個表生成的hashmap,這裡定義這些表以多少個線程去寫這些表。50是另一個參數,沒有這個參數的時候,每個表會生成一個目錄,這個參數設定的是将多少個表合并,再統一寫入一個目錄,此參數是為了測試降低寫入資料的頻次,此參數在本次測試隻用了一次,在下文未提及此參數時,都是按照每個表寫一個目錄的情況
							String out = CallRestUrl.callSeadoopMrPostBySerial(
									requestId, serialNum, transSeq, host,
									remotePath, fileName, type, fileType,
									tableAlias,"16","50");
							 String key = out.split("IP:")[1].split(",")[0];
							 if(map.containsKey(key)){
								 map.put(key, map.get(key)+1);
							 }else{
								 map.put(key, 1);
							 }
							
							System.out.println("Success :"+getSuccessCount()+" "+serialNum+" end,cost "+(System.currentTimeMillis() - t1)+" :" + out);
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
				});
			}
		}
		
		System.out.println("=============All threads have stated !");
		
		service.shutdown();
        while(true){  
           if(service.isTerminated()){  
                System.out.println("=============All threads have end!"); 
                Enumeration<String> em = map.keys();
                while(em.hasMoreElements()){
                	String tt = em.nextElement();
                	System.out.println(tt+":"+map.get(tt));
                }
                break;  
            }  
            Thread.sleep(1000);   
        }
	}
           
/**
 *遠端調用service
 */
public static String callSeadoopMrPostBySerial(String requestId,String serialNum,String transSeq,
			String host,String remotePath,String fileName,String type,String fileType,String tableAlias,String tableThread,String tableCount) throws Exception{
		//URL postUrl = new URL("http://10.38.199.201:30088/parseFileBySerialModelWriteAllTableLocal?");
		//URL postUrl = new URL("http://10.38.150.64:8088/parseFileBySerialNum?");
		//URL postUrl = new URL("http://10.43.79.103:8088/parseFileBySerialModelWriteAllTableLocal?");
		//URL postUrl = new URL("http://10.38.199.203:8088/parseFileBySerialModel?");
		URL postUrl = new URL("http://10.4.140.149:30088/parseFileBySerialModelWriteAllTableLocal?");//GIS dev environment
        HttpURLConnection connection = (HttpURLConnection) postUrl.openConnection();  
        connection.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)");
        connection.setDoOutput(true);
        connection.setDoInput(true);
        connection.setRequestMethod("POST");      
        connection.setUseCaches(false);  
        connection.setInstanceFollowRedirects(true);      
        connection.connect();
        connection.setReadTimeout(0);
        DataOutputStream out = new DataOutputStream(connection.getOutputStream());
        
        StringBuffer sb = new StringBuffer();
        sb.append("uuid=" + URLEncoder.encode(UUID.randomUUID() + "", "UTF-8")).append("&");
        sb.append("requestId=" + URLEncoder.encode(requestId, "UTF-8")).append("&");
        sb.append("serialNum=" + URLEncoder.encode(serialNum, "UTF-8")).append("&");
        sb.append("transSeq=" + URLEncoder.encode(transSeq, "UTF-8")).append("&");
        sb.append("host=" + URLEncoder.encode(host, "UTF-8")).append("&");
        sb.append("remotePath=" + URLEncoder.encode(remotePath, "UTF-8")).append("&");
        sb.append("fileName=" + URLEncoder.encode(fileName, "UTF-8")).append("&");
        sb.append("type=" + URLEncoder.encode(type, "UTF-8")).append("&");
        sb.append("fileType=" + URLEncoder.encode(fileType, "UTF-8")).append("&");
        sb.append("tableAlias=" + URLEncoder.encode(tableAlias, "UTF-8")).append("&");
        sb.append("tableThread=" + URLEncoder.encode(tableThread, "UTF-8")).append("&");
        sb.append("tableCount=" + URLEncoder.encode(tableCount, "UTF-8"));
        
        out.writeBytes(sb.toString());
        out.flush();
        out.close();
        BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        String line = reader.readLine();
        /*while ((line = reader.readLine()) != null){
            System.out.println(line);
        }*/
        reader.close();
        connection.disconnect();
        
        return line;
	}
           

寫入後的資料目錄如下

【性能測試】記一次性能測試

寫入的資料格式如下

【性能測試】記一次性能測試

四.HDFS測試

在考慮資料往什麼地方寫的時候,一個往hdfs寫,或者使用kubernetes的pv,還有就是往對象存儲ceph或者S3寫。考慮現有環境,還是往hdfs寫是最友善的。應用部署在一個199網段的叢集裡,裡面裝了Rancher2.5.1,建立了kubernetes叢集。因為199叢集的Hadoop環境存在問題,datanode很多都挂了,起不來,是以想把資料直接寫到另一個測試環境的149網段hadoop環境裡。

可以預見的,從199往149寫,網絡傳輸肯定是一個問題,其次hdfs寫資料的性能也是關鍵因素。往hdfs寫資料使用的java支援的hdfs jar包。

從之前設計的幾個方案調整了參數,初步的測試結果如下

1780sn,90G,txt
memory 12G
pod num 8 16 25
cpu 2 4 8 2 4 2
concurrent thread 50 1 50 50 50 50 50 100 100 25 50
table write thread 50 50 16 50 50 50 16 50 50 50 50
Time(min) 18.1 206sn/18.2 18.6 18.1 18.4 17.8 18.7 16 17.4 21.9 18.1

從表格可以看出,不論哪種配置,最終跑完的時間都在18分鐘左右(紅色字型是跑了206個serial number,18.2分鐘),這似乎與測試場景不符合,那麼肯定是有瓶頸。具體在哪裡,就需要排查了。

首先從傳回的日志來看,時間主要耗費在寫資料,可以看出download和parse解碼時間不是很長,寫資料基本都在十幾秒到二十秒,這裡一定是不正常。

【性能測試】記一次性能測試

為了測試寫資料是否是真的寫的慢還是由于資源競争導緻的,我測試了并發數為一個的情況,就是表格中紅字的那個測試場景,結果如下,可以看出單跑一個并發,不管是download,parse還是寫資料都是很快的。由此可以看出上圖出現的寫資料慢必定是資源競争造成的。

【性能測試】記一次性能測試

那麼到底是什麼資源在競争,哪個才是瓶頸?無非幾個:cpu,memrory,network,還有磁盤寫入的IO。咱們一個個看。

第一個看cpu,在跑的過程中,我用rancher的grafana插件檢視cpu的占用,2core的cpu在程式運作期間,整個負載并不高。在檢視4core的時候,也是類似情況,這裡說明整個程式cpu的占用率并不高。

【性能測試】記一次性能測試

從另一個監控看cpu的使用率,主要看cpu usage,在跑的過程中,cpu usage始終不高(注:1000m為一個cpu核數)

【性能測試】記一次性能測試
【性能測試】記一次性能測試

第二再看memrory,這裡使用了kubernete指令檢視了pod的記憶體變化,記憶體逐漸在上升,且到達頂點後基本就不動了,因為是java程式,jvm到達最大記憶體後,是不會釋放記憶體的,記憶體的管理都是在jvm裡面,從這裡并不能看出記憶體的影響,需要到jvm内部檢視記憶體的影響。

[[email protected] seadoopmr]# kubectl top pods
NAME                         CPU(cores)   MEMORY(bytes)
parse-dev-575f597d85-44x47   1287m        7793Mi
parse-dev-575f597d85-4qs2t   11m          4492Mi
parse-dev-575f597d85-5l7gb   1105m        7372Mi
parse-dev-575f597d85-6nmqf   2423m        10609Mi
parse-dev-575f597d85-csxfw   797m         6621Mi
parse-dev-575f597d85-jrj24   1611m        10242Mi
parse-dev-575f597d85-qjt2t   975m         6948Mi
parse-dev-575f597d85-snql8   1097m        8500Mi
[ro[email protected] seadoopmr]# kubectl top pods
NAME                         CPU(cores)   MEMORY(bytes)
parse-dev-575f597d85-44x47   1214m        9419Mi
parse-dev-575f597d85-4qs2t   384m         5026Mi
parse-dev-575f597d85-5l7gb   743m         8551Mi
parse-dev-575f597d85-6nmqf   1964m        12836Mi
parse-dev-575f597d85-csxfw   195m         7891Mi
parse-dev-575f597d85-jrj24   2315m        12708Mi
parse-dev-575f597d85-qjt2t   1319m        9285Mi
parse-dev-575f597d85-snql8   1927m        11488Mi
[[email protected] seadoopmr]# kubectl top pods
NAME                         CPU(cores)   MEMORY(bytes)
parse-dev-575f597d85-44x47   1149m        12612Mi
parse-dev-575f597d85-4qs2t   277m         5251Mi
parse-dev-575f597d85-5l7gb   1770m        10514Mi
parse-dev-575f597d85-6nmqf   792m         12844Mi
parse-dev-575f597d85-csxfw   1463m        8387Mi
parse-dev-575f597d85-jrj24   844m         12719Mi
parse-dev-575f597d85-qjt2t   954m         11074Mi
parse-dev-575f597d85-snql8   1366m        12671Mi
[[email protected] seadoopmr]# kubectl top pods
NAME                         CPU(cores)   MEMORY(bytes)
parse-dev-575f597d85-44x47   468m         12614Mi
parse-dev-575f597d85-4qs2t   6m           6168Mi
parse-dev-575f597d85-5l7gb   501m         12692Mi
parse-dev-575f597d85-6nmqf   1611m        12855Mi
parse-dev-575f597d85-csxfw   1298m        11440Mi
parse-dev-575f597d85-jrj24   1549m        12736Mi
parse-dev-575f597d85-qjt2t   863m         12596Mi
parse-dev-575f597d85-snql8   1288m        12726Mi
[[email protected] seadoopmr]# kubectl top pods
NAME                         CPU(cores)   MEMORY(bytes)
parse-dev-575f597d85-44x47   641m         12748Mi
parse-dev-575f597d85-4qs2t   5m           6846Mi
parse-dev-575f597d85-5l7gb   841m         12706Mi
parse-dev-575f597d85-6nmqf   1073m        12860Mi
parse-dev-575f597d85-csxfw   986m         12629Mi
parse-dev-575f597d85-jrj24   1458m        12744Mi
parse-dev-575f597d85-qjt2t   1212m        12790Mi
parse-dev-575f597d85-snql8   996m         12726Mi
[[email protected] seadoopmr]# kubectl get pod -o wide
NAME                         READY   STATUS    RESTARTS   AGE   IP             NODE   NOMINATED NODE   READINESS GATES
parse-dev-575f597d85-44x47   1/1     Running   0          29s   10.42.17.182   t008   <none>           <none>
parse-dev-575f597d85-4qs2t   1/1     Running   0          30s   10.42.14.132   t020   <none>           <none>
parse-dev-575f597d85-5l7gb   1/1     Running   0          29s   10.42.20.171   t013   <none>           <none>
parse-dev-575f597d85-6nmqf   1/1     Running   0          30s   10.42.19.197   t012   <none>           <none>
parse-dev-575f597d85-csxfw   1/1     Running   0          29s   10.42.16.179   t019   <none>           <none>
parse-dev-575f597d85-jrj24   1/1     Running   0          29s   10.42.18.170   t011   <none>           <none>
parse-dev-575f597d85-qjt2t   1/1     Running   0          29s   10.42.10.178   t014   <none>           <none>
parse-dev-575f597d85-snql8   1/1     Running   0          30s   10.42.11.173   t015   <none>           <none>
[[email protected] seadoopmr]# kubectl top pods
NAME                         CPU(cores)   MEMORY(bytes)
parse-dev-575f597d85-44x47   673m         12768Mi
parse-dev-575f597d85-4qs2t   202m         8539Mi
parse-dev-575f597d85-5l7gb   1169m        12727Mi
parse-dev-575f597d85-6nmqf   1440m        12878Mi
parse-dev-575f597d85-csxfw   251m         12696Mi
parse-dev-575f597d85-jrj24   850m         12802Mi
parse-dev-575f597d85-qjt2t   471m         12815Mi
parse-dev-575f597d85-snql8   1498m        12744Mi
           

Rancher的監控如下,可見記憶體在一直上升,且到達jvm最大記憶體後趨平。另外還有一個情況,為甚有些線在最高點突然會降到最低點500M,原因是pod記憶體超出了限制,導緻pod重新開機,pod重新開機後記憶體重新劃分到jvm最低啟動記憶體500M,所有會出現從高點到低點的現象。至于pod為何會重新開機,首先設定的jvm堆記憶體是12G,設定pod的記憶體資源限制是14G。因為java程式運作時,不僅使用堆記憶體,也會使用本地記憶體,當本地記憶體超出了14-12=2G,整個pod的記憶體會超過14G,此時kubernetes會殺掉該pod,重新開機啟動,這也導緻了這樣的現象。

【性能測試】記一次性能測試

pod restart的情況

【性能測試】記一次性能測試

從pod内部檢視memror變化。在程式跑的時候,我進入到多個pod内部,檢視了jvm記憶體的變化及GC的時間,如下。可以看出jvm的記憶體在一直變化,但是整個程式跑完GC的時間也就39s左右,并不是很長,也沒有出現頻繁full GC的情況,可以看出記憶體雖然一直在工作,但并不是程式的瓶頸。

程式過程中的GC情況

【性能測試】記一次性能測試

跑完之後的GC情況

【性能測試】記一次性能測試

排除了cpu和memrory,那麼再看網絡,總的計算18分鐘,寫了95G的資料,可以計算寫入速率是90MB/s。

下圖是每個pod的IO情況,看Transmit,從該點看,總網絡傳輸為87.9MB/s

【性能測試】記一次性能測試

下圖展示kubernetes叢集的最高網絡傳輸為88.6MB/s

【性能測試】記一次性能測試

從這裡可以看出網絡傳入的速率和最後寫入的速率基本相等,可以推斷目前的瓶頸應該就是網絡傳輸了。這裡我後來咨詢了網絡管理者,199網段和149網段之間的帶寬是1000Mbps,換算為MB為125MB/s,考慮網絡網卡與交換機之間的損耗,與計算的90MB/s相差不多,可以佐證該點。

【性能測試】記一次性能測試

那麼我們基本确定了上面測試的情況為network網絡瓶頸。我們再來看看hdfs datanode的IO問題。找到幾台data node,使用iostat去檢視io的狀态,如下圖。檢視了某幾台IO的情況,IO的寫入速率最高在10MB/s左右,cpu的iowait百分比最高也隻有10,可見IO寫入速率也不高,可以排除IO瓶頸。

[[email protected] ~]# iostat -m 30 100
Linux 3.10.0-1062.18.1.el7.x86_64 (seadoop-test128.wux.chin.seagate.com)        05/26/2021      _x86_64_        (16 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.88    0.00    1.92    1.03    0.00   84.17

Device:            tps    MB_read/s    MB_wrtn/s    MB_read    MB_wrtn
sda              42.22         0.38         1.40   13488093   49805393
sdb              11.05         0.28         0.04   10065196    1532877
sdc              10.39         0.31         0.03   11129480    1019587

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          10.36    0.00    2.98    0.23    0.00   86.43

Device:            tps    MB_read/s    MB_wrtn/s    MB_read    MB_wrtn
sda             121.53         0.01         6.67          0        200
sdb             114.40         0.00         7.38          0        221
sdc             122.17         0.00         8.28          0        248

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          14.40    0.00    3.78    0.11    0.00   81.70

Device:            tps    MB_read/s    MB_wrtn/s    MB_read    MB_wrtn
sda             128.20         0.01         9.81          0        294
sdb             102.80         0.01         5.64          0        169
sdc              61.30         0.00         3.17          0         95

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          10.91    0.00    3.22    0.10    0.00   85.76

Device:            tps    MB_read/s    MB_wrtn/s    MB_read    MB_wrtn
sda             116.53         0.01         6.59          0        197
sdb              65.33         0.00         4.71          0        141
sdc             120.87         0.01         7.08          0        212
           
[[email protected] ~]# iostat -m 30 100
Linux 3.10.0-1062.18.1.el7.x86_64 (seadoop-test133.wux.chin.seagate.com)        05/26/2021      _x86_64_        (16 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          15.31    0.00    1.00    1.24    0.00   82.45

Device:            tps    MB_read/s    MB_wrtn/s    MB_read    MB_wrtn
sdb              14.00         0.26         0.11    9264726    3909152
sdc              13.37         0.24         0.10    8449701    3523896
sda              22.88         0.25         0.25    8766283    8770946

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.67    0.00    4.36   10.79    0.00   72.17

Device:            tps    MB_read/s    MB_wrtn/s    MB_read    MB_wrtn
sdb            1936.80         7.72         0.18        231          5
sdc            1637.47         6.37         0.61        191         18
sda              74.47         1.86         0.25         55          7

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          15.45    0.00    3.95   10.48    0.00   70.13

Device:            tps    MB_read/s    MB_wrtn/s    MB_read    MB_wrtn
sdb             796.67         2.96         5.31         88        159
sdc             126.70         0.21         5.88          6        176
sda             690.20         2.80         3.76         84        112

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          17.57    0.00    4.07    9.24    0.00   69.12

Device:            tps    MB_read/s    MB_wrtn/s    MB_read    MB_wrtn
sdb              85.27         0.13         5.55          3        166
sdc              84.33         0.06         3.95          1        118
sda            1817.13         7.18         3.86        215        115

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          13.07    0.00    3.82   10.74    0.00   72.37

Device:            tps    MB_read/s    MB_wrtn/s    MB_read    MB_wrtn
sdb              93.20         0.09         3.45          2        103
sdc              93.70         0.02         6.70          0        200
sda            1804.10         7.12         8.89        213        266

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          14.82    0.00    4.67    9.00    0.00   71.50

Device:            tps    MB_read/s    MB_wrtn/s    MB_read    MB_wrtn
sdb             100.53         0.32         7.32          9        219
sdc             123.20         0.02         8.34          0        250
sda            1860.23         7.33         6.01        220        180
           

綜上可以得出結論,不管調cpu,memrory,或是并發數,table寫入線程,還是pod number的數量,總時間基本都在18分鐘,且最終的瓶頸分析為網絡。

五.本地測試

因為在hdfs測試并發數和table線程數設定過大,推斷出網絡的瓶頸,沒有調試出最佳的配置,于是決定寫本地資料,找出最佳的配置(主要cpu,memrory和pod number)。

修改pod的deployment,挂載本地磁盤,使用hostpath挂載pod内部目錄/tmp/parse到本機目錄/tmp/parse

【性能測試】記一次性能測試

調整參數,得出如下結果,分兩個測試8G和12G,這裡table write thread之是以設定為16,是因為node節點的cpu核數的16,也就是cpu同時可以有16個線程運作,如果單個節點隻配置設定到一個request,那麼能確定最大的線程數在寫。

Case 1
memory 8
pod num 6 8 12 16 20
cpu 2 2 2 2 4 2 2 2 2 2 2 2 2 2 2 2 2
concurrent thread 16 24 24 32 24 24 32 48 24 32 48 65 24 32 48 65 80
table write thread 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16
Time(min) 15.1 14.1 10.8 9.8 9.7 7.6 7.7 7.5 7.9 6.6 6 6.7 6.8 6.7 6.7 5.5 6.9
Case2
memory 12
pod num 6 8 12 16 20
cpu 2 2 2 2 4 2 2 2 2 2 2 2 2 2 2 2 2
concurrent thread 16 24 24 32 24 24 32 48 24 32 48 65 24 32 48 65 80
table write thread 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16
Time(min) 14.3 10.7 8.9 10.2 7.9 8.7 6.4 7.6 7.6 5.9 5.8 5.6 6.2 6.9 5.3 6.3 6.7
【性能測試】記一次性能測試
【性能測試】記一次性能測試

分析兩次測試memrory升高的影響一般,基本沒有影響,cpu也沒有太大的影響,但是pod數量的變化對最終時間是有影響的,而并發數的變化對結果影響也不大。這裡再次分析了相關參數的變化,看出在增加pod數量到16後,與20pod的時間變化不大。

檢視傳回的日志,可以看出往本地寫,沒有網絡傳輸,table寫入資料的時間也不高了。

【性能測試】記一次性能測試

其次檢視了pod的restart情況,pod的restart變多了,原因分析本地寫入速度變快,每個pod處理request的頻次變高,pod記憶體加速上升了,是以pod的restart情況也會變多。

再分析了幾個參數對結果的影響因素,大概判斷這次測試的瓶頸可能在IO,因為調試了記憶體,pod number等參數,并沒有大的改善,而且pod number在16個之後,變化不大,于是去主要檢視了node IO情況。截取了2個node節點的監控名額,可以看出IO的速率上限在40MB/s。考慮到目前有8個節點在工作,兩者相乘整個叢集的IO寫入為320MB/s。 而從資料總量(95G)和寫入時間(最低5.3min)計算,得出305MB/s,符合叢集總體寫入速率。

【性能測試】記一次性能測試
【性能測試】記一次性能測試

為了測試節點的IO上限,還特别做了單節點IO測試,如下圖看出單個節點并發數在3以後,就達到了IO上限40MB/s,後面再增加并發,cpu的IO wait時間百分比線性上升,可以得出本次此時的瓶頸确實是在IO。

【性能測試】記一次性能測試

t011測試機控制台iostat 30s列印一次IO

【性能測試】記一次性能測試
【性能測試】記一次性能測試

分析為何pod到16的時候,總時間基本沒有變化。原因分析如下:

從io的測試來看,一個節點并發數在2-3的時候IO已經到上限了,目前節點node一共8個,16個pod配置設定到8個node的時候正好每個節點2個pod,每個節點2個pod同時寫資料時就基本能達到節點IO的上限了,是以在增加pod時,到20個,那麼某幾個節點會有3個pod,而每個節點2個pod就達到IO上限,是以再增加pod數量并不會對IO有變化,是以得出pod數量在16個之後,因為節點IO的問題,總時間不會有大的變化。是以對于本case IO瓶頸的情況,16個pod,cpu設定為2,memrory設定為8G是最佳配置。

六.CPU測試

為了測試cpu的使用情況,做了下面的測試,不寫資料時,隻解碼。

12pod,2core,12G

【性能測試】記一次性能測試
【性能測試】記一次性能測試

從上圖可以看出,在不寫資料,隻解碼的情況下,cpu的使用率是很高的,這與上面的測試形成了對比,分析在IO和網絡是瓶頸的時候,cpu的使用率就不明顯了。

七.pod負載問題

在測試中,pod負載量也是一個參考因素,因為pod負載不均衡,那麼就會導緻測試時間的差異。我在此測試中,就出現了該問題,kubernetes自帶的負載均衡政策隻有兩種,一種是輪詢,一種是sessionAffinity:ClientIP綁定IP,就是同一個IP的request指定到固定的pod,當然我測試的時候用的是輪詢,但是在輪詢的情況,pod的負載情況也不是很均衡,如下圖。這種情況建議用外部的負載均衡器

【性能測試】記一次性能測試
【性能測試】記一次性能測試
【性能測試】記一次性能測試

總結:

1.本次測試了兩種方式,分别是往hdfs寫資料和往本地寫資料

2.往hdfs寫網絡是瓶頸,往本地寫io是瓶頸

3.本次測試的場景,雖然結果來看主要是IO密集型任務,但是在撇除寫資料的功能,解碼這一塊也是會耗費大量的cpu操作(做過測試,不寫資料,隻解碼,速度很快,但是cpu很高),隻是在IO時間的影響下,cpu的操作顯得不明顯了。

4.在選擇最佳配置的時候,先考慮網絡和IO的瓶頸。如果是網絡瓶頸,降低并發數,找到網絡上限最低的并發數,在這個并發數的情況下,再去調cpu,memory和pod number的配置情況。如果是IO瓶頸,先測試單台node IO最大時的最低并發數,在此基礎上設定pod數量為node數量的該并發數的倍數。再去調cpu和memory。

5.性能測試影響的因素很多,不是幾個測試就能找出最優配置,環境的差異,軟體層面的差異,你的測試方法都會很大程度上影響最終結果,這些隻能在工作中慢慢摸索,積累,在後面的工作中給你幫助。