《大规模Java平台虚拟化与调优》——2.2 SQLFire特性

本文涉及的产品
数据管理 DMS,安全协同 3个实例 3个月
推荐场景:
学生管理系统数据库
简介:

本节书摘来自华章计算机《大规模Java平台虚拟化与调优》一书中的第2章,第2.2节,作者:(美)Emad BenjaminLiang) 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

2.2 SQLFire特性

本节将会介绍SQLFire的关键特性,这些特性使其成为一个面向内存同时支持磁盘持久化的数据管理系统。SQLFire的特性如下:
服务器分组(server group):这能够让你对SQLFire成员(JVM)进行逻辑分组,使其具有更好的可扩展性权重(也就是在SQLFire数据fabric的特定分区上部署更多的计算资源)。服务器分组指明了为某个表保存数据的SQLFire成员。你可以使用服务器分组来对SQLFire的数据存储进行逻辑分组,以管理表中的数据。存放数据的任意数量的SQLFire成员可以分到一个或更多的服务器分组。当启动SQLFire数据存储时,你需要指明已命名的服务器分组。
在本书中,术语data fabric和data cluster是可以替换使用的。
分区(partitioning):这项功能所描述的是在分布式系统中,将特定表中的数据分割为更小的可管理的数据块(实际上就是跨多JVM分割数据的一种机制)。传统的关系型数据库管理员(DBA)可能会熟悉这种机制,如果表超过了一个特定的可管理和可执行的规模,就可以使用这种机制,它会将表分割为多张表。按照类似的方式,SQLFire使用分区机制来完成类似的数据分割,不过SQLFire并不像RDBMS那样将其分割到严格的磁盘空间之中,而是将其分割到多个SQLFire成员JVM上(不过,它当然也能够输出到磁盘上,以实现额外的持久化保证)。你需要在CREATE TABLE语句中使用PARTITION BY子句来指明某个表的分区策略。可用的策略包括基于每行的主键值进行hash分区、基于非主键列进行hash分区、区间分区(range partitioning)以及列表分区(list partitioning)。SQLFire将分区表中的每一行映射到一个逻辑桶(bucket)中。将行映射到桶的过程基于你所指定的分区策略。例如,如果基于主键进行hash分区,SQLFire就会通过对表的主键进行hash操作来确定逻辑桶。每个桶被分配到一个或多个成员上,这取决于你为该表所配置的副本数量。配置分区表有一个或更多的数据冗余副本能够确保即便有一个成员出现故障,分区的数据依然是可用的。当成员出现故障或被移除时,逻辑桶会根据负载被重新分派到新的成员中。你可以使用CREATE TABLE语句的BUCKETS子句来指定要使用的桶的总数。桶的默认数量是113。
冗余(redundancy):该功能指定了你希望SQLFire为你管理多少份数据副本。在生产环境的系统中,通常会希望至少有一份冗余。冗余是分区数据在内存中一个额外的副本。
位置协同(colocation):例如,如果两个表通常要联合起来以完成一个业务查询,那么就可以使用位置协同机制使主数据和外键部分的数据放到同一个分区中,这样的话查询就会在一个SQLFire成员JVM中执行,该JVM中会包含所有需要的数据。
磁盘持久化(disk persistence):你可以将数据复制到磁盘上,以实现额外的弹性。
事务(transactions):在内存提交之时,通过乐观锁机制锁定数据从而控制数据一致性的能力。这是使SQLFire成为数据库的关键特性之一。
缓存插件(cache plug-in):在缓存缺失的场景下,执行自定义业务逻辑的能力。
监听器(listener):在vFabric SQLFire中,你可以实现任意数量的监听器,这些监听器的触发可以基于应用所执行的特定SQL DML操作。
writer:vFabric SQLFire writer是一个事件处理器,它会在表真正发生变化之前同步处理这些变化。缓存writer的主要用途就是执行输入校验。
异步监听器(asynchronous listener):AsyncEventListener实例会有专用的线程为其提供服务,在线程中会调用一个回调方法。与DML操作相对应的事件会放到一个内部队列之中,专用的线程会将一批事件一次性地分发给用户实现的回调类。
DBSynchronizer:DBSynchronizer是一个内置的AsyncEventListener实现,你可以使用它异步地将数据持久化到兼容JDBC 4.0的第三方数据库中。
DDLUtils:vFabric SQLFire提供了一个命令行界面和DDLUtils,它能够帮助你基于众多支持的RDBMS源模式,生成目标模式和数据加载文件。
2.2.1 服务器分组
服务器分组指明了为某个表存储数据的vFabric SQLFire成员。为了管理表中的数据,你可以使用服务器分组实现vFabric SQLFire数据存储的逻辑化分组。存储数据的任意数量的vFabric SQLFire成员可以参与到一个或多个服务器分组中。当你启动vFabric SQLFire数据存储时,需要指定已命名的服务器分组。默认情况下,所有储存数据的服务器会被添加到default服务器分组中。不同的逻辑数据库模式通常由不同的服务器分组来管理。例如,订单管理系统可能会将所有的客户和订单放到一个Orders模式中进行管理并部署到一个服务器分组中。
同一个系统中可能会将派送和物流数据放到一个不同的服务器分组中进行管理。一个端点(peer)或服务器可以参与到多个服务器分组中,一般的做法会将相关的数据放到一起或者在复制表中控制冗余副本的数量。因为支持分组成员的动态性,所以服务器分组中存储数据的进程数量可以动态变化。但是,服务器分组成员的动态性对应用开发人员进行了抽象,他们可以将服务器分组视为一个逻辑服务器。
服务器分组只是决定了表的数据由那些端和服务器进行管理。表可以由分布式系统中的任何一个端来进行访问,也可以由连接到某个服务器上的瘦客户端访问。当触发服务端程序时,你可以将该程序在服务器分组中的所有成员中并行执行。这些数据感知(data-aware)的程序也会在属于服务器分组端点的客户端上执行。因为不必将表关联到特定成员的IP地址上,所以服务器分组的容量可以动态添加或减少,而不会影响到已有的服务器和客户端应用。vFabric SQLFire可以自动重平衡服务器分组中的表到新添加的成员上。图2-7展现了vFabric SQLFire的服务器分组。


