Ignite SQL网格快速学习(一)

由于之前做了一段时间的规则引擎的翻译与学习,现在回来再续上Ignite的博文,希望大家一起进步。

1.基础的SQL Query体验之SqlQuery

在Ignite中,SQL查询常用的分为两种类型,分别是SqlQuery和SqlFieldQuery,还有其他的一些功能性的查询,我也会一一讲到。我们下面分两小点来讲,所以从我们的标题也看出来,第一个小点讲SqlQuery.

1.1 准备工作

	//java类 Company,getter个setter就不附了,只是一个简单的POJO
public class Company {
    private static final AtomicLong ID_GEN = new AtomicLong();

    @QuerySqlField(index = true)
    private Long id;

    @QuerySqlField(index = true)
    private String name;
    ...
}
	//java类 Company,getter个setter就不附了,只是一个简单的POJO
public class Person {
    private static final AtomicLong ID_GEN = new AtomicLong();
    @QuerySqlField(index = true)
    public Long id;
    @QuerySqlField(index = true)
    public Long companyId;
    @QuerySqlField
    public String firstName;
    @QuerySqlField
    public String lastName;
    @QuerySqlField
    public String resume;
    @QuerySqlField(index = true)
    public double salary;
    private transient AffinityKey<Long> key;
    
    public AffinityKey<Long> key() {
        if (key == null)
            key = new AffinityKey<>(id, companyId);
        return key;
    }
	...
}
	//测试数据的生成
	private static void initData(IgniteCache<Long, Company> cacheOnlyCompany, IgniteCache<AffinityKey<Long>,Person> cacheaffinity, IgniteCache<Long, Person> cacheOnlyPerson) {
		//初始化Company
		Company cloud = new Company("yihecloud");
		Company ultra = new Company("ultrapower");
		//存储Company
		cacheOnlyCompany.put(cloud.id(), cloud);
		cacheOnlyCompany.put(ultra.id(), ultra);
		//初始化person
        Person p1 = new Person(cloud, "John", "Doe", 2000D, "John Doe has Master Degree.");
        Person p2 = new Person(cloud, "Jane", "Doe", 1000D, "Jane Doe has Bachelor Degree.");
        Person p3 = new Person(ultra, "John", "Smith", 3000D, "John Smith has Bachelor Degree.");
        Person p4 = new Person(ultra, "Jane", "Smith", 4000D, "Jane Smith has Master Degree.");
        //并置方式(affinity,PS:我在数据网格快速学习中有深入的讲解)存储
        cacheaffinity.put(p1.key(), p1);
        cacheaffinity.put(p2.key(), p2);
        cacheaffinity.put(p3.key(), p3);
        cacheaffinity.put(p4.key(), p4);
        //非并置方式存储      
        cacheOnlyPerson.put(p1.id, p1);
        cacheOnlyPerson.put(p2.id, p2);
        cacheOnlyPerson.put(p3.id, p3);
        cacheOnlyPerson.put(p4.id, p4);
	}	

1.2 SqlQuery执行

	public static void main(String[] args) {
	//1.0 启动Ignite
		try(Ignite ignite = Ignition.start("examples/config/example-ignite.xml")){
		//2.1 配置Company的缓存
			CacheConfiguration<Long, Company> cacheOnlyCompanyCfg = new CacheConfiguration<Long, Company>("CACHE_ONLY_COMPANY");
			cacheOnlyCompanyCfg.setIndexedTypes(Long.class, Company.class);
			//2.2 配置并置关联的person的缓存(看缓存的键的类型!!)
			CacheConfiguration<AffinityKey<Long>, Person> cacheaffinityCfg = new CacheConfiguration<AffinityKey<Long>, Person>("CACHE_AFFINITY");
			cacheaffinityCfg.setIndexedTypes(AffinityKey.class, Company.class);
			//2.3 配置非并置关联的person的缓存(看缓存的键的类型!!)
			CacheConfiguration<Long, Person> cacheOnlyPersonCfg = new CacheConfiguration<Long, Person>("CACHE_ONLY_PERSON");
			cacheOnlyPersonCfg.setIndexedTypes(Long.class,Person.class);
			//3.0 获取cache实例
			try(IgniteCache<Long, Company> cacheOnlyCompany = ignite.getOrCreateCache(cacheOnlyCompanyCfg);
				IgniteCache<AffinityKey<Long>, Person> cacheaffinity = ignite.getOrCreateCache(cacheaffinityCfg);
				IgniteCache<Long, Person> cacheOnlyPerson = ignite.getOrCreateCache(cacheOnlyPersonCfg)){
			//4.0 初始化	initData(cacheOnlyCompany,cacheaffinity,cacheOnlyPerson);
			//SqlQuery 执行
				sqlQueryEnjoy(cacheOnlyPerson);
			}
			 
		}
	}
	private static void sqlQueryEnjoy(IgniteCache<Long, Person> cacheOnlyPerson) {
		String sql = "salary > ? and salary < ?";
		List<Entry<Long, Person>> matched = cacheOnlyPerson.query(new SqlQuery<Long,Person>(Person.class, sql).setArgs(1000,4000)).getAll();
		if(null != matched)
			matched.stream().forEach((entry) -> {
				System.out.println(entry.getKey() + ":" + entry.getValue());
			});
	}

