到目前為止虽界,我們已經(jīng)介紹了Apache Ignite及其體系結構的基本功能。現(xiàn)在涛菠,是時候深入了解Apache Ignite的實現(xiàn)了莉御,并觀察它如何提高應用程序性能撇吞。在前幾章中,我們已經(jīng)討論了很多關于Ignite特性和體系結構的內(nèi)容礁叔,現(xiàn)在我們將介紹所有最重要的引爆內(nèi)存數(shù)據(jù)網(wǎng)格特性牍颈,并解釋用例,以了解應該如何以及何時使用這些特性琅关。本章的主要目標是演示如何使用Apache Ignite來加速應用程序性能而不改變代碼煮岁。IMDG或內(nèi)存數(shù)據(jù)網(wǎng)格不是內(nèi)存中的關系數(shù)據(jù)庫、NoSQL數(shù)據(jù)庫或關系數(shù)據(jù)庫涣易。但是画机,它是一個分布式鍵值存儲,可以將其想象為一個分布式分區(qū)散列映射都毒,其中集群中的每個節(jié)點都有自己的數(shù)據(jù)部分色罚。數(shù)據(jù)模型分布在單個位置或多個位置的多個服務器上。這種分布稱為數(shù)據(jù)結構账劲。這種分布式模型也稱為共享的無架構戳护。IMDG具有以下特點:
- 所有服務器都可以在每個節(jié)點上活動。
- 所有數(shù)據(jù)都存儲在服務器的RAM中瀑焦。
- 可以不受干擾地添加或刪除服務器腌且,以增加可用的RAM數(shù)量。
- 數(shù)據(jù)模型是非關系型的榛瓮,并且是基于對象的铺董。
- 分布式應用程序是用平臺獨立語言編寫的。
- 數(shù)據(jù)結構具有彈性禀晓,允許對單個服務器或多個服務器進行非破壞性的自動檢測和恢復精续。
正如我們前面討論的,Apache Ignite實現(xiàn)了JCache規(guī)范來開發(fā)內(nèi)存數(shù)據(jù)網(wǎng)格粹懒。然而重付,Ignite為內(nèi)存數(shù)據(jù)網(wǎng)格提供了許多高級功能。在本章中凫乖,我們將討論以下主題: - Apache Ignite 作為一個 2nd 級別的cache确垫。
- Java方法的緩存。
- Web sessions集群帽芽。
- Apache Ignite作為一個 big memory, off-heap memory删掀。
2nd級別緩存
通過避免昂貴的數(shù)據(jù)庫調用,將數(shù)據(jù)保持在應用程序的本地导街,第二級緩存可以提高應用程序的性能披泪。二級緩存由持久性提供程序完全管理,通常對應用程序是透明的搬瑰。也就是說付呕,應用程序在不知道緩存的情況下通過實體管理器讀取计福、寫入和提交數(shù)據(jù)。
還有基于持久性提供者(如MyBatis或Hibernate)的一級緩存徽职。第1級用于緩存當前數(shù)據(jù)庫會話中從數(shù)據(jù)庫檢索的對象象颖。當前端(web頁面或web服務)調用一個服務時,將打開一個HTTP會話并重用它姆钉,直到服務方法返回说订。在服務方法返回之前執(zhí)行的所有操作都將共享L1緩存,因此相同的對象不會從數(shù)據(jù)庫中檢索兩次潮瓶。完成數(shù)據(jù)庫會話后陶冷,從數(shù)據(jù)庫檢索的對象將不可用。在大多數(shù)持久性提供程序中毯辅,默認情況下總是啟用第1級緩存埂伦。
與第一級緩存不同,二級緩存能夠跨越數(shù)據(jù)庫會話思恐,存儲數(shù)據(jù)庫對象和查詢結果(查詢緩存)沾谜。它位于持久性提供者和數(shù)據(jù)庫之間。持久性上下文共享緩存胀莹,使第二級緩存在整個應用程序中都可用基跑。因此,由于實體被加載到共享緩存中并從共享緩存中可用描焰,因此數(shù)據(jù)庫流量大大減少媳否。因此,簡而言之荆秦,二級緩存提供了以下好處:
- 通過避免昂貴的數(shù)據(jù)庫調用來提高性能篱竭。
- 數(shù)據(jù)對應用程序保持透明。
- CRUD操作可以通過普通的持久性管理器函數(shù)執(zhí)行步绸。
- 通過使用二級緩存掺逼,您可以在不更改代碼的情況下加速應用程序的性能。
MyBatis二級緩存
在Ignite中靡努,MyBatis的第2級緩存存儲的是實體數(shù)據(jù)坪圾,而不是實體或對象本身杂瘸。數(shù)據(jù)以序列化格式存儲赚窃,看起來像hashmap苔咪,其中鍵是實體Id,值是原始值的列表漾月。
在這里,我們的目標是最小化查詢執(zhí)行時間胃珍。接下來梁肿,我們將開發(fā)一個使用MyBatis和Ignite的應用程序蜓陌,以實現(xiàn)計算性能提升。
Step1
創(chuàng)建一個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
現(xiàn)在揍堰,我們必須在項目的資源目錄中添加spring上下文XML文件鹏浅,以將其添加到java類路徑中。spring上下文文件的完整版本將類似个榕。讓我們詳細地看一下spring-core.xml文件篡石。
<!--在本節(jié)中,我們聲明了所有必需的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節(jié)點的主要配置部分。這里我們聲明了名為myBatisCache的緩存名稱霹崎,將緩存模式配置為分區(qū)珊搀。
注意,緩存模式也可以復制尾菇。另外境析,我們?yōu)榫彺媾渲昧艘粋€備份副本,并啟用了緩存統(tǒng)計數(shù)據(jù)派诬。
屬性name= " backup "value= " 1 "表示緩存項在另一個節(jié)點上總是有一個冗余副本劳淆。
在稍后的配置中,我們添加了SPI discovery來查找集群中的節(jié)點成員默赂。
在我們的例子中沛鸵,我們使用多播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數(shù)據(jù)源。我們還添加了帶有JDBC URL吊趾、用戶名和密碼的標準數(shù)據(jù)源連接咙鞍,沒有任何連接池。-->
<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
現(xiàn)在趾徽,我們將添加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集群中將創(chuàng)建一個復制緩存朗恳。在這種情況下,緩存名稱將是com.mycookcode.bigData.ignite.mapper.UserMapper载绿。對于緩存類型粥诫,我們聲明了Ignite cache適配器的接口。接下來崭庸,我們添加了SQL查詢怀浆,它是參數(shù)類型和返回值的類型。我們使用非常簡單的SQL查詢來通過雇員名獲取雇員怕享。在這里执赡,我們已經(jīng)通過XML完成了所有聲明性配置。現(xiàn)在我們準備向應用程序添加業(yè)務邏輯函筋。
Step5
從文件夾腳本中執(zhí)行以下DDL和DML腳本沙合,以創(chuàng)建數(shù)據(jù)庫表,并向表中插入幾行跌帐。為了簡單起見首懈,我們使用Oracle數(shù)據(jù)庫中著名的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
既然已經(jīng)設置了項目和構建系統(tǒng)挎袜,就可以繼續(xù)創(chuàng)建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發(fā)現(xiàn)web服務WSDL。現(xiàn)在可以使用soap客戶機調用web服務野崇,我將使用chrome瀏覽器中的開發(fā)者工具控制臺來測試服務称开。當我第一次調用服務時,調用時間大約是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上創(chuàng)建唯一的索引菱肖,并重新執(zhí)行服務調用客冈。
CREATE UNIQUE INDEX ename_idx ON emp (ename);
在字段ename上創(chuàng)建一個btree索引。現(xiàn)在稳强,在SOAP消息中更改雇員表的ename字段场仲,例如,F(xiàn)ORD并再次執(zhí)行web方法退疫。
現(xiàn)在的響應時間是84毫秒渠缕,你可能會認為差別不大。但是在生產(chǎn)系統(tǒng)中褒繁,將擁有數(shù)百萬行亦鳞,而不是數(shù)據(jù)庫表中的13行。此外,當表上有索引時燕差,DML操作每次都會重新索引數(shù)據(jù)庫表遭笋,這也會降低應用程序的性能。大多數(shù)時候徒探,Ignite緩存的響應時間不會改變瓦呼,因為沒有額外的開銷來消耗DB連接、SQL查詢的軟/硬解析测暗。
Calculate application speedup計算應用加速:
可以使用Amdahl's law計算應用程序的加速比央串。公式:1/((1 - Proportion speed up) + Proportion speed up / speed up)
- P是可以并行的比例
- S是這部分并行可以加速S倍 (S可以理解是CPU核的個數(shù),即新代碼的執(zhí)行時間為原來執(zhí)行時間的1/S)
另外碗啄,請注意质和,對于web應用程序,系統(tǒng)應該包括瀏覽器展現(xiàn)時間和網(wǎng)絡延遲稚字。
在使用緩存時侦另,應用程序的性能至少取決于以下兩個因素: - 應用程序檢索緩存數(shù)據(jù)片段的次數(shù);
- 緩存減少了響應時間的比例。
假設我們有一個web應用程序尉共,未加緩存的整個頁面呈現(xiàn)時間是259毫秒“担現(xiàn)在,讓我們從數(shù)據(jù)庫級緩存計算速度袄友。在我們的例子中: - 打開頁面時間:259毫秒殿托。
- 數(shù)據(jù)庫時間:84毫秒。
- 緩存檢索時間:79毫秒剧蚣。
- 比例:84/259~32.4%
預期的系統(tǒng)加速率應該是:
1/((1-0.324)+0.324/(84/79))=1/(0.676+0.305)~1.01倍的系統(tǒng)加速支竹。
雖然1.01倍的系統(tǒng)速度不是很令人印象深刻,但在生產(chǎn)環(huán)境中鸠按,緩存后的結果將與沒緩存前的結果有很大不同礼搁。