fce5eef6282d17422f215c186e8d5044ca22b91c

最佳实践5:使用服务器分组
你可以将你的服务器指定到逻辑分组上,客户端可以在其连接配置中指明逻辑分组。例如,如果服务器的一个子集为某个特定的表存储数据,你可能会使用分组以确保那些只对这些表感兴趣地客户端会连接到这些服务器上。或者你可以使用一个分组将所有以数据库为中心的流量都转移到服务器的子集上,这一部分服务器直接连接到后端的数据库上。服务器可以属于多个组。客户端只需指明要使用哪个组,而无须知道哪个服务器属于哪个组。
图2-7中的方案1展现了vFabric SQLFire数据管理系统被分为Group 1和Group 2。
图2-7中的方案2展示了第三个组Group 3,这个组包含了Group 1和Group 2。像Group 3这样的分组通常会考虑到引用数据(reference data)被数据fabric中的所有成员(Group 1与Group 2中的成员)所共享,而Group 1与Group 2有不同的数据分区。一个这样的样例就是股票交易数据放在Group 1中,定价数据放在Group 2中,而引用数据放在Group 3中。
服务器分组要与各种业务功能结合使用(如风险管理保存在一个分组中,而库存管理放在另外一个分组中),这样你就能够为不同的业务线提供不同的服务质量,这是通过为每个独立的分组添加或移除处理功能实现的。
例如,在服务器分组中,通常会遵循如下的最佳实践:
引用数据可以放到Group 1中,这是因为它主要是不经常发生变化的复制表数据,因此需要较少的计算资源。但是,数据快速发生变化且数据量足够大需要进行分区的表(如交易、位置以及定价数据)可以由更强大的计算资源来进行管理,就像Group 2中那样。
借助服务器分组,你能够为特定的分组添加/移除处理能力。如果添加了处理能力,你必须在所有服务器都启动后执行一条重平衡的命令。
对于具体的存储过程,你可以选择并行地在服务器分组的所有成员上执行服务器端的存储过程,也可以只在特定服务器分组的成员上执行。
在表的定义中,始终都要设置服务器分组以指明表存在于哪个分组中。如果你不指名服务器分组的名字,就会使用默认的分组,这个分组就会包含系统中所有的vFabric SQLFire成员。这样表就会在整个默认服务器分组的成员中进行分区和复制。如果某个复制表中的数据经常发生变化,在默认分组的每个服务器上来维护副本的成本会是相当高的。在这种情况下,你可以考虑用一个更小的分组,将这种类型的数据放到特定的一组成员/主机上,将其封装到专用的分组中。复制表只应该用于满足数据元素之间的多对多关系。通过设置所需的冗余等级,分区表也可以按照类似RAID的方式进行复制。
服务器分组的便利性有助于为某个特定的分组设置回收策略(eviction policy)的堆百分比,因此能够控制资源消耗。
你可以在某个特定的分组中使用AsynchEventListener。
2.2.2 分区
在后端,由RDBMS模式支撑的企业级应用中,有些表在常规业务中被访问的频率很高,分区策略能够提升性能进而更容易满足SLA的需求。这种被经常访问的表通常被称之为热表(hot table),这是因为它进行数据插入、更新、删除以及读取的频率很高。除了高频率的数据变化,这些热表通常还会因为数据规模导致在单个节点上无法对其进行管理。在这种情况下,你可以使用vFabric SQLFire的水平分区策略将数据分割到多个更小的可管理的数据分区中。
借助vFabric SQLFire的水平分区,一整行的数据会被存储到同一个hash索引的桶(hash indexed bucket)中。桶是数据的容器,它会确定数据的存储点、冗余点以及用来实现重平衡的迁移单元。你可以基于表的主键来对表进行hash分区,如果表没有主键的话,也可以使用内部生成的唯一行ID。其他的分区策略可以在CREATE TABLE语句中的PARTITION BY子句中指定。vFabric SQLFire支持的策略包括基于非主键的列进行hash分区、区间分区以及列表分区。
图2-8展现了一个样例表Flights,该表基于FLIGHT_ID(表Flights的主键)分区为3个桶,第一个桶中包含了行1~3,位于vFabric SQLFire服务器1中,第二个桶中包含了行4~6,位于vFabric SQLFire服务器2中,第三个桶中包含了行7~9,位于vFabric SQLFire服务器3中。所有根据FLIGHT_ID主键对行1~3的航班数据访问都会被vFabric SQLFire转移到vFabric SQLFire服务器1上执行,其他的行情况类似。vFabric SQLFire会通过这种桶的系统自动化地管理所有分区,设计师只需在表的定义中提供正确的PARTITION BY COLUMN(FLIGHT_ID)子句即可。


6ed919ad2bd1da656c4f482a5597e9d67ed79906