日志

	//PS:我们查询的就是salary大于1000小于4000的
1:Person [id=1, companyId=1, lastName=Doe, firstName=John, salary=2000.0, resume=John Doe has Master Degree.]
3:Person [id=3, companyId=2, lastName=Smith, firstName=John, salary=3000.0, resume=John Smith has Bachelor Degree.]

1.3 总结

虽然我们上述代码中做了一些与SqlQuery不相关的一些事,但这都是在下面的知识点中会用到的。一是给大家一个眼熟,那么下面讲起来就不会陌生了;二是我下面不想写那么多遍,所以就开头写,这时候大家注意力还是很集中的。

因为这是Sql网格的第一个知识点,我分条目来讲

1.3.1 Sql语句

Ignite的SQL网格完全支持所有SQL和DML命令,包括SELECT、UPDATE、INSERT、MERGE和DELETE查询。

具体的Sql语法已经超出了本命题的范畴,我们直接进入下一个知识点

1.3.2 SqlQuery

我们通过Sql语句作为参数,构造了一个SqlQuery对象,它是Query抽象类的一个实现,我们之后会将的SqlFieldQuery也是Query的实现之一。该类并没有特别的注释,我这里只讲一下它的数据结构,接下来再讲IgniteCache.query(Query q)该方法。

public final class SqlQuery<K, V> extends Query<Cache.Entry<K, V>> {
	...
    public SqlQuery(String type, String sql) {
        setType(type);
        setSql(sql);
    }
    public SqlQuery(Class<?> type, String sql) {
        setType(type);
        setSql(sql);
    }
    ....
}

截取的这段代码是SqlQuery的源码的声明,该类有两个参数化的类型。

方法参数介绍
SqlQueryString type:表示我们所获取的结果的类型, String sql:这即是我们会执行的SQL语句)这两个重载方法只是看用户偏好,当然还是传递字符串比较好,其实Ignite也是以String格式来存储它为SqlQuery的type字段,所以还是直接传递类名吧
SqlQuery(Class<?> type:表示我们所获取的结果的类型,但不是String字符串,而是class实例, String sql)看上面

1.3.3 IgniteCache#query(Query< R > qry)

    @SuppressWarnings("unchecked")
    @Override public <R> QueryCursor<R> query(Query<R> qry) {
        A.notNull(qry, "qry");

        GridCacheGateway<K, V> gate = this.gate;

        CacheOperationContext prev = onEnter(gate, opCtx);

        try {
            ctx.checkSecurity(SecurityPermission.CACHE_READ);

            validate(qry);

            convertToBinary(qry);

            CacheOperationContext opCtxCall = ctx.operationContextPerCall();

            boolean keepBinary = opCtxCall != null && opCtxCall.isKeepBinary();

            if (qry instanceof ContinuousQuery)
                return (QueryCursor<R>)queryContinuous((ContinuousQuery<K, V>)qry, qry.isLocal(), keepBinary);

            if (qry instanceof SqlQuery)
                return (QueryCursor<R>)ctx.kernalContext().query().querySql(ctx, (SqlQuery)qry, keepBinary);

            if (qry instanceof SqlFieldsQuery)
                return (FieldsQueryCursor<R>)ctx.kernalContext().query().querySqlFields(ctx, (SqlFieldsQuery)qry,
                    keepBinary);

            if (qry instanceof ScanQuery)
                return query((ScanQuery)qry, null, projection(qry.isLocal()));

            return (QueryCursor<R>)query(qry, projection(qry.isLocal()));
        }
        catch (Exception e) {
            if (e instanceof CacheException)
                throw (CacheException)e;

            throw new CacheException(e);
        }
        finally {
            onLeave(gate, prev);
        }
    }

上面贴的是我们调用的query方法的源码,我也看不懂,但是我们来只看对我们的SqlQuery有用的这段,即if条件判断那里
这里写图片描述

截图有点模糊,这里做一下解释。因为我们做的SqlQuery,所以在我们光标所在的地方进入表达式内。我额外打出的是ctx,即上下文对象的一些信息,我们可以看到,它是CACHE_ONLY_PERSON,对的,与我们的缓存实例的名字一致的。

我们来看上述截图中光标所在的哪一行的表达式:

	return (QueryCursor<R>)ctx.kernalContext().query().querySql(ctx, (SqlQuery)qry, keepBinary);

首先该函数在此就返回了,也可以说是结束句了。

该表达式首先调用的是ctx:GridCacheContext<K, V>的kernalContext()方法,ctx是GridCacheContext对象,它是缓存上下文对象,因此我们才会看到,ctx的名字是我们配置的缓存名字。该对象维护着一个GridKernalContext对象属性,它是网格内核上下文。我下面做一个类集成图:

	IgniteCache的实现类IgniteCacheProxy的query方法
	-query->  GridCacheContext<K, V>(即我们截图中的ctx)
	-维护着->  GridKernalContext
	-集成自->  Iterable<GridComponent>  
	------->  GridComponent(接口,它是所有主要的内部Ignite组件)

