批量处理是很多公司在开发Mobile或者Web 服务后端时都会想要拥有的一种服务,典型应用场景为数据的批量导入import,以及批量的更新update
这里希望基于以往的实践经验给大家提供参考。
情景1) 批量处理同质化的请求
可以将这种功能内置到某个REST controller 的内部,将的参数封装在一个数组里面,传来
controller method ,这样HTTP body 类似与
{
"parameters": [
{
"size": 12
.......
},
{
"size": 12
.......
},
.............
]
}
情景二) 批量处理异质化的请求,且请求之间没有相互依赖关系
这种就没有办法放在某个已有的 controller 里面了,我们要有真正的 BatchController了,注意我们的request body 要能够 wrap 各个请求的参数,而且我们的 response body也要能够 wrap 各个请求的响应。处理的时候需要遍历每个子请求,使用每个URL对应的dispatcher (参考
RestLifecycleListener) 派送子请求并保存对应的响应。
protected BatchResult[] performBatchOperations( BatchOperation[] operations, String basePath, HttpServletRequest request )
{
for ( BatchOperation operation: operations )
{
BatchRestRequest batchRequest = new BatchRestRequest( request, operation, url );
String uri = batchRequest.getRequestURI();
dispatcher = getRequestDispatcher( request, url );
if ( dispatcher != null )
{
try
{
BatchRestResponse responseWrapper = new BatchRestResponse();
try
{
dispatcher.forward( batchRequest, responseWrapper );
}
rawBody = wrapper.getRawBody();
status = wrapper.getStatus();
headers = wrapper.getHeaders();
}
}
BatchResult res = new BatchResult( operation.getMethod(), operation.getRelativeUrl(), url, status, rawBody, exception, headers );
result[ i++ ] = res;
................
}
并发优化后的版本为:
注意第一个参数从 BatchOperation数组 变为处理单个请求,方便单核并发或多核并行处理。
protected BatchResult performBatchOperation( BatchOperation operation, String basePath, HttpServletRequest request ) {}
保留结果顺序,与请求一一对应。
class BatchResultGenerator implements Callable
{
private int _order;
private BatchOperation _batchOperation;
private String _basePath;
private HttpServletRequest _request;
public BatchResultGenerator( BatchOperation batchOperation, String basePath, HttpServletRequest request )
{
_batchOperation = batchOperation;
_basePath = basePath;
_request = request;
}
public BatchResultGenerator withIndex( int order )
{
_order = order;
return this;
}
@Override
public BatchResultWrapper call() throws Exception
{
return new BatchResultWrapper().setOrder( _order ).setResult( performBatchOperation( _batchOperation, _basePath,
_request ) );
}
}
protected BatchResult[] performBatchOperations( BatchOperation[] operations, String basePath, HttpServletRequest request )
{
BatchResult[] result = new BatchResult[ operations.length ];
CompletionService service = new ExecutorCompletionService<>( Executors.newCachedThreadPool() );
for ( int count = 0; count < operations.length; count++ )
{
service.submit( new BatchResultGenerator( operations[ count ], basePath, request ).withIndex( count ) );
}
for ( int count = 0; count < operations.length; count++ )
BatchResultWrapper wrapper = service.take().get();
result[ wrapper.getOrder() ] = wrapper.getResult();
}
return result;
}
情景三) 批量处理异质化的请求,且请求之间有相互依赖关系,即后面的request需要依赖前来某个请求的response提供参数
这也是最复杂的一种,不过也有两种解决方案:
方案1,基于情景二dispatch 模型,但是需要为每个BatchRequest指定一个ID方便被引用, 对象中存入所依赖的BatchRequest的ID,然后拓扑排序拿到对应的拓扑结构,由此决定串行线和并行线。分别发出请求,处理请求,再发请求等。优点是只调用相关服务并处理请求的派送与结果的组装,缺点是效率相对直接代码调用要低。
方案2,不基于dispatch,同样的,
需要为每个BatchRequest指定一个ID方便被引用, 对象中存入所依赖的BatchRequest的ID,服务器端拓扑排序拿到对应的拓扑结构,由此决定串行线和并行线,直接调用代码而不是内部转发HTTP请求,优点是效率高,缺点是需要直接touch具体处理请求的模块之代码,Controller方法要被直接调用,增加了代码的耦合度。
情景四)
批量处理异质化的请求,且请求之间有相互依赖关系,同时具有事务性,即后面的request需要依赖前来某个请求的response提供参数,有一个依赖不满足,整个孤岛请求就不成功,部分处理成功的请求还需要回滚。
基于情景三,是事务性的问题,每种请求都要定义回滚的请求,比如 POST 创建 =》 DELETE 删除 , POST 更新 =》POST ROLL Back, DELETE 删除 =》 POST
ROLL Back
后面两需要来自数据库的支持。
情景五) 批量处理请求,并返回各个任务进度
由于REST并不支持长连接,客户端需要请求相应的进度API来得到最新的进度,服务器端的进度数据可以存到数据库里面以供查询。
阅读(824) | 评论(0) | 转发(0) |