在图2-9中,基于前面的讨论再看一下航班样例的模式快照。这里展现的模式是典型的master-detail设计模式,在大多数RDBMS模式中都可以看到。图2-9展现的航班模式包括了表FLIGHTS、FLIGHTAVAILABILITY和AIRLINES。FLIGHTS和FLIGHTAVAILABILITY有一对多关系,而AIRLINES与FLIGHTS和FLIGHTAVAILABILITY之间存在多对多关系。
表Flights按照其主键FLIGHT_ID进行分区并且REDUNDANCY值为1。这意味着,每一行FLIGHT数据都会有一份备份的副本,该副本存在于集群中某一个冗余vFabric SQLFire服务器上。而FLIGHTAVILABILITY根据FLIGHT_ID进行分区,并且COLOCATE被设置为FLIGHTS,这意味着vFabric SQLFire会管理数据分区,当出现FLIGHTS和FLIGHTAVILABILITY表之间的关联查询时,查询会在同一个vFabric SQLFire成员/内存空间中执行,从而优化了性能。AIRLINES表中是不会经常发生变化的引用数据,与FLIGHTS和FLIGHTAVAILABILITY存在多对多关系。为了在vFabric SQLFire中配置这种关系,你可以使用REPLICATE关键字来指定在分布式系统的每个vFabric SQLFire成员上都存有一份完整的AIRLINES表副本。


a82d9e5c3f67231fe8013a1868bbbaee02183ff5

最佳实践6:水平分区
水平分区能够极大地提升企业级应用的可扩展性和性能。
最为常见的形式是将Partition By应用到表的主键上。
vFabric SQLFire水平分区将一整行放到相同的hash索引桶中,因此对桶中某一行的所有数据操作会在相同的内存空间中执行。
要经常测试你的分区模式以确保所有的vFabric SQLFire成员能够保持平衡。
2.2.3 冗余
在vFabric SQLFire中,你可以指定特定的数据要保持多少冗余副本。vFabric SQLFire会管理主副本以及所有备份副本之间数据变化的同步。当一台服务器出现故障时,试图在故障成员中读取和写入数据的操作会被vFabric SQLFire自动重新路由到可用的成员中。在图2-10 中,冗余子句被声明为REDUNDANCY 1,这表明vFabric SQLFire会将一份冗余副本复制到集群中的另一个成员之中。
最佳实践7:冗余
当你在一个vFabric SQLFire表定义中使用REDUNDANCY 1声明时,vFabric SQLFire会在内存中管理数据的一份冗余副本。当vFabric SQLFire成员发生故障时,vFabric SQLFire会将备份服务器升级为主服务器,并将所有的访问请求路由到这个新的主服务器上,因此为企业级应用实现了持续的容错性。
因为vFabric SQLFire是面向内存的数据管理平台,因此具备一定等级的冗余是推荐的做法。但是,冗余的等级必须要在增加冗余数量所带来的优势和不足间保持平衡。增加冗余的数量能够形成更为健壮的系统,但也会带来性能的损耗。额外的冗余副本会导致网络传输和内存使用的增长,因为vFabric SQLFire会一直保持冗余副本处于同步状态。
在生产环境的系统中,REDUNDANCY 1就足够了,尤其是联合使用磁盘asynchronous PERSISTENCE时。对于严重依赖vFabric SQLFire 作为数据管理系统的大多数生产环境系统来说,REDUNDANCY 1并联合使用磁盘asynchronous PERSISTENCE能够提供很好的性能、可扩展性以及可靠性。
规划好系统的规模大小,一旦发生故障时,剩余的节点有足够的处理能力承担新数据和新的客户端请求。


<a href=https://yqfile.alicdn.com/4ab8723821add0cbcd4b4b8dda1baac555f76244.png
" >

2.2.4 位置协同
如前面图2-9所示,FLIGHTS和FLIGHTAVAILABILITY表之间存在父子关系。这表明FLIGHTS和FLIGHTAVAILABILITY这两个表的SQL联合查询必须要在相同的内存空间之中。vFabric SQLFire的COLOCATE子句表明这两个表是要放在一起的,也就是说,这两个表中用于外键关联的列如果具有相同的值,会被强制分区到同一个vFabric SQLFire成员之中。在图2-11中,COLOCATE WITH (FLIGHTS)子句被用到了FLIGHTAVAI-LABILITY上。


8adf77ebdc706591244edc35e745aae0582733d3

最佳实践8:位置协同
如果你使用图2-9中的模式进行如下的select语句查询时,这个查询联合了表FLIGHT和FLIGHTAVAILABILITY,在表定义时需要使用COLOCATE WITH子句:

52acc4134c25778b680b89bbaf8244c1d4c0b4de

联合的条件必须要在flight_id列上,因为它是FLIGHTS和FLIGHTAVAILABILITY表的分区列。在这种情况下,你应该使用COLOCATE WITH(FLIGHTS),如图2-11所示。
在你创建分区表时,COLOCATE WITH子句中所引用的表必须已经存在。当这两个表进行分区和联合确定位置时,两个表中用于外键关联关系的列如果具有相同的值,会被强制分区到同一个vFabric SQLFire成员之中。
两个分区表要进行位置协同时,两个表的CREATE TABLE语句中,SERVER GROUP子句必须是一致的。
2.2.5 磁盘持久化
为了提供额外的数据可靠性,vFabric SQLFire可以将表数据持久化到磁盘上作为内存数据的备份,同时还可以将溢出的表数据存储到磁盘上。这两种磁盘存储方案,也就是持久化和溢出,可以单独使用也可以联合使用。溢出使用磁盘存储作为内存表管理的一种扩展,可以用于分区表和复制表。持久化则是存储每个端中所管理表数据的完整备份。图2-12展现了vFabric SQLFire的主成员和冗余成员如何持久化到外部磁盘存储之中的。

eb53822ba27e977a21b5775302fb9b0e17d2832f

