[关闭]
@liyuj 2018-05-09T09:54:10.000000Z 字数 42733 阅读 19506

Apache-Ignite-2.3.0-中文开发手册

1.基本概念

1.1.Ignite是什么

Ignite是:
1.一个以内存为中心的数据平台
2.可持久化、强一致和高可用
3.强大的SQL、键-值存储及相关的API

1.1.1.固化内存

Ignite的固化内存组件不仅仅将内存作为一个缓存层,还视为一个全功能的存储层。这意味着可以按需将持久化打开或者关闭。如果持久化关闭,那么Ignite就可以作为一个分布式的内存数据库或者内存数据网格,这完全取决于使用SQL和键-值API的喜好。如果持久化打开,那么Ignite就成为一个分布式的,可水平扩展的数据库,它会保证完整的数据一致性以及集群故障的可恢复能力。

1.1.2.Ignite持久化

Ignite的原生持久化是一个分布式的、支持ACID以及兼容SQL的磁盘存储,它可以作为一个可选的磁盘层与Ignite的固化内存透明地集成,然后将数据和索引存储在SSD、闪存、3D XPoint以及其他类型的非易失性存储中。
打开Ignite的持久化之后,就不需要将所有的数据和索引保存在内存中,或者在节点或者集群重启后对数据进行预热,因为固化内存和持久化紧密耦合之后,会将其视为一个二级存储层,这意味着在内存中数据和索引的一个子集如果丢失了,固化内存会从磁盘上进行获取。

1.1.3.ACID兼容

存储在Ignite中的数据,在内存和磁盘上是同时支持ACID的,使Ignite成为一个强一致的系统,Ignite可以在整个网络的多台服务器上保持事务。

1.1.4.完整的SQL支持

Ignite提供了完整的SQL、DDL和DML的支持,可以使用纯SQL而不用写代码与Ignite进行交互,这意味着只使用SQL就可以创建表和索引,以及插入、更新和查询数据。有这个完整的SQL支持,Ignite就可以作为一种分布式SQL数据库

1.1.5.键-值

Ignite的内存数据网格组件是一个完整的事务型分布式键值存储,它可以在有几百台服务器的集群上进行水平扩展。在打开持久化时,Ignite可以存储比内存容量更大的数据,并且在整个集群重启之后仍然可用。

1.1.6.并置处理

大多数传统数据库是以客户机-服务器的模式运行的,这意味着数据必须发给客户端进行处理,这个方式需要在客户端和服务端之间进行大量的数据移动,通常来说不可扩展。而Ignite使用了另外一种方式,可以将轻量级的计算发给数据,即数据的并置计算,从结果上来说,Ignite扩展性更好,并且使数据移动最小化。

1.1.7.可扩展性和持久性

Ignite是一个弹性的、可水平扩展的分布式系统,它支持按需地添加和删除节点,Ignite还可以存储数据的多个副本,这样可以使集群从部分故障中恢复。如果打开了持久化,那么Ignite中存储的数据可以在集群的完全故障中恢复。Ignite集群重启会非常快,因为数据从磁盘上获取,瞬间就具有了可操作性。从结果上来说,数据不需要在处理之前预加载到内存中,而Ignite会缓慢地恢复内存级的性能。

1.2.Ignite定位

Ignite是不是持久化或者纯内存存储?
都是,Ignite的原生持久化可以打开,也可以关闭。这使得Ignite可以存储比可用内存容量更大的数据集。也就是说,可以只在内存中存储较少的操作性数据集,然后将不适合存储在内存中的较大数据集存储在磁盘上,即为了提高性能将内存作为一个缓存层。
Ignite是不是分布式数据库?
,在整个集群的多个节点中,Ignite中的数据要么是分区模式的,要么是复制模式的,这给系统带来了伸缩性,增加了弹性。Ignite可以自动化地控制数据如何分区,然而,开发者也可以插入自定义的函数,以及为了提高效率将部分数据并置在一起。
Ignite是不是关系型SQL数据库?
不完整,尽管Ignite的目标是和其他的关系型SQL数据库具有类似的行为,但是在处理约束和索引方面还是有不同的。Ignite支持一级和二级索引,但是只有一级索引支持唯一性,Ignite还不支持外键约束。
从根本上来说,Ignite作为约束不支持任何会导致集群广播消息的更新以及显著降低系统性能和可伸缩性的操作。
Ignite是不是内存数据库?
,虽然Ignite的固化内存在内存和磁盘中都工作得很好,但是磁盘持久化是可以禁用的,使Ignite作为一个纯粹的内存数据库。
Ignite是不是事务型数据库?
不完整,ACID事务是支持的,但是仅仅在键-值API级别,Ignite还支持跨分区的事务,这意味着事务可以跨越不同服务器不同分区中的键。
在SQL层,Ignite支持原子性,还不是事务型一致性,社区计划在2.2版本中实现SQL事务。
Ignite是不是键-值存储?
,Ignite提供了键-值API,兼容于JCache (JSR-107),并且支持Java,C++和.NET。
Ignite是不是内存数据网格(IMDG)?
,Ignite是一个全功能的数据网格,它既可以用于纯内存模式,也可以带有Ignite的原生持久化,它也可以与任何第三方数据库集成,包括RDBMS和NoSQL。
固化内存是什么?
Ignite的固化内存架构使得Ignite可以将内存计算延伸至磁盘,它基于一个页面化的堆外内存分配器,它通过写前日志(WAL)的持久化来对数据进行固化,当持久化禁用之后,固化内存就会变成一个纯粹的内存存储。
并置处理是什么?
Ignite是一个分布式系统,因此,有能力将数据和数据以及数据和计算进行并置就变得非常重要,这会避免分布式数据噪声。当执行分布式SQL关联时数据的并置就变得非常的重要。Ignite还支持将用户的逻辑(函数,lambda等)直接发到数据所在的节点然后在本地进行数据的运算。

1.3.入门

这部分帮助你开始一个新的Ignite程序,你会发现他瞬间就可以跑起来。

1.3.1.准备

Apache Ignite官方在如下环境中进行的测试:

1.3.2.安装

下面是安装Apache Ignite的简要步骤:

从源代码构建
如果你下载的是源代码包,可以用如下命令构建:

  1. # Unpack the source package
  2. $ unzip -q apache-ignite-{version}-src.zip
  3. $ cd apache-ignite-{version}-src
  4. # Build In-Memory Data Fabric release (without LGPL dependencies)
  5. $ mvn clean package -DskipTests
  6. # Build In-Memory Data Fabric release (with LGPL dependencies)
  7. $ mvn clean package -DskipTests -Prelease,lgpl
  8. # Build In-Memory Hadoop Accelerator release
  9. # (optionally specify version of hadoop to use)
  10. $ mvn clean package -DskipTests -Dignite.edition=hadoop [-Dhadoop.version=X.X.X]

1.3.3.从命令行启动

一个Ignite节点可以从命令行通过默认的配置或者传入外部配置文件的方式启动。可以启动很多很多的节点然后他们会自动地发现对方。
通过默认配置
要启动一个基于默认配置的网格节点,打开命令行然后切换到IGNITE_HOME(安装文件夹),然后输入如下命令:
Linux:

  1. $ bin/ignite.sh

Windows

  1. $ bin/ignite.bat

然后会看到输出大体是如下的样子:

  1. [02:49:12] Ignite node started OK (id=ab5d18a6)
  2. [02:49:12] Topology snapshot [ver=1, nodes=1, CPUs=8, heap=1.0GB]

ignite.sh启动ignite节点会使用默认的配置文件:config/default-config.xml
传递配置文件
要从命令行显式地传递一个配置文件,只需要在安装文件夹路径下输入ignite.sh <配置文件路径>,比如:
Linux

  1. $ bin/ignite.sh examples/config/example-cache.xml

Windows

  1. $ bin/ignite.bat examples/config/example-cache.xml

配置文件的路径既可以是绝对路径,也可以是相对于IGNITE_HOME的相对路径,也可以是相对于类路径的META-INF文件夹。

交互式模式
要在一个交互模式传递配置文件,可以加上-i参数,像这样:ignite.sh -i

1.3.4.从Maven获得

在项目里使用Apache Ignite的另一个方式是使用Maven2依赖管理。
Ignite只需要一个ignite-core强依赖,通常还需要添加ignite-spring,来做基于spring的XML配置,还有ignite-indexing,来做SQL查询。
确保将${ignite-version}替换为实际的版本号。

  1. <dependency>
  2. <groupId>org.apache.ignite</groupId>
  3. <artifactId>ignite-core</artifactId>
  4. <version>${ignite.version}</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.apache.ignite</groupId>
  8. <artifactId>ignite-spring</artifactId>
  9. <version>${ignite.version}</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.apache.ignite</groupId>
  13. <artifactId>ignite-indexing</artifactId>
  14. <version>${ignite.version}</version>
  15. </dependency>

Maven设置
关于如何包含个别的ignite maven模块的更多信息,可以参考1.4.Maven设置章节。

