html5调用系统声音1s响一次_记录一次系统性能调优过程
在線上環境,由于業務場景需要,要求程序能夠在普通的4G機器中依然正常運行。 而原來的環境配置為8核16G,微服務部署,一共有6個功能模塊。 而現在要求在一臺4核4G的設備上正常運行。
問題清單
問題排查
1. 代碼沖突
- 包名沖突。 不同模塊的包名設計上有重復
- 類名沖突。 @Configuration @Bean @Controller @Service @Repository 等注解中沒有指定Bean實例的名稱。
2. 事件處理性能慢
現有的處理流程如下:
項目采用SpringBoot構建,引入 spring-boot-stater-redis
1. 通過HTTP接收到異步事件,存儲到Redis;
2. 存儲的同時,將事件通過Redis的發布訂閱發送到不同的處理單元進行處理;
3. 每個事件處理單元通過Redis訂閱,然后處理事件;
4. 起一個定時器,每秒鐘從Redis中查詢一個時間窗口的事件,構建索引,然后bulkIndex到ES
2.1 問題發現
1. Redis的訂閱發布,內部會維護一個container線程,此線程會一直存在;2. 每次訂閱,都會產生一個新的前綴為RedisListeningContainer-的線程處理;3. 通過jvisualvm.exe 查看線程數,該類線程數一直在飆升2.2 問題定位
2.2.1 Redis訂閱發布問題
程序中的實現如下:
@Bean
RedisMessageListenerContainer manageContainer(
RedisConnectionFactory factory, MessageListener listener) {
RedisMessageListenerContainer manageContainer =
new RedisMessageListenerContainer ();
manageContainer.setConnectionFactory(factory);
// manageContainer.setTaskExecutor();
}
代碼中被注釋掉的那一行,實際代碼中是沒有該行的,也就是沒有設置 taskExecutor
- 查詢了 spring-redis.xsd 中關于 listener-container 的說明,默認的 task-executor 和 subscription-task-executor 使用的是 SimpleAsyncTaskExecutor 。
- 在源碼中的位置
RedisMessageListenerContainer.class...
protected TaskExecutor createDefaultTaskExecutor() {
String threadNamePrefix = (beanName != null ? beanName + "-" :
DEFAULT_THREAD_NAME_PREFIX) ;
return new SimpleAsyncTaskExecutor(threadNamePrefix);
}
...
SimpleAsyncTaskExecutor.class
protected void doExecute(Runnable task) {
Thread thread =
(this.threadFactory != null
? this.threadFactory,newThread(task)
: createThread(task));
thread.start();
}
- SimpleAsyncTaskExecutor 的execute()方法,是很無恥的 new Thread() ,調用 thread.start() 來執行任務
2.2.2 事件處理都是耗時操作,造成線程數越來越多,甚至OOM
2.3 問題解決
找到問題的產生原因,主要的解決思路有三種:
- 配置 manageContainer.setTaskExecutor(); 然后選擇自己創建的線程池;
- 去掉一部分發布訂閱,改用 Spring 提供的 觀察者模式 ,將絕大部分事件處理的場景,通過此方式完成發布。 SpringUtils.getApplicationContext() .publihEvent(newEventOperation(eventList));
- 采用 Rector 模式實現事件的異步高效處理;
@Override
public void onApplicationEvent (EventOperation event) {
eventTaskExecutor.execute(() -> {
doDealEventOperation(event);
});
}
3. 事件丟失
現有的處理流程如下:
項目采用SpringBoot構建,引入 spring-boot-stater-redis1. 后臺維護了一個定時器,每秒鐘從Redis中查詢一個時間窗口的事件3.1 問題發現
在后臺定位日志輸出,正常情況下,應該是每秒鐘執行一次定時,
但實際是,系統并不保證一定能每隔1S執行一次,
由于系統中線程比較多,CPU的切換頻繁,
導致定時有可能1S執行幾次或者每隔幾秒執行一次
3.2 問題定位
3.2.1 定時任務不可靠
由于定時并無法保證執行,而定時任務獲取事件時,是按照時間窗口截取,通過redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore)實現,勢必會造成有數據無法被加載到程序中,而一直保存在Redis中,無法獲取,也無法刪除3.3 問題解決
找到問題的產生原因,主要的解決思路有兩種:
- 加大容錯率,將時間窗口拉大,原來是相隔1S的時間窗口,修改為相隔1MIN 【治標不治本,極端情況下,仍有可能造成該問題】;
- 采用 MQ消費 ,此方法需要額外部署MQ服務器,在集群配置高的情況下,可以采用,在配置低的機器下不合適;
- 采用 阻塞隊列 ,利用 Lock.newCondition() 或者最普通的網絡監聽模式 while() 都可以;
本次問題中采用的是第三種形式。 起一個單獨的線程,阻塞監聽。
1. 事件接收后,直接塞到一個BlockingQueue中;
2. 當BlockingQueue有數據時,While循環不阻塞,逐條讀取隊列中的信息;
3. 每隔1000條數據,或者每隔1S,將數據寫入ES,并分發其他處理流程
4. 系統cache一直在不斷的上漲
在4G的機器下,發現經過一段時間的發包處理后,系統cache增長的非常快,最后幾近于全部占滿:
大概每秒鐘10M的漲幅4.1 問題發現
1. 因為對于ES的了解,插入數據時,先寫緩存,后fsync到磁盤上,因此懷疑ES可能存在問題;2. 項目中日志使用log4j2不當: * 日志輸出過多, * 日志沒有加判斷:if (log.isInfoEnabled()) * 日志文件append過大,沒有按照大小切分等(本項目此問題之前已解決)4.2 問題定位
4.2.1 ES插入機制問題
經過隔段分析,將有可能出現問題的地方,分別屏蔽后,進行測試。
最終定位到,在ES批量寫入數據時,才會出現cache大量增長的現象
4.3 問題解決
用命令查看內存 free -m ,
- buffer : 作為 buffer cache 的內存,是 塊設備 的讀寫緩沖區
- cached 表示 page cache的內存 和 文件系統的cache
- 如果 cached 的值很大,說明cache住的文件數很多
ES操作數據的 底層機制 :
數據寫入時,ES內存緩慢上升,是因為小文件過多(ES本身會在index時候建立大量的小文件), linux dentry 和 inode cache 會增加。 可以參考: ES內存持續上升問題定位
本問題其實并沒有完全解決,只是在一定程度上用性能換取緩存。
## 這些參數是之前優化的
threadpool.bulk.type: fixed
threadpool.bulk.min: 10
threadpool.bulk.max: 10
threadpool.bulk.queue_size: 2000
threadpool.index.type: fixed
threadpool.index.size: 100
threadpool.index.queue_size: 1000
index.max_result_window: 1000000
index.query.bool.max_clause_count: 1024000
# 以下的參數為本次優化中添加的:
# 設置ES最大緩存數據條數和緩存失效時間
index.cache.field.max_size: 20000
index.cache.field.expire: 1m
# 當內存不足時,對查詢結果數據緩存進行回收
index.cache.field.type: soft
# 當內存達到一定比例時,觸發GC。默認為JVM的70%[內存使用最大值]
#indices.breaker.total.limit: 70%
# 用于fielddata緩存的內存數量,
# 主要用于當使用排序操作時,ES會將一些熱點數據加載到內存中來提供客戶端訪問
indices.fielddata.cache.expire: 20m
indices.fielddata.cache.size: 10%
# 一個節點索引緩沖區的大小[max 默認無限制]
#indices.memory.index_buffer_size: 10%
#indices.memory.min_index_buffer_size: 48M
#indices.memory.max_index_buffer_size: 100M
# 執行數據過濾時的數據緩存,默認為10%
#indices.cache.filter.size: 10%
#indices.cache.filter.expire: 20m
# 當tranlog的大小達到此值時,會進行一次flush操作,默認是512M
index.translog.flush_threshold_size: 100m
# 在指定時間間隔內如果沒有進行進行flush操作,會進行一次強制的flush操作,默認是30分鐘
index.translog.flush_threshold_period: 1m
# 多長時間進行一次的磁盤操作,默認是5S
index.gateway.local.sync: 1s
歷程回顧
- 對于本次調優過程,其主要修改方向還是代碼,即代碼的使用不當,或者考慮不周導致
- 其次,對于ES的底層實現機制并不很熟悉,定位到具體的問題所在;
- 本次優化過程中,涉及到對GC的定位,對Linux系統底層參數的配置等
- 由于日志傳輸采用HTTP,故每次傳輸都是新的線程。 IO開銷比較大,后續會考慮替換成 長連接 。
總結
以上是生活随笔為你收集整理的html5调用系统声音1s响一次_记录一次系统性能调优过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 塑钢瓦图片_塑钢瓦和彩钢瓦哪种好 如何准
- 下一篇: js rsa验签_js rsa sig