jedis是一款用于java连接redis,并提供对应操作API的工具,有点类似于jdbc。
首先通过Maven引入Jedis的依赖:
redis.clients
jedis
2.9.0
创建Jedis对象:
// 连接Redis
Jedis jedis = new Jedis("localhost", 6379);
// 操作Redis
jedis.set("name", "xiaoming");
String name = jedis.get("name");
System.out.println(name);
jedis.rpush("list1", "v1", "v2", "v3");
List list = jedis.lrange("list1", 0, -1);
list.forEach(value -> {
System.out.println(value);
});
// 关闭Redis
jedis.close();
Jedis对象不是线程安全的,在多线程下使用同一个Jedis对象会出现并发问题,为了避免每次使用Jedis对象时都需要重新创建,Jedis提供了JedisPool 线程安全的连接池。
配置属性文件:
#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
3、Jedis数据操作
Jedis类用于java程序和redis数据库进行交互,一个最简单的程序就是用java往redis里放数据,注意Jedis类的构造方法如果使用的是空参构造则默认值为自己的ip和6379端口,若要连接别人的数据库则要用别人的ip和端口。
//1.获取连接
Jedis jedis = new Jedis("localhost",6379);
//2.调用对应的方法操作
jedis.set("username","zhangsan");
String username = jedis.get("username");
//3.关闭连接
jedis.close();
可以使用Jedis操作redis的各种数据结构,需要知道的是jedis的方法和我们用命令操作redis是相似的。
1)string类型
//1.获取连接
Jedis jedis = new Jedis("localhost",6379);
//2.调用对应的方法操作
jedis.set("username","zhangsan");
String username = jedis.get("username");
//可存储指定过期时间的数据
jedis.setex("activeCode",20,"valueString");
System.out.println(username);
//3.关闭连接
jedis.close();
2)Hash类型
//1.获取连接
Jedis jedis = new Jedis("localhost",6379);
//2.调用对应的方法操作
//存储hash
jedis.hset("user","name","zhangsan");
jedis.hset("user","age","23");
jedis.hset("user","gender","male");
//获取单个hash数据
String name = jedis.hget("user", "name");
System.out.println(name);
//获取hash的所有map中的数据
Map user = jedis.hgetAll("user");
for (String key : user.keySet()) {
String value = user.get(key);
System.out.println(key+":"+value);
}
//3.关闭连接
jedis.close();
3)列表类型
//1.获取连接
Jedis jedis = new Jedis("localhost",6379);
//2.调用对应的方法操作
//一次可以存多个值
jedis.lpush("mylist","a","b","c");//从左边存
jedis.rpush("mylist","a","b","c");//从右边存
//获取数据
List mylist = jedis.lrange("mylist", 0, -1);
System.out.println(mylist);
String element1 = jedis.lpop("mylist");
System.out.println(element1);
//3.关闭连接
jedis.close();
4)set类型
//1.获取连接
Jedis jedis = new Jedis("localhost",6379);
//2.调用对应的方法操作
jedis.sadd("myset","java","php","cpp");
Set myset = jedis.smembers("myset");
System.out.println(myset);
//3.关闭连接
jedis.close();
5)有序set类型
//1.获取连接
Jedis jedis = new Jedis("localhost",6379);
//2.调用对应的方法操作
jedis.zadd("mysortedset",3,"亚索");
jedis.zadd("mysortedset",5,"盖伦");
jedis.zadd("mysortedset",4,"猴子");
Set mysortedset = jedis.zrange("mysortedset", 0, -1);
System.out.println(mysortedset);
//3.关闭连接
jedis.close();
封装操作:
@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;
}
二、jedis连接池
jedis的连接池叫JedisPool,在创建连接池后我们可以从连接池中获取连接,客户端连接Redis使用的是TCP协议,直连的方式每次需要建立TCP连接,而连接池的方式是可以预先初始化好Jedis连接,所以每次只需要从Jedis连接池借用即可,而借用和归还操作是在本地进行的,只有少量的并发同步开销,远远小于新建TCP连接的开销。
基于连接池获取连接:
- JedisPool:Jedis 提供的连接池技术
- poolConfig:连接池配置对象
- host:redis 服务地址
- port:redis 服务端口号
用连接池的一次普通的流程:
//创建配置对象
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(50);//最大的连接数50
config.setMaxIdle(10);//最大的空闲连接数
//创建jedis连接池对象
JedisPool jedisPool = new JedisPool(config,"localhost",6379);
//获取连接
Jedis jedis = jedisPool.getResource();
//使用连接
jedis.set("username","hehe");
//关闭、归还连接到连接池中
jedis.close();
将来实际应用的难点在于参数的配置:
#最大活动对象数
redis.pool.maxTotal=1000
#最大能够保持idel状态的对象数
redis.pool.maxIdle=100
#最小能够保持idel状态的对象数
redis.pool.minIdle=50
#当池内没有返回对象时,最大等待时间
redis.pool.maxWaitMillis=10000
#当调用borrow Object方法时,是否进行有效性检查
redis.pool.testOnBorrow=true
#当调用return Object方法时,是否进行有效性检查
redis.pool.testOnReturn=true
#“空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1.
redis.pool.timeBetweenEvictionRunsMillis=30000
#向调用者输出“链接”对象时,是否检测它的空闲超时;
redis.pool.testWhileIdle=true
# 对于“空闲链接”检测线程而言,每次检测的链接资源的个数。默认为3.
redis.pool.numTestsPerEvictionRun=50
#redis服务器的IP
redis.ip=xxxxxx
#redis服务器的Port
redis1.port=6379
连接池工具类:
前面的示例程序还是有点儿小问题,就是 Jedis 对象的管理是我们自己创建的,真实企业开发中是不可能让你去 new 一个的,因此接下来就要做一个工具类。简单来说,就是做一个创建 Jedis 的这样的一个工具。
如果把配置放在代码里的话耦合度会比较高,所以一般我们把配置放在配置文件中,这样要使用的时候加载配置即可。
创建 jedis 的配置文件:jedis.properties
host=127.0.0.1
port=6379
maxTotal=50
maxIdle=10
加载配置我们在工具类的静态代码块中执行:
package util;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* Jedis连接池的工具类
* 加载配置文件,配置连接池的参数
* 提供获取连接的方法
*/
public class JedisPoolUtils {
private static JedisPool jedisPool;
static{
//获取输入流
InputStream is = JedisPoolUtils.class.getClassLoader().getResourceAsStream("jedis.properties");
//创建properties对象
Properties properties = new Properties();
// 加载配置文件,获取连接信息
// ResourceBundle bundle = ResourceBundle.getBundle("jedis");
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
}
//获取数据、设置到JedisPoolConfig中
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(Integer.parseInt(properties.getProperty("maxTotal")));
jedisPoolConfig.setMaxIdle(Integer.parseInt(properties.getProperty("maxIdle")));
String host = properties.getProperty("host");
Integer port = Integer.parseInt(properties.getProperty("port"));
System.out.println(host+","+port);
jedisPool = new JedisPool(jedisPoolConfig,host,port);
}
/**
* 获取连接的方法
*/
public static Jedis getJedis(){
return jedisPool.getResource();
}
}
测试代码:
import redis.clients.jedis.Jedis;
public class Demo {
public static void main(String[] args) {
Jedis jedis = JedisPoolUtils.getJedis();
System.out.println(jedis);
}
}
三、SharedJedis
为了提高应用的性能,我们通常不止使用一台Redis做缓存服务,而在Redis2.x版本中并没有支持集群,Redis从3.x版本才开始支持集群。Redis2.x版本下实现Redis服务器的扩展需要通过横向扩展的方式,即多个相互独立的主从服务器群,使用SharedJedis实现分布式缓存,SharedJedis通过一致性哈希来实现分布式缓存,即实现一定的策略将不同的key分配到不同的Redis Server上,达到横向扩展的目的。
1、RedisSharedPoolpublic 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 jedisShardInfoList = new ArrayList(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一致性哈希实现采用以下方案:
Redis服务器节点划分:将每台服务器节点采用hash算法划分为160个虚拟节点(可以配置划分权重)
1. 将划分的虚拟节点采用TreeMap存储。
2. 对每个Redis服务器的物理连接采用LinkedHashMap存储。
3. 对Key or KeyTag 采用同样的hash算法,然后从TreeMap获取大于等于键hash值得节点,取最邻近节点存储;当key的hash值大于虚拟节点hash值得最大值时,存入第一个虚拟节点。
sharded采用的hash算法:MD5 和 MurmurHash两种。
默认采用64位的MurmurHash算法,MurmurHash是一种高效,低碰撞的hash算法。