d2js 运行于 servlet 容器,如tomcat,由于容器自身支持并发,似乎 d2js 只要使用 nashorn 运行脚本即可。这样我们得到最简单的实现方式:

在该方式中,nashorn引擎仅存在于Servlet.service调用栈,在调用完成后即释放。Hotspot 将栈上对象(局部变量、牵连的函数调用中的局部变量)也分配在堆里,但是栈上对象存活时间很短,只要新生代空间足够,其永远不会进入年老代,回收策略简单。对于高并发少持久数据的网站系统,加大新生代当是有效做法。

为每个请求创建ScriptEngine的方式缺陷明显:

1.每次都创建一个 ScriptEngine,输出响应后即释放,代价很高

2.每次脚本都要编译(nashorn 确实编译了js),花销很大

以上缺陷导致该方式没有实用价值,仅在开发原型时有意义。但该方式也有它的参考意义:隔离是并发的秘诀,充分的隔离=完美的并发,该方式对每个请求使用独立的ScriptEngine,开发者在 js 中可以尽情使用全局变量而不用担心全局污染,这也使人员要求大为降低。

本轮优化前 d2js 使用的是这个方案的升级版本,采用“每个 d2js 文件一个 ScriptEngine Pool” 的方案,可以方便的使用全局变量,对开发人员要求较低。这个办法资源消耗巨大,行不通。

性能优化的目标是:让所有d2js文件运行于同一引擎实例!!每个d2js文件只加载一次,除非文件发生写入。

同一引擎不存在难度,就是个单例。但总有一些弯路让我们技痒难耐。比如有一个貌似合理的替代方案:ThreadLocal<ScriptEngine>,这个方案假设 ScriptEngine 只适合单线程,考虑到目前 jdbc 都是同步的,在 d2js函数调用期间,不会发生线程切换,采用 ThreadLocal 即使使用全局变量也是安全的——既然引擎是线程安全的,引擎内的变量自然也是线程安全的。但该方式实测性能不及使用同一引擎。

使用同一引擎后,原来通过全局变量表示的 d2js, request, response,session 等都需要重构。

首先是 d2js 对象。d2js 文件中接口是以 d2js.func = function(){} 形式插拔的。使用同一引擎后,d2js 不再是全局变量,要保持该表达方式,目前采用的办法是对每个d2js文件套用一个模板代码,该模板是一个外层闭包,将 d2js 化为该闭包的变量:

