October 20th, Q&A session: Get you issues solved and questions answered!

GitHub logo
Edit

.NET Thin Client

Prerequisites

  • Supported runtimes: .NET 4.0+, .NET Core 2.0+

  • Supported OS: Windows, Linux, macOS (any OS supported by .NET Core 2.0+)

Installation

The .NET thin client API is provided by the Ignite.NET API library, which is located in the {IGNITE_HOME}/platforms/dotnet directory of the Ignite distribution package. The API is located in the Apache.Ignite.Core assembly.

Connecting to Cluster

The thin client API entry point is the Ignition.StartClient(IgniteClientConfiguration) method. The IgniteClientConfiguration.Endpoints property is mandatory; it must point to the host where the server node is running.

var cfg = new IgniteClientConfiguration
{
    Endpoints = new[] {"127.0.0.1:10800"}
};

using (var client = Ignition.StartClient(cfg))
{
    var cache = client.GetOrCreateCache<int, string>("cache");
    cache.Put(1, "Hello, World!");
}

Failover

You can provide multiple node addresses. In this case thin client connects to a random node in the list, and failover mechanism is enabled: if a server node fails, client tries other known addresses and reconnects automatically. Note that IgniteClientException can be thrown if a server node fails while client operation is being performed - user code should handle this exception and implement retry logic accordingly.

Automatic Server Node Discovery

Thin client can discover server nodes in the cluster automatically. This behavior is enabled when Partition Awareness is enabled.

Server discovery is an asynchronous process - it happens in the background. Additionally, thin client receives topology updates only when it performs some operations (to minimize server load and network traffic from idle connections).

You can observe the discovery process by enabling logging and/or calling IIgniteClient.GetConnections:

var cfg = new IgniteClientConfiguration
{
    Endpoints = new[] {"127.0.0.1:10800"},
    EnablePartitionAwareness = true,

    // Enable trace logging to observe discovery process.
    Logger = new ConsoleLogger { MinLevel = LogLevel.Trace }
};

var client = Ignition.StartClient(cfg);

// Perform any operation and sleep to let the client discover
// server nodes asynchronously.
client.GetCacheNames();
Thread.Sleep(1000);

foreach (IClientConnection connection in client.GetConnections())
{
    Console.WriteLine(connection.RemoteEndPoint);
}
Warning

Server discovery may not work when servers are behind a NAT server or a proxy. Server nodes provide their addresses and ports to the client, but when the client is in a different subnet, those addresses won’t work.

Partition Awareness

Partition awareness allows the thin client to send query requests directly to the node that owns the queried data.

Warning

Partition awareness is an experimental feature whose API or design architecture might change before a GA version is released.

Without partition awareness, an application that is connected to the cluster via a thin client executes all queries and operations via a single server node that acts as a proxy for the incoming requests. These operations are then re-routed to the node that stores the data that is being requested. This results in a bottleneck that could prevent the application from scaling linearly.

Without Partition Awareness

Notice how queries must pass through the proxy server node, where they are routed to the correct node.

With partition awareness in place, the thin client can directly route queries and operations to the primary nodes that own the data required for the queries. This eliminates the bottleneck, allowing the application to scale more easily.

With Partition Awareness

To enable partition awareness, set the IgniteClientConfiguration.EnablePartitionAwareness property to true. This enables server discovery as well. If the client is behind a NAT or a proxy, automatic server discovery may not work. In this case provide addresses of all server nodes in the client’s connection configuration.

Using Key-Value API

Getting Cache Instance

The ICacheClient interface provides the key-value API. You can use the following methods to obtain an instance of ICacheClient:

  • GetCache(cacheName) — returns an instance of an existing cache.

  • CreateCache(cacheName) — creates a cache with the given name.

  • GetOrCreateCache(CacheClientConfiguration) — gets or creates a cache with the given configuration.

var cacheCfg = new CacheClientConfiguration
{
    Name = "References",
    CacheMode = CacheMode.Replicated,
    WriteSynchronizationMode = CacheWriteSynchronizationMode.FullSync
};
var cache = client.GetOrCreateCache<int, string>(cacheCfg);

Use IIgniteClient.GetCacheNames() to obtain a list of all existing caches.

Basic Operations

The following code snippet demonstrates how to execute basic cache operations on a specific cache.

var data = Enumerable.Range(1, 100).ToDictionary(e => e, e => e.ToString());

cache.PutAll(data);

var replace = cache.Replace(1, "2", "3");
Console.WriteLine(replace); //false

var value = cache.Get(1);
Console.WriteLine(value); //1

replace = cache.Replace(1, "1", "3");
Console.WriteLine(replace); //true

value = cache.Get(1);
Console.WriteLine(value); //3

cache.Put(101, "101");

cache.RemoveAll(data.Keys);
var sizeIsOne = cache.GetSize() == 1;
Console.WriteLine(sizeIsOne); //true

value = cache.Get(101);
Console.WriteLine(value); //101

cache.RemoveAll();
var sizeIsZero = cache.GetSize() == 0;
Console.WriteLine(sizeIsZero); //true

Working With Binary Objects

The .NET thin client supports the Binary Object API described in the Working with Binary Objects section. Use ICacheClient.WithKeepBinary() to switch the cache to binary mode and start working directly with binary objects avoiding serialization/deserialization. Use IIgniteClient.GetBinary() to get an instance of IBinary and build an object from scratch.

var binary = client.GetBinary();

var val = binary.GetBuilder("Person")
    .SetField("id", 1)
    .SetField("name", "Joe")
    .Build();

var cache = client.GetOrCreateCache<int, object>("persons").WithKeepBinary<int, IBinaryObject>();

cache.Put(1, val);

var value = cache.Get(1);

Scan Queries

Use a scan query to get a set of entries that satisfy a given condition. The thin client sends the query to the cluster node where it is executed as a normal scan query.

The query condition is specified by an ICacheEntryFilter object that is passed to the query constructor as an argument.

Define a query filter as follows:

class NameFilter : ICacheEntryFilter<int, Person>
{
    public bool Invoke(ICacheEntry<int, Person> entry)
    {
        return entry.Value.Name.Contains("Smith");
    }
}

Then execute the scan query:

var cache = client.GetOrCreateCache<int, Person>("personCache");

cache.Put(1, new Person {Name = "John Smith"});
cache.Put(2, new Person {Name = "John Johnson"});

using (var cursor = cache.Query(new ScanQuery<int, Person>(new NameFilter())))
{
    foreach (var entry in cursor)
    {
        Console.WriteLine("Key = " + entry.Key + ", Name = " + entry.Value.Name);
    }
}

Executing SQL Statements

The thin client provides a SQL API to execute SQL statements. SQL statements are declared using SqlFieldsQuery objects and executed through the ICacheClient.Query(SqlFieldsQuery) method. Alternatively, SQL queries can be performed via Ignite LINQ provider.

var cache = client.GetOrCreateCache<int, Person>("Person");
cache.Query(new SqlFieldsQuery(
        $"CREATE TABLE IF NOT EXISTS Person (id INT PRIMARY KEY, name VARCHAR) WITH \"VALUE_TYPE={typeof(Person)}\"")
    {Schema = "PUBLIC"}).GetAll();

var key = 1;
var val = new Person {Id = key, Name = "Person 1"};

cache.Query(
    new SqlFieldsQuery("INSERT INTO Person(id, name) VALUES(?, ?)")
    {
        Arguments = new object[] {val.Id, val.Name},
        Schema = "PUBLIC"
    }
).GetAll();

var cursor = cache.Query(
    new SqlFieldsQuery("SELECT name FROM Person WHERE id = ?")
    {
        Arguments = new object[] {key},
        Schema = "PUBLIC"
    }
);

var results = cursor.GetAll();

var first = results.FirstOrDefault();
if (first != null)
{
    Console.WriteLine("name = " + first[0]);
}

Using Cluster API