1.3.5.第一个SQL应用

下面会创建两张表及其索引,分别为City表和Person表,分别表示居住在城市中的人,并且城市中会有很多的人,通过WITH子句然后指定affinityKey=city_id,可以将人对象和其居住的城市对象并置在一起。
启动Ignite集群节点后,可以通过下面的语句创建SQL模式:
SQL:

  1. CREATE TABLE City (
  2. id LONG PRIMARY KEY, name VARCHAR)
  3. WITH "template=replicated"
  4. CREATE TABLE Person (
  5. id LONG, name VARCHAR, city_id LONG, PRIMARY KEY (id, city_id))
  6. WITH "backups=1, affinityKey=city_id"
  7. CREATE INDEX idx_city_name ON City (name)
  8. CREATE INDEX idx_person_name ON Person (name)

JDBC:

  1. // Register JDBC driver.
  2. Class.forName("org.apache.ignite.IgniteJdbcThinDriver");
  3. // Open JDBC connection.
  4. Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/");
  5. // Create database tables.
  6. try (Statement stmt = conn.createStatement()) {
  7. // Create table based on REPLICATED template.
  8. stmt.executeUpdate("CREATE TABLE City (" +
  9. " id LONG PRIMARY KEY, name VARCHAR) " +
  10. " WITH \"template=replicated\"");
  11. // Create table based on PARTITIONED template with one backup.
  12. stmt.executeUpdate("CREATE TABLE Person (" +
  13. " id LONG, name VARCHAR, city_id LONG, " +
  14. " PRIMARY KEY (id, city_id)) " +
  15. " WITH \"backups=1, affinityKey=city_id\"");
  16. // Create an index on the City table.
  17. stmt.executeUpdate("CREATE INDEX idx_city_name ON City (name)");
  18. // Create an index on the Person table.
  19. stmt.executeUpdate("CREATE INDEX idx_person_name ON Person (name)");
  20. }

ODBC:

  1. SQLHSTMT stmt;
  2. // Allocate a statement handle.
  3. SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
  4. // Create table based on REPLICATED template.
  5. SQLCHAR query1[] = "CREATE TABLE City ("
  6. "id LONG PRIMARY KEY, name VARCHAR) "
  7. "WITH \"template=replicated\"";
  8. SQLSMALLINT queryLen1 = static_cast<SQLSMALLINT>(sizeof(query1));
  9. SQLExecDirect(stmt, query, queryLen);
  10. // Create table based on PARTITIONED template with one backup.
  11. SQLCHAR query2[] = "CREATE TABLE Person ( "
  12. "id LONG, name VARCHAR, city_id LONG "
  13. "PRIMARY KEY (id, city_id)) "
  14. "WITH \"backups=1, affinityKey=city_id\"";
  15. SQLSMALLINT queryLen2 = static_cast<SQLSMALLINT>(sizeof(query2));
  16. SQLExecDirect(stmt, query, queryLen);
  17. // Create an index on the City table.
  18. SQLCHAR query3[] = "CREATE INDEX idx_city_name ON City (name)";
  19. SQLSMALLINT queryLen3 = static_cast<SQLSMALLINT>(sizeof(query3));
  20. SQLRETURN ret = SQLExecDirect(stmt, query3, queryLen3);
  21. // Create an index on the Person table.
  22. SQLCHAR query4[] = "CREATE INDEX idx_person_name ON Person (name)";
  23. SQLSMALLINT queryLen4 = static_cast<SQLSMALLINT>(sizeof(query4));
  24. ret = SQLExecDirect(stmt, query4, queryLen4);

下一步,需要往两个表中注入一些数据,比如:
SQL:

  1. INSERT INTO City (id, name) VALUES (1, 'Forest Hill');
  2. INSERT INTO City (id, name) VALUES (2, 'Denver');
  3. INSERT INTO City (id, name) VALUES (3, 'St. Petersburg');
  4. INSERT INTO Person (id, name, city_id) VALUES (1, 'John Doe', 3);
  5. INSERT INTO Person (id, name, city_id) VALUES (2, 'Jane Roe', 2);
  6. INSERT INTO Person (id, name, city_id) VALUES (3, 'Mary Major', 1);
  7. INSERT INTO Person (id, name, city_id) VALUES (4, 'Richard Miles', 2);

JDBC:

  1. // Register JDBC driver
  2. Class.forName("org.apache.ignite.IgniteJdbcThinDriver");
  3. // Open JDBC connection
  4. Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/");
  5. // Populate City table
  6. try (PreparedStatement stmt =
  7. conn.prepareStatement("INSERT INTO City (id, name) VALUES (?, ?)")) {
  8. stmt.setLong(1, 1L);
  9. stmt.setString(2, "Forest Hill");
  10. stmt.executeUpdate();
  11. stmt.setLong(1, 2L);
  12. stmt.setString(2, "Denver");
  13. stmt.executeUpdate();
  14. stmt.setLong(1, 3L);
  15. stmt.setString(2, "St. Petersburg");
  16. stmt.executeUpdate();
  17. }
  18. // Populate Person table
  19. try (PreparedStatement stmt =
  20. conn.prepareStatement("INSERT INTO Person (id, name, city_id) VALUES (?, ?, ?)")) {
  21. stmt.setLong(1, 1L);
  22. stmt.setString(2, "John Doe");
  23. stmt.setLong(3, 3L);
  24. stmt.executeUpdate();
  25. stmt.setLong(1, 2L);
  26. stmt.setString(2, "Jane Roe");
  27. stmt.setLong(3, 2L);
  28. stmt.executeUpdate();
  29. stmt.setLong(1, 3L);
  30. stmt.setString(2, "Mary Major");
  31. stmt.setLong(3, 1L);
  32. stmt.executeUpdate();
  33. stmt.setLong(1, 4L);
  34. stmt.setString(2, "Richard Miles");
  35. stmt.setLong(3, 2L);
  36. stmt.executeUpdate();
  37. }

ODBC:

  1. SQLHSTMT stmt;
  2. // Allocate a statement handle.
  3. SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
  4. // Populate City table.
  5. SQLCHAR query1[] = "INSERT INTO City (id, name) VALUES (?, ?)";
  6. SQLRETURN ret = SQLPrepare(stmt, query1, static_cast<SQLSMALLINT>(sizeof(query1)));
  7. char name[1024];
  8. int32_t key = 1;
  9. strncpy(name, "Forest Hill", sizeof(name));
  10. ret = SQLExecute(stmt);
  11. key = 2;
  12. strncpy(name, "Denver", sizeof(name));
  13. ret = SQLExecute(stmt);
  14. key = 3;
  15. strncpy(name, "Denver", sizeof(name));
  16. ret = SQLExecute(stmt);
  17. // Populate Person table
  18. SQLCHAR query2[] = "INSERT INTO Person (id, name, city_id) VALUES (?, ?, ?)";
  19. ret = SQLPrepare(stmt, query2, static_cast<SQLSMALLINT>(sizeof(query2)));
  20. key = 1;
  21. strncpy(name, "John Doe", sizeof(name));
  22. int32_t city_id = 3;
  23. ret = SQLExecute(stmt);
  24. key = 2;
  25. strncpy(name, "Jane Roe", sizeof(name));
  26. city_id = 2;
  27. ret = SQLExecute(stmt);
  28. key = 3;
  29. strncpy(name, "Mary Major", sizeof(name));
  30. city_id = 1;
  31. ret = SQLExecute(stmt);
  32. key = 4;
  33. strncpy(name, "Richard Miles", sizeof(name));
  34. city_id = 2;
  35. ret = SQLExecute(stmt);

Java API:

  1. // Connecting to the cluster.
  2. Ignite ignite = Ignition.start();
  3. // Getting a reference to an underlying cache created for City table above.
  4. IgniteCache<Long, City> cityCache = ignite.cache("SQL_PUBLIC_CITY");
  5. // Getting a reference to an underlying cache created for Person table above.
  6. IgniteCache<PersonKey, Person> personCache = ignite.cache("SQL_PUBLIC_PERSON");
  7. // Inserting entries into City.
  8. SqlFieldsQuery query = new SqlFieldsQuery(
  9. "INSERT INTO City (id, name) VALUES (?, ?)");
  10. cityCache.query(query.setArgs(1, "Forest Hill")).getAll();
  11. cityCache.query(query.setArgs(2, "Denver")).getAll();
  12. cityCache.query(query.setArgs(3, "St. Petersburg")).getAll();
  13. // Inserting entries into Person.
  14. query = new SqlFieldsQuery(
  15. "INSERT INTO Person (id, name, city_id) VALUES (?, ?, ?)");
  16. personCache.query(query.setArgs(1, "John Doe", 3)).getAll();
  17. personCache.query(query.setArgs(2, "Jane Roe", 2)).getAll();
  18. personCache.query(query.setArgs(3, "Mary Major", 1)).getAll();
  19. personCache.query(query.setArgs(4, "Richard Miles", 2)).getAll();

下面就可以查询数据了,可以查询人及其居住的城市,这会进行两个表的关联:
SQL:

  1. SELECT p.name, c.name
  2. FROM Person p, City c
  3. WHERE p.city_id = c.id

JDBC:

  1. // Register JDBC driver
  2. Class.forName("org.apache.ignite.IgniteJdbcThinDriver");
  3. // Open JDBC connection
  4. Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1/");
  5. // Get data
  6. try (Statement stmt = conn.createStatement()) {
  7. try (ResultSet rs =
  8. stmt.executeQuery("SELECT p.name, c.name " +
  9. " FROM Person p, City c " +
  10. " WHERE p.city_id = c.id")) {
  11. while (rs.next())
  12. System.out.println(rs.getString(1) + ", " + rs.getString(2));
  13. }
  14. }

ODBC:

  1. SQLHSTMT stmt;
  2. // Allocate a statement handle
  3. SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);
  4. // Get data using an SQL join sample.
  5. SQLCHAR query[] = "SELECT p.name, c.name "
  6. "FROM Person p, City c "
  7. "WHERE p.city_id = c.id";
  8. SQLSMALLINT queryLen = static_cast<SQLSMALLINT>(sizeof(query));
  9. SQLRETURN ret = SQLExecDirect(stmt, query, queryLen);

Java API:

  1. // Connecting to the cluster.
  2. Ignite ignite = Ignition.start();
  3. // Getting a reference to an underlying cache created for City table above.
  4. IgniteCache<Long, City> cityCache = ignite.cache("SQL_PUBLIC_CITY");
  5. // Querying data from the cluster using a distributed JOIN.
  6. SqlFieldsQuery query = new SqlFieldsQuery("SELECT p.name, c.name " +
  7. " FROM Person p, City c WHERE p.city_id = c.id");
  8. FieldsQueryCursor<List<?>> cursor = cityCache.query(query);
  9. Iterator<List<?>> iterator = cursor.iterator();
  10. while (iterator.hasNext()) {
  11. List<?> row = iterator.next();
  12. System.out.println(row.get(0) + ", " + row.get(1));
  13. }

这会产生如下的输出:

  1. Mary Major, Forest Hill
  2. Jane Roe, Denver
  3. Richard Miles, Denver
  4. John Doe, St. Petersburg

1.3.6.第一个计算应用

作为第一个计算应用,它会计算一句话中非空白字符的字符数量。作为一个示例,首先将一句话分割为多个单词,然后通过计算作业来计算每一个独立单词中的字符数量。最后,我们将从每个作业获得的结果简单相加来获得整个的数量。
Java8:

  1. try (Ignite ignite = Ignition.start("examples/config/example-ignite.xml")) {
  2. Collection<IgniteCallable<Integer>> calls = new ArrayList<>();
  3. // Iterate through all the words in the sentence and create Callable jobs.
  4. for (final String word : "Count characters using callable".split(" "))
  5. calls.add(word::length);
  6. // Execute collection of Callables on the grid.
  7. Collection<Integer> res = ignite.compute().call(calls);
  8. // Add up all the results.
  9. int sum = res.stream().mapToInt(Integer::intValue).sum();
  10. System.out.println("Total number of characters is '" + sum + "'.");
  11. }

Java7:

  1. try (Ignite ignite = Ignition.start("examples/config/example-ignite.xml")) {
  2. Collection<IgniteCallable<Integer>> calls = new ArrayList<>();
  3. // Iterate through all the words in the sentence and create Callable jobs.
  4. for (final String word : "Count characters using callable".split(" ")) {
  5. calls.add(new IgniteCallable<Integer>() {
  6. @Override public Integer call() throws Exception {
  7. return word.length();
  8. }
  9. });
  10. }
  11. // Execute collection of Callables on the grid.
  12. Collection<Integer> res = ignite.compute().call(calls);
  13. int sum = 0;
  14. // Add up individual word lengths received from remote nodes.
  15. for (int len : res)
  16. sum += len;
  17. System.out.println(">>> Total number of characters in the phrase is '" + sum + "'.");
  18. }

零部署
注意,由于Ignite的零部署特性,当从IDE运行上面的程序时,远程节点没有经过显式地部署,就获得了计算作业。