我们需要知道的是ctx是与我们的缓存实例相关的即可,它所维护的GridKernalContext,则是一个可迭代的GridComponent集合对象,即网格组件对象集合。而网格组件GridComponent则是我们很多Ignite的负借口,比如我们目前所需要的是Sql查询,那么在调用GridKernalContext#query()时候,其实是返回的GridQueryProcessor这个组件(不建议看源码,2600行,人生苦短,对自己好点),而它是网格组件GridComponent的子类之一。

方法调用结果返回的是QueryCursor<Cache.Entry<K,V>>类型的数据。该类是一个接口,我们可以看它的实现类QueryCursorImpl

public class QueryCursorImpl<T> implements QueryCursorEx<T>, FieldsQueryCursor<T> 

该类继承自Iterable,它的Iterator iter属性用来记录查询所得数据。具体的我会在后面用到时候再讲解。

当你需要在查询执行结束时返回整个对象,存储在缓存中(键和值都缓存),返回最终结果集,那么sqlQuery是很有用的。但是如果你知识需要部分属性或者是一个计算之后的值,那么你可以使用SqlFieldQuery。

2. 基础的SQL Query体验之SqlFieldQuery

2.1 准备工作(与1.1基本类似)

2.2 SqlFieldQuery执行

	public static void main(String[] args) {
		try(Ignite ignite = Ignition.start("examples/config/example-ignite.xml")){
			CacheConfiguration<Long,Company> companyConfig = new CacheConfiguration<Long,Company>("CACHE_ONLY_COMPANY");
			companyConfig.setIndexedTypes(Long.class,Company.class);
			
			
			CacheConfiguration<Long, Person> personConfig = new CacheConfiguration<>("CACHE_ONLY_PERSON");
			personConfig.setIndexedTypes(Long.class, Person.class);
			
			try(IgniteCache<Long, Company> cacheOnlyCompany = ignite.getOrCreateCache(companyConfig);
				IgniteCache<Long, Person> cacheOnlyPerson = ignite.getOrCreateCache(personConfig)){
				//初始化数据
				initData(cacheOnlyCompany,cacheOnlyPerson);
				executeSqlFieldQuery(cacheOnlyPerson);
				
			}
		}
	}
	private static void executeSqlFieldQuery(IgniteCache<Long, Person> cacheOnlyPerson) {
		
		SqlFieldsQuery sqlFieldsQuery = new SqlFieldsQuery("select lastName,salary from Person");
		FieldsQueryCursor<List<?>> result = cacheOnlyPerson.query(sqlFieldsQuery);
		result.getAll().stream().forEach((data) -> {
			System.out.println("start...." );
			data.stream().forEach(System.out::println);
			System.out.println("end...." );
		});
	}

输出日志

[10:52:46] Ignite node started OK (id=2707ffc6)
[10:52:46] Topology snapshot [ver=1, servers=1, clients=0, CPUs=4, heap=0.87GB]
start....
Doe
2000.0
end....
start....
Doe
1000.0
end....
start....
Smith
3000.0
end....
start....
Smith
4000.0
end....
[10:53:05] Ignite node stopped OK [uptime=00:00:18:593]

2.3 总结

通过上述代码以及日志输出,我们可以看到最终的查询输出,并不是一个完整对象,而只是对象的某几个属性而已。这就是SqlFieldQuery的价值所在。下面我会从源码层面解析下SqlFieldQuery。

2.3.1 SqlFieldQuery

在executeSqlFieldQuery方法中,我们首先是根据sql语句创建了一个SqlFieldQuery对象,这个对象是待会提交给IgniteCache#query方法的参数。

SqlFieldQuery是Query抽象类的子类,它与SqlQuery类一样。下面是该类的签名

public class SqlFieldsQuery extends Query<List<?>>

通过签名我们大体可以知道,该类所查询出的最终结果集,应该是以List集合的形式来存储(当然实际也是这样)。

下面我们罗列下该类的构造器

方法参数介绍
SqlFieldsQueryString sql:应该不难理解,即sql语句比较常用的
SqlFieldsQueryString sql:应该不难理解,即sql语句, boolean collocated:源码中的注释标明这是一个以优化为目标的属性,通过简单的样例暂时无法得知其实际作用,且不会影响并置与非并置查询。如果以affinity方式并置了缓存,那么建议开启它,既然说可以优化,那么应该是在Sql做的优化

该类的其他属性都是一些比较好理解的,下面我将常用的方法介绍一下,其他的自己看一下即可,并不难理解

