@liyuj
2017-06-14T13:49:51.000000Z
字数 13796
阅读 5394
Apache-Ignite-2.0.0-中文开发手册
从1.5版本开始,Ignite引入了一个在缓存中存储数据的新概念,名为二进制对象,这个新的序列化格式提供了若干个优势:
二进制对象只可以用于使用默认的二进制编组器时(即没有在配置中显式地设置其他的编组器)
限制
BinaryObject格式实现也带来了若干个限制:
1. 在内部Ignite不会写属性以及类型的名字,但是使用一个小写的名字哈希来标示一个属性或者类型,这意味着属性或者类型不能有同样的名字哈希。即使序列化不会在哈希冲突的情况下工作,但Ignite在配置级别提供了一种方法来解决此冲突;
2.同样的原因,BinaryObject格式在类的不同层次上也不允许有同样的属性名;
3.默认会忽略Externalizable接口。如果使用了BinaryObject格式,Externalizable类型会与Serializable类型是同样的处理方式,没有writeExternal()和readExternal()方法。如果由于某些原因这样不行,需要实现Binarylizable接口,加入一个自定义BinarySerializer或者切换到OptimizedMarshaller。
IgniteBinary入口,可以从Ignite的实例获得,包含了操作二进制对象的所有必要的方法。
在绝大多数情况下不需要额外地配置二进制对象。
但是,如果需要覆写默认的类型和属性ID计算或者加入BinarySerializer,可以为IgniteConfiguration定义一个BinaryConfiguration对象,这个对象除了为每个类型指定映射以及序列化器之外还可以指定一个全局的Name映射、一个全局ID映射以及一个全局的二进制序列化器。对于每个类型的配置,通配符也是支持的,这时提供的配置会适用于匹配类型名称模板的所有类型。
配置二进制类型:
<bean id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration"><property name="binaryConfiguration"><bean class="org.apache.ignite.configuration.BinaryConfiguration"><property name="nameMapper" ref="globalNameMapper"/><property name="idMapper" ref="globalIdMapper"/><property name="typeConfigurations"><list><bean class="org.apache.ignite.binary.BinaryTypeConfiguration"><property name="typeName" value="org.apache.ignite.examples.*"/><property name="serializer" ref="exampleSerializer"/></bean></list></property></bean></property>...
Ignite默认使用反序列化值作为最常见的使用场景,要启用BinaryObject处理,需要获得一个IgniteCache的实例然后使用withKeepBinary()方法。启用之后,如果可能,这个标志会确保从缓存返回的对象都是BinaryObject格式的。将值传递给EntryProcessor和CacheInterceptor也是同样的处理。
平台类型
注意当通过withKeepBinary()方法启用BinaryObject处理时并不是所有的对象都会表示为BinaryObject,会有一系列的平台类型,包括基本类型,String,UUID,Date,Timestamp,BigDecimal,Collections,Maps和Arrays,他们不会被表示为BinaryObject。
注意在下面的示例中,键类型为Integer,他是不会被修改,因为他是平台类型。
获取BinaryObject:
// Create a regular Person object and put it to the cache.Person person = buildPerson(personId);ignite.cache("myCache").put(personId, person);// Get an instance of binary-enabled cache.IgniteCache<Integer, BinaryObject> binaryCache = ignite.cache("myCache").withKeepBinary();// Get the above person object in the BinaryObject format.BinaryObject binaryPerson = binaryCache.get(personId);
BinaryObject实例是不能修改的,要更新属性或者创建新的BinaryObject,必须使用BinaryObjectBuilder的实例。
BinaryObjectBuilder的实例可以通过IgniteBinary入口获得。他可以使用类型名创建,这时返回的对象不包含任何属性,或者他也可以通过一个已有的BinaryObject创建,这时返回的对象会包含从给定的BinaryObject中拷贝的所有属性。
获取BinaryObjectBuilder实例的另外一个方式是调用已有BinaryObject实例的toBuilder()方法,这种方式创建的对象也会从BinaryObject中拷贝所有的数据。
BinaryObjectBuilder和哈希值
注意如果构造的BinaryObject用做缓存键,很重要的是要为BinaryObjectBuilder设置正确的哈希值,因为构造器不会自动计算哈希值,然后导致返回的BinaryObject哈希值为0。
下面是一个使用BinaryObjectAPI来处理服务端节点的数据而不需要将程序部署到服务端以及不需要实际的数据反序列化的示例:
EntryProcessor内的BinaryObject:
cache.<Integer, BinaryObject>withKeepBinary().invoke(new CacheEntryProcessor<Integer, BinaryObject, Void>() {@Override Void process(MutableEntry<Integer, BinaryObject> entry, Object... args) {// Create builder from the old value.BinaryObjectBuilder bldr = entry.getValue().toBuilder();//Update the field in the builder.bldr.setField("name", "Ignite");// Set new value to the entry.entry.setValue(bldr.build());return null;}});
像上面描述的那样,二进制对象结构可以在运行时进行修改,因此获取一个存储在缓存中的一个特定类型的信息也可能是有用的,比如属性名,属性类型,属性类型名,关系属性名,Ignite通过BinaryType接口满足这样的需求。
这个接口还引入了一个属性getter的更快的版本,叫做BinaryField。这个概念类似于Java的反射,可以缓存BinaryField实例中读取的属性的特定信息,他有助于从一个很大的二进制对象集合中读取同一个属性。
Collection<BinaryObject> persons = getPersons();BinaryField salary = null;double total = 0;int cnt = 0;for (BinaryObject person : persons) {if (salary == null)salary = person.type().field("salary");total += salary.value(person);cnt++;}double avg = total / cnt;
在缓存API上调用withKeepBinary()方法对于将用户对象传入CacheStore的方式不起作用,这么做是故意的,因为大多数情况下单个CacheStore实现要么使用反序列化类,要么使用BinaryObject表示。要控制对象传入Store的方式,需要使用CacheConfiguration的storeKeepBinary标志,当该标志设置为false时,会将反序列化值传入Store,否则会使用BinaryObject表示。
下面是一个使用BinaryObject的Store的伪代码实现的示例:
public class CacheExampleBinaryStore extends CacheStoreAdapter<Integer, BinaryObject> {@IgniteInstanceResourceprivate Ignite ignite;/** {@inheritDoc} */@Override public BinaryObject load(Integer key) {IgniteBinary binary = ignite.binary();List<?> rs = loadRow(key);BinaryObjectBuilder bldr = binary.builder("Person");for (int i = 0; i < rs.size(); i++)bldr.setField(name(i), rs.get(i));return bldr.build();}/** {@inheritDoc} */@Override public void write(Cache.Entry<? extends Integer, ? extends BinaryObject> entry) {BinaryObject obj = entry.getValue();BinaryType type = obj.type();Collection<String> fields = type.fieldNames();List<Object> row = new ArrayList<>(fields.size());for (String fieldName : fields)row.add(obj.field(fieldName));saveRow(entry.getKey(), row);}}
在内部,Ignite不会写属性或者类型名字的完整字符串,而是因为性能的原因,为类型或者属性名写一个整型哈希值作为替代。经过测试,在类型相同时,属性名或者类型名的哈希值冲突实际上是不存在的,为了获得性能,使用哈希值是安全的。对于当不同的类型或者属性确实冲突的场合,BinaryNameMapper和BinaryIdMapper可以为该类型或者属性名覆写自动生成的哈希值。
BinaryNameMapper - 映射类型/类和属性名到不同的名字;
BinaryIdMapper - 映射从BinaryNameMapper来的类型和属性名到ID,以便于Ignite内部使用。
Ignite提供了下面的开箱即用的映射器实现:
BinaryBasicNameMapper:BinaryNameMapper的一个基本实现,对于一个给定的类,根据使用的setSimpleName(boolean useSimpleName)属性值,会返回一个完整或者简单的名字;BinaryBasicIdMapper:BinaryIdMapper的一个基本实现,他有一个setLowerCase(boolean isLowerCase)配置属性,如果属性设置为false,那么会返回一个给定类型或者属性名的哈希值,如果设置为true,会返回一个给定类型或者属性名的小写形式的哈希值。如果仅仅使用Java客户端并且在BinaryConfiguration中没有指定映射器,那么Ignite会使用BinaryBasicNameMapper并且simpleName属性会被设置为false,使用BinaryBasicIdMapper并且lowerCase属性会被设置为true。
如果使用了.Net或者C++客户端并且在BinaryConfiguration中没有指定映射器,那么Ignite会使用BinaryBasicNameMapper并且simpleName属性会被设置为true,使用BinaryBasicIdMapper并且lowerCase属性会被设置为true。
如果使用Java、.Net或者C++,默认是不需要任何配置的,只有当需要平台协同、名字转换复杂的情况下,才需要配置映射器。
从2.0版本开始,Ignite推出了一个新的堆外内存架构。
新的内存架构有如下的好处:
页面内存是一个可管理的基于堆外的的内存架构,它将内存拆分为固定大小的页面,下面看一下架构图,然后理解有关这一架构的更多信息。
内存区
每个Ignite节点的整个页面内存可以由一个或者多个内存区组成,一个内存区是通过内存策略配置的逻辑可扩展区域,这个区域大小可变,退出策略以及其他的参数在下面的内存策略中会详述。
内存块
每个内存区都开始于初始值,然后有一个可增长的最大值。这个区域扩展至其最大值的过程中,都会被分配连续的内存块。内存区的最大值默认为系统可用物理内存的80%。
默认最大值
如果内存区的最大值没有显式地配置(通过org.apache.ignite.configuration.MemoryPolicyConfiguration.setMaxSize()),那么它会使用机器可用RAM的80%。
一个内存块是从操作系统获得的物理连续字节数组,这个块会被分为固定大小的页面,该块中可以驻留若干种不同类型的页面:
数据页面
数据页面存储的是从应用端插入Ignite缓存中的缓存条目(数据页面在上图中标注为绿色)。
通常,一个数据页面持有多个键-值条目,以更高效地利用内存避免内存碎片。当新的键-值条目加入缓存时,页面内存机制会查找适合该条目的页面然后加入里面。但是,当条目的总大小达到通过MemoryConfiguration.setPageSize(..)参数配置的页面大小时,该条目会占据多于一个数据页面。
数据页面对于条目的所有权
一个键值条目不会一直绑定到一个特定的页面,比如在一次更新过程中,条目占用空间变大,然后它的当前页面不再适合它,然后页面内存会查找一个新的数据页面以能够容纳更新后的条目并且将其移动到那里。
B+树和索引页面
应用定义和使用的SQL索引是以B+树数据结构的形式进行维护的。对于一个SQL模式中声明的每个唯一索引,Ignite会实例化并且管理一个专用的B+树实例。
哈希索引
B+树数据结构也会引用缓存条目的键,它们通过哈希值进行排序。
如上图所示,整个B+树的目的就是链接和排序索引页面,这些索引页面是在页面内存的随机物理空间分配和存储的。
从内部来说,索引页面包括了定位索引值、索引指向的缓存条目在数据页面中的偏移量、还有到其他索引页面的引用(用来遍历树)等所有必要的信息,索引页面在上图中标注为紫色。
B+树的元页面需要获得特定B+树的根和它的层次,以高效地执行范围查询。比如,当执行myCache.get(keyA)操作时,在一个节点上它会触发下面的操作流程:
myCache属于那个内存区;myCache的键的B+树的元页面;keyA的哈希值,然后在B+树中检索该键所属的索引页面;myCache中不存在,然后Ignite会返回null作为myCache.get(keyA)操作的返回值;keyA所在的数据页面的所有必要信息;空闲列表元数据和结构
前述章节的执行流程描述的是当应用希望获取缓存时在页面内存中如何检索缓存的条目。但是,当调用像myCache.put(keyA,valueA)这样的操作时,页面内存如何知道将一个新的条目放于何处呢?
在这个场景中,页面内存依赖的是空闲列表数据结构。基本上来说,空闲列表是一个双向链表,它存储了到大致相当于空闲空间的内存页面的引用。比如,有一个空闲列表,它存储了所有的数据页面,它占用了最多75%的空闲空间,还有一个列表来跟踪索引页面,它占用了剩余的25%的容量,数据和索引页面是由独立的空闲列表来跟踪的。
记住,在一个Ignite节点中myCache.put(keyA,valueA)操作的执行流程,对于一个条目来说可能是主,也可能是备节点,大体如下:
myCache所属的内存区;myCache的键的B+树的元页面;keyA的哈希值,然后在B+树中检索该键所属的索引页面;配置参数
要调整全局页面内存的配置,比如页面大小,可以使用org.apache.ignite.configuration.MemoryConfiguration,它可以通过IgniteConfiguration.setMemoryConfiguration(...)方法传入,下面是所有可用的参数:
| 参数 | 描述 | 默认值 |
|---|---|---|
setPageSize(...) |
设置默认页面大小 | 2KB |
setDefaultMemoryPolicySize(...) |
设置自动创建的默认内存区的大小,如果该属性未设置,那么默认的区域会消耗本地主机可用内存的80%。 | 内存的80% |
setDefaultMemoryPolicyName(...) |
设置默认内存策略的名字,每个缓存都会绑定到一个通过本策略实例化的内存区域。 | default |
setMemoryPolicies(...) |
设置集群中可用的所有内存策略的列表。 | 一个空数据,这里不包括创建默认内存区域的配置。 |
setSystemCacheInitialSize(...) |
设置为系统缓存预留的内存区域的初始大小。 | 40MB |
setSystemCacheMaxSize(...) |
设置为系统缓存预留的内存区域的最大值。因为内部数据结构的限制,总大小不应小于10MB。 | 100MB |
setConcurrencyLevel(...) |
设置在Ignite的内部页面映射表中并行段的数量。 | 可用CPU总数的4倍。 |
下面的示例代码显示通过MemoryConfiguration如何改变页面大小和并发层级:
XML:
<bean class="org.apache.ignite.configuration.IgniteConfiguration"><property name="memoryConfiguration"><bean class="org.apache.ignite.configuration.MemoryConfiguration"><!-- Setting the concurrency level --><property name="concurrencyLevel" value="4"/><!-- Setting the page size to 4 KB --><property name="pageSize" value="4096"/></bean></property><!--- Additional settings ----></bean>
Java:
// Ignite configuration.IgniteConfiguration cfg = new IgniteConfiguration();// Page memory configuration.MemoryConfiguration memCfg = new MemoryConfiguration();// Altering the concurrency level.memCfg.setConcurrencyLevel(4);// Changing the page size to 4 KB.memCfg.setPageSize(4096);// Applying the new page memory configuration.cfg.setMemoryConfiguration(memCfg);
页面内存默认会初始化一个单一的可扩展的内存区,这个内存区会使用本地主机最多80%的可用内存。但是,通过各种参数也可以定义多个内存区,然后通过内存策略API也可以对行为进行自定义。
内存策略是一组配置参数的集合,这些参数都是通过org.apache.ignite.configuration.MemoryPolicyConfiguration暴露的,比如初始和最大内存区大小,退出策略,交换文件等等。
比如,要配置一个500MB的内存区,开启数据页面退出,如下所示:
XML:
<bean class="org.apache.ignite.configuration.IgniteConfiguration"><!-- Page memory configuration --><property name="memoryConfiguration"><bean class="org.apache.ignite.configuration.MemoryConfiguration"><!-- Defining a custom memory policy. --><property name="memoryPolicies"><list><!-- 500 MB total size and RANDOM_2_LRU eviction algorithm. --><bean class="org.apache.ignite.configuration.MemoryPolicyConfiguration"><property name="name" value="500MB_Region_Eviction"/><!-- 100 MB initial size. --><property name="initialSize" value="#{100 * 1024 * 1024}"/><!-- 500 MB maximum size. --><property name="maxSize" value="#{500 * 1024 * 1024}"/><!-- Enabling data pages eviction. --><property name="pageEvictionMode" value="RANDOM_2_LRU"/></bean></list></property></bean></property><!-- The rest of the configuration. --><!-- ....... --></bean>
Java:
// Ignite configuration.IgniteConfiguration cfg = new IgniteConfiguration();// Page memory configuration.MemoryConfiguration memCfg = new MemoryConfiguration();// Creating a custom memory policy for a new memory region.MemoryPolicyConfiguration plCfg = new MemoryPolicyConfiguration();// Policy/region name.plCfg.setName("500MB_Region_Eviction");// Setting initial size.plCfg.setInitialSize(100L * 1024 * 1024);// Setting maximum size.plCfg.setMaxSize(500L * 1024 * 1024);// Setting data pages eviction algorithm.plCfg.setPageEvictionMode(DataPageEvictionMode.RANDOM_2_LRU);// Applying the memory policy.memCfg.setMemoryPolicies(plCfg);// Applying the new page memory configuration.cfg.setMemoryConfiguration(memCfg);
一个Ignite缓存可以被映射到该内存区(如下所示),怎么做呢,需要将策略的名字作为参数传递给org.apache.ignite.configuration.CacheConfiguration.setMemoryPolicyName(...)方法:
XML:
<bean class="org.apache.ignite.configuration.IgniteConfiguration"><!-- Page memory and other configuration parameters. --><!-- ....... --><property name="cacheConfiguration"><list><!-- Cache that is mapped to non-default memory region. --><bean class="org.apache.ignite.configuration.CacheConfiguration"><!--Setting a memory policy name to bind to a specific region.--><property name="memoryPolicyName" value="500MB_Region_Eviction"/><!-- Cache unique name. --><property name="name" value="SampleCache"/><!-- Additional cache configuration parameters --></bean></list></property><!-- The rest of the configuration. --><!-- ....... --></bean>
Java:
// Ignite configuration.IgniteConfiguration cfg = new IgniteConfiguration();// Page memory configuration and the rest of the configuration.// ....// Creating a cache configuration.CacheConfiguration cacheCfg = new CacheConfiguration();// Setting a memory policy name to bind to a specific memory region.cacheCfg.setMemoryPolicyName("500MB_Region_Eviction");// Setting the cache name.cacheCfg.setName("SampleCache");// Applying the cache configuration.cfg.setCacheConfiguration(cacheCfg);
当Ignite集群通过这个配置启动时,这个内存页面可以分配从100M~500M的内存区,这个新的内存区会和默认的内存区共存,在该示例中,SampleCache会驻留在该内存区,除非通过上面的方法明确指定,其他的缓存都会被映射到默认的内存区。
如果内存占用超过了参数配置的最大值,那么就会抛出内存溢出异常,要解决这个问题,可以使用下面描述的退出算法,或者将内存区最大值调大。
调整默认内存策略
通过org.apache.ignite.configuration.MemoryConfiguration.createDefaultPolicyConfig()方法会初始化默认的内存区。如果要调整部分参数,可以这样做:
1.通过自定义的名字和参数创建一个新的内存策略;
2.将策略的名字传递给org.apache.ignite.configuration.MemoryConfiguration. setDefaultMemoryPolicyName(...)方法。
这个示例代码显示了在集群中如何配置和使用多个内存区。
配置参数
org.apache.ignite.configuration.MemoryPolicyConfiguration支持如下的参数:
| 参数名 | 描述 | 默认值 |
|---|---|---|
setName(...) |
内存策略的名字 | 必须参数,无默认值 |
setInitialSize(...) |
设置该内存策略定义的内存区的初始大小,当内存使用量达到该值时,会分配一个新的内存块,直到达到最大值。 | 256MB |
setMaxSize(...) |
设置该内存策略定义的内存区的最大值,由于内部数据结构的限制,总大小不能小于10MB。如果内存使用量超过该值,会抛出内存溢出异常,要解决这个问题,可以配置退出算法,或者将该值调大。 | 内存的80% |
setSwapFilePath(...) |
设置该内存策略定义的内存区的内存映射文件路径,配置该参数后,可以为该区域提供基于底层文件系统的交换能力。 | 默认禁用 |
setPageEvictionMode(...) |
设置数据页面使用的退出算法。 | 默认禁用 |
setEvictionThreshold(...) |
内存页面退出的初始阈值,比如,如果阈值配置为0.9,这意味着当页面内存占用了90%的内存区之后,退出就会生效。 | 0.9 |
setEmptyPagesPoolSize(...) |
设置该内存策略的重用列表中的空白页面的最小数量。当键值对的大小超过页面大小的一半时,这个参数会确保Ignite能成功地退出旧的条目。如果缓存中包含很大的条目时,可以调大该参数(池中页面的总大小足够容纳最大的条目)。 | 100 |
setMetricsEnabled(...) |
为该内存区开启内存指标收集。 | false |
内存策略可以为存储键值条目的数据页面配置各种退出模式,详细信息可以参照3.14.退出策略章节。
页面内存是堆外的内存,它是在Java堆之外分配的内存区,然后将数据条目存储在其中。但是,通过将org.apache.ignite.configuration.CacheConfiguration.setOnheapCacheEnabled(...)配置为true为缓存条目开启堆内缓存。
当以二进制形式处理缓存条目或者调用缓存的反序列化时在服务端节点有大量的读操作,堆内缓存对这样的场景非常有用。比如,当一个分布式计算或者部署的服务为下一步处理从缓存中获取一些数据时,就会发生这样的情况。
堆内缓存大小
要管理堆内缓存的大小,避免其不断增长,一定要配置一个可用的基于缓存条目的退出策略。