apache ignite系列(三):数据处理(数据加载,数据并置,数据查询)

​ 使用ignite的一个常见思路就是将现有的关系型数据库中的数据导入到ignite中,然后直接使用ignite中的数据,相当于将ignite作为一个缓存服务,当然ignite的功能远不止于此,下面以将ignite集成进java服务的方式进行演示ignite的数据存储和查询相关的功能。由于个人习惯,示例演示没有使用测试代码,而是使用rest接口演示。

​ 在讲数据加载之前,ignite中存储的几种模式(LOCAL, REPLICATED, PARTITIONED):

LOCAL:本地模式,数据都存储在本地,无数据再平衡,类似常见的存储服务;

PARTITIONED:分区模式,数据分散到集群中的各个节点,分区模式适合存储数量庞大的数据

564309-20180820002917305-1125500042.png

如图所示是设置了Backup备份数的,默认备份数是0,如果分区模式下不设置备份数的话则会存在丢失数据的风险。

REPLICATED:复制模式,有数据再平衡过程,主节点(Primary)数据与分区模式的一致,只是复制模式默认备份了除主节点数据外的其余数据。复制模式适合存储数据量小,增长不快的数据。

564309-20180820002930554-1123850824.png

分区模式和复制模式各有优点和缺点,具体选择要根据实际场景的特点去权衡:

模式优点缺点
分区模式(PARTITIONED)能存储海量数据,频繁更新对其影响不大查询缓存涉及到数据移动,对查询性能有影响
复制模式(REPLICATED)适合存储数据量不大的数据,数据查询性能稳定频繁更新对其影响较大

1,数据加载

这里使用mybatis查询MYSQL里的数据然后存入ignite,完整代码可以参考:

https://github.com/cording/ignite-example

为了演示,需要先在MYSQL中生成样本数据,相关sql脚本为ignite-example\src\main\resources\import.sql,执行该SQL脚本即可完成表的创建和测试数据的初始化。

在配置文件中定义缓存

                <bean class="org.apache.ignite.configuration.CacheConfiguration">
                    <property name="name" value="student"/>
                    <property name="cacheMode" value="REPLICATED"/>
                    <property name="backups" value="1"/>
                    <property name="atomicityMode" value="ATOMIC"/>
                    <property name="copyOnRead" value="false"/>
                    <property name="dataRegionName" value="Default_Region"/>
                    <property name="indexedTypes">
                        <list>
                            <value>java.lang.Long</value>
                            <value>org.cord.ignite.data.domain.Student</value>
                        </list>
                    </property>
                </bean>

添加相关依赖

<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>
<!-- 使用索引的话需要用到ignite-indexing这个模块 -->
<dependency>
    <groupId>org.apache.ignite</groupId>
    <artifactId>ignite-indexing</artifactId>
    <version>${ignite.version}</version>
</dependency>

​ 一般将数据导入ignite集群的方式是使用cache.put(...)方法,但是当有大量的数据需要导入的时候,put的效率已经无法满足了,针对大量数据导入可以使用ignite的流处理器:

DataLoader.java

......
    /**导入数据到ignite*/
    public void loadData(){
        //查询student集合
        List<Student> result = studentDao.findAllStudents();
        //分布式id生成器
        IgniteAtomicSequence sequence = ignite.atomicSequence("studentPk", 0, true);
        //根据缓存名获取流处理器,并往流处理器中添加数据
        try(IgniteDataStreamer<Long, Student> streamer = ignite.dataStreamer(CacheKeyConstant.STUDENT)) {
            result.stream().forEach(r -> streamer.addData(sequence.incrementAndGet(), r));
            //将流里面的剩余数据压进ignite
            streamer.flush();
        }
    }
......

导入数据之后,可以在监控程序中看到数据存储情况:

564309-20180820002952841-590438923.png

​ 流之所以能提高加载数据的速度,是因为流本质就是一个批处理。ignite是通过一致性哈希保证一致性的,每往集群中存入一条cache记录,ignite会先根据一致性哈希算法计算出这条cache映射到哪个节点,然后会将这条记录存储在该节点。而在流处理器中,流处理器会将映射到相同节点的数据批量存储到对应节点,这样会显著提升数据加载的效率。

2,数据查询