function createD2js(){

    var d2js/**/ = new D2JS();

    // ------- 用户代码插入模板-----------------
d2js.func = function(){}   // 将用户代码置入模板内
// --------以上用户代码--------------------- … … return d2js; } 

加载 d2js 文件时套用模板、执行该模板函数并获得返回值。每个 d2js 文件都得到一个 D2JS 实例,所有D2JS实例存放于 ConcurrentHashMap<String filename, Object D2JS Instance> allD2js 。

处理HTTP请求时,根据文件名找到 D2JS 实例,由相应实例提供服务。

d2js 函数中还需要访问 request, response, session 等对象。这些对象是HTTP请求带来的,每个请求都不同,同一 d2js 对象要支持对并发请求提供服务。由于使用的是同一个ScriptEngine,全局变量表达方式显然行不通了。可行的有两种做法:

1. 扩充参数栈,增加一个参数:d2js.func = function(params, http){}

http 是封装了 {request:request, response: response, session: session} 的一个对象,一个复合粒子。

这个方式的好处是,有时 d2js.func 不需要使用 request 等对象,该形参就可以留空,重构代价不大。缺点是,当函数需要更多参数时(如给其它函数调用的私有函数),很难确切知道应该在哪个位置提供该参数。

2.将 request, response 等表达为 this.request, this.response。这个做法更有效,最终选择了这种方式。

怎么才能让同一个 d2js 实例分别对应不同的 request、response 呢?

办法很有趣。我将上述存放在 allD2js 中的 D2JS 实例作为 prototype,每次请求时创建一个基于该 prototype 的新实例为该请求服务,服务完毕后该实例即释放。

该方式流程如下:

在原理上这种一次性的附带上当次调用信息的镜像实例与闭包的偏函数(柯里化)本质相同。该实现方式发挥了 js 语言原型链思想,特别适合按上下文偏分服务的并发情形,和 erlang 将偏分信息全部放置于栈相比,该方式具备基于原型链的面向对象特点,但又延续了栈隔离的特色。未来 nashorn 实现 async/await、 jdbc 实现 async-jdbc 后,该并发表达方式将会获得更强的生命力。

采取该方式后,每次请求仅在栈上生成一个镜像 D2JS 对象,内存消耗少,业务间互相隔绝,除修改D2JS文件导致的并发冲突外,无其它潜在冲突资源,不使用任何锁。实测效率与 JSP 相比约为 JSP 的 50%-90%,对于运行于 JVM 的脚本语言这已经非常不错了。

D2js 是如何处理并发的的更多相关文章

  1. iis如何处理并发请求

    文章:IIS是怎么处理同时到来的多个请求的? 文章:你真的了解:IIS连接数.IIS并发连接数.IIS最大并发工作线程数.应用程序池的队列长度.应用程序池的... 文章:IIS最大工作进程数设置引发串 ...

  2. flask如何处理并发

    1.使用自身服务器的多进程或者多线程,参考werkzeug的run_simple函数的入参.注意,进程和线程不能同时开启 2.使用gunicorn使用多进程,-w worker 进程数,类型于运行多个 ...

  3. Code First开发系列之管理并发和事务

    返回<8天掌握EF的Code First开发>总目录 本篇目录 理解并发 理解积极并发 理解消极并发 使用EF实现积极并发 EF的默认并发 设计处理字段级别的并发应用 实现RowVersi ...

  4. [渣译文] 使用 MVC 5 的 EF6 Code First 入门 系列:为ASP.NET MVC应用程序处理并发

    这是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译,这里是第十篇:为ASP.NET MVC应用程序 ...

  5. Code First开发系列之管理并发和事务(转)

    转自:http://www.cnblogs.com/farb/p/ConcurrencyAndTransctionManagement.html 返回<8天掌握EF的Code First开发&g ...

  6. EntityFramework_MVC4中EF5 新手入门教程之七 ---7.通过 Entity Framework 处理并发

    在以前的两个教程你对关联数据进行了操作.本教程展示如何处理并发性.您将创建工作与各Department实体的 web 页和页,编辑和删除Department实体将处理并发错误.下面的插图显示索引和删除 ...

  7. linux设备驱动第五篇:驱动中的并发与竟态

    综述 在上一篇介绍了linux驱动的调试方法,这一篇介绍一下在驱动编程中会遇到的并发和竟态以及如何处理并发和竞争. 首先什么是并发与竟态呢?并发(concurrency)指的是多个执行单元同时.并行被 ...

  8. Contoso 大学 - 7 – 处理并发

    原文 Contoso 大学 - 7 – 处理并发 By Tom Dykstra, Tom Dykstra is a Senior Programming Writer on Microsoft's W ...

  9. [翻译][MVC 5 + EF 6] 10:处理并发

    原文:Handling Concurrency with the Entity Framework 6 in an ASP.NET MVC 5 Application 1.并发冲突: 当一个用户编辑一 ...

随机推荐

  1. [LeetCode] Odd Even Linked List 奇偶链表

    Given a singly linked list, group all odd nodes together followed by the even nodes. Please note her ...

  2. [LeetCode] Reverse Linked List 倒置链表

    Reverse a singly linked list. click to show more hints. Hint: A linked list can be reversed either i ...

  3. 踢出非法Linux用户

    非法添加用户及非法进去的远程操作用户! 01.非法用户闯入系统 最简单的办法就是用 w 命令来检查. 如果确认有非法用户出现在系统内,可以立即 kill 用户相关进程. kill  -9  `lsof ...

  4. java之并发编程线程池的学习

    如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. java.uitl.concurrent.Thre ...

  5. python学习之路 第六天

    1.正则表达式 re.match() 从头匹配: re.match("[0-9]","123abc789") 只匹配一个数字: re.match("[ ...

  6. IO流-----写到输出流

    输出流:---链接:http://blog.csdn.net/du_minchao/article/details/49045421 /** * 方法名:writeStream * 方法描述:写到输出 ...

  7. React数据流和组件间的沟通总结

    今天来给大家总结下React的单向数据流与组件间的沟通. 首先,我认为使用React的最大好处在于:功能组件化,遵守前端可维护的原则. 先介绍单向数据流吧. React单向数据流: React是单向数 ...

  8. java中IO流小解

    下面这张图列出了java中一些处理流: java中根据操作对象的不同可以分为:字节流和字符流. 首先我们先表示一下什么叫节点流和处理流: 节点流:可以从或向一个特定的地方(节点)读写数据.如FileR ...

  9. neo4j-java连接

    本例使用neo4j3.0.1架包 使用maven下载架包 <!-- https://mvnrepository.com/artifact/org.neo4j/neo4j-jdbc-driver ...

  10. Android——Adapter

    Adapter——本身只是一个接口.Adapter是将数据绑定到UI界面上的桥接类.Adapter负责创建显示每个项目的子View和提供对下层数据的访问. 数据适配器作用:把复杂的数据(数组.链表.数 ...