基于递归实现,有两个函数相互递归调用的情况。 在c中这种情况可以用栈结构和指针进行递归消除,但是java中没有指针(或者说指针的功能是阉割版的) 所以目前这一步优化只能用java的引用机制来做,获得的效果并不理想,因此暂时弃用。 见 filter.java
最开始的时候server服务有三种选择:com.sun.httpserver、jetty、tomcat
sun是jdk自带的httpserver包,我们可以构建自己的嵌入式Http Server,它支持Http和Https协议, 提供了HTTP1.1的部分实现,没有被实现的那部分可以通过扩展已有的Http Server API来实现,程序员必须自己实现HttpHandler接口,HttpServer会调用HttpHandler实现类的回调方法来处理客户端请求, 在这里,我们把一个Http请求和它的响应称为一个交换,包装成HttpExchange类,HttpServer负责将HttpExchange传给HttpHandler 实现类的回调方法.
public class httpWork {
public static void main(String[] args)throws IOException
{
//创建server
HttpServer server = HttpServer.create(new InetSocketAddress(8000),0);
server.createContext("/", new testHandler());
//监听
server.setExecutor(null);
server.start();
}
public static String getMessageBody(HttpExchange t)throws IOException
{
InputStream is = t.getRequestBody();
StringBuilder sb = new StringBuilder(1024);
byte[] bytes = new byte[1024];
int length;
while ((length = is.read(bytes)) != -1) {
String str = new String(bytes, 0, length);
sb.append(str);
} // .. read the request body
return sb.toString();
}
}
class testHandler implements HttpHandler {
public void handle(HttpExchange t) throws IOException{
String sb=httpWork.getMessageBody(t)+"test done.";
t.sendResponseHeaders(200, sb.length());
OutputStream os = t.getResponseBody();
os.write(t.getRequestMethod().getBytes());
os.write(sb.getBytes());
os.close();
}
}
jetty是目前google在采用的servlet引擎,它的架构比较简单,也是一个可扩展性和非常灵活的应用服务器,它有一个基本数据模型,这个数据模型就是 Handler,所有可以被扩展的组件都可以作为一个 Handler,添加到 Server 中,Jetty 就是帮你管理这些 Handler。
Tomcat是由Apache软件基金会下属的Jakarta项目开发的一个Servlet容器,按照Sun Microsystems提供的技术规范,实现了对Servlet和JavaServer Page(JSP)的支持,并提供了作为Web服务器的一些特有功能,如Tomcat管理和控制平台、安全域管理和Tomcat阀等。由于Tomcat本身也内含了一个HTTP服务器,它也可以被视作一个单独的Web服务器。
优化的方案都是一致的,用线程池减少连接的开销。由于三个库的线程池的封装性都比较强,所以 用到的方法比较单一,就是调整线程池的初始线程数、最大线程数、空闲线程数量区间、最高请求持续时间这些参数。 线程数并不是越大越好,我们当前的场景是处理较高并发量,较短连接时长的请求,因此选择初始线程数和最大线程 数相近的参数获得较好效果。空闲线程数量的下线值过低会导致高并发时的大量错误,上线值过高会导致线程效率低 下。另外三个库的最佳设置线程数并不一样,这和各自线程池的context switch实现逻辑有关。 具体操作
将以下代码替换server.setExecutor(null);
//thread pool
ThreadPoolExecutor thp = (ThreadPoolExecutor) Executors.newCachedThreadPool();
thp.setMaximumPoolSize(1000);
thp.setCorePoolSize(1000);
thp.prestartAllCoreThreads();
server.setExecutor(thp);
#####2)jetty
用以下方式生成Server
QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.setMaxThreads(1024);
Server server = new Server(threadPool);
修改tomcat配置文件,$tomcat_home/conf/server.xml 修改<Connector ... />
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" connectionTimeout="300000"
executor="tomcatThreadPool"
minSpareThreads="256"
maxThreads="512"
redirectPort="8443" />
同样的我们利用redis连接池可以减少redis连接开销。但是redis连接池留给我们的配置余地则是比较大的。 但是根据我们的业务逻辑,一个http请求会产生多次redis操作,但是一个请求期间redis操作的时间比例不 是很高。因此有两个设计方案
- 一个请求分配一个redis连接,二者的生命周期基本同步
- 一个请求在它需要redis操作的时候分配redis连接,用完就放回连接池
在本例中方案1的测试性能略高于2,这是由于本次场景redis操作数量较为频繁的缘故。
redis连接池代码 RedisManager.java
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Jedis;
/**
* Created by dong on 15-8-26.
*/
public class redisManager {
private static final redisManager instance = new redisManager();
private static JedisPoolConfig redisConfig;
private static JedisPool redisPool;
private redisManager() {}
public final static redisManager getInstance() {
return instance;
}
public void connect() {
//redis config
redisConfig = new JedisPoolConfig();
//连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
//config.setBlockWhenExhausted(true);
//设置的逐出策略类名, 默认DefaultEvictionPolicy(当连接超过最大空闲时间,或连接数超过最大空闲连接数)
//config.setEvictionPolicyClassName( "org.apache.commons.pool2.impl.DefaultEvictionPolicy" );
//是否启用pool的jmx管理功能, 默认true
//config.setJmxEnabled( true );
//MBean ObjectName = new ObjectName("org.apache.commons.pool2:type=GenericObjectPool,name=" + "pool" + i); 默 认为"pool", JMX不熟,具体不知道是干啥的...默认就好.
//config.setJmxNamePrefix( "pool" );
//是否启用后进先出, 默认true
//config.setLifo( true );
//最大空闲连接数, 默认8个
redisConfig.setMaxIdle(50);
//最小空闲连接数, 默认0
redisConfig.setMinIdle(5);
//最大连接数, 默认8个
redisConfig.setMaxTotal(512);
//获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1
redisConfig.setMaxWaitMillis(3000);
//在获取连接的时候检查有效性, 默认false
redisConfig.setTestOnBorrow(true);
//返回一个jedis实例给连接池时,是否检查连接可用性(ping())
redisConfig.setTestOnReturn( true );
//在空闲时检查有效性, 默认false
redisConfig.setTestWhileIdle(true);
//逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
redisConfig.setMinEvictableIdleTimeMillis(1000L * 60L * 1L);
//对象空闲多久后逐出, 当空闲时间>该值 ,且 空闲连接>最大空闲数 时直接逐出,不再根据MinEvictableIdleTimeMillis判断 (默认逐出策略),默认30m
redisConfig.setSoftMinEvictableIdleTimeMillis(1000L * 60L * 1L);
//逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
redisConfig.setTimeBetweenEvictionRunsMillis(60000); //1m
//每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
redisConfig.setNumTestsPerEvictionRun(10);
redisPool = new JedisPool(redisConfig,"127.0.0.1",6379);
}
public void release() {
redisPool.destroy();
}
public Jedis getJedis() {
return redisPool.getResource();
}
public void returnJedis(Jedis jedis) {
redisPool.returnResourceObject(jedis);
}
}
redis连接池的初始化应当与server初始化同步,具体操作如下:
编写tomcatContainerStartup.java
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class tomcatContainerStartup implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
// Do your thing during webapp's startup.
//create and configure a RedisPool.
redisManager.getInstance().connect();
}
public void contextDestroyed(ServletContextEvent event) {
// Do your thing during webapp's shutdown.
//destroy a RedisPool.
redisManager.getInstance().release();
}
}
在$tomcat_home/webapps/YOUR_APP_ROOT/WEB-INF/web.xml的 之间添加
<listener>
<listener-class>tomcatContainerStartup</listener-class>
</listener>
在server的启动部分加入
redisManager.getInstance().connect();
即可
当以上优化方案做完之后,在siege的基准测试下,tomcat表现最优,1000并发、重复1次的场景下最快完成速度1.67秒。