Redis

How to create a cache and a lock using Redis.

Key takeaways

RedisCache Distributed cache that stores values in a Redis in-memory database.
RedisLock Distributed lock that is implemented based on a Redis in-memory database.

Introduction

In this tutorial, you will see how to use two components related to the Redis database. The first is the RedisCache class, which can be used to create distributed caches that store values in Redis. The second is RedisLock, a component that allows us to create a distributed lock based on the Redis database.

Pre-requisites

Before using this library, we need to install the Redis module. This can be done with the following command:

npm install pip-services-redis-nodex --save

dotnet add package PipServices3.Redis

go get -u github.com/pip-services3-gox/pip-services3-redis-gox@latest

dart pub add pip_services3_redis

pip install pip-services3-redis

Not available

RedisCache

This component provides a way to create a distributed cache that stores values in a Redis database. The sections below explain how to create this cache and perform basic CRUD operations with it.

Pre-requisites

In order to use the RedisCache component, we need to import it first. The command to do this is:

import { RedisCache } from 'pip-services3-redis-nodex';
using PipServices3.Redis.Cache;
import (
    rcache "github.com/pip-services3-gox/pip-services3-redis-gox/cache"
)
import 'package:pip_services3_redis/pip_services3_redis.dart';
from pip_services3_redis.cache import RedisCache
Not available

Component creation

To be able to interact with a Redis database, we need to create an instance of the RedisCache component and configure it. The following example shows how to do this.

import { ConfigParams } from 'pip-services3-commons-nodex';

let cache = new RedisCache();
cache.configure(ConfigParams.fromTuples(
    "connection.host", "localhost",
    "connection.port", 6379
));
using PipServices3.Commons.Config;

var cache = new RedisCache();
cache.Configure(ConfigParams.FromTuples(
    "connection.host", "localhost",
    "connection.port", 6379
));
import (
	conf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
	rcache "github.com/pip-services3-gox/pip-services3-redis-gox/cache"
)


cache := rcache.NewRedisCache()
cache.Configure(context.Background(), conf.NewConfigParamsFromTuples(
	"connection.host", "localhost",
	"connection.port", 6379,
))
import 'package:pip_services3_commons/pip_services3_commons.dart';
import 'package:pip_services3_redis/pip_services3_redis.dart';

var cache = RedisCache();
cache.configure(ConfigParams.fromTuples(
    ['connection.host', 'localhost', 'connection.port', 6379]));
from pip_services3_commons.config import ConfigParams

cache = RedisCache()
cache.configure(ConfigParams.from_tuples(
    "connection.host", "localhost",
    "connection.port", 6379
))
Not available

Once our component has been configured, we can connect it to the database via the open() method:

await cache.open(null);
await cache.OpenAsync(null);
err := cache.Open(context.Background(), "123")
await cache.open(null);
cache.open("123")
Not available

Once our task has been completed, we can free used resources by closing this component with the close() method, which takes the correlationId as its only input parameter.

await cache.close(null);
await cache.CloseAsync(null);
err = cache.Close(context.Background(), "123")
await cache.close(null);
cache.close('123')
Not available

CRUD operations

The RedisCache component offers the necessary methods to manage the interaction with a Redis database and perform all the CRUD operations. They are:

Create and update

We can use the store() method to create a new entry. This method accepts the correlationId, key, value, and expiration time as inputs. It returns True if the operation was successful and False otherwise. If the key already exists in the database, it updates the old value with the new value. An example of its usage is.

let result = await cache.store(null, "key1", "ABC", 10000); // Returns "OK" if successful 
var result = await cache.StoreAsync(null, "key1", "ABC", 10000); // Returns True if successful and False otherwise 
result, err := cache.Store(context.Background(), "123", "key1", "ABC", 10000) // Returns saved value and err or nil 
var result = await cache.store(null, 'key1', 'ABC', 10000); // Returns "OK" if successful
result = cache.store("123", "key1", "ABC", None)  # Returns True if successful and False otherwise 
Not available
Read