方法参数介绍
setArgs()Object… args:可变参数,一般我们在写sql时候,如果有的值需要指定的话,我们会以?来作为占位符,而这个方法的作用就是为我们填充这些占位符,这个参数的类型是Object类型,因此可以传递所需的任何数值进去比较常用,毕竟业务不可能都是不变的,通过这个方法可以让我们sql更具有通用性
setTimeout()int timeout:超时时间, TimeUnit timeUnit:超时时间的单位在我们希望系统更加的具有可响应性,我们可以用这个方法,做到定时处理
setLocal()boolean loc:布尔值,标识是否执行本地查询设置为true的话,将会值查询本地节点的数据,也就是说在分片模式下,查询的数据可能是不完整的。设置为false的话,标识此次查询可能要做分布式查询
setReplicatedOnlyboolean replicatedOnly:指定该查询是否只包含复制的表。也是一个优化的属性当你已经确定了你所查询的缓存执行的是replicated策略,即复制,那么Ignite可以执行优化,来使得查询只在本地查询即可,提高速度
setDistributedJoinsboolean distributedJoins:布尔值,标识该查询是否启动分布式该属性会影响到我们的查询数据的完整度。假设当前两个服务器节点,那么我们在其中一个节点之上进行跨缓存的查询(其实就类似于Mysql的连表查询),并设置此值为false,那么查询结果只会包含本节点的数据,而不会包含另外一个节点的数据,不过如果只查询一个缓存的数据,也就是说没有表连接操作,那么及时设置了该值为false,Ignite还是会查询所有结果出来。

2.3.2 IgniteCache#query

该方法的截图请参考1.3.3中的代码,是一样的,我们之前讲过是通过条件语句来控制的执行流程,因为我们现在是SqlFieldQuery,所以自然进它的流程中。

PS:需要注意的是,Ignite2.0和Ignite2.2的源码做了很大调整,所以尽量将自己换将升级到最新的版本,否则我们看到的代码不是一样的!!

最终进行功能执行的还是GridQueryProcessor,它是GridKernalContext所持有的组件(GridComponent)之一.

下面我们来看一下GridQueryProcessor的结构:

//GridQueryProcessor 的声明
public class GridQueryProcessor extends GridProcessorAdapter 
//GridProcessorAdapter 的声明
public abstract class GridProcessorAdapter implements GridProcessor
//GridProcessor 的声明
public interface GridProcessor extends GridComponent

通过上述的类声明,我们可以很清楚的知道GridQueryProcessor 的类组织结构。

我们执行该类的query(…)方法,返回值为FieldsQueryCursor<List<?>>类型,它是一个接口,同时还是QueryCursor 的子类。而实现类与SqlQuery所用的类是同一个:QueryCursorImpl,该类为SqlQuery和SqlFieldQuery这两种查询的数据集服务。有什么不懂的就参考1.3.3的内容。

3. 跨缓存查询

3.1 准备工作

数据按照第1小节中的来即可

3.2 跨缓存查询

	public static void main(String[] args) {
		try (Ignite ignite = Ignition.start("examples/config/example-ignite.xml")) {
			CacheConfiguration<Long, Company> companyConfig = new CacheConfiguration<Long, Company>("CACHE_ONLY_COMPANY");
			companyConfig.setIndexedTypes(Long.class, Company.class);

			CacheConfiguration<Long, Person> personConfig = new CacheConfiguration<>("CACHE_ONLY_PERSON");
			personConfig.setIndexedTypes(Long.class, Person.class);
			try (IgniteCache<Long, Company> cacheOnlyCompany = ignite.getOrCreateCache(companyConfig);
					IgniteCache<Long, Person> cacheOnlyPerson = ignite.getOrCreateCache(personConfig)) {
				// 初始化数据
				initData(cacheOnlyCompany, cacheOnlyPerson);
				executeAcrossCacheQuery(cacheOnlyPerson);

			}
		}
	}
	private static void executeAcrossCacheQuery(IgniteCache<Long, Person> cacheOnlyPerson) {
		final String ORG_CACHE = "CACHE_ONLY_COMPANY";
		String sql = "select concat(firstName, ' ', lastName), org.name " +
	            "from Person, \"" + ORG_CACHE + "\".Company as org " +
	            "where Person.companyId = org.id";
		SqlFieldsQuery sqlFieldsQuery = new SqlFieldsQuery(sql);
		sqlFieldsQuery.setDistributedJoins(true);
		FieldsQueryCursor<List<?>> result = cacheOnlyPerson.query(sqlFieldsQuery);
		result.getAll().stream().forEach((data) -> {
			System.out.println("start...." );
			data.stream().forEach(System.out::println);
			System.out.println("end...." );
		});
		
	}

日志输出

[19:08:36] Topology snapshot [ver=2, servers=2, clients=0, CPUs=4, heap=1.7GB]
start....
John Smith
ultrapower
end....
start....
John Doe
yihecloud
end....
start....
Jane Doe
yihecloud
end....
start....
Jane Smith
ultrapower
end....
[19:08:38] Ignite node stopped OK [uptime=00:00:02:088]

3.3 总结

Ignite将缓存cache的名字,做为每一个Table的Scheme,我们在写SQL语句时候,连接其他库的表也是需要带上库名的,相同道理,我们连接其他缓存的表,那么就加上该缓存的Scheme。这也是为什么我们的SQL中无端的加上了一个缓存Cache名字的原因。当然scheme并不是不可以自动以的,我们可以通过CacheConfiguration.setSqlSchema(…)来设置为你喜欢的Scheme.

上述的SQL中我们发现Person并没有加上scheme,因为Person是当前我们所使用的IgniteCache实例所持有的表,因此我们可以不加scheme。

总体来说,与我们SQL语法基本类似,只需要多练习几次即可。