另一个例子,创建一个应用,它会读取第一个SQL应用中保存的数据,然后在这些对象上进行一些额外的处理。
下面会创建一个天气警报应用,假定丹佛有一个天气警报,然后需要提醒丹佛的居民为恶劣天气做好准备。
下面是代码片段:

  1. Ignite ignite = Ignition.start();
  2. long cityId = 2; // Id for Denver
  3. // Sending the logic to a cluster node that stores Denver and its residents.
  4. ignite.compute().affinityRun("SQL_PUBLIC_CITY", cityId, new IgniteRunnable() {
  5. @IgniteInstanceResource
  6. Ignite ignite;
  7. @Override
  8. public void run() {
  9. // Getting an access to Persons cache.
  10. IgniteCache<BinaryObject, BinaryObject> people = ignite.cache(
  11. "Person").withKeepBinary();
  12. ScanQuery<BinaryObject, BinaryObject> query =
  13. new ScanQuery <BinaryObject, BinaryObject>();
  14. try (QueryCursor<Cache.Entry<BinaryObject, BinaryObject>> cursor =
  15. people.query(query)) {
  16. // Iteration over the local cluster node data using the scan query.
  17. for (Cache.Entry<BinaryObject, BinaryObject> entry : cursor) {
  18. BinaryObject personKey = entry.getKey();
  19. // Picking Denver residents only only.
  20. if (personKey.<Long>field("CITY_ID") == cityId) {
  21. person = entry.getValue();
  22. // Sending the warning message to the person.
  23. }
  24. }
  25. }
  26. }
  27. }

在上例中使用了affinityRun()方法,并且指定了SQL_PUBLIC_CITY缓存,cityId以及一个新创建的IgniteRunnable(),这样确保了计算被发送到丹佛及其居民所在的节点,使得可以直接在数据所在的地方执行业务逻辑,避免了昂贵的序列化可网络开销。

1.3.7.第一个数据网格应用

我们再来一个小例子,它从/往分布式缓存中获取/添加数据,并且执行基本的事务。
因为在应用中使用了缓存,要确保他是经过配置的,我们可以用Ignite自带的示例配置,他已经做了一些缓存的配置。

  1. $ bin/ignite.sh examples/config/example-cache.xml

Put和Get:

  1. try (Ignite ignite = Ignition.start("examples/config/example-ignite.xml")) {
  2. IgniteCache<Integer, String> cache = ignite.getOrCreateCache("myCacheName");
  3. // Store keys in cache (values will end up on different cache nodes).
  4. for (int i = 0; i < 10; i++)
  5. cache.put(i, Integer.toString(i));
  6. for (int i = 0; i < 10; i++)
  7. System.out.println("Got [key=" + i + ", val=" + cache.get(i) + ']');
  8. }

原子化操作:

  1. // Put-if-absent which returns previous value.
  2. Integer oldVal = cache.getAndPutIfAbsent("Hello", 11);
  3. // Put-if-absent which returns boolean success flag.
  4. boolean success = cache.putIfAbsent("World", 22);
  5. // Replace-if-exists operation (opposite of getAndPutIfAbsent), returns previous value.
  6. oldVal = cache.getAndReplace("Hello", 11);
  7. // Replace-if-exists operation (opposite of putIfAbsent), returns boolean success flag.
  8. success = cache.replace("World", 22);
  9. // Replace-if-matches operation.
  10. success = cache.replace("World", 2, 22);
  11. // Remove-if-matches operation.
  12. success = cache.remove("Hello", 1);

事务:

  1. try (Transaction tx = ignite.transactions().txStart()) {
  2. Integer hello = cache.get("Hello");
  3. if (hello == 1)
  4. cache.put("Hello", 11);
  5. cache.put("World", 22);
  6. tx.commit();
  7. }

分布式锁:

  1. // Lock cache key "Hello".
  2. Lock lock = cache.lock("Hello");
  3. lock.lock();
  4. try {
  5. cache.put("Hello", 11);
  6. cache.put("World", 22);
  7. }
  8. finally {
  9. lock.unlock();
  10. }

1.3.8.第一个服务网格应用

Ignite的服务网格对于在集群中部署微服务非常有用,Ignite会处理和部署的服务有关的任务的生命周期,并且提供了在应用中调用服务的简单方式。
作为一个示例,下面会开发一个服务,它会返回一个特定城市当前的天气预报。首先,它会创建一个只有一个方法的服务接口,这个接口扩展自org.apache.ignite.services.Service

  1. import org.apache.ignite.services.Service;
  2. public interface WeatherService extends Service {
  3. /**
  4. * Get a current temperature for a specific city in the world.
  5. *
  6. * @param countryCode Country code (ISO 3166 country codes).
  7. * @param cityName City name.
  8. * @return Current temperature in the city in JSON format.
  9. * @throws Exception if an exception happened.
  10. */
  11. String getCurrentTemperature(String countryCode, String cityName)
  12. throws Exception;
  13. }

服务的实现会接入天气频道然后获取天气数据,代码如下:

  1. import java.io.BufferedReader;
  2. import java.io.InputStreamReader;
  3. import java.net.HttpURLConnection;
  4. import java.net.URL;
  5. import org.apache.ignite.services.ServiceContext;
  6. public class WeatherServiceImpl implements WeatherService {
  7. /** Weather service URL. */
  8. private static final String WEATHER_URL = "http://samples.openweathermap.org/data/2.5/weather?";
  9. /** Sample app ID. */
  10. private static final String appId = "ca7345b4a1ef8c037f7749c09fcbf808";
  11. /** {@inheritDoc}. */
  12. @Override public void init(ServiceContext ctx) throws Exception {
  13. System.out.println("Weather Service is initialized!");
  14. }
  15. /** {@inheritDoc}. */
  16. @Override public void execute(ServiceContext ctx) throws Exception {
  17. System.out.println("Weather Service is started!");
  18. }
  19. /** {@inheritDoc}. */
  20. @Override public void cancel(ServiceContext ctx) {
  21. System.out.println("Weather Service is stopped!");
  22. }
  23. /** {@inheritDoc}. */
  24. @Override public String getCurrentTemperature(String cityName,
  25. String countryCode) throws Exception {
  26. System.out.println(">>> Requested weather forecast [city="
  27. + cityName + ", countryCode=" + countryCode + "]");
  28. String connStr = WEATHER_URL + "q=" + cityName + ","
  29. + countryCode + "&appid=" + appId;
  30. URL url = new URL(connStr);
  31. HttpURLConnection conn = null;
  32. try {
  33. // Connecting to the weather service.
  34. conn = (HttpURLConnection) url.openConnection();
  35. conn.setRequestMethod("GET");
  36. conn.connect();
  37. // Read data from the weather server.
  38. try (BufferedReader reader = new BufferedReader(
  39. new InputStreamReader(conn.getInputStream()))) {
  40. String line;
  41. StringBuilder builder = new StringBuilder();
  42. while ((line = reader.readLine()) != null)
  43. builder.append(line);
  44. return builder.toString();
  45. }
  46. } finally {
  47. if (conn != null)
  48. conn.disconnect();
  49. }
  50. }
  51. }

最后,服务需要在集群中进行部署,然后就可以在应用端进行调用,为了简化,服务在同一个应用中进行部署和调用,如下:

  1. import org.apache.ignite.Ignite;
  2. import org.apache.ignite.Ignition;
  3. public class ServiceGridExample {
  4. public static void main(String[] args) throws Exception {
  5. try (Ignite ignite = Ignition.start()) {
  6. // Deploying a single instance of the Weather Service
  7. // in the whole cluster.
  8. ignite.services().deployClusterSingleton("WeatherService",
  9. new WeatherServiceImpl());
  10. // Requesting current weather for London.
  11. WeatherService service = ignite.services().service("WeatherService");
  12. String forecast = service.getCurrentTemperature("London", "UK");
  13. System.out.println("Weather forecast in London:" + forecast);
  14. }
  15. }
  16. }

零部署和服务网格
零部署是不支持服务网格的,如果希望将上面的服务部署在通过ignite.sh或者ignite.bat文件启动的节点上,那么就需要将服务的实现打成jar包然后放在{apache_ignite_version}/libs文件夹中。

1.4.Maven配置

1.4.1.摘要

如果项目里用Maven管理依赖,可以单独地导入各个Ignite模块,

注意,在下面的例子中,要将${ignite.version}替换为实际的版本。

1.4.2.常规依赖

Ignite强依赖于ignite-core.jar

  1. <dependency>
  2. <groupId>org.apache.ignite</groupId>
  3. <artifactId>ignite-core</artifactId>
  4. <version>${ignite.version}</version>
  5. </dependency>

然而,很多时候需要其他更多的依赖,比如,要使用Spring配置或者SQL查询等。
下面就是最常用的可选模块:

  1. <dependency>
  2. <groupId>org.apache.ignite</groupId>
  3. <artifactId>ignite-core</artifactId>
  4. <version>${ignite.version}</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.apache.ignite</groupId>
  8. <artifactId>ignite-spring</artifactId>
  9. <version>${ignite.version}</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.apache.ignite</groupId>
  13. <artifactId>ignite-indexing</artifactId>
  14. <version>${ignite.version}</version>
  15. </dependency>

1.4.3.导入独立模块

可以一个个地导入Ignite模块,唯一必须的就是ignite-core,其他的都是可选的,所有可选模块都可以像核心模块一样导入,只是构件Id不同。
现在提供如下模块:

构件版本
注意,导入若干Ignite模块时,他们的版本号应该相同,比如,如果使用ignite-core1.8,所有其他的模块也必须导入1.8版本。

1.4.4.LGPL依赖

下面的Ignite模块有LGPL依赖,因此无法部署到Maven中央仓库:

要使用这些模块,需要手工从源代码进行构建然后加入自己的项目,比如,要将ignite-hibernate安装到本地库,可以在Ignite的源代码包中运行如下的命令:

  1. mvn clean install -DskipTests -Plgpl -pl modules/hibernate -am

第三方仓库
GridGain提供自己的Maven仓库,包含了Apache Ignite的LGPL构件,比如ignite-hibernate
注意位于GridGain的Maven库中的构件仅仅为了方便使用,并不是官方的Apache Ignite构件。

1.5.Ignite生命周期

1.5.1.摘要

Ignite是基于JVM的,一个JVM可以运行一个或者多个逻辑Ignite节点(大多数情况下,一个JVM运行一个Ignite节点)。在整个Ignite文档中,会交替地使用术语Ignite运行时以及Ignite节点,比如说可以该主机运行5个节点,技术上通常意味着主机上启动5个JVM,每个JVM运行一个节点,Ignite也支持一个JVM运行多个节点,事实上,通常作为Ignite内部测试用。

Ignite运行时 == JVM进程 == Ignite节点(多数情况下)

1.5.2.Ignition类

Ignition类在网络中启动各个Ignite节点,注意一台物理服务器(网络中的一台计算机)可以运行多个Ignite节点。
下面的代码是在全默认配置下在本地启动网格节点;

  1. Ignite ignite = Ignition.start();

或者传入一个配置文件:

  1. Ignite ignite = Ignition.start("examples/config/example-cache.xml");

配置文件的路径既可以是绝对路径,也可以是相对于IGNITE_HOME的相对路径,也可以是相对于类路径的META-INF文件夹。

1.5.3.LifecycleBean

有时可能希望在Ignite节点启动和停止的之前和之后执行特定的操作,这个可以通过实现LifecycleBean接口实现,然后在spring的配置文件中通过指定IgniteConfigurationlifecycleBeans属性实现。

  1. <bean class="org.apache.ignite.IgniteConfiguration">
  2. ...
  3. <property name="lifecycleBeans">
  4. <list>
  5. <bean class="com.mycompany.MyLifecycleBean"/>
  6. </list>
  7. </property>
  8. ...
  9. </bean>

LifecycleBean也可以像下面这样通过编程的方式实现:

  1. // Create new configuration.
  2. IgniteConfiguration cfg = new IgniteConfiguration();
  3. // Provide lifecycle bean to configuration.
  4. cfg.setLifecycleBeans(new MyLifecycleBean());
  5. // Start Ignite node with given configuration.
  6. Ignite ignite = Ignition.start(cfg)

一个LifecycleBean的实现可能如下所示:

  1. public class MyLifecycleBean implements LifecycleBean {
  2. @Override public void onLifecycleEvent(LifecycleEventType evt) {
  3. if (evt == LifecycleEventType.BEFORE_NODE_START) {
  4. // Do something.
  5. ...
  6. }
  7. }
  8. }

也可以将Ignite实例以及其他有用的资源注入LifecycleBean实现,查看1.8.资源注入章节可以了解更多的信息。

1.5.4.生命周期事件类型

当前支持如下生命周期事件类型:

1.6.异步支持

1.6.1.摘要

Ignite的多数API即可以支持同步模式,也可以支持异步模式,异步方法后面追加了Async后缀。

  1. // Synchronous get
  2. V get(K key);
  3. // Asynchronous get
  4. IgniteFuture<V> getAsync(K key);

异步操作返回的是一个IgniteFuture或其子类的实例,通过如下方式可以获得异步操作的结果,或者调用阻塞的IgniteFuture.get()方法,或者通过IgniteFuture.listen()方法或者IgniteFuture.chain()方法注册一个闭包,然后等待当操作完成后调用闭包。

1.6.2.支持的接口

下面列出的接口可以用于同步或者异步模式:

1.6.3.监听器和Future链

要在非阻塞模式下等待异步操作的结果(IgniteFuture.get()),可以使用IgniteFuture.listen()方法或者IgniteFuture.chain()方法注册一个闭包,当操作完成后,闭包会被调用,比如:

  1. IgniteCompute compute = ignite.compute();
  2. // Execute a closure asynchronously.
  3. IgniteFuture<String> fut = compute.callAsync(() -> {
  4. return "Hello World";
  5. });
  6. // Listen for completion and print out the result.
  7. fut.listen(f -> System.out.println("Job result: " + f.get()));

闭包执行和线程池
异步操作完成后,如果通过IgniteFuture.listen()或者IgniteFuture.chain()方法传递了闭包,那么闭包就会被调用线程以同步的方式执行,否则,闭包就会随着操作的完成异步地执行。
根据操作的类型,闭包可能被系统线程池中的线程调用(异步缓存操作),或者被公共线程池中的线程调用(异步计算操作)。因此需要避免在闭包实现中调用同步的缓存和计算操作,否则可能导致死锁。
要实现Ignite计算操作异步嵌套执行,可以使用自定义线程池,相关内容可以查看1.9.线程池中的相关内容。

1.7.客户端和服务端

1.7.1.摘要

Ignite有一个可选的概念,就是客户端节点服务端节点,服务端节点参与缓存、计算执行、流式处理等等,而原生的客户端节点提供了远程连接服务端的能力。Ignite原生客户端可以使用完整的Ignite API集合,包括近缓存、事务、计算、流、服务等等。
所有的Ignite节点默认都是以服务端模式启动的,客户端模式需要显式地启用。

1.7.2.配置客户端和服务端

可以通过IgniteConfiguration.setClientMode(...)属性配置一个节点,或者为客户端,或者为服务端。
XML:

  1. <bean class="org.apache.ignite.configuration.IgniteConfiguration">
  2. ...
  3. <!-- Enable client mode. -->
  4. <property name="clientMode" value="true"/>
  5. ...
  6. </bean>

Java:

  1. IgniteConfiguration cfg = new IgniteConfiguration();
  2. // Enable client mode.
  3. cfg.setClientMode(true);
  4. // Start Ignite in client mode.
  5. Ignite ignite = Ignition.start(cfg);

方便起见,也可以通过Ignition类来打开或者关闭客户端模式作为替代,这样可以使客户端和服务端共用一套配置。

  1. Ignition.setClientMode(true);
  2. // Start Ignite in client mode.
  3. Ignite ignite = Ignition.start();

1.7.3.创建分布式缓存

当在Ignite中创建缓存时,不管是通过XML方式,还是通过Ignite.createCache(...)或者Ignite.getOrCreateCache(...)方法,Ignite会自动地在所有的服务端节点中部署分布式缓存。

当分布式缓存创建之后,他会自动地部署在所有的已有或者未来的服务端节点上。

  1. // Enable client mode locally.
  2. Ignition.setClientMode(true);
  3. // Start Ignite in client mode.
  4. Ignite ignite = Ignition.start();
  5. CacheConfiguration cfg = new CacheConfiguration("myCache");
  6. // Set required cache configuration properties.
  7. ...
  8. // Create cache on all the existing and future server nodes.
  9. // Note that since the local node is a client, it will not
  10. // be caching any data.
  11. IgniteCache<?, ?> cache = ignite.getOrCreateCache(cfg);

1.7.4.客户端或者服务端计算

IgniteCompute默认会在所有的服务端节点上执行作业,然而,也可以通过创建相应的集群组来选择是只在服务端节点还是只在客户端节点上执行作业。
服务端节点执行:

  1. IgniteCompute compute = ignite.compute();
  2. // Execute computation on the server nodes (default behavior).
  3. compute.broadcast(() -> System.out.println("Hello Server"));

客户端节点执行:

  1. ClusterGroup clientGroup = ignite.cluster().forClients();
  2. IgniteCompute clientCompute = ignite.compute(clientGroup);
  3. // Execute computation on the client nodes.
  4. clientCompute.broadcast(() -> System.out.println("Hello Client"));

1.7.5.管理慢客户端

很多部署环境中,客户端节点是在主集群外启动的,机器和网络都比较差,在这些场景中服务端可能产生负载(比如持续查询通知)而客户端没有能力处理,导致服务端的输出消息队列不断增长,这可能最终导致服务端出现内存溢出的情况,或者如果打开背压控制时导致整个集群阻塞。
要管理这样的状况,可以配置允许向客户端节点输出消息的最大值,如果输出队列的大小超过配置的值,该客户端节点会从集群断开以防止拖慢整个集群。
下面的例子显示了如何通过XML或者编程的方式配置慢客户端队列限值:
Java:

  1. IgniteConfiguration cfg = new IgniteConfiguration();
  2. // Configure Ignite here.
  3. TcpCommunicationSpi commSpi = new TcpCommunicationSpi();
  4. commSpi.setSlowClientQueueLimit(1000);
  5. cfg.setCommunicationSpi(commSpi);

XML:

  1. <bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
  2. <!-- Configure Ignite here. -->
  3. <property name="communicationSpi">
  4. <bean class="org.apache.ignite.spi.communication.tcp.TcpCommunicationSpi">
  5. <property name="slowClientQueueLimit" value="1000"/>
  6. </bean>
  7. </property>
  8. </bean>

1.7.6.客户端重连

有几种情况客户端会从集群中断开:

当一个客户端发现它与一个集群断开时,会为自己赋予一个新的节点id然后试图与该服务端重新连接。注意:这会产生一个副作用,就是当客户端重建连接时本地ClusterNodeid属性会发生变化,这意味着,如果业务逻辑依赖于这个id,就会受到影响。
当客户端处于一个断开状态并且试图重建与集群的连接过程中时,Ignite API会抛出一个特定的异常:IgniteClientDisconnectedException,这个异常提供了一个future,当客户端重连成功后他会完成(IgniteCacheAPI会抛出CacheException,他有一个IgniteClientDisconnectedException作为他的cause)。这个future也可以通过IgniteCluster.clientReconnectFuture()方法获得。
此外,客户端重连也有一些特定的事件(这些事件是本地化的,也就是说他们只会在客户端节点触发):

下面的例子显示IgniteClientDisconnectedException如何使用:
计算:

  1. IgniteCompute compute = ignite.compute();
  2. while (true) {
  3. try {
  4. compute.run(job);
  5. }
  6. catch (IgniteClientDisconnectedException e) {
  7. e.reconnectFuture().get(); // Wait for reconnection.
  8. // Can proceed and use the same IgniteCompute instance.
  9. }
  10. }

缓存:

  1. IgniteCache cache = ignite.getOrCreateCache(new CacheConfiguration<>());
  2. while (true) {
  3. try {
  4. cache.put(key, val);
  5. }
  6. catch (CacheException e) {
  7. if (e.getCause() instanceof IgniteClientDisconnectedException) {
  8. IgniteClientDisconnectedException cause =
  9. (IgniteClientDisconnectedException)e.getCause();
  10. cause.reconnectFuture().get(); // Wait for reconnection.
  11. // Can proceed and use the same IgniteCache instance.
  12. }
  13. }
  14. }

客户端自动重连可以通过TcpDiscoverySpiclientReconnectDisabled属性禁用,如果重连被禁用那么当发现与集群断开时客户端节点就会停止。
下面的例子显示了如何禁用客户端重连:

  1. IgniteConfiguration cfg = new IgniteConfiguration();
  2. // Configure Ignite here.
  3. TcpDiscoverySpi discoverySpi = new TcpDiscoverySpi();
  4. discoverySpi.setClientReconnectDisabled(true);
  5. cfg.setDiscoverySpi(discoverySpi);

1.7.7.客户端节点强制服务端模式

客户端节点需要网络中有存活的服务端节点才能启动。
然而,如果在没有运行中的服务端节点时还要启动一个客户端节点,可以通过如下方式在客户端节点强制服务端模式发现。
如果不管服务端节点是否存活都要启动客户端节点非常必要,可以以如下的方式在客户端强制服务端模式发现:

  1. IgniteConfiguration cfg = new IgniteConfiguration();
  2. cfg.setClientMode(true);
  3. // Configure Ignite here.
  4. TcpDiscoverySpi discoverySpi = new TcpDiscoverySpi();
  5. discoverySpi.setForceServerMode(true);
  6. cfg.setDiscoverySpi(discoverySpi);

这种情况下,如果网络中的所有节点都是服务端节点时发现就会发生。

这种情况下为了发现能正常工作,发现SPI在所有节点上使用的所有地址应该是可以相互访问的。

1.8.资源注入

1.8.1.摘要

Ignite中,预定义的资源都是可以进行依赖注入的,同时支持基于属性和基于方法的注入。任何加注正确注解的资源都会在初始化之前注入相对应的任务、作业、闭包或者SPI。

1.8.2.基于属性和基于方法

可以通过在一个属性或者方法上加注注解来注入资源。当加注在属性上时,Ignite只是在注入阶段简单地设置属性的值(不会理会该属性的访问修饰符)。如果在一个方法上加注了资源注解,他会访问一个与注入资源相对应的输入参数的类型,如果匹配,那么在注入阶段,就会将适当的资源作为输入参数,然后调用该方法。
基于属性:

  1. Ignite ignite = Ignition.ignite();
  2. Collection<String> res = ignite.compute().broadcast(new IgniteCallable<String>() {
  3. // Inject Ignite instance.
  4. @IgniteInstanceResource
  5. private Ignite ignite;
  6. @Override
  7. public String call() throws Exception {
  8. IgniteCache<Object, Object> cache = ignite.getOrCreateCache(CACHE_NAME);
  9. // Do some stuff with cache.
  10. ...
  11. }
  12. });

基于方法:

  1. public class MyClusterJob implements ComputeJob {
  2. ...
  3. private Ignite ignite;
  4. ...
  5. // Inject Ignite instance.
  6. @IgniteInstanceResource
  7. public void setIgnite(Ignite ignite) {
  8. this.ignite = ignite;
  9. }
  10. ...
  11. }

1.8.3.预定义的资源

有很多的预定义资源可供注入:

资源 描述
CacheNameResource CacheConfiguration.getName()提供,注入网格缓存名
CacheStoreSessionResource 注入当前的CacheStoreSession实例
IgniteInstanceResource 注入当前的Ignite实例
JobContextResource 注入ComputeJobContext的实例。作业的上下文持有关于一个作业执行的有用的信息。比如,可以获得包含与作业并置的条目的缓存的名字。
LoadBalancerResource 注入ComputeLoadBalancer的实例,注入后可以用于任务的负载平衡。
LoggerResource 注入IgniteLogger的实例,他可以用于向本地节点的日志写消息。
ServiceResource 通过指定服务名注入Ignite的服务。
SpringApplicationContextResource 注入Spring的ApplicationContext资源。
SpringResource 从Spring的ApplicationContext注入资源,当希望访问在Spring的ApplicationContext XML配置中指定的一个Bean时,可以用它。
TaskContinuousMapperResource 注入一个ComputeTaskContinuousMapper的实例,持续映射可以在任何时点从任务中发布作业,即使过了map的初始化阶段。
TaskSessionResource 注入ComputeTaskSession资源的实例,它为一个特定的任务执行定义了一个分布式的会话。

1.9.线程池

1.9.1.摘要

Ignite创建并且维护着一组线程池,根据使用的API不同分别用于不同的目的。本章节中会列出一些众所周知的内部线程池,然后会展示如何自定义线程池。在IgniteConfiguration的javadoc中,可以看到Ignite中可用的完整线程池列表。

1.9.2.系统线程池

系统线程池处理所有与缓存相关的操作,除了SQL以及其他的查询类型,它们会使用查询线程池,同时这个线程池也负责处理Ignite计算任务的取消操作。
默认的线程池数量为max(8,CPU总核数),使用IgniteConfiguration.setSystemThreadPoolSize(...)可以进行调整。

1.9.3.公共线程池

公共线程池负责Ignite的计算网格,所有的计算任务都由这个线程池接收然后处理。
默认的线程池数量为max(8,CPU总核数),使用IgniteConfiguration.setPublicThreadPoolSize(...)可以进行调整。

1.9.4.查询线程池

查询线程池处理集群内所有的SQL、扫描和SPI查询。
默认的线程池数量为max(8,CPU总核数),使用IgniteConfiguration.setQueryThreadPoolSize(...)可以进行调整。

1.9.5.服务线程池

Ignite的服务网格调用使用的是服务线程池,Ignite的服务和计算网格组件都有专用的线程池,可以避免当一个服务实现希望调用一个计算(或者反之)时的线程争用和死锁。
默认的线程池数量为max(8,CPU总核数),使用IgniteConfiguration.setServiceThreadPoolSize(...)可以进行调整。

1.9.6.平行线程池

平行线程池通过将操作展开为多个平行的执行,有助于显著加速基本的缓存操作以及事务,因为可以避免相互竞争。
默认的线程池数量为max(8,CPU总核数),使用IgniteConfiguration.setStripedPoolSize(...)可以进行调整。

1.9.7.数据流处理器线程池

数据流处理器线程池用于处理来自IgniteDataStreamer的所有消息和请求,各种内置的使用IgniteDataStreamer的流适配器也可以。
默认的线程池数量为max(8,CPU总核数),使用IgniteConfiguration.setDataStreamerThreadPoolSize(...)可以进行调整。

1.9.8.自定义线程池

对于Ignite的计算任务,也可以配置自定义的线程池,当希望同步地从一个计算任务调用另一个的时候很有用,因为可以避免死锁。要保证这一点,需要确保执行嵌套任务的线程池不同于上级任务的线程池。
自定义线程池需要在IgniteConfiguration中进行定义,并且需要有一个唯一的名字:
Java:

  1. IgniteConfiguration cfg = ...;
  2. cfg.setExecutorConfiguration(new ExecutorConfiguration("myPool").setSize(16));

XML:

  1. <bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
  2. ...
  3. <property name="executorConfiguration">
  4. <list>
  5. <bean class="org.apache.ignite.configuration.ExecutorConfiguration">
  6. <property name="name" value="myPool"/>
  7. <property name="size" value="16"/>
  8. </bean>
  9. </list>
  10. </property>
  11. ...
  12. </bean>

这样,假定下面的计算任务由上面定义的myPool线程池中的线程执行:

  1. public class InnerRunnable implements IgniteRunnable {
  2. @Override public void run() {
  3. System.out.println("Hello from inner runnable!");
  4. }
  5. }

怎么做呢,需要使用IgniteCompute.withExecutor(),他会被上级任务的实现马上执行,像下面这样:

  1. public class OuterRunnable implements IgniteRunnable {
  2. @IgniteInstanceResource
  3. private Ignite ignite;
  4. @Override public void run() {
  5. // Synchronously execute InnerRunnable in custom executor.
  6. ignite.compute().withExecutor("myPool").run(new InnerRunnable());
  7. }
  8. }

上级任务的执行可通过如下方式触发,对于这个场景,它会由公共线程池执行:

  1. ignite.compute().run(new OuterRunnable());

未定义线程池
如果应用请求在自定义线程池执行计算任务,而该线程池在Ignite节点中未定义,那么一个特定的警告消息就会在节点的日志中输出,然后任务就会被公共线程池接管执行。

1.10.二进制编组器

1.10.1.基本概念

从1.6版本开始,Ignite引入了一个在缓存中存储数据的新概念,名为二进制对象,这个新的序列化格式提供了若干个优势:

二进制对象只可以用于使用默认的二进制编组器时(即没有在配置中显式地设置其他的编组器)

限制
BinaryObject格式实现也带来了若干个限制:
1. 在内部Ignite不会写属性以及类型的名字,但是使用一个小写的名字哈希来标示一个属性或者类型,这意味着属性或者类型不能有同样的名字哈希。即使序列化不会在哈希冲突的情况下工作,但Ignite在配置级别提供了一种方法来解决此冲突;
2.同样的原因,BinaryObject格式在类的不同层次上也不允许有同样的属性名;
3.默认会忽略Externalizable接口。如果使用了BinaryObject格式,Externalizable类型会与Serializable类型是同样的处理方式,没有writeExternal()readExternal()方法。如果由于某些原因这样不行,需要实现Binarylizable接口,加入一个自定义BinarySerializer或者切换到OptimizedMarshaller

IgniteBinary入口,可以从Ignite的实例获得,包含了操作二进制对象的所有必要的方法。

自动化哈希值计算和Equals实现
如果一个对象可以被序列化到二进制形式,那么Ignite会在序列化期间计算它的哈希值并且将其写入最终的二进制数组。另外,Ignite还为二进制对象的比较需求提供了equals方法的自定义实现。这意味着,不需要为在Ignite中使用的自定义键和值覆写GetHashCodeEquals方法,除非他们无法序列化成二进制形式。
比如,Externalizable类型的对象无法被序列化成二进制形式,这时就需要自行实现hashCodeequals方法,具体可以看上面的限制章节。

1.10.2.配置二进制对象

在绝大多数情况下不需要额外地配置二进制对象。
但是,如果需要覆写默认的类型和属性ID计算或者加入BinarySerializer,可以为IgniteConfiguration定义一个BinaryConfiguration对象,这个对象除了为每个类型指定映射以及序列化器之外还可以指定一个全局的Name映射、一个全局ID映射以及一个全局的二进制序列化器。对于每个类型的配置,通配符也是支持的,这时提供的配置会适用于匹配类型名称模板的所有类型。
配置二进制类型:

  1. <bean id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
  2. <property name="binaryConfiguration">
  3. <bean class="org.apache.ignite.configuration.BinaryConfiguration">
  4. <property name="nameMapper" ref="globalNameMapper"/>
  5. <property name="idMapper" ref="globalIdMapper"/>
  6. <property name="typeConfigurations">
  7. <list>
  8. <bean class="org.apache.ignite.binary.BinaryTypeConfiguration">
  9. <property name="typeName" value="org.apache.ignite.examples.*"/>
  10. <property name="serializer" ref="exampleSerializer"/>
  11. </bean>
  12. </list>
  13. </property>
  14. </bean>
  15. </property>
  16. ...

1.10.3.BinaryObject缓存API

Ignite默认使用反序列化值作为最常见的使用场景,要启用BinaryObject处理,需要获得一个IgniteCache的实例然后使用withKeepBinary()方法。启用之后,如果可能,这个标志会确保从缓存返回的对象都是BinaryObject格式的。将值传递给EntryProcessorCacheInterceptor也是同样的处理。

平台类型
注意当通过withKeepBinary()方法启用BinaryObject处理时并不是所有的对象都会表示为BinaryObject,会有一系列的平台类型,包括基本类型,String,UUID,Date,Timestamp,BigDecimal,Collections,Maps和Arrays,他们不会被表示为BinaryObject
注意在下面的示例中,键类型为Integer,他是不会被修改,因为他是平台类型。

获取BinaryObject:

  1. // Create a regular Person object and put it to the cache.
  2. Person person = buildPerson(personId);
  3. ignite.cache("myCache").put(personId, person);
  4. // Get an instance of binary-enabled cache.
  5. IgniteCache<Integer, BinaryObject> binaryCache = ignite.cache("myCache").withKeepBinary();
  6. // Get the above person object in the BinaryObject format.
  7. BinaryObject binaryPerson = binaryCache.get(personId);

1.10.4.使用BinaryObjectBuilder修改二进制对象

BinaryObject实例是不能修改的,要更新属性或者创建新的BinaryObject,必须使用BinaryObjectBuilder的实例。
BinaryObjectBuilder的实例可以通过IgniteBinary入口获得。他可以使用类型名创建,这时返回的对象不包含任何属性,或者他也可以通过一个已有的BinaryObject创建,这时返回的对象会包含从给定的BinaryObject中拷贝的所有属性。
获取BinaryObjectBuilder实例的另外一个方式是调用已有BinaryObject实例的toBuilder()方法,这种方式创建的对象也会从BinaryObject中拷贝所有的数据。
下面是一个使用BinaryObjectAPI来处理服务端节点的数据而不需要将程序部署到服务端以及不需要实际的数据反序列化的示例:
EntryProcessor内的BinaryObject:

  1. // The EntryProcessor is to be executed for this key.
  2. int key = 101;
  3. cache.<Integer, BinaryObject>withKeepBinary().invoke(
  4. key, new CacheEntryProcessor<Integer, BinaryObject, Object>() {
  5. public Object process(MutableEntry<Integer, BinaryObject> entry,
  6. Object... objects) throws EntryProcessorException {
  7. // Create builder from the old value.
  8. BinaryObjectBuilder bldr = entry.getValue().toBuilder();
  9. //Update the field in the builder.
  10. bldr.setField("name", "Ignite");
  11. // Set new value to the entry.
  12. entry.setValue(bldr.build());
  13. return null;
  14. }
  15. });

1.10.5.BinaryObject类型元数据

像上面描述的那样,二进制对象结构可以在运行时进行修改,因此获取一个存储在缓存中的一个特定类型的信息也可能是有用的,比如属性名,属性类型,属性类型名,关系属性名,Ignite通过BinaryType接口满足这样的需求。
这个接口还引入了一个属性getter的更快的版本,叫做BinaryField。这个概念类似于Java的反射,可以缓存BinaryField实例中读取的属性的特定信息,他有助于从一个很大的二进制对象集合中读取同一个属性。

  1. Collection<BinaryObject> persons = getPersons();
  2. BinaryField salary = null;
  3. double total = 0;
  4. int cnt = 0;
  5. for (BinaryObject person : persons) {
  6. if (salary == null)
  7. salary = person.type().field("salary");
  8. total += salary.value(person);
  9. cnt++;
  10. }
  11. double avg = total / cnt;

1.10.6.BinaryObject和CacheStore

在缓存API上调用withKeepBinary()方法对于将用户对象传入CacheStore的方式不起作用,这么做是故意的,因为大多数情况下单个CacheStore实现要么使用反序列化类,要么使用BinaryObject表示。要控制对象传入Store的方式,需要使用CacheConfigurationstoreKeepBinary标志,当该标志设置为false时,会将反序列化值传入Store,否则会使用BinaryObject表示。
下面是一个使用BinaryObject的Store的伪代码实现的示例:

  1. public class CacheExampleBinaryStore extends CacheStoreAdapter<Integer, BinaryObject> {
  2. @IgniteInstanceResource
  3. private Ignite ignite;
  4. /** {@inheritDoc} */
  5. @Override public BinaryObject load(Integer key) {
  6. IgniteBinary binary = ignite.binary();
  7. List<?> rs = loadRow(key);
  8. BinaryObjectBuilder bldr = binary.builder("Person");
  9. for (int i = 0; i < rs.size(); i++)
  10. bldr.setField(name(i), rs.get(i));
  11. return bldr.build();
  12. }
  13. /** {@inheritDoc} */
  14. @Override public void write(Cache.Entry<? extends Integer, ? extends BinaryObject> entry) {
  15. BinaryObject obj = entry.getValue();
  16. BinaryType type = obj.type();
  17. Collection<String> fields = type.fieldNames();
  18. List<Object> row = new ArrayList<>(fields.size());
  19. for (String fieldName : fields)
  20. row.add(obj.field(fieldName));
  21. saveRow(entry.getKey(), row);
  22. }
  23. }

1.10.7.二进制Name映射器和二进制ID映射器

在内部,Ignite不会写属性或者类型名字的完整字符串,而是因为性能的原因,为类型或者属性名写一个整型哈希值作为替代。经过测试,在类型相同时,属性名或者类型名的哈希值冲突实际上是不存在的,为了获得性能,使用哈希值是安全的。对于当不同的类型或者属性确实冲突的场合,BinaryNameMapperBinaryIdMapper可以为该类型或者属性名覆写自动生成的哈希值。
BinaryNameMapper - 映射类型/类和属性名到不同的名字;
BinaryIdMapper - 映射从BinaryNameMapper来的类型和属性名到ID,以便于Ignite内部使用。
Ignite提供了下面的开箱即用的映射器实现:

如果仅仅使用Java客户端并且在BinaryConfiguration中没有指定映射器,那么Ignite会使用BinaryBasicNameMapper并且simpleName属性会被设置为false,使用BinaryBasicIdMapper并且lowerCase属性会被设置为true
如果使用了.Net或者C++客户端并且在BinaryConfiguration中没有指定映射器,那么Ignite会使用BinaryBasicNameMapper并且simpleName属性会被设置为true,使用BinaryBasicIdMapper并且lowerCase属性会被设置为true
如果使用Java、.Net或者C++,默认是不需要任何配置的,只有当需要平台协同、名字转换复杂的情况下,才需要配置映射器。

1.11.日志

Ignite支持各种日志库和框架,可以开箱即用地使用Log4jLog4j2JCLSLF4J,本文会描述如何使用它们。

1.11.1.Log4j

如果在启动独立集群节点时要使用Log4j模块,需要在执行ignite.{sh|bat}脚本前,将optional/ignite-log4j文件夹移动到Ignite发行版的lib目录下,这时这个模块目录中的内容会被添加到类路径。
如果项目中使用maven进行依赖管理,那么需要添加如下的依赖:

  1. <dependency>
  2. <groupId>org.apache.ignite</groupId>
  3. <artifactId>ignite-log4j</artifactId>
  4. <version>${ignite.version}</version>
  5. </dependency>

${ignite.version}替换为实际使用的Ignite版本。
要使用Log4j进行日志记录,需要配置IgniteConfigurationgridLogger属性,如下所示:
XML:

  1. <bean class="org.apache.ignite.configuration.IgniteConfiguration">
  2. <property name="gridLogger">
  3. <bean class="org.apache.ignite.logger.log4j.Log4JLogger">
  4. <constructor-arg type="java.lang.String" value="log4j.xml"/>
  5. </bean>
  6. </property>
  7. <!-- Other Ignite configurations -->
  8. ...
  9. </bean>

Java:

  1. IgniteConfiguration cfg = new IgniteConfiguration();
  2. IgniteLogger log = new Log4JLogger("log4j.xml");
  3. cfg.setGridLogger(log);
  4. // Start Ignite node.
  5. Ignite ignite = Ignition.start(cfg);
  6. ignite.log().info("Info Message Logged!");

在上面的配置中,log4j.xml的路径要么是绝对路径,要么是相对路径,相对路径可以相对于META-INF,也可以相对于IGNITE_HOME

1.11.2.Log4j2

如果在启动独立集群节点时要使用Log4j2模块,需要在执行ignite.{sh|bat}脚本前,将optional/ignite-log4j2文件夹移动到Ignite发行版的lib目录下,这时这个模块目录中的内容会被添加到类路径。
如果项目中使用maven进行依赖管理,那么需要添加如下的依赖:

  1. <dependency>
  2. <groupId>org.apache.ignite</groupId>
  3. <artifactId>ignite-log4j2</artifactId>
  4. <version>${ignite.version}</version>
  5. </dependency>

${ignite.version}替换为实际使用的Ignite版本。
要使用Log4j2进行日志记录,需要配置IgniteConfigurationgridLogger属性,如下所示:
XML:

  1. <bean class="org.apache.ignite.configuration.IgniteConfiguration">
  2. <property name="gridLogger">
  3. <bean class="org.apache.ignite.logger.log4j2.Log4J2Logger">
  4. <constructor-arg type="java.lang.String" value="log4j2.xml"/>
  5. </bean>
  6. </property>
  7. <!-- Other Ignite configurations -->
  8. ...
  9. </bean>

Java:

  1. IgniteConfiguration cfg = new IgniteConfiguration();
  2. IgniteLogger log = new Log4J2Logger("log4j2.xml");
  3. cfg.setGridLogger(log);
  4. // Start Ignite node.
  5. Ignite ignite = Ignition.start(cfg);
  6. ignite.log().info("Info Message Logged!");

在上面的配置中,log4j2.xml的路径要么是绝对路径,要么是相对路径,相对路径可以相对于META-INF,也可以相对于IGNITE_HOME

1.11.3.JCL

如果在启动独立集群节点时要使用JCL模块,需要在执行ignite.{sh|bat}脚本前,将optional/ignite-jcl文件夹移动到Ignite发行版的lib目录下,这时这个模块目录中的内容会被添加到类路径。
如果项目中使用maven进行依赖管理,那么需要添加如下的依赖:

  1. <dependency>
  2. <groupId>org.apache.ignite</groupId>
  3. <artifactId>ignite-jcl</artifactId>
  4. <version>${ignite.version}</version>
  5. </dependency>

${ignite.version}替换为实际使用的Ignite版本。
要使用JCL进行日志记录,需要配置IgniteConfigurationgridLogger属性,如下所示:
XML:

  1. <bean class="org.apache.ignite.configuration.IgniteConfiguration">
  2. <property name="gridLogger">
  3. <bean class="org.apache.ignite.logger.jcl.JclLogger">
  4. <constructor-arg type="org.apache.commons.logging.Log">
  5. <bean class="org.apache.commons.logging.impl.Log4JLogger">
  6. <constructor-arg type="java.lang.String" value="log4j.xml"/>
  7. </bean>
  8. </constructor-arg>
  9. </bean>
  10. </property>
  11. <!-- Other Ignite configurations -->
  12. ...
  13. </bean>

Java:

  1. IgniteConfiguration cfg = new IgniteConfiguration();
  2. IgniteLogger log = new JclLogger(new
  3. org.apache.commons.logging.impl.Log4JLogger("log4j.xml"));
  4. cfg.setGridLogger(log);
  5. // Start Ignite node.
  6. Ignite ignite = Ignition.start(cfg);
  7. ignite.log().info("Info Message Logged!");

1.11.4.SLF4J

如果在启动独立集群节点时要使用SLF4J模块,需要在执行ignite.{sh|bat}脚本前,将optional/ignite-slf4j文件夹移动到Ignite发行版的lib目录下,这时这个模块目录中的内容会被添加到类路径。
如果项目中使用maven进行依赖管理,那么需要添加如下的依赖:

  1. <dependency>
  2. <groupId>org.apache.ignite</groupId>
  3. <artifactId>ignite-slf4j</artifactId>
  4. <version>${ignite.version}</version>
  5. </dependency>

${ignite.version}替换为实际使用的Ignite版本。
要使用JCL进行日志记录,需要配置IgniteConfigurationgridLogger属性,如下所示:
XML:

  1. <bean class="org.apache.ignite.configuration.IgniteConfiguration">
  2. <property name="gridLogger">
  3. <bean class="org.apache.ignite.logger.slf4j.Slf4jLogger"/>
  4. </property>
  5. <!-- Other Ignite configurations -->
  6. ...
  7. </bean>

Java:

  1. IgniteConfiguration cfg = new IgniteConfiguration();
  2. IgniteLogger log = new Slf4jLogger();
  3. cfg.setGridLogger(log);
  4. // Start Ignite node.
  5. Ignite ignite = Ignition.start(cfg);
  6. ignite.log().info("Info Message Logged!");

要了解更多的信息,可以看SLF4J手册

1.11.5.默认日志

Ignite默认会使用java.util.logging.Logger(JUL),通过$IGNITE_HOME/config/java.util.logging.properties配置文件进行配置,然后将日志写入$IGNITE_HOME/work/log文件夹,要修改这个日志目录,需要使用IGNITE_LOG_DIR环境变量。
另外,Ignite启动于quiet模式,会阻止INFODEBUG日志的输出。要关闭quiet模式,可以使用-DIGNITE_QUIET=false系统属性。注意,quiet模式的所有信息都是输出到标准输出(STDOUT)的。

如果使用jul-to-slf4j桥,要确保配置正确
如果使用了jul-to-slf4j桥,需要特别关注下Ignite中的JUL日志级别。如果在org.apache上配置了DEBUG级别,那么最终的日志级别会为INFO。这意味着在生成日志时会产生十倍的负载,然后在通过桥时被丢弃。JUL默认级别为INFO,在org.apache.ignite.logger.java.JavaLogger#isDebugEnabled中设置一个断点,会发现JUL子系统会生成DEBUG级别的日志。

1.12.FAQ

1.堆内和堆外内存存储有何不同?
当处理很大的堆时,通过在Java主堆空间外部缓存数据,可以使缓存克服漫长的JVM垃圾收集(GC)导致的暂停,但是数据仍然在内存中。
更多信息
2.Apache Ignite是一个键值存储么?
Apache Ignite是一个具有计算能力的、有弹性的内存中的分布式对象存储。在其最简单的形式中,是的,Apache Ignite可以作为一个键/值存储(缓存),但是也暴露了更丰富的API来与数据交互,比如完整的ANSI99兼容的SQL查询、文本检索、事务等等。
更多信息
3.Apache Ignite是否支持JSON文档?
当前,Apache Ignite并不完整支持JSON文档,但是当前处于beta阶段的Node.js客户端会支持JSON文档。
4.Apache Ignite是否可以用于Apache Hive?
是,Apache Ignite的Hadoop加速器提供了一系列的组件,支持在任何的Hadoop发行版中执行内存中的Hadoop作业执行和文件系统操作,包括Apache Hive。
在Ignite化的Hadoop中运行Apache Hive
5.在事务隔离的悲观模式中,是否锁定键的读和写?
是的,主要的问题在于,在悲观模式中,访问是会获得锁,而在乐观模式中,锁是在提交阶段获得的。
更多信息
6.是否可以用Hibernate访问Apache Ignite?
是的,Apache Ignite可以用作Hibernate的二级缓存(或者L2缓存),他可以显著地提升应用的持久化层的速度。
更多信息
7.Apache Ignite是否支持JDBC?
是的,Apache Ignite提供了JDBC驱动,可以在缓存中使用标准SQL查询和JDBC API获得分布式的数据。
更多信息
8.Apache Ignite是否保证消息的顺序?
是的,如果希望收到消息的顺序与发送消息的顺序一致,可以使用sendOrdered(...)方法。可以传递一个超时时间来指定一条消息在队列中的等待时间,他会等待本来应在其之前发送的消息。如果超时时间过期,所有的还没有到达该节点中一个给定主题的消息都会被忽略。
更多信息
9.是否可以运行Java和.Net闭包?他是如何工作的?
.Net节点可以同时执行Java和.Net闭包,而标准Java节点只能执行Java闭包。当启动ApacheIgnite.exe时,他会使用位于IGNITE_HOME/platforms/dotnet/bin的一个脚本在同一个进程下同时启动JVM和CLR,.Net闭包会被CLR处理执行。
10.Java和.Net之间的转换成本是什么?
仅有的最小可能的开销是一个额外的数组复制+JNI调用,在本地测试时这个开销可能降低性能,但在真正的分布式负载环境下可以忽略不计。
11.闭包是如何传输的?
每个闭包都是一个特定类的对象。当它要被发送时会序列化成二进制的形式,通过线路发送到一个远程节点然后在那里反序列化。该远程节点在类路径中应该有该闭包类,或者开启peerClassLoading以从发送端加载该类。
12.SQL查询是否被负载平衡?
SQL查询总是被广播到保存有要查询的数据的每个节点,例外就是本地SQL查询(query.setLocal(true)),他只是在一个本地节点执行,还有就是可以精确标识节点的部分查询。
13.用户是否可以控制资源分配?即,是否可以限制用户A为50个节点,但是用户B可以在所有的100个节点上执行任务?
多租户只在缓存中存在,他们可以在创建在一个节点的子集上(可以看CacheConfiguration.setNodeFilter)以及在每个缓存基础上安全地赋予权限。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注