1. 程式人生 > 實用技巧 >【效能優化】小夥伴問我效能指標監控怎麼做,這次我安排上了!!

【效能優化】小夥伴問我效能指標監控怎麼做,這次我安排上了!!

寫在前面

在【冰河技術】微信公眾號的《【高併發】面試官:效能優化有哪些衡量指標?需要注意什麼?》一文中,介紹了效能優化的衡量指標和期間需要注意的事項,屬於理論篇的內容。這不,在文章下方就有小夥伴問我:監控怎麼做?我理解了他的意思後,今天就安排上了。

小夥伴的疑問

小夥伴:監控怎麼做?

我:你指的是?

小夥伴:效能指標。

我:後面會專門寫這些文章。

使用JMX監控Tomcat

關於監控的文章,先寫些什麼呢?想來想去,我們先來寫一篇使用JMX監控Tomcat的實戰文章吧。好了,我們直接進入主題。

啟用Tomcat的JMX遠端配置

要通過JMX遠端監控Tomcat,首先需要啟用Tomcat的JMX遠端配置。

① 修改指令碼

先修改Tomcat的啟動指令碼,windows下為bin/catalina.bat(linux下為catalina.sh),新增以下內容,8999是jmxremote使用的埠號,第二個false表示不需要鑑權:

set JMX_REMOTE_CONFIG=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
set CATALINA_OPTS=%CATALINA_OPTS% %JMX_REMOTE_CONFIG%

要注意以上語句的位置不能太后面,可以加在【if "%OS%" == "Windows_NT" setlocal】一句後的大段的註釋後面。

參考官方說明:

② 鑑權

上面的配置是不需要鑑權的,如果需要鑑權則新增的內容為:

set JMX_REMOTE_CONFIG=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=true -Dcom.sun.management.jmxremote.password.file=../conf/jmxremote.password -Dcom.sun.management.jmxremote.access.file=../conf/jmxremote.access
set CATALINA_OPTS=%CATALINA_OPTS% %JMX_REMOTE_CONFIG%

③ 複製並修改授權檔案

$JAVA_HOME/jre/lib/management下有jmxremote.access和jmxremote.password的模板檔案,將兩個檔案複製到$CATALINA_BASE/conf目錄下

◆ 修改$CATALINA_BASE/conf/jmxremote.access 新增內容

monitorRole readonly
controlRole readwrite

◆ 修改$CATALINA_BASE/conf/jmxremote.password 新增內容:

monitorRole binghe
controlRole binghe

注意: 如果進行了以上步驟導致Tomcat啟動不了,那麼很可能是密碼檔案的許可權問題

需要修改jmxremote.password檔案的訪問許可權,只有執行Tomcat的使用者才能擁有訪問許可權 :

Windows的NTFS檔案系統下,選中檔案,點右鍵 -->“屬性”-->“安全”--> 點“高階”--> 點“更改許可權”--> 去掉“從父項繼承....”--> 彈出視窗中選“刪除”,這樣就刪除了所有訪問許可權。再選“新增”--> “高階”--> “立即查詢”,選中你的使用者(或使用者組,如果選使用者不行那就選使用者組),例administrator,點“確定",“確定"。來到許可權專案視窗,勾選“完全控制”,點“確定”,OK了。

官方的提示

The password file should be read-only and only accessible by the operating system user Tomcat is running as.

④驗證配置

重新啟動Tomcat,在Windows命令列輸入“netstat -a”檢視配置的埠號是否已開啟,如果開啟,說明上面的配置成功了。

⑤ 使用jconsole測試JMX

執行$JAVA_HOME/bin目錄下的jconsole.exe,開啟J2SE監視和管理控制檯,然後建立連線,如果是本地的Tomcat則直接選擇然後點選連線,如果是遠端的,則進入遠端選項卡,填寫地址、埠號、使用者名稱、口令即可連線。。Mbean屬性頁中給出了相應的資料,Catalina中是tomcat的,java.lang是jvm的。對於加粗的黑體屬性值,需雙擊一下才可看內容。

程式碼獲取監控指標

  • 關鍵程式碼
String jmxURL = "service:jmx:rmi:///jndi/rmi://192.168.10.93:8999/jmxrmi";
JMXServiceURL serviceURL = new JMXServiceURL(jmxURL);
 
