在Java中操作Redis,使用Redis實現分佈式鎖


 

操作redis常用的有2種方式:spring data redis、jedis,jedis中的方法和redis-cli的操作方式相同,方法使用簡單、方便,但需要自己創建連接,可以作爲spring data redis之外一種的選擇。
 

springboot整合redis(spring data方式)

依賴

創建時勾線redis
在這裏插入圖片描述
或者手動添加依賴

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

 

application.properties

###redis config###
#單機版redis server
spring.redis.host=192.168.1.9
spring.redis.port=6379
#redis集羣
#spring.redis.cluster.nodes=192.168.1.1:6379 192.168.1.2:6379 192.168.1.3:6379 192.168.1.4:6379 192.168.1.5:6379 192.168.1.6:6379

#要使用的數據庫,0-15,缺省默認使用db0
#spring.redis.database=0
#連接到redis服務器的超時時間,ms
spring.redis.timeout=3000

#如果jedis連接池沒有閒置連接可用,最多等待多少ms
spring.redis.jedis.pool.max-wait=3000
#默認8,-1表示不限制
spring.redis.jedis.pool.max-active=200
#默認0
spring.redis.jedis.pool.min-idle=50
#默認8
spring.redis.jedis.pool.max-idle=100

連接池默認使用jedis的

 

工具類

直接使用提供的方法操作redis有點麻煩,一句代碼要寫很長,可以自己封裝一下

@Component
public class RedisUtil {
    @Autowired
    private static StringRedisTemplate redisTemplate;

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

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

    public static void hset(String key,String field,Object value {
        redisTemplate.opsForHash().put(key,field,value);
    }

    public static Object hget(String key,String field){
        return redisTemplate.opsForHash().get(key,field);
    }

    //......

}

使用時注入RedisUtil,當然不封裝,直接注入StringRedisTemplate也可以。

 

jedis方式

依賴

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.3.0</version>
</dependency>

 

使用jedis

// 重載方法很多,可指定超時時間、是否使用ssl等
Jedis jedis=new Jedis("192.168.1.9", 6379);

// jedis.auth("xxxx");  //如果需要密碼
// jedis.select(0);  //選擇要使用的數據庫

if (jedis.ping().equals("PONG")){  //連接成功
    jedis.zadd("users", 1223, "zhangsan");
    jedis.zadd("users", 2568, "lisi");
    jedis.zadd("users", 1379, "wangwu");

    //迭代
    Set<Tuple> users = jedis.zrangeWithScores("users", 0, -1);  //[0,-1]
    for (Tuple user:users){
        String name = user.getElement(); //獲取元素值
        double score = user.getScore(); //獲取分數
        System.out.println(name+"的積分是:"+score);
    }
}
else{
    System.out.println("無法連接redis服務器");
}

jedis.close();

 

從jedis連接池獲取連接

// jedis連接池配置
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(15);
jedisPoolConfig.setMaxIdle(10);
jedisPoolConfig.setMinIdle(5);
jedisPoolConfig.setMaxWaitMillis(180);//若一個Jedis對象閒置多少秒,就銷燬

// 重載方法很多,可指定超時時間、是否使用ssl、密碼等
JedisPool jedisPool= new JedisPool(jedisPoolConfig, "192.168.1.9", 6379);
Jedis jedis = jedisPool.getResource();

 

連接redis集羣

// 集羣節點
Set<HostAndPort> nodes = new HashSet();
nodes.add(new HostAndPort("192.168.1.1", 6379));
nodes.add(new HostAndPort("192.168.1.2", 6379));
nodes.add(new HostAndPort("192.168.1.3", 6379));
nodes.add(new HostAndPort("192.168.1.4", 6379));
nodes.add(new HostAndPort("192.168.1.5", 6379));
nodes.add(new HostAndPort("192.168.1.6", 6379));

// 獲取JedisCluster對象,通過JedisCluster對象來操作
JedisCluster jedisCluster = new JedisCluster(nodes);

String user = jedisCluster.get("user");

jedisCluster.close();

 

使用Redis實現分佈式鎖

分佈式鎖需要解決的問題

  • 互斥性:同一時間段,只能有一個redis客戶端獲取到鎖、持有鎖
  • 安全性:鎖只能由持有者刪除,不能由其它redis客戶端刪除
  • 防止死鎖:若持有鎖的redis客戶端長時間未完成任務,可能發生死鎖,應該及時釋放鎖
  • 容錯:持有鎖的redis客戶端故障時,應該及時釋放鎖,避免鎖一直被持有

 

jedis方式

setnx() 設置key

  • 若返回1,說明設置成功,獲取到鎖;獲取到鎖後用expire() 給key設置有效期,預防發生故障鎖不能及時釋放
  • 若返回0,說明設置失敗,這個key已經存在,鎖已被其它線程持有,重試。重試可以用for循環來寫,設置重試次數,失敗時線程沉睡一小段時間,醒來繼續循環、重試,如果達到指定次數還未獲取到鎖就退出循環、放棄

在任務執行過程中,如果發生異常,不能繼續往下執行,也應該馬上釋放鎖。任務完成後用del()刪除key或者用expire()將有效期置爲0,釋放鎖。
 

上面的方式存在問題:分爲setnx()、expire()2步,原子性得不到滿足,可能出現併發問題。
更好的方式:使用redis命令的原子性

#ex指定過期時間(s),px也是指定過期時間(ms),nx是key不存在時才執行set(即新建),xx表示key已存在時才執行set(即更新)
set lock "xxxxxxx" ex 60 nx


// jedis
jedis.set("lock", "xxxxxx", SetParams.setParams().ex(120).nx());

值一般設置爲線程號

 

spring data redis方式

對應的方法如下

// 設置key,absent,缺席、不存在,返回布爾值
Boolean redisTemplate.opsForValue().setIfAbsent(key,value)     
// 設置有效期,key、數值、單位
redisTemplate.expire(key,2,TimeUnit.MINUTES)

// 更好的寫法,原子性
Boolean redisTemplate.opsForValue().setIfAbsent(key,value,2,TimeUnit.MINUTES)    

// 刪除key
redisTemplate.delete(key)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章