Java مع Redis

لدى Java عدة عملاء لـ Redis. يغطي هذا الدرس استخدام Jedis و Lettuce.

عملاء Java لـ Redis

عملاء Java الرئيسيون لـ Redis:

💡 الاختيار: استخدم Jedis للسيناريوهات البسيطة، Lettuce للسيناريوهات عالية الأداء، و Redisson للسيناريوهات الموزعة.

Jedis

إضافة التبعيات

Maven:

XML
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>5.1.0</version>
</dependency>

Gradle:

GRADLE
implementation 'redis.clients:jedis:5.1.0'

الاتصال الأساسي

JAVA
import redis.clients.jedis.Jedis;

public class RedisDemo {
    public static void main(String[] args) {
        // الاتصال بـ Redis
        Jedis jedis = new Jedis("localhost", 6379);

        // اختبار الاتصال
        System.out.println(jedis.ping()); // PONG

        // إغلاق الاتصال
        jedis.close();
    }
}

الاتصال بكلمة مرور

JAVA
Jedis jedis = new Jedis("localhost", 6379);
jedis.auth("your_password");

عمليات السلاسل

JAVA
import redis.clients.jedis.Jedis;

Jedis jedis = new Jedis("localhost", 6379);

// SET و GET
jedis.set("name", "Alice");
String name = jedis.get("name"); // "Alice"

// SET مع انتهاء صلاحية
jedis.setex("session", 3600, "data"); // تنتهي صلاحيته بعد ساعة

// MSET و MGET
jedis.mset("key1", "value1", "key2", "value2");
List<String> values = jedis.mget("key1", "key2");

// INCR
jedis.set("counter", "0");
Long count = jedis.incr("counter"); // 1

// DEL
jedis.del("name");

عمليات التجزئة

JAVA
Jedis jedis = new Jedis("localhost", 6379);

// HSET و HGET
jedis.hset("user:1", "name", "Alice");
jedis.hset("user:1", "age", "25");
String name = jedis.hget("user:1", "name"); // "Alice"

// HMSET و HMGET
Map<String, String> user = new HashMap<>();
user.put("name", "Bob");
user.put("age", "30");
jedis.hset("user:2", user);

List<String> values = jedis.hmget("user:2", "name", "age");

// HGETALL
Map<String, String> allFields = jedis.hgetAll("user:2");

// HDEL
jedis.hdel("user:2", "age");

عمليات القوائم

JAVA
Jedis jedis = new Jedis("localhost", 6379);

// LPUSH و RPUSH
jedis.lpush("mylist", "value1", "value2");
jedis.rpush("mylist", "value3");

// LRANGE
List<String> list = jedis.lrange("mylist", 0, -1);

// LPOP و RPOP
String value = jedis.lpop("mylist");
String value2 = jedis.rpop("mylist");

// LLEN
long length = jedis.llen("mylist");

عمليات المجموعات

JAVA
Jedis jedis = new Jedis("localhost", 6379);

// SADD
jedis.sadd("myset", "a", "b", "c");

// SMEMBERS
Set<String> members = jedis.smembers("myset");

// SISMEMBER
boolean exists = jedis.sismember("myset", "a"); // true

// SREM
jedis.srem("myset", "a");

// SINTER, SUNION, SDIFF
jedis.sadd("set1", "a", "b", "c");
jedis.sadd("set2", "b", "c", "d");
Set<String> inter = jedis.sinter("set1", "set2"); // [b, c]

عمليات المجموعات المرتبة

JAVA
Jedis jedis = new Jedis("localhost", 6379);

// ZADD
Map<String, Double> scores = new HashMap<>();
scores.put("player1", 100.0);
scores.put("player2", 200.0);
jedis.zadd("leaderboard", scores);

// ZRANGE
Set<String> top = jedis.zrange("leaderboard", 0, 9);

// ZRANGE WITHSCORES
Set<Tuple> topWithScores = jedis.zrangeWithScores("leaderboard", 0, 9);

