Using the Split C# SDK with Redis on the Azure Cloud

Split is the premier feature delivery platform. Split can enhance performance and reliability of its SDK on the server-side by having customers deploy a Redis instance and a Split Synchronizer service to keep it continually up-to-date.

Azure allows creating a Redis cache, and deploying the Split Synchronizer service (details below). Develop an Azure Function in Visual Studio, include the right Split packages, and you’ll have a function that uses Redis to initialize for feature flagging.

Using Redis lowers the latency of obtaining feature flag data, and also shelters the application code from network interruptions. You can also use the traditional CDN approach to obtaining feature flag data, in which case you would skip the steps for installing Redis and Split Synchronizer.

Installing Redis

From your Azure Portal, create an Azure Cache for Redis. Make sure your cache is in the same location as where you plan to deploy your Azure Function. I used Central US and my Redis cache was called split-dbm. My Redis hostname was split-dbm.redis.cache.windows.net and default port 6379.

Go under Access keys and copy your primary key. You’ll use this a couple of times later.

When your Redis instance is open for business, you can install the Split Synchronizer.

Installing Split Synchronizer

You can install the Synchronizer a variety of ways, as covered in the official documentation:
– Using docker with or without kubernetes
– Platform-specific binaries
– Building from source

Synchronizer is a Go application, and few customers build it from source. For Azure, I created a new virtual machine specifically for Synchronizer and gave it a ubuntu operating system. If you use Windows instead, you can download the Synchronizer from the link in the official documentation.

On a linux command line:

`curl -L -o install_linux.bin 'https://downloads.split.io/synchronizer/install_split_sync_linux.bin'  && chmod 755 install_linux.bin &&  ./install_linux.bin`

This should get you a split-sync binary. Create a default config file, like this one (spliti.azure.config.json):

split-sync -write-default-config config.json

Now edit in your own parameters:

{
"apikey": "<your split server-side api key",
"ipAddressEnabled": true,
"initialization": {
"timeoutMS": 10000,
"forceFreshStartup": false
},
"storage": {
"type": "redis",
"redis": {
"host": "xxx.xxx.xxx.xxx",
"port": 6379,
"db": 0,
"password": "<your redis password>",
"prefix": "",
...

There’s more, but important ones are at the top:

  • apikey is a Split server-side API key

You can get a server-side Split API key by opening (in Split console) one of your feature flag definitions for the right environment (prod, qa, dev, etc.) and selecting the Syntax button from the menu next to the KILL button. Go to .NET and copy the key you find generated.

The environment you choose is critical because the Synchronizer is tied to only one environment.

  • Redis host
    You should be able to use the fully qualified domain name, or the IP address of Redis.
  • Redis port
    Default is 6379
  • Redis db
    If you don’t know, it’s probably zero.

You can have many synchronizers (environments) sharing a single Redis instance by setting the prefix. I leave it with a blank default.

Then you can start the Synchronizer using a script like this one:

#!/usr/bin/bash
set -x
split-sync -config splitio.azure.config.json

Sample output (truncated):

[Split logo in ascii]
Split Synchronizer - Version: 5.1.0 (05b66c6)

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code:  gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET  /admin/dashboard  --> github.com/splitio/split-synchronizer/v5/splitio/admin/controllers.(*DashboardController).dashboard-fm (1 handlers)
[GIN-debug] GET  /admin/dashboard/segmentKeys/:segment --> github.com/splitio/split-synchronizer/v5/splitio/admin/controllers.(*DashboardController).segmentKeys-fm (1 handlers)

Now, to double check, act as a redis client from your Synchronizer’s VM. You have to use the AUTH command and provide the password you saved when you installed Redis earlier.

$ telnet foo.redis.cache.windows.net 6379

Trying 40.77.8.217...
Connected to foo.redis.cache.windows.net.
Escape character is '^]'.
AUTH <your Redis password>
+OK
keys *
*22
$33
SPLITIO.split.uhd_super_streaming
$19
SPLITIO.split.loans
...

Typing

keys *

… after you’ve authenticated should dump a list of your feature flags. I have a few dozen; you may only have a few. But look for your feature flag names.

If Redis is empty of keys, the Synchronizer isn’t talking to it. You may need to tinker with the network configuration of your Synchronizer’s VM to open port 6379 to Redis.

If everything looks good, you can nohup it and move on.

nohup split-sync -config splitio.azure.config.json &

Building the Azure Function

Experiments with building at the portal itself were frustrating. Use Visual Studio.

It is hard describe in writing the steps to take with the GUI, but these are the important ones:

  • Create a new Azure Function
  • Select the HTTP Trigger

You can publish this function to the Azure cloud and test it from the portal. It expects an input like:

{
  "name": "Layla"
}

If your function prints out a happy response, continue by adding Split.

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Splitio.Services.Client.Classes;

namespace VitaminC
{
    public static class VitaminC
    {
        [FunctionName("VitaminC")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");
            // var config = new ConfigurationOptions();
            long startTimeInMillis = DateTimeOffset.Now.ToUnixTimeMilliseconds();

            var cacheAdapterConfigurationOptions = new CacheAdapterConfigurationOptions
            {
                Type = AdapterType.Redis,
                Host = "split-dbm.redis.cache.windows.net",
                Port = "6379",
                Password = "<redis password here>",
                Database = 0,
                ConnectTimeout = 5000,
                ConnectRetry = 3,
                SyncTimeout = 1000,
                UserPrefix = ""
            };
            var config = new ConfigurationOptions
            {
                Mode = Mode.Consumer,
                CacheAdapterConfig = cacheAdapterConfigurationOptions
            };

            // var config = new ConfigurationOptions();

            var factory = new SplitFactory("<your split server-side api key>", config);
            var sdk = factory.Client();

            try
            {
                sdk.BlockUntilReady(3000);
                long endTimeInMillis = DateTimeOffset.Now.ToUnixTimeMilliseconds();
                log.LogInformation("SDK ready in " + (endTimeInMillis - startTimeInMillis) + "ms");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

            log.LogInformation("dmartin treatment? " + sdk.GetTreatment("dmartin", "new_onboarding"));

            var result = sdk.GetTreatmentWithConfig("dmartin", "multivariant_demo");
            log.LogInformation("split json? " + result.Config);

            string name = req.Query["name"];

            // bool success = client.Track(name, "user", "function_triggered");

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            string responseMessage = string.IsNullOrEmpty(name)
                ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
                        : $"Hello, {name}. The treatment is {result.Treatment}.\n {result.Config}";

            return new OkObjectResult(responseMessage);
        }
    }
}

Note that my namespace, function, and class name are all “VitaminC”. You can leave that part of your code alone and swap only the body of the trigger function. Remember to insert your own Redis password (you captured it during install) and create a new Split server-side API key. You can get a server-side Split API key by opening one of your feature flag definitions for the right environment (prod, qa, dev, etc.) and selecting the Syntax button from the menu next to the KILL button. Go to .NET and copy the key you find generated.

Then add using Splitio.Services.Client.Classes;. You can find this discussed on the official .NET SDK documentation page.

Finally, you must Nuget the Splitio and Splitio.Redis packages (v7.0.1 at time of writing). At this point, all compilation errors should disappear.

Build locally to check for hidden errors, then publish and test!

Troubleshooting

You should be able to telnet to port 6379 of your redis host from your Synchronizer virtual machine. This lets you test that the port is open, and also verify that Synchronizer has begun writing flags to Redis.

Managing the Splitio and Splitio.Redis dependencies proved impossible for me on the portal itself (using function.proj). Build and publish your Azure functions from Visual Studio.

Written with StackEdit.