We can read a value from the database with the retrieve() method, which given a correlationId and a key, returns the corresponding value.

result = await cache.retrieve(null, "key1");  // Returns "ABC"
result = await cache.RetrieveAsync<string>(null, "key1");  // Returns "ABC"
res, err := cache.Retrieve(context.Background(), "123", "key1")  // Returns "ABC"
result = await cache.retrieve(null, 'key1'); // Returns "ABC"
result = cache.retrieve('123', 'key1')  # Returns b'ABC'
Not available
Delete

To delete a record from the database, we can use the remove method. This method accepts the correlationId and the key as inputs and returns 1 if the removal was successful and 0 otherwise.

await cache.remove(null, "key1");
await cache.RemoveAsync(null, "key1");
err := cache.Remove(context.Background(), "123", "key1")
await cache.remove(null, 'key1');
result = cache.remove('123','key1') # Returns 1 if successful and 0 otherwise
Not available

RedisLock

This component can be used to create a distributed lock that is implemented using a Redis database.

Pre-requisites

In order to use this lock, we need to import the RedisLock class first. This can be done with the following command:

import { RedisLock } from 'pip-services3-redis-nodex';
using PipServices3.Redis.Lock;
import (
    rlock "github.com/pip-services3-gox/pip-services3-redis-gox/lock"
)
import 'package:pip_services3_redis/pip_services3_redis.dart';
from pip_services3_redis.lock import RedisLock
Not available

Component creation

Now, we can create our lock by defining an instance of the class and configuring it. For this, it is convenient to use the ConfigParams component, which creates a key-value map with the configuration parameters and their values. The example below shows how to do this.

import { ConfigParams } from 'pip-services3-commons-nodex';

let lock = new RedisLock();
lock.configure(ConfigParams.fromTuples(
    "connection.host", "localhost",
    "connection.port", 6379
));

using PipServices3.Commons.Config;

var rLock = new RedisLock();
rLock.Configure(ConfigParams.FromTuples(
    "connection.host", "localhost",
    "connection.port", 6379
));

import (
	conf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
	rlock "github.com/pip-services3-gox/pip-services3-redis-gox/lock"
)


lock := rlock.NewRedisLock()
lock.Configure(context.Background(), conf.NewConfigParamsFromTuples(
	"connection.host", "localhost",
	"connection.port", 6379,
))

import 'package:pip_services3_commons/pip_services3_commons.dart';
import 'package:pip_services3_redis/pip_services3_redis.dart';

var lock = RedisLock();
lock.configure(ConfigParams.fromTuples(
    ['connection.host', 'localhost', 'connection.port', 6379]));

from pip_services3_commons.config import ConfigParams

lock = RedisLock()
lock.configure(ConfigParams.from_tuples(
    "connection.host", "localhost",
    "connection.port", 6379
))

Not available

Once our component has been defined, we can connect it to the Redis database with the open() method, which accepts the correlationId as an input parameter.

await lock.open(null);
await rLock.OpenAsync(null);
err := lock.Open("123")
await lock.open(null);
lock.open("123")
Not available

Later on, once we have completed our task, we can close the lock with the close() method to free used resources.

await lock.close(null);
await rLock.CloseAsync(null);
err = lock.Close(context.Background(), "123")
await lock.close(null);
lock.close('123')
Not available

Managing the lock

Once our lock is ready, we need to manage it. For this, we have two main operations: acquire and release, which can be executed with a set of methods available in this class.

Acquire

Here, we have two different methods. They are as follows:

tryAcquireLock()

To acquire our lock, we can use the try_acquire_lock() method. This method makes a single attempt to acquire the lock and returns a Boolean value indicating success or failure. It accepts the correlationId, the key, and the timeout in milliseconds as input parameters. The following code explains its usage.

let locked = await lock.tryAcquireLock(null, "key1", 3300);
var locked = rLock.TryAcquireLock(null, "key1", 3300);
locked, err := lock.TryAcquireLock(context.Background(), "123", "key1", 3300)
var locked = await lock.tryAcquireLock(null, 'key1', 3300);
locked = lock.try_acquire_lock("123", "key1", 3300) # returns bool
Not available
acquireLock()

