使用spring loaded提高开发效率

spring-loadedjrebel类似,它能发现您修改的类并重新加载。在开发测试时,您只需要使用它启动后,就可以一直写代码了。jrebel是收费的软件,spring-loaded免费,而且他很了解您代码中用到的spring特性,能很智能的帮忙重新加载类,并把bean注册到spring容器中。

使用很简单,参考下面的步骤:

  1. 下载springloaded
  2. 配置IDEA

    • 打开Run/Debug Configuations,在Defaults中选择Application
    • 在右边的Configuration tab中配置VM options

        -javaagent:/Users/bohr/software/springloaded/springloaded-1.2.3.RELEASE.jar -noverify
      

    有了此默认配置,一劳永逸。上面的路径地址修改为您保存springloaded的路径

  3. 执行任何java类
  4. 修改后,编译此java类就能看到修改后的效果了。

Enterprise Nashorn

https://community.oracle.com/docs/DOC-910779

本文讲述了Nashorn的一些使用场景:

  1. 由外部提供计算逻辑,服务端执行计算逻辑返回结果
  2. 用java来定义接口,js来写逻辑,方便动态更新计算逻辑。(比如短信中的路由策略,需要经常更新)
  3. 写shell脚本(访问数据库,访问网络,监控…)

Nashorn的性能比前任强多了,但是和java比较还是有差距,主要的性能开销在js->java上。在关注性能的场景,可以在业务使用时,采用方法2,并缓存proxy对象。

private static String isPrime = " function test(num) {\n" + "if (num % 2 == 0)" + "return false;" + "for (var i = 3; i * i <= num; i += 2)"
									+ "if (num % i == 0)" + "return false;" + "return true;" + "}";
private Supplier<Predicate<Long>> supplier = Suppliers.memoize(() -> getFilter());

private Predicate getFilter() {
	Invocable invocable = (Invocable) this.engine;
	try {
		this.engine.eval(isPrime);
		return invocable.getInterface(Predicate.class);
	} catch (Exception ex) {
		throw Throwables.propagate(ex);
	}
}
@Test
public void testJavaScriptWithMemoize() throws Exception {
	supplier.get().test(172673l);
}

对比了下nashorngroovy的性能:

NashornPerfTest.testJavaScript: [measured 50000 out of 51000 rounds, threads: 4 (all cores)]
 	round: 0.00 [+- 0.00], round.block: 0.00 [+- 0.00], round.gc: 0.00 [+- 0.00], GC.calls: 96, GC.time: 0.25, time.total: 34.20, time.warmup: 3.44, time.bench: 30.76
NashornPerfTest.testJava: [measured 50000 out of 51000 rounds, threads: 4 (all cores)]
 	round: 0.00 [+- 0.00], round.block: 0.00 [+- 0.00], round.gc: 0.00 [+- 0.00], GC.calls: 0, GC.time: 0.00, time.total: 0.28, time.warmup: 0.01, time.bench: 0.27
NashornPerfTest.testJavaScriptWithMemoize: [measured 50000 out of 51000 rounds, threads: 4 (all cores)]
 	round: 0.00 [+- 0.00], round.block: 0.00 [+- 0.00], round.gc: 0.00 [+- 0.00], GC.calls: 0, GC.time: 0.00, time.total: 0.35, time.warmup: 0.06, time.bench: 0.29
GroovyTest.testGroovyWithMemoize: [measured 50000 out of 51000 rounds, threads: 4 (all cores)]
 	round: 0.00 [+- 0.00], round.block: 0.00 [+- 0.00], round.gc: 0.00 [+- 0.00], GC.calls: 5, GC.time: 0.07, time.total: 3.86, time.warmup: 1.75, time.bench: 2.11

spring 多线程中两个事务执行顺序控制

有些场景需要控制两个事务的执行顺序,比如事务A执行完后,需要事务B中执行费时操作。因为是费时操作,一般会把事务B放到独立的线程中执行。然而事务A和事务B在不同的线程中,事务B还会依赖事务A提交的数据。如果在事务A中新启动线程执行事务B,有可能事务A还没有提交,就开始执行事务B了。这种场景需要保证事务A提交后,才能在新线程中执行事务B。

可以使用TransactionSynchronizationManager来解决这个问题:

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
       void afterCommit(){
            //submit transaction B to threadpool
       }
});

在事务A中加上此钩子,在afterCommit方法中向线程池提交事务A任务。具体处理代码AbstractPlatformTransactionManager#triggerAfterCommitThreadLocal清理AbstractPlatformTransactionManager#cleanupAfterCompletion.

Web应用的缓存设计模式

http://robbinfan.com/blog/38/orm-cache-sumup

robbin大哥讲解了对ORM缓存的理解.我司也有不少项目用了ORM,大多数人没有使用缓存的意识,有这样意识的同学提到过用ehcache,这在单节点的情况下,工作得很好,但是到了线上多机部署,数据不一致的问题就会出现,至少也要选用分布式缓存系统来实现哈。还有一点,因为有了缓存,数据订正这种事情就要谨慎了。

java反射的性能

先看看下面的数据:

Benchmark                                  Mode  Cnt     Score    Error  Units
 	testInvokeMethod_Direct                avgt   20     0.587 ±  0.036  ns/op
 	testInvokeMethod_Reflectasm            avgt   20    39.940 ±  1.957  ns/op
 	testInvokeMethod_Reflectasm_withCache  avgt   20     9.784 ±  0.745  ns/op
 	testInvokeMethod_reflect               avgt   20  1513.409 ± 85.396  ns/op
 	testInvokeMethod_reflect_withCache     avgt   20    29.444 ±  1.863  ns/op

上面的数据测试了直接调用java方法通过反射调用java通过ReflectASM调用javawithCache意思是把中间对象缓存起来。 反射确实很慢,但是只要把反射对象缓存起来,性能提升很大,Reflectasm_withCachereflect_withCache快了3倍多。

Reflectasm的原理是生成java源代码来实现反射的调用,下面就是生成的源代码。

package reflectasm;
import java.util.Map;
import com.esotericsoftware.reflectasm.MethodAccess;
public class PojoMethodAccess extends MethodAccess {
	public Object invoke(Object paramObject, int paramInt, Object[] paramArrayOfObject) {
		Pojo localPojo = (Pojo) paramObject;
		switch (paramInt) {
			case 0:
				return Boolean.valueOf(localPojo.equals((Object) paramArrayOfObject[0]));
			case 1:
				return localPojo.toString();
			case 2:
				return Integer.valueOf(localPojo.hashCode());
			case 3:
				return localPojo.getName();
			case 4:
				localPojo.setName((String) paramArrayOfObject[0]);
				return null;
		}
		throw new IllegalArgumentException("Method not found: " + paramInt);
	}
}

补充:

ROUND 1:
Benchmark                           Mode  Cnt   Score   Error  Units
 	MHOpto.mh_invoke                    avgt   15  11.332 ± 0.577  ns/op
 	MHOpto.mh_invokeExact               avgt   15  10.605 ± 0.667  ns/op
 	MHOpto.mh_invokeExact_static_fianl  avgt   15   3.797 ± 0.201  ns/op
 	MHOpto.plain                        avgt   15   4.093 ± 0.156  ns/op
 	MHOpto.reflect                      avgt   15  11.599 ± 0.646  ns/op
 	MHOpto.unreflect_invoke             avgt   15  11.147 ± 0.743  ns/op
 	MHOpto.unreflect_invokeExact        avgt   15  11.392 ± 0.518  ns/op
ROUND 2: Benchmark Mode Cnt Score Error Units
MHOpto.mh_invoke                    avgt   15  11.799 ± 0.847  ns/op
MHOpto.mh_invokeExact               avgt   15  11.830 ± 0.637  ns/op
MHOpto.mh_invokeExact_static_fianl  avgt   15   4.415 ± 0.191  ns/op
MHOpto.plain                        avgt   15   4.084 ± 0.300  ns/op
MHOpto.reflect                      avgt   15  12.191 ± 0.637  ns/op
MHOpto.unreflect_invoke             avgt   15  11.535 ± 0.816  ns/op
MHOpto.unreflect_invokeExact        avgt   15  11.828 ± 0.666  ns/op

MethodHandle太牛叉了。

spring mvc的异步servlet实现

spring异步web处理流程,我们先以Controller方法返回Callable对象为例

  1. http线程处理请求到Controller方法,返回Callable结果
  2. spring选用CallableMethodReturnValueHandler来处理Callable结果,提交Callable到线程池,当前http线程返回,参考WebAsyncManager.startCallableProcessing()
  3. 线程池中线程执行Callable任务,并且dispatch请求到容器,参考WebAsyncManager.setConcurrentResultAndDispatch()
  4. 容器选取http线程继续处理请求

通过分析源代码,下面几点需要关注下:

  1. dispatch请求后,又会执行filterchain,我们需要保证filter只执行一次,filter最好继承OncePerRequestFilter

  2. spring内置了几种异步结果处理器,CallableMethodReturnValueHandlerAsyncTaskMethodReturnValueHandlerDeferredResultMethodReturnValueHandler分别支持方法返回Callable,WebAsyncTask,DeferredResult

  3. org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#taskExecutor此默认线程池为SimpleAsyncTaskExecutor,此taskExecutor每次都会都会新建线程来处理任务,生产环境建议单独配置线程池。

The Asset Pipeline

http://guides.rubyonrails.org/asset_pipeline.html

Asset Pipeline对网站的静态资源进行预处理(合并、简化、压缩、预处理coffeescirpt sass等)。对于静态资源的处理,这里面提到的Fingerprinting来优化http 缓存可以借鉴下。

Fingerprinting技术是在文件名中加上文件内容的标识,当文件内容改变时,文件名也改变。比如文件global.css加入md5的指纹后,文件名为global-908e25f4bf641868d8683022a5b62f54.css.

以前我们经常用query string中来标识版本,比如main.js?1.4/main.js?v=1.4.这种方式在某些CDN中有问题(有些CDN只识别文件名,新的版本文件会替换原版本的文件,在部署这个时间窗口会导致页面混乱)。

在使用浏览器缓存时,一般涉及到http header包括下面两种方案:

  1. ExpiresCache-Control: max-age (没有过期之前,完全不发送请求)
  2. Last-ModifedETag (内容协商,需要发一个请求,如果内容没有变化,响应304)

当方案2和Fingerprinting结合起来时,就比较完美了。对于现在很多开放CDN来讲,基本上都会用方案1,这是开源库名字里面的版本号就起着Fingerprinting的作用。也有用方案2的,估计是出于统计分析的目的。

对于静态资源的缓存,理想的组合是:

  1. 配置很长的本地缓存时间(善用ExpiresCache-Control: max-age),比如1年
  2. 通过Fingerprinting控制缓存(静态资源文件改变,对应的html的资源引用url也改变)