Redis
Spring 集成 Reids(Spring-data-redis)
Redis 客户端连接:Java
Redis 通过监听一个 TCP 端口或者 Unix socket 的方式来接收来自客户端的连接,当一个连接建立后,Redis 内部会进行以下一些操作:
- 客户端 socket 会被设置为非阻塞模式,因为 Redis 在网络事件处理上采用的是非阻塞多路复用模型。
- 为这个 socket 设置 TCP_NODELAY 属性,禁用 Nagle 算法
- 创建一个可读的文件事件用于监听这个客户端 socket 的数据发送
Redis 对于客户端连接相关的命令如下:
config get maxclients # 获取 redis-server 的客户端连接上限
config set maxclients millseconds_num # 修改 redis-server 客户端连接上限
client list # 返回连接到 redis-server 的客户端列表
client getname # 返回当前连接名称
client setname # 设置当前的连名称
client pause 10000 # 挂起当前客户端连接 10000 毫秒
client kill addr ip_port # 关闭指定端口的客户端连接,该 ip_port 必须是 client list 中列出的
client kill id client_id # 关闭指定 id 的客户端连接,该 client_id 必须是 client list 中列出的
如果需要允许所有外部 IP 访问 Redis ,需要注销掉 redis 配置文件中的
bind 127.0.0.1(此时 redis 最好设置终端登陆密码);
当然也可以通过 bind 设置指定的多个 ip 访问 redis;
Java 客户端连接的基本代码
在编写运行 Java 客户端之前,请下载安装配置 redis 服务端:
01. Redis 安装 & 配置
Java 连接 redis-server,读写 redis-server 缓存数据的基本代码如下:
public class RedisJavaTest {
private final static Logger log = LogManager.getLogger();
public static void main(String[] args) throws IOException {
/* 创建 redis 客户端 */
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.auth("assad"); //密码登陆
log.debug(jedis.ping()); //进行登陆测试
/* 从 redis-server 读写数据 */
// 写入 Java String 和 基本类型,对应 redis string 储存类型
jedis.set("key1","Hello world!");
jedis.set("key2",2333+"");
jedis.set("key3",2.333+"");
jedis.set("key4",false+"");
// 获取 redis string 类型
String val1 = jedis.get("key1");
int val2 = Integer.parseInt(jedis.get("key2"));
double val3 = Double.parseDouble(jedis.get("key3"));
boolean val4 = Boolean.parseBoolean(jedis.get("key4"));
log.debug("key1: " + val1);
log.debug("key2: " + val2);
log.debug("key3: " + val3);
log.debug("key4: " + val4);
// 写入 Java List 类型, 对应 redis list 类型
List<String> staffs = Arrays.asList("assad","vancy","canndy");
for(String staff : staffs)
jedis.rpush("staffs",staff);
// 获取 redis list 类型
List<String> staffList = jedis.lrange("staffs",0,-1);
log.debug("staffs: " + staffList.stream().collect(Collectors.joining(", ")));
// 写入 Java Set 类型,对应 redis set 类型
Set<String> citys = new HashSet<>(Arrays.asList("Guangzhou", "Shanghai", "Beijing"));
for(String city : citys)
jedis.sadd("citys",city);
// 获取 redis set 类型
Set<String> cityList = jedis.smembers("citys");
log.debug("citys: " + cityList.stream().collect(Collectors.joining(", ")));
// 写入Java Bean :写入为 redis hash 类型
User user1 = new User("assad",22,"Guangzou");
Map<String,String> userMap = new HashMap<>();
userMap.put("name",user1.getName());
userMap.put("age",user1.getAge()+"");
userMap.put("city",user1.getCity());
jedis.hmset("user:assad",userMap);
// 获取 redis hash 类型(Java bean)
Map<String,String> userMapped = jedis.hgetAll("user:assad");
User user2 = new User(userMapped.get("name"), Integer.parseInt(userMapped.get("age")), userMapped.get("city"));
log.debug("user: " + user2);
// 获取所有 keys
Set<String> keyset = jedis.keys("*");
log.debug("keys: " + keyset.stream().collect(Collectors.joining(", ")));
}
static class User implements Serializable{
public final static long serialVersionUID = 2333L;
private String name;
private int age;
private String city;
// getter,setter,contructor,toString
}
}
运行输出如下:
19:41:51.189 [main] DEBUG redis.RedisJavaTest - PONG
19:41:51.192 [main] DEBUG redis.RedisJavaTest - key1: Hello world!
19:41:51.192 [main] DEBUG redis.RedisJavaTest - key2: 2333
19:41:51.192 [main] DEBUG redis.RedisJavaTest - key3: 2.333
19:41:51.192 [main] DEBUG redis.RedisJavaTest - key4: false
19:41:51.232 [main] DEBUG redis.RedisJavaTest - staffs: assad, vancy, canndy
19:41:51.234 [main] DEBUG redis.RedisJavaTest - citys: Guangzhou, Beijing, Shanghai
19:41:51.235 [main] DEBUG redis.RedisJavaTest - user: User{name='assad', age=22, city='Guangzou'}
19:41:51.235 [main] DEBUG redis.RedisJavaTest - keys: key1, key2, user:assad, key3, key4, citys, staffs
可以看到,其实 Jedis 对于 redis-server 键值操作的成员方法大部分都对应 redis 的键值操作指令,关于 redis 基本的键值操作指令:
02. Redis 基本键值操作
以上写入 Java Bean 是将 Java Bean 转换为 redis hash 的方案,此外还可以将 POJO 直接转换为二进制字节写入,如下编写一个工具类:
public class RedisUnit {
// 序列化对象为字节
public static byte[] serialize(Object object){
ObjectOutputStream oos = null;
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(object);
byte[] bytes = baos.toByteArray();
return bytes;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
// 反序列化字节为对象
public static Object unserialize(byte[] bytes){
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try {
bais = new ByteArrayInputStream(bytes);
ois = new ObjectInputStream(bais);
Object obj = ois.readObject();
return obj;
}
catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
return null;
}
}
向 redis-server 读写 POJO 的代码:
// 写入 POJO
User user = new User("assad",22,"Guangzou");
jedis.setbit(user.getName(), 0, RedisUnit.serialize(user));
// 获取 POJO
User user2 = RedisUnit.unserialize(jedis.getbit("assad",0));
Java 客户端使用 Redis 管道技术
Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应,由此极大地提高数据传输速度;
以下使用 jedis 客户端使用和没有使用 pipeline 传输 100000 条 ping 指令的性能对比:
public class RedisPipeLineTest {
private final static Logger log = LogManager.getLogger();
public static void main(String[] args) throws IOException {
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.auth("assad");
final int commandCount = 100000;
//没有使用 pipeline 的情况;
long start = System.currentTimeMillis();
for(int i = 0; i < commandCount; i++){
jedis.ping();
}
long end = System.currentTimeMillis();
log.debug("without pipeline: " + (end - start) / 1000f + "s");
//使用 pipline 的情况;
start = System.currentTimeMillis();
Pipeline pipe = jedis.pipelined();
for(int i = 0; i < commandCount; i++)
pipe.ping();
end = System.currentTimeMillis();
log.debug("with pipeline: " + (end - start) / 1000f + "s");
pipe.clear();
pipe.close();
}
}
结果输出如下,可以看到使用 pipeline 和不使用 pipeline 之间的巨大性能差异;
20:11:59.045 [main] DEBUG redis.RedisJavaTest - without pipeline: 3.908s
20:11:59.081 [main] DEBUG redis.RedisJavaTest - with pipeline: 0.033s
使用 JedisPool 管理 Jedis 资源
当程序中不只使用一个 Jedis 实例时,可以使用 JedisPool 资源池对 Jedis 资源进行管理,示例代码如下:
//JedisPool 参数对象
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(50);
config.setMaxIdle(10);
config.setMaxWaitMillis(1000);
config.setTestOnBorrow(true);
config.setTestOnReturn(true);
String redisIp = "127.0.0.1"; //redis-server ip
int redisPort = 6379; //redis-server port
//创建 Jedis 连接池
JedisPool jedisPool = new JedisPool(config, redisIp, redisPort);
//从 JedisPool 中获取 Jedis
Jedis jedis = jedisPool.getResource();
//use jedis ......
//关闭 Jedis,将 Jedis 资源归还给 JedisPool
jedis.close();
JedisPoolConfig 的常用设置参数如下:
- blockWhenExhausted: 当连接池连接数耗尽时是否阻塞,默认 true(阻塞到超时),false 会报错;
- lifo: 对连接是否启用 LIFO 后进先出策略,默认 true;
- maxTotal: 最大连接数,默认 8;
- maxIdle : 最大空闲连接数,默认 8;
- maxWaitMillis: 获取连接时的最大等待毫秒数,默认 -1;
- minIdle: 最小空闲连接数,默认 0;
- minEvictableIdleTimeMillis: 逐出连接的最小空闲毫秒数,默认 1800000 ms(30 min);
- numTestsPerEvictionRun: 每次逐出检查时,逐出的最大数目,默认 3;
- softMinEvictableIdleTimeMillis:当连接对象空闲时间 > 该值,且 空闲连接 > 最大连接时,直接逐出该连接对象,不经过 minEvictableIdleTimeMillis 检查;
- timeBetweenEvictionRunsMillis:逐出扫描的时间间隔,默认 -1(不运行逐出线程);
- testOnBorrow:在获取连接时,是否检查其有效性,默认 false;
- testOnReturn:在归还连接对象时,是否检查有效性,默认 false;
- testWhileIdle:在连接空闲时,是否检查有效性,默认 false;
当然可以将这些启动参数编写到一个 properties 资源文件中,通过 Java Properties 对象获取该文件中的属性,以保护连接数据隔离,类似如下:
Properties props = new Properties();
props.load(getClassLoader().getResourceAsStream("./jedisPool.properties"));
JedisPool jedisPool = new JedisPool(props.getProperties("redisServer.ip"),props.getProperties("redisServer.port"));
......