Jedis连接Redis
1.添加Jedis依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
2.在虚拟机端配置:将bind注释掉,然后改protected-mode为no

改了之后保存并重启Redis。
3.使用Jedis提供的Jedis这个工具类来连接Jedis,首先在虚拟机使用ifconfig命令查看虚拟机的ip,然后向Redis发送一个ping命令,测试一下是否可以连接上远程的Redis:
public class RedisTest {
private Jedis jedis = null;
@Before
public void getConnection() {
jedis = new Jedis("192.168.92.128", 6379);
}
/**
* 测试和虚拟机上的Redis的连通性
*/
@Test
public void pingTest() {
System.out.println(jedis.ping());
}
}
可以连接上Redis的标志是程序运行后打印”PONG“

如果程序运行出现了JedisConnectionException
,这种情况要么是你代码中把ip或端口写错了,要不就是由于Linux的防火墙导致的,在你确定你没有写错ip或端口的前提下,你可以直接关闭防火墙或者为了安全你可以开放6379这个端口给远程,Centos 7上开放端口有关的命令操作如下:
# 开放6379端口
firewall-cmd --permanent --add-port=6379/tcp
# 查询端口是否开放,yes就是开放的
firewall-cmd --query-port=6379/tcp
# 重新加载配置
firewall-cmd --reload
# 移除指定开放的端口
firewall-cmd --permanent --remove-port=6379/tcp
Jedis常用API
Jedis操作Redis的常用API几乎和Redis的命令是一样的,比如操作String:
/**
* String类型的数据接的测试
*/
@Test
public void testString() {
//set 添加key和value 如果已经有了就覆盖
jedis.set("k1", "111");
jedis.set("k2", "222");
jedis.set("k3", "333");
//get 获取值
System.out.println(jedis.get("k1") + " " + jedis.get("k2") + " " + jedis.get("k3"));
//set可以设置参数:EX(秒)/PX(毫秒)-->过期时间 NX 不存在时操作 XX存才时操作
jedis.set("k4", "444", new SetParams().nx()); //key不存在的时候进行操作
jedis.set("k3", "123", new SetParams().xx()); //key存在的时候操作
jedis.set("k5", "345", new SetParams().ex(60)); //设置key的生命时间,单位s
jedis.set("k6", "666", new SetParams().px(10000)); //设置key的生命时间,单位ms
//append 追加内容
jedis.append("k1", "hello"); //追加
//STRLEN
System.out.println(jedis.get("k1") + ",v1的长度:" + jedis.strlen("k1"));
//INCR INCRBY DECR DECRBY
jedis.incr("k6"); //加1
jedis.incrBy("k5", 100); //加任意增量
jedis.decr("k4"); //减1
jedis.decrBy("k3", 100); //减去任意减量
System.out.println(jedis.get("k3") + " " + jedis.get("k4") + " " + jedis.get("k5") + " " + jedis.get("k6") + " ");
//GETRANGE 获得字符串的一部分
System.out.println(jedis.getrange("k1", 0, -1));
//SETRANGE 设置字符串的一部分
jedis.setrange("k1", 0, "redis");
System.out.println(jedis.get("k1"));
jedis.mset("msg1", "hello", "msg2", "error", "msg3", "success");
System.out.println(jedis.mget("msg1", "msg2", "msg3"));
}
可以看到通过Jedis操作Redis所调用的API和Redis的命令是一样的,所以只要熟悉Redis的关于5大常用数据类型的命令,那么使用Jedis操作Redis就没有大的问题。如果你还不是熟悉Redis的关于5大常用数据类型的命令,可以参考我的这篇笔记:Redis五大常用数据类型
Jedis事务
Redis中和事务有关的命令:mulit
、exec
,discard
,watch
和unwatch
,然而Jedis中操作Redis事务的API也和这几个命令是一样的,比如我们实现一个简单的事务:
@Test
public void tx(){
Jedis jedis=new Jedis("192.168.92.128", 6379);
Transaction transaction = jedis.multi();//开启事务
//命令入队
transaction.set("k1","v1");
transaction.set("k2","v2");
transaction.get("k1");
//执行事务
List<Object> exec =transaction.exec();
//transaction.discard(); //取消事物
//打印执行事务后的返回结果
exec.forEach(ele->System.out.println(ele));
}
有两个关键字balance表示信用卡的余额(初始值为1000),debt表示信用卡的欠额(初始值为0),使用redis提供的乐观锁watch来实现对消费的记录
package com.xust.iot;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import java.util.List;
public class TxTest {
private Integer balance; //账户余额
private Integer debt; //账户欠额
private static Jedis jedis = new Jedis("192.168.92.128", 6379);
private static final int autoDECR = 10; //模拟每次消费的金额
public boolean coustmer() {
try {
String balance = jedis.watch("balance");//开启监控
//jedis.mset("balance","100","debt","400"); //模拟另一个线程在本线程开启了对balance监控以后改变了balance的值
this.balance = Integer.parseInt(jedis.get("balance"));
List<Object> exec =null;
if (this.balance > autoDECR) {
Transaction transaction = jedis.multi(); //开启事务
transaction.decrBy("balance", autoDECR);
transaction.incrBy("debt", autoDECR);
exec = transaction.exec(); //提交事务,执行事务后会自动unwatch
} else {
jedis.unwatch();
}
if(null!=exec){
System.out.println("操作成功:");
System.out.println("余额:"+jedis.get("balance") + " 欠额:" + jedis.get("debt"));
return true;
}else{
System.out.println("操作失败,监控到balance发生改动");
return false;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
jedis.close(); //关闭连接
}
return false;
}
public static void main(String[] args) {
TxTest txTest = new TxTest();
while (!txTest.coustmer()); //自旋,直到执行成功为止
}
}
正常情况下(就是没有别的线程干扰):

异常情况下:

解释一下这两种不同的结果: 当开启监控后,如果期间别的线程把监控的关键字的值改变了,那么Redis就会在本次事务期间不执行任何操作,即使使用exec提交事务了,也不会执行(这时返回exec的返回值是null),这种基于CAS的监控,不仅保证了共享数据的安全,而且还提高了响应速速。这也正是程序所体现的,当jedis.mset("balance","100","debt","400"); 这条语句被注释掉以后,程序可以正常执行,执行后返回true,程序结束;当jedis.mset("balance","100","debt","400");语句没有注释以后,在开启watch以后,相当于别的进程改变了监控关键字的值,那么这时Redis就不会在执行事务了,exec就会返回false,然后 while (!txTest.coustmer()); 就又再次调用方法,直到执行成功,然后结束程序。
JedisPool连接池
类似于mysql的数据库连接池c3p0、Durid等,JedisPool是java连接Redis的连接池,基本的使用方式如下:
public class RedisTest2 {
public static void main(String[] args) {
// 比较特殊的是,redis连接池的配置首先要创建一个连接池配置对象
JedisPoolConfig config = new JedisPoolConfig();
// 当然这里还有设置属性的代码
// 创建Jedis连接池对象
JedisPool jedisPool = new JedisPool(config,"localhost",6379);
// 获取连接
Jedis jedis = jedisPool.getResource();
// 使用
// 关闭,归还连接到连接池
jedis.close();
}
}
一般我们可以各种配置的代码抽取出来写一个工具类,下面是一个基于单例模式的JedisPoolUtils: 首先我们需要一个redis.properties的配置文件,用于配置JedisPool的一些属性:
redis.host=192.168.92.128
redis.port=6379
redis.maxTotal=50
redis.maxIdle=10
JedisPoolUtils.java
public class JedisPoolUtils {
private static volatile JedisPool jedisPool=null;
public JedisPoolUtils(){}
public static JedisPool getJedisPool(){
if(null==jedisPool){
synchronized (JedisPoolUtils.class){
if(null==jedisPool){
try {
//设置各种属性
JedisPoolConfig poolConfig = new JedisPoolConfig();
ResourceBundle rs = ResourceBundle.getBundle("redis");
poolConfig.setMaxIdle(Integer.parseInt(rs.getString("redis.maxIdle")));
poolConfig.setMaxTotal(Integer.parseInt(rs.getString("redis.maxTotal")));
jedisPool = new JedisPool(poolConfig, rs.getString("redis.host"),Integer.parseInt(rs.getString("redis.port")));
}catch (Exception e){
e.printStackTrace();
}
}
}
}
return jedisPool;
}
}
然后测试一下我们的JedisPoolUtils工具类:
public class JedisPoolTest {
public static void main(String[] args){
JedisPool jedisPool = JedisPoolUtils.getJedisPool();
Jedis jedis = jedisPool.getResource();
jedis.set("msg","jedisPopl test success!");
System.out.println(jedis.get("msg"));
jedis.close(); //把jedis放回池子
}
}
测试结果:

SpringBoot连接Redis
导入redis的相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在application.yml中配置redis的有关连接信息
spring:
#Redis的有关配置
redis:
host: 192.168.92.128
port: 6379
password:
#在SpirngBoot1.x中配置Jedis作为连接Redis的客户端
jedis:
pool:
max-wait: 200
max-idle: 10
max-active: 10
timeout: 2000
---
spring:
redis:
host: 192.168.92.145
port: 6379
#password: CySynWsw5NLzbzHne3XcXaRGPWKpJxSKsHikLYvQ6VI0dNKjScuyYPjt
database: 0
timeout: 10000
#springBoot 2.x应该配置lettuce作为Redis客户端 Spring1.x配置jedis
lettuce:
pool:
max-active: 8
min-idle: 0
max-idle: 8
max-wait: 5000ms
shutdown-timeout: 100ms
自定义RedisTemplate
SpringBoot自动帮我们在容器中生成了一个RedisTemplate和一个StringRedisTemplate。我们可以使用RedisTemplate来像Jedis一样操作Redis。但是,这个RedisTemplate的泛型是<Object,Object>,写代码不方便,需要写好多类型转换的代码;我们需要一个泛型为<String,Object>形式的RedisTemplate。并且,这个RedisTemplate没有设置数据存在Redis时,key及value的序列化方式。

RedisConfig.java
@Configuration
public class RedisConfig {
/***
* 自定义的redisTemplate
* @param factory
* @return
*/
@Bean
public RedisTemplate<Object, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<Object, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//默认的序列化方式:jackson
redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);
redisTemplate.setKeySerializer(RedisSerializer.string());
//设置了默认的序列化就不需要在设置下面的了,因为afterPropertiesSet()中会设置为默认的序列化方式
//redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
//redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer);
//redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}