2012-01-24 28 views
12

Đã xảy ra sự cố gần đây trong đó tất cả 200 luồng chứa web bị treo, nghĩa là không có chủ đề nào sẵn có để phục vụ các yêu cầu đến và do đó ứng dụng bị đóng băng.Triển khai thực hiện WebSphere 7 HTTPSession có trái với thông số J2EE không?

Đây là một ứng dụng web đơn giản và kiểm tra JMeter mà tôi nghĩ rằng chứng minh nguyên nhân của vấn đề này. Các ứng dụng web bao gồm hai lớp, các servlet sau:

public class SessionTestServlet extends HttpServlet { 

    protected static final String SESSION_KEY = "session_key"; 

    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
      throws ServletException, IOException { 
     // set data on session so the listener is invoked 
     String sessionData = new String("Session data"); 
     request.getSession().setAttribute(SESSION_KEY, sessionData); 
     PrintWriter writer = response.getWriter();       
     writer.println("<html><body>OK</body></html>");      
     writer.flush(); 
     writer.close(); 
    } 
} 

và việc thực hiện sau đây của HttpSessionListener và HTTPSessionAttributeListener:

public class SessionTestListener implements 
     HttpSessionListener, HttpSessionAttributeListener { 

    private static final ConcurrentMap<String, HttpSession> allSessions 
     = new ConcurrentHashMap<String, HttpSession>(); 

    public void attributeRemoved(HttpSessionBindingEvent hsbe) {} 

    public void attributeAdded(HttpSessionBindingEvent hsbe) { 
     System.out.println("Attribute added, " + hsbe.getName() 
      + "=" + hsbe.getValue()); 

     int count = 0; 
     for (HttpSession session : allSessions.values()) { 
      if (session.getAttribute(SessionTestServlet.SESSION_KEY) != null) { 
       count++; 
      } 
     } 
     System.out.println(count + " of " + allSessions.size() 
      + " sessions have attribute set."); 
    } 

    public void attributeReplaced(HttpSessionBindingEvent hsbe) {} 

    public void sessionCreated(HttpSessionEvent hse) { 
     allSessions.put(hse.getSession().getId(), session);        
    } 

    public void sessionDestroyed(HttpSessionEvent hse) { 
     allSessions.remove(hse.getSession().getId()); 
    }     
} 

Các thử nghiệm JMeter đã 100 yêu cầu nhấn servlet mỗi giây:

