前言:在 JavaWeb 中实现对 Redis 的操作,主要有两种方式:Jedis和RedisTemplate。Jedis是Redis官方推荐的面向Java操作Redis的客户端开发Jar包;而RedisTemplate是Spring框架对Jedis API的进行了高度封装,支持连接池自动管理,我们可以在Spring应用中通过简单的连接池配置信息就能访问Redis服务并进行相关缓存操作。也就是Spring的spring-data-redis的相关Jar包,它还默认提供了两个使用Redis的类StringRedisTemplate和RedisTemplate,其中RedisTemplate可以支持Redis没有的缓存对象的操作,而StringRedisTemplate用来存储字符串。
一、Jedis的概述
1、什么是Jedis?
Jedis 是 Redis 官方推荐的Java客户端开发包,集成了Redis的命令操作,提供了连接池管理。通过Jedis我们可以实现连接Redis,以及操作 Redis 。简而言之,Jedis是Redis的Java连接开发工具。
redis-cli.exe是Redis官方提供的客户端,可以看作一个shell程序,它可以发送命令对redis进行操作;对于Jedis而言,同理是使用Java语言操作Redis,相当于Mysql驱动。Jedis提供了很多接口供Java语言调用。可以在Redis官网下载,当然还有一些开源爱好者提供的客户端,如Jredis、SRP等等。
Jedis常用API:
方法 | 解释 |
---|---|
new Jedis(host, port) | 创建jedis对象,参数host是redis服务器地址,参数port是redis服务端口 |
set(key,value) | 设置字符串类型的数据 |
get(key) | 获得字符串类型的数据 |
hset(key,field,value) | 设置哈希类型的数据 |
hget(key,field) | 获得哈希类型的数据 |
lpush(key,values) | 设置列表类型的数据 |
lpop(key) | 列表左面弹栈 |
rpop(key) | 列表右面弹栈 |
del(key) | 删除指定的key |
2、Jedis 的使用
就像在学习 springmvc 框架之前学习 servlet 一样,了解 Jedis 的使用,看一下单机中 Jedis 的使用,
(1)Maven导入相关jar包
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
(2)Jedis直连使用
Jedis直连,本质是定义一个tcp连接,然后使用socket技术进行通信。每次操作新创建一个Jedis对象,执行完毕后关闭连接释放对象,对应的就是一次tcp连接。
package test;
import com.hs.springbootdemo.SpringbootdemoApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import redis.clients.jedis.Jedis;
@RunWith(SpringRunner.class)//SpringBoot 2.X 默认使用Junit4
@SpringBootTest(classes = SpringbootdemoApplication.class)
public class JedisTest
{
@Test
public void testJedis()
{
//1、生成一个jedis对象,这个对象负责和指定Redis节点进行通信
Jedis jedis = new Jedis("localhost", 6379);
//带密码需要执行认证方法,这里我的Redis没有设密码就不用管
//jedis.auth("123456");
//2、jedis存入数据
jedis.set("hello", "world");
//3、jedis获取数据
String value = jedis.get("hello");
Logger logger = LoggerFactory.getLogger(JedisTest.class);
logger.warn("从Redis中存取数据:"+value);
}
}
测试结果:直接使用Jedis很简单
Redis 中最主要的就是读写数据。Redis 只能支持六种数据类型(string/hash/list/set/zset/hyperloglog)的操作,但在 Java 中我们却通常以类对象为主,所以在需要 Redis 存储的五中数据类型与 Java 对象之间进行转换,如果自己编写一些工具类,比如一个角色对象的转换,还是比较容易的,但是涉及到许多对象的时候,这其中无论工作量还是工作难度都是很大的,所以总体来说,就操作对象而言,使用 Redis 还是挺难的,好在 Spring 框架对这些进行了封装和支持。
上面说到了 Jedis 无法操作对象的问题,无法在那些基础类型和 Java 对象之间方便的转换,但是在 Spring 应用中,这些问题都可以通过使用RedisTemplate得到解决!
(3)Jedis直连的缺陷
首先我们如果每次使用缓存都生成一个 Jedis 对象的话,这样意味着会建立很多 socket 连接,造成系统资源浪费;同时Jedis
在实现上是直连redis server
,多线程环境下非线程安全,除非使用连接池,为每个Jedis实例增加物理连接。
因此,为了避免这些问题,引入了池的概念 JedisPool。JedisPool 是一个线程安全的网络连接池,预先生成一批jedis连接对象放入连接池中,当需要对redis进行操作时从连接池中借用jedis对象,操作完成后归还。这样jedis对象可以重复使用,避免了频繁创建socket连接,节省了连接开销。所以就可以有效的解决以上问题以实现系统的高性能。
我们可以把JedisPool理解成项目中的数据库连接池,例如:阿里巴巴的 druid~
3、JedisPool连接池使用
Jedis连接资源的创建与销毁是很消耗程序性能,所以Jedis为我们提供了Jedis的池化技术。
JedisPool在创建时初始化一些连接资源存储到连接池中,使用Jedis连接资源时不需要创建,而是从连接池中获取一个资源进行Redis的操作,使用完毕后,不需要销毁该Jedis连接资源,而是将该资源归还给连接池,供其他请求使用。这样jedis对象可以重复使用,避免了频繁创建socket连接,节省了连接开销。
Jedis直连和使用连接池JedisPool的对比:
优点 | 缺点 | |
---|---|---|
直连 | 简单方便适用于少量长期连接的场景 | 存在每次新建/关闭TCP开销,资源无法控制,存在连接泄露的可能,Jedis对象线程不安全 |
连接池 | Jedis预先生成,降低开销,连接池的形式保护和控制资源的使用 | 相对于直连,使用相对麻烦,尤其在资源管理上需要很多参数来保证,一旦规划不合理也会出现问题。 |
(1)JedisPool简单使用
这里只是对连接池进行一个简单使用,实际开发通常会对JedisPool进行封装,进行一些参数配置和方法定义等
package test;
import com.hs.springbootdemo.SpringbootdemoApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
*JedisPool类:因为连接池中会有很多jedis实例,RedisPool对象会很大,所以我们需要把他写成单例模式,
* 如果是交由Spring管理就不用了,因为Spring管理的Bean默认是单例的。
*/
@RunWith(SpringRunner.class)//SpringBoot 2.X 默认使用Junit4
@SpringBootTest(classes = SpringbootdemoApplication.class)
public class JedisPoolTest
{
private Logger logger = LoggerFactory.getLogger(JedisTest.class);//logback日志工具
@Test
public void testJedisPool()
{
//1、获得连接池配置对象,设置配置项
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(30); // 最大连接数
config.setMaxIdle(10); // 最大空闲连接数
config.setMaxWaitMillis(10*1000); // 最大等待时间
//2、初始化Jedis连接池,通常来讲JedisPool应该是单例的
JedisPool jedisPool = new JedisPool(config,"localhost",6379);
Jedis jedis=null;
try {
//从连接池获取jedis核心对象
jedis = jedisPool.getResource();
//设置数据
jedis.set("name","hs");
//获得数据
String str = jedis.get("name");
logger.warn("RedisPool使用:"+str);
}
catch (Exception e) {
e.printStackTrace();
}
finally {
if(jedis!=null){
jedis.close();//关闭连接,释放资源
}
}
//虚拟机关闭时,释放pool资源
if(jedisPool!=null){
jedisPool.close();
}
}
}
测试结果:
(2)JedisPool 属性配置(JedisPoolConfig)
对于企业级开发来说,连接池的合理使用是非常重要的,如果设置不当会引起很多不必要的麻烦,容易造成线上的故障。
为了方便使用,Jedis提供了JedisPoolConfig
,它本身继承了GenericObjectPoolConfig
设置了一些空闲监测设置
JedisPool的配置参数大部分是由JedisPoolConfig的对应项来赋值的。
具体如何正确设置,可以置参考博客:Jedis连接池的使用及配置优化
(3)JedisPool封装工具类例子
JedisPoolUtil类:封装Redis连接池配置JedisPoolConfig信息,通过单例RedisPool获取redis对象
因为连接池中会有很多jedis
实例,RedisPool对象会很大,所以我们需要把他写成单例模式,如果是交由Spring管理就不用了,因为Spring管理的Bean默认是单例的。
a、JedisPoolUtil类
package test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* Redis 连接池工具包:封装Jedis连接池配置JedisPoolConfig信息,通过单例RedisPool获取redis对象
*/
public class JedisPoolUtil
{
//下面的这些配置属性可以根据需要修改。其实一般通过读取jedis.properties配置文件指定比较方便,这里只是作封装例子
private static final String HOST = "132.232.6.208";
private static final int PORT = 6379;
private static final int MAX_TOTAL = 100;
private static final int MAX_IDEL = 100;
private static final int MAX_WAITMILLIS = 10 * 1000;
private static volatile JedisPool jedisPool = null;
private JedisPoolUtil() {
}
/**
* 1、获取RedisPool单例模式的对象:单例模式指的是在应用整个生命周期内只能存在一个实例
*
* @return RedisPool实例(单例)
*/
public static JedisPool getJedisPoolInstance()
{
if (jedisPool == null)
{
synchronized (JedisPoolUtil.class)
{
if (jedisPool == null) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(MAX_TOTAL); // 最大连接数
poolConfig.setMaxIdle(MAX_IDEL); // 最大空闲连接数
poolConfig.setMaxWaitMillis(MAX_WAITMILLIS); // 最大等待时间
poolConfig.setTestOnBorrow(true); // 检查连接可用性, 确保获取的redis实例可用
jedisPool = new JedisPool(poolConfig, HOST, PORT);
}
}
}
return jedisPool;
}
/**
* 2、从连接池中获取一个 Jedis 实例(连接)
*
* @return Jedis 实例
*/
public static Jedis getJedisInstance() {
return getJedisPoolInstance().getResource();
}
/**
* 3、将Jedis对象(连接)归还连接池
*
* @param jedis 连接对象
*/
public static void releaseJeids(Jedis jedis) {
if (jedis != null) {
jedis.close();// jedisPool.returnResourceObject(jedis)已废弃
}
}
}
b、编写测试代码:
package test;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class JedisPoolUtilTest {
JedisPool jedisPool = JedisPoolUtil.getJedisPoolInstance();
// 测试单例
@Test
public void test1() {
JedisPool A = JedisPoolUtil.getJedisPoolInstance();
JedisPool B = JedisPoolUtil.getJedisPoolInstance();
System.out.println(A == B);
}
@Test
public void test2() {
Jedis jedis = null;
try {
jedis = jedisPool.getResource(); // 获取Redis连接对象
// 业务
jedis.set("key1", "value111");
System.out.println(jedis.get("key1"));
} finally {
jedis.close(); // 关闭redis连接
}
}
}
运行结果:获取JedisPool的对象是单例模式的,然后从JedisPool连接池里面取出jedis对象进行缓存存取操作
4、项目使用的JedisUtil工具类
JedisPool一般通过读取jedis.properties配置文件指定JedisPoolConfig信息比较方便,这样需求变动时只需要修改配置文件,而不用修改代码。
(1)在项目Resource目录下新建jedis.properties配置文件如下
jedis.properties配置文件:
#redis服务器地址
host=127.0.0.1
#redis端口
port=6379
#最大连接数
maxtotal=100
#最大空闲连接数
maxidel=32
#最大等待时间10秒
maxwaitmillis=10000
(2)Jedis封装工具类
package test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.ResourceBundle;
/**
* Jedis工具类:从配置文件jedis.properties中读取JedisPoolConfig信息生成JedisPool,然后从连接池获取jedis对象进行缓存存取操作
*/
public final class JedisUtil {
private static String host;
private static int port;
private static int maxtotal;
private static int maxidel;
private static int maxwaitmillis;
private JedisUtil() {}
private static volatile JedisPool jedisPool = null;
/**
* static静态块的代码主要用于类的初始化。在虚拟机加载类的时候就会加载执行,而且只执行一次,会在项目main函数之前执行。
* 1、读取jedis.properties配置文件
*/
static{
ResourceBundle rb = ResourceBundle.getBundle("jedis");
host = rb.getString("host");
port = Integer.parseInt(rb.getString("port"));
maxtotal = Integer.parseInt(rb.getString("maxtotal"));
maxidel = Integer.parseInt(rb.getString("maxidel"));
maxwaitmillis = Integer.parseInt(rb.getString("maxwaitmillis"));
}
/**
* 一个类中可以定义多个静态代码块,按顺序执行
* 2、创建单例连接池
*/
static{
if (jedisPool == null)
{
synchronized (JedisPoolUtil.class)
{
if (jedisPool == null) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(maxtotal); // 最大连接数
poolConfig.setMaxIdle(maxidel); // 最大空闲连接数
poolConfig.setMaxWaitMillis(maxwaitmillis); // 最大等待时间
poolConfig.setTestOnBorrow(true); // 检查连接可用性, 确保获取的redis实例可用
jedisPool = new JedisPool(poolConfig,host,port);
}
}
}
}
/**
* 3、获取jedis
*/
public static Jedis getJedis(){
return jedisPool.getResource();
}
/**
** 4、将Jedis对象(连接)归还连接池
*/
public static void close(Jedis jedis){
if(jedis!=null){
jedis.close();
}
}
}
(3)JedisUtil测试代码
package test;
import org.junit.Test;
import redis.clients.jedis.Jedis;
public class JedisUtilTest
{
@Test
public void test()
{
Jedis jedis = null;
try {
jedis = JedisUtil.getJedis(); // 获取Redis连接对象
// 业务
jedis.set("key1", "value111");
System.out.println(jedis.get("key1"));
} finally {
JedisUtil.close(jedis); // 归还redis连接给JedisPool
}
}
}
测试结果:
二、Redis、Jedis和Spring Data Redis的区别
1、Redis
redis是一款开源的高性能Key-Value数据库,运行在内存中,由ANSI C编写。企业开发通常采用Redis来实现缓存。同类的产品还有memcache 、memcached 、MongoDB等。
2、Jedis
Jedis是Redis官方推出的一款面向Java的客户端,提供了很多接口供Java语言调用。可以在Redis官网下载,当然还有一些开源爱好者提供的客户端,如Jredis、SRP等等。
3、Spring Data Redis
Spring-data-redis是Spring大家族的一部分,提供了在srping应用中通过简单的连接池配置访问redis服务,对Reids底层开发包(Jedis, JRedis, and RJC)进行了高度封装。其中的RedisTemplate提供了redis各种操作、异常处理及序列化,支持发布订阅,并对spring 3.1 cache进行了实现。而且RedisTemplate还支持对象缓存操作。
spring-data-redis针对Jedis 提供了如下功能:
a.连接池自动管理,提供了一个高度封装的“RedisTemplate”类
b.针对Jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口
-
ValueOperations:简单K-V操作
-
SetOperations:set类型数据操作
-
ZSetOperations:zset类型数据操作
-
HashOperations:针对map类型的数据操作
-
ListOperations:针对list类型的数据操作
三、SpringBoot中使用RedisTemplate
第一部分我们对Jedis的使用进行了介绍,而且封装了一个JedisPoolUtil工具类。可是这样还是很麻烦,难道每次我们使用的时候都得复制这个JedisUtill 连接池配置工具类到新项目中?而且Jedis还不支持缓存对象的操作。程序员从来不需要重复遭轮子,Spring框架已经帮我们封装好了这一切!
Spring框架中的spring-data-redis模块对Jedis API的进行了高度封装,提供了在Spring应用中通过简单的连接池信息配置就可以访问Redis服务并进行相关缓存操作。SpringDataRedis相对于Jedis来说可以方便地更换Redis的Java客户端如多线程安全的Lettuce,比Jedis多了自动管理连接池的特性,不需要我们自己的JediPoolUtil封装工具类。
它还默认提供了两个使用Redis的类StringRedisTemplate和RedisTemplate,其中RedisTemplate可以支持Redis没有的缓存对象的操作,而StringRedisTemplate用来存储字符串。(其实它们都是RedisTemplate<K, V>泛型接口的实现类,我们可以自定义模板然后@AutoWired注入IOC容器中使用)
1、pom.xml添加redis的起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、通过自带的StringRedisTemplate类存储对象到Redis中
package test;
import com.hs.springbootdemo.SpringbootdemoApplication;
import com.hs.springbootdemo.dao.entity.UserEntity;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@RunWith(SpringRunner.class)//SpringBoot 2.X 默认使用Junit4
@SpringBootTest(classes = SpringbootdemoApplication.class)
public class RedisTest {
//如果无法注入RedisTemplate,就使用@Resource试试
@Autowired
private StringRedisTemplate stringRedisTemplate;//自带的字符串模板类,用于存储字符串
@Autowired
private RedisTemplate redisTemplate;//自带的对象模板类,用于存储对象
@Test
public void test() throws Exception
{
// 保存字符串
stringRedisTemplate.opsForValue().set("username", "redis!!!");
Logger logger = LoggerFactory.getLogger(RedisTest.class);
String str = stringRedisTemplate.opsForValue().get("username");
logger.warn(str);
}
@Test
public void test1() throws Exception {
UserEntity user = new UserEntity();
user.setUsername("hello");
user.setPassword("12345");
redisTemplate.opsForValue().set("user_1", user);
UserEntity user1 = (UserEntity) redisTemplate.opsForValue().get("user_1");
System.out.println(user1.getUsername());
}
}
SpringBoot 2.0已经使用Lettuce代替Jedis
其实,随着Spring Boot2.x
的到来,支持的组件越来越丰富,也越来越成熟,其中对Redis
的支持不仅仅是丰富了它的API,更是替换掉底层Jedis
的依赖,取而代之换成了Lettuce
高级Redis客户端,用于多线程安全同步,异步和响应使用。
Lettuce
和Jedis
的都是连接Redis Server
的客户端程序。Jedis
在实现上是直连redis server
,多线程环境下非线程安全,除非使用连接池JedisPool,为每个Jedis实例增加物理连接。Lettuce
基于Netty的连接实例(StatefulRedisConnection),可以在多个线程间并发访问,且线程安全,满足多线程环境下的并发访问,同时它是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。
为了多线程安全,以前是Jedis+JedisPool组合 ,现在在SpringBoot 2.0应用中直接使用
Lettuce客户端的API封装
RedisTemplate即可,
只要配置好连接池属性,那么SpringBoot就能自动管理连接池。