这两天无事,正好学习下。出发点是, 怎样模拟高并发访问restful api。步骤如下:
1、客户端
客户端模拟多线程访问,有两种方式:
1.1、利用synchronized、object的wait和notifyAll方法,代码如下:
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.2、利用jdk concurrent包下的api,代码如下:
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);
}
}
说明:
Executors.newCachedThreadPool() 这个方法,如果线程数太多,会造成机器之前重启。我的是mac电脑,5000个线程直接导致重启了。
2、服务器端
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,没有其他的东西。
2.1、tomcat配置:
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.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.3、操作系统内核参数优化:
文件:/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
3、结论
按照以上配置,并发1万个线程,100、80、60、50、30、20等等的并发量,有小于10%的失败率,并发量越小,失败率越低。
tomcat监控截图:
不知道,怎么保证100%的正常,请大家帮助我。
完整代码地址:代码