​ 最直接的查询缓存方式是使用cache.get(...)方法,这种方式只能应对简单的key-value缓存,如果是设置了索引类型(indexedTypes),则缓存就会变成SQL table,这个时候就需要使用SQL方式查询,当使用SQL方式查询的时候,一般会有各种查询条件,这些查询条件对应的字段均需要预先设置索引。ignite里面有两种索引,一种是普通索引,一种是组合索引,要用到@QuerySqlField注解。而查询用到的api主要是SqlFieldsQuerySqlQuery,前者是域查询,也就是查询部分字段结果集,而后者是普通查询。

​ 所以,如果想使用SQL查询,就需要在加载数据之前在缓存定义中设置索引类型(indexedTypes),并对查询中可能用到的字段在对应实体类中相关属性添加注解,有必要的情况下还要设置索引。当定义缓存的时候设置了索引类型,则缓存不再是普通的KV形式的缓存了,而是具有数据库表的特性,这时候ignite就变成了分布式的内存数据库了,其sql相关功能是基于h2的sql引擎实现的。

1) 设置缓存索引类型
  • JAVA代码定义缓存时设置索引类型

这里以long为主键,String为实体类作为示例:

使用CacheConfiguration.setIndexedTypes(Long.class, String.class)即可设置索引

  • XML配置中设置索引类型

同样也是设置indexedTypes属性即可

<bean class="org.apache.ignite.configuration.CacheConfiguration">
......
    <property name="indexedTypes">
        <list>
            <value>java.lang.Long</value>
            <value>org.cord.ignite.data.domain.Student</value>
        </list>
    </property>
......
</bean>
2) 注解@QuerySqlField的三种用法
  • 启用实体类属性为查询域
    @QuerySqlField
    private String test;

加上该注解后,test字段才可以在sql语句中访问,这种形式不对该属性列创建索引。

  • 启用查询域并为该列设置普通索引
    @QuerySqlField(index = true)
    private String test;
  • 启用查询域并设置组合索引
    @QuerySqlField(orderedGroups = {@QuerySqlField.Group(
            name = "student", order = 0)})
    private String name;

    @QuerySqlField(orderedGroups = {@QuerySqlField.Group(
            name = "student", order = 1)})
    private String email;

其中name属性指定了组合索引的名字,order表示该字段在组合索引中的顺序。

该组合索引与普通数据库类似,同样遵循最左原则,即组合索引是否会用到会受到最左原则的限制。

3) 使用 SqlFieldsQuery进行域查询

SQL语法中有两个预定义字段_key_val

_key : 表示缓存中的所有键

_val: 表示缓存中的所有值对象

List<List<?>> res = cache.query(new SqlFieldsQuery("select _VAL,name from \"Test\".student")).getAll();
System.out.format("The name is %s.\n", res.get(0).get(0));
4) 使用SqlQuery进行普通查询

NormalController.class

    @RequestMapping("/sqlQuery")
    public @ResponseBody
    String sqlQuery(HttpServletRequest request, HttpServletResponse response) {
        IgniteCache<Long, Student> tempCache = ignite.cache(CacheKeyConstant.STUDENT);

        /**普通查询*/
        String sql_query = "name = ? and email = ?";
        SqlQuery<Long, Student> cSqlQuery = new SqlQuery<>(Student.class, sql_query);
        cSqlQuery.setReplicatedOnly(true).setArgs("student_44", "student_44gmail.com");

        List<Cache.Entry<Long, Student>> tempResult = tempCache.query(cSqlQuery).getAll();

        if (CollectionUtils.isEmpty(tempResult)) {
            return "result is Empty!";
        }
        Student student = tempResult.stream().map(t -> t.getValue()).findFirst().get();
        System.out.format("the beginning of student[student_44] is %s\n", student.getDob());

        /**聚合函数查询*/
        /**[count]*/
        String sql_count = "select count(1) from student";
        SqlFieldsQuery countQuery = new SqlFieldsQuery(sql_count);
        countQuery.setReplicatedOnly(true);
        List<List<?>> countList =  tempCache.query(countQuery).getAll();

        long count = 0;
        if(!CollectionUtils.isEmpty(countList)) {
            count = (Long)countList.get(0).get(0);
        }
        System.out.format("count of cache[student] is %s\n", count);

        /**[sum]*/
        String sql_sum = "select sum(studId) from student";
        SqlFieldsQuery sumQuery = new SqlFieldsQuery(sql_sum);
        sumQuery.setReplicatedOnly(true);
        List<List<?>> sumList = tempCache.query(sumQuery).getAll();
        long sum = 0;
        if(!CollectionUtils.isEmpty(sumList)) {
            sum = (Long)sumList.get(0).get(0);
        }
        System.out.format("sum of cache[student.id] is %s\n", sum);

        return "all executed!";
    }