最佳实践9:磁盘持久化
网关发送者、AsyncEventListener以及DBSynchronizer队列需要进行溢出处理并且可以进行持久化。
为了达到最佳的性能,将每个磁盘存储放到一块单独的物理磁盘上。
对网关发送者队列使用磁盘持久化,因为它们通常已经启用了溢出功能,持久化这些队列的能够提供额外的高可用性。
表可以进行溢出处理,可以进行持久化,也可以两者兼具。为了提升性能,要将用于存储溢出表数据的磁盘存储放到一块专用的物理磁盘上。将持久化的表数据或者兼有持久化和溢出的表数据,放到不同的物理磁盘上。
当计算磁盘需求时,要考虑表的修改模式(modification pattern)以及压缩策略(compaction strategy)。vFabric SQLFire创建的每个oplog文件都有指定的MAXLOGSIZE。过期的DML操作只有在压缩的时候才会在oplog文件中移除,因此你需要有足够的空间来存储两次压缩之间的所有操作。对于兼具更新和删除的表,如果你使用自动压缩的话,所需磁盘空间的一个合适上限如下:(1 / (1 – (Compaction_threshold / 100) ) ) * Data size,在这里,Data size指的是在磁盘存储中所有的表数据的总量。默认的COMPACTIONTHRESHOLD为50,因此磁盘空间大致是数据量的两倍。压缩线程可能延后于其他操作执行,因此会导致磁盘的使用暂时超过阈值。如果你禁用自动压缩的话,所需的磁盘量依赖于两次手动压缩之间会累计多少的过期操作。
当你启动系统时,并行启动所有具备持久化表的成员。为了保证一致性和完整性,可以创建和使用启动脚本。
使用sqlf shut-down-all命令来关闭系统。这是一个顺序化的关闭过程,会完整的刷新数据并将其安全地存储到磁盘上。
确定文件压缩策略,如果需要的话,开发程序来监控你的文件并定时执行压缩。
为磁盘存储确定备份策略并遵循之。你可以在系统离线的时候,通过复制文件的方式进行备份,也可以使用sqlf命令备份在线的系统。如果你在磁盘存储离线的时候删除或修改任何的持久化表,要考虑同步磁盘存储中的表。
如果你要将整个数据fabric停掉,在系统关闭的时候压缩磁盘存储,这样能够使启动的性能达到最优。
2.2.6 事务
在vFabric SQLFire中,一条或多条SQL语句组成一个逻辑工作单元的场景具有事务的语义。也就是,整个工作单元可以完整地提交或回滚。vFabric SQLFire的分布式事务协调机制能够实现线性可扩展性且遵循原子性、一致性、隔离性和持久性,也就是所谓的ACID属性。因为参与事务的vFabric SQLFire成员会保持其本身的事务状态,对数据库的查询看到的总是已提交的数据,并且不需要获取锁。因此会在READ_COMMITTED隔离级别,读和写可以并行运行。
在事务写的情况下,vFabric SQLFire会在每个相关节点上锁定要更新行的每一份副本。这减弱了对分布式锁管理器的需求,允许有更大的可扩展性。同时,vFabric SQLFire对REPEATABLE_READ使用了特殊的读取锁和外键检查,以确保在事务执行期间这些行不会发生变化。如果有其他活跃事务的并发写操作正在运行导致无法获取锁时,vFabric SQLFire锁会立即出错(fail-fast)并提示冲突异常。这种立即出错行为的一种例外情况就是初始化事务的vFabric SQLFire节点如果同时也是存储数据的节点的时候。在这种情况下,基于性能考虑,vFabric SQLFire会将事务分批放在本地成员中,并且只有在提交前vFabric SQLFire刷新批处理数据时,冲突才可能被探测到。
当数据由分区表所管理时,对于非事务性的操作,每行数据都由一个成员所持有。但是,在分布式事务中,行的所有副本都会被同等对待 ,更新操作会并行地路由到所有副本中。这样分区表的事务性行为类似于复制表的行为。事务管理器会与vFabric SQLFire成员管理系统紧密协作来确保不管是发生失败还是添加/移除成员,在提交时所有行的变更要么会应用到所有副本中,要么这些变更没有应用到任何副本上。
例如,如图2-13所示,初始化了一个事务来执行updateFlights。在这个方法中的所有语句被视为一个逻辑单元,并且会并行地应用到系统的所有副本中。在这个例子中,vFabric SQLFire服务器1会作为“拥有行的成员(row-owning member)”,协调事务的执行,将事务视为一个逻辑工作单元。

<a href=https://yqfile.alicdn.com/6e87d2e0f18cbeb130f533d5c8a1f7824b3b0a52.png
" >

图2-13~图2-16描述了同一个事务的多个步骤。
图2-14展现了图2-13中updateFlights事务的后续过程。具有updateFlights事

<a href=https://yqfile.alicdn.com/01ff829f0ac61e52f9a3087be93e47168fbc6d60.png
" >

务的vFabric SQLFire成员也就是拥有行的成员,会并行地分发更新命令到所有副本上。同时,该成员在执行提交之前,会等待所有的确认消息(acknowledgment,ACK)到达。ACK表明收到了更新操作,事务可以进入提交状态了。
图2-15中展现了有可能会初始化第二个事务,也就是图中的事务2。事务2可能会与最初的事务updateFlights竞争相同的数据,为了避免冲突,vFabric SQLFire将会回滚事务2的整个逻辑单元,从而让最初的updateFlights事务得以执行。


55515de4a0662ee0ee0534f478f8fedf4b5cc21d

