Skip to content

Ignite.NET

1.配置选项

1.1.概述

Ignite.NET节点可以通过各种方式进行配置,然后通过Ignition.Start*方法用指定的配置就可以启动。

1.2.通过编程方式配置

在C#应用中,使用Ignition.Start(IgniteConfiguration)方法可以配置一个Ignite.NET节点。

csharp
Ignition.Start(new IgniteConfiguration
{
    DiscoverySpi = new TcpDiscoverySpi
    {
        IpFinder = new TcpDiscoveryStaticIpFinder
        {
            Endpoints = new[] {"127.0.0.1:47500..47509"}
        },
        SocketTimeout = TimeSpan.FromSeconds(0.3)
    },
    IncludedEventTypes = EventType.CacheAll,
    JvmOptions = new[] { "-Xms1024m", "-Xmx1024m" }
});

1.3.通过应用或Web配置文件方式配置

Ignition.StartFromApplicationConfiguration方法可以从app.configweb.config文件的Apache.Ignite.Core.IgniteConfigurationSection中读取配置。

IgniteConfigurationSection.xsd模式文件位于二进制包和Apache.Ignite.SchemaNuGet包的Apache.Ignite.Core.dll同级目录处,在Visual Studio中通过None构建操作将其包含在项目中,这样当编辑配置文件的IgniteConfigurationSection段时,可以开启智能提示。

xml
<configuration>
    <configSections>
        <section name="igniteConfiguration" type="Apache.Ignite.Core.IgniteConfigurationSection, Apache.Ignite.Core" />
    </configSections>

    <runtime>
        <gcServer enabled="true"/>
    </runtime>

    <igniteConfiguration xmlns="http://ignite.apache.org/schema/dotnet/IgniteConfigurationSection" gridName="myGrid1">
        <discoverySpi type="TcpDiscoverySpi">
            <ipFinder type="TcpDiscoveryStaticIpFinder">
                <endpoints>
                    <string>127.0.0.1:47500..47509</string>
                </endpoints>
            </ipFinder>
        </discoverySpi>

        <cacheConfiguration>
            <cacheConfiguration cacheMode='Replicated' readThrough='true' writeThrough='true' />
            <cacheConfiguration name='secondCache' />
        </cacheConfiguration>

        <includedEventTypes>
            <int>42</int>
            <int>TaskFailed</int>
            <int>JobFinished</int>
        </includedEventTypes>

        <userAttributes>
            <pair key='myNode' value='true' />
        </userAttributes>

        <JvmOptions>
          <string>-Xms1024m</string>
          <string>-Xmx1024m</string>
        </JvmOptions>
    </igniteConfiguration>
</configuration>
csharp
var ignite = Ignition.StartFromApplicationConfiguration("igniteConfiguration");

提示

要将IgniteConfigurationSection.xsd模式文件添加到Visual Studio项目中,可以转到Projects菜单,单击Add Existing Item...菜单项,之后找到Apache Ignite包中的IgniteConfigurationSection.xsd并且选中。

或者,安装NuGet软件包:Install-Package Apache.Ignite.Schema,这将自动将xsd文件添加到项目中。

为了改善编辑体验,需要在Tools ⇒ Options ⇒ Text Editor ⇒ XML中启用了Statement Completion选项。

1.3.1.Ignite配置段语法

