1. Windows下客户端的安装和使用
redis-desktop-manager-0.9.3.817.exe
2. redis客户端使用
基于 redis 开放的通信协议,大神们纷纷开发了各种语言的 redis 客户端,有 c、c++、java、python、php、nodeJs 等等开发语言的客户端,准确来说其实这些客户端都是基于 redis 命令做了一层封装,然后打包成工具以便大家更佳方便的操作 redis,以 Java 项目为例,使用最广的就是以下三种客户端:
2. Jedis
Jedis 是老牌的 Redis 的 Java 客户端,提供了比较全面的 Redis 命令的操作支持,也是目前使用最广泛的客户端。官网:点我跳转
2.1 基本使用
2.1.1 基本使用
首先创建一个普通的 Maven 项目,然后添加Jedis依赖包!
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.9.0</version> </dependency>
|
然后创建一个简单的测试,即可实现连接!
public class JedisMain {
public static void main(String[] args) { // 1.构造一个 Jedis 对象,因为这里使用的默认端口 6379,所以不用配置端口 Jedis jedis = new Jedis("127.0.0.1", 6379); // 2.密码认证 jedis.auth("111111"); // 3.测试是否连接成功 String ping = jedis.ping(); // 4.返回 pong 表示连接成功 System.out.println(ping); } }
|
对于 Jedis 而言,一旦连接上了 Redis 服务器,剩下的操作就非常容易了,由于 Jedis 中的 API 和 Redis 的命令高度一致,所以,Jedis 中的方法见名知意,直接使用即可。
2.1.2 连接池
虽然 redis 服务端是单线程操作,但是在实际项目中,使用 Jedis 对象来操作 redis 时,每次操作都需要新建/关闭 TCP 连接,连接资源开销很高,同时 Jedis 对象的个数不受限制,在极端情况下可能会造成连接泄漏,同时 Jedis 存在多线程不安全的问题。
为什么说 Jedis 线程不安全,更加详细的原因可以访问这个地址。
所以我们需要将 Jedis 交给线程池来管理,使用 Jedis 对象时,从连接池获取 Jedis,使用完成之后,再还给连接池。
在使用之前,需要添加common-pool线程池依赖包!
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.11.1</version> </dependency>
|
创建一个简单的使用线程池测试用例。
public class JedisPoolMain {
public static void main(String[] args) { // 1. 构造一个 Jedis 连接池 JedisPool pool = new JedisPool("127.0.0.1", 6379); // 2. 从连接池中获取一个 Jedis 连接 Jedis jedis = pool.getResource(); jedis.auth("111111"); // 3. Jedis 操作 String ping = jedis.ping(); System.out.println(ping); // 4. 归还连接 jedis.close(); } }
|
2.1.3 连接池配置
在实际的使用过程中,我们常常会这样来初始化线程池JedisPool,详细代码如下:
public class RedisPoolUtils {
private static JedisPool jedisPool = null;
/** * redis服务器地址 */ private static String addr = "127.0.0.1";
/** * redis服务器端口 */ private static int port = 6379;
/** * redis服务器密码 */ private static String auth = "111111";
static{ try { JedisPoolConfig config = new JedisPoolConfig(); //连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true config.setBlockWhenExhausted(true); //设置的逐出策略类名, 默认DefaultEvictionPolicy(当连接超过最大空闲时间,或连接数超过最大空闲连接数) config.setEvictionPolicyClassName("org.apache.commons.pool2.impl.DefaultEvictionPolicy"); //是否启用pool的jmx管理功能, 默认true config.setJmxEnabled(true); //MBean ObjectName = new ObjectName("org.apache.commons.pool2:type=GenericObjectPool,name=" + "pool" + i); 默认为"pool", JMX不熟,具体不知道是干啥的...默认就好. config.setJmxNamePrefix("pool"); //是否启用后进先出, 默认true config.setLifo(true); //最大空闲连接数, 默认8个 config.setMaxIdle(8); //最大连接数, 默认8个 config.setMaxTotal(8); //获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1 config.setMaxWaitMillis(-1); //逐出连接的最小空闲时间 默认1800000毫秒(30分钟) config.setMinEvictableIdleTimeMillis(1800000); //最小空闲连接数, 默认0 config.setMinIdle(0); //每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3 config.setNumTestsPerEvictionRun(3); //对象空闲多久后逐出, 当空闲时间>该值 且 空闲连接>最大空闲数 时直接逐出,不再根据MinEvictableIdleTimeMillis判断 (默认逐出策略) config.setSoftMinEvictableIdleTimeMillis(1800000); //在获取连接的时候检查有效性, 默认false config.setTestOnBorrow(false); //在空闲时检查有效性, 默认false config.setTestWhileIdle(false); //逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1 config.setTimeBetweenEvictionRunsMillis(-1); jedisPool = new JedisPool(config, addr, port, 3000, auth); } catch (Exception e) { e.printStackTrace(); } }
/** * 获取 Jedis 资源 * @return */ public static Jedis getJedis() { if (jedisPool != null) { return jedisPool.getResource(); } return null; }
/** * 释放Jedis资源 */ public static void close(final Jedis jedis) { if (jedis != null) { jedis.close(); } }
}
|
简单测试:
public static void main(String[] args) throws InterruptedException { //获取 jedis 客户端 Jedis jedis = RedisPoolUtils.getJedis();
System.out.println("清空数据:"+jedis.flushDB()); System.out.println("判断某个键是否存在:"+jedis.exists("username")); System.out.println("新增<'username','xmr'>的键值对:"+jedis.set("username", "xmr")); System.out.println(jedis.exists("username")); System.out.println("新增<'password','password'>的键值对:"+jedis.set("password", "123")); System.out.print("系统中所有的键如下:"); Set<String> keys = jedis.keys("*"); System.out.println(keys); System.out.println("删除键password:"+jedis.del("password")); System.out.println("判断键password是否存在:"+jedis.exists("password")); System.out.println("设置键username的过期时间为5s:"+jedis.expire("username", 8L)); TimeUnit.SECONDS.sleep(1); System.out.println("查看键username的剩余生存时间:"+jedis.ttl("username")); System.out.println("移除键username的生存时间:"+jedis.persist("username")); System.out.println("查看键username的剩余生存时间:"+jedis.ttl("username")); System.out.println("查看键username所存储的值的类型:"+jedis.type("username")); RedisPoolUtils.close(jedis); }
|
运行结果如下:
清空数据:OK 判断某个键是否存在:false 新增<'username','xmr'>的键值对:OK true 新增<'password','password'>的键值对:OK 系统中所有的键如下:[password, username] 删除键password:1 判断键password是否存在:false 设置键username的过期时间为5s:1 查看键username的剩余生存时间:7 移除键username的生存时间:1 查看键username的剩余生存时间:-1 查看键username所存储的值的类型:string
|
2.1.4 字符串常用API操作
public class RedisClientUtil {
private static final Logger log = LoggerFactory.getLogger(RedisClientUtil.class);
/** * 获取指定key的值,如果key不存在返回null * 返回值:返回 key 的值,如果 key 不存在时,返回 nil * @param key * @return */ public static String get(String key) { try (Jedis jedis = jedisPool.getResource()) { return jedis.get(key); } catch (Exception e){ log.error("get命令操作失败,请求参数:{}", key,e); } return null; }
/** * 设置key的值为value * 返回值:操作成功完成时返回 OK * @param key * @return */ public static String set(String key, String value) { try (Jedis jedis = jedisPool.getResource()) { return jedis.set(key, value); } catch (Exception e){ log.error("set命令操作失败,参数key:{},参数value:{}", key, value,e); } return null; }
/** * 删除指定的key,返回值:被删除 key 的数量 * 返回值:被删除 key 的数量 * @param key * @return */ public static Long del(String key) { try (Jedis jedis = jedisPool.getResource()) { Long result = jedis.del(key); return jedis.del(key); } catch (Exception e){ log.error("del命令操作失败,参数key:{}", key,e); } return 0L; }
/** * 通过key向指定的value值追加值 * 返回值:追加指定值之后, key中字符串的长度 * @param key * @return */ public static Long append(String key, String value) { try (Jedis jedis = jedisPool.getResource()) { return jedis.append(key, value); } catch (Exception e){ log.error("append命令操作失败,参数key:{},参数value:{}", key, value,e); } return 0L; }
/** * 判断key是否存在 * 返回值:true/false * @param key * @return */ public static Boolean exists(String key) { try (Jedis jedis = jedisPool.getResource()) { return jedis.exists(key); } catch (Exception e){ log.error("exists命令操作失败,参数key:{}", key,e); } return false; }
/** * 设置key的超时时间为seconds * 返回值:若 key 存在返回 1 ,否则返回 0 * @param key * @return */ public static Long expire(String key, long seconds) { try (Jedis jedis = jedisPool.getResource()) { return jedis.expire(key, seconds); } catch (Exception e){ log.error("expire命令操作失败,参数key:{},参数seconds:{}", key, seconds,e); } return 0L; }
/** * 返回 key 的剩余过期时间(单位秒) * 返回值:当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以秒为单位,返回 key 的剩余生存时间 * @param key * @return */ public static Long ttl(String key) { try (Jedis jedis = jedisPool.getResource()) { return jedis.ttl(key); } catch (Exception e){ log.error("ttl命令操作失败,参数key:{}", key,e); } return 0L; }
/** * 设置指定key的值为value,当key不存在时才设置 * 返回值:设置成功返回 1,设置失败返回 0 * @param key * @return */ public static Long setnx(String key, String value) { try (Jedis jedis = jedisPool.getResource()) { return jedis.setnx(key, value); } catch (Exception e){ log.error("setnx命令操作失败,参数key:{},参数value:{}", key, value,e); } return 0L; }
/** * 设置指定key的值为value,并设置过期时间 * 返回值:设置成功时返回 OK * @param key * @return */ public static String setex(String key, String value, long seconds) { try (Jedis jedis = jedisPool.getResource()) { return jedis.setex(key, seconds, value); } catch (Exception e){ log.error("setex命令操作失败,参数key:{},参数value:{}", key, value,e); } return null; }
/** * 通过key 和offset 从指定的位置开始将原先value替换 * 返回值:被修改后的字符串长度 * @param key * @return */ public static Long setrange(String key, int offset, String value) { try (Jedis jedis = jedisPool.getResource()) { return jedis.setrange(key, offset, value); } catch (Exception e){ log.error("setrange命令操作失败,参数key:{},参数value:{},参数offset:{}", key, value, offset,e); } return null; }
/** * 通过批量的key获取批量的value * 返回值:一个包含所有给定 key 的值的列表。 * @param keys * @return */ public static List<String> mget(String... keys) { try (Jedis jedis = jedisPool.getResource()) { return jedis.mget(keys); } catch (Exception e){ log.error("mget命令操作失败,参数key:{}", keys.toString(),e); } return null; }
/** * 批量的设置key:value,也可以一个 * 返回值:总是返回 OK * @param keysValues * @return */ public static String mset(String... keysValues) { try (Jedis jedis = jedisPool.getResource()) { return jedis.mset(keysValues); } catch (Exception e){ log.error("mset命令操作失败,参数key:{}", keysValues.toString(),e); } return null; }
/** * 设置key的值,并返回一个旧值 * 返回值:返回给定 key 的旧值,当 key 没有旧值时,即 key 不存在时,返回 nil * @param key * @return */ public static String getSet(String key, String value) { try (Jedis jedis = jedisPool.getResource()) { return jedis.getSet(key, value); } catch (Exception e){ log.error("getSet命令操作失败,参数key:{},参数value:{}", key, value,e); } return null; }
/** * 通过下标和 key 获取指定下标位置的 value * 返回值:截取得到的子字符串 * @param key * @return */ public static String getrange(String key, int startOffset, int endOffset) { try (Jedis jedis = jedisPool.getResource()) { return jedis.getrange(key, startOffset, endOffset); } catch (Exception e){ log.error("getrange命令操作失败,参数key:{},参数startOffset:{},参数offset:{}", key, startOffset, endOffset,e); } return null; }
/** * 通过key 对value进行加值+1操作,当value不是int类型时会返回错误,当key不存在是则value为1 * 返回值:执行INCR命令之后 key 的值 * @param key * @return */ public static Long incr(String key) { try (Jedis jedis = jedisPool.getResource()) { return jedis.incr(key); } catch (Exception e){ log.error("incr命令操作失败,参数key:{}", key, e); } return 0L; }
/** * 通过key给指定的value加值 * 返回值:执行INCR命令之后 key 的值 * @param key * @return */ public static Long incrBy(String key, long increment) { try (Jedis jedis = jedisPool.getResource()) { return jedis.incrBy(key, increment); } catch (Exception e){ log.error("incrBy命令操作失败,参数key:{},参数increment:{}", key, increment,e); } return 0L; }
/** * 对key的值做减减操作 * 返回值:执行INCR命令之后 key 的值 * @param key * @return */ public static Long decr(String key) { try (Jedis jedis = jedisPool.getResource()) { return jedis.decr(key); } catch (Exception e){ log.error("decr命令操作失败,参数key:{}", key, e); } return 0L; }
/** * 对key的值做减减操作,减去指定的值 * 返回值:执行INCR命令之后 key 的值 * @param key * @return */ public static Long decrBy(String key, long decrement) { try (Jedis jedis = jedisPool.getResource()) { return jedis.decrBy(key, decrement); } catch (Exception e){ log.error("decrBy命令操作失败,参数key:{},参数decrement:{}", key, decrement,e); } return 0L; }
/** * 通过key获取value值的长度 * 返回值:value值的长度 * @param key * @return */ public static Long strlen(String key) { try (Jedis jedis = jedisPool.getResource()) { return jedis.strlen(key); } catch (Exception e){ log.error("strlen命令操作失败,参数key:{}", key, e); } return 0L; } }
|
2.1.5 哈希常用API操作
public class RedisClientUtil {
private static final Logger log = LoggerFactory.getLogger(RedisClientUtil.class);
/** * 通过key 和 field 获取指定的 value * 返回值:对应的value值 * @param key * @return */ public static String hget(String key, String field) { try (Jedis jedis = jedisPool.getResource()) { return jedis.hget(key, field); } catch (Exception e){ log.error("hget命令操作失败,参数key:{},参数field:{}", key, field,e); } return null; }
/** * 通过key给field设置指定的值,如果key不存在,则先创建 * 返回值:如果字段是哈希表中的一个新建字段,并且值设置成功,返回 1 ;如果哈希表中域字段已经存在且旧值已被新值覆盖,返回 0 。 * @param key * @return */ public static Long hset(String key, String field, String value) { try (Jedis jedis = jedisPool.getResource()) { return jedis.hset(key, field, value); } catch (Exception e){ log.error("hset命令操作失败,参数key:{},参数field:{},参数value:{}", key, field, value,e); } return 0L; }
/** * 通过key和field判断是否有指定的value存在 * 返回值:true/false * @param key * @return */ public static Boolean hexists(String key, String field) { try (Jedis jedis = jedisPool.getResource()) { return jedis.hexists(key, field); } catch (Exception e){ log.error("hexists命令操作失败,参数key:{},参数field:{}", key, field,e); } return false; }
/** * 通过key返回field的数量 * 返回值:field的数量 * @param key * @return */ public static Long hlen(String key) { try (Jedis jedis = jedisPool.getResource()) { return jedis.hlen(key); } catch (Exception e){ log.error("hlen命令操作失败,参数key:{}", key,e); } return 0L; }
/** * 通过key 删除指定的 field * 返回值:删除的数量 * @param key * @return */ public static Long hdel(String key, String... fields) { try (Jedis jedis = jedisPool.getResource()) { return jedis.hdel(key, fields); } catch (Exception e){ log.error("hdel命令操作失败,参数key:{},参数fields:{}", key, fields.toString(),e); } return 0L; }
/** * 通过key返回所有的field * 返回值:field集合 * @param key * @return */ public static Set<String> hkeys(String key) { try (Jedis jedis = jedisPool.getResource()) { return jedis.hkeys(key); } catch (Exception e){ log.error("hkeys命令操作失败,参数key:{}", key,e); } return null; }
/** * 通过key获取所有的field和value * 返回值:map对象 * @param key * @return */ public static Map<String, String> hgetAll(String key) { try (Jedis jedis = jedisPool.getResource()) { return jedis.hgetAll(key); } catch (Exception e){ log.error("hgetAll命令操作失败,参数key:{}", key,e); } return null; } }
|
2.1.6 列表常用API操作
public class RedisClientUtil {
private static final Logger log = LoggerFactory.getLogger(RedisClientUtil.class);
/** * 过key向list头部添加字符串 * 返回值:执行 LPUSH 命令后,列表的长度 * @param key * @return */ public static Long lpush(String key, String... strs) { try (Jedis jedis = jedisPool.getResource()) { return jedis.lpush(key, strs); } catch (Exception e){ log.error("lpush命令操作失败,参数key:{},参数strs:{}", key, strs.toString(),e); } return null; }
/** * 通过key向list尾部添加字符串 * 返回值:执行 RPUSH 命令后,列表的长度 * @param key * @return */ public static Long rpush(String key, String... strs) { try (Jedis jedis = jedisPool.getResource()) { return jedis.rpush(key, strs); } catch (Exception e){ log.error("rpush命令操作失败,参数key:{},参数strs:{}", key, strs.toString(),e); } return null; }
/** * 通过key设置list指定下标位置的value 如果下标超过list里面value的个数则报错 * 返回值:操作成功返回 ok ,否则返回错误信息 * @param key * @return */ public static String lset(String key, Long index, String value) { try (Jedis jedis = jedisPool.getResource()) { return jedis.lset(key, index, value); } catch (Exception e){ log.error("lset命令操作失败,参数key:{},参数index:{},参数value:{}", key, index, value,e); } return null; }
/** * 通过key从对应的list中删除指定的count个 和 value相同的元素 * 返回值:返回被删除的个数 * @param key * @return */ public static Long lrem(String key, long count, String value) { try (Jedis jedis = jedisPool.getResource()) { return jedis.lrem(key, count, value); } catch (Exception e){ log.error("lrem命令操作失败,参数key:{},参数count:{},参数value:{}", key, count, value,e); } return null; }
/** * 通过key保留list中从strat下标开始到end下标结束的value值 * 返回值:操作成功返回 ok ,否则返回错误信息 * @param key * @return */ public static String ltrim(String key, long start, long end) { try (Jedis jedis = jedisPool.getResource()) { return jedis.ltrim(key, start, end); } catch (Exception e){ log.error("ltrim命令操作失败,参数key:{},参数start:{},参数end:{}", key, start, end,e); } return null; }
/** * 通过key从list的头部删除一个value,并返回该value * 返回值:value值 * @param key * @return */ public static String lpop(String key) { try (Jedis jedis = jedisPool.getResource()) { return jedis.lpop(key); } catch (Exception e){ log.error("lpop命令操作失败,参数key:{}", key,e); } return null; }
/** * 通过key从list尾部删除一个value,并返回该元素 * 返回值:value值 * @param key * @return */ public static String rpop(String key) { try (Jedis jedis = jedisPool.getResource()) { return jedis.rpop(key); } catch (Exception e){ log.error("rpop命令操作失败,参数key:{}", key,e); } return null; }
/** * 通过key获取list中指定下标位置的value * 返回值:value值 * @param key * @return */ public static String lindex(String key, long index){ try (Jedis jedis = jedisPool.getResource()) { return jedis.lindex(key, index); } catch (Exception e){ log.error("lindex命令操作失败,参数key:{},参数index:{}", key, index,e); } return null; }
/** * 通过key返回list的长度 * 返回值:value值 * @param key * @return */ public static Long llen(String key) { try (Jedis jedis = jedisPool.getResource()) { return jedis.llen(key); } catch (Exception e){ log.error("llen命令操作失败,参数key:{}", key,e); } return null; }
/** * 通过key获取list指定下标位置的value 如果start 为 0 end 为 -1 则返回全部的list中的value * 返回值:value值 * @param key * @return */ public static List<String> lrange(String key, long start, long end) { try (Jedis jedis = jedisPool.getResource()) { return jedis.lrange(key, start, end); } catch (Exception e){ log.error("lrange命令操作失败,参数key:{},参数start:{},参数end:{}", key, start, end,e); } return null; }
}
|
2.1.7 集合常用API操作
public class RedisClientUtil {
private static final Logger log = LoggerFactory.getLogger(RedisClientUtil.class);
/** * 通过key向指定的set中添加value * 返回值:添加成功的个数 * @param key * @return */ public static Long sadd(String key, String... members) { try (Jedis jedis = jedisPool.getResource()) { return jedis.sadd(key, members); } catch (Exception e){ log.error("sadd命令操作失败,参数key:{},参数members:{}", key, members.toString(),e); } return null; }
/** * 通过key删除set中对应的value值 * 返回值:删除成功的个数 * @param key * @return */ public static Long srem(String key, String... members) { try (Jedis jedis = jedisPool.getResource()) { return jedis.srem(key, members); } catch (Exception e){ log.error("srem命令操作失败,参数key:{},参数members:{}", key, members.toString(),e); } return null; }
/** * 通过key获取set中value的个数 * 返回值:value的个数 * @param key * @return */ public static Long scard(String key) { try (Jedis jedis = jedisPool.getResource()) { return jedis.scard(key); } catch (Exception e){ log.error("scard命令操作失败,参数key:{}", key,e); } return 0L; }
/** * 通过key判断value是否是set中的元素 * 返回值:true/false * @param key * @return */ public static Boolean sismember(String key, String member) { try (Jedis jedis = jedisPool.getResource()) { return jedis.sismember(key, member); } catch (Exception e){ log.error("sismember命令操作失败,参数key:{},参数member:{}", key, member,e); } return false; }
/** * 通过key获取set中所有的value * 返回值:所有的value * @param key * @return */ public static Set<String> smembers(String key) { try (Jedis jedis = jedisPool.getResource()) { return jedis.smembers(key); } catch (Exception e){ log.error("smembers命令操作失败,参数key:{}", key,e); } return null; }
}
|
2.1.8 有序集合常用API操作
public class RedisClientUtil {
private static final Logger log = LoggerFactory.getLogger(RedisClientUtil.class);
/** * 通过key向zset中添加value,score,其中score就是用来排序的 如果该value已经存在则根据score更新元素 * 返回值:被成功添加的新成员的数量,不包括那些被更新的、已经存在的成员 * @param key * @return */ public static Long zadd(String key, double score, String member) { try (Jedis jedis = jedisPool.getResource()) { return jedis.zadd(key, score, member); } catch (Exception e){ log.error("zadd命令操作失败,参数key:{},参数score:{},参数member:{}", key, score, member,e); } return null; }
/** * 通过key删除在zset中指定的value * 返回值:删除个数 * @param key * @return */ public static Long zrem(String key, String... members) { try (Jedis jedis = jedisPool.getResource()) { return jedis.zrem(key, members); } catch (Exception e){ log.error("zrem命令操作失败,参数key:{},参数members:{}", key, members.toString(),e); } return null; }
/** * 通过key增加该zset中value的score的值 * 返回值:member 成员的新分数值 * @param key * @return */ public static Double zincrby(String key, double score, String member) { try (Jedis jedis = jedisPool.getResource()) { return jedis.zincrby(key, score, member); } catch (Exception e){ log.error("zincrby命令操作失败,参数key:{},参数score:{},参数member:{}", key, score, member,e); } return null; }
/** * 通过key返回zset中value的排名 下标从小到大排序 * 返回值:返回 member 的排名 * @param key * @return */ public static Long zrank(String key, String member) { try (Jedis jedis = jedisPool.getResource()) { return jedis.zrank(key, member); } catch (Exception e){ log.error("zrank命令操作失败,参数key:{},参数member:{}", key, member,e); } return null; }
/** * 通过key将获取score从start到end中zset的value socre从大到小排序 当start为0 end为-1时返回全部 * 返回值:返回 member 集合 * @param key * @return */ public static Set<String> zrevrange(String key, long start, long end) { try (Jedis jedis = jedisPool.getResource()) { return jedis.zrevrange(key, start, end); } catch (Exception e){ log.error("zrevrange命令操作失败,参数key:{},参数start:{},参数end:{}", key, start, end,e); } return null; }
/** * 返回指定区间内zset中value的数量 * 返回值:返回 member 集合 * @param key * @return */ public static Long zcount(String key, String min, String max) { try (Jedis jedis = jedisPool.getResource()) { return jedis.zcount(key, min, max); } catch (Exception e){ log.error("zcount命令操作失败,参数key:{},参数min:{},参数max:{}", key, min, max,e); } return null; }
/** * 通过key返回zset中的value个数 * 返回值:返回 member 集合 * @param key * @return */ public static Long zcard(String key) { try (Jedis jedis = jedisPool.getResource()) { return jedis.zcard(key); } catch (Exception e){ log.error("zcard命令操作失败,参数key:{}", key,e); } return null; }
/** * 返回满足pattern表达式的所有key keys(*) 返回所有的key * 返回值:返回 key 集合 * @param pattern * @return */ public static Set<String> keys(String pattern) { try (Jedis jedis = jedisPool.getResource()) { return jedis.keys(pattern); } catch (Exception e){ log.error("keys命令操作失败,参数pattern:{}", pattern,e); } return null; }
/** * 通过key判断值得类型 * 返回值:值的类型 * @param key * @return */ public static String type(String key) { try (Jedis jedis = jedisPool.getResource()) { return jedis.type(key); } catch (Exception e){ log.error("type命令操作失败,参数key:{}", key,e); } return null; }
}
|
2.2 集群配置
在实际的项目生产环境中,redis 通常不是以单台服务实例来运行的,因为一旦服务器挂了,可能所有的下游服务都会受到影响,因此为了保障单台服务器即使出现故障也能运行,通常运维组会搭建集群环境,来保证服务高可用。
搭建的方式有两种,哨兵模式和 Cluster 模式。
- 哨兵模式:对redis服务器进行监控,如果有宕机的,就从备机里面选一个出来作为主机,实现自动切换
- Cluster 模式:将数据进行分片存储,避免全部节点数据都一样,浪费空间
2.2.1 哨兵模式
哨兵模式简单的说,就是一台主机,一台备机,外加一台监控服务,当监控服务观测到主机已经宕机,就会将备用机切换成主机,以便继续提供服务。
public class RedisPoolUtils {
private static Jedis jedis;
private static JedisSentinelPool jedisSentinelPool;
static{ try { JedisPoolConfig config = new JedisPoolConfig(); //最大空闲连接数, 默认8个 config.setMaxIdle(8); //最大连接数, 默认8个 config.setMaxTotal(8); //最小空闲连接数, 默认0 config.setMinIdle(0); //获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1 config.setMaxWaitMillis(3000); //在获取连接的时候检查有效性,表示取出的redis对象可用, 默认false config.setTestOnBorrow(true);
//redis服务器列表 Set<String> sentinels = new HashSet<>(); sentinels.add(new HostAndPort("192.168.43.212", 26379).toString()); sentinels.add(new HostAndPort("192.168.43.213", 26379).toString()); sentinels.add(new HostAndPort("192.168.43.214", 26379).toString());
//初始化连接池 jedisSentinelPool = new JedisSentinelPool("mymaster", sentinels, config, "111111"); // 从池中获取一个Jedis对象 jedis = jedisSentinelPool.getResource(); } catch (Exception e) { e.printStackTrace(); } } }
|
2.2.2 集群模式
为了保证高可用,redis-cluster集群通常会引入主从复制模型,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点。
public class RedisPoolUtils {
static{ try { JedisPoolConfig config = new JedisPoolConfig(); //最大空闲连接数, 默认8个 config.setMaxIdle(8); //最大连接数, 默认8个 config.setMaxTotal(8); //最小空闲连接数, 默认0 config.setMinIdle(0); //获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1 config.setMaxWaitMillis(3000); //在获取连接的时候检查有效性,表示取出的redis对象可用, 默认false config.setTestOnBorrow(true);
Set<HostAndPort> nodes = new HashSet<>(); nodes.add(new HostAndPort("192.168.43.212", 26379)); nodes.add(new HostAndPort("192.168.43.213", 26379)); nodes.add(new HostAndPort("192.168.43.214", 26379));
JedisCluster jedisCluster = new JedisCluster(nodes, config); jedisCluster.set("key", "hello world");
jedisCluster.close(); } catch (Exception e) { e.printStackTrace(); } } }
|
2.3 优缺点
jedis客户端是目前使用最广泛的一款 java 客户端,也是老牌的 Redis 的 Java 实现客户端。
优点很突出:
- 比较全面的提供了 Redis 的操作特性,也就是说你能用 redis 命令操作的,Jedis 包都也给你封装好了,直接使用即可
- 使用广泛,易上手
当然,缺点也有:
- Jedis 客户端实例不是线程安全的,需要借助连接池来管理和使用 Jedis
- 使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到 sockets 处理完 I/O 才能执行,不支持异步
3. Lettuce
Lettuce 是Redis的一款高级 Java 客户端,与 Jedis 并列成为最热门的客户端之一,目前已成为 SpringBoot 2.0 版本默认的 redis 客户端。
相比老牌 Jedis,Lettuce 属于后起之秀,不仅功能丰富,而且提供了很多新的功能特性,比如异步操作、响应式编程等等,同时还解决了 Jedis 中线程不安全的问题。官网:点我跳转
3.1 基本使用
首先,创建一个 maven 项目,引入lettuce-core包,就可以使用了。
<dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>5.3.1.RELEASE</version> </dependency>
|
使用 lettuce 连接 redis,测试是否能正常联通!
public class LettuceMain {
public static void main(String[] args) { RedisURI redisUri = RedisURI.builder() .withHost("127.0.0.1") .withPort(6379) .withPassword("111111") .withTimeout(Duration.of(10, ChronoUnit.SECONDS)) .build(); RedisClient redisClient = RedisClient.create(redisUri); StatefulRedisConnection<String, String> connection = redisClient.connect(); RedisCommands<String, String> commands = connection.sync(); System.out.println(commands.ping()); connection.close(); redisClient.shutdown(); } }
|
3.1.1 同步操作
基本上只要是 Jedis 支持的同步命令操作,Lettuce 都支持。
下面,我们以同步操作字符串为例,Lettuce 的 api 操作如下!
public class LettuceSyncMain {
public static void main(String[] args) { RedisURI redisUri = RedisURI.builder() .withHost("127.0.0.1").withPort(6379).withPassword("111111") .withTimeout(Duration.of(10, ChronoUnit.SECONDS)) .build(); RedisClient redisClient = RedisClient.create(redisUri); StatefulRedisConnection<String, String> connection = redisClient.connect(); //获取同步操作命令工具 RedisCommands<String, String> commands = connection.sync();
System.out.println("清空数据:"+commands.flushdb()); System.out.println("判断某个键是否存在:"+commands.exists("username")); System.out.println("新增<'username','xmr'>的键值对:"+commands.set("username", "xmr")); System.out.println("新增<'password','password'>的键值对:"+commands.set("password", "123")); System.out.println("获取<'password'>键的值:"+commands.get("password")); System.out.println("系统中所有的键如下:" + commands.keys("*")); System.out.println("删除键password:"+commands.del("password")); System.out.println("判断键password是否存在:"+commands.exists("password")); System.out.println("设置键username的过期时间为5s:"+commands.expire("username", 5L)); System.out.println("查看键username的剩余生存时间:"+commands.ttl("username")); System.out.println("移除键username的生存时间:"+commands.persist("username")); System.out.println("查看键username的剩余生存时间:"+commands.ttl("username")); System.out.println("查看键username所存储的值的类型:"+commands.type("username"));
connection.close(); redisClient.shutdown(); } }
|
3.1.2 异步操作
除此之外,Lettuce 还支持异步操作,将上面的操作改成异步处理,结果如下!
public class LettuceASyncMain {
public static void main(String[] args) throws Exception { RedisURI redisUri = RedisURI.builder() .withHost("127.0.0.1").withPort(6379).withPassword("111111") .withTimeout(Duration.of(10, ChronoUnit.SECONDS)) .build(); RedisClient redisClient = RedisClient.create(redisUri); StatefulRedisConnection<String, String> connection = redisClient.connect(); //获取异步操作命令工具 RedisAsyncCommands<String, String> commands = connection.async();
System.out.println("清空数据:"+commands.flushdb().get()); System.out.println("判断某个键是否存在:"+commands.exists("username").get()); System.out.println("新增<'username','xmr'>的键值对:"+commands.set("username", "xmr").get()); System.out.println("新增<'password','password'>的键值对:"+commands.set("password", "123").get()); System.out.println("获取<'password'>键的值:"+commands.get("password").get()); System.out.println("系统中所有的键如下:" + commands.keys("*").get()); System.out.println("删除键password:"+commands.del("password").get()); System.out.println("判断键password是否存在:"+commands.exists("password").get()); System.out.println("设置键username的过期时间为5s:"+commands.expire("username", 5L).get()); System.out.println("查看键username的剩余生存时间:"+commands.ttl("username").get()); System.out.println("移除键username的生存时间:"+commands.persist("username").get()); System.out.println("查看键username的剩余生存时间:"+commands.ttl("username").get()); System.out.println("查看键username所存储的值的类型:"+commands.type("username").get());
connection.close(); redisClient.shutdown(); } }
|
3.1.3 响应式编程
Lettuce 除了支持异步编程以外,还支持响应式编程,Lettuce 引入的响应式编程框架是Project Reactor,如果没有响应式编程经验可以先自行了解一下,访问地址https://projectreactor.io/。
响应式编程使用案例如下:
public class LettuceMain {
public static void main(String[] args) throws Exception { RedisURI redisUri = RedisURI.builder() .withHost("127.0.0.1").withPort(6379).withPassword("111111") .withTimeout(Duration.of(10, ChronoUnit.SECONDS)) .build(); RedisClient redisClient = RedisClient.create(redisUri); StatefulRedisConnection<String, String> connection = redisClient.connect(); //获取响应式API操作命令工具 RedisReactiveCommands<String, String> commands = connection.reactive();
Mono<String> setc = commands.set("name", "mayun"); System.out.println(setc.block()); Mono<String> getc = commands.get("name"); getc.subscribe(System.out::println); Flux<String> keys = commands.keys("*"); keys.subscribe(System.out::println);
//开启一个事务,先把count设置为1,再将count自增1 commands.multi().doOnSuccess(r -> { commands.set("count", "1").doOnNext(value -> System.out.println("count1:" + value)).subscribe(); commands.incr("count").doOnNext(value -> System.out.println("count2:" + value)).subscribe(); }).flatMap(s -> commands.exec()) .doOnNext(transactionResult -> System.out.println("transactionResult:" + transactionResult.wasDiscarded())).subscribe();
Thread.sleep(1000 * 5); connection.close(); redisClient.shutdown(); } }
|
3.1.4 发布和订阅
Lettuce 还支持 redis 的消息发布和订阅,具体实现案例如下:
public class LettuceReactiveMain1 {
public static void main(String[] args) throws Exception { RedisURI redisUri = RedisURI.builder() .withHost("127.0.0.1").withPort(6379).withPassword("111111") .withTimeout(Duration.of(10, ChronoUnit.SECONDS)) .build(); RedisClient redisClient = RedisClient.create(redisUri); //获取发布订阅操作命令工具 StatefulRedisPubSubConnection<String, String> pubsubConn = redisClient.connectPubSub(); pubsubConn.addListener(new RedisPubSubListener<String, String>() { @Override public void unsubscribed(String channel, long count) { System.out.println("[unsubscribed]" + channel); } @Override public void subscribed(String channel, long count) { System.out.println("[subscribed]" + channel); } @Override public void punsubscribed(String pattern, long count) { System.out.println("[punsubscribed]" + pattern); } @Override public void psubscribed(String pattern, long count) { System.out.println("[psubscribed]" + pattern); } @Override public void message(String pattern, String channel, String message) { System.out.println("[message]" + pattern + " -> " + channel + " -> " + message); } @Override public void message(String channel, String message) { System.out.println("[message]" + channel + " -> " + message); } }); RedisPubSubAsyncCommands<String, String> pubsubCmd = pubsubConn.async(); pubsubCmd.psubscribe("CH"); pubsubCmd.psubscribe("CH2"); pubsubCmd.unsubscribe("CH");
Thread.sleep(100 * 5); pubsubConn.close(); redisClient.shutdown(); } }
|
3.1.5 客户端资源与参数配置
Lettuce 客户端的通信框架集成了 Netty 的非阻塞 IO 操作,客户端资源的设置与 Lettuce 的性能、并发和事件处理紧密相关,如果不是特别熟悉客户端参数配置,不建议在没有经验的前提下凭直觉修改默认值,保持默认配置就行。
非集群环境下,具体的配置案例如下:
public class LettuceMain {
public static void main(String[] args) throws Exception { ClientResources resources = DefaultClientResources.builder() .ioThreadPoolSize(4) //I/O线程数 .computationThreadPoolSize(4) //任务线程数 .build(); RedisURI redisUri = RedisURI.builder() .withHost("127.0.0.1").withPort(6379).withPassword("111111") .withTimeout(Duration.of(10, ChronoUnit.SECONDS)) .build(); ClientOptions options = ClientOptions.builder() .autoReconnect(true)//是否自动重连 .pingBeforeActivateConnection(true)//连接激活之前是否执行PING命令 .build(); RedisClient client = RedisClient.create(resources, redisUri); client.setOptions(options); StatefulRedisConnection<String, String> connection = client.connect(); RedisCommands<String, String> commands = connection.sync(); commands.set("name", "关羽"); System.out.println(commands.get("name"));
connection.close(); client.shutdown(); resources.shutdown(); } }
|
集群环境下,具体的配置案例如下:
public class LettuceMain {
public static void main(String[] args) throws Exception { ClientResources resources = DefaultClientResources.builder() .ioThreadPoolSize(4) //I/O线程数 .computationThreadPoolSize(4) //任务线程数 .build(); RedisURI redisUri = RedisURI.builder() .withHost("127.0.0.1").withPort(6379).withPassword("111111") .withTimeout(Duration.of(10, ChronoUnit.SECONDS)) .build(); ClusterClientOptions options = ClusterClientOptions.builder() .autoReconnect(true)//是否自动重连 .pingBeforeActivateConnection(true)//连接激活之前是否执行PING命令 .validateClusterNodeMembership(true)//是否校验集群节点的成员关系 .build(); RedisClusterClient client = RedisClusterClient.create(resources, redisUri); client.setOptions(options); StatefulRedisClusterConnection<String, String> connection = client.connect(); RedisAdvancedClusterCommands<String, String> commands = connection.sync(); commands.set("name", "张飞"); System.out.println(commands.get("name"));
connection.close(); client.shutdown(); resources.shutdown(); } }
|
3.1.6 线程池配置
Lettuce 连接设计的时候,就是线程安全的,所以一个连接可以被多个线程共享,同时 lettuce 连接默认是自动重连的,使用单连接基本可以满足业务需求,大多数情况下不需要配置连接池,多连接并不会给操作带来性能上的提升。
但在某些特殊场景下,比如事物操作,使用连接池会是一个比较好的方案,那么如何配置线程池呢?
public class LettuceMain {
public static void main(String[] args) throws Exception { RedisURI redisUri = RedisURI.builder() .withHost("127.0.0.1") .withPort(6379) .withPassword("111111") .withTimeout(Duration.of(10, ChronoUnit.SECONDS)) .build(); RedisClient client = RedisClient.create(redisUri); //连接池配置 GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); poolConfig.setMaxIdle(2);
GenericObjectPool<StatefulRedisConnection<String, String>> pool = ConnectionPoolSupport.createGenericObjectPool(client::connect, poolConfig); StatefulRedisConnection<String, String> connection = pool.borrowObject(); RedisCommands<String, String> commands = connection.sync(); commands.set("name", "张飞"); System.out.println(commands.get("name"));
connection.close(); pool.close(); client.shutdown(); } }
|
3.2 集群配置
3.2.1 主从模式配置
redis 一般采用主从复制模式,搭建高可用的架构,简单的说就一个主节点,多个从节点,自动从主节点同步最新数据。
Lettuce 支持自动发现主从模式下的节点信息,然后保存到本地,具体配置如下:
public class LettuceMain {
public static void main(String[] args) throws Exception { //这里只需要配置一个节点的连接信息,不一定需要是主节点的信息,从节点也可以;可以自动发现主从节点 RedisURI uri = RedisURI.builder().withHost("192.168.31.111").withPort(6379).withPassword("123456").build(); RedisClient client = RedisClient.create(uri); StatefulRedisMasterReplicaConnection<String, String> connection = MasterReplica.connect(client, StringCodec.UTF8, uri); //从节点读取数据 connection.setReadFrom(ReadFrom.REPLICA);
RedisCommands<String, String> commands = connection.sync(); commands.set("name", "张飞"); System.out.println(commands.get("name"));
connection.close(); client.shutdown(); } }
|
当然我们也可以手动指定集群节点来加载,具体配置如下:
public class LettuceMain {
public static void main(String[] args) throws Exception { //集群节点 List<RedisURI> uris = new ArrayList(); uris.add(RedisURI.builder().withHost("192.168.31.111").withPort(6379).withPassword("111111").build()); uris.add(RedisURI.builder().withHost("192.168.31.112").withPort(6379).withPassword("111111").build()); uris.add(RedisURI.builder().withHost("192.168.31.113").withPort(6379).withPassword("111111").build());
RedisClient client = RedisClient.create(); StatefulRedisMasterReplicaConnection<String, String> connection = MasterReplica.connect(client, StringCodec.UTF8, uris); //从节点读取数据 connection.setReadFrom(ReadFrom.REPLICA);
RedisCommands<String, String> commands = connection.sync(); commands.set("name", "张飞"); System.out.println(commands.get("name"));
connection.close(); client.shutdown(); } }
|
3.2.2 哨兵模式配置
哨兵模式,也是 redis 实现服务高可用的一大亮点,具体配置实现如下:
public class LettuceMain {
public static void main(String[] args) throws Exception { //集群节点 List<RedisURI> uris = new ArrayList(); uris.add(RedisURI.builder().withSentinel("192.168.31.111", 26379).withSentinelMasterId("mymaster").withPassword("123456").build()); uris.add(RedisURI.builder().withSentinel("192.168.31.112", 26379).withSentinelMasterId("mymaster").withPassword("123456").build()); uris.add(RedisURI.builder().withSentinel("192.168.31.113", 26379).withSentinelMasterId("mymaster").withPassword("123456").build());
RedisClient client = RedisClient.create(); StatefulRedisMasterReplicaConnection<String, String> connection = MasterReplica.connect(client, StringCodec.UTF8, uris); //从节点读取数据 connection.setReadFrom(ReadFrom.REPLICA);
RedisCommands<String, String> commands = connection.sync(); commands.set("name", "赵云"); System.out.println(commands.get("name"));
connection.close(); client.shutdown(); } }
|
3.2.3 Cluster 集群模式配置
Cluster 集群模式,是之后推出的一种高可用的架构模型,主要是采用分片方式来存储数据,具体配置如下:
public class LettuceReactiveMain4 {
public static void main(String[] args) throws Exception { Set<RedisURI> uris = new HashSet<>(); uris.add(RedisURI.builder().withHost("192.168.31.111").withPort(7000).withPassword("123456").build()); uris.add(RedisURI.builder().withHost("192.168.31.112").withPort(7000).withPassword("123456").build()); uris.add(RedisURI.builder().withHost("192.168.31.113").withPort(7000).withPassword("123456").build()); uris.add(RedisURI.builder().withHost("192.168.31.114").withPort(7000).withPassword("123456").build()); uris.add(RedisURI.builder().withHost("192.168.31.115").withPort(7000).withPassword("123456").build()); uris.add(RedisURI.builder().withHost("192.168.31.116").withPort(7001).withPassword("123456").build());
RedisClusterClient client = RedisClusterClient.create(uris); StatefulRedisClusterConnection<String, String> connection = client.connect(); RedisAdvancedClusterCommands<String, String> commands = connection.sync(); commands.set("name", "关羽"); System.out.println(commands.get("name"));
//选择从节点,只读 NodeSelection<String, String> replicas = commands.replicas(); NodeSelectionCommands<String, String> nodeSelectionCommands = replicas.commands(); Executions<List<String>> keys = nodeSelectionCommands.keys("*"); keys.forEach(key -> System.out.println(key));
connection.close(); client.shutdown(); } }
|
3.3 优缺点
Lettuce 相比老牌的 Jedis 客户端,功能更加强大,不仅解决了线程安全的问题,还支持异步和响应式编程,支持集群,Sentinel,管道和编码器等等功能。
4. Redission
什么是 Redisson?来自于官网上的描述内容如下!
Redisson 是一个在 Redis 的基础上实现的 Java 驻内存数据网格客户端(In-Memory Data Grid)。它不仅提供了一系列的 redis 常用数据结构命令服务,还提供了许多分布式服务,例如分布式锁、分布式对象、分布式集合、分布式远程服务、分布式调度任务服务等等。
相比于 Jedis、Lettuce 等基于 redis 命令封装的客户端,Redisson 提供的功能更加高端和抽象,逼格高!
官网:点我跳转
4.1 基本使用
跟过去一样,首先创建一个 maven 项目,添加Redisson依赖包。
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.13.6</version> </dependency>
|
单机环境下,简单样例如下!
public class RedissonMain {
public static void main(String[] args) { Config config = new Config(); config.useSingleServer() .setAddress("redis://127.0.0.1:6379") .setPassword("123456") .setDatabase(0); //获取客户端 RedissonClient redissonClient = Redisson.create(config); //获取所有的key redissonClient.getKeys().getKeys().forEach(key -> System.out.println(key)); //关闭客户端 redissonClient.shutdown(); } }
|
4.1.1 字符串操作
Redisson 支持通过RBucket对象来操作字符串数据结构,通过RBucket实例可以设置value或设置value和有效期,简单样例如下!
RBucket<String> rBucket = redissonClient.getBucket("strKey");
rBucket.set("张三", 30, TimeUnit.SECONDS);
System.out.println(redissonClient.getBucket("strKey").get());
|
4.1.2 对象操作
Redisson 支持将对象作为value存入redis,被存储的对象事先必须要实现序列化接口Serializable,否则会报错,简单样例如下!
public class Student implements Serializable {
private Long id;
private String name;
private Integer age;
@Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; } }
|
Student student = new Student(); student.setId(1L); student.setName("张三"); student.setAge(18);
RBucket<Student> rBucket = redissonClient.getBucket("objKey");
rBucket.set(student, 30, TimeUnit.SECONDS);
System.out.println(redissonClient.getBucket("objKey").get());
|
4.1.3 哈希操作
Redisson 支持通过RMap对象来操作哈希数据结构,简单样例如下!
RMap<String, String> rMap = redissonClient.getMap("mapkey");
rMap.put("id", "123"); rMap.put("name", "赵四"); rMap.put("age", "50");
rMap.expire(30, TimeUnit.SECONDS);
System.out.println(redissonClient.getMap("mapkey").get("name"));
|
4.1.4 列表操作
Redisson 支持通过RList对象来操作列表数据结构,简单样例如下!
RList<Student> rList = redissonClient.getList("listkey");
Student student1 = new Student(); student1.setId(1L); student1.setName("张三"); student1.setAge(18); rList.add(student1);
Student student2 = new Student(); student2.setId(2L); student2.setName("李四"); student2.setAge(19); rList.add(student2);
rList.expire(30, TimeUnit.SECONDS);
System.out.println(redissonClient.getList("listkey"));
|
4.1.5 集合操作
Redisson 支持通过RSet对象来操作集合数据结构,简单样例如下!
RSet<Student> rSet = redissonClient.getSet("setkey");
Student student1 = new Student(); student1.setId(1L); student1.setName("张三"); student1.setAge(18); rSet.add(student1);
Student student2 = new Student(); student2.setId(2L); student2.setName("李四"); student2.setAge(19); rSet.add(student2);
rSet.expire(30, TimeUnit.SECONDS);
System.out.println(redissonClient.getSet("setkey"));
|
4.1.6 有序集合操作
Redisson 支持通过RSortedSet对象来操作有序集合数据结构,在使用对象来存储之前,实体对象必须先实现Comparable接口,并重写比较逻辑,否则会报错,简单样例如下!
public class Student implements Serializable, Comparable<Student> {
private Long id;
private String name;
private Integer age;
@Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}'; }
@Override public int compareTo(Student obj) { return this.getId().compareTo(obj.getId()); } }
|
RSortedSet<Student> sortSetkey = redissonClient.getSortedSet("sortSetkey");
Student student1 = new Student(); student1.setId(1L); student1.setName("张三"); student1.setAge(18); sortSetkey.add(student1);
Student student2 = new Student(); student2.setId(2L); student2.setName("李四"); student2.setAge(19); sortSetkey.add(student2);
System.out.println(redissonClient.getSortedSet("sortSetkey"));
|
4.1.7 布隆过滤器
布隆过滤器(Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。
布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
Redisson 支持通过RBloomFilter对象来操作布隆过滤器,简单样例如下!
RBloomFilter rBloomFilter = redissonClient.getBloomFilter("seqId");
rBloomFilter.tryInit(10000, 0.01);
rBloomFilter.add("100"); rBloomFilter.add("200"); rBloomFilter.add("300");
rBloomFilter.expire(30, TimeUnit.SECONDS);
System.out.println(rBloomFilter.contains("300")); System.out.println(rBloomFilter.contains("200")); System.out.println(rBloomFilter.contains("999"));
|
4.1.8 分布式自增ID
ID 是数据的唯一标识,传统的做法是利用 UUID 和数据库的自增 ID。
但由于 UUID 是无序的,不能附带一些其他信息,因此实际作用有限。
随着业务的发展,数据量会越来越大,需要对数据进行分表,甚至分库。分表后每个表的数据会按自己的节奏来自增,这样会造成 ID 冲突,因此这时就需要一个单独的机制来负责生成唯一 ID,redis 原生支持生成全局唯一的 ID。
简单样例如下!
final String lockKey = "aaaa";
RAtomicLong atomicLong = redissonClient.getAtomicLong(lockKey);
atomicLong.expire(30, TimeUnit.SECONDS);
System.out.println(atomicLong.incrementAndGet());
|
4.1.9 分布式锁
Redisson 最大的亮点,也是使用最多的功能,就是提供了强大的分布式锁实现,特点是:使用简单、安全!
简单使用样例如下!
Config config = new Config(); config.useSingleServer() .setAddress("redis://127.0.0.1:6379") .setPassword("123456") .setDatabase(0); RedissonClient redissonClient = Redisson.create(config);
final String lockKey = "abc"; RLock rLock = redissonClient.getLock(lockKey);
try {
boolean res = rLock.tryLock(5L, 60L, TimeUnit.SECONDS); if (res) {
System.out.println("获取锁成功"); } } catch (Exception e) { System.out.println("获取锁失败,失败原因:" + e.getMessage()); } finally {
rLock.unlock(); }
redissonClient.shutdown();
|
以上是单机环境下的分布式锁实现逻辑,如果是集群环境下,应该如何处理呢?
Redisson 提供RedissonRedLock操作类,也被称为红锁,实现原理简单的总结有以下几点:
- 1.如果有多个 redis 集群的时候,当且仅当从大多数(N/2+1,比如有3个 redis 节点,那么至少有2个节点)的 Redis 节点都取到锁,并且获取锁使用的总耗时小于锁失效时间时,锁才算获取成功
- 2.如果获取失败,客户端会在所有的 Redis 实例上进行解锁操作
- 3.集群环境下,redis 服务器直接不存在任何复制或者其他隐含的分布式协调机制,否则会存在实效的可能
RedissonRedLock简单使用样例如下!
Config config1 = new Config(); config1.useSingleServer().setAddress("redis://192.168.3.111:6379").setPassword("a123456").setDatabase(0); RedissonClient redissonClient1 = Redisson.create(config1);
Config config2 = new Config(); config2.useSingleServer().setAddress("redis://192.168.3.112:6379").setPassword("a123456").setDatabase(0); RedissonClient redissonClient2 = Redisson.create(config2);
Config config3 = new Config(); config3.useSingleServer().setAddress("redis://192.168.3.113:6379").setPassword("a123456").setDatabase(0); RedissonClient redissonClient3 = Redisson.create(config3);
final String lockKey = "abc"; RLock lock1 = redissonClient1.getLock(lockKey); RLock lock2 = redissonClient2.getLock(lockKey); RLock lock3 = redissonClient3.getLock(lockKey);
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {
boolean res = redLock.tryLock(5L, 60L, TimeUnit.SECONDS); if (res) {
System.out.println("获取锁成功");
} } catch (Exception e) { System.out.println("获取锁失败,失败原因:" + e.getMessage()); } finally {
redLock.unlock(); }
|
4.2 集群配置
4.2.1 集群模式
如果是集群环境,我们可以采用如下方式进行配置:
Config config = new Config(); config.useClusterServers() .setScanInterval(2000) // 集群状态扫描间隔时间,单位是毫秒 //可以用"rediss://"来启用SSL连接 .addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001") .addNodeAddress("redis://127.0.0.1:7002");
RedissonClient redisson = Redisson.create(config);
|
4.2.2 哨兵模式
哨兵模式,参数配置方式如下:
Config config = new Config(); config.useSentinelServers() .setMasterName("mymaster") //可以用"rediss://"来启用SSL连接 .addSentinelAddress("127.0.0.1:26389", "127.0.0.1:26379") .addSentinelAddress("127.0.0.1:26319");
RedissonClient redisson = Redisson.create(config);
|
4.2.3 主从模式
主从模式,参数配置方式如下:
Config config = new Config(); config.useMasterSlaveServers() //可以用"rediss://"来启用SSL连接 .setMasterAddress("redis://127.0.0.1:6379") .addSlaveAddress("redis://127.0.0.1:6389", "redis://127.0.0.1:6332", "redis://127.0.0.1:6419") .addSlaveAddress("redis://127.0.0.1:6399");
RedissonClient redisson = Redisson.create(config);
|
5. 总结
- Jedis:Redis 官方推出的用于通过 Java 连接 Redis 客户端的一个工具包,它提供了全面的类似于 Redis 原生命令的支持,是目前使用最广的一款 java 客户端。
- Lettuce:一个可扩展的线程安全的 Redis 客户端,通讯框架基于 Netty 开发,支持高级的 Redis 特性,比如哨兵,集群,管道,自动重新连接等特性。从 Spring Boot 2.x 开始, Lettuce 已取代 Jedis 成为首选 Redis 的客户端。
- Redisson:一款架设在 Redis 基础上,通讯基于 Netty 的综合的、新型的中间件,是企业级开发中使用 Redis 的最佳范本。
总结下来,Jedis 把 Redis 命令封装的非常全面,Lettuce 则进一步丰富了 Api,支持 Redis 各种高级特性。
但是两者并没有进一步深化,只给了你操作 Redis 数据库的工具,而 Redisson 则是基于 Redis、Lua 和 Netty 建立起了一套的分布式解决方案,比如分布式锁的实现,分布式对象的操作等等。
在实际使用过程中,Lettuce + Redisson组合使用的比较多,两者相铺相成。
关于分布式锁实现的应用,生产环境推荐尽量采用单点环境来实现,基本上解决绝大部分的分布式锁问题,如果当前服务的环境确实很复杂,可以采用RedissonRedLock来实现。