运行结果如下:

the beginning of student[student_44] is Thu Sep 28 00:00:00 GMT+08:00 2017
count of cache[student] is 500
sum of cache[student.id] is 125250

3,数据并置与关联查询

​ 数据的并置主要是针对采用分区模式存储的数据,所谓的数据并置,就是提供一种约束,将相关连的数据存储在相同的网格节点上,这样在数据查询或者分布式计算的时候就不需要数据移动了,这样会提升整体的性能。

​ 以下以X,Y,Z三个cache的并置作为示例,完整代码请查看示例工程ignite-example

564309-20180820205724609-573881880.jpg

其中X,Y,Z为三个分区模式的cache,Y与X并置,也就是说,Y的数据在存储的时候,会根据其XId属性,将数据存储在对应的X所在的节点上,同理,Z与Y并置,也就是将Z的数据存储在其YId属性对应的Y所在的节点上。以此形成一种约束,使得数据的分配可以人为控制。

要使用数据并置,就不得不提到一个API了,也就是AffinityKey,当一个cache与另外一个cache并置的时候,其cache的Key就得是AffinityKey类型了。

首先进行数据初始化:

CollocatedController.java

    private String init(){
        if(init.get()){
            return "already execute init.";
        }
        //定义三个缓存
        CacheConfiguration<Long, X>  xcf = new CacheConfiguration<Long, X>("X")
                .setCacheMode(CacheMode.PARTITIONED)
                .setIndexedTypes(Long.class, X.class);
        CacheConfiguration<AffinityKey<Long>, Y>  ycf = new CacheConfiguration<AffinityKey<Long>, Y>("Y")
                .setCacheMode(CacheMode.PARTITIONED)
                .setIndexedTypes(Affinity.class, Y.class);
        CacheConfiguration<AffinityKey<Long>, Z>  zcf = new CacheConfiguration<AffinityKey<Long>, Z>("Z")
                .setCacheMode(CacheMode.PARTITIONED)
                .setIndexedTypes(Affinity.class, Z.class);

        ignite.destroyCache("X");
        ignite.destroyCache("Y");
        ignite.destroyCache("Z");
        ignite.getOrCreateCache(xcf);
        ignite.getOrCreateCache(ycf);
        ignite.getOrCreateCache(zcf);

        IgniteCache<Long, X> xc = ignite.cache("X");
        IgniteCache<AffinityKey<Long>, Y> yc = ignite.cache("Y");
        IgniteCache<AffinityKey<Long>, Z> zc = ignite.cache("Z");
        //加载数据
        Y y;
        Z z;
        for (long i = 0; i < 100; i++) {
            xc.put(i, new X(i, String.valueOf(i)));
            y = new Y(i, String.valueOf(i), i);
            yc.put(y.key(), y);
            z = new Z(i, String.valueOf(i), i);
            zc.put(z.key(), z);
        }

        init.set(true);
        return "all executed.";
    }

而cache并置之后怎么校验并置是否成功呢?这就要用到Affinity.classmapKeyToNode()方法了,其作用是根据给定的key,找到存储该key的节点信息,具体使用方法如下:

@RequestMapping("/verify")
public @ResponseBody
String verifyCollocate(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if(!init.get()){
        init();
    }

    Affinity<Long> affinityX = ignite.affinity("X");
    Affinity<Long> affinityY = ignite.affinity("Y");
    Affinity<Long> affinityZ = ignite.affinity("Z");

    for (long i = 0; i < 100; i++) {
        ClusterNode nodeX = affinityX.mapKeyToNode(i);
        ClusterNode nodeY = affinityY.mapKeyToNode(i);
        ClusterNode nodeZ = affinityZ.mapKeyToNode(i);

        if(nodeX.id() != nodeY.id() || nodeY.id() != nodeZ.id() || nodeX.id() != nodeZ.id()){
            throw new Exception("cache collocated is error!");
        }
    }
    System.out.println("cache collocated is right!");

    return "all executed.";
}

执行/verify之后,无异常抛出,在监控程序中查看一下存储情况:

564309-20180820003038409-492118854.png

会发现三个cache的数据分布完全一致,这与验证程序的结果(无异常抛出)保持一致,说明cache并置成功。

当数据并置成功后,就可以使用关联查询了,可以类比数据库中的多表联查:

@RequestMapping("/query")
public @ResponseBody
String query(HttpServletRequest request, HttpServletResponse response){
    if(!init.get()){
        init();
    }
    IgniteCache<Long, X> xc = ignite.cache("X");
    IgniteCache<AffinityKey<Long>, Y> yc = ignite.cache("Y");
    IgniteCache<AffinityKey<Long>, Z> zc = ignite.cache("Z");

    String sql1 = "from Y,\"X\".X " +
            "where Y.XId = X.id " +
            "and Y.info = ?";
    String sql2 = "from Z,\"Y\".Y " +
            "where Z.YId = Y.id " +
            "and Z.info = ?";
    String sql3 = "from Z,\"Y\".Y,\"X\".X " +
            "where Z.YId = Y.id and Y.XId = X.id " +
            "and Z.info = ?";

    int i = IntStream.range(1, 100).skip((int)(100*Math.random())).findFirst().getAsInt();

    System.out.println("query X and Y:");
    System.out.println(yc.query(new SqlQuery<AffinityKey<Long>, Y>(Y.class, sql1).setArgs(i)).getAll());
    System.out.println("**************************************************************************************");

    System.out.println("query Y and Z:");
    System.out.println(zc.query(new SqlQuery<AffinityKey<Long>, Z>(Z.class, sql2).setArgs(i)).getAll());
    System.out.println("**************************************************************************************");

    System.out.println("query X and Y and Z:");
    System.out.println(zc.query(new SqlQuery<AffinityKey<Long>, Z>(Z.class, sql3).setArgs(i)).getAll());
    System.out.println("**************************************************************************************");

    return "all executed.";
}

执行结果如下:

query X and Y:
[Entry [key=AffinityKey [key=83, affKey=83], val=org.cord.ignite.example.collocated.Y@605e8969]]
**************************************************************************************
query Y and Z:
[Entry [key=AffinityKey [key=83, affKey=83], val=org.cord.ignite.example.collocated.Z@562dbd4]]
**************************************************************************************
query X and Y and Z:
[Entry [key=AffinityKey [key=83, affKey=83], val=org.cord.ignite.example.collocated.Z@7ff851ce]]
**************************************************************************************

如果是没有并置的缓存,在关联查询的时候就需要启用非并置的分布式关联:SqlQuery.setDistributedJoins(true)

数据并置还可以使用注解@AffinityKeyMapped注解,其用法与使用AffinityKey .class类似,完整示例可参看AffinityMappedController.class

至此,ignite的数据处理相关内容结束。

转载于:https://www.cnblogs.com/cord/p/9431859.html

