Jedis和RedisTemplate

前言:RedisTemplate是对jedis和lettuce的封装,springboot2.0之后,默认使用 lettuce。jedis性能较强,jedis的性能至少是RedisTemplate的3倍以上,jedis结合jedisPool使用既可以有高性能又可以保证redis的连接可控。在性能要求、并发操作不高的场景建议使用RedisTemplate,在并发高,性能要求高的场景下建议使用jedis。


一、Jedis的概述

1.1、什么是Jedis?

Jedis 是 Redis 官方推荐的Java客户端开发包,集成了Redis的命令操作,提供了连接池管理。通过Jedis我们可以实现连接Redis,以及操作 Redis 。简而言之,Jedis是Redis的Java连接开发工具。

1.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

1.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对象进行缓存存取操作

1.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的区别

2.1、Redis

redis是一款开源的高性能Key-Value数据库,运行在内存中,由ANSI C编写。企业开发通常采用Redis来实现缓存。同类的产品还有memcache 、memcached 、MongoDB等。

2.2、Jedis

Jedis是Redis官方推出的一款面向Java的客户端,提供了很多接口供Java语言调用。可以在Redis官网下载,当然还有一些开源爱好者提供的客户端,如Jredis、SRP等等。

2.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容器中使用)

3.1、pom.xml添加redis的起步依赖

   <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

3.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());
    }
}

3.3、SpringBoot 2.0已经使用Lettuce代替Jedis

其实,随着Spring Boot2.x的到来,支持的组件越来越丰富,也越来越成熟,其中对Redis的支持不仅仅是丰富了它的API,更是替换掉底层Jedis的依赖,取而代之换成了**Lettuce高级Redis客户端**,用于多线程安全同步,异步和响应使用。

LettuceJedis的都是连接Redis Server的客户端程序。Jedis在实现上是直连redis server,多线程环境下非线程安全,除非使用连接池JedisPool,为每个Jedis实例增加物理连接。Lettuce基于Netty的连接实例(StatefulRedisConnection),可以在多个线程间并发访问,且线程安全,满足多线程环境下的并发访问,同时它是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

为了多线程安全,以前是Jedis+JedisPool组合 ,现在在SpringBoot 2.0应用中直接使用Lettuce客户端的API封装RedisTemplate即可只要配置好连接池属性,那么SpringBoot就能自动管理连接池。

本文转自 https://blog.csdn.net/CSDN2497242041/article/details/102675435,如有侵权,请联系删除。

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值