2015年04月Reading Notes

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每次都会都会新建线程来处理任务,生产环境建议单独配置线程池。

给攻城狮一个小小的鼓励!