jedis
Redis向けのシンプルで高速なJava/Kotlinクライアント。同期的なAPIと豊富なRedis機能サポートで、エンタープライズアプリケーションに最適。
GitHub概要
redis/jedis
Redis Java client
スター12,173
ウォッチ675
フォーク3,902
作成日:2010年6月11日
言語:Java
ライセンス:MIT License
トピックス
javajedisredisredis-clientredis-cluster
スター履歴
データ取得日時: 2025/10/22 09:55
概要
JedisはRedis向けの人気のあるJava/Kotlinクライアントライブラリです。シンプルで直感的なAPIを提供し、Redisのほぼすべての機能をサポートしています。同期的な設計により、理解しやすく使いやすいのが特徴です。エンタープライズアプリケーションやAndroidアプリケーションでの採用実績も豊富です。
主な特徴
- 完全なRedis機能サポート: すべてのRedisコマンドに対応
- 接続プール: JedisPoolによる効率的な接続管理
- クラスタ対応: Redis Clusterのネイティブサポート
- Sentinel対応: 高可用性構成のサポート
- パイプライン: バッチ処理による高速化
- トランザクション: MULTI/EXECによるアトミック操作
注意事項
Jedisは**同期的(ブロッキング)**なライブラリです。Kotlinコルーチンと組み合わせる場合は、必ずDispatchers.IOでラップする必要があります。非同期処理が必要な場合は、Lettuceなどの非同期クライアントの使用を検討してください。
インストール
Gradle (Kotlin DSL)
dependencies {
implementation("redis.clients:jedis:5.1.3")
}
Android プロジェクトでの設定
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />
// build.gradle.kts
android {
// ProGuard設定(必要に応じて)
buildTypes {
release {
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
}
dependencies {
implementation("redis.clients:jedis:5.1.3")
}
基本的な使い方
接続プールの設定(推奨)
import redis.clients.jedis.JedisPool
import redis.clients.jedis.JedisPoolConfig
// シングルトンパターンで接続プールを管理
object RedisClient {
val pool: JedisPool by lazy {
val poolConfig = JedisPoolConfig().apply {
maxTotal = 10 // 最大接続数
maxIdle = 5 // 最大アイドル接続数
minIdle = 2 // 最小アイドル接続数
testOnBorrow = true // 接続時のヘルスチェック
testWhileIdle = true // アイドル時のヘルスチェック
}
// JedisPool(poolConfig, host, port, timeout, password)
JedisPool(poolConfig, "localhost", 6379, 3000, null)
}
}
// 使用例
fun example() {
RedisClient.pool.resource.use { jedis ->
jedis.set("greeting", "こんにちは")
val value = jedis.get("greeting")
println(value) // こんにちは
}
}
高度な使い方
Kotlinコルーチンとの統合
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import redis.clients.jedis.JedisPool
class RedisRepository(private val pool: JedisPool) {
// 文字列の操作
suspend fun setString(key: String, value: String, exSeconds: Long? = null): String =
withContext(Dispatchers.IO) {
pool.resource.use { jedis ->
if (exSeconds != null) {
jedis.setex(key, exSeconds, value)
} else {
jedis.set(key, value)
}
}
}
suspend fun getString(key: String): String? =
withContext(Dispatchers.IO) {
pool.resource.use { jedis ->
jedis.get(key)
}
}
// リストの操作
suspend fun pushToList(key: String, vararg values: String): Long =
withContext(Dispatchers.IO) {
pool.resource.use { jedis ->
jedis.lpush(key, *values)
}
}
suspend fun getListRange(key: String, start: Long = 0, stop: Long = -1): List<String> =
withContext(Dispatchers.IO) {
pool.resource.use { jedis ->
jedis.lrange(key, start, stop)
}
}
// ハッシュの操作
suspend fun setHash(key: String, field: String, value: String): Long =
withContext(Dispatchers.IO) {
pool.resource.use { jedis ->
jedis.hset(key, field, value)
}
}
suspend fun setHashMap(key: String, map: Map<String, String>): String =
withContext(Dispatchers.IO) {
pool.resource.use { jedis ->
jedis.hset(key, map)
}
}
suspend fun getHash(key: String): Map<String, String> =
withContext(Dispatchers.IO) {
pool.resource.use { jedis ->
jedis.hgetAll(key)
}
}
// セットの操作
suspend fun addToSet(key: String, vararg members: String): Long =
withContext(Dispatchers.IO) {
pool.resource.use { jedis ->
jedis.sadd(key, *members)
}
}
suspend fun getSetMembers(key: String): Set<String> =
withContext(Dispatchers.IO) {
pool.resource.use { jedis ->
jedis.smembers(key)
}
}
}
Redis Clusterの設定
import redis.clients.jedis.HostAndPort
import redis.clients.jedis.JedisCluster
import redis.clients.jedis.JedisPoolConfig
object RedisClusterClient {
val cluster: JedisCluster by lazy {
val clusterNodes = setOf(
HostAndPort("127.0.0.1", 7000),
HostAndPort("127.0.0.1", 7001),
HostAndPort("127.0.0.1", 7002)
)
val poolConfig = JedisPoolConfig().apply {
maxTotal = 50
maxIdle = 10
minIdle = 5
}
JedisCluster(
clusterNodes,
3000, // connectionTimeout
3000, // soTimeout
5, // maxAttempts
"password", // password (nullの場合は認証なし)
poolConfig
)
}
}
// 使用例
suspend fun clusterExample() = withContext(Dispatchers.IO) {
RedisClusterClient.cluster.set("{user}:1", "John")
RedisClusterClient.cluster.set("{user}:2", "Jane")
// {user}は同じハッシュスロットを使用することを保証
}
Redis Sentinelの設定
import redis.clients.jedis.JedisSentinelPool
object RedisSentinelClient {
val pool: JedisSentinelPool by lazy {
val sentinels = setOf(
"127.0.0.1:26379",
"127.0.0.1:26380",
"127.0.0.1:26381"
)
val poolConfig = JedisPoolConfig().apply {
maxTotal = 10
maxIdle = 5
minIdle = 2
}
JedisSentinelPool(
"mymaster", // masterName
sentinels,
poolConfig,
3000, // timeout
"password" // password
)
}
}
実践的な例
トランザクション処理
class BankingService(private val pool: JedisPool) {
suspend fun transferMoney(
fromAccount: String,
toAccount: String,
amount: Double
): TransferResult = withContext(Dispatchers.IO) {
pool.resource.use { jedis ->
val fromKey = "account:$fromAccount:balance"
val toKey = "account:$toAccount:balance"
// トランザクション開始前にキーを監視
jedis.watch(fromKey)
val currentBalance = jedis.get(fromKey)?.toDoubleOrNull() ?: 0.0
if (currentBalance < amount) {
jedis.unwatch()
return@withContext TransferResult.InsufficientFunds
}
// トランザクション開始
val transaction = jedis.multi()
try {
transaction.incrByFloat(fromKey, -amount)
transaction.incrByFloat(toKey, amount)
transaction.lpush("transactions", "$fromAccount->$toAccount:$amount")
val results = transaction.exec()
if (results == null) {
// WATCHしたキーが変更された
TransferResult.ConcurrentModification
} else {
TransferResult.Success
}
} catch (e: Exception) {
transaction.discard()
TransferResult.Error(e.message ?: "Unknown error")
}
}
}
}
sealed class TransferResult {
object Success : TransferResult()
object InsufficientFunds : TransferResult()
object ConcurrentModification : TransferResult()
data class Error(val message: String) : TransferResult()
}
パイプライン処理
class AnalyticsService(private val pool: JedisPool) {
suspend fun recordBulkEvents(events: List<Event>) = withContext(Dispatchers.IO) {
pool.resource.use { jedis ->
val pipeline = jedis.pipelined()
events.forEach { event ->
// 複数のコマンドをバッチ処理
pipeline.hincrBy("analytics:${event.type}", event.date, 1)
pipeline.zadd("events:${event.type}", event.timestamp.toDouble(), event.id)
pipeline.expire("events:${event.type}", 86400) // 1日で期限切れ
}
// すべてのコマンドを一度に送信
pipeline.sync()
}
}
suspend fun getBulkData(keys: List<String>): Map<String, String?> =
withContext(Dispatchers.IO) {
pool.resource.use { jedis ->
val pipeline = jedis.pipelined()
val responses = keys.map { key ->
key to pipeline.get(key)
}
pipeline.sync()
responses.associate { (key, response) ->
key to response.get()
}
}
}
}
data class Event(
val id: String,
val type: String,
val date: String,
val timestamp: Long
)
Kotlin拡張関数
// JedisExtensions.kt
import redis.clients.jedis.Jedis
import redis.clients.jedis.params.SetParams
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.decodeFromString
/**
* JSONオブジェクトとして保存・取得
*/
inline fun <reified T> Jedis.setJson(key: String, value: T, exSeconds: Long? = null) {
val json = Json.encodeToString(value)
if (exSeconds != null) {
setex(key, exSeconds, json)
} else {
set(key, json)
}
}
inline fun <reified T> Jedis.getJson(key: String): T? {
val json = get(key) ?: return null
return try {
Json.decodeFromString<T>(json)
} catch (e: Exception) {
null
}
}
/**
* アトミックな条件付き設定
*/
fun Jedis.setIfAbsent(key: String, value: String, exSeconds: Long? = null): Boolean {
val params = SetParams().nx() // SET if Not eXists
if (exSeconds != null) {
params.ex(exSeconds)
}
return set(key, value, params) == "OK"
}
/**
* 複数キーの一括取得
*/
fun Jedis.mgetAsMap(vararg keys: String): Map<String, String> {
if (keys.isEmpty()) return emptyMap()
val values = mget(*keys)
return keys.zip(values)
.filter { it.second != null }
.associate { it.first to it.second!! }
}
// 使用例
data class User(val id: Long, val name: String, val email: String)
suspend fun extensionExample(pool: JedisPool) = withContext(Dispatchers.IO) {
pool.resource.use { jedis ->
// JSONとして保存
val user = User(1, "山田太郎", "[email protected]")
jedis.setJson("user:1", user, 3600)
// JSONとして取得
val retrieved = jedis.getJson<User>("user:1")
println(retrieved?.name)
// 条件付き設定
val wasSet = jedis.setIfAbsent("lock:resource", "locked", 30)
if (wasSet) {
// ロック取得成功
}
}
}
エラーハンドリングとリトライ
import kotlinx.coroutines.delay
import redis.clients.jedis.exceptions.JedisConnectionException
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
class ResilientRedisClient(private val pool: JedisPool) {
suspend fun <T> executeWithRetry(
maxAttempts: Int = 3,
initialDelay: Duration = 100.milliseconds,
maxDelay: Duration = 1000.milliseconds,
factor: Double = 2.0,
operation: suspend (jedis: redis.clients.jedis.Jedis) -> T
): T {
var currentDelay = initialDelay
var lastException: Exception? = null
repeat(maxAttempts) { attempt ->
try {
return withContext(Dispatchers.IO) {
pool.resource.use { jedis ->
operation(jedis)
}
}
} catch (e: JedisConnectionException) {
lastException = e
if (attempt < maxAttempts - 1) {
delay(currentDelay)
currentDelay = (currentDelay.inWholeMilliseconds * factor)
.coerceAtMost(maxDelay.inWholeMilliseconds)
.milliseconds
}
}
}
throw lastException ?: IllegalStateException("Retry failed")
}
// 使用例
suspend fun getSafe(key: String): String? {
return try {
executeWithRetry { jedis ->
jedis.get(key)
}
} catch (e: Exception) {
// ログ記録など
null
}
}
}
パフォーマンスのベストプラクティス
1. 接続プールの最適化
val optimizedPoolConfig = JedisPoolConfig().apply {
// 接続プール設定
maxTotal = 50 // 最大接続数
maxIdle = 30 // 最大アイドル接続数
minIdle = 10 // 最小アイドル接続数
// 接続検証
testOnBorrow = true // 借用時の検証
testOnReturn = false // 返却時の検証(通常不要)
testWhileIdle = true // アイドル時の検証
// タイムアウト設定
maxWaitMillis = 3000 // 接続取得の最大待機時間
// エビクション設定
timeBetweenEvictionRunsMillis = 30000 // 30秒ごとにチェック
minEvictableIdleTimeMillis = 60000 // 60秒アイドルで削除対象
numTestsPerEvictionRun = 3 // 1回のチェックで検証する接続数
}
2. バッチ処理の活用
class OptimizedRedisOperations(private val pool: JedisPool) {
// パイプラインを使用した高速バッチ処理
suspend fun batchGet(keys: List<String>): List<String?> =
withContext(Dispatchers.IO) {
pool.resource.use { jedis ->
val pipeline = jedis.pipelined()
val responses = keys.map { pipeline.get(it) }
pipeline.sync()
responses.map { it.get() }
}
}
// Luaスクリプトを使用したアトミック操作
suspend fun incrementWithLimit(key: String, limit: Long): Long? =
withContext(Dispatchers.IO) {
pool.resource.use { jedis ->
val script = """
local current = redis.call('GET', KEYS[1])
if not current then current = 0 else current = tonumber(current) end
if current >= tonumber(ARGV[1]) then
return nil
else
return redis.call('INCR', KEYS[1])
end
""".trimIndent()
val result = jedis.eval(script, 1, key, limit.toString())
result as? Long
}
}
}
3. Android向けの最適化
class AndroidRedisManager(context: Context) {
private val pool: JedisPool by lazy {
val config = JedisPoolConfig().apply {
// Androidでは控えめな設定を使用
maxTotal = 5
maxIdle = 3
minIdle = 1
// バッテリー消費を抑えるため、頻繁なヘルスチェックは避ける
testWhileIdle = false
}
// 通常はローカルネットワークまたはVPN経由
JedisPool(config, BuildConfig.REDIS_HOST, BuildConfig.REDIS_PORT)
}
// アプリケーションのライフサイクルに合わせて管理
fun onAppDestroy() {
pool.close()
}
}
他のRedisクライアントとの比較
Jedis vs Lettuce
| 特徴 | Jedis | Lettuce |
|---|---|---|
| 同期/非同期 | 同期のみ | 両方対応 |
| パフォーマンス | ○ | ◎ |
| 使いやすさ | ◎ | ○ |
| コルーチン統合 | 要ラッパー | ネイティブ対応 |
| メモリ効率 | ○ | ◎ |
| 機能の豊富さ | ◎ | ◎ |
Jedis vs Redisson
| 特徴 | Jedis | Redisson |
|---|---|---|
| APIの複雑さ | シンプル | 高機能 |
| 分散ロック | 手動実装 | 組み込み |
| コレクションAPI | 基本的 | 高度 |
| ライブラリサイズ | 小 | 大 |
まとめ
Jedisは、そのシンプルさと完全なRedis機能サポートにより、Java/Kotlinアプリケーションで広く使用されているRedisクライアントです。同期的な設計は理解しやすく、小規模から中規模のアプリケーションに最適です。ただし、高度な非同期処理が必要な場合は、Lettuceなどの非同期クライアントの使用を検討すべきです。Kotlinコルーチンと組み合わせる際は、必ずDispatchers.IOでラップすることを忘れないでください。