一次線上問題的排查解決過程

tinyking發表於2019-02-24

問題

XX系統中,一個使用者需要維護的專案數過多,填寫的任務數超多,產生了一次工時儲存中,只有前面一部分的xx資料持久化到資料庫,後面的資料沒有儲存。

圖1

一次線上問題的排查解決過程

排查過程

1.增加日誌,監控引數資訊

首先想到的是否後面部分的資料在儲存過程中發生了異常。排查異常日誌,發現沒有該問題存在。

然後增加方法引數資訊日誌,資料引數資訊。發現引數集合size=200,前端傳送集合size=400。判斷問題可以能是因為伺服器容器環境(Nginx+Tomcat)導致

2.開發環境問題重現

2.1 模擬資料

在測試環境模擬線上資料。如圖1

2.2 只配置Tomcat

在idea中直接啟動tomcat,無nginx環境,如果沒有問題,則可暫時確定為nginx問題。

然而,在過程中發現了新的問題。

org.springframework.beans.InvalidPropertyException: Invalid property `detail[256]` of bean class [com.suning.asvp.mer.entity.InviteCooperationInfo]: Index of out of bounds in property path `detail[256]`; nested exception is java.lang.IndexOutOfBoundsException: Index: 256, Size: 256  
    at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:833) ~[spring-beans-3.1.2.RELEASE.jar:3.1.2.RELEASE]  
    at org.springframework.beans.BeanWrapperImpl.getNestedBeanWrapper(BeanWrapperImpl.java:576) ~[spring-beans-3.1.2.RELEASE.jar:3.1.2.RELEASE]  
    at org.springframework.beans.BeanWrapperImpl.getBeanWrapperForPropertyPath(BeanWrapperImpl.java:553) ~[spring-beans-3.1.2.RELEASE.jar:3.1.2.RELEASE]  
    at org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.java:914) ~[spring-beans-3.1.2.RELEASE.jar:3.1.2.RELEASE]  
    at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:76) ~[spring-beans-3.1.2.RELEASE.jar:3.1.2.RELEASE]  
    at org.springframework.validation.DataBinder.applyPropertyValues(DataBinder.java:692) ~[spring-context-3.1.2.RELEASE.jar:3.1.2.RELEASE]  
    at org.springframework.validation.DataBinder.doBind(DataBinder.java:588) ~[spring-context-3.1.2.RELEASE.jar:3.1.2.RELEASE]  
    at org.springframework.web.bind.WebDataBinder.doBind(WebDataBinder.java:191) ~[spring-web-3.1.2.RELEASE.jar:3.1.2.RELEASE]  
    at org.springframework.web.bind.ServletRequestDataBinder.bind(ServletRequestDataBinder.java:112) ~[spring-web-3.1.2.RELEASE.jar:3.1.2.RELEASE] 
複製程式碼

檢視BeanWrapperImpl原始碼

else if (value instanceof List) {  
    int index = Integer.parseInt(key);                        
    List list = (List) value;  
    growCollectionIfNecessary(list, index, indexedPropertyName, pd, i + 1);                       
    value = list.get(index);// 測試報錯時,此處list只有256個,index為256時,取第257個報錯  
}  
複製程式碼
@SuppressWarnings("unchecked")  
    private void growCollectionIfNecessary(  
            Collection collection, int index, String name, PropertyDescriptor pd, int nestingLevel) {  
  
  
        if (!this.autoGrowNestedPaths) {  
            return;  
        }  
        int size = collection.size();  
        // 當個數小於autoGrowCollectionLimit這個值時才會向list中新增新元素  
        if (index >= size && index < this.autoGrowCollectionLimit) {  
            Class elementType = GenericCollectionTypeResolver.getCollectionReturnType(pd.getReadMethod(), nestingLevel);  
            if (elementType != null) {  
                for (int i = collection.size(); i < index + 1; i++) {  
                    collection.add(newValue(elementType, name));  
                }  
            }  
        }  
    }  
複製程式碼

根據上面的分析找到autoGrowCollectionLimit的定義

public class DataBinder implements PropertyEditorRegistry, TypeConverter {  
  
    /** Default object name used for binding: "target" */  
    public static final String DEFAULT_OBJECT_NAME = "target";  
  
    /** Default limit for array and collection growing: 256 */  
    public static final int DEFAULT_AUTO_GROW_COLLECTION_LIMIT = 256;  
  
    private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT; 
複製程式碼

解決方案,是在自己的Controller中加入如下方法

@InitBinder  
protected void initBinder(WebDataBinder binder) {  
    binder.setAutoGrowNestedPaths(true);  
    binder.setAutoGrowCollectionLimit(1024);  
}  
複製程式碼

==BUT 這個問題和線上的不同,只能算是意外收穫。革命尚未成功,同志仍需努力!!!!==

2.3 增加Nginx

經過2.2的奮鬥,暫時判定是否為Nginx post請求引數做了限制。嗯,開搞~ 在開發環境配置Nginx代理,過程略·····

nginx.conf 如下

upstream xxxxxxx {
	server 127.0.0.1:8080  weight=10 max_fails=2 fail_timeout=30s ;
}

server {
    listen       80;
    server_name  xxxxxxx.com;
    client_max_body_size 100M;  # 配置post size
    
    #charset koi8-r;
  
    #access_log  logs/host.access.log  main;
	
   location / {
		#proxy_next_upstream     http_500 http_502 http_503 http_504 error timeout invalid_header;
		proxy_set_header        Host  $host;
		proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_pass              http://xxxxxxx;
		expires                 0;
	}
}
複製程式碼

對於client_max_body_size 100M;,網上都是與檔案上傳相關的。不過都是通過post, request body的方式上傳資料,所以通用。

測試~~

功能正常,沒有重現線上問題。 哭死~~~

革命還要繼續~~

2.4 Tomcat post設定

去線上伺服器拉去配置

<Connector port="1601" maxParameterCount="1000" protocol="HTTP/1.1" redirectPort="8443" maxSpareThreads="750" maxThreads="1000" minSpareTHreads="50" acceptCount="1000" connectionTimeout="20000" URIEncoding="utf-8"/>
複製程式碼

經分析,發現線上沒有body size的配置,卻有maxParameterCount="1000"。該引數為限制請求的引數個數,從而變相限制body size。

在開發環境配置該引數,測試,問題重現

3. 解決

問題原因定位好了,剩下的就是如何解決了。

兩個方案:

  • 修改線上配置

    該上實施難度係數高,因為公司使用的統一發布部署平臺,開發人員無伺服器操作許可權。

  • 修改程式碼

    修改儲存邏輯,分片儲存

總結

問題排查,需要先對整體有個把握,然後分析影響範圍。不能鑽牛角尖,採用西醫“頭疼醫頭”的方式。有可能最後結果還是要醫頭,但此時的醫頭已經是建立在中醫的辯證主義上,對症下藥。

相關文章