The cluster APIs let you create a group of cluster nodes and run various operations against the group. The IClientCluster interface is the entry-point to the APIs that can be used as follows:

  • Get or change the state of a cluster

  • Get a list of all cluster nodes

  • Create logical groups our of cluster nodes and use other Ignite APIs to perform certain operations on the group

Use the instance of IClientCluster to obtain a reference to the IClientCluster that comprises all cluster nodes, and activate the whole cluster as well as write-ahead-logging for the my-cache cache:

IIgniteClient client = Ignition.StartClient(cfg);
IClientCluster cluster = client.GetCluster();
cluster.SetActive(true);
cluster.EnableWal("my-cache");

Logical nodes grouping

You can use the IClientClusterGroup interface of the cluster APIs to create various groups of cluster nodes. For instance, one group can comprise all servers nodes, while the other group can include only those nodes that match a specific TCP/IP address format. The example below shows how to create a group of server nodes located in the dc1 data center:

IIgniteClient client = Ignition.StartClient(cfg);
IClientClusterGroup serversInDc1 = client.GetCluster().ForServers().ForAttribute("dc", "dc1");
foreach (IClientClusterNode node in serversInDc1.GetNodes())
    Console.WriteLine($"Node ID: {node.Id}");

Note, the IClientCluster instance implements IClientClusterGroup which is the root cluster group that includes all nodes of the cluster.

Refer to the main cluster groups documentation page for more details on the capability.

Executing Compute Tasks

Presently, the .NET thin client supports basic compute capabilities by letting you execute those compute tasks that are already deployed in the cluster. You can either run a task across all cluster nodes or a specific cluster group.

By default, the execution of tasks, triggered by the thin client, is disabled on the cluster side. You need to set the ThinClientConfiguration.MaxActiveComputeTasksPerConnection parameter to a non-zero value in the configuration of your server nodes and thick clients:

<bean class="org.apache.ignite.configuration.IgniteConfiguration" id="ignite.cfg">
  <property name="clientConnectorConfiguration">
    <bean class="org.apache.ignite.configuration.ClientConnectorConfiguration">
      <property name="thinClientConfiguration">
        <bean class="org.apache.ignite.configuration.ThinClientConfiguration">
          <property name="maxActiveComputeTasksPerConnection" value="100" />
        </bean>
      </property>
    </bean>
  </property>
</bean>
var igniteCfg = new IgniteConfiguration
{
    ClientConnectorConfiguration = new ClientConnectorConfiguration
    {
        ThinClientConfiguration = new ThinClientConfiguration
        {
            MaxActiveComputeTasksPerConnection = 10
        }
    }
};

IIgnite ignite = Ignition.Start(igniteCfg);

The example below shows how to get access to the compute APIs via the IComputeClient interface and execute the compute task named org.foo.bar.AddOneTask passing 1 as an input parameter:

IIgniteClient client = Ignition.StartClient(cfg);
IComputeClient compute = client.GetCompute();
int result = compute.ExecuteJavaTask<int>("org.foo.bar.AddOneTask", 1);

Security

SSL/TLS

To use encrypted communication between the thin client and the cluster, you have to enable SSL/TLS in both the cluster configuration and the client configuration. Refer to the Enabling SSL/TLS for Thin Clients section for the instruction on the cluster configuration.

The following code example demonstrates how to configure SSL parameters in the thin client.

var cfg = new IgniteClientConfiguration
{
    Endpoints = new[] {"127.0.0.1:10800"},
    SslStreamFactory = new SslStreamFactory
    {
        CertificatePath = ".../certs/client.pfx",
        CertificatePassword = "password",
    }
};
using (var client = Ignition.StartClient(cfg))
{
    //...
}

Authentication

Configure authentication on the cluster side and provide a valid user name and password in the client configuration.

var cfg = new IgniteClientConfiguration
{
    Endpoints = new[] {"127.0.0.1:10800"},
    UserName = "ignite",
    Password = "ignite"
};
using (var client = Ignition.StartClient(cfg))
{
    //...
}