<?xml version="1.0" encoding="UTF-8"?> 
<jmeterTestPlan version="1.2" properties="2.1"> 
    <hashTree> 
    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true"> 
     <stringProp name="TestPlan.comments"></stringProp> 
     <boolProp name="TestPlan.functional_mode">false</boolProp> 
     <boolProp name="TestPlan.serialize_threadgroups">false</boolProp> 
     <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> 
     <collectionProp name="Arguments.arguments"/> 
     </elementProp> 
     <stringProp name="TestPlan.user_define_classpath"></stringProp> 
    </TestPlan> 
    <hashTree> 
     <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true"> 
     <stringProp name="ThreadGroup.on_sample_error">continue</stringProp> 
     <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true"> 
      <boolProp name="LoopController.continue_forever">false</boolProp> 
      <intProp name="LoopController.loops">-1</intProp> 
     </elementProp> 
     <stringProp name="ThreadGroup.num_threads">100</stringProp> 
     <stringProp name="ThreadGroup.ramp_time">1</stringProp> 
     <longProp name="ThreadGroup.start_time">1327193422000</longProp> 
     <longProp name="ThreadGroup.end_time">1327193422000</longProp> 
     <boolProp name="ThreadGroup.scheduler">false</boolProp> 
     <stringProp name="ThreadGroup.duration"></stringProp> 
     <stringProp name="ThreadGroup.delay"></stringProp> 
     </ThreadGroup> 
     <hashTree> 
     <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="HTTP Request" enabled="true"> 
      <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true"> 
      <collectionProp name="Arguments.arguments"/> 
      </elementProp> 
      <stringProp name="HTTPSampler.domain">localhost</stringProp> 
      <stringProp name="HTTPSampler.port">9080</stringProp> 
      <stringProp name="HTTPSampler.connect_timeout"></stringProp> 
      <stringProp name="HTTPSampler.response_timeout"></stringProp> 
      <stringProp name="HTTPSampler.protocol">http</stringProp> 
      <stringProp name="HTTPSampler.contentEncoding"></stringProp> 
      <stringProp name="HTTPSampler.path">/SESSION_TESTWeb/SessionTestServlet</stringProp> 
      <stringProp name="HTTPSampler.method">GET</stringProp> 
      <boolProp name="HTTPSampler.follow_redirects">true</boolProp> 
      <boolProp name="HTTPSampler.auto_redirects">false</boolProp> 
      <boolProp name="HTTPSampler.use_keepalive">true</boolProp> 
      <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> 
      <boolProp name="HTTPSampler.monitor">false</boolProp> 
      <stringProp name="HTTPSampler.embedded_url_re"></stringProp> 
     </HTTPSamplerProxy> 
     <hashTree> 
      <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true"> 
      <stringProp name="ConstantTimer.delay">1000</stringProp> 
      </ConstantTimer> 
      <hashTree/> 
     </hashTree> 
     </hashTree> 
     <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true"> 
     <boolProp name="ResultCollector.error_logging">false</boolProp> 
     <objProp> 
      <name>saveConfig</name> 
      <value class="SampleSaveConfiguration"> 
      <time>true</time> 
      <latency>true</latency> 
      <timestamp>true</timestamp> 
      <success>true</success> 
      <label>true</label> 
      <code>true</code> 
      <message>true</message> 
      <threadName>true</threadName> 
      <dataType>true</dataType> 
      <encoding>false</encoding> 
      <assertions>true</assertions> 
      <subresults>true</subresults> 
      <responseData>false</responseData> 
      <samplerData>false</samplerData> 
      <xml>true</xml> 
      <fieldNames>false</fieldNames> 
      <responseHeaders>false</responseHeaders> 
      <requestHeaders>false</requestHeaders> 
      <responseDataOnError>false</responseDataOnError> 
      <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage> 
      <assertionsResultsToSave>0</assertionsResultsToSave> 
      <bytes>true</bytes> 
      </value> 
     </objProp> 
     <stringProp name="filename"></stringProp> 
     </ResultCollector> 
     <hashTree/> 
     <ResultCollector guiclass="SummaryReport" testclass="ResultCollector" testname="Summary Report" enabled="true"> 
     <boolProp name="ResultCollector.error_logging">false</boolProp> 
     <objProp> 
      <name>saveConfig</name> 
      <value class="SampleSaveConfiguration"> 
      <time>true</time> 
      <latency>true</latency> 
      <timestamp>true</timestamp> 
      <success>true</success> 
      <label>true</label> 
      <code>true</code> 
      <message>true</message> 
      <threadName>true</threadName> 
      <dataType>true</dataType> 
      <encoding>false</encoding> 
      <assertions>true</assertions> 
      <subresults>true</subresults> 
      <responseData>false</responseData> 
      <samplerData>false</samplerData> 
      <xml>true</xml> 
      <fieldNames>false</fieldNames> 
      <responseHeaders>false</responseHeaders> 
      <requestHeaders>false</requestHeaders> 
      <responseDataOnError>false</responseDataOnError> 
      <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage> 
      <assertionsResultsToSave>0</assertionsResultsToSave> 
      <bytes>true</bytes> 
      </value> 
     </objProp> 
     <stringProp name="filename"></stringProp> 
     </ResultCollector> 
     <hashTree/> 
    </hashTree> 
    </hashTree> 
</jmeterTestPlan> 

Khi thử nghiệm này được chạy với ứng dụng web thử nghiệm được triển khai trên WebSphere 7, ứng dụng sẽ nhanh chóng dừng đáp ứng và kết xuất lõi hiển thị điều này:

1LKDEADLOCK Deadlock detected !!! 
NULL   --------------------- 
NULL   
2LKDEADLOCKTHR Thread "WebContainer : 2" (0x000000000225C600) 
3LKDEADLOCKWTR is waiting for: 
4LKDEADLOCKMON  sys_mon_t:0x00000000151938C0 infl_mon_t: 0x0000000015193930: 
4LKDEADLOCKOBJ  com/ibm/ws/session/store/memory/[email protected]/00000000A38EA0D4: 
3LKDEADLOCKOWN which is owned by: 
2LKDEADLOCKTHR Thread "WebContainer : 1" (0x00000000021FB500) 
3LKDEADLOCKWTR which is waiting for: 
4LKDEADLOCKMON  sys_mon_t:0x0000000015193820 infl_mon_t: 0x0000000015193890: 
4LKDEADLOCKOBJ  com/ibm/ws/session/store/memory/[email protected]/00000000A14E22CC: 
3LKDEADLOCKOWN which is owned by: 
2LKDEADLOCKTHR Thread "WebContainer : 2" (0x000000000225C600) 
NULL 