Alternatively, we could use the acquireLock() method, which makes multiple attempts to acquire the lock. This class inherits this method from the Lock class, which it extends.

This method accepts the correlationId, key, timeout, and acquisition timeout as input parameters. The following example shows how to use it.

await lock.acquireLock(null, "key1", 3000, 1000);
rLock.AcquireLock(null, "key1", 3000, 1000);
err = lock.AcquireLock(context.Background(), "123", "key1", 3000, 1000)
await lock.acquireLock(null, 'key1', 3000, 1000);
lock.acquire_lock("123", "key1", 3000, 1000)
Not available
Release

Once the lock has completed its function, it needs to be released. This can be done with the releaseLock() method, which takes the correlationId and the key as input parameters. The following example shows how to release a lock.

await lock.releaseLock(null, "key1");
rLock.ReleaseLock(null, "key1");
err = lock.ReleaseLock(context.Background(), "123", "key1")
await lock.releaseLock(null, 'key1');
lock.release_lock("123", "key1")
Not available

Example

The example below summarizes what we learned in the previous sections. In it, a lock is created and configured. Then, it is connected to the database. Once connected, it is acquired, some task processing is done, and after this task is completed, the lock is released.

However, an important point to consider is that if the task was not completed before the timeout expires, the lock is released anyway.

import { RedisLock } from 'pip-services3-redis-nodex';
import { ConfigParams } from 'pip-services3-commons-nodex';

let lock = new RedisLock();
lock.configure(ConfigParams.fromTuples(
    "connection.host", "localhost",
    "connection.port", 6379
));

await lock.open(null);
await lock.acquireLock(null, "key1", 3000, 1000);

try {
    // Processing...
} finally {
    await lock.releaseLock(null, "key1");
}
using PipServices3.Redis.Lock;
using PipServices3.Commons.Config;

var rLock = new RedisLock();
rLock.Configure(ConfigParams.FromTuples(
    "connection.host", "localhost",
    "connection.port", 6379
));


await rLock.OpenAsync(null);

rLock.AcquireLock(null, "key1", 3000, 1000);

try
{
    // Processing...
} finally
{
    rLock.ReleaseLock(null, "key1");
}
import (
	conf "github.com/pip-services3-gox/pip-services3-commons-gox/config"
	rlock "github.com/pip-services3-gox/pip-services3-redis-gox/lock"
)

lock := rlock.NewRedisLock()
lock.Configure(context.Background(), conf.NewConfigParamsFromTuples(
	"connection.host", "localhost",
	"connection.port", 6379,
))
err := lock.Open(context.Background(), "123")

err = lock.AcquireLock(context.Background(), "123", "key1", 3000, 1000)

defer lock.ReleaseLock(context.Background(), "123", "key1")

// Processing...
import 'package:pip_services3_commons/pip_services3_commons.dart';
import 'package:pip_services3_redis/pip_services3_redis.dart';

var lock = RedisLock();
lock.configure(ConfigParams.fromTuples(
  ['connection.host', 'localhost', 'connection.port', 6379]));

await lock.open(null);
await lock.acquireLock(null, 'key1', 3000, 1000);

try {
  // Processing...
} finally {
  await lock.releaseLock(null, 'key1');
}
from pip_services3_redis.lock import RedisLock
from pip_services3_commons.config import ConfigParams

lock = RedisLock()
lock.configure(ConfigParams.from_tuples(
    "connection.host", "localhost",
    "connection.port", 6379
))

lock.open("123")

lock.acquire_lock("123", "key1", 3000, 1000)

try:
    # Processing...
    pass
finally:
    lock.release_lock("123", "key1")
Not available

Wrapping up

In this tutorial, we have learned how to use the RedisCache and RedisLock components. The first is used to create a distributed cache that stores values in Redis. The second helps us create distributed locks based on a Redis database.

For the RedisCache component, we saw how to perform the different CRUD operations and, for the RedisLock component, we understood how to manage it in practical situations.