這兩天無事,正好學習下。出發點是, 怎樣模擬高併發訪問restful api。步驟以下:html
一、客戶端java
客戶端模擬多線程訪問,有兩種方式:nginx
1.一、利用synchronized、object的wait和notifyAll方法,代碼以下:git
package com.mxsoft.web; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * 模擬高併發 * * @author zhangyingxuan */ public class SimulateHighConcurrency1 { static volatile int successNum = 0; static volatile int failNum = 0; public static void main(String[] args) throws InterruptedException { final Object obj = new Object(); synchronized (obj) { List<Thread> threadList = new ArrayList<>(); for (int i = 0; i < 1000; i++) { Thread th = new Thread(() -> { try { obj.wait(); TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } String doneWork = TestDemo.doneWork(); if ((doneWork.contains("success"))) { successNum++; } else { failNum++; } }); threadList.add(th); } for (Thread thread : threadList) { thread.start(); } obj.notifyAll(); for (Thread thread : threadList) { thread.join(); } System.out.println("run done: success -> " + successNum + "; fail -> " + failNum); } } }
1.二、利用jdk concurrent包下的api,代碼以下:web
package com.mxsoft.web; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; /** * 模擬高併發 * * @author zhangyingxuan */ public class SimulateHighConcurrency2 { //請求總數 public static int clientTotal = 100000; //同時併發執行的線程數 public static int threadTotal = 100; private static AtomicInteger num = new AtomicInteger(); public static volatile int successNum = 0; public static volatile int failNum = 0; public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(1000); //信號量, 此處用於控制併發的線程數 final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for(int i = 0; i < clientTotal; i ++) { executorService.execute(() -> { try { semaphore.acquire(); String doneWork = TestDemo.doneWork(); if (doneWork.contains("success")) { successNum++; } else { System.out.println(doneWork); failNum++; } semaphore.release(); } catch (Exception e) { e.printStackTrace(); } countDownLatch.countDown(); System.out.println(num.incrementAndGet()); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("successNum: " + successNum + " failNum: " + failNum); } }
說明:apache
Executors.newCachedThreadPool() 這個方法,若是線程數太多,會形成機器以前重啓。個人是mac電腦,5000個線程直接致使重啓了。
二、服務器端api
package com.mxsoft.web; import com.mxsoft.util.ThreadPoolExecutorUtils; import com.mxsoft.util.UserThreadState; import com.mxsoft.util.UserUtils; import com.mxsoft.web.bean.UserObj; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class HellowordServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { UserObj currentUser = UserUtils.getCurrentUser(); System.out.println(currentUser.getName() + "->" + currentUser.hashCode()); // new Thread(new Runnable() { // @Override // public void run() { // UserObj user = UserUtils.getCurrentUser(); // System.out.println("自線程: " + (user == null ? "user爲null" : user.getName() + "->" + user.hashCode())); // } // }).start(); ThreadPoolExecutorUtils.execute(new UserRunnable(new UserThreadState(currentUser))); // super.doGet(req, resp); resp.getWriter().write("success"); resp.getWriter().flush(); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } } class UserRunnable implements Runnable { UserThreadState userThreadState; public UserRunnable( UserThreadState userThreadState) { this.userThreadState = userThreadState; } @Override public void run() { userThreadState.bind(); UserObj user = UserUtils.getCurrentUser(); //輸出子線程的user信息 System.out.println("自線程: " + (user == null ? "user爲null" : user.getName() + "->" + user.hashCode())); userThreadState.restore(); //輸出富顯成的user信息 UserObj currentUser = UserUtils.getCurrentUser(); System.out.println("original: " + currentUser.getName()+"->" + currentUser.hashCode()); } }
說明:服務器端其實就是一個servlet,沒有其餘的東西。tomcat
2.一、tomcat配置:bash
catalina.sh以下:服務器
JAVA_OPTS="-Xmx1024m -Xms1024m -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=172.16.125.140 -Dcom.sun.management.jmxremote.port=8180 -Dcom.sun.management.jmxremote.authenticate=false"
server.xml以下:
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="400" maxQueueSize="80" minSpareThreads="30" maxIdleTime="60000"/> <Connector executor="tomcatThreadPool" port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" URIEncoding="UTF-8" compression="off" enableLookups="false" maxKeepAliveRequests="20" bufferSize="8192" connectionTimeout="5000" redirectPort="8443" maxPostSize="20971520"/>
兩臺tomcat,配置如上,除了端口差異,其餘都是同樣的。
2.二、用nginx作了負載均衡,nginx配置以下:
#user nobody; worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; upstream tomcats { server 127.0.0.1:8080; server 127.0.0.1:7080; } server { listen 80; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { #root html; proxy_pass http://tomcats; index index.html index.htm; } # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }
2.三、操做系統內核參數優化:
文件:/etc/sysctl.conf
vm.overcommit_memory = 1 #net.ipv4.ip_local_port_range = 1024 65536 net.ipv4.tcp_fin_timeout = 1 net.ipv4.tcp_keepalive_time = 1200 net.ipv4.tcp_mem = 94500000 915000000 927000000 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_timestamps = 0 net.ipv4.tcp_synack_retries = 1 net.ipv4.tcp_syn_retries = 1 net.ipv4.tcp_abort_on_overflow = 0 net.core.rmem_max = 16777216 net.core.wmem_max = 16777216 net.core.netdev_max_backlog = 262144 net.core.somaxconn = 262144 net.ipv4.tcp_max_orphans = 3276800 net.ipv4.tcp_max_syn_backlog = 262144 net.core.wmem_default = 8388608 net.core.rmem_default = 8388608 #net.ipv4.netfilter.ip_conntrack_max = 2097152 net.nf_conntrack_max = 655360 net.netfilter.nf_conntrack_tcp_timeout_established = 1200
文件:/etc/security/limits.conf
* soft nofile 655350 * hard nofile 655350
ulimit -n 65535 ulimit -n
三、結論
按照以上配置,併發1萬個線程,100、80、60、50、30、20等等的併發量,有小於10%的失敗率,併發量越小,失敗率越低。
tomcat監控截圖:
不知道,怎麼保證100%的正常,請你們幫助我。
完整代碼地址:代碼