Dường như khi một sợi (T1) thực hiện phương pháp của servlet doGet() gọi setAttribute() trên các trường hợp thực hiện HttpSession (S1), nó khóa trên màn hình của S1. Trong khi giữ khóa đó, nó đi vào sự lặp lại của allSessions bên trong phương thức attributeAdded() của người nghe và gọi getAttribute(). Nó trông giống như bên trong getAttribute(), WebSphere khóa trên màn hình của cá thể đó (có thể vì nó đang thiết lập một trường lastUpdateTime?). Vì vậy, T1 sẽ lần lượt khóa màn hình của S1, S2, S3, S4, S5 ... tất cả trong khi giữ khóa trên S1 từ cuộc gọi setAttribute() trong servlet. Vì vậy, nếu cùng một lúc một luồng (T2) khác đang khóa trên màn hình phiên khác (S2) trong servlet và sau đó đi vào vòng lặp trong addAttribute(), khóa bế tắc trên màn hình S1 và S2.

tôi đã không thể tìm thấy bất cứ điều gì rõ ràng trong thông số kỹ thuật J2EE về vấn đề này nhưng phần này của Servlet 2.4 đặc tả ngụ ý rằng các thùng chứa không nên đồng bộ hóa trên các trường hợp triển khai HttpSession:

SRV.7.7 .1 Các vấn đề về luồng

Nhiều chuỗi yêu cầu thực thi servlet có thể có quyền truy cập hoạt động một đối tượng phiên duy nhất cùng một lúc. Nhà phát triển có trách nhiệm để đồng bộ hóa quyền truy cập vào tài nguyên phiên như thích hợp.

JBoss không hiển thị bất kỳ deadlocks nào khi chúng tôi chạy thử nghiệm chống lại nó. Vì vậy, câu hỏi của tôi là:

  • Sự hiểu biết của tôi có đúng không?
  • Nếu có, đây có phải là lỗi hoặc trái với thông số J2EE trong WebSphere không?
  • Nếu không, và đó là hành vi hợp lệ mà nhà phát triển nên biết và mã hóa xung quanh, hành vi này có được ghi lại ở bất kỳ đâu không?

Cảm ơn

+3

WS giữ tôi tuyệt vời. Đẹp testcase. Tôi không thể trả lời từ kinh nghiệm hoặc tài nguyên có thẩm quyền, nhưng tôi sẽ không ngạc nhiên khi điều này thực sự là một quirk khác. – BalusC

Trả lời

3

Các Servlet 2.5 MR6 chứa một clarification đến một phần của spec Servlet trích dẫn trong câu hỏi:

Làm rõ SRV 7.7.1 "Vấn đề Threading" (Issue 33)

Thay đổi đoạn hiện tại là

"Nhiều chuỗi yêu cầu thực thi servlet có thể có quyền truy cập hoạt động vào đối tượng phiên duy nhất cùng một lúc. Các Nhà phát triển có trách nhiệm để đồng bộ hóa quyền truy cập vào phiên nguồn lực cho phù hợp. "

để đọc

" Nhiều servlets thực hiện đề nghị có thể tiếp cận tích cực để cùng một đối tượng phiên tại cùng một lúc. Vùng chứa phải đảm bảo rằng thao tác của các cấu trúc dữ liệu nội bộ đại diện cho thuộc tính phiên được thực hiện theo cách an toàn chủ đề . Nhà phát triển có trách nhiệm chủ đề an toàn quyền truy cập vào các đối tượng thuộc tính. Điều này sẽ bảo vệ bộ sưu tập thuộc tính bên trong đối tượng HttpSession từ đồng thời truy cập, loại bỏ cơ hội cho một ứng dụng để gây ra điều đó bộ sưu tập để trở thành hỏng."

này vẫn còn hiện tại trong Servlet 3.0 MR1 và làm cho WS của hành vi Tuy nhiên, tôi sẽ lấy nó từ đó * đặt * Thuộc tính có thể được đồng bộ hóa nhưng không phải là * nhận được * Thuộc tính sẽ là.

Vì vậy, tôi nghĩ rằng câu trả lời là:

  • WS tuân thủ spec Servlet theo làm rõ trong 2,5 MR6
  • Các spec lá chỗ cho hiểu lầm
  • WS được sốt sắng hơn với sự đồng bộ hóa của nó sẽ hợp lý hơn so với thông số kỹ thuật và AFAIK hành vi này không được ghi rõ ở bất cứ nơi nào

(Là một lưu ý phụ, thay đổi trường hợp thử nghiệm để listener.attributeAdded() gọi setAttribute thay vì getAttribute không gây ra deadlocks trên JBoss 4 hoặc 5.)

