到目前为止,我们已经介绍了Apache Ignite及其体系结构的基本功能。现在,是时候深入了解Apache Ignite的实现了,并观察它如何提高应用程序性能。在前几章中,我们已经讨论了很多关于Ignite特性和体系结构的内容,现在我们将介绍所有最重要的引爆内存数据网格特性,并解释用例,以了解应该如何以及何时使用这些特性。本章的主要目标是演示如何使用Apache Ignite来加速应用程序性能而不改变代码。IMDG或内存数据网格不是内存中的关系数据库、NoSQL数据库或关系数据库。但是,它是一个分布式键值存储,可以将其想象为一个分布式分区散列映射,其中集群中的每个节点都有自己的数据部分。数据模型分布在单个位置或多个位置的多个服务器上。这种分布称为数据结构。这种分布式模型也称为共享的无架构。IMDG具有以下特点:
- 所有服务器都可以在每个节点上活动。
- 所有数据都存储在服务器的RAM中。
- 可以不受干扰地添加或删除服务器,以增加可用的RAM数量。
- 数据模型是非关系型的,并且是基于对象的。
- 分布式应用程序是用平台独立语言编写的。
- 数据结构具有弹性,允许对单个服务器或多个服务器进行非破坏性的自动检测和恢复。
正如我们前面讨论的,Apache Ignite实现了JCache规范来开发内存数据网格。然而,Ignite为内存数据网格提供了许多高级功能。在本章中,我们将讨论以下主题: - Apache Ignite 作为一个 2nd 级别的cache。
- Java方法的缓存。
- Web sessions集群。
- Apache Ignite作为一个 big memory, off-heap memory。
2nd级别缓存
通过避免昂贵的数据库调用,将数据保持在应用程序的本地,第二级缓存可以提高应用程序的性能。二级缓存由持久性提供程序完全管理,通常对应用程序是透明的。也就是说,应用程序在不知道缓存的情况下通过实体管理器读取、写入和提交数据。
还有基于持久性提供者(如MyBatis或Hibernate)的一级缓存。第1级用于缓存当前数据库会话中从数据库检索的对象。当前端(web页面或web服务)调用一个服务时,将打开一个HTTP会话并重用它,直到服务方法返回。在服务方法返回之前执行的所有操作都将共享L1缓存,因此相同的对象不会从数据库中检索两次。完成数据库会话后,从数据库检索的对象将不可用。在大多数持久性提供程序中,默认情况下总是启用第1级缓存。
与第一级缓存不同,二级缓存能够跨越数据库会话,存储数据库对象和查询结果(查询缓存)。它位于持久性提供者和数据库之间。持久性上下文共享缓存,使第二级缓存在整个应用程序中都可用。因此,由于实体被加载到共享缓存中并从共享缓存中可用,因此数据库流量大大减少。因此,简而言之,二级缓存提供了以下好处:
- 通过避免昂贵的数据库调用来提高性能。
- 数据对应用程序保持透明。
- CRUD操作可以通过普通的持久性管理器函数执行。
- 通过使用二级缓存,您可以在不更改代码的情况下加速应用程序的性能。
MyBatis二级缓存
在Ignite中,MyBatis的第2级缓存存储的是实体数据,而不是实体或对象本身。数据以序列化格式存储,看起来像hashmap,其中键是实体Id,值是原始值的列表。
在这里,我们的目标是最小化查询执行时间。接下来,我们将开发一个使用MyBatis和Ignite的应用程序,以实现计算性能提升。
Step1
创建一个java项目
mvn archetype:generate -DgroupId=com.mycookcode.bigData.ignite -DartifactId=ignite-mybatis -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
Step2
在maven配置文件中添加以下依赖:
<dependency>
<groupId>org.apache.ignite</groupId>
<artifactId>ignite-core</artifactId>
<version>${ignite.version}</version>
</dependency>
<dependency>
<groupId>org.apache.ignite</groupId>
<artifactId>ignite-spring</artifactId>
<version>${ignite.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ignite</artifactId>
<version>1.0.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
</dependency>
在这个项目中,我们将使用spring框架,因此必须添加一些spring相关的依赖项。此外,我们还添加了MyBatis核心库和mybatise-ignite库,以集成Apache Ignite作为二级缓存。在编译时,Maven使用这些信息在Maven存储库中查找上述所有库。Maven首先查看本地计算机上的存储库。如果库不存在,它将从公共Maven存储库下载它们,并将它们存储在本地存储库中。
Step3
现在,我们必须在项目的资源目录中添加spring上下文XML文件,以将其添加到java类路径中。spring上下文文件的完整版本将类似。让我们详细地看一下spring-core.xml文件。
<!--在本节中,我们声明了所有必需的XML名称空间,我们将在这个XML配置文件中使用这些名称空间。所有这些名称空间和URI都是标准的spring名称空间。-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Enable annotation-driven caching. -->
<cache:annotation-driven/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- beans -->
<bean id="servicesBean" class="com.mycookcode.bigData.ignite.WebServices">
<property name="dao" ref="userServicesBean"/>
</bean>
<!--声明了soap服务bean和user mapper bean,这个类和接口的源代码将在稍后解释。-->
<bean id="userServicesBean" class="com.mycookcode.bigData.ignite.dao.UserServices">
<property name="userMapper" ref="userMapper"/>
</bean>
<!--Ignite-->
<bean id="cacheManager" class="org.apache.ignite.cache.spring.SpringCacheManager">
<property name="configuration" ref="ignite.cfg" />
</bean>
<!--这是Ignite节点的主要配置部分。这里我们声明了名为myBatisCache的缓存名称,将缓存模式配置为分区。
注意,缓存模式也可以复制。另外,我们为缓存配置了一个备份副本,并启用了缓存统计数据。
属性name= " backup "value= " 1 "表示缓存项在另一个节点上总是有一个冗余副本。
在稍后的配置中,我们添加了SPI discovery来查找集群中的节点成员。
在我们的例子中,我们使用多播TCP/IP查找程序。如果您有自己的Ignite集群运行,不要忘记在配置文件中添加或更新IP地址,如下所示。
-->
<bean id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
<!-- Set to true to enable distributed class loading for examples, default is false. -->
<property name="peerClassLoadingEnabled" value="false"/>
<property name="gridName" value="TestGrid"/>
<property name="clientMode" value="false"/>
<property name="cacheConfiguration">
<list>
<bean class="org.apache.ignite.configuration.CacheConfiguration">
<!-- Set a cache name. -->
<property name="name" value="myBatisCache"/>
<!--<property name="atomicityMode" value="ATOMIC"/>-->
<!-- Set cache mode. -->
<property name="cacheMode" value="PARTITIONED"/>
<property name="backups" value="1"/>
<property name="statisticsEnabled" value="true" />
</bean>
</list>
</property>
<!-- Explicitly configure TCP discovery SPI to provide list of initial nodes. -->
<property name="discoverySpi">
<bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
<property name="ipFinder">
<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder">
<property name="addresses">
<list>
<!-- In distributed environment, replace with actual host IP address. -->
<value>127.0.0.1:47500..47509</value>
</list>
</property>
</bean>
</property>
</bean>
</property>
</bean>
<!--配置了MyBatis SQL mapper bean。使用SQL session factory指定映射器接口,并在XML中添加所有SQL映射器文件的类路径。-->
<bean id="userMapper" autowire="byName" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.mycookcode.bigData.ignite.mapper.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath*:mapper/*Mapper.xml"/>
</bean>
<!--为MySQL服务器设置了JDBC数据源。我们还添加了带有JDBC URL、用户名和密码的标准数据源连接,没有任何连接池。-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.mycookcode.bigData.ignite.mapper" />
</bean>
</beans>
Step4
现在,我们将添加UserMapper.xml到类路径中。注意,以下xml文件是位于绝对类路径(/resources/mapper/ usermap.xml)中。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mycookcode.bigData.ignite.mapper.UserMapper">
<cache type="org.mybatis.caches.ignite.IgniteCacheAdapter" />
<select id="getEmploee" parameterType="String" resultType="com.mycookcode.bigData.ignite.dto.Employee" useCache="true">
SELECT * FROM emp WHERE ename = #{ename}
</select>
</mapper>
映射器名称空间(mapper namespace)和缓存类型是配置中最重要的部分。注意,对于每个映射器名称空间,在Ignite集群中将创建一个复制缓存。在这种情况下,缓存名称将是com.mycookcode.bigData.ignite.mapper.UserMapper。对于缓存类型,我们声明了Ignite cache适配器的接口。接下来,我们添加了SQL查询,它是参数类型和返回值的类型。我们使用非常简单的SQL查询来通过雇员名获取雇员。在这里,我们已经通过XML完成了所有声明性配置。现在我们准备向应用程序添加业务逻辑。
Step5
从文件夹脚本中执行以下DDL和DML脚本,以创建数据库表,并向表中插入几行。为了简单起见,我们使用Oracle数据库中著名的emp和dept实体。我稍微修改了DDL/DML脚本,让它们运行到MySQL中。department (dept)和employee (emp)表的结构非常简单,它们彼此之间有一对多的关系。
create table dept(
deptno integer,
dname text,
loc text,
constraint pk_dept primary key (deptno)
);
create table emp(
empno integer,
ename text,
job text,
mgr integer,
hiredate date,
sal integer,
comm integer,
deptno integer,
constraint pk_emp primary key (empno),
constraint fk_deptno foreign key (deptno) references dept (deptno)
);
Step6
既然已经设置了项目和构建系统,就可以继续创建web服务了。
此时,soap web服务只包含一个web方法getEmployee。
package com.mycookcode.bigData.ignite;
import com.mycookcode.bigData.ignite.dao.UserServices;
import com.mycookcode.bigData.ignite.dto.Employee;
import javax.jws.WebMethod;
import javax.jws.WebService;
@WebService(name="BusinessRulesServices",
serviceName = "BusinessRulesServices",
targetNamespace = "http://com.ignite.rules/services")
public class WebServices {
private UserServices userServices;
@WebMethod(exclude = true)
public void setDao(UserServices userServices){
this.userServices = userServices;
}
@WebMethod(operationName = "getEmploee")
public Employee getEmploee(String ename) {return userServices.getEmploee(ename);}
}
您可以从源代码中获得其他所有的类,比如DTO。
Step7
要运行web服务,我们将使用带有maven构建的one-jar插件。通过以下命令构建项目。
mvn clean install
Step8
运行web服务。
java -jar ./target/ignite-mybatis-1.0-SNAPSHOT.one-jar.jar
如果一切顺利,您应该会在控制台上看到以下日志:
web service服务运行在本地的7001端口上。可以通过这个URL web服务http://localhost:7001/invokeRules?wsdl发现web服务WSDL。现在可以使用soap客户机调用web服务,我将使用chrome浏览器中的开发者工具控制台来测试服务。当我第一次调用服务时,调用时间大约是259毫秒,因为查询结果还没有在缓存中。
再次调用Web方法。
这一次,响应时间是79毫秒,响应速度明显提高。MyBatis只返回Ignite缓存的结果。它几乎是实时的响应。让我们来看一下Ignite缓存中的缓存条目。Ignitevisor命令扫描可以帮助您找到缓存中的所有条目。
Cache Key = org.apache.ibatis.cache.CacheKey [idHash=1538632341, hash=872929822, checksum=\
2936898376, count=6, multiplier=37, hashcode=872929822, updateList=[com.blu.imdg.mapper.Us\
erMapper.getEmploee, 0, 2147483647, SELECT * FROM emp WHERE ename = ?, KING, SqlSessionFac\
toryBean]]
Key Value = [com.blu.imdg.dto.Employee [idHash=545458831, hash=342167489, date=null, ename\ =KING, mgr=null, empno=7839, job=PRESIDENT, deptno=10, sal=5000]]
在emp表中有一些行(总共12行),并且在字段ename上没有任何索引。让我们在表emp的字段ename上创建唯一的索引,并重新执行服务调用。
CREATE UNIQUE INDEX ename_idx ON emp (ename);
在字段ename上创建一个btree索引。现在,在SOAP消息中更改雇员表的ename字段,例如,FORD并再次执行web方法。
现在的响应时间是84毫秒,你可能会认为差别不大。但是在生产系统中,将拥有数百万行,而不是数据库表中的13行。此外,当表上有索引时,DML操作每次都会重新索引数据库表,这也会降低应用程序的性能。大多数时候,Ignite缓存的响应时间不会改变,因为没有额外的开销来消耗DB连接、SQL查询的软/硬解析。
Calculate application speedup计算应用加速:
可以使用Amdahl's law计算应用程序的加速比。公式:1/((1 - Proportion speed up) + Proportion speed up / speed up)
- P是可以并行的比例
- S是这部分并行可以加速S倍 (S可以理解是CPU核的个数,即新代码的执行时间为原来执行时间的1/S)
另外,请注意,对于web应用程序,系统应该包括浏览器展现时间和网络延迟。
在使用缓存时,应用程序的性能至少取决于以下两个因素: - 应用程序检索缓存数据片段的次数;
- 缓存减少了响应时间的比例。
假设我们有一个web应用程序,未加缓存的整个页面呈现时间是259毫秒。现在,让我们从数据库级缓存计算速度。在我们的例子中: - 打开页面时间:259毫秒。
- 数据库时间:84毫秒。
- 缓存检索时间:79毫秒。
- 比例:84/259~32.4%
预期的系统加速率应该是:
1/((1-0.324)+0.324/(84/79))=1/(0.676+0.305)~1.01倍的系统加速。
虽然1.01倍的系统速度不是很令人印象深刻,但在生产环境中,缓存后的结果将与没缓存前的结果有很大不同。