Redis Client
RedisClient
is the interface managing all the connections with Redis. We can establish a single-node or a cluster connection with it. A client can be re-used to establish as many connections as needed (recommended). However, if your use case is quite simple, you can opt for a default client to be created for you.
Establishing connection
For all the effect-based APIs the process of acquiring a client and a commands connection is quite similar, and they all return a Resource
.
Let’s have a look at the following example, which acquires a connection to the Strings API
:
import cats.effect.{IO, Resource}
import dev.profunktor.redis4cats._
import dev.profunktor.redis4cats.algebra.StringCommands
import dev.profunktor.redis4cats.connection._
import dev.profunktor.redis4cats.data.RedisCodec
import dev.profunktor.redis4cats.log4cats._
import org.typelevel.log4cats.Logger
import org.typelevel.log4cats.slf4j.Slf4jLogger
implicit val logger: Logger[IO] = Slf4jLogger.getLogger[IO]
val stringCodec: RedisCodec[String, String] = RedisCodec.Utf8
val commandsApi: Resource[IO, StringCommands[IO, String, String]] =
RedisClient[IO]
.from("redis://localhost")
.flatMap(Redis[IO].fromClient(_, stringCodec))
Redis[IO].fromClient
returns a Resource[IO, RedisCommands[IO, K, V]]
, but here we’re downcasting to a more specific API. This is not necessary but it shows how you can have more control over what commands you want a specific function to have access to. For the Strings API
is StringCommands
, for Sorted Sets API
is SortedSetCommands
, and so on. For a complete list please take a look at the algebras.
Acquiring a connection using fromClient
, we can share the same RedisClient
to establish many different connections. If you don’t need this, have a look at the following sections.
Client configuration
When you create a RedisClient
, it will use sane defaults for timeouts, auto-reconnection, etc. These defaults can be customized by providing a io.lettuce.core.ClientOptions
as well as the RedisURI
.
import dev.profunktor.redis4cats.config._
import io.lettuce.core.{ ClientOptions, TimeoutOptions }
import java.time.Duration
val mkOpts: IO[ClientOptions] =
IO {
ClientOptions.builder()
.autoReconnect(false)
.pingBeforeActivateConnection(false)
.timeoutOptions(
TimeoutOptions.builder()
.fixedTimeout(Duration.ofSeconds(10))
.build()
)
.build()
}
val api: Resource[IO, StringCommands[IO, String, String]] =
for {
opts <- Resource.eval(mkOpts)
client <- RedisClient[IO].withOptions("redis://localhost", opts)
redis <- Redis[IO].fromClient(client, stringCodec)
} yield redis
Furthermore, you can pass a customized Redis4CatsConfig
to configure behaviour which isn’t covered by io.lettuce.core.ClientOptions
:
import scala.concurrent.duration._
val config = Redis4CatsConfig().withShutdown(ShutdownConfig(1.seconds, 5.seconds))
val configuredApi: Resource[IO, StringCommands[IO, String, String]] =
for {
uri <- Resource.eval(RedisURI.make[IO]("redis://localhost"))
opts <- Resource.eval(mkOpts)
client <- RedisClient[IO].custom(uri, opts, config)
redis <- Redis[IO].fromClient(client, stringCodec)
} yield redis
A RedisURI can also be created using the redis
string interpolator:
import dev.profunktor.redis4cats.syntax.literals._
val uri = redis"redis://localhost"
val secure = redis"rediss://localhost"
val withPassword = redis"redis://:password@localhost"
val withDatabase = redis"redis://localhost/1"
val sentinel = redis"redis-sentinel://localhost:26379,localhost:26380?sentinelMasterId=m"
val `redis+ssl` = redis"redis+ssl://localhost"
val `redis+tls` = redis"redis+tls://localhost"
val `redis-socket` = redis"redis-socket:///tmp/redis.sock"
Single node connection
For those who only need a simple API access to Redis commands, there are a few ways to acquire a connection:
val simpleApi: Resource[IO, StringCommands[IO, String, String]] =
Redis[IO].simple("redis://localhost", RedisCodec.Ascii)
A simple connection with custom client options:
val simpleOptsApi: Resource[IO, StringCommands[IO, String, String]] =
Resource.eval(IO(ClientOptions.create())).flatMap { opts =>
Redis[IO].withOptions("redis://localhost", opts, RedisCodec.Ascii)
}
Or the most common one:
val utf8Api: Resource[IO, StringCommands[IO, String, String]] =
Redis[IO].utf8("redis://localhost")
Logger
In order to create a client and/or connection you must provide a Log
instance that the library uses for internal logging. You could either use log4cats
(recommended), one of the simpler instances such as NoOp
and Stdout
, or roll your own. redis4cats
can derive an instance of Log[F]
if there is an instance of Logger[F]
in scope, just need to add the extra dependency redis4cats-log4cats
and import dev.profunktor.redis4cats.log4cats._
.
Take a look at the examples to find out more.
Disable logging
If you don’t need logging at all, use the following import wherever a Log
instance is required:
// Available for any `Applicative[F]`
import dev.profunktor.redis4cats.effect.Log.NoOp._
If you need simple logging to STDOUT for quick debugging, you can use the following one:
// Available for any `Sync[F]`
import dev.profunktor.redis4cats.effect.Log.Stdout._
Standalone, Sentinel or Cluster
You can connect in any of these modes by either using JRedisURI.create
or JRedisURI.Builder
. More information
here.
Cluster connection
The process looks mostly like standalone connection but with small differences.
val clusterApi: Resource[IO, StringCommands[IO, String, String]] =
for {
uri <- Resource.eval(RedisURI.make[IO]("redis://localhost:30001"))
client <- RedisClusterClient[IO](uri)
redis <- Redis[IO].fromClusterClient(client, stringCodec)()
} yield redis
You can also make it simple if you don’t need to re-use the client.
val clusterUtf8Api: Resource[IO, StringCommands[IO, String, String]] =
Redis[IO].clusterUtf8("redis://localhost:30001")()
Master / Replica connection
The process is a bit different. First of all, you don’t need to create a RedisClient
, it’ll be created for you. All you need is RedisMasterReplica
that exposes two different constructors as Resource
.
def make[K, V](
codec: RedisCodec[K, V],
uris: RedisURI*
)(readFrom: Option[JReadFrom] = None): Resource[F, RedisMasterReplica[K, V]]
And a way to customize the underlying client options.
def withOptions[K, V](
codec: RedisCodec[K, V],
opts: ClientOptions,
uris: RedisURI*
)(readFrom: Option[JReadFrom] = None): Resource[F, RedisMasterReplica[K, V]]
Example using the Strings API
import cats.effect.{IO, Resource}
import cats.implicits._
import dev.profunktor.redis4cats.Redis
import dev.profunktor.redis4cats.algebra.StringCommands
import dev.profunktor.redis4cats.connection.RedisMasterReplica
import dev.profunktor.redis4cats.data.ReadFrom
val commands: Resource[IO, StringCommands[IO, String, String]] =
for {
uri <- Resource.eval(RedisURI.make[IO]("redis://localhost"))
conn <- RedisMasterReplica[IO].make(RedisCodec.Utf8, uri)(ReadFrom.UpstreamPreferred.some)
redis <- Redis[IO].masterReplica(conn)
} yield redis
commands.use { redis =>
redis.set("foo", "123") >> IO.unit // do something
}
Find more information here.