前言
Redis在Java’中的客户端在GitHub上start最高的是Jedis和Redisson。Jedis提供了完整的Redis命令,而Redisson有更多分布式的容器实现。
如何使用Jedis
1.导入依赖
首先通过Maven引入Jedis的依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.0</version>
</dependency>
2.创建Jedis对象
Jedis jedis = new Jedis("localhost", 6379);
jedis.set("singleJedis", "hello jedis!");
System.out.println(jedis.get("singleJedis"));
jedis.close();
Jedis对象不是线程安全的,在多线程下使用同一个Jedis对象会出现并发问题,为了避免每次使用Jedis对象时都需要重新创建,Jedis提供了JedisPool。Jedis是线程安全的连接池
3.配置属性文件
#redis config start
redis.ip=127.0.0.1
redis.port=6379
#最大连接数
redis.max.total=20
#最大空闲数
redis.max.idle=10
#最小空闲数
redis.min.idle=2
#从jedis连接池获取连接时,校验并返回可用的连接
redis.test.borrow=true
#把连接放回jedis连接池时,校验并返回可用的连接
redis.test.return=true
4.JedisPool
public class RedisPool {
private static JedisPool pool;//jedis连接池
private static Integer maxTotal = Integer.parseInt(PropertiesUtil.getProperty("redis.max.total","20")); //最大连接数
private static Integer maxIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.max.idle","20"));//在jedispool中最大的idle状态(空闲的)的jedis实例的个数
private static Integer minIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.min.idle","20"));//在jedispool中最小的idle状态(空闲的)的jedis实例的个数
private static Boolean testOnBorrow = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.borrow","true"));//在borrow一个jedis实例的时候,是否要进行验证操作,如果赋值true。则得到的jedis实例肯定是可以用的。
private static Boolean testOnReturn = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.return","true"));//在return一个jedis实例的时候,是否要进行验证操作,如果赋值true。则放回jedispool的jedis实例肯定是可以用的。
private static String redisIp = PropertiesUtil.getProperty("redis.ip");
private static Integer redisPort = Integer.parseInt(PropertiesUtil.getProperty("redis.port"));
private static void initPool(){
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(maxTotal);
config.setMaxIdle(maxIdle);
config.setMinIdle(minIdle);
config.setTestOnBorrow(testOnBorrow);
config.setTestOnReturn(testOnReturn);
config.setBlockWhenExhausted(true);//连接耗尽的时候,是否阻塞,false会抛出异常,true阻塞直到超时。默认为true。
pool = new JedisPool(config,redisIp,redisPort,1000*2);
}
//静态代码块,初始化Redis池
static{
initPool();
}
public static Jedis getJedis(){
return pool.getResource();
}
public static void returnBrokenResource(Jedis jedis){
pool.returnBrokenResource(jedis);
}
public static void returnResource(Jedis jedis){
pool.returnResource(jedis);
}
}
5.封装Jedis
@Slf4j
public class RedisPoolUtil {
/**
* 设置key的有效期,单位是秒
* @param key
* @param exTime
* @return
*/
public static Long expire(String key,int exTime){
Jedis jedis = null;
Long result = null;
try {
//从Redis连接池中获得Jedis对象
jedis = RedisPool.getJedis();
//设置成功则返回Jedis对象
result = jedis.expire(key,exTime);
} catch (Exception e) {
log.error("expire key:{} error",key,e);
RedisPool.returnBrokenResource(jedis);
return result;
}
RedisPool.returnResource(jedis);
return result;
}
//exTime的单位是秒
//设置key-value并且设置超时时间
public static String setEx(String key,String value,int exTime){
Jedis jedis = null;
String result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.setex(key,exTime,value);
} catch (Exception e) {
log.error("setex key:{} value:{} error",key,value,e);
RedisPool.returnBrokenResource(jedis);
return result;
}
RedisPool.returnResource(jedis);
return result;
}
public static String set(String key,String value){
Jedis jedis = null;
String result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.set(key,value);
} catch (Exception e) {
log.error("set key:{} value:{} error",key,value,e);
RedisPool.returnBrokenResource(jedis);
return result;
}
RedisPool.returnResource(jedis);
return result;
}
public static String get(String key){
Jedis jedis = null;
String result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.get(key);
} catch (Exception e) {
log.error("get key:{} error",key,e);
RedisPool.returnBrokenResource(jedis);
return result;
}
RedisPool.returnResource(jedis);
return result;
}
public static Long del(String key){
Jedis jedis = null;
Long result = null;
try {
jedis = RedisPool.getJedis();
result = jedis.del(key);
} catch (Exception e) {
log.error("del key:{} error",key,e);
RedisPool.returnBrokenResource(jedis);
return result;
}
RedisPool.returnResource(jedis);
return result;
}
}
SharedJedis
为了提高应用的性能,我们通常不止使用一台Redis做缓存服务,而在Redis2.x版本中并没有支持集群,Redis从3.x版本才开始支持集群。Redis2.x版本下实现Redis服务器的扩展需要通过横向扩展的方式,即多个相互独立的主从服务器群,使用SharedJedis实现分布式缓存,SharedJedis通过一致性哈希来实现分布式缓存,即实现一定的策略将不同的key分配到不同的Redis Server上,达到横向扩展的目的。
1.RedisSharedPool
public class RedisShardedPool {
private static ShardedJedisPool pool;//sharded jedis连接池
private static Integer maxTotal = Integer.parseInt(PropertiesUtil.getProperty("redis.max.total","20")); //最大连接数
private static Integer maxIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.max.idle","20"));//在jedispool中最大的idle状态(空闲的)的jedis实例的个数
private static Integer minIdle = Integer.parseInt(PropertiesUtil.getProperty("redis.min.idle","20"));//在jedispool中最小的idle状态(空闲的)的jedis实例的个数
private static Boolean testOnBorrow = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.borrow","true"));//在borrow一个jedis实例的时候,是否要进行验证操作,如果赋值true。则得到的jedis实例肯定是可以用的。
private static Boolean testOnReturn = Boolean.parseBoolean(PropertiesUtil.getProperty("redis.test.return","true"));//在return一个jedis实例的时候,是否要进行验证操作,如果赋值true。则放回jedispool的jedis实例肯定是可以用的。
//获取Redis1相关信息
private static String redis1Ip = PropertiesUtil.getProperty("redis1.ip");
private static Integer redis1Port = Integer.parseInt(PropertiesUtil.getProperty("redis1.port"));
//获取Redis2的相关信息
private static String redis2Ip = PropertiesUtil.getProperty("redis2.ip");
private static Integer redis2Port = Integer.parseInt(PropertiesUtil.getProperty("redis2.port"));
private static void initPool(){
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(maxTotal);
config.setMaxIdle(maxIdle);
config.setMinIdle(minIdle);
config.setTestOnBorrow(testOnBorrow);
config.setTestOnReturn(testOnReturn);
config.setBlockWhenExhausted(true);//连接耗尽的时候,是否阻塞,false会抛出异常,true阻塞直到超时。默认为true。
JedisShardInfo info1 = new JedisShardInfo(redis1Ip,redis1Port,1000*2);
JedisShardInfo info2 = new JedisShardInfo(redis2Ip,redis2Port,1000*2);
//创建一个JedisList
List<JedisShardInfo> jedisShardInfoList = new ArrayList<JedisShardInfo>(2);
jedisShardInfoList.add(info1);
jedisShardInfoList.add(info2);
//RedisSharedPool传入JedisList
pool = new ShardedJedisPool(config,jedisShardInfoList, Hashing.MURMUR_HASH, Sharded.DEFAULT_KEY_TAG_PATTERN);
}
static{
initPool();
}
public static ShardedJedis getJedis(){
return pool.getResource();
}
public static void returnBrokenResource(ShardedJedis jedis){
pool.returnBrokenResource(jedis);
}
public static void returnResource(ShardedJedis jedis){
pool.returnResource(jedis);
}
}
2.封装ShraedJedis
@Slf4j
public class RedisShardedPoolUtil {
/**
* 设置key的有效期,单位是秒
* @param key
* @param exTime
* @return
*/
public static Long expire(String key,int exTime){
ShardedJedis jedis = null;
Long result = null;
try {
//根据SharedJedis的一致性哈希策略从对应的Redis Server获取值或设置值
jedis = RedisShardedPool.getJedis();
result = jedis.expire(key,exTime);
} catch (Exception e) {
log.error("expire key:{} error",key,e);
RedisShardedPool.returnBrokenResource(jedis);
return result;
}
RedisShardedPool.returnResource(jedis);
return result;
}
//exTime的单位是秒
public static String setEx(String key,String value,int exTime){
ShardedJedis jedis = null;
String result = null;
try {
jedis = RedisShardedPool.getJedis();
result = jedis.setex(key,exTime,value);
} catch (Exception e) {
log.error("setex key:{} value:{} error",key,value,e);
RedisShardedPool.returnBrokenResource(jedis);
return result;
}
RedisShardedPool.returnResource(jedis);
return result;
}
public static String set(String key,String value){
ShardedJedis jedis = null;
String result = null;
try {
jedis = RedisShardedPool.getJedis();
result = jedis.set(key,value);
} catch (Exception e) {
log.error("set key:{} value:{} error",key,value,e);
RedisShardedPool.returnBrokenResource(jedis);
return result;
}
RedisShardedPool.returnResource(jedis);
return result;
}
public static String getSet(String key,String value){
ShardedJedis jedis = null;
String result = null;
try {
jedis = RedisShardedPool.getJedis();
result = jedis.getSet(key,value);
} catch (Exception e) {
log.error("getset key:{} value:{} error",key,value,e);
RedisShardedPool.returnBrokenResource(jedis);
return result;
}
RedisShardedPool.returnResource(jedis);
return result;
}
public static String get(String key){
ShardedJedis jedis = null;
String result = null;
try {
jedis = RedisShardedPool.getJedis();
result = jedis.get(key);
} catch (Exception e) {
log.error("get key:{} error",key,e);
RedisShardedPool.returnBrokenResource(jedis);
return result;
}
RedisShardedPool.returnResource(jedis);
return result;
}
public static Long del(String key){
ShardedJedis jedis = null;
Long result = null;
try {
jedis = RedisShardedPool.getJedis();
result = jedis.del(key);
} catch (Exception e) {
log.error("del key:{} error",key,e);
RedisShardedPool.returnBrokenResource(jedis);
return result;
}
RedisShardedPool.returnResource(jedis);
return result;
}
public static Long setnx(String key,String value){
ShardedJedis jedis = null;
Long result = null;
try {
jedis = RedisShardedPool.getJedis();
result = jedis.setnx(key,value);
} catch (Exception e) {
log.error("setnx key:{} value:{} error",key,value,e);
RedisShardedPool.returnBrokenResource(jedis);
return result;
}
RedisShardedPool.returnResource(jedis);
return result;
}
}
除了初始化SharedJedisPool时需要加入多个Redis服务器信息外,其他和Jedis差不多
Shared一致性哈希实现
shared一致性哈希实现采用以下方案:
Redis服务器节点划分:将每台服务器节点采用hash算法划分为160个虚拟节点(可以配置划分权重)
将划分的虚拟节点采用TreeMap存储
对每个Redis服务器的物理连接采用LinkedHashMap存储
对Key or KeyTag 采用同样的hash算法,然后从TreeMap获取大于等于键hash值得节点,取最邻近节点存储;当key的hash值大于虚拟节点hash值得最大值时,存入第一个虚拟节点
sharded采用的hash算法:MD5 和 MurmurHash两种;默认采用64位的MurmurHash算法;MurmurHash是一种高效,低碰撞的hash算法