1

Bạn có thể đã tìm thấy một trường hợp sử dụng không được hỗ trợ của HttpSession trong việc thực hiện cụ thể của IBM WebSphere. Tại sao không báo cáo cho IBM?

Một điểm bạn đã bỏ lỡ khi triển khai: vùng chứa JavaEE có thể thụ động HttpSession đối tượng (bằng cách tuần tự hóa nó trên đĩa hoặc cơ sở dữ liệu) để giải phóng bộ nhớ nếu máy chủ phải xử lý quá nhiều phiên đang tải. Người nghe của bạn ngăn chặn bộ thu gom rác miễn phí các phiên đó.

Nhân tiện, đối tượng HttpSession được cho là chỉ được sử dụng bởi chuỗi tương ứng với phiên của riêng nó. Như bạn đã tìm thấy trong đặc điểm kỹ thuật, trong trường hợp có nhiều chuỗi đồng thời từ cùng một phiên, mã phải sử dụng cơ chế đồng bộ hóa trên trình gỡ lỗi HttpSession.

Trình nghe phiên là dựa trên sự kiện với tất cả thông tin cần thiết trong sự kiện, thiết kế như vậy là đủ để tránh người nghe giữ tất cả các tham chiếu đến việc sống HttpSession đối tượng theo cách bạn làm.

Ghering từ một chuỗi tất cả các phiên sống trong vùng chứa lạ và bất ngờ. Nó không phải là công việc của một ứng dụng web mà là một công cụ giám sát hoặc kiểm toán. Trong trường hợp đó, các phương tiện khác như truy vấn JMX hoặc giao diện PMI trong ngữ cảnh WebSphere cụ thể sẽ được sử dụng.

Để trợ giúp bạn, đây là một triển khai thay thế cho người nghe của bạn để đạt được cùng một thuộc tính phiên nhưng không giữ bất kỳ tham chiếu nào trên HttpSession. Hãy coi chừng: nó không được biên soạn và kiểm tra.

public class SessionTestListener implements 
     HttpSessionListener, HttpSessionAttributeListener { 

    private static final Set<String> sessionsIds 
     = new ConcurrentSkipListSet<String>(); 

    private static final ConcurrentMap<String, Object> sessionsKeys 
     = new ConcurrentHashMap<String, Object>(); 

    public void attributeRemoved(HttpSessionBindingEvent hsbe) { 
     System.out.println("Attribute removed, " + hsbe.getName() 
      + "=" + hsbe.getValue()); 
     if (SessionTestServlet.SESSION_KEY.equals(hsbe.getName())) { 
      sessionsKeys.remove(hsbe.getSession().getId()); 
     } 
    } 

    public void attributeAdded(HttpSessionBindingEvent hsbe) { 
     System.out.println("Attribute added, " + hsbe.getName() 
      + "=" + hsbe.getValue()); 

     if (SessionTestServlet.SESSION_KEY.equals(hsbe.getName())) { 
      if (hsbe.getValue() == null) { 
       sessionsKeys.remove(hsbe.getSession().getId()); 
      } else { 
       sessionsKeys.put(hsbe.getSession().getId(), hsbe.getValue()); 
      } 
     } 
     System.out.println(sessionsKeys.size() + " of " + sessionsIds.size() 
      + " sessions have attribute set."); 
    } 

    public void attributeReplaced(HttpSessionBindingEvent hsbe) {} 

    public void sessionCreated(HttpSessionEvent hse) { 
     sessionsIds.add(hse.getSession().getId()); 
    } 

    public void sessionDestroyed(HttpSessionEvent hse) { 
     sessionsIds.remove(hse.getSession().getId()); 
     sessionsKeys.remove(hse.getSession().getId()); 
    }     
} 
+0

Cảm ơn Yves. Có phải "đối tượng HttpSession được cho là chỉ được sử dụng bởi chuỗi tương ứng với phiên riêng của nó" một nguyên tắc hay quy tắc hay thực hành tốt nhất? –

+0

Tôi sẽ nói đó là trường hợp sử dụng tiêu chuẩn quy định việc thực hiện HttpSession từ quan điểm của nhà cung cấp container JavaEE. Bất kỳ trường hợp sử dụng nào khác chỉ phù hợp với các công cụ chứa nội bộ hoặc các công cụ kiểm tra/kiểm tra. Nhân tiện, bạn vẫn có giới hạn để truy cập các phiên bị động mà không có chi tiết triển khai cụ thể của vùng chứa. –

Các vấn đề liên quan