// ZSCORE
Double score = jedis.zscore("leaderboard", "player1");

// ZINCRBY
jedis.zincrby("leaderboard", 50.0, "player1");

مجموعة اتصال Jedis

JAVA
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisPool {
    private static JedisPool pool;

    static {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(100);      // الحد الأقصى للاتصالات
        config.setMaxIdle(50);        // الحد الأقصى للاتصالات الخاملة
        config.setMinIdle(10);        // الحد الأدنى للاتصالات الخاملة
        config.setTestOnBorrow(true); // اختبار عند الاقتراض

        pool = new JedisPool(config, "localhost", 6379);
    }

    public static Jedis getJedis() {
        return pool.getResource();
    }

    public static void close() {
        pool.close();
    }
}

// استخدام مجموعة الاتصال
try (Jedis jedis = RedisPool.getJedis()) {
    jedis.set("key", "value");
    String value = jedis.get("key");
}

خط أنابيب Jedis

JAVA
Jedis jedis = new Jedis("localhost", 6379);

Pipeline pipeline = jedis.pipelined();

pipeline.set("key1", "value1");
pipeline.set("key2", "value2");
pipeline.set("key3", "value3");
pipeline.get("key1");

List<Object> results = pipeline.syncAndReturnAll();

معاملات Jedis

JAVA
Jedis jedis = new Jedis("localhost", 6379);

Transaction tx = jedis.multi();

tx.set("key1", "value1");
tx.set("key2", "value2");
tx.incr("counter");

List<Object> results = tx.exec();

Lettuce

إضافة التبعيات

Maven:

XML
<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.3.0</version>
</dependency>

الاتصال الأساسي

JAVA
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisConnection;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;

public class LettuceDemo {
    public static void main(String[] args) {
        // إنشاء عميل
        RedisClient client = RedisClient.create("redis://localhost:6379");

        // إنشاء اتصال
        StatefulRedisConnection<String, String> connection = client.connect();

        // الحصول على أوامر متزامنة
        RedisCommands<String, String> commands = connection.sync();

        // تنفيذ الأوامر
        commands.set("key", "value");
        String value = commands.get("key");

        // إغلاق الاتصال
        connection.close();
        client.shutdown();
    }
}

العمليات غير المتزامنة

JAVA
import io.lettuce.core.RedisClient;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.async.RedisAsyncCommands;

RedisClient client = RedisClient.create("redis://localhost:6379");
StatefulRedisConnection<String, String> connection = client.connect();

// الحصول على أوامر غير متزامنة
RedisAsyncCommands<String, String> async = connection.async();

// SET غير متزامن
RedisFuture<String> future = async.set("key", "value");

// انتظار النتيجة
String result = future.get();

مجموعة اتصال Lettuce

JAVA
import io.lettuce.core.RedisClient;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DefaultClientResources;

ClientResources resources = DefaultClientResources.builder()
    .ioThreadPoolSize(4)
    .computationThreadPoolSize(4)
    .build();

RedisClient client = RedisClient.create(resources, "redis://localhost:6379");

تكامل Spring

تكامل Spring Boot

إضافة التبعية:

XML
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

الإعدادات:

YAML
# application.yml
spring:
  redis:
    host: localhost
    port: 6379
    password: your_password
    database: 0
    lettuce:
      pool:
        max-active: 100
        max-idle: 50
        min-idle: 10

الاستخدام:

JAVA
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class RedisService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    public void delete(String key) {
        redisTemplate.delete(key);
    }
}

عمليات RedisTemplate

JAVA
@Autowired
private RedisTemplate<String, Object> redisTemplate;

// عمليات السلاسل
redisTemplate.opsForValue().set("key", "value");
Object value = redisTemplate.opsForValue().get("key");

// عمليات التجزئة
redisTemplate.opsForHash().put("user:1", "name", "Alice");
Object name = redisTemplate.opsForHash().get("user:1", "name");