同时,也表明了Ignite是支持跨缓存操作的。我们在测试样例中加了一行sqlFieldsQuery.setDistributedJoins(true);因为我测试时候是启动了两个节点,而person对象数据是以非并置方式放进Ignite缓存中的,如果我不加这一行句子,Ignite则只会查询本地节点的数据,最终的数据是不完整的,而不会执行分布式查询,来获取完整的数据,希望你们可以自己测试一下,感受一下这个属性的魅力。

4. 分布式并置连接查询(Distributed Collocated join query )

第四和第五点分别是分布式查询相关的知识,且比较难懂,我们首先开始分布式并置连接查询

what:什么是分布式并置连接查询呢??

我们在讲解数据网格时候讲了affinity的功能,它即是为我们提供并置功能:将带存储数据与我们的affinityKey相关的数据存储在同一节点上。分布式的意思呢,就是多节点协作查询。假如:我们当前的Ignite集群中的节点使用的是分区策略(这是默认策略),而不是复制或者local策略,那么我们的数据会被存储在集群中的任何一个节点之上,当我们查询数据的时候,单个节点是无法查询出所有的数据的,所以需要多个节点通力合作,这即是我们这里所说的分布式关键字。

分布式并置关联查询:即由多个节点通力合作,查询由affinityKey关联起来的跨缓存的数据出来

4.1 数据准备

	private static void initData(IgniteCache<Long, Company> cacheOnlyCompany, IgniteCache<AffinityKey<Long>,Person> cacheaffinity) {
		//初始化cacheOnlyCompany,这里两个参数分别是名字和id属性
		Company cloud = new Company("yihecloud",1L);
		Company ultra = new Company("ultrapower",11L);
		cacheOnlyCompany.put(cloud.id(), cloud);
		cacheOnlyCompany.put(ultra.id(), ultra);
		//初始化cacheaffinity
        Person p1 = new Person(cloud, "John", "Doe", 2000D, "John Doe has Master Degree.");
        Person p2 = new Person(cloud, "Jane", "Doe", 1000D, "Jane Doe has Bachelor Degree.");
        Person p3 = new Person(ultra, "John", "Smith", 3000D, "John Smith has Bachelor Degree.");
        Person p4 = new Person(ultra, "Jane", "Smith", 4000D, "Jane Smith has Master Degree.");
        //初始化cacheOnlyPerson
        cacheaffinity.put(p1.key(), p1);
        cacheaffinity.put(p2.key(), p2);
        cacheaffinity.put(p3.key(), p3);
        cacheaffinity.put(p4.key(), p4);
	}

4.2 分布式并置关联查询

	public static void main(String[] args) {
		try (Ignite ignite = Ignition.start("examples/config/example-ignite.xml")) {
			CacheConfiguration<Long, Company> companyConfig = new CacheConfiguration<Long, Company>(
					"CACHE_ONLY_COMPANY");
			companyConfig.setIndexedTypes(Long.class, Company.class);

			CacheConfiguration<AffinityKey<Long>, Person> personConfig = new CacheConfiguration<AffinityKey<Long>, Person>(
					"CACHE_AFFINITY_PERSON");
			personConfig.setIndexedTypes(AffinityKey.class, Person.class);

			try (IgniteCache<Long, Company> cacheOnlyCompany = ignite.getOrCreateCache(companyConfig);
					IgniteCache<AffinityKey<Long>, Person> cacheAffinityPerson = ignite
							.getOrCreateCache(personConfig)) {
				// 初始化数据
				initData(cacheOnlyCompany, cacheAffinityPerson);
				distributedCollocatedJoin(cacheAffinityPerson);

			}
		}
	}
	private static void distributedCollocatedJoin(IgniteCache<AffinityKey<Long>, Person> cacheAffinityPerson) {
		final String ORG_CACHE = "CACHE_ONLY_COMPANY";
		String sql = "select concat(firstName, ' ', lastName), org.name " + "from Person, \"" + ORG_CACHE
				+ "\".Company as org " + "where Person.companyId = org.id";
		SqlFieldsQuery sqlFieldsQuery = new SqlFieldsQuery(sql);
		FieldsQueryCursor<List<?>> result = cacheAffinityPerson.query(sqlFieldsQuery);
		result.getAll().stream().forEach((data) -> {
			System.out.println("start....");
			data.stream().forEach(System.out::println);
			System.out.println("end....");
		});
		
		
		QueryCursor<Entry<AffinityKey<Long>, Person>> query = cacheAffinityPerson.query(new SqlQuery<AffinityKey<Long>, Person>(Person.class, "from Person").setLocal(true));
		print("Local all persons:", query);
		IgniteCache<Long, Company> cacheOnlyCompany = Ignition.ignite().cache("CACHE_ONLY_COMPANY");
		QueryCursor<Entry<Long, Company>> query2 = cacheOnlyCompany
				.query(new SqlQuery<Long, Company>(Company.class, "from Company").setLocal(true));
		print("Local all company:", query2);
	}

输出日志

