这两天无事,正好学习下。出发点是, 怎样模拟高并发访问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%的正常,请你们帮助我。
完整代码地址:代码