// عمليات القوائم
redisTemplate.opsForList().leftPush("mylist", "value1");
Object value = redisTemplate.opsForList().leftPop("mylist");

// عمليات المجموعات
redisTemplate.opsForSet().add("myset", "a", "b", "c");
Set<Object> members = redisTemplate.opsForSet().members("myset");

// عمليات المجموعات المرتبة
redisTemplate.opsForZSet().add("leaderboard", "player1", 100);
Set<Object> top = redisTemplate.opsForZSet().range("leaderboard", 0, 9);

أمثلة عملية

مثال 1: قفل موزع (Jedis)

JAVA
import redis.clients.jedis.Jedis;
import java.util.UUID;

public class DistributedLock {
    private Jedis jedis;
    private String lockKey;
    private String identifier;
    private int expireTime;

    public DistributedLock(Jedis jedis, String lockKey, int expireTime) {
        this.jedis = jedis;
        this.lockKey = lockKey;
        this.expireTime = expireTime;
        this.identifier = UUID.randomUUID().toString();
    }

    public boolean acquire() {
        String result = jedis.set(lockKey, identifier, "NX", "EX", expireTime);
        return "OK".equals(result);
    }

    public boolean release() {
        String script =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "    return redis.call('del', KEYS[1]) " +
            "else " +
            "    return 0 " +
            "end";

        Object result = jedis.eval(script, 1, lockKey, identifier);
        return Long.valueOf(1).equals(result);
    }
}
▶ جرّب الكود

مثال 2: أداة تخزين مؤقت

JAVA
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import com.fasterxml.jackson.databind.ObjectMapper;

public class RedisCache {
    private JedisPool pool;
    private ObjectMapper mapper = new ObjectMapper();

    public RedisCache(JedisPool pool) {
        this.pool = pool;
    }

    public void set(String key, Object value, int expireSeconds) {
        try (Jedis jedis = pool.getResource()) {
            String json = mapper.writeValueAsString(value);
            jedis.setex(key, expireSeconds, json);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public <T> T get(String key, Class<T> clazz) {
        try (Jedis jedis = pool.getResource()) {
            String json = jedis.get(key);
            if (json != null) {
                return mapper.readValue(json, clazz);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
▶ جرّب الكود

❓ أسئلة شائعة

س كيف أختار بين Jedis و Lettuce؟
ج Jedis بسيط لكنه غير آمن للخيوط. Lettuce آمن للخيوط ويدعم غير المتزامن. Lettuce موصى به.
س لماذا يحتاج Jedis مجموعة اتصال؟
ج Jedis غير آمن للخيوط — كل خيط يحتاج اتصاله الخاص. مجموعة الاتصال تعيد استخدام الاتصالات لأداء أفضل.
س أي عميل يستخدم Spring Boot افتراضيًا؟
ج Spring Boot 2.x يستخدم Lettuce افتراضيًا.
س كيف أتعامل مع استثناءات اتصال Redis؟
ج استخدم try-with-resources لضمان إغلاق الاتصالات، وكوّن آليات إعادة المحاولة.
س كيف أختار بين RedisTemplate و Jedis؟
ج استخدم RedisTemplate لمشاريع Spring، Jedis أو Lettuce للمشاريع غير Spring.

📖 ملخص

📝 تمارين

  1. العمليات الأساسية: استخدم Jedis لعمليات السلاسل والتجزئات والقوائم
  2. مجموعة الاتصال: كوّن مجموعة اتصال Jedis واختبر إعادة استخدام الاتصال
  3. تكامل Spring: استخدم RedisTemplate في مشروع Spring Boot
  4. تطبيقي: نفذ قفلًا موزعًا بسيطًا أو أداة تخزين مؤقت

الدرس التالي

في الدرس التالي، سنتعلم Node.js مع Redis، والذي يغطي عمليات Node.js مع Redis.

100%