Map map = new HashMap();
// 使用者名稱密碼,在jmxremote.password檔案中檢視
String[] credentials = new String[] { "monitorRole", "tomcat" };
map.put("jmx.remote.credentials", credentials);
JMXConnector connector = JMXConnectorFactory.connect(serviceURL, map);
MBeanServerConnection mbsc = connector.getMBeanServerConnection();
 
// 埠最好是動態取得
ObjectName threadObjName = new ObjectName("Catalina:type=ThreadPool,name=http-8080");
MBeanInfo mbInfo = mbsc.getMBeanInfo(threadObjName);
 
// tomcat的執行緒數對應的屬性值
String attrName = "currentThreadCount";
MBeanAttributeInfo[] mbAttributes = mbInfo.getAttributes();
System.out.println("currentThreadCount:" + mbsc.getAttribute(threadObjName, attrName));
  • 完整程式碼
import java.lang.management.MemoryUsage;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
 
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanServerConnection;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
/**
 * @author binghe
 * @description JMX監控Tomcat程式碼實戰
 */
public class JMXTest {
	public static void main(String[] args) {
		try {
			String jmxURL = "service:jmx:rmi:///jndi/rmi://127.0.0.1:8999/jmxrmi";
			JMXServiceURL serviceURL = new JMXServiceURL(jmxURL);
			Map map = new HashMap();
			String[] credentials = new String[] { "monitorRole", "tomcat" };
			map.put("jmx.remote.credentials", credentials);
			JMXConnector connector = JMXConnectorFactory.connect(serviceURL,map);
			MBeanServerConnection mbsc = connector.getMBeanServerConnection();
			// 埠最好是動態取得
			ObjectName threadObjName = new ObjectName("Catalina:type=ThreadPool,name=http-8080");
			MBeanInfo mbInfo = mbsc.getMBeanInfo(threadObjName);
			String attrName = "currentThreadCount";// tomcat的執行緒數對應的屬性值
			MBeanAttributeInfo[] mbAttributes = mbInfo.getAttributes();
			System.out.println("currentThreadCount:" + mbsc.getAttribute(threadObjName, attrName));
 
			// heap
			for (int j = 0; j < mbsc.getDomains().length; j++) {
				System.out.println("###########" + mbsc.getDomains()[j]);
			}
			Set MBeanset = mbsc.queryMBeans(null, null);
			System.out.println("MBeanset.size() : " + MBeanset.size());
			Iterator MBeansetIterator = MBeanset.iterator();
			while (MBeansetIterator.hasNext()) {
				ObjectInstance objectInstance = (ObjectInstance) MBeansetIterator.next();
				ObjectName objectName = objectInstance.getObjectName();
				String canonicalName = objectName.getCanonicalName();
				System.out.println("canonicalName : " + canonicalName);
				if (canonicalName.equals("Catalina:host=localhost,type=Cluster")) {
					// Get details of cluster MBeans
					System.out.println("Cluster MBeans Details:");
					System.out.println("=========================================");
					// getMBeansDetails(canonicalName);
					String canonicalKeyPropList = objectName.getCanonicalKeyPropertyListString();
				}
			}
			// ------------------------- system ----------------------
			ObjectName runtimeObjName = new ObjectName("java.lang:type=Runtime");
			System.out.println("廠商:"+ (String) mbsc.getAttribute(runtimeObjName, "VmVendor"));
			System.out.println("程式:"+ (String) mbsc.getAttribute(runtimeObjName, "VmName"));
			System.out.println("版本:"+ (String) mbsc.getAttribute(runtimeObjName, "VmVersion"));
			Date starttime = new Date((Long) mbsc.getAttribute(runtimeObjName,"StartTime"));
			SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			System.out.println("啟動時間:" + df.format(starttime));
 
			Long timespan = (Long) mbsc.getAttribute(runtimeObjName, "Uptime");
			System.out.println("連續工作時間:" + JMXTest.formatTimeSpan(timespan));
			// ------------------------ JVM -------------------------
			// 堆使用率
			ObjectName heapObjName = new ObjectName("java.lang:type=Memory");
			MemoryUsage heapMemoryUsage = MemoryUsage.from((CompositeDataSupport) mbsc.getAttribute(heapObjName,"HeapMemoryUsage"));
			long maxMemory = heapMemoryUsage.getMax();// 堆最大
			long commitMemory = heapMemoryUsage.getCommitted();// 堆當前分配
			long usedMemory = heapMemoryUsage.getUsed();
			System.out.println("heap:" + (double) usedMemory * 100 / commitMemory + "%");// 堆使用率
 
			MemoryUsage nonheapMemoryUsage = MemoryUsage.from((CompositeDataSupport) mbsc.getAttribute(heapObjName,"NonHeapMemoryUsage"));
			long noncommitMemory = nonheapMemoryUsage.getCommitted();
			long nonusedMemory = heapMemoryUsage.getUsed();
			System.out.println("nonheap:" + (double) nonusedMemory * 100 / noncommitMemory + "%");
 
			ObjectName permObjName = new ObjectName("java.lang:type=MemoryPool,name=Perm Gen");
			MemoryUsage permGenUsage = MemoryUsage.from((CompositeDataSupport) mbsc.getAttribute(permObjName, "Usage"));
			long committed = permGenUsage.getCommitted();// 持久堆大小
			long used = heapMemoryUsage.getUsed();//
			System.out.println("perm gen:" + (double) used * 100 / committed + "%");// 持久堆使用率
 
			// -------------------- Session ---------------
			ObjectName managerObjName = new ObjectName("Catalina:type=Manager,*");
			Set<ObjectName> s = mbsc.queryNames(managerObjName, null);
			for (ObjectName obj : s) {
				System.out.println("應用名:" + obj.getKeyProperty("path"));
				ObjectName objname = new ObjectName(obj.getCanonicalName());
				System.out.println("最大會話數:" + mbsc.getAttribute(objname, "maxActiveSessions"));
				System.out.println("會話數:" + mbsc.getAttribute(objname, "activeSessions"));
				System.out.println("活動會話數:" + mbsc.getAttribute(objname, "sessionCounter"));
			}
 
			// ----------------- Thread Pool ----------------
			ObjectName threadpoolObjName = new ObjectName("Catalina:type=ThreadPool,*");
			Set<ObjectName> s2 = mbsc.queryNames(threadpoolObjName, null);
			for (ObjectName obj : s2) {
				System.out.println("埠名:" + obj.getKeyProperty("name"));
				ObjectName objname = new ObjectName(obj.getCanonicalName());
				System.out.println("最大執行緒數:" + mbsc.getAttribute(objname, "maxThreads"));
				System.out.println("當前執行緒數:" + mbsc.getAttribute(objname, "currentThreadCount"));
				System.out.println("繁忙執行緒數:" + mbsc.getAttribute(objname, "currentThreadsBusy"));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
 
	public static String formatTimeSpan(long span) {
		long minseconds = span % 1000;
 
		span = span / 1000;
		long seconds = span % 60;
 
		span = span / 60;
		long mins = span % 60;
 
		span = span / 60;
		long hours = span % 24;
 
		span = span / 24;
		long days = span;
		return (new Formatter()).format("%1$d天 %2$02d:%3$02d:%4$02d.%5$03d", days, hours, mins, seconds, minseconds).toString();
	}
}

Tomcat9 JVM引數調優

修改配置

#要新增在tomcat 的bin 下catalina.sh 裡新增
JAVA_OPTS="-Xms1024m -Xmx2048m -Xss2048K -XX:PermSize=128m -XX:MaxPermSize=256m"

引數說明

  • -Xms 初始化記憶體大小,一般設定為和Xmx一致,避免每次垃圾回收後重新分配記憶體
  • -Xmx 最大可用記憶體
  • -Xmn 年輕代大小
  • -Xss 設定每個執行緒棧的大小
  • -XX:MetaspaceSize=512M 初始元空間大小,達到該值就會觸發垃圾收集進行型別解除安裝,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize時,適當提高該值。
  • -XX:MaxMetaspaceSize=512M
  • -XX:+UseConcMarkSweepGC 併發標記清除(CMS)收集器
  • -XX:+CMSClassUnloadingEnabled
  • -XX:+HeapDumpOnOutOfMemoryError 表示當JVM發生OOM時,自動生成DUMP檔案。
  • -XX:HeapDumpPath=${目錄}引數表示生成DUMP檔案的路徑,也可以指定檔名稱,例如:XX:HeapDumpPath=${目錄}/java_heapdump.hprof。如果不指定檔名,預設為:java_

Tomcat 相關引數優化

連線數,執行緒數,快取,修改server.xml

開啟被註釋的預設連線池配置。

預設配置如下所示:

<!--  
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"  
maxThreads="150" minSpareThreads="4"/>  
-->  

修改例項:

<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"  
         maxThreads="150" 
         minSpareThreads="100"   
         prestartminSpareThreads="true" 
         maxQueueSize="100"/> 

引數說明:

  • name:執行緒名稱
  • namePrefix:執行緒字首
  • maxThreads:最大併發連線數,不配置時預設200,一般建議設定500~ 800 ,要根據自己的硬體設施條件和實際業務需求而定。
  • minSpareThreads:Tomcat啟動初始化的執行緒數,預設值25
  • prestartminSpareThreads:在tomcat初始化的時候就初始化minSpareThreads的值, 不設定trueminSpareThreads 的值就沒啥效果了 。
  • maxQueueSize:最大的等待佇列數,超過則拒絕請求

修改後的配置如下所示:

<Connector port="8080" protocol="HTTP/1.1"
        connectionTimeout="20000"  
        redirectPort="8443"   
        executor="tomcatThreadPool"  
        enableLookups="false"   
        maxIdleTime="60000"
        acceptCount="100"   
        maxPostSize="10485760" 
        acceptorThreadCount="2"    
        disableUploadTimeout="true"   
        URIEncoding="utf-8"
        keepAliveTimeout ="6000"  
        maxKeppAliveRequests="500"   
      />

引數說明:

  • port:連線埠。
  • protocol:聯結器使用的傳輸方式。
  • executor: 聯結器使用的執行緒池名稱
  • enableLookups:禁用DNS 查詢
  • maxIdleTime:執行緒空閒時間,超過該時間後,空閒執行緒會被銷燬,預設值為6000(1分鐘),單位毫秒。
  • acceptCount:指定當所有可以使用的處理請求的執行緒數都被使用時,可以放到處理佇列中的請求數,超過這個數的請求將不予處理,預設設定 100 。
  • maxPostSize:限制 以FORM URL 引數方式的POST請求的內容大小,單位位元組,預設是 2097152(2兆),10485760 為 10M。如果要禁用限制,則可以設定為 -1。
  • acceptorThreadCount: 用於接收連線的執行緒的數量,預設值是1。一般這個指需要改動的時候是因為該伺服器是一個多核CPU,如果是多核 CPU 一般配置為 2。
  • disableUploadTimeOut:允許Servlet容器,正在執行使用一個較長的連線超時值,以使Servlet有較長的時間來完成它的執行,預設值為false。
  • keepAliveTimeout - 表示在下次請求過來之前,tomcat保持該連線多久。這就是說假如客戶端不斷有請求過來,且未超過過期時間,則該連線將一直保持。
  • maxKeepAliveRequests -表示該連線最大支援的請求數。超過該請求數的連線也將被關閉(此時就會返回一個Connection: close頭給客戶端).(maxKeepAliveRequests="1"代表禁用長連線)(1表示禁用,-1表示不限制個數,預設100個。一般設定在100~200之間)

重磅福利

微信搜一搜【冰河技術】微信公眾號,關注這個有深度的程式設計師,每天閱讀超硬核技術乾貨,公眾號內回覆【PDF】有我準備的一線大廠面試資料和我原創的超硬核PDF技術文件,以及我為大家精心準備的多套簡歷模板(不斷更新中),希望大家都能找到心儀的工作,學習是一條時而鬱鬱寡歡,時而開懷大笑的路,加油。如果你通過努力成功進入到了心儀的公司,一定不要懈怠放鬆,職場成長和新技術學習一樣,不進則退。如果有幸我們江湖再見!

另外,我開源的各個PDF,後續我都會持續更新和維護,感謝大家長期以來對冰河的支援!!

寫在最後

如果你覺得冰河寫的還不錯,請微信搜尋並關注「 冰河技術 」微信公眾號,跟冰河學習高併發、分散式、微服務、大資料、網際網路和雲原生技術,「 冰河技術 」微信公眾號更新了大量技術專題,每一篇技術文章乾貨滿滿!不少讀者已經通過閱讀「 冰河技術 」微信公眾號文章,吊打面試官,成功跳槽到大廠;也有不少讀者實現了技術上的飛躍,成為公司的技術骨幹!如果你也想像他們一樣提升自己的能力,實現技術能力的飛躍,進大廠,升職加薪,那就關注「 冰河技術 」微信公眾號吧,每天更新超硬核技術乾貨,讓你對如何提升技術能力不再迷茫!