Manik,您能否首先跟大家讲一下在您所接触或者了解的客户中,大部分人都是怎样运用JBoss Cache的?缓存能带来哪些优点?尤其是在高度的可用性方面,缓存带来了怎样的进步?
从持续存储、尤其是数据库中读取数据需要付出昂贵的代价。而且,数据库在伸缩性方面也是臭名昭著(或者不便宜),当你想要扩展前端或增加更多客户端时,这个弊端显然就成了障碍。另一方面,CPU和内存的价格越来越便宜,这意味着更多的人可以负担得起架设高可用系统所需的成本。“本站正在维护中”的暂停服务方式都应当成为历史。
像JBoss Cache这样的分布式缓存扮演的是一个处于应用服务前端和数据库间的中间层的角色,提供对持久性数据状态在内存中的快速访问。JBoss Cache能够确保缓存中的数据状态和数据库中的状态一致、及时更新数据状态、并且保证JVM不会出现堆溢出问题。
JBoss Cache和其它一些开源项目,例如Hibernate和JBoss Seam等的集成情况怎样?
一些开源项目确实用到了JBoss Cache。Hibernate(以及JBoss Application Server的EJB3实现)使用JBoss Cache来存储从数据库后端读取的实体数据,这样一来在调用实体时就不需要每次都连接到数据库去查找。我这样说纯粹只是一个简单的概括,Hibernate运用分布式缓存的实际操作其实更复杂。
Seam也通过分布式缓存来缓存生成JSF页面元素,从而改善那些页面或者页面元素生成速度比较缓慢的站点的伸缩性。
另外还有一些开源项目,如Lucene、Hibernate Search、GridGain、JBoss应用服务器的HTTP Session集群和集群的单点登录(Single Sign-On)代码等都用到了JBoss Cache。
JBoss Cache提供两种缓存方式:核心缓存和POJO缓存。您是否能给我们概括一下这两者主要区别在哪里?
核心缓存会直接把您传递给它的数据存储在一个树型结构中。键/值对被存储在树的节点上,出于复制或持续性的需要它们都被序列化了。
POJO 缓存则采用比较复杂的机制——利用字节码编织来内省(introspecting)用户类,并向用户类的域添加侦听器,一旦域值有任何变化,侦听器会立刻通知缓存。例如,如果要在POJO缓存中存储一个庞大、复杂的对象,会导致POJO缓存内省对象的字节码,最终只把该对象的原始域存储到树结构中。一旦域值有所变化,缓存只复制这个改变了的域值而不会去复制整个用户类,这是高效的细粒度复制。
当然还有一些其它的不同之处,但最主要的区别还是我刚才讲的。
细粒度复制定然会导致POJO缓存和核心缓存间在性能方面巨大的差异。您有没有对两者之间差异做过评估呢?
这类评估很大程度上决定于系统配置,如果只是做一般评估没多大意义。在缓存面对庞大、复杂的对象的时候,细粒度复制确实有助于提高性能。但如果只是用它来存储一些String的话,细粒度复制就没有什么特别价值。类似地,对简单的用户对象运用POJO缓存——比方说一个只拥有两个String域的Person类,与其说对性能有什么帮助,倒不如说它是浪费开销。
这就是为什么我一直建议大家依赖于用例编写基准测试来做比较。我们开发了一个框架在不同缓存和不同配置情况下进行基准测试——开发这个框架主要还是为了方便我们内部比较不同版本的JBoss Cache之间的差异——但我们也提供该框架的下载,大家可以对它进行扩展,使用自定义的对象类型和访问模式来重新编写自定义测试。
你们如何管理引用完整性(referential integrity),尤其是POJO缓存?
如果你指的是对象的引用,那你刚好点到了之所以引进字节码编织的原因。我们针对POJO添加了拦截器并在缓存内容中插入了引用域。
对于用户来说,为什么要选择本地缓存,而不用HashMap呢?
很多人认为Map是考虑缓存的出发点(实际上,JSR-107 JCACHE专家组曾经在Map的基础上扩展实现javax.cache.Cache)。尽管Map非常适合用来存储简单的键/值对,在缓存必需的其它特性上,它就难免有点黔驴技穷,比如内存管理(eviction)、钝化(passivation)和持续性、细粒度锁定模型(首先,HashMap根本不是线程安全的;而ConcurrentHashMap采用的锁是粗粒度级的,它甚至不允许非阻塞用户或多用户从map中读取数据)等。而对于“合格的”缓存来说,它还需要具备一些“企业”特性,包括JTA兼容、附加侦听器等功能。
Map虽然是个好的起点,但如果需要实现或者管理我刚才提到的那些特性的话,选择缓存还是要比Map来得更合适一些。
分布式缓存中采用哪种锁定机制?和传统数据库中采用的是同一种机制吗?
JBoss Cache采用传统的悲观锁(pessimistic locking)的方式,树结构中的每个节点对应一个锁。这些锁的隔离级别和数据库实施的隔离级别相同,允许多用户同时读取数据。
我们也提供乐观锁定(optimistically lock)方式,这个方式则牵涉到数据版本、每个事务的副本维护、主要树结构提交的事务副本确认等等。在乐观锁定方式下,需要承载大量的数据读取请求的系统因此可以获得高度并发性。那些请求读取数据的用户不会因为并发数据库写入操作而受到阻塞。而且,乐观锁定方式还可以避免悲观锁定中有可能发生的死锁。
我们携带多版本并发控制(Multi Versioned Concurrency Control--MVCC)功能的JBoss Cache 3.0.0正在发布阶段,当前的开发任务非常重。大部分数据库系统都用到了多版本并发控制这种锁定方式,它为我们提供了最好的乐观锁定和悲观锁。由于我们的实现不会阻碍任何用户读取数据,因此在数据访问速度上较之前者也胜出百倍。在MVCC功能相对稳定之后,我们希望能把它设置为JBoss Cache默认的锁定机制。
您能否谈一下JGroups集成?
JBoss Cache用JGroups作为组通信类库,用来侦测组成员和组建集群。我们也把JGroups作为一个信道,在其上我们实现了一个RPC机制与组中其它缓存进行通讯。由于JGoups的应用,JBoss Cache获得了高度灵活性,并在网络协议和调整方面也极具扩展性。JBoss Cache因此还使得缓存能够摆脱LAN集群的框框,能够穿透防火墙的限制并组建WAN集群等。
可以脱离JBoss AS单独使用缓存吗?
当然可以!很多人都误认为JBoss Cache一定得在JBoss App Server下才能使用,其实不然。JBoss Cache可以在独立的Java程序中使用,也可以在GUI前端使用,还能在其它一些应用服务器中使用。我们只是把它捆绑在JBoss App Server中发布而已。
失败转移的关键是把数据复制到多个节点,在实际开发中有很多策略可供选择来复制数据。JBoss Cache支持的是哪种复制模式呢?
目前,我们支持两种方式——全局复制(total replication——TR)和buddy复制(buddy replication——BR)。全局复制将状态复制给小组中的所有成员。这种方式能够帮助成员间共享数据状态,保证在失败转移时可以转移到小组中的任何一个成员,但它限制了系统的伸缩性。Buddy复制则挑选特定成员担当备份数据的责任,数据状态相应地只会复制到这些特定节点上。也就是说直接转移到复制节点的失败转移效率非常高,但即使转移到任何非复制节点,失败转移也同样都顺利进行,因为数据状态会根据请求转移到相应的节点。BR最好用于 session密切相关(session affinity)的情况下,因为数据状态的代价可能很高,所以应该尽量仅仅在发生失败转移的时候调用它。
某些特定的构架中,点对点的节点复制方式会影响到系统的伸缩性。JBoss Cache中有类似的问题存在吗?
没有。P2P网络和小组通讯在使用LAN和IP多播的时候效率非常高,伸缩性很强。大多数现代网络设施都支持IP多播。但P2P数据复制中,由于每个节点都拥有整个系统的数据状态,系统伸缩性因此受到影响。我下面会对全局复制稍加评论。基于前面提到的原因,我们建议用户使用与session密切相关的buddy复制。
我们还在开发分区功能,这个功能能够帮助我们在保证伸缩性的前提下真正地把数据状态发送到各个数据组,而且不需要与session密切相关(session affinity)。希望这个功能的推出能够取代全局复制和buddy复制。
在缓存和集群方面,您对近期的发展状况有怎样的期望?JBoss Cache将会如何去满足新的用户需求?
随着硬件越来越便宜、CPU厂商在每块芯片上放置越来越多的内核,分布式缓存将会越来越重要。这无疑意味着需要更多的“虚拟”机,意味着数据库需要“竭尽全力”去管理高度的并发性,也意味着分布式缓存将会成为数据瓶颈(data bottleneck)最重要的解决方式之一。逐渐流行的数据网格和云计算也同样会推动分布式缓存的发展,无论是“云”还是网格的数据节点都需要访问和共享数据。
分区和MVCC功能也将助JBoss Cache一臂之力,能够把集群伸缩性提高一个数量级。
【编辑推荐】