【性能测试】记一次性能测试

9 篇文章 1 订阅
5 篇文章 0 订阅

目录

一.测试环境介绍

二.本次测试参数介绍

三.解码程序介绍

四.HDFS测试

五.本地测试

六.CPU测试

七.pod负载问题

总结:


公司创建了一个全局的kubernetes集群,服务器在泰国,使用rancher2.5.5搭建的。因为里面配置的资源很大,但是还没有正式的应用部署在上面,领导要求部署几个app上去跑跑资源。决定下来将解码程序放上去跑跑,解码程序是将工厂测试数据的文件解码出来,一个serial numner文件一般有240张表,全部解出写入存储目录,一个serial number一张表为一个目录,先不压缩,直接写txt文件。

一.测试环境介绍

Rancher版本2.5.1

节点配置

master3cpu48corememrory125G
node8cpu16corememrory48G

二.本次测试参数介绍

测试数据量
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
memory12G
pod num81625
cpu248242
concurrent thread50150505050501001002550
table write thread5050165050501650505050
Time(min)18.1206sn/18.218.618.118.417.818.71617.421.918.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内部查看内存的影响。

[root@t001 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
[root@t001 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
[root@t001 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
[root@t001 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
[root@t001 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
[root@t001 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>
[root@t001 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瓶颈。

[root@seadoop-test128 ~]# 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
[root@seadoop-test133 ~]# 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 
memory8
pod num68121620
cpu22224222222222222
concurrent thread1624243224243248243248652432486580
table write thread1616161616161616161616161616161616
Time(min)15.114.110.89.89.77.67.77.57.96.666.76.86.76.75.56.9

Case2 
memory12
pod num68121620
cpu22224222222222222
concurrent thread1624243224243248243248652432486580
table write thread1616161616161616161616161616161616
Time(min)14.310.78.910.27.98.76.47.67.65.95.85.66.26.95.36.36.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.性能测试影响的因素很多,不是几个测试就能找出最优配置,环境的差异,软件层面的差异,你的测试方法都会很大程度上影响最终结果,这些只能在工作中慢慢摸索,积累,在后面的工作中给你帮助。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值