[10:16:44] Topology snapshot [ver=2, servers=2, clients=0, CPUs=4, heap=1.7GB]
start....
John Smith
ultrapower
end....
start....
Jane Smith
ultrapower
end....
start....
John Doe
yihecloud
end....
start....
Jane Doe
yihecloud
end....
------------------黄金分割线----------------------
>>> Local all persons:
>>>     Entry [key=AffinityKey [key=3, affKey=11], val=Person [id=3, companyId=11, lastName=Smith, firstName=John, salary=3000.0, resume=John Smith has Bachelor Degree.]]
>>>     Entry [key=AffinityKey [key=4, affKey=11], val=Person [id=4, companyId=11, lastName=Smith, firstName=Jane, salary=4000.0, resume=Jane Smith has Master Degree.]]

>>> Local all company:
>>>     Entry [key=11, val=Organization [id=11, name=ultrapower93]
[10:16:45] Ignite node stopped OK [uptime=00:00:01:727]

我这里做一个简单的解释。首先我是启动了两个服务器节点,包括测试main方法这里也有服务器节点的启动。初始化数据时候,我们初始化了两个Company和4个关联的Person,Person是要与其相关的Company处于同一节点上的,因为我们使用了affinityKey并置。

在最终日志输出中,我们黄金分割线之下的,我是以本地查询模式,查询main所在的服务器节点上的数据,数据显示本节点只有一个Company以及两个与其相关的Person,完全符合我们的定义。另一个Company和其相关的两个Person处于另外一台服务器节点之上。

然后我们来看我们分割线之上的日志,我们竟然全部查询出来了所有的Person,至于原因,我下面讲解

4.3 总结

4.3.1 原理

这里写图片描述

我们来看上图,本图中有三个服务器节点,分别为Node1~Node3.作为客户端的我们发起调用Q,那么Ignite会将我们的请求Q发送给所有的集群中的节点,我们在图中也可以看到我们的Client有三条Q的交互路线,分别对应每一个服务器节点(Ps:我们这里使用的是默认的分片策略,如果是复制策略就不同啦)。我们的用例中是有关联查询的,即Person表与Company表,而且我们的数据使用的是并置affinity,在每一个节点上的E(Q)操作就是对本地数据集的查询,我们知道Person肯定与其关联的Company是处于同一节点上的,因此必然有数据。当然Ignite默认情况下也是执行本地数据集关联查询,因此Affinity只可以说是一种为了适配于Ignite而做的,以完成我们的关联查询,毕竟在业务中,关联查询是必须的,但是Ignite却默认执行的本地查询,只能以Affinity实现并置,来保证数据的完整度。

在E(Q)执行完之后,将数据集R返回给Client上,让它做map-reduce操作。整个流程结束。

通过affinity实现并置,具有很多优点,因为它执行的是本地数据集的关联查询,所以在速度上快,这也是生产中并置查询占绝大多数的原因。

4.3.2 其他场景(一)

上述的测试场景是以默认的分区模式来模拟的。那么假设我们的Company是分区的,而Person是复制的,那么执行流程是怎么样的??

下面先看一下日志输出

[11:05:07] Topology snapshot [ver=2, servers=2, clients=0, CPUs=4, heap=1.7GB]
start....
John Smith
ultrapower
end....
start....
Jane Smith
ultrapower
end....
start....
John Doe
yihecloud
end....
start....
Jane Doe
yihecloud
end....

>>> Local all persons:
>>>     Entry [key=AffinityKey [key=3, affKey=11], val=Person [id=3, companyId=11, lastName=Smith, firstName=John, salary=3000.0, resume=John Smith has Bachelor Degree.]]
>>>     Entry [key=AffinityKey [key=1, affKey=1], val=Person [id=1, companyId=1, lastName=Doe, firstName=John, salary=2000.0, resume=John Doe has Master Degree.]]
>>>     Entry [key=AffinityKey [key=4, affKey=11], val=Person [id=4, companyId=11, lastName=Smith, firstName=Jane, salary=4000.0, resume=Jane Smith has Master Degree.]]
>>>     Entry [key=AffinityKey [key=2, affKey=1], val=Person [id=2, companyId=1, lastName=Doe, firstName=Jane, salary=1000.0, resume=Jane Doe has Bachelor Degree.]]

>>> Local all company:
>>>     Entry [key=11, val=Organization [id=11, name=ultrapower93]
[11:05:09] Ignite node stopped OK [uptime=00:00:02:194]

通过日志我们可以看出,所有的用户还是被查询出来了,与上述没有差别,本地的节点还是只有一个Company,但是用户却有4个,因为我们使用了复制模式。各位可能会不理解原因。其实与我们上述的图示一致的。

当我们将请求Q发出,其还是将请求路由给所有的集群节点,并回收所有的结果集。我们假设两个节点A和B,B也是我们的main方法启动的。我们通过日志看到B节点上有ultrapoer这个Company,那么也就是说节点A上是有yihecloud这个Company,当请求发出后,我们的A节点可以查出所有与yihecloud相关的用户,毕竟有关联嘛,而节点B则查出所有的ultrapower公司的员工。结果集全部聚集到Client上,我们得到了完整的结果集。

4.3.3 其他场景(二)

我们接着上述的情景,但是我们知道了每个节点上都有Person,只是Company有点分散罢了,我们能不能只在一个节点上查询,但是允许节点间通信,使得我们在一个节点就查询到4个Person??(这里需要指明,当我们设置本地查询时候,也就是setLocal(true),这种情况下,虽然每个节点都有全部的Person数据,但是Company不完整,我们的join操作会过滤掉不存在的Company的Person,也就是说在我们的用例情境下,只会查询出本地节点所拥有的Company下的所有Person)。

在我的一番测试中这是不可以的,当然也许是有方式的,只是本人不知道。但是实现的方法并不是没有,即:将Company的缓存与Person的缓存均采取LOCAL策略,那么我们可以在我们的本地模式下完成功能,但是LOCAL模型下,你的缓存时无法被其他节点发现的,也就是说你在LOCAL模式下产生的数据,其他节点是无法看到的,切记~~

5. 分布式非并置连接查询(Distributed Non-Collocated join query )

我们在上面讲的是关联关系以AffinityKey来保持并置,但是在复杂的领域逻辑下,不得已时候需要组装部分信息,即使他们是不相关的聚合,这时候我们是不可能为这种数据使用AffinityKey的。那么我们需要进行关联的话,就无法像第四节讲的这样随性啦。

PS:Ignite是自1.7版本才开始支持非并置关联查询的,因为我们知道,Ignite默认是在在本地节点执行关联查询的,而不会与其他节点交互的,知道完成关联查询,将数据送还给Client端。

自Ignite1.7之后,Ignite开始支持分布式非并置关联查询,下面让我带你领略一下:
##5.1 数据准备

	private static void initData(IgniteCache<Long, Company> cacheOnlyCompany,
			IgniteCache<Long, Person> cacheOnlyPerson) {
		// 初始化cacheOnlyCompany
		Company cloud = new Company("yihecloud",1L);
		Company ultra = new Company("ultrapower",11L);
		cacheOnlyCompany.put(cloud.id(), cloud);
		cacheOnlyCompany.put(ultra.id(), ultra);
		// 初始化cacheaffinity
		Person p1 = new Person(cloud, "John", "Doe", 2000D, "John Doe has Master Degree.");
		Person p2 = new Person(cloud, "Jane", "Doe", 1000D, "Jane Doe has Bachelor Degree.");
		Person p3 = new Person(ultra, "John", "Smith", 3000D, "John Smith has Bachelor Degree.");
		Person p4 = new Person(ultra, "Jane", "Smith", 4000D, "Jane Smith has Master Degree.");
		// 初始化cacheOnlyPerson
        cacheOnlyPerson.put(p1.id, p1);
        cacheOnlyPerson.put(p2.id, p2);
        cacheOnlyPerson.put(p3.id, p3);
        cacheOnlyPerson.put(p4.id, p4);
	}

测试数据并没有使用Affinity,而是将Company和Person分别缓存

5.2 分布式非并置关联查询

	public static void main(String[] args) {
		try (Ignite ignite = Ignition.start("examples/config/example-ignite.xml")) {
			CacheConfiguration<Long, Company> companyConfig = new CacheConfiguration<Long, Company>(
					"CACHE_ONLY_COMPANY");
			companyConfig.setIndexedTypes(Long.class, Company.class);

			CacheConfiguration<Long, Person> personConfig = new CacheConfiguration<Long, Person>("CACHE_ONLY_PERSON");
			personConfig.setIndexedTypes(Long.class,Person.class);
			try (IgniteCache<Long, Company> cacheOnlyCompany = ignite.getOrCreateCache(companyConfig);
					IgniteCache<Long, Person> cacheOnlyPerson = ignite
							.getOrCreateCache(personConfig)) {
				// 初始化数据
				initData(cacheOnlyCompany, cacheOnlyPerson);
				distributedNonCollocatedJoin(cacheOnlyPerson);
			}
		}
	}
	private static void distributedNonCollocatedJoin(IgniteCache<Long, Person> cacheAffinityPerson) {
		final String ORG_CACHE = "CACHE_ONLY_COMPANY";
		String sql = "select concat(firstName, ' ', lastName), org.name " + "from Person, \"" + ORG_CACHE
				+ "\".Company as org " + "where Person.companyId = org.id";
		SqlFieldsQuery sqlFieldsQuery = new SqlFieldsQuery(sql);
		sqlFieldsQuery.setDistributedJoins(true);
		FieldsQueryCursor<List<?>> result = cacheAffinityPerson.query(sqlFieldsQuery);
		result.getAll().stream().forEach((data) -> {
			System.out.println("start....");
			data.stream().forEach(System.out::println);
			System.out.println("end....");
		});
		
		
		QueryCursor<Entry<AffinityKey<Long>, Person>> query = cacheAffinityPerson.query(new SqlQuery<AffinityKey<Long>, Person>(Person.class, "from Person").setLocal(true));
		print("Local all persons:", query);
		IgniteCache<Long, Company> cacheOnlyCompany = Ignition.ignite().cache("CACHE_ONLY_COMPANY");
		QueryCursor<Entry<Long, Company>> query2 = cacheOnlyCompany
				.query(new SqlQuery<Long, Company>(Company.class, "from Company").setLocal(true));
		print("Local all company:", query2);
	}

日志输出:

[15:28:01] Topology snapshot [ver=2, servers=2, clients=0, CPUs=4, heap=1.7GB]
start....
John Smith
ultrapower
end....
start....
John Doe
yihecloud
end....
start....
Jane Doe
yihecloud
end....
start....
Jane Smith
ultrapower
end....
-------------------黄金分割线------------------------
>>> Local all persons:
>>>     Entry [key=3, val=Person [id=3, companyId=11, lastName=Smith, firstName=John, salary=3000.0, resume=John Smith has Bachelor Degree.]]

>>> Local all company:
>>>     Entry [key=11, val=Organization [id=11, name=ultrapower93]
[15:28:04] Ignite node stopped OK [uptime=00:00:02:192]

通过日志我们可以看出,本地节点上只有一个Company:ultrapower93和一个Person,虽然它们二者是有关的,这个Person属于这个Comnpany,但是这只是巧合罢了。我们还做了禁止非关联分布式的测试,我会在下面讲的

5.3 总结

5.3.1 分布式非关联关联查询

在我们的测试用例代码中的distributedNonCollocatedJoin方法中,我们调用了setDistributedJoins(true),来开启非并置关联查询,如果你想获取完整的数据集,那么这个属性是必须设置的,否则数据将会不完整,下面的5.3.2我会讲到。

先上一张图:
这里写图片描述

我们以这张图来分解分布式非并置关联查询:

客户方Client发起请求Q,因为我们并没有设置本次SQL以Local形式执行,那么Ignite将Q分布式发往所有的服务器集群节点(注意:我们这里是分片模式),每一个节点在其本地执行关联查询,但是我们知道,肯定有数据是无法关联到的,毕竟我们采取的是非并置方式。

我们就以我的测试用例来分解,我测试一共启动两个节点,第一个节点A一直在做while循环空转,而第二个节点B就是我们所看到的main方法启动的节点。我们从日志打印的黄金分割线之下的数据日志可以看到,在我的本地节点上只有一个Company{name:ultrapower,id:11},而且在这个节点上还有一个Person{id:3,companyId:11},那么其他的数据必然是在我们的A节点上。 如果按照Ignite的默认方式,查询只在本地节点上关联的话,那么应该只有三条数据(动脑子想想~~),但是我们的测试数据却是四条,那么也就是说,A节点上的id=4的那个Person执行了上图中的D(Q)操作,与其他节点做了信息的共享,所以它才会关联到B节点上的id=11的Company。

最后,合并数据收工

5.3.1.1 调优

在上述的场景中,为了让ID=4的员工可以查询出来(因为它在A节点,而它所属的Company在B节点上),我们的节点会进行广播。也就是通知所有的节点,配合它完成关联查询,但是这样的效率是很慢的。

String sql = "select concat(firstName, ' ', lastName), org.name " + "from Person, \"" + ORG_CACHE
				+ "\".Company as org " + "where Person.companyId = org._key";

还好的是,Ignite提供了两个关键字,可以帮我们

  • _key
  • _key

他们的功能是可以让我完整的拿到我们做缓存时候的键与值,除了这个有点之外,我们来看上述的sql语句,本来是写org.id的地方,我们却写了org._key,因为我们key就是id嘛,不仅结果是完整的,而且这可以使得Ignite定位到数据所在的节点,然后以单播的形式发往该节点,这样效率提升了一大截,原理其实与采用Affinity存储数据时候是一致的,为什么我们的数据可以存储在其关联数据所在的节点上??回头看数据网格快学吧!!

5.3.2 关闭分布式非并置关联查询

下面的日志,是我们调用setDistributedJoins(false)的情况下发生。

输出日志

[15:36:56] Topology snapshot [ver=2, servers=2, clients=0, CPUs=4, heap=1.7GB]
start....
John Smith
ultrapower
end....
start....
John Doe
yihecloud
end....
start....
Jane Doe
yihecloud
end....
--------------------------黄金分割线------------------------------
>>> Local all persons:
>>>     Entry [key=3, val=Person [id=3, companyId=11, lastName=Smith, firstName=John, salary=3000.0, resume=John Smith has Bachelor Degree.]]

>>> Local all company:
>>>     Entry [key=11, val=Organization [id=11, name=ultrapower93]
[15:36:59] Ignite node stopped OK [uptime=00:00:02:994]

从日志我们可以看到,我们的输出结果是3个。黄金分割线之下,表明我们本地节点上只有Company{name:ultrapower,id:11}和Person{id:3,companyId:11,firstname:John,lastname:Smith},细心的朋友已经知道了,上述结果缺少的就是我们的4号Person -->Jane Smith,它在A节点,但是它是B节点上的Company的员工,但是我们关闭了分布式非并置关联,所以它无法进行关联,导致无法查出来,所以数据也是不完整的。

5.3.3 忠告

因为分布式非关联连接查询的效率慢,而且牵一发而动全身,所以建议不要作为银弹来使用。我建议项目中使用比率不要超过5~10%,可以使用Affinity解决的就并置,解决不了的建议采取冗余字段。、

6. SQL事务

我想在我们的业务中,事务应该算是一个重点。

但是Ignite并不支持事务操作,它是原子性的。不过官网也说了, 一旦Ignite SQL网格使用MVCC进行控制,SQL网格也会支持事务模式。所以我们可以预见的是,未来的版本,事务还是会支持的。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值