Ignite的配置段直接映射到IgniteConfiguration类。

  • 简单属性(字符串、基础类型、枚举)映射到XML属性(属性名为驼峰式的C#属性名);
  • 复杂属性映射到嵌套的XML元素(元素名为驼峰式的C#属性名);
  • 当复杂属性是接口或抽象类时,type属性将用于指定类型,需要使用程序集限定名。对于内置类型(例如上面代码示例中的TcpDiscoverySpi),可以省略程序集名和命名空间;
  • 如有疑问,可以查询IgniteConfigurationSection.xsd

1.4.通过Spring XML方式配置

Spring的XML文件可以使用原生的基于Java的Ignite配置,Spring的配置文件可以通过Ignition.Start(string)方法以及IgniteConfiguration.SpringConfigUrl属性传入。这样在Ignite.NET不直接支持某些Java属性时,此功能会有用。

使用IgniteConfiguration.SpringConfigUrl属性时,Spring的配置会首先加载,在其之上才会应用其他的IgniteConfiguration属性。

xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/util
                           http://www.springframework.org/schema/util/spring-util.xsd">
    <bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
        <property name="localHost" value="127.0.0.1"/>
        <property name="gridName" value="grid1"/>
        <property name="userAttributes">
            <map>
                <entry key="my_attr" value="value1"/>
            </map>
        </property>

        <property name="cacheConfiguration">
            <list>
                <bean class="org.apache.ignite.configuration.CacheConfiguration">
                    <property name="name" value="cache1"/>
                    <property name="startSize" value="10"/>
                </bean>
            </list>
        </property>

        <property name="discoverySpi">
            <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
                <property name="ipFinder">
                    <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">
                        <property name="addresses">
                            <list>
                                <value>127.0.0.1:47500..47509</value>
                            </list>
                        </property>
                    </bean>
                </property>
                <property name="socketTimeout" value="300" />
            </bean>
        </property>
    </bean>
</beans>
csharp
var ignite = Ignition.Start("spring-config.xml");

2.部署选项

2.1.概述

Ignite.NET由.NET程序集和Java jar文件组成。.NET程序集由具体的项目引用,并在自动化构建过程中复制到输出文件夹。Jar文件可以自动或手动处理,具体取决于处理方法。Ignite.NET通过IgniteHomeJvmClasspath配置发现它们。

本章节会介绍Ignite.NET节点的几种最常用的部署选项。

2.2.NuGet部署

Apache.IgniteNuGet包包含一个lib文件夹,其中包含所有必需的jar文件。此文件夹具有<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>构建动作,并在构建或发布过程中自动复制到输出目录。

确认未全局配置IGNITE_HOME,除了ASP.NET环境(参见下文)外,通常不需要使用NuGet配置IGNITE_HOME

要禁用默认的构建动作,需要在.csproj文件对应的<PackageReference>中添加<ExcludeAssets>build</ExcludeAssets>属性,这对于瘦客户端场景或者希望自定义部署,是有用的。

xml
<Project Sdk="Microsoft.NET.Sdk">
    ...

    <ItemGroup>
      <PackageReference Include="Apache.Ignite" Version="2.9.1">
          <ExcludeAssets>build</ExcludeAssets>
      </PackageReference>
    </ItemGroup>

</Project>

2.3.单文件部署

Ignite.NET支持在.NET Core 3/.NET 5+中可用的单文件部署

  • 使用IncludeAllContentForSelfExtractMSBuild 属性将jar文件包含到单文件包中,或单独发送;
  • 参见故障排除:DllNotFoundException以了解某些Ignite版本在.NET 5上的解决方法。

发布命令示例:

shell
dotnet publish --self-contained true -r linux-x64 -p:PublishSingleFile=true -p:IncludeAllContentForSelfExtract=true

2.4.完整二进制包部署

  • 复制从https://ignite.apache.org下载的整个发行版内容以及自己的应用;
  • 配置IGNITE_HOME环境变量或IgniteConfiguration.IgniteHome以指向该文件夹。

2.5.自定义部署

Jar文件位于二进制包的libs文件夹和NuGet包中。

Ignite.NET必需的最小Jar集合是:

  • ignite-core-{VER}.jar
  • cache-api-1.0.0.jar
  • ignite-indexing文件夹(如果使用缓存查询);
  • ignite-spring文件夹(如果使用基于spring的配置)。

将jar部署到默认位置:

  • 将jar文件复制到Apache.Ignite.Core.dll旁边的libs文件夹中;
  • 不要配置IgniteConfiguration.JvmClasspathIgniteConfiguration.IgniteHome属性和IGNITE_HOME环境变量。

将jar文件部署到任意位置:

  • 将jar文件复制到某处;
  • IgniteConfiguration.JvmClasspath属性配置为指向每个jar文件路径的字符串,多个用分号分割;
  • 不要设置IGNITE_HOME环境变量和IgniteConfiguration.IgniteHome属性。

IgniteConfiguration.JvmClasspath示例:

c:\ignite-jars\ignite-core-1.5.0.final.jar;c:\ignite-jars\cache-api-1.0.0.jar

2.6.ASP.NET部署

在Web环境(IIS和IIS Express)中使用Ignite时,JvmClasspathIgniteHome必须显式配置,因为dll文件复制到了临时文件夹,而Ignite无法自动定位这些jar文件。

在ASP.NET环境中可以像下面这样配置IgniteHome

csharp
Ignition.Start(new IgniteConfiguration
{
    IgniteHome = HttpContext.Current.Server.MapPath(@"~\bin\")
});

或者,可以全局配置IGNITE_HOME,将下面这行代码添加到Global.asax.csApplication_Start方法的顶部:

csharp
Environment.SetEnvironmentVariable("IGNITE_HOME", HttpContext.Current.Server.MapPath(@"~\bin\"));

或者,可以使用以下方法填充JvmClasspath

csharp
static string GetDefaultWebClasspath()
{
    var dir = HttpContext.Current.Server.MapPath(@"~\bin\libs");

    return string.Join(";", Directory.GetFiles(dir, "*.jar"));
}

2.7.IIS程序池生命周期、AppDomains和Ignite.NET

IIS有一个已知的问题:当重启Web应用时(由于代码更改或手动重启),程序池进程仍然在线,而AppDomain被回收。

卸载AppDomain后,Ignite.NET会自动停止。不过在旧域卸载过程中,可能会启动新域,因此,旧域中的节点可能与新域中的节点发生IgniteConfiguration.IgniteInstanceName冲突。

要解决此问题,需要确保为IgniteInstanceName分配唯一值或将IgniteConfiguration.AutoGenerateIgniteInstanceName属性设置为true

csharp
var cfg = new IgniteConfiguration { AutoGenerateIgniteInstanceName = true };
xml
<igniteConfiguration autoGenerateIgniteInstanceName="true">
  ...
</igniteConfiguration>

3.独立节点

3.1.概述

Ignite.NET节点可以在.NET应用的代码中通过使用Ignition.Start()启动,也可以使用可执行的Apache.Ignite.exe(位于{apache_ignite_release}\platforms\dotnet\bin文件夹下)作为单独的进程启动。像通常一样,在内部Apache.Ignite.exe引用Apache.Ignite.Core.dll和使用Ignition.Start(),并且可以使用下面列出的命令行参数进行配置,方法是将它们作为命令行选项传递或直接在Apache.Ignite.exe.config文件中进行设置。

通常以独立模式启动服务端节点,Ignite集群是一组互连在一起的服务端节点,目的是为应用提供RAM和CPU等共享资源。

3.2.通过命令行配置独立节点

下面是基本的Ignite参数,当使用Apache.Ignite.exe程序启动节点时,这些参数可以作为命令行参数传入:

命令行参数含义
-IgniteHomeIgnite安装目录路径(如果未提供会使用IGNITE_HOME环境变量)
-ConfigFileNameapp.config文件路径(如果未提供会使用Apache.Ignite.exe.config
-ConfigFileName配置文件中IgniteConfigurationSection的名字
-SpringConfigUrlSpring配置文件路径
-JvmDllPathJVM库jvm.dll的路径(如果未提供会使用JAVA_HOME环境变量)
-JvmClasspath传递给JVM的类路径(在这里注册其它的jar文件)
-SuppressWarnings是否输出警告信息
-J<javaOption>JVM参数
-Assembly要加载的其它.NET程序集
-JvmInitialMemoryMB初始Java堆大小(MB),对应于-XmsJava参数
-JvmMaxMemoryMB最大Java堆大小(MB),对应于-XmxJava参数
/install根据指定的参数将Ignite安装为Windows服务
/uninstall卸载Ignite Windows服务

示例:

shell
Apache.Ignite.exe -ConfigFileName=c:\ignite\my-config.xml -ConfigSectionName=igniteConfiguration -Assembly=c:\ignite\my-code.dll -J-Xms1024m -J-Xmx2048m

3.3.通过XML文件配置独立节点

通过app.configXML文件或/和Spring配置文件,也可以配置独立节点。上面列出的每个命令行参数,也可以用于Apache.Ignite.exe.configappSettings段。

xml
<configuration>
  <configSections>
    <section name="igniteConfiguration" type="Apache.Ignite.Core.IgniteConfigurationSection, Apache.Ignite.Core" />
  </configSections>

  <igniteConfiguration springConfigUrl="c:\ignite\spring.xml">
    <cacheConfiguration name="myCache" cacheMode="Replicated" />
  </igniteConfiguration>

  <appSettings>
    <add key="Ignite.Assembly.1" value="my-assembly.dll"/>
    <add key="Ignite.Assembly.2" value="my-assembly2.dll"/>
    <add key="Ignite.ConfigSectionName" value="igniteConfiguration" />
  </appSettings>
</configuration>

这个示例定义了igniteConfiguration段,并通过Ignite.ConfigSectionName配置用其启动Ignite,它还引用了一个Spring配置文件,两者最终会组合在一起。

3.4.加载用户程序集

某些Ignite的API涉及了远程代码执行,因此需要将代码和程序集一起加载到Apache.Ignite.exe,这可以通过-Assembly命令行参数或者Ignite.Assembly应用配置来实现。

以下功能要求在所有节点上加载相应的程序集:

  • ICompute(支持自动加载,具体可以参见远程程序集加载)。
  • 带过滤器的扫描查询;
  • 带过滤器的持续查询;
  • 远程程序集加载方法;
  • 带过滤器的ICache.LoadCache
  • IServices
  • IMessaging.RemoteListen
  • IEvents.RemoteQuery

缺失用户程序集

如果一个用户程序集无法加载,会抛出Could not load file or assembly 'MyAssembly' or one of its dependencies异常。

注意任何程序集的依赖也是必须要加入该列表的。

3.5.Ignite.NET作为Windows服务

Apache.Ignite.exe可以安装为Windows的服务,因此可以通过/install命令行参数自动启动。每次服务启动时,所有其它命令行参数将被保留和使用。使用/uninstall可以卸载服务。

shell
Apache.Ignite.exe /install -J-Xms513m -J-Xmx555m -ConfigSectionName=igniteConfiguration

4.异步API

4.1.概述

许多Ignite API具有异步变体,例如:void ICache.PutTask ICache.PutAsync,异步API允许开发者编写高效的非阻塞代码

csharp
ICache<int, string> cache = ignite.GetOrCreateCache<int, string>("name");

// Sync, blocks thread on every call.
cache.Put(1, "Hello");
string hello = cache.Get(1);

// Async, does not block threads.
await cache.PutAsync(1, "Hello");
string hello = await cache.GetAsync(1);

使用异步API,在等待缓存操作完成时,当前线程不会被阻塞,线程返回线程池,可以执行其他工作。

当异步操作完成时,方法会恢复执行(要么在同一个线程上,要么在不同的线程上),这取决于环境和配置。这称为“异步延续”。

4.2.异步延续

除非另有说明,否则Ignite在.NET线程池上执行异步延续,这是安全的,不需要任何特别关注。

4.2.1.瘦客户端

所有瘦客户端的异步API都使用.NET线程池用于异步延续。

4.2.2.胖客户端

除非使用IgniteConfiguration.AsyncContinuationExecutor配置了不同的执行器,服务端和胖客户端节点上的异步缓存操作的回调都使用Java的ForkJoinPool#commonPool调用。

  • 此默认执行器对于回调中的任何操作都是安全的;
  • Ignite 2.11中的默认行为已更改,在此之前,异步缓存操作回调是从Ignite系统线程池(所谓的“平行线程池”)调用的;
  • 要恢复以前的行为,可以使用IgniteConfiguration.AsyncContinuationExecutor = AsyncContinuationExecutor.UnsafeSynchronous
    • 以前的行为性能更好些,因为回调是在没有任何间接或调度的情况下执行的;
    • UNSAFE:系统线程执行回调时缓存操作无法继续,如果在回调中调用其他缓存操作,则可能引发死锁。

Ignite 2.10及之前:死锁和系统线程池饥饿的可能性

在Ignite 2.10及之前版本中,系统线程池用于运行异步延续,这意味着上面代码中的GetAsync调用由系统线程执行。

如果用户代码阻塞线程,这可能导致死锁,或者由于系统线程忙于运行用户代码而不是执行缓存操作而导致饥饿。

要启用安全行为,需要手动将延续移动到.NET线程池:

csharp
await cache.PutAsync(1, "Hello").ContinueWith(
                t => {},
                CancellationToken.None,
                TaskContinuationOptions.None,
                TaskScheduler.Default);

string hello = await cache.GetAsync(1).ContinueWith(
                t => t.Result,
                CancellationToken.None,
                TaskContinuationOptions.None,
                TaskScheduler.Default);

提示:使用扩展方法来减少冗长。

4.2.3.计算

Ignite 2.11及后续版本:所有ICompute异步API都使用.NET线程池来运行异步延续。

Ignite 2.10及之前:在Ignite公共线程池上执行计算异步延续。为了减少公共池的负载,建议使用前述的ContinueWith方法:

csharp
await compute.CallAsync(new MyAction()).ContinueWith(
                t => t.Result,
                CancellationToken.None,
                TaskContinuationOptions.None,
                TaskScheduler.Default);

这会将延续从Ignite公共线程池(为计算功能保留)移动到.NET线程池(TaskScheduler.Default)。

4.3.ConfigureAwait

Task.ConfigureAwait方法可以用于所有的Ignite异步API。

更多的详细信息,请参见ConfigureAwait的常见问题解答

5.日志

5.1.概述

Ignite默认使用底层的Java Log4j日志记录系统,.NET和Java端的日志消息都会记录在那里,开发者也可以通过IIgnite.Logger记录日志。

csharp
var ignite = Ignition.Start();
ignite.Logger.Info("Hello World!");

LoggerExtensions类提供了访问ILogger.Log方法的各种便捷方式。

5.2.自定义Logger

通过IgniteConfiguration.LoggerILogger接口,开发者可以自定义日志记录器实现,.NET和Java端的日志消息都会转发到这里。

csharp
var cfg = new IgniteConfiguration
{
  Logger = new MemoryLogger()
}

var ignite = Ignition.Start();

class MemoryLogger : ILogger
{
  // Logger can be called from multiple threads, use concurrent collection
  private readonly ConcurrentBag<string> _messages = new ConcurrentBag<string>();

  public void Log(LogLevel level, string message, object[] args,
                  IFormatProvider formatProvider, string category,
                  string nativeErrorInfo, Exception ex)
  {
    _messages.Add(message);
  }

  public bool IsEnabled(LogLevel level)
  {
    // Accept any level.
    return true;
  }
}
xml
<igniteConfiguration>
  <logger type="MyNamespace.MemoryLogger, MyAssembly" />
</igniteConfiguration>

5.3.NLog和log4net日志

Ignite.NET为NLoglog4net提供了ILogger实现,他们位于二进制包(Apache.Ignite.NLog.dllApache.Ignite.Log4Net.dll)中并且可以通过NuGet安装:

  • Install-Package Apache.Ignite.NLog
  • Install-Package Apache.Ignite.Log4Net

NLog和Log4Net使用静态预定义的配置,因此在Ignite中无需配置IgniteConfiguration.Logger

csharp
var cfg = new IgniteConfiguration
{
  Logger = new IgniteNLogLogger()  // or IgniteLog4NetLogger
}

var ignite = Ignition.Start();
xml
<igniteConfiguration>
  <logger type="Apache.Ignite.NLog.IgniteNLogLogger, Apache.Ignite.NLog" />
</igniteConfiguration>

使用NLog可以配置基于文件的简单日志,如下所示:

csharp
var nlogConfig = new LoggingConfiguration();

var fileTarget = new FileTarget
{
  FileName = "ignite_nlog.log"
};
nlogConfig.AddTarget("logfile", fileTarget);

nlogConfig.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, fileTarget));
LogManager.Configuration = nlogConfig;

var igniteConfig = new IgniteConfiguration
{
  Logger = new IgniteNLogLogger()
};
Ignition.Start(igniteConfig);

6.LINQ实现

6.1.概述

Ignite.NET包含了一个与Ignite SQL API集成的LINQ实现。可以避免直接处理SQL语法,而是在C#中使用LINQ编写查询。Ignite LINQ实现支持ANSI-99 SQL的所有功能,包括分布式关联、分组、聚合、排序等。

6.2.安装

  • 如果使用的是Ignite二进制包:需要添加Apache.Ignite.Linq.dll引用;
  • 如果使用的是NuGet:Install-Package Apache.Ignite.Linq

6.3.配置

SQL索引的配置方式与常规SQL查询相同,具体请参见定义索引章节的介绍。

6.4.用法

Apache.Ignite.Linq.CacheLinqExtensions类是LINQ实现的入口。通过调用AsCacheQueryable方法可以在Ignite缓存上获取可查询实例,并在其上使用LINQ:

csharp
ICache<EmployeeKey, Employee> employeeCache = ignite.GetCache<EmployeeKey, Employee>(CacheName);

IQueryable<ICacheEntry<EmployeeKey, Employee>> queryable = cache.AsCacheQueryable();

Employee[] interns = queryable.Where(emp => emp.Value.IsIntern).ToArray();

警告

可以直接在缓存实例上使用LINQ,而无需调用AsCacheQueryable()。但是,这将导致LINQ到对象的查询在本地获取并处理整个缓存数据集,这是非常低效的。

6.5.内省

Ignite LINQ实现底层使用的是ICache.QueryFields。在物化语句(ToListToArray等)之前,都可以通过将IQueryable转换为ICacheQueryable来检查生成的SqlFieldsQuery

csharp
// Create query
var query = ignite.GetCache<EmployeeKey, Employee>(CacheName).AsCacheQueryable().Where(emp => emp.Value.IsIntern);

// Cast to ICacheQueryable
var cacheQueryable = (ICacheQueryable) query;

// Get resulting fields query
SqlFieldsQuery fieldsQuery = cacheQueryable.GetFieldsQuery();

// Examine generated SQL
Console.WriteLine(fieldsQuery.Sql);

// Output: select _T0._key, _T0._val from "persons".Person as _T0 where _T0.IsIntern

6.6.投影

ICacheEntry对象上的简单Where查询操作,可以分别查询键、值或任何键和值的字段,可以使用匿名类型查询多个字段。

csharp
var query = ignite.GetCache<EmployeeKey, Employee>(CacheName).AsCacheQueryable().Where(emp => emp.Value.IsIntern);

IQueryable<EmployeeKey> keys = query.Select(emp => emp.Key);

IQueryable<Employee> values = query.Select(emp => emp.Value);

IQueryable<string> names = values.Select(emp => emp.Name);

var custom = query.Select(emp => new {Id = emp.Key, Name = emp.Value.Name, Age = emp.Value.Age});

6.7.编译查询

LINQ实现在表达式解析和SQL生成上有一些开销,因此对于经常使用的查询需要消除这个开销。

Apache.Ignite.Linq.CompiledQuery类支持查询的编译,调用其Compile方法后可以创建一个新的表示已编译查询的委托,所有的查询参数都在委托参数中。

csharp
var queryable = ignite.GetCache<EmployeeKey, Employee>(CacheName).AsCacheQueryable();

// Regular query
var persons = queryable.Where(emp => emp.Value.Age > 21);
var result = persons.ToArray();

// Corresponding compiled query
var compiledQuery = CompiledQuery.Compile((int age) => queryable.Where(emp => emp.Value.Age > age));
IQueryCursor<ICacheEntry<EmployeeKey, Employee>> cursor = compiledQuery(21);
result = cursor.ToArray();

关于LINQ实现在性能方面的更多信息,请参见LINQ和SQL这篇博客文章。

6.8.关联

LINQ实现支持跨越多个缓存/表和节点的关联。

csharp
var persons = ignite.GetCache<int, Person>("personCache").AsCacheQueryable();
var orgs = ignite.GetCache<int, Organization>("orgCache").AsCacheQueryable();

// SQL join on Person and Organization to find persons working for Apache
var qry = from person in persons from org in orgs
          where person.Value.OrgId == org.Value.Id
          && org.Value.Name == "Apache"
          select person

foreach (var cacheEntry in qry)
    Console.WriteLine(cacheEntry.Value);

// Same query with method syntax
qry = persons.Join(orgs, person => person.Value.OrgId, org => org.Value.Id,
(person, org) => new {person, org}).Where(p => p.org.Name == "Apache").Select(p => p.person);

6.9.包含

支持ICollection.Contains,当需要通过一组ID检索数据时,这很有用,例如:

csharp
var persons = ignite.GetCache<int, Person>("personCache").AsCacheQueryable();
var ids = new int[] {1, 20, 56};

var personsByIds = persons.Where(p => ids.Contains(p.Value.Id));

该查询会被转换为…​ where Id IN (?, ?, ?)命令。但是要注意,由于变量数目可变,因此无法使用编译查询。更好的替代方法是在ids集合上使用关联:

csharp
var persons = ignite.GetCache<int, Person>("personCache").AsCacheQueryable();
var ids = new int[] {1, 20, 56};

var personsByIds = persons.Join(ids,
                                person => person.Value.Id,
                                id => id,
                                (person, id) => person);

该LINQ查询会被转换为临时表关联:select _T0._KEY, _T0._VAL from "person".Person as _T0 inner join table (F0 int = ?) _T1 on (_T1.F0 = _T0.ID),并且只有一个数组参数,因此可以正确地缓存计划,还可以使用编译查询。

6.10.支持的SQL函数

以下是Ignite LINQ实现支持的.NET函数及其等价的SQL列表:

.NET函数SQL函数
String.LengthLENGTH
String.ToLowerLOWER
String.ToUpperUPPER
String.StartsWith("foo")LIKE 'foo%'
String.EndsWith("foo")LIKE '%foo'
String.Contains("foo")LIKE '%foo%'
String.IndexOf("abc")INSTR(MyField, 'abc') - 1
String.IndexOf("abc", 3)INSTR(MyField, 'abc', 3) - 1
String.Substring("abc", 4)SUBSTRING(MyField, 4 + 1)
String.Substring("abc", 4, 7)SUBSTRING(MyField, 4 + 1, 7)
String.Trim()TRIM
String.TrimStart()LTRIM
String.TrimEnd()RTRIM
String.Trim('x')TRIM(MyField, 'x')
String.TrimStart('x')LTRIM(MyField, 'x')
String.TrimEnd('x')RTRIM(MyField, 'x')
String.ReplaceREPLACE
String.PadLeftLPAD
String.PadRightRPAD
Regex.ReplaceREGEXP_REPLACE
Regex.IsMatchREGEXP_LIKE
Math.AbsABS
Math.AcosACOS
Math.AsinASIN
Math.AtanATAN
Math.Atan2ATAN2
Math.CeilingCEILING
Math.CosCOS
Math.CoshCOSH
Math.ExpEXP
Math.FloorFLOOR
Math.LogLOG
Math.Log10LOG10
Math.PowPOWER
Math.RoundROUND
Math.SignSIGN
Math.SinSIN
Math.SinhSINH
Math.SqrtSQRT
Math.TanTAN
Math.TanhTANH
Math.TruncateTRUNCATE
DateTime.YearYEAR
DateTime.MonthMONTH
DateTime.DayDAY_OF_MONTH
DateTime.DayOfYearDAY_OF_YEAR
DateTime.DayOfWeekDAY_OF_WEEK - 1
DateTime.HourHOUR
DateTime.MinuteMINUTE
DateTime.SecondSECOND

7.执行Java服务

7.1.概述

Ignite.NET可以与.NET服务一样使用Java服务,要从.NET应用调用Java服务,需要了解服务的接口。

7.2.示例

下面通过一个示例了解如何使用此功能。

7.2.1.创建Java服务

java
public class MyJavaService implements Service {
  // Service method to be called from .NET
  public String testToUpper(String x) {
    return x.toUpperCase();
  }

  // Service interface implementation
  @Override public void cancel(ServiceContext context) { // No-op.  }
  @Override public void init(ServiceContext context) throws Exception { // No-op. }
  @Override public void execute(ServiceContext context) throws Exception { // No-op. }
}

该Java服务可以部署在任何节点(.NET、C++、仅Java)上,因此对部署没有限制:

java
ignite.services().deployClusterSingleton("myJavaSvc", new MyJavaService());

7.2.2..NET端调用Java服务

创建服务接口的.NET版本:

csharp
// Interface can have any name
interface IJavaService
{
  // Method must have the same name (case-sensitive) and same signature:
  // argument types and order.
  // Argument names and return type do not matter.
  string testToUpper(string str);
}

获取服务代理并调用该方法:

csharp
var config = new IgniteConfiguration
{
  // Make sure that Java service class is in classpath on all nodes, including .NET
  JvmClasspath = @"c:\my-project\src\Java\target\classes\"
}

var ignite = Ignition.Start(config);

// Make sure to use the same service name as in deployment
var prx = ignite.GetServices().GetServiceProxy<IJavaService>("myJavaSvc");
string result = prx.testToUpper("invoking Java service...");
Console.WriteLine(result);

7.3.接口方法映射

.NET服务接口动态映射到其对应的Java对象,这是在方法调用时发生的:

  • 不必在.NET接口中指定所有Java服务方法;
  • .NET接口可以包含Java服务中不存在的成员,除非调用这些缺少的方法,否则不会抛出任何异常。

Java方法解析方式如下:

  • Ignite寻找具有指定名称和参数计数的方法。如果只找到一个,Ignite将使用它;
  • 在匹配的方法中,Ignite会寻找参数兼容的方法(通过Class.isAssignableFrom)。如果兼容Ignite会调用该方法,如果不兼容则会抛出异常;
  • 方法返回类型将被忽略,因为.NET和Java不允许同一个方法有不同的返回类型。

有关方法参数和结果映射的详细信息,请参见类型兼容性章节的介绍。注意params/varargs也是支持的,因为在.NET和Java中,它们是对象数组的语法糖。

8..NET平台缓存

警告

这是一个试验性API。

Ignite.NET在CLR堆中提供了额外的缓存层,平台缓存会保留当前节点上存在的每个缓存条目的反序列化副本,从而以增加内存使用为代价大大提高了缓存读取性能。

提示

事务中绕过平台缓存:在事务中cache.Get以及下面列出的其他API不会使用平台缓存,事务支持后续会推出。

8.1.配置平台缓存

8.1.1.服务端节点

通过设置CacheConfiguration.PlatformCacheConfiguration为非空值,可以为所有服务端节点配置平台缓存。服务端节点上的平台缓存将分配给该节点的所有主缓存和备份缓存项存储在.NET内存中。条目是实时更新的,在任何时刻都保证是最新的,甚至在用户代码访问它们之前。

警告

平台缓存导致服务端节点上的内存使用量翻倍,每个缓存项存储两次:在堆外内存中序列化,在CLR堆中反序列化。

csharp
var cacheCfg = new CacheConfiguration("my-cache")
{
    PlatformCacheConfiguration = new PlatformCacheConfiguration()
};

var cache = ignite.CreateCache<int, string>(cacheCfg);

8.1.2.客户端节点

客户端节点上的平台缓存需要配置近缓存,因为客户端节点不会存储数据。客户端节点上的平台缓存与该节点上的近缓存保持相同的条目集,近缓存的退出策略也适用于平台缓存。

csharp
var nearCacheCfg = new NearCacheConfiguration
{
    // Keep up to 1000 most recently used entries in Near and Platform caches.
    EvictionPolicy = new LruEvictionPolicy
    {
        MaxSize = 1000
    }
};

var cache = ignite.CreateNearCache<int, string>("my-cache",
    nearCacheCfg,
    new PlatformCacheConfiguration());

8.2.支持的API

下面的ICache<K, V>API会使用平台缓存(包括对应的异步版本):

  • GetTryGet、索引(ICache[k]);
  • GetAll(首先从平台缓存读取,并在必要时回退到分布式缓存);
  • ContainsKeyContainsKeys
  • LocalPeekTryLocalPeek
  • GetLocalEntries
  • GetLocalSize
  • 使用ScanQueryQuery
    • 使用平台缓存将值传递给ScanQuery.Filter
    • ScanQuery.Localtrue以及ScanQuery.Partition不为空时直接迭代平台缓存。

8.3.直接访问平台缓存数据

无需修改代码即可利用平台缓存。已有的对ICache.Get以及上面列出的其他API的调用,都会尽可能从平台缓存读取来改进性能,当平台缓存中不存在该条目时,Ignite会退回到正常路径并从集群中检索数据。

但是,有时可能希望专门访问平台缓存,从而避免Java和网络调用,LocalAPI结合CachePeekMode.Platform可以做到这一点:

csharp
var cache = ignite.GetCache<int, string>("my-cache");

// Get value from platform cache.
bool hasKey = cache.TryLocalPeek(1, out var val, CachePeekMode.Platform);

// Get platform cache size (current number of entries on local node).
int size = cache.GetLocalSize(CachePeekMode.Platform);

// Get all values from platform cache.
IEnumerable<ICacheEntry<int, string>> entries = cache.GetLocalEntries(CachePeekMode.Platform);

8.4.高级配置

8.4.1.二进制模式

如果要在平台缓存中使用二进制对象,需要将PlatformCacheConfiguration.KeepBinary配置为true

csharp
var cacheCfg = new CacheConfiguration("people")
{
    PlatformCacheConfiguration = new PlatformCacheConfiguration
    {
        KeepBinary = true
    }
};

var cache = ignite.CreateCache<int, Person>(cacheCfg)
    .WithKeepBinary<int, IBinaryObject>();

IBinaryObject binaryPerson = cache.Get(1);

8.4.2.键和值类型

在将Ignite缓存与值类型一起使用时,应相应地设置PlatformCacheConfiguration.KeyTypeNameValueTypeName以获得最佳性能并减少GC压力:

csharp
var cacheCfg = new CacheConfiguration("people")
{
    PlatformCacheConfiguration = new PlatformCacheConfiguration
    {
        KeyTypeName = typeof(long).FullName,
        ValueTypeName = typeof(Guid).FullName
    }
};

var cache = ignite.CreateCache<long, Guid>(cacheCfg);

Ignite默认使用ConcurrentDictionary<object, object>存储平台缓存数据,因为实际类型事先未知。这将导致对值类型进行装箱和拆箱,从而降低性能并消耗更多的内存。当在PlatformCacheConfiguration中配置KeyTypeNameValueTypeName时,Ignite会使用这些类型来创建一个内部的ConcurrentDictionary而不是默认的object

警告

错误的KeyTypeName和/或ValueTypeName设置可能导致运行时强制转换异常。

9.插件

9.1.概述

Ignite.NET的插件系统使得第三方可以扩展Ignite.NET的核心功能。解释Ignite插件的工作方式的最好方法是查看插件的生命周期。

9.2.IgniteConfiguration.PluginConfigurations

作为一个Ignite插件,首先需要在IgniteConfiguration.PluginConfigurations属性上注册IPluginConfiguration接口的实现,从用户的角度来看,这是一个手动过程,即必须明确引用和配置插件的程序集。

IPluginConfiguration接口有两个与Ignite.NET的Java部分交互的方法,下面会有介绍。除此之外IPluginConfiguration实现还应包含所有其他和插件有关的配置属性。

IPluginConfiguration实现的另一部分是必要的PluginProviderType属性,其会将插件配置与插件的实现进行链接,例如:

csharp
    [PluginProviderType(typeof(MyPluginProvider))]
    public class MyPluginConfiguration : IPluginConfiguration
    {
        public string MyProperty { get; set; }  // Plugin-specific property

        public int? PluginConfigurationClosureFactoryId
        {
            get { return null; }  // No Java part
        }

        public void WriteBinary(IBinaryRawWriter writer)
        {
            // No-op.
        }
    }

总结:

  • 开发者将IPluginConfiguration实现的实例添加到IgniteConfiguration中;
  • 使用准备好的配置启动Ignite节点;
  • 在Ignite节点初始化完成之前,插件引擎将检查IPluginConfiguration实现的PluginProviderType属性并实例化指定的类;

9.3.IPluginProvider

IPluginProvider实现是新添加的插件的主力,它通过处理OnIgniteStartOnIgniteStop方法的调用来处理Ignite节点的生命周期,另外它还通过GetPlugin<T>()方法为开发者提供了一个可选的API。

Ignite.NET引擎在IPluginProvider实现上要调用的第一个方法是Start(IPluginContext<TestIgnitePluginConfiguration> context)IPluginContext中可以访问初始的插件配置以及与Ignite进行交互的所有方法。

当停止Ignite时,会依次调用StopOnIgniteStop方法,以便插件实现可以完成所有的清理和关闭相关的任务。

9.4.IIgnite.GetPlugin

通过IIgnite.GetPlugin(string name)方法可以访问插件暴露给用户的API,Ignite引擎会使用传递的名字查找IPluginProvider并在其上调用GetPlugin

9.5.与Java交互

Ignite.NET插件可以通过PlatformTargetIPlatformTarget接口对与Ignite Java插件进行交互。

9.5.1.Java端

  • 实现PlatformTarget接口,它是与.NET的通信端点:
java
class MyPluginTarget implements PlatformTarget {
  @Override public long processInLongOutLong(int type, long val) throws IgniteCheckedException {
    if (type == 1)
    	return val + 1;
    else
      return val - 1;
  }
  ...  // Other methods here.
}
  • 实现PlatformPluginExtension接口:
java
public class MyPluginExtension implements PlatformPluginExtension {
  @Override public int id() {
    return 42;  // Unique id to be used from .NET side.
  }

  @Override public PlatformTarget createTarget() {
    return new MyPluginTarget();  // Return target from previous step.
  }
}
  • 实现PluginProvider.initExtensions方法并注册PlatformPluginExtension类:
java
@Override public void initExtensions(PluginContext ctx, ExtensionRegistry registry) {
  registry.registerExtension(PlatformPluginExtension.class, new MyPluginExtension());
}

9.5.2..NET端

通过对应的ID调用IPluginContext.GetExtension,其会调用Java端的createTarget方法:

csharp
IPlatformTarget extension = pluginContext.GetExtension(42);

long result = extension.InLongOutLong(1, 2);  // processInLongOutLong is called in Java

其他的IPlatformTarget方法会以高效的方式在Java和.NET之间交换任意类型的数据。

9.5.3.Java端的回调

上面介绍了.NET -> Java的调用机制,当然也可以进行Java -> .NET的调用:

  • 在.NET端通过RegisterCallback方法使用一些ID注册回调处理器;
  • 在Java端使用该ID调用PlatformCallbackGateway.pluginCallback

完整的示例

完整的示例,可以参见这篇文章

10.序列化

大多数用户定义的类都会使用Ignite.NET API通过网络传递给其他节点,这些类包括:

  • 缓存键和值;
  • 缓存处理器和过滤器(ICacheEntryProcessorICacheEntryFilterICacheEntryEventFilterICacheEntryEventListener);
  • 计算函数(IComputeFunc)、操作(IComputeAction)和作业(IComputeJob);
  • 服务(IService);
  • 事件和消息处理器(IEventListenerIEventFilterIMessageListener)。

通过网络传输的这些类对象需要序列化,Ignite.NET支持以下序列化用户数据的方式:

  • Apache.Ignite.Core.Binary.IBinarizable接口;
  • Apache.Ignite.Core.Binary.IBinarySerializer接口;
  • System.Runtime.Serialization.ISerializable接口;
  • Ignite反射式序列化(当以上都不适用时)。

10.1.IBinarizable

IBinarizable方式提供了对序列化的细粒度控制,这是高性能生产代码的首选方法。

首先,在自己的类中实现IBinarizable接口:

csharp
public class Address : IBinarizable
{
    public string Street { get; set; }

    public int Zip { get; set; }

    public void WriteBinary(IBinaryWriter writer)
    {
        // Alphabetic field order is required for SQL DML to work.
        // Even if DML is not used, alphabetic order is recommended.
        writer.WriteString("street", Street);
        writer.WriteInt("zip", Zip);
    }

    public void ReadBinary(IBinaryReader reader)
    {
      	// Read order does not matter, however, reading in the same order
        // as writing improves performance.
        Street = reader.ReadString("street");
        Zip = reader.ReadInt("zip");
    }
}

IBinarizable也可以在没有字段名的原始模式下实现,这提供了最快和最紧凑的序列化,但是SQL查询不可用:

csharp
public class Address : IBinarizable
{
    public string Street { get; set; }

    public int Zip { get; set; }

    public void WriteBinary(IBinaryWriter writer)
    {
        var rawWriter = writer.GetRawWriter();

        rawWriter.WriteString(Street);
        rawWriter.WriteInt(Zip);
    }

    public void ReadBinary(IBinaryReader reader)
    {
        // Read order must be the same as write order
        var rawReader = reader.GetRawReader();

        Street = rawReader.ReadString();
        Zip = rawReader.ReadInt();
    }
}

自动化GetHashCode和Equals实现

如果对象可以序列化为二进制形式,则Ignite将在序列化时计算其哈希值,并将其写入二进制数组。此外,Ignite还提供了equals方法的自定义实现,用于二进制对象的比较。这意味着无需覆盖自定义键和值的GetHashCodeEquals方法即可在Ignite中使用它们。

10.2.IBinarySerializer

IBinarySerializerIBinarizable类似,但是将序列化逻辑与类实现分开。当无法修改类代码,并且在多个类之间共享序列化逻辑等场景时,这可能很有用。以下代码产生与上面的Address示例完全相同的序列化:

csharp
public class Address : IBinarizable
{
    public string Street { get; set; }

    public int Zip { get; set; }
}

public class AddressSerializer : IBinarySerializer
{
    public void WriteBinary(object obj, IBinaryWriter writer)
    {
      	var addr = (Address) obj;

        writer.WriteString("street", addr.Street);
        writer.WriteInt("zip", addr.Zip);
    }

    public void ReadBinary(object obj, IBinaryReader reader)
    {
      	var addr = (Address) obj;

        addr.Street = reader.ReadString("street");
        addr.Zip = reader.ReadInt("zip");
    }
}

序列化器应在配置中指定,如下:

csharp
var cfg = new IgniteConfiguration
{
    BinaryConfiguration = new BinaryConfiguration
    {
        TypeConfigurations = new[]
        {
            new BinaryTypeConfiguration(typeof (Address))
            {
                Serializer = new AddressSerializer()
            }
        }
    }
};

using (var ignite = Ignition.Start(cfg))
{
  ...
}

10.3.ISerializable

实现System.Runtime.Serialization.ISerializable接口的类型将相应地进行序列化(通过调用GetObjectData和序列化构造函数)。所有的系统功能都支持:包括IObjectReferenceIDeserializationCallbackOnSerializingAttributeOnSerializedAttributeOnDeserializingAttributeOnDeserializedAttribute

GetObjectData的结果以Ignite二进制格式写入,以下三个类提供相同的序列化表示形式:

csharp
class Reflective
{
	public int Id { get; set; }
	public string Name { get; set; }
}

class Binarizable : IBinarizable
{
	public int Id { get; set; }
	public string Name { get; set; }

	public void WriteBinary(IBinaryWriter writer)
	{
		writer.WriteInt("Id", Id);
		writer.WriteString("Name", Name);
	}

	public void ReadBinary(IBinaryReader reader)
	{
		Id = reader.ReadInt("Id");
		Name = reader.ReadString("Name");
	}
}

class Serializable : ISerializable
{
	public int Id { get; set; }
	public string Name { get; set; }

	public Serializable() {}

	protected Serializable(SerializationInfo info, StreamingContext context)
	{
		Id = info.GetInt32("Id");
		Name = info.GetString("Name");
	}

	public void GetObjectData(SerializationInfo info, StreamingContext context)
	{
		info.AddValue("Id", Id);
		info.AddValue("Name", Name);
	}
}

10.4.Ignite反射式序列化

Ignite反射式序列化本质上是一种IBinarizable方式,其是通过反射所有字段并发出读/写调用自动实现的。

该机制没有条件,任何类或结构都可以序列化(包括所有系统类型、委托、表达式树、匿名类型等)。

可以使用NonSerialized属性排除不需要的字段。

可以通过BinaryReflectiveSerializer显式启用原始模式:

csharp
var binaryConfiguration = new BinaryConfiguration
{
    TypeConfigurations = new[]
    {
        new BinaryTypeConfiguration(typeof(MyClass))
        {
            Serializer = new BinaryReflectiveSerializer {RawMode = true}
        }
    }
};
xml
<igniteConfiguration>
	<binaryConfiguration>
		<typeConfigurations>
			<binaryTypeConfiguration typeName='Apache.Ignite.ExamplesDll.Binary.Address'>
				<serializer type='Apache.Ignite.Core.Binary.BinaryReflectiveSerializer, Apache.Ignite.Core' rawMode='true' />
			</binaryTypeConfiguration>
		</typeConfigurations>
	</binaryConfiguration>
</igniteConfiguration>

如果没有这个配置,BinaryConfiguration是不需要的。

性能与手工实现IBinarizable相同,反射只在启动阶段使用,用于遍历所有的字段并发出有效的IL代码。

带有Serializable属性但没有ISerializable接口的类型使用Ignite反射式序列化器写入。

10.5.使用Entity Framework POCOs

Ignite中可以直接使用Entity Framework POCOs。

但是,Ignite无法直接序列化或反序列化POCO代理https://msdn.microsoft.com/zh-cn/data/jj592886.aspx,因为代理类型是动态类型。

将EF对象与Ignite结合使用时,要确认禁用创建代理:

csharp
ctx.Configuration.ProxyCreationEnabled = false;
csharp
ctx.ContextOptions.ProxyCreationEnabled = false;

10.6.更多信息

有关本章节介绍的各种模式的序列化性能的更多信息,请参见Ignite的序列化性能这篇文章。

11.跨平台支持

11.1.概述

从2.4版本开始,同Windows平台一样,也可以在Linux和macOS平台上运行.NET节点以及开发Ignite.NET应用,.NET Core和Mono平台都是支持的。

11.2..NET Core

环境要求

运行二进制包中的示例

  • 这里下载二进制包然后解压;
  • cd platforms/dotnet/examples/dotnetcore
  • dotnet run

11.3.Java检测

Ignite.NET会在如下位置寻找Java安装目录:

  • HKLM\Software\JavaSoft\Java Runtime Environment(Windows);
  • /usr/bin/java(Linux);
  • /Library/Java/JavaVirtualMachines(macOS)。

如果修改了Java的默认位置,那么需要使用下面的方法之一指定实际的位置:

  • 配置IgniteConfiguration.JvmDllPath属性;
  • 配置JAVA_HOME环境变量。

11.4.已知问题

No Java runtime present, requesting install

在macOS上Java的8u151版本存在一个问题:JDK-7131356,一定要安装8u152及其以后的版本。

Serializing delegates is not supported on this platform

.NET Core不支持序列化委托,执行System.MulticastDelegate.GetObjectData会抛出异常,因此Ignite.NET无法对委托或包含委托的对象进行序列化。

Could not load file or assembly 'System.Configuration.ConfigurationManager'

已知的.NET问题(506),有时需要额外的包引用:

  • dotnet add package System.Configuration.ConfigurationManager

12.平台互操作性

Ignite允许不同的平台(例如.NET、Java和C++)彼此互操作,由一个平台定义和写入Ignite的类和对象可以由另一平台读取和使用。

12.1.标识符

为了实现互操作性,Ignite使用通用二进制格式写入对象,此格式使用整数标识符对对象类型和字段进行编码。

Ignite通过两个阶段,将对象的类型和字段名转换为整数值:

  • 名称转换:将完整的类型名和字段名传递给IBinaryNameMapper接口,并转换为某种通用形式;
  • ID转换:将生成的字符串传递给IBinaryIdMapper以生成字段ID或者类型ID。

可以在BinaryConfiguration中配置全局映射器,也可以在BinaryTypeConfiguration中为具体类型配置映射器。

Java有相同的接口BinaryNameMapperBinaryIdMapper,它们也是配置在BinaryConfigurationBinaryTypeConfiguration上。

12.2.默认行为

.NET和Java类型必须映射到相同的类型ID,并且相关字段必须映射到相同的字段ID。

Ignite.NET的.NET部分默认会应用以下转换:

  • 名称转换:System.Type.FullName面向非泛型类型的属性,字段或属性名称不变;
  • ID转换:将名称转换为小写,然后以与Java中的java.lang.String.hashCode()方法相同的方式计算ID。

Ignite.NET的Java部分默认会应用以下转换:

  • 名称转换:Class.getName()方法用于获取类名称,字段名称保持不变;
  • ID转换:将名称转换为小写,然后用java.lang.String.hashCode()计算ID。

例如,如果以下两种类型在.NET命名空间和Java包外部,则它们将自动彼此映射:

csharp
class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public byte[] Data { get; set; }
}
java
class Person
{
    public int id;
    public String name;
    public byte[] data;
}

不过类型通常在某些命名空间或包中,包和命名空间的命名约定在Java和.NET中有所不同,.NET命名空间与Java包相同可能会出现问题。

简单名称映射器(忽略命名空间)可以避免此问题,其应该在.NET端和Java端中同时配置:

xml
<bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
    ...
    <property name="binaryConfiguration">
        <bean class="org.apache.ignite.configuration.BinaryConfiguration">
            <property name="nameMapper">
                <bean class="org.apache.ignite.binary.BinaryBasicNameMapper">
                    <property name="simpleName" value="true"/>
                </bean>
            </property>
        </bean>
    </property>
    ...
</bean>
csharp
var cfg = new IgniteConfiguration
{
  BinaryConfiguration = new BinaryConfiguration
  {
    NameMapper = new BinaryBasicNameMapper {IsSimpleName = true}
  }
}
xml
<igniteConfiguration>
  <binaryConfiguration>
    <nameMapper type="Apache.Ignite.Core.Binary.BinaryBasicNameMapper, Apache.Ignite.Core" isSimpleName="true" />
  </binaryConfiguration>
</igniteConfiguration>

12.3.类型兼容性

C#Java
boolboolean
byte(*),sbytebyte
shortushort(*)short
int, uint(*)int
long,ulong(*)long
charchar
floatfloat
doubledouble
decimaljava.math.BigDecimal(**)
decimaljava.lang.String
Guidjava.util.UUID
DateTimejava.util.Date,java.sql.Timestamp
  • byteushortuintulong没有对应的Java类型,将直接按字节映射(无范围检查),例如在C#中byte值为200,对应在Java中为有符号的byte-56

** Java中BigDecimal可以有任意的大小和精度,而C#中数值型固定为16个字节和28-29位精度,如果反序列化时一个BigDecimal无法匹配decimal,则Ignite.NET会抛出BinaryObjectException

Enum:在Ignite中,Java的writeEnum只会写入序数值,但是在.NET中,可以为enumValue分配任何数字,因此要注意,不会考虑任何自定义的枚举到原始值的绑定。

DateTime序列化

DateTime可以是Local和UTC,Java中Timestamp只能是UTC。因此Ignite.NET可以通过如下方式对DateTime进行序列化:

  • .NET风格(可以与非UTC值一起使用,在SQL中不可用)和作为Timestamp(对非UTC值抛出异常,可用在SQL中);
  • 反射式序列化:使用QuerySqlField标记字段以强制执行时间戳序列化,或者配置BinaryReflectiveSerializer.ForceTimestamptrue,这个可以每类型单独配置,也可以全局配置,比如:IgniteConfiguration.BinaryConfiguration = new BinaryConfiguration { Serializer = new BinaryReflectiveSerializer { ForceTimestamp = true } }
  • IBinarizable:使用IBinaryWriter.WriteTimestamp方法。

如果无法通过QuerySqlField修改类来标记字段或无法实现IBinarizable,可以使用IBinarySerializer方式。具体请参见序列化

12.4.集合兼容性

简单类型数组(上表所示)和对象数组都是可互操作的,所有其他集合和数组的默认行为(使用反射式序列化或IBinaryWriter.WriteObject)将使用BinaryFormatter,Java代码是无法读取的(为了正确支持泛型)。如果要将集合写为可互操作的格式,需要实现IBinarizable接口,并使用IBinaryWriter.WriteCollectionIBinaryWriter.WriteDictionaryIBinaryReader.ReadCollectionIBinaryReader.ReadDictionary方法。

12.5.混合平台集群

Ignite、Ignite.NET和Ignite.C++节点可以加入同一个集群。

所有平台都是基于Java构建的,因此任何节点都可以执行Java计算。但是.NET和C++计算只能由相对应的节点执行。

如果集群中至少有一个非.NET节点,则不支持以下Ignite.NET功能:

  • 带过滤器的扫描查询;
  • 带过滤器的持续查询;
  • ICache.Invoke方法;
  • 带过滤器的ICache.LoadCache
  • 服务;
  • IMessaging.RemoteListen
  • IEvents.RemoteQuery

有关多平台搭建的实战文章:构建多平台的Ignite集群:Java+.NET

12.6.混合平台集群中的计算

ICompute.ExecuteJavaTask方法在任何集群上的执行都没有限制。其他ICompute方法将仅在.NET节点上执行闭包。

13.远程程序集加载

13.1.概述

许多Ignite API都涉及远程代码执行,例如将计算任务和函数序列化,然后发送到远程节点并执行。但是远程节点必须加载具有这些功能的.NET程序集(dll文件),以便实例化和反序列化任务实例。

在Ignite的2.1版之前,必须手动加载程序集(使用Apache.Ignite.exe的-assembly开关或其他方式)。从2.1版开始引入了自动远程程序集加载,由IgniteConfiguration.PeerAssemblyLoadingMode控制。其默认值为Disabled,这意味着和之前的行为一致。在集群中的所有节点上,该属性值必须全部相同。还有另一种可用的模式是CurrentAppDomain

13.2.CurrentAppDomain模式

开启PeerAssemblyLoadingMode.CurrentAppDomain可以实现集群中其他节点的自动按需程序集请求,将程序集加载到运行Ignite节点的AppDomain中。

考虑以下代码:

csharp
// Print Hello World on all cluster nodes.
ignite.GetCompute().Broadcast(new HelloAction());

class HelloAction : IComputeAction
{
  public void Invoke()
  {
    Console.WriteLine("Hello World!");
  }
}
  • Ignite序列化HelloAction实例并发送到集群中的每个节点;
  • 远程节点尝试反序列化HelloAction,如果当前已加载或引用的程序集中没有此类,则将请求发送到Broadcast被调用的节点,然后发送到其他节点(如有必要);
  • 程序集文件从其他节点作为字节数组发送,并通过Assembly.Load(byte[])方法加载。

13.2.1.版本控制

程序集限定类型名(包括程序集版本)用于解析类型。

如果集群处于运行态,需要在逻辑上做出如下的修改,然后查看程序集如何自动加载:

  • 修改HelloAction实例以输出一些内容;
  • 修改AssemblyVersion
  • 重新编译然后运行应用代码;
  • 新版本的程序集会在其他节点上部署并运行。

但是如果不修改AssemblyVersion,则Ignite将使用以前加载的现有程序集,因为类型名没有变化。

不同版本的程序集可以共存,老节点可以继续运行旧代码,而新节点可以使用同一类的较新版本执行计算。

AssemblyVersion属性可以包含星号(*),以在构建时可以自动增量处理:[assembly: AssemblyVersion("1.0.*")]。这样就可以保持集群运行,重复修改和运行计算,并且每次都将部署新的程序集版本。

13.2.2.依赖

依赖程序集也会自动加载(例如ComputeAction从其他程序集调用某些代码时),因此使用很重的框架和库时,要注意单个计算调用可能导致大量程序集的网络传输。

13.2.3.卸载

.NET不允许卸载程序集,只能是AppDomain作为整体卸载所有的程序集。

当前可用的CurrentAppDomain模式使用现有的AppDomain,这意味着在当前AppDomain存在时,所有对等部署的程序集将保持加载状态,这可能会导致内存使用增加。

13.3.示例

实践中,可以使用PeerAssemblyLoadingExample尝试远程程序集加载。

  • 在Visual Studio中创建一个新的控制台应用;
  • 安装Ignite.NET NuGet软件包Install-Package Apache.Ignite
  • 打开packages\Apache.Ignite.2.1\lib\net40文件夹;
  • <igniteConfiguration>元素添加peerAssemblyLoadingMode='CurrentAppDomain'属性;
  • 运行Apache.Ignite.exe(一次或多次),保持进程运行;
  • AssemblyInfo.cs中的AssemblyVersion值改为1.0.*
  • 在Visual Studio中修改Program.cs,如下所示:
csharp
using System;
using Apache.Ignite.Core;
using Apache.Ignite.Core.Compute;
using Apache.Ignite.Core.Deployment;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var cfg = new IgniteConfiguration
            {
                PeerAssemblyLoadingMode = PeerAssemblyLoadingMode.CurrentAppDomain
            };

            using (var ignite = Ignition.Start(cfg))
            {
                ignite.GetCompute().Broadcast(new HelloAction());
            }
        }

        class HelloAction : IComputeAction
        {
            public void Invoke()
            {
                Console.WriteLine("Hello, World!");
            }
        }
    }
}
xml
<igniteConfiguration peerAssemblyLoadingMode='CurrentAppDomain' />
...
[assembly: AssemblyVersion("1.0.*")]
...
  • 运行该项目并观察"Hello,World!"在所有Apache.Ignite.exe窗口的控制台输出;
  • 将"Hello,World!"改为其他的值,然后再次运行该程序;
  • 观察之前使用Apache.Ignite.exe 启动的节点的输出有何不同。

14.问题解决

14.1.概述

本章节介绍了几种故障排除技术和在生产中构建和使用Ignite.NET应用时可能遇到的常见问题。

14.2.使用控制台排除故障

Ignite会产生控制台输出(stdout):信息、指标、警告、错误详细信息等,如果应用未打开控制台,则可以将控制台输出重定向到字符串或文件:

csharp
var sw = new StringWriter();
Console.SetOut(sw);

// Examine output:
sw.ToString();

14.3.获取有关异常的更多信息

当拿到IgniteException时,一定要检查其中的InnerException属性,该属性通常包含有关错误的更多详细信息,具体可以在Visual Studio的调试器中或通过在异常对象上调用ToString()方法看到:

csharp
try {
    IQueryCursor<List> cursor = cache.QueryFields(query);
}
catch (IgniteException e) {
    // Printing out the whole exception meesage.
    Console.WriteLine(e.ToString());
}

14.4.常见问题

下面将介绍设计Ignite.NET应用时可能遇到的几个问题。

14.4.1.无法加载jvm.dll

确认已安装JDK,配置好了JAVA_HOME环境变量并指向JDK安装目录。最新的JDK可以在这里找到:http://www.oracle.com/technetwork/java/javase/downloads/index.html

errorCode=193ERROR_BAD_EXE_FORMAT,通常是由x64/x86不匹配引起的。确认已安装的JDK和应用具有相同的x64/x86平台架构。当未设置JAVA_HOME时,Ignite会自动检测到合适的JDK,因此即使同时安装了x86和x64的JDK,也没有问题。

丢失依赖时会发生126 ERROR_MOD_NOT_FOUND

14.4.2.无法找到Java类

检查IGNITE_HOME环境变量、IgniteConfiguration.IgniteHomeIgniteConfiguration.JvmClasspath属性,具体请参见部署章节,ASP.NET/IIS场景还需要其他的步骤。

14.4.3.Ignition.Start阻塞

检查控制台输出。

最常见的原因是拓扑连接失败:

  • DiscoverySpi部分的配置不正确;
  • ClientModetrue的,但是没有服务端节点(请参见客户端和服务端)。

14.4.4.启动管理器失败: GridManagerAdapter

检查控制台输出,通常这是由无效或不兼容的配置引起的:

  • 某些配置属性值无效(超出范围等);
  • 某些配置属性与其他集群节点中的值不兼容。尤其是BinaryConfiguration中的属性,例如CompactFooterIdMapperNameMapper应在所有节点上是相同的。

当搭建混合集群(Java + .NET节点)时,通常会出现后一个问题,因为这些平台上的默认配置是不同的。.NET仅支持BinaryBasicIdMapperBinaryBasicNameMapper,因此必须通过以下方式调整Java配置以启用.NET节点的接入:

xml
<property name="binaryConfiguration">
    <bean class="org.apache.ignite.configuration.BinaryConfiguration">
        <property name="compactFooter" value="true"/>
        <property name="idMapper">
            <bean class="org.apache.ignite.binary.BinaryBasicIdMapper">
                <constructor-arg value="true"/>
            </bean>
        </property>
        <property name="nameMapper">
            <bean class="org.apache.ignite.binary.BinaryBasicNameMapper">
                <constructor-arg value="true"/>
            </bean>
        </property>
    </bean>
</property>

14.4.5.无法加载文件或程序集'MyAssembly'或其依赖,系统无法找到指定文件

远程节点缺失某程序集时会抛出该异常,具体请参见加载用户程序集

14.4.6.堆栈溢出错误,.NET终止

在Linux平台的.NET Core环境下,当用户代码抛出NullReferenceException异常时,就会发生这种情况。其原因是,.NET和Java都使用SIGSEGV来处理某些异常,包括NullPointerExceptionNullReferenceException,当JVM与.NET运行在同一进程中时,它将覆盖该处理器,破坏.NET异常处理(具体参见12)。

.NET Core 3.0解决了该问题(#25972:将COMPlus_EnableAlternateStackCheck环境变量设置为1)。

14.4.7.Linux中的僵尸进程

在Linux中,.NET和Java都会安装SIGCHLD处理器来处理子进程终止。

  • 处理器会延迟安装(当一个Process第一次启动时);
  • 同时只能存在一个处理器。

因此,Java可能会覆盖.NET的处理器,反之亦然,从而导致无法清理某个平台上的子进程,进而导致僵尸进程

在一个特殊的场景中,Ignite在Java端会使用子进程:即当启用持久化并使用direct-io模块时。这时就不应该使用.NET的System.Diagnostics.ProcessAPI。

解决方法

要解决此问题,需确保仅在Java端或.NET端创建子进程。

例如,当使用direct-io并且.NET代码需要启动子进程时,需要将进程处理逻辑移至Java端并使用计算引擎的ExecuteJavaTaskAPI对其进行调用,或者,使用服务API从.NET调用Java服务。

14.4.8.DllNotFoundException: 无法加载共享库'libcoreclr.so'或其依赖

在.NET 5上以单文件发布模式(例如dotnet publish --self-contained true -r linux-x64 -p:PublishSingleFile=true)时发生。

解决办法:

在启动Ignite节点之前添加以下代码:

csharp
NativeLibrary.SetDllImportResolver(
    typeof(Ignition).Assembly,
    (lib, _, _) => lib == "libcoreclr.so" ? (IntPtr) (-1) : IntPtr.Zero);

15.集成

15.1.ASP.NET输出缓存

15.1.1.概述

Ignite缓存可用作ASP.NET的输出缓存,这对于在Web服务器之间共享输出缓存尤其有效。

15.1.2.安装

  • 二进制包:添加对Apache.Ignite.AspNet.dll的引用;
  • NuGetInstall-Package Apache.Ignite.AspNet

15.1.3.自动启动Ignite

如果要自动启动Ignite用于输出缓存,可以在web.config中配置IgniteConfigurationSection

xml
<configuration>
    <configSections>
        <section name="igniteConfiguration" type="Apache.Ignite.Core.IgniteConfigurationSection, Apache.Ignite.Core" />
    </configSections>

    <igniteConfiguration autoGenerateIgniteInstanceName="true">
        <cacheConfiguration>
            <cacheConfiguration name='myWebCache' />
        </cacheConfiguration>
    </igniteConfiguration>
</configuration>

web.config中配置输出缓存:

xml
<system.web>
  <caching>
    <outputCache defaultProvider="apacheIgnite">
      <providers>
          <add name="apacheIgnite" type="Apache.Ignite.AspNet.IgniteOutputCacheProvider, Apache.Ignite.AspNet" igniteConfigurationSectionName="igniteConfiguration" cacheName="myWebCache" />
      </providers>
    </outputCache>
  </caching>
</system.web>

15.1.4.手动启动Ignite

也可以手动启动Ignite的实例,然后在提供者配置中指定它的名字:

xml
<system.web>
  <caching>
    <outputCache defaultProvider="apacheIgnite">
      <providers>
          <add name="apacheIgnite" type="Apache.Ignite.AspNet.IgniteOutputCacheProvider, Apache.Ignite.AspNet" cacheName="myWebCache" />
      </providers>
    </outputCache>
  </caching>
</system.web>

在接收任何请求之前,应该先启动Ignite的实例,通常这是在Global.asaxApplication_Start方法中实现的。

对于和Web环境尤其是和IGNITE_HOME有关的内容,请参见ASP.NET部署

15.2.ASP.NET会话状态缓存

15.2.1.概述

会话状态的值和信息默认存储在ASP.NET进程的内存中,而会话状态缓存旨在将用户会话数据存储在不同的源中。

Ignite.NET实现了一个会话状态存储,其会将会话数据存储在Ignite缓存中,该缓存将会话状态分布在多个服务器上,以实现更高的可用性和容错能力。

开发调试

在开发和调试过程中,IIS将在构建和运行Web应用时动态检测代码更新,不过不会重启嵌入式Ignite实例,这可能导致异常和不确定的行为,因此使用Ignite会话状态缓存时,要确认手动重启IIS

15.2.2.安装

  • 二进制包:添加对Apache.Ignite.AspNet.dll的引用;
  • NuGetInstall-Package Apache.Ignite.AspNet

15.2.3.配置

要启用基于Ignite的会话状态缓存,需要像下面这样修改web.config

xml
<system.web>
  ...
  <sessionState mode="Custom" customProvider="IgniteSessionStateProvider">
    <providers>
      <add name="IgniteSessionStateProvider"
           type="Apache.Ignite.AspNet.IgniteSessionStateStoreProvider, Apache.Ignite.AspNet"
           igniteConfigurationSectionName="igniteConfiguration"
           applicationId="myApp"
           gridName="myGrid"
           cacheName="aspNetSessionCache" />
    </providers>
  </sessionState>
  ...
</<system.web>

nametype属性是必须的,其他都是可选的。

属性描述
igniteConfigurationSectionNameconfigSections中定义的web.config段名,具体请参见配置章节的内容,该配置会在未启动Ignite时启动Ignite。
applicationId仅当多个Web应用共享同一Ignite会话状态缓存时才应使用。会分配不同的ID字符串,以避免应用之间的会话数据冲突。建议通过cacheName属性每个应用都使用单独的缓存。
gridName会话状态缓存实现通过该网格名调用Ignition.TryGetIgnite来检查Ignite是否已启动。
cacheName会话状态缓存名,默认为ASPNET_SESSION_STATE

关于在ASP.NET应用中启用Ignite的更多信息,请参见ASP.NET输出缓存

对于和Web环境尤其是和IGNITE_HOME有关的内容,请参见ASP.NET部署

15.3.Entity Framework二级缓存

15.3.1.概述

Entity Framework像大多数其他ORM一样,可以在多个层级上使用缓存。

  • 一级缓存是DbContext在实体级别控制的(实体缓存在对应的DbSet中);
  • 二级缓存位于DataReader层级,并保存原始查询数据(不过Entity Framework6中没有现成的二级缓存机制)。

Ignite.NET提供了EF6的二级缓存解决方案。该方案将数据存储在分布式Ignite缓存中,尤其适用于多个应用服务器通过Entity Framework访问单个SQL数据库的场景,缓存的数据在集群的所有主机之间共享。

15.3.2.安装

  • 二进制包:添加对Apache.Ignite.EntityFramework.dll的引用;
  • NuGetInstall-Package Apache.Ignite.EntityFramework

15.3.3.配置

Ignite.NET提供了一个支持二级缓存的自定义DbConfiguration实现Apache.Ignite.EntityFramework.IgniteDbConfiguration。将DbConfiguration应用于EntityFrameworkDbContext的方法有很多,具体请参见以下MSDN文档:msdn.microsoft.com/zh-cn/library/jj680699

不过最简单方法是使用DbConfigurationType属性:

csharp
[DbConfigurationType(typeof(IgniteDbConfiguration))]
class MyContext : DbContext
{
  public virtual DbSet<Foo> Foos { get; set; }
  public virtual DbSet<Bar> Bars { get; set; }
}

要自定义缓存行为,可以创建一个继承IgniteDbConfiguration的类并调用其某个基类构造函数,下面是一个示例:

csharp
private class MyDbConfiguration : IgniteDbConfiguration
{
  public MyDbConfiguration()
    : base(
      // IIgnite instance to use
      Ignition.Start(),
      // Metadata cache configuration (small cache, does not tolerate data loss)
      // Should be replicated or partitioned with backups
      new CacheConfiguration("metaCache")
      {
        CacheMode = CacheMode.Replicated
      },
      // Data cache configuration (large cache, holds actual query results,
      // tolerates data loss). Can have no backups.
      new CacheConfiguration("dataCache")
      {
        CacheMode = CacheMode.Partitioned,
        Backups = 0
      },
      // Custom caching policy.
      new MyCachingPolicy())
    {
      // No-op.
    }
}

// Apply custom configuration to the DbContext
[DbConfigurationType(typeof(MyDbConfiguration))]
class MyContext : DbContext
{
  ...
}

缓存策略

缓存策略控制缓存模式、过期时间以及应缓存的实体集。该策略默认为空,所有实体集都缓存为ReadWrite模式并且没有过期时间。可以通过实现IDbCachingPolicy接口或继承DbCachingPolicy类来配置缓存策略,下面是一个简单的示例:

csharp
public class DbCachingPolicy : IDbCachingPolicy
{
  /// <summary>
  /// Determines whether the specified query can be cached.
  /// </summary>
  public virtual bool CanBeCached(DbQueryInfo queryInfo)
  {
    // This method is called before database call.
    // Cache only Persons.
    return queryInfo.AffectedEntitySets.All(x => x.Name == "Person");
  }

  /// <summary>
  /// Determines whether specified number of rows should be cached.
  /// </summary>
  public virtual bool CanBeCached(DbQueryInfo queryInfo, int rowCount)
  {
    // This method is called after database call.
    // Cache only queries that return less than 1000 rows.
    return rowCount < 1000;
  }

  /// <summary>
  /// Gets the absolute expiration timeout for a given query.
  /// </summary>
  public virtual TimeSpan GetExpirationTimeout(DbQueryInfo queryInfo)
  {
    // Cache for 5 minutes.
    return TimeSpan.FromMinutes(5);
  }

  /// <summary>
  /// Gets the caching strategy for a given query.
  /// </summary>
  public virtual DbCachingMode GetCachingMode(DbQueryInfo queryInfo)
  {
    // Cache with invalidation.
    return DbCachingMode.ReadWrite;
  }
}

缓存模式

DbCachingMode含义
ReadOnly只读模式。永不失效,该模式中数据库更新将被忽略。查询结果被缓存后会一直保留在缓存中直到过期(不指定则永不过期)。该模式适用于预计不会更改的数据(例如国家/地区列表和其他字典数据)。
ReadWrite读写模式。当底层实体集更改时,缓存的数据将失效。这是常规的缓存模式,始终提供正确的查询结果。注意只有由配置了Ignite缓存的DbContext执行所有数据库更新,该模式才能正常工作,而其他的数据库更新则被无视。

15.3.4.app.config和web.config

通过提供IgniteDbConfiguration(或其子类)的程序集限定类型名,可以在配置文件中启用Ignite缓存:

app.config:

xml
<entityFramework codeConfigurationType="Apache.Ignite.EntityFramework.IgniteDbConfiguration, Apache.Ignite.EntityFramework">
    ...Your EF config...
</entityFramework>

15.3.5.高级配置

如果无法继承IgniteDbConfiguration(已经继承了其他类),还可以在构造函数中调用IgniteDbConfiguration.InitializeIgniteCaching静态方法,然后将this作为第一个参数:

csharp
private class MyDbConfiguration : OtherDbConfiguration
{
  public MyDbConfiguration() : base(...)
  {
    IgniteDbConfiguration.InitializeIgniteCaching(this, Ignition.GetIgnite(), null, null, null);
  }
}

18624049226