(四)apache ignite-In-memory caching

到目前为止,我们已经介绍了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级缓存。


image.png

与第一级缓存不同,二级缓存能够跨越数据库会话,存储数据库对象和查询结果(查询缓存)。它位于持久性提供者和数据库之间。持久性上下文共享缓存,使第二级缓存在整个应用程序中都可用。因此,由于实体被加载到共享缓存中并从共享缓存中可用,因此数据库流量大大减少。因此,简而言之,二级缓存提供了以下好处:

  1. 通过避免昂贵的数据库调用来提高性能。
  2. 数据对应用程序保持透明。
  3. CRUD操作可以通过普通的持久性管理器函数执行。
  4. 通过使用二级缓存,您可以在不更改代码的情况下加速应用程序的性能。

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

如果一切顺利,您应该会在控制台上看到以下日志:

image.png

web service服务运行在本地的7001端口上。可以通过这个URL web服务http://localhost:7001/invokeRules?wsdl发现web服务WSDL。现在可以使用soap客户机调用web服务,我将使用chrome浏览器中的开发者工具控制台来测试服务。当我第一次调用服务时,调用时间大约是259毫秒,因为查询结果还没有在缓存中。
image.png

再次调用Web方法。
image.png

这一次,响应时间是79毫秒,响应速度明显提高。MyBatis只返回Ignite缓存的结果。它几乎是实时的响应。让我们来看一下Ignite缓存中的缓存条目。Ignitevisor命令扫描可以帮助您找到缓存中的所有条目。
image.png

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方法。


image.png

现在的响应时间是84毫秒,你可能会认为差别不大。但是在生产系统中,将拥有数百万行,而不是数据库表中的13行。此外,当表上有索引时,DML操作每次都会重新索引数据库表,这也会降低应用程序的性能。大多数时候,Ignite缓存的响应时间不会改变,因为没有额外的开销来消耗DB连接、SQL查询的软/硬解析。

Calculate application speedup计算应用加速:

可以使用Amdahl's law计算应用程序的加速比。公式:1/((1 - Proportion speed up) + Proportion speed up / speed up)


image.png
  • 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倍的系统速度不是很令人印象深刻,但在生产环境中,缓存后的结果将与没缓存前的结果有很大不同。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,113评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,644评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,340评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,449评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,445评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,166评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,442评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,105评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,601评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,066评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,161评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,792评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,351评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,352评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,584评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,618评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,916评论 2 344

推荐阅读更多精彩内容

  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,898评论 2 89
  • 为了更好地理解Apache Ignite和用例的功能,理解它的体系结构和拓扑结构非常重要。通过更好地理解Ignit...
    席梦思阅读 7,532评论 1 8
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,601评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,524评论 25 707
  • 西门庆一回到家的话,就开始问自己小老婆的病情。吴月娘责怪的说,你整天都是不回家,在外面闲游浪荡吴月娘的眼里,对西门...
    原野草阅读 92评论 0 1