<think>好的,我现在需要处理用户关于Apache Ignite数据迁移最佳实践与方法的问题。首先,我得确认用户的需求是什么。用户提到了数据迁移策略,可能想知道如何高效地将数据从传统数据库迁移到Ignite,或者在不同节点之间迁移数据时的注意事项。根据用户提供的引用,特别是引用[1]和[4],用户可能遇到了性能问题或者迁移过程中的挑战。 首先,我应该回顾Apache Ignite的官方文档和相关资料,确保我的回答准确。根据引用[1],Ignite提供了数据库迁移解决方案,例如从MySQL迁移到Ignite的案例。用户可能需要具体的步骤,比如配置数据源、使用工具或API进行数据导入,以及激活集群等步骤。 接下来,引用[4]提到在迁移过程中遇到了性能问题,单台Ignite服务的查询性能不如MySQL,这可能涉及到配置不当或者数据分布的问题。因此,在回答最佳实践时,需要强调配置优化、数据预热和集群激活的重要性。 然后,我需要组织回答的结构,按照系统级指令的要求,使用中文,生成相关问题,并正确引用文献。同时,确保数学公式和代码块格式正确。例如,在解释数据分布策略时,可能需要提到一致性哈希算法,使用$$...$$格式展示公式,如$$h(key) \mod N$$,但要注意Ignite可能使用其他分区算法,比如Rendezvous Hashing,需要核实。 用户的问题可能还涉及数据同步策略,如全量迁移和增量迁移的区别,以及如何通过CDC工具实现实时同步。引用[3]提到Ignite的高性能数据处理能力,因此在增量迁移中使用CDC可以提升实时性。 另外,引用[4]提到激活集群的命令,这部分需要包含在操作步骤中,确保用户知道在数据加载后激活集群。同时,代码示例部分,可能需要展示如何使用Ignite的SQL或DataStreamer API导入数据,比如Java代码片段,但用户示例中的代码是Python的,是否要使用Python?需要看Ignite是否支持Python API。不过通常Ignite的DataStreamer是Java的,所以可能需要用Java示例,或者说明使用SQL工具如sqlline。 在最佳实践中,数据预热、分区策略调整、网络优化和监控都是关键点。比如,调整分区数量(partitions)来避免数据倾斜,或者使用基准测试工具进行性能验证,如YCSB。 最后,生成的相关问题需要围绕用户可能进一步关心的点,比如性能调优、数据一致性、与其他系统的对比,以及错误处理等。这些问题应该帮助用户深入理解迁移过程中的各个方面。 现在需要检查是否有遗漏的引用内容,比如引用[2]提到版本2.15.0,可能涉及新特性,但用户的问题可能不特定于版本,所以可以暂时不深入。引用[3]强调极限速度和数据处理,因此在优化部分可以强调Ignite的内存优势。 总结回答结构:先概述数据迁移策略类型,然后分步骤说明实践方法,接着给出最佳实践,最后附上相关问题。确保每个部分都有引用支持,比如配置数据源引用[1],激活集群引用[4],性能问题引用[4],CDC引用[3]等。 需要避免技术错误,比如确认Ignite数据迁移工具是否支持全量和增量,或者是否需要第方工具。根据引用[1],Ignite有内置的迁移解决方案,可能包括数据导入工具,但增量可能需要CDC。同时,DataStreamer是Ignite的高性能数据加载工具,应该提到。 最后,检查LaTeX格式是否正确,例如变量用$...$,独立公式用$$...$$,代码块使用正确语法,引用标识位置合适。</think>### Apache Ignite 数据迁移最佳实践与方法 Apache Ignite数据迁移策略主要围绕**数据加载**、**分布式协调**和**性能优化**展开,以下是具体实践方法: --- #### 一、数据迁移策略分类 1. **全量迁移** - 适用于初始化场景,通过批量导入工具(如`SQLLine`或自定义脚本)将历史数据一次性加载Ignite集群。 - 示例代码(使用Ignite的`DataStreamer`高性能API): ```java try (IgniteDataStreamer<Integer, User> streamer = ignite.dataStreamer("users")) { streamer.perNodeBufferSize(10_000); // 优化缓冲区大小 for (User user : mysqlUsers) { streamer.addData(user.getId(), user); } } ``` > **最佳实践**:调整`perNodeBufferSize`以避免内存溢出[^1]。 2. **增量迁移** - 通过**变更数据捕获(CDC)**工具(如Debezium)实时同步数据库变更到Ignite[^3]。 - 关键公式: $$ \Delta D_{target} = \Delta D_{source} \cdot f_{sync}(t) $$ 其中$\Delta D$表示增量数据,$f_{sync}$为同步频率函数。 --- #### 二、迁移步骤与配置 1. **配置数据源与目标** - 在`ignite-config.xml`中定义数据源连接(如MySQL)和Ignite缓存配置: ```xml <bean class="org.apache.ignite.configuration.CacheConfiguration"> <property name="name" value="users"/> <property name="cacheStoreFactory"> <bean class="org.apache.ignite.cache.store.jdbc.CacheJdbcPojoStoreFactory"> <property name="dataSourceBean" value="mysqlDataSource"/> </bean> </property> </bean> ``` [^1] 2. **数据分布优化** - 使用`Rendezvous Hashing`分区算法确保数据均匀分布: $$ partition = \text{hash}(key) \mod P $$ 其中$P$为分区数,建议设置为集群节点数的10-100倍[^4]。 3. **集群激活与监控** - 数据加载完成后,执行激活命令: ```bash control.sh --activate ``` 并通过`ignitevisor`监控数据分布均衡性[^4]。 --- #### 、性能调优最佳实践 1. **预热缓存** - 在迁移后执行全表扫描查询,强制数据加载至内存: ```sql SELECT /*+ LOCAL */ * FROM users; ``` 2. **网络优化** - 调整Ignite的`communicationSPI`与`discoverySPI`参数,减序列化开销。 3. **避免数据倾斜** - 通过`AffinityKeyMapper`自定义关联键,分散高频访问数据。 --- #### 四、常见问题与解决方案 - **性能低于源数据库**:检查分区配置与网络延迟,增加内存页大小(`pageSize`)。 - **数据不一致**:启用Ignite事务或通过`WRITE_THROUGH`模式保证持久化。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值