Next Generation Session Management with Spring Session

http://www.infoq.com/articles/Next-Generation-Session-Management-with-Spring-Session

在大规模集群的场景,无状态的应用能减少运维成本、缩短应用恢复时间。spring session主要解决了web应用session持久化的问题。把session存储在应用外部,让应用无状态。

spring session主要提供如下能力:

  1. 把session的存储逻辑抽取出来,可以选择rdis,或者自己实现session存储
  2. 使用websocket也能保持会话
  3. 非web应用也能使用会话
  4. 支持多个session(可以登陆多个用户)
  5. Restful API也能通过http header维护会话

spring session 1.1版本支持

1
HttpSessionListener
.如果使用的redis,spring session通过key过期时的过期事件+redis消息推送来实现。可惜我们用的是
1
codis
,我们实现了
1
DisabledRedisMessageListenerContainer
把这个能力屏蔽了。

更多参考:spring session 官方文档 Add HttpSessionListener Support

The Twelve-Factor App

http://12factor.net

The twelve-factor app is a methodology for building software-as-a-service apps.

  1. Codebase:One codebase tracked in revision control, many deploys

    one codebase per app(all tracked in revision control), but there will be many deploys of the app(one or more staging use same code,use multi config file or configuration management system for different stage ).

  2. Dependencies:Explicitly declare and isolate dependencies

    declares all dependencies via a dependency declaration manifest,and use dependencies check strategy to ensure that no implicit dependencies.

  3. Config:Store config in the environment

    use mutli config file (build env-awared app) or use configuration management system (spring cloud config

  4. Backing Services:Treat backing services as attached resources

    app makes no distinction between local and third party services(both are attached resources). A deploy should be able to swap out a local MySQL database with one managed by a third party (such as Amazon RDS) without any changes to the app’s code.

  5. Build, release, run:Strictly separate build and run stages

    app uses strict separation between the build, release, and run stages. For example, it is impossible to make changes to the code at runtime, since there is no way to propagate those changes back to the build stage(we can put dynamic part code into db ).

  6. Processes:Execute the app as one or more stateless processes

    Twelve-factor processes are stateless and share-nothing. Any data that needs to persist must be stored in a stateful backing service, typically a database.Sticky sessions should never be used or relied upon.

  7. Port binding:Export services via port binding

    The twelve-factor app is completely self-contained and does not rely on runtime injection of a webserver into the execution environment to create a web-facing service. The web app exports HTTP as a service by binding to a port, and listening to requests coming in on that port.

  8. Concurrency:Scale out via the process model

    processes are a first class citizen.Processes in the twelve-factor app take strong cues from the unix process model for running service daemons. Using this model, the developer can architect their app to handle diverse workloads by assigning each type of work to a process type.

  9. Disposability:Maximize robustness with fast startup and graceful shutdown

    Processes should strive to minimize startup time.

    Processes shut down gracefully ( ceasing to listen on the service port,thereby refusing any new requests, allowing any current requests to finish, and then exiting.)when they receive a SIGTERM signal from the process manager.

    For a worker process, graceful shutdown is achieved by returning the current job to the work queue.Implicit in this model is that all jobs are reentrant, which typically is achieved by wrapping the results in a transaction, or making the operation

    1
    idempotent
    
    .

    Processes should also be robust against sudden death, in the case of a failure in the underlying hardware. A recommended approach is use of a robust queueing backend that returns jobs to the queue when clients disconnect or time out. Either way, a twelve-factor app is architected to handle unexpected, non-graceful terminations.

  10. Dev/prod parity:Keep development, staging, and production as similar as possible

    x Traditional app Twelve-factor app
    Time between deploys Weeks Hours
    Code authors vs code deployers Different people Same people
    Dev vs production environments Divergent As similar as possible

    The twelve-factor developer resists the urge to use different backing services between development and production, even when adapters theoretically abstract away any differences in backing services.

  11. Logs:Treat logs as event streams

    A twelve-factor app never concerns itself with routing or storage of its output stream(maybe dont fit for java app).

  12. Admin processes:Run admin/management tasks as one-off processes

    One-off admin processes should be run in an identical environment as the regular long-running processes of the app. They run against a release, using the same codebase and config as any process run against that release. Admin code must ship with application code to avoid synchronization issues.

使用spring loaded提高开发效率

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

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

  1. 下载springloaded
  2. 配置IDEA

    • 打开
      1
      Run/Debug Configuations
      
      ,在
      1
      Defaults
      
      中选择
      1
      Application
      
    • 在右边的

      1
      Configuration
      
      tab中配置
      1
      VM options
      

      1
        -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

本文讲述了

1
Nashorn
的一些使用场景:

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
}

对比了下

1
nashorn
1
groovy
的性能:

1
2
3
4
5
6
7
8
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。

可以使用

1
TransactionSynchronizationManager
来解决这个问题:

1
2
3
4
5
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
       void afterCommit(){
            //submit transaction B to threadpool
       }
});

在事务A中加上此钩子,在

1
afterCommit
方法中向线程池提交事务A任务。具体处理代码
1
AbstractPlatformTransactionManager#triggerAfterCommit
1
ThreadLocal
清理
1
AbstractPlatformTransactionManager#cleanupAfterCompletion
.

Web应用的缓存设计模式

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

robbin大哥讲解了对

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

java反射的性能

先看看下面的数据:

1
2
3
4
5
6
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

上面的数据测试了

1
直接调用java方法
1
通过反射调用java
1
通过ReflectASM调用java
1
withCache
意思是把中间对象缓存起来。 反射确实很慢,但是只要把反射对象缓存起来,性能提升很大,
1
Reflectasm_withCache
1
reflect_withCache
快了3倍多。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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);
	}
}

补充:

1
2
3
4
5
6
7
8
9
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
1
2
3
4
5
6
7
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

1
MethodHandle
太牛叉了。

spring mvc的异步servlet实现

spring异步web处理流程,我们先以

1
Controller
方法返回
1
Callable
对象为例

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

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

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

    1
    OncePerRequestFilter
    

  2. spring内置了几种异步结果处理器,

    1
    CallableMethodReturnValueHandler
    
    1
    AsyncTaskMethodReturnValueHandler
    
    1
    DeferredResultMethodReturnValueHandler
    
    分别支持方法返回
    1
    Callable
    
    ,
    1
    WebAsyncTask
    
    ,
    1
    DeferredResult
    

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