最近,業務增長的很迅猛,對於我們后台這塊也是一個不小的挑戰,這次遇到的核心業務接口的性能瓶頸,並不是單獨的一個問題導致的,而是幾個問題揉在一起:我們解決一個之后,發上線,之后發現還有另一個的性能瓶頸問題。這也是我經驗不足,導致沒能一下子定位解決;而我又對我們后台整個團隊有着固執的自尊,不想通過大量水平擴容這種方式挺過壓力高峰,導致線上連續幾晚都出現了不同程度的問題,肯定對於我們的業務增長是有影響的。這也是我不成熟和要反思的地方。這系列文章主要記錄下我們針對這次業務增長,對於我們后台微服務系統做的通用技術優化,針對業務流程和緩存的優化由於只適用於我們的業務,這里就不再贅述了。本系列會分為如下幾篇:
- 改進客戶端負載均衡算法
- 開發日志輸出異常堆棧的過濾插件
- 針對 x86 雲環境改進異步日志等待策略
- 增加對於同步微服務的 HTTP 請求等待隊列的監控以及雲上部署,需要小心達到實例網絡流量上限導致的請求響應緩慢
- 針對系統關鍵業務增加必要的侵入式監控
增加對於同步微服務的 HTTP 請求等待隊列的監控
同步微服務對於請求超時存在的問題
相對於基於 spring-webflux 的異步微服務,基於 spring-webmvc 的同步微服務沒有很好的處理客戶端有請求超時配置的情況。當客戶端請求超時時,客戶端會直接返回超時異常,但是調用的服務端任務,在基於 spring-webmvc 的同步微服務並沒有被取消,基於 spring-webflux 的異步微服務是會被取消的。目前,還沒有很好的辦法在同步環境中可以取消這些已經超時的任務。
我們的基於 spring-webmvc 的同步微服務,HTTP 容器使用的是 Undertow。在 spring-boot 環境下,我們可以配置處理 HTTP 請求的線程池大小:
server:
undertow:
# 以下的配置會影響buffer,這些buffer會用於服務器連接的IO操作
# 如果每次需要 ByteBuffer 的時候都去申請,對於堆內存的 ByteBuffer 需要走 JVM 內存分配流程(TLAB -> 堆),對於直接內存則需要走系統調用,這樣效率是很低下的。
# 所以,一般都會引入內存池。在這里就是 `BufferPool`。
# 目前,UnderTow 中只有一種 `DefaultByteBufferPool`,其他的實現目前沒有用。
# 這個 DefaultByteBufferPool 相對於 netty 的 ByteBufArena 來說,非常簡單,類似於 JVM TLAB 的機制
# 對於 bufferSize,最好和你系統的 TCP Socket Buffer 配置一樣
# `/proc/sys/net/ipv4/tcp_rmem` (對於讀取)
# `/proc/sys/net/ipv4/tcp_wmem` (對於寫入)
# 在內存大於 128 MB 時,bufferSize 為 16 KB 減去 20 字節,這 20 字節用於協議頭
buffer-size: 16364
# 是否分配的直接內存(NIO直接分配的堆外內存),這里開啟,所以java啟動參數需要配置下直接內存大小,減少不必要的GC
# 在內存大於 128 MB 時,默認就是使用直接內存的
directBuffers: true
threads:
# 設置IO線程數, 它主要執行非阻塞的任務,它們會負責多個連接, 默認設置每個CPU核心一個讀線程和一個寫線程
io: 4
# 阻塞任務線程池, 當執行類似servlet請求阻塞IO操作, undertow會從這個線程池中取得線程
# 它的值設置取決於系統線程執行任務的阻塞系數,默認值是IO線程數*8
worker: 128
其背后的線程池,是 jboss 的線程池:org.jboss.threads.EnhancedQueueExecutor
,spring-boot 目前不能通過配置修改這個線程池的隊列大小,默認隊列大小是 Integer.MAX
我們需要監控這個線程池的隊列大小,並針對這個指標做一些操作:
- 當這個任務持續增多的時候,就代表這時候請求處理跟不上請求到來的速率了,需要報警。
- 當累積到一定數量時,需要將這個實例暫時從注冊中心取下,並擴容。
- 待這個隊列消費完之后,重新上線。
- 當超過一定時間還是沒有消費完的話,將這個實例重啟。
添加同步微服務 HTTP 請求等待隊列監控
幸運的是,org.jboss.threads.EnhancedQueueExecutor
本身通過 JMX 暴露了 HTTP servlet 請求的線程池的各項指標:
我們的項目中,使用兩種監控:
- prometheus + grafana 微服務指標監控,這個主要用於報警以及快速定位問題根源
- JFR 監控,這個主要用於詳細定位單實例問題
對於 HTTP 請求等待隊列監控,我們應該通過 prometheus 接口向 grafana 暴露,采集指標並完善響應操作。
暴露 prometheus 接口指標的代碼是:
@Log4j2
@Configuration(proxyBeanMethods = false)
//需要在引入了 prometheus 並且 actuator 暴露了 prometheus 端口的情況下才加載
@ConditionalOnEnabledMetricsExport("prometheus")
public class UndertowXNIOConfiguration {
@Autowired
private ObjectProvider<PrometheusMeterRegistry> meterRegistry;
//只初始化一次
private volatile boolean isInitialized = false;
//需要在 ApplicationContext 刷新之后進行注冊
//在加載 ApplicationContext 之前,日志配置就已經初始化好了
//但是 prometheus 的相關 Bean 加載比較復雜,並且隨着版本更迭改動比較多,所以就直接偷懶,在整個 ApplicationContext 刷新之后再注冊
// ApplicationContext 可能 refresh 多次,例如調用 /actuator/refresh,還有就是多 ApplicationContext 的場景
// 這里為了簡單,通過一個簡單的 isInitialized 判斷是否是第一次初始化,保證只初始化一次
@EventListener(ContextRefreshedEvent.class)
public synchronized void init() {
if (!isInitialized) {
Gauge.builder("http_servlet_queue_size", () ->
{
try {
return (Integer) ManagementFactory.getPlatformMBeanServer()
.getAttribute(new ObjectName("org.xnio:type=Xnio,provider=\"nio\",worker=\"XNIO-2\""), "WorkerQueueSize");
} catch (Exception e) {
log.error("get http_servlet_queue_size error", e);
}
return -1;
}).register(meterRegistry.getIfAvailable());
isInitialized = true;
}
}
}
之后,調用 /actuator/prometheus
我們就能看到對應的指標:
# HELP http_servlet_queue_size
# TYPE http_servlet_queue_size gauge
http_servlet_queue_size 0.0
當發生隊列堆積時,我們能快速的報警,並且直觀地從 grafana 監控上發現:
對於公有雲部署,關注網絡限制的監控
現在的公有雲,都會針對物理機資源進行虛擬化,對於網絡網卡資源,也是會虛擬化的。以 AWS 為例,其網絡資源的虛擬化實現即 ENA(Elastic Network Adapter)。它會對以下幾個指標進行監控並限制:
- 帶寬:每個虛擬機實例(AWS 中為每個 EC2 實例),都具有流量出的最大帶寬以及流量入的最大帶寬。這個統計使用一種網絡 I/O 積分機制,根據平均帶寬使用率分配網絡帶寬,最后的效果是允許短時間內超過額定帶寬,但是不能持續超過。
- 每秒數據包 (PPS,Packet Per Second) 個數:每個虛擬機實例(AWS 中為每個 EC2 實例)都限制 PPS 大小
- 連接數:建立連接的個數是有限的
- 鏈接本地服務訪問流量:一般在公有雲,每個虛擬機實例 (AWS 中為每個 EC2 實例)訪問 DNS,元數據服務器等,都會限制流量
同時,成熟的公有雲,這些指標一般都會對用戶提供展示分析界面,例如 AWS 的 CloudWatch 中,就提供了以下幾個指標的監控:
在業務流量突增時,我們通過 JFR 發現訪問 Redis 有性能瓶頸,但是 Redis 本身的監控顯示他並沒有遇到性能瓶頸。這時候就需要查看是否因為網絡流量限制導致其除了問題,在我們出問題的時間段,我們發現 NetworkBandwidthOutAllowanceExceeded 事件顯著提高了很多:
對於這種問題,就得需要考慮垂直擴容(提升實例配置)與水平擴容(多實例負載均衡)了,或者減少網絡流量(增加壓縮等)
微信搜索“我的編程喵”關注公眾號,每日一刷,輕松提升技術,斬獲各種offer: