美文网首页Thinking In Data
(四)apache ignite-In-memory cachi

(四)apache ignite-In-memory cachi

作者: 席梦思 | 来源:发表于2018-09-10 23:51 被阅读50次

    到目前为止,我们已经介绍了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倍的系统速度不是很令人印象深刻,但在生产环境中,缓存后的结果将与没缓存前的结果有很大不同。

    相关文章

      网友评论

        本文标题:(四)apache ignite-In-memory cachi

        本文链接:https://www.haomeiwen.com/subject/dkfvvftx.html