在图2-16中,updateFlights事务执行到了提交状态,在将要结束提交状态之前,它会在所有对应的数据副本上添加锁,并且以一致性的方式传播数据的变更。vFabric SQLFire服务器1是“持有行的成员”,因为它在事务提交过程中会作为事务的协调者,也就是获取本地的写入锁并将所有的变更分发到副本上。
vFabric SQLFire没有中心化的事务协调者。事务发起的成员在事务的持久化过程中会作为事务协调者。如果应用要更新一行或多行数据,事务的协调者会确定哪些成员会被涉及,并且会在所有行的副本上获取本地的“写入”锁。在提交时,所有的变更都会应用到本地数据存储以及所有冗余的副本中。如果有另外一个并发的线程试图修改其中的一行时,对那一行获取本地写入锁时会失败,因此那个事务会自动回滚。
如果没有涉及持久化表的操作,那就没有必要对冗余成员使用二阶段提交。在这种情况下,提交会是高效的、单阶段操作。不像传统的分布式数据库,vFabric SQLFire没有使用写前日志(write-ahead logging)的功能,复制或冗余更新到一个或多个成员时如果发生失败的话,这些日志会用于事务恢复。最可能出现的失败场景就是成员处于不正常的状态并在分布式系统中被移除掉,vFabric SQLFire能够保证数据的一致性。当失败的成员重新在线时,它


3f89b2471d32201221b0c56944788c85f0164bb2

会自动恢复复制/冗余的数据集并与其他的成员建立一致。如果有些数据的所有副本在事务提交前都出现了故障,那么这种情况会被组成员系统探测到,事务会在所有成员上回滚。
最佳实践10:事务
vFabric SQLFire实现了乐观的事务机制。该事务模型对位置协同(colocated)的数据进行了高度优化,这种情况下事务更新的所有行都由单个成员持有。
让事务的持续时间以及事务所涉及的行数尽可能越少越好。vFabric SQLFire会立即(eagerly)获取锁,因此长时间的事务会增加冲突和事务失败的可能性。
理解vFabric SQLFire 所提供的所有隔离级别。vFabric SQLFire 的隔离级别如下:
NONE:默认情况下,vFabric SQLFire的连接并不会参与事务,这与其他的数据库有所不同。但是,这种默认行为并不意味着不存在任何的隔离级别,也不意味着连接能够访问到其他正在处理事务的未提交状态。如果没有事务的话(事务的隔离被设置为NONE),vFabric SQLFire会确保先入先出(first in first out,FIFO)的表更新一致性。某个线程的写入操作在提交后,所有其他的进程都能看到写入的结果,但是不同进程的写入操作对于其他的进程来说,看到的顺序可能是不一样的。当一个表被分区到分布式系统的多个成员之中的时候,vFabric SQLFire会在成员间均匀分布数据集,因此这些存储表的成员都不会成为可扩展性的瓶颈。vFabric SQLFire在指定的时间内,会为特定的行(通过主键标识)分配一个拥有者。当拥有行的成员发生故障时,行的拥有权会以一致的方式转移到另一个可用成员上,这样所有的端服务器会看到相同的行拥有者。
READ_UNCOMMITTED:vFabric SQLFire内部将这种隔离级别升级为READ_COMMITTED。
READ_COMMITTED:vFabric SQLFire不允许正在进行中的事务以及非事务(隔离级别为NONE)操作读取未提交的(脏)数据。在实现这一点时,vFabric SQLFire会将事务的变更维持在一个单独的事务状态中,它只有在提交的时候才会应用到数据存储的实际表中。
REPEATABLE_READ:vFabric SQLFire支持这种隔离级别,遵循了ANSI SQL标准。在事务内,对某一行两次以上的读取都能看到相同的列值。REPEATABLE_READ也能保证在事务持续期间,在第一次读取后,表中提交的行就不会发生变更。vFabric SQLFire会将这种读写锁应用到选定数据的副本中,这样在事务持续期间读取就是可重复的。vFabric SQLFire没有使用范围锁(range lock),因此在这种隔离级别依然是有可能出现幻读(phantom read)的。
vFabric SQLFire会克隆已有的行、应用更新(修改一个或多个域)然后自动使用更新过的行替换原有的行。这种机制避免了并发线程读取和写入行时访问到的是部分更新的数据。如果你的系统中存在事务的话,要小心规划重平衡操作。重平衡可能会在不同的成员间转移数据,这可能会导致事务出现TransactionDataRebalancedException异常而失败。在应用中要有足够的异常处理和事务重试逻辑。
vFabric SQLFire VSD提供了事务提交、回滚以及失败的统计信息,这样你就能够监控vFabric SQLFire事务了。
2.2.7 缓存插件
当分布式系统对某个具有特定标识符的数据行的查询请求无法满足时,可以调用一个加载器(loader)在外部数据源中检索数据。vFabric SQLFire会锁定相关的行并阻止并发获取同一行的数据,从而避免后端数据库的负荷过载。
在图2-17中,在分布式系统的每个vFabric SQLFire都注册了一个RowLoader。这个RowLoader实现了getRow()方法,当缓存缺失时就会调用该方法。RowLoader.getRow会包含必要的业务逻辑来从RDBMS或外部系统检索所需的数据。
最佳实践11:RowLoader
当为一个表配置了加载器时,对该表执行select语句时,在内部会获取到数据并将其插入到vFabric SQLFire表中。基于这种行为模式,配置了加载器的表可以进行约束校验(比如,加载器试图将后端数据库得到的数据插入到vFabric SQLFire的表中,但这些数据违反了表的约束条件)。
当实现RowLoader时,要遵循RowLoader接口。

79785792d0c53a548bf9a6954e7e065a6760df3a

RowLoader接口由如下的方法所组成:
init(String initStr)方法:当将你的实现类注册到表上的时候,这个方法可以用来获取参数。当使用SYS.ATTACH_LOADER程序将RowLoader注册到表上时,vFabric SQLFire会调用实现类的init()方法。所有的参数会在一个String对象中传递进来。
getRow(String schemaName, String tableName, Object[ ] primarykey)方法:每次加载器被触发从外部数据源获取数据时,vFabric SQLFire会调用这个方法的实现来提供模式名、表名以及主键值所组成的一个对象数组(按照表定义中的顺序)。
在你的实现中,getRow的返回值必须是以下几种情况之一:
一个对象数组,其中的元素是每列的值,包括主键,要按照vFabric SQLFire 中表定义的顺序。
null,适用于找不到元素的情况。
java.sql.ResultSet实例,可能是对另一个数据库的查询结果,只会使用第一行数据。结果中的列要与vFabric SQLFire表相匹配。
空的java.sql.ResultSet,适用于找不到元素的情况。
在编译完RowLoader实现之后,将其添加到类路径中,通过执行内置的SYS.ATTACH_LOADER程序将RowLoader注册到表中。
2.2.8 监听器
在vFabric SQLFire中,你可以实现任意数量的监听器,这些监听器可以在应用程序执行特定的SQL DML操作时触发。监听器在插入/更新/删除操作之后触发,也就是所谓的事后事件(after-event)。在图2-18中,注册了3个监听器来监听SQL DML的变更。

25fb8b004ff8e5bd4a1ce1224dafe28a6a0ebd1c

各监听器详述如下:
UpdateListener在图2-19中以绿色文本框来显示。当对表Flights进行更新时,这个监听器就会被触发。Flights表来源于前文图2-9中所介绍的模式。在vFabric SQLFire中,任何使用SQL DML对Flights表所做的更新都会触发UpdateListener以事后事件的方式来执行。这也就是说,vFabric SQLFire中Flights表的数据变更在先,然后UpdateListener回调会以同步的方式执行,从而将数据变更同步到后端的传统关系型数据库之中。在图2-18中,更新DML事件是通过“更新flight F22”触发的,也就是图中针对绿色箭头的那个绿色指示框所示。图中的绿色实线箭头表示vFabric SQLFire成员会首先发生变更,然后绿色的点线箭头触发UpdateListener,它会作为同步的事后事件。当UpdateListener触发时,会执行监听器的回调处理器,它会将变更通过图中的绿色点线发送到数据库中,那个小的文档片段图片表示变更已经发送到了数据库之中。


<a href=https://yqfile.alicdn.com/50e864a51df3405b26724f07c77bb9692d5e1fad.png
" >

InsertListener和DeleteListener也注册到了Flights表上,如图2-18所示。这些监听器的行为与前面所描述的UdpateListener类似。
在图2-19中,如果UpdateListener因为某些原因发生了阻塞,而此时变更已经应用到持有特定Flights分区的vFabric SQLFire成员上了,监听器会抛出异常并且不会完成。
最佳实践12:监听器
监听器能够让其他的系统接收到表变更(插入、更新和删除)的事后事件通知。如果要集成企业范围内依赖于数据变化的系统,这会是很有用的一项技术,这些数据变化都来源于数据fabric。
在有些场景下,你可能想要只执行DML,而不触发监听器。你可以在每个连接上使用skip-listeners属性来忽略所配置的监听器。设置这个属性不会影响到WAN分布式系统。DML事件的发送会始终跨WAN设置/网关发送者。
你可以使用内置的SYS.ADD_LISTENER程序将一个或多个监听器关联到表上。
同一个表上可以定义任意数量的监听器。这样有助于提供多个自定义的插入/更新/删除监听器来满足不同的业务需求。如果是只允许定义一个监听器的话,那么事件处理代码将会需要复杂的if/else上下文切换或很长的case语句,这会使得代码难以维护。
如果监听器中抛出异常的话,更新会成功,但异常会传播回最初的节点中。
2.2.9 writer
vFabric SQLFire的writer是一个事件处理器,它会在变更发生之前同步地处理对表所作出的修改。缓存writer的主要用处是进行输入校验,从而保护数据库不会存储错误的数据。在数据对vFabric SQLFire的表中可见之前,可以使用这种机制来更新外部的数据源。writer为外部的数据源提供了write-through式缓存。与监听器不同,对于某个表只能关联一个writer。当表要因为插入、更新和删除操作而进行修改时,vFabric SQLFire会使用回调机制通知writer,使原有的操作暂时挂起。writer可以通过抛出SQLException从而拒绝正在进行中的变更操作。writer回调是同步执行的,如果回调阻塞的话,操作也会阻塞。
在图2-20中,表Flights上注册了一个InsertWriter。“插入flight 747”会触发InsertWriter的回调事件处理器,首先同步外部数据库,然后再往vFabric SQLFire Flights数据分区中执行插入操作。这是它与vFabric SQLFire监听器的关键不同之处,监听器是在DML已经应用到vFabric SQLFire数据分区之后再将变更传播到外部的系统中。另外一个重要的不同点在于你可以注册多个监听器,但是只能注册一个writer。
在图2-21中,InsertWriter事件处理器在试图往外部数据库写入时抛出了一个异常。在这种情况下,数据库的整个数据变更会回滚,数据变更也不会应用到vFabric SQLFire的Flights数据分区之中。

16d6eb645dc2f6c1b15386ae5d47f82d5a6025aa


63751f35fac4efd63010b72bc58e245da9ca4722

最佳实践13:writer
writer能够让其他的系统在表发生变更(插入、更新和删除)时接收到事前事件通知。为了保持外部DB与vFabric SQLFire同步,应用于vFabric SQLFire的变更已经通过了外部系统的校验,这是writer可以做到的一点。
你可以使用内置的SYS.ATTACH_WRITER程序关联writer。
需要注意的是,一个表只能注册一个writer。
vFabric SQLFire安装了一个样例writer实现,地址是/vFabric_SQLFire_10x/examples/EventCallbackWriterImpl.javam。这个writer实现可以执行对任意JDBC数据源的写入,只要对应的驱动器位于类路径下即可。
事件的类型可以分为TYPE.BEFORE_INSERT、TYPE.BEFORE_UPDATE以及TYPE.BEFORE_DELETE。
2.2.10 异步监听器
前面的讨论主要集中在监听器和writer上,它们本质上都是同步的回调处理器。如果企业的应用程序不适合使用同步处理器的话,这可能是因为同步处理器会产生阻塞或者使用它的客户端可靠性不佳,那么异步处理器可以作为可行的备选方案。AsyncEventListener实例会使用专用的线程调用其回调方法。对应于DML操作的事件会放到内部队列中,有一个专用的线程会将一批事件分发给用户实现的回调类。事件分发的频率通过vFabric SQLFire中AsyncEventListener的配置来管理。
在图2-22中,在Flights表上注册了TestAsynchEventListener,当DML事件发生时,就会触发回调。DML事件会放到一个事件队列中,然后根据所配置的周期,再从队列中取出。当事件从队列中取出时,会调用已注册的TestAsyncEventListener.processEvents()方法。

762ea98aea3ae3de7f5eb43bd9c12093d0930315

最佳实践14:异步监听器
AsyncEventListener实例通过专用的线程来提供服务,在线程中会调用回调方法。
对应于DML操作的事件会放到内部队列中,有一个专用的线程会将一批事件分发给用户实现的回调类。在集成企业内的系统时,这种方式会很有用,因为这些被集成的系统可能会具有不同的可靠性以及响应时间SLA。
如果多个线程同时更新的数据导致AsyncEventListener实现类排队的话,那么有可能会出现一定的数据一致性问题。这说明事件并不能保证顺序。
只有对于单线程执行的情况,vFabric SQLFire才能够保证DML语句按照顺序应用于分布式系统(以及AsyncEventListener队列和远程WAN站点)。多个线程的更新会保证FIFO顺序。vFabric SQLFire并没有“总体顺序(total ordering)”的保证。
如果多个线程并发更新复制表的相同行,或者多线程并发地更新复制表中存在父子关系的相关行,有可能会出现数据不一致性的问题。在复制表中并发更新相同的行可能会导致一些副本的值与其他副本的值不一致。并发删除父表中的行并在子表中插入一行,有可能会出现孤立无关联的(orphaned)行。
当DML操作要在AsyncEventListener或远程WAN站点上排队时,并发访问表会遇到类似的不一致性问题。例如,如果两个独立的线程并发更新父表和子表,vFabric SQLFire将这些更新在AsyncEventListener或WAN网关上排队时的顺序并不一定与主分布式系统的更新顺序相匹配。这可能会导致在后端数据库上违反外键约束(例如,使用DBSynchronizer)或者当初始的表更新时,在远程WAN并没有发生更新。如果多个线程并发更新分区表中相同的key,并不会发生这种无序更新。但是,对于更新多行的任意操作,应用中都应该使用事务。
所有的AsyncEventListener实现都应该检查已有的数据库连接是否由于之前出现的异常已经关闭。例如,在catch代码块中检查Connection.isClosed(),并在执行进一步操作之前,如果需要的话,重新建立连接。vFabric SQLFire中的DBSynchronizer实现在重用连接前会自动执行这种检查。
AsyncEventListener实现必须安装到一个或多个vFabric SQLFire系统的成员中。你只能将AsyncEventListener安装到数据存储成员上(也就是将host-data属性设置为true的端)。
可以将监听器安装到多个成员上以提供高可用性并保证事件的正常递送,以防备带有活跃AsyncEventListener的vFabric SQLFire成员被关闭。在任意的时间点上,只会有一个成员会包含活跃的线程来派发事件。其他成员的线程会处于冗余的备用状态。
为了实现高可用性以及事件投递的可靠性,将事件队列配置为支持持久化和冗余。
2.2.11 DBSynchronizer
DBSynchronizer是内置的AsyncEventListener实现,可以使用它同步持久化数据到第三方兼容JDBC 4.0的RDBMS中。你可以将DBSynchronizer作为AsyncEvent-Listener安装到多个数据存储之中。在vFabric SQLFire上执行的DML语句会传递到DBSynchronizer以及所配置的JDBC RDBMS上。DBSynchronizer只应该安装到数据存储成员上(也就是将host-data属性设置为true的成员)。每个DBSynchronizer实例维护了一个内部队列来分批处理DML语句,并且会有一个专用的线程来做这件事。该线程从队列中取出DML语句,并使用prepared statement将它们应用到外部数据库中,如图2-23所示。


d7a8139cee110aea7f386615ac11b3fb7b458923

最佳实践15:DBSynchronizer
配置DBSynchronizer队列使其支持持久化和冗余,以实现高可用性和事件投递的可靠性。要在不止一个数据存储上安装DBSynchronizer以保证高可用性。在任意的时间点,只有一个成员中会有活跃的DBSynchronizer线程,它会在外部数据库上执行DML。其他成员上的线程会作为备用(冗余)来保证当含有活跃DBSynchronizer线程的成员出现故障时,DML依然能够执行。
默认情况下,如果正在活跃的成员被关闭,在DBSynchronizer内部队列中所有未执行的DML操作都会丢失。如果你想避免丢失操作的话,需要将DBSynchronizer的内部队列配置为支持持久化。
由于性能和内存的原因,所安装的备用DBSynchronizer不要超过一个(最多一个冗余)。
如果活跃的DBSynchronizer线程出现故障时,DML操作可能会重新应用到RDBMS上。如果具有活跃DBSynchronizer的成员出现故障时,恰好正在发送一批操作,此时这一批事件中有一些DML语句可能已经应用到了RDBMS上面。在故障恢复时,新的DBSynchronizer线程会重新发送失败的批处理操作并重新应用最初的DML操作。当发生这种情况时,RDBMS可能会处于不同步的状态,这取决于DML操作的特性、如何修改表的列以及是否存在列约束。
如果表定义了约束(主键、唯一键)的话,在故障恢复时,重新执行如下类型的DML操作并不会导致不同步现象。
在具有主键的表上重新执行创建操作。这种情况下,会违反主键约束并抛出SQLException,但是DBSynchronizer会忽略该异常。
导致违反唯一键的创建或更新操作。重新执行创建和更新操作导致重复的值,这会违反唯一性约束。DBSynchronizer会忽略这种情况下所抛出的SQLException。
导致违反检查约束的创建或更新操作。重新执行创建或更新操作(如,递增或递减某一个列的值)可能会导致违反检查约束。DBSynchronizer会忽略这种情况下所抛出的SQLException。
如果DBSynchronizer在更新或提交至数据库时,遇到了异常,这批操作会被保留,DBSynchronizer线程会继续尝试执行这批操作直至成功。
如果在vFabric SQLFire系统中并发执行的DML操作是针对父子表的外键关联关系,那么将DML应用到外部数据库时可能会出现违反外键约束的情况。这种现象甚至可能发生在vFabric SQLFire系统已成功执行DML的情况之中。尽管在vFabric SQLFire系统中对父子表的插入是按照顺序执行的,但是插入数据的操作到达DBSynchronizer时,可能是相反的顺序。为了避免这种行为,要在一个应用线程中执行父表和子表的更新。
2.2.12 SQLF命令与DDLUtils
vFabric SQLFire提供了命令行界面以及DDLUtils,它们可以基于众多支持的RDBMS源模式生成目标模式以及数据加载文件。图2-24列出了DDLUtils能够迁移的RDBMS。


c6ab2a72c14e65b2a3b63e8de487bffd1ffa6f4c
相关实践学习
MySQL基础-学生管理系统数据库设计
本场景介绍如何使用DMS工具连接RDS,并使用DMS图形化工具创建数据库表。
相关文章
|
14天前
|
缓存 算法 Java
Java 实现的局域网管控软件的性能调优
局域网管控软件在企业网络管理中至关重要,但随着网络规模扩大和功能需求增加,其性能可能受影响。文章分析了数据处理效率低下、网络通信延迟和资源占用过高等性能瓶颈,并提出了使用缓存、优化算法、NIO库及合理管理线程池等调优措施,最终通过性能测试验证了优化效果,显著提升了软件性能。
29 1
|
12天前
|
存储 安全 Java
Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
【10月更文挑战第17天】Java Map新玩法:探索HashMap和TreeMap的高级特性,让你的代码更强大!
36 2
|
13天前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
30 3
|
13天前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
26 2
|
15天前
|
存储 算法 Java
Java Set因其“无重复”特性在集合框架中独树一帜
【10月更文挑战第14天】Java Set因其“无重复”特性在集合框架中独树一帜。本文深入解析Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定的数据结构(哈希表、红黑树)确保元素唯一性,并提供最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的`hashCode()`与`equals()`方法。
25 3
|
19天前
|
安全 Java API
Java 17新特性让你的代码起飞!
【10月更文挑战第4天】自Java 8发布以来,Java语言经历了多次重大更新,每一次都引入了令人兴奋的新特性,极大地提升了开发效率和代码质量。本文将带你从Java 8一路走到Java 17,探索那些能让你的代码起飞的关键特性。
66 1
|
22天前
|
算法 Java 测试技术
java性能调优涉及哪些方面
本文详细探讨了性能调优的各个方面,包括Java编程、多线程、JVM监控、设计模式和数据库调优。文章还介绍了性能调优的标准制定、介入时机、系统性能的影响因素,以及如何衡量和判断系统的性能与负载承受能力。最后,提出了性能调优的具体策略,包括代码、设计、算法优化及参数调整,并讨论了限流、智能化扩容等兜底策略。
java性能调优涉及哪些方面
|
2天前
|
监控 前端开发 Java
Java SpringBoot –性能分析与调优
Java SpringBoot –性能分析与调优
|
10天前
|
存储 Java API
优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。
【10月更文挑战第19天】本文介绍了如何优雅地使用Java Map,通过掌握其高级特性和技巧,让代码更简洁。内容包括Map的初始化、使用Stream API处理Map、利用merge方法、使用ComputeIfAbsent和ComputeIfPresent,以及Map的默认方法。这些技巧不仅提高了代码的可读性和维护性,还提升了开发效率。
31 3
|
10天前
|
存储 安全 Java
Java Map新玩法:深入探讨HashMap和TreeMap的高级特性
【10月更文挑战第19天】Java Map新玩法:深入探讨HashMap和TreeMap的高级特性,包括初始容量与加载因子的优化、高效的遍历方法、线程安全性处理以及TreeMap的自然排序、自定义排序、范围查询等功能,助你提升代码性能与灵活性。
17 2