Redis不仅支持简单的键值存储,还提供了丰富的数据结构(如列表、哈希表、集合等)和强大的原子操作,使得它在存储和处理数据时非常高效。关于这些数据结构的学习可以学习下面的博客:
【Redis】String的常用命令及图解String使用场景_string命令在做什么-CSDN博客
【Redis】哈希类型的常用命令以及使用场景-CSDN博客
【Redis】List的常用命令以及常用场景-CSDN博客
【Redis】Set 集合常用命令以及使用场景-CSDN博客
【Redis】Zset有序集合常用命令以及使用场景-CSDN博客
1. 引言
在实际的开发中,我们总不可能直接还是在redis-cli中使用命令来操作吧。就像是MySQL我们可以通过JDBC\MyBatis等来操作数据库,在java中同样可以使用java客户端的Jedis来操作redis。使用Redis客户端,比如Jedis,可以方便地在Java应用程序中与Redis进行通信,从而利用其快速的数据读写能力和丰富的数据操作功能。
Jedis是Redis官方推荐的Java客户端之一,它基于Redis的RESP(Redis Serialization Protocol)协议实现,提供了简洁清晰的API,支持连接池管理和管道操作,同时在社区中有广泛的使用和支持。
在使用Jedis作为Java应用与Redis交互的客户端时,一个常见的问题是其线程安全性。Jedis是直连模式的客户端,意味着在多线程环境下共享一个Jedis实例可能会导致数据混乱和不一致性。
edis的线程不安全性主要源于其底层的RedisOutputStream和RedisInputStream对象,它们是Jedis中的全局属性。当多个线程同时使用同一个Jedis实例时,它们会共享这些全局属性,这可能导致以下问题:
- 数据混乱和不一致性: 多个线程同时操作Redis的写流和读流,可能导致数据的交叉写入或读取,从而产生不可预测的结果。
- 并发操作异常: 在高并发情况下,多个线程对同一个Jedis实例的并发操作可能触发Jedis内部状态的不一致,进而导致异常或数据丢失。
解决方案:使用Jedis连接池(JedisPool)
为了避免上述问题,推荐的做法是为每个线程分配独立的Jedis实例,而不是共享一个实例。这可以通过Jedis连接池(JedisPool)来实现,连接池能够有效地管理多个Jedis实例,并确保线程安全的使用方式。
2. 快速上手
2.1 配置相关以及环境要求
在maven仓库找到Jedis的依赖:
<dependencies><!-- https://mvnrepository.com/artifact/redis.clients/jedis --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.4.2</version></dependency></dependencies>
在导入依赖后,并不是直接写代码就可以上手的。目前一般来说使用的环境是在windows上,而现在我的Redis客户端是在服务器上的。直接开放redis的端口是比较危险的,因此这里一般有两个做法:
- 直接让java程序也在服务器上运行。也就是把自己写好的代码打成jar包拷贝到linux的服务器上执行。手动还挺麻烦的,也有一些第三方的插件来简化这些步骤;
- 配置ssh端口转发,把云服务器的redis端口映射到本地主机。
这里主要讲解一下通过配置端口转发来解决上述问题。
此时访问本地端口就相当于访问远程服务器的端口。
在 xshell 中,进行如下配置:
1)右键云服务器的会话,选择属性
2)找到隧道 ->配置转移规则
2.2 连接Redis Server
public class RedisExample {public static void main(String[] args) {// 创建Jedis对象,指定Redis服务器地址和端口号Jedis jedis = new Jedis("127.0.0.1", 8888);// 如果Redis服务器设置了密码,需要进行认证// jedis.auth("password");// 测试连接是否正常System.out.println("连接成功");// 存储数据jedis.set("name", "Alice");String value = jedis.get("name");System.out.println("Stored value in Redis: " + value);// 关闭连接jedis.close();}
3.3 Jedis针对不同数据结构
Redis支持多种数据结构,例如哈希表、列表、集合等,可以使用Jedis进行操作:
/ 连接Redis
Jedis jedis = new Jedis("localhost", 6379);// 使用哈希表存储用户信息
jedis.hset("user:1", "name", "Alice");
jedis.hset("user:1", "age", "30");// 获取用户信息
String name = jedis.hget("user:1", "name");
String age = jedis.hget("user:1", "age");
System.out.println("User name: " + name + ", age: " + age);// 关闭连接
jedis.close();
jedis实例实现了大多数Redis命令。有关支持的命令的完整列表,请参阅jedis Javadocs:Jedis - jedis 5.2.0-beta4 javadochttps://www.javadoc.io/doc/redis.clients/jedis/latest/redis/clients/jedis/Jedis.html
3.4 连接池管理
在Java应用中使用Jedis操作Redis时,使用连接池(如JedisPool)是非常重要的。
Jedis实例是线程不安全的。Jedis实例中的RedisOutputStream
和RedisInputStream
是全局属性,当多个线程同时使用同一个Jedis实例时,会导致数据混乱和不可预测的结果。因此,需要保证每个线程都使用独立的Jedis实例。
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;public class RedisExample {private static JedisPool jedisPool;static {// 配置连接池JedisPoolConfig poolConfig = new JedisPoolConfig();poolConfig.setMaxTotal(10); // 设置最大连接数poolConfig.setMaxIdle(5); // 设置最大空闲连接数poolConfig.setMinIdle(1); // 设置最小空闲连接数poolConfig.setTestOnBorrow(true); // 借用连接时进行测试// 初始化连接池jedisPool = new JedisPool(poolConfig, "localhost", 8888, 2000, "your_redis_password"); // 如果有密码,请填写}public static void main(String[] args) {// 使用连接池获取Jedis实例try (Jedis jedis = jedisPool.getResource()) {// 测试连接是否正常System.out.println("连接成功: " + jedis.ping());// 进行Redis操作jedis.set("key", "value");String value = jedis.get("key");System.out.println("Stored value in Redis: " + value);} catch (Exception e) {e.printStackTrace();} finally {// 关闭连接池if (jedisPool != null) {jedisPool.close();}}}
}
连接池配置解释
setMaxTotal
: 最大连接数,连接池中能够分配的最大Jedis实例数。setMaxIdle
: 最大空闲连接数,连接池中允许保留的最大空闲Jedis实例数。setMinIdle
: 最小空闲连接数,连接池中最小的空闲Jedis实例数,低于这个数量时,连接池会创建新的实例。setTestOnBorrow
: 借用连接时进行测试,确保获取的Jedis实例可用。
对于线程池还不了解到可以去看这篇博客:
深入解剖线程池(ThreadPoolExecutor)_newfixedthreadpool 线程释放-CSDN博客
线程池中的参数很相似。
连接池参数源码
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阻塞直到超时, 默认trueconfig.setBlockWhenExhausted(true);// 设置的逐出策略类名, 默认DefaultEvictionPolicy(当连接超过最大空闲时间,或连接数超过最大空闲连接数)config.setEvictionPolicyClassName("org.apache.commons.pool2.impl.DefaultEvictionPolicy");// 是否启用pool的jmx管理功能, 默认trueconfig.setJmxEnabled(true);// MBean ObjectName = new ObjectName("org.apache.commons.pool2:type=GenericObjectPool,name=" + "pool" + i); 默认为"pool", JMX不熟,具体不知道是干啥的...默认就好.config.setJmxNamePrefix("pool");// 是否启用后进先出, 默认trueconfig.setLifo(true);// 最大空闲连接数, 默认8个config.setMaxIdle(8);// 最大连接数, 默认8个config.setMaxTotal(8);// 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1config.setMaxWaitMillis(-1);// 逐出连接的最小空闲时间 默认1800000毫秒(30分钟)config.setMinEvictableIdleTimeMillis(1800000);// 最小空闲连接数, 默认0config.setMinIdle(0);// 每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3config.setNumTestsPerEvictionRun(3);// 对象空闲多久后逐出, 当空闲时间>该值 且 空闲连接>最大空闲数 时直接逐出,不再根据MinEvictableIdleTimeMillis判断 (默认逐出策略)config.setSoftMinEvictableIdleTimeMillis(1800000);// 在获取连接的时候检查有效性, 默认falseconfig.setTestOnBorrow(false);// 在空闲时检查有效性, 默认falseconfig.setTestWhileIdle(false);// 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1config.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();}}
}