Redis从入门到实战:实战篇

前言

之前只是在项目简单使用了Redis(只是充当缓存层实现),对Redis的体系技术没深入了解,最近时间比较充裕,所以再次复习巩固Redis,然后打算写几篇博客记录以及分享所复习的Redis知识。

  1. Redis从入门到实战:入门篇
  2. Redis从入门到实战:实战篇
  3. Redis从入门到实战:进阶篇
  4. Redis从入门到实战:完结篇
Redis从入门到实战:实战篇
  1. Redis Java客户端介绍
  2. Jedis操作Redis
  3. SpringData-Redis操作Redis
  4. SpringBoot操作Redis

Redis Java客户端介绍

Redis Java客户端主要有三种:Jedis、Lettuce、Redisson。

Jedis
  1. Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持。
  2. Jedis中的方法调用是比较底层的暴露的Redis的API,也即Jedis中的Java方法基本和Redis的API保持着一致,了解Redis的API,也就能熟练的使用Jedis。
  3. Jedis使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis。
  4. Jedis仅支持基本的数据类型如:String、Hash、List、Set、Sorted Set。
Lettuce
  1. Lettuce是高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。目前springboot默认使用的客户端。
  2. Lettuce基于Netty框架的事件驱动的通信层,其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作。
Redisson
  1. Redisson实现了分布式和可扩展的Java数据结构,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过Redis支持延迟队列。和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
  2. Redisson中的方法则是进行比较高的抽象,每个方法调用可能进行了一个或多个Redis方法调用。
  3. Redisson基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作。
  4. Redisson不仅提供了一系列的分布式Java常用对象,基本可以与Java的基本数据结构通用,还提供了许多分布式服务,其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service)。

Jedis操作Redis

上面介绍了三种Redis Java客户端,接下来就使用Jedis操作Redis,首先引入Jedis依赖:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.1.0</version>
</dependency>

在使用Jedis的时候,有两种方式可选:

  1. 单机版Jedis,对应redis.clients.jedis.Jedis类。
  2. Jedis连接池,对应redis.clients.jedis.JedisPoolConfig类。

单机版Jedis

单机版的Jedis就是一旦与Redis建立连接成功后,然后进行一系列对Redis的操作之后,就要关闭Redis连接对象,否则会占用系统资源。

@Test
public void jedis(){
    Jedis jedis = null;
    try {
        jedis = new Jedis("localhost",6379);//设置地址和端口
//            jedis.auth(PASSWORD);//如果redis服务器配置了需要密码,此处必须设置
        jedis.select(5);//选择指定数据库
        //存储集合到redis,并取出
        jedis.lpush("names","xxxq","xxq","xq");
        System.out.println(jedis.lrange("names", 0, -1));
    }finally {
        jedis.close();
    }
}

Jedis连接池

以前我们使用MySQL、Oracle等关系型数据库的时候,都会使用数据库连接池,比如:C3P0、Druid等等。在Jedis中,也提供了一个Redis连接池来管理Redis连接对象,就是redis.clients.jedis.JedisPool,还有一个对Jedis连接池配置的类:redis.clients.jedis.JedisPoolConfig,下面我们对它进行配置。

  1. 配置连接池需要一些参数,把这些参数单独放在一个配置文件中:redis.properties
    redis.host = xxx
    redis.port = 6379
    redis.password = xxx
    redis.timeout = 5000
    redis.maxTotal = 100
    redis.maxIdle = 20
    redis.minIdle = 5
    
  2. 下面封装一个Jedis连接池工具类,简化后续的开发工作。
    public class JedisPoolUtils {
        private static JedisPool  pool = null;
    
        /**
         *  读取属性配置文件,配置Jedis连接池
         */
        static {
            Properties properties = new Properties();
            try {
                properties.load(JedisPoolUtils.class.getClassLoader().getResourceAsStream("redis.properties"));
            } catch (IOException e) {
                e.printStackTrace();
            }
            JedisPoolConfig poolConfig = new JedisPoolConfig();
            int maxIdle = new Integer(properties.getProperty("redis.maxIdle"));
            int minIdle = new Integer(properties.getProperty("redis.minIdle"));
            int maxTotal = new Integer(properties.getProperty("redis.maxTotal"));
            poolConfig.setMaxIdle(maxIdle);
            poolConfig.setMinIdle(minIdle);
            poolConfig.setMaxTotal(maxTotal);
            //设置主机
            String host = properties.getProperty("redis.host");
            //设置端口号
            int port = new Integer(properties.getProperty("redis.port"));
            //创建Jedis连接池
            pool = new JedisPool(poolConfig,host,port);
        }
        
        /**
            获取单机版Jedis
        */
        public static Jedis getJedis(){
            return pool.getResource();
        }
    }
    
  3. 使用Jedis连接池创建单机版Jedis,测试是否配置成功。使用Jedis连接池,我们不需要手动关闭Jedis连接对象。
    public class JedisPoolTest{
        public static void main(String[] args) {
            Jedis jedis = JedisPoolUtils.getJedis();
            System.out.println("Redis服务正在运行:"+jedis.ping());
            //set命令
            jedis.set("name", "zwq");
            System.out.println(jedis.get("name"));
        }
    }
    
    执行结果:
        Redis服务正在运行:PONG
        zwq
    

SpringData-Redis操作Redis

  1. Jedis支持五种数据类型(string/hash/list/set/zset/)的操作,但在Java中我们却通常以类对象为主,所以在实际项目中,我们需要将Redis存储的五种数据类型与Java对象之间进行切换,我们可以自己编写一些工具类,实现两者之间的转换,但是涉及到许多对象的时候,这其中无论工作量还是工作难度都是很大的,所以总体来说,就操作对象而言,使用原生的Jedis还是挺难的,好在Spring对这些进行了封装和支持,也就是SpringData-Redis
  2. 引入SpringData-Redis依赖
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-redis</artifactId>
        <version>2.2.4.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.1.0</version>
    </dependency>
    
  3. 接下来我们使用Spring配置Jedis连接池。
    #redis.properties配置文件
    redis.host = xxx
    redis.port = 6379
    redis.password = xxx
    redis.timeout = 5000
    redis.maxTotal = 100
    redis.maxIdle = 20
    redis.minIdle = 5
    
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p" 
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
    
        <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
            <!--Redis服务地址-->
            <property name="hostName" value="${redis.host}"/>
            <!--端口号-->
            <property name="port" value="${redis.port}"/>
            <!--如果有密码则需要配置密码-->
            <!--<property name="password" value="${redis.password}"/>-->
            <!--连接池配置-->
            <property name="poolConfig" ref="poolConfig"/>
        </bean>
    
        <bean id="redisTemplate"
              class="org.springframework.data.redis.core.RedisTemplate"
              p:connection-factory-ref="connectionFactory"/>
    
        <!--加载属性文件-->
        <context:property-placeholder location="classpath:redis.properties"/>
    
        <!--Spring整合配置,连接池配置-->
        <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
            <!--最大连接数-->
            <property name="maxTotal" value="${redis.maxTotal}"/>
            <!--最大空闲数-->
            <property name="maxIdle" value="${redis.maxIdle}"/>
            <!--最大等待时间-->
            <property name="maxWaitMillis" value="20000"/>
            <!--最小空闲数-->
            <property name="minIdle" value="${redis.minIdle}"/>
        </bean>
    </beans>
    
  4. 上面配置了RedisTemplate,因为普通的Redis连接对象没有办法直接将Java对象直接存入Redis内存中,我们需要替代的方案:将对象序列化(可以简单的理解为实现Serializable接口)。我们可以把对象序列化之后存入Redis缓存中,然后在取出的时候又通过转换器,将序列化之后的对象反序列化回对象,这样就完成了我们的要求。RedisTemplate可以帮助我们完成这份工作,它会找到对应的序列化器去转换Redis的键值。
  5. 创建用户实体类,实现Serializable接口。
    @Data
    public class User implements Serializable {
        private String name;
        private Integer age;
    }
    
  6. 测试Spring配置是否成功。
    @Test
    public void springDataRedis(){
        ApplicationContext context = new ClassPathXmlApplicationContext("redis.xml");
        RedisTemplate template = context.getBean(RedisTemplate.class);
        User user = new User();
        user.setName("zhangsan");
        user.setAge(22);
        template.opsForValue().set("zhangsan",user);
        User u = (User) template.opsForValue().get("zhangsan");
        System.out.println(u);
    }
    运行结果:
        User(name=zhangsan, age=22)
    
  7. 从运行结果来看,SpringData-Redis已经自动帮我们序列化对象与反序列化对象了,使用起来非常简便。但是当我们查看Redis服务器的时候,发现存入的数据乱码了,如下图所示:


    在这里插入图片描述
  8. 上面说了RedisTemplate通过序列化器来转换Redis的键值,而RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此序列化器完成的。出现上面问题说明就是JDK序列化器在序列化与反序列化时编码问题,我们可以更改RedisTemplate序列化器,除了JDK序列化器JdkSerializationRedisSerializer,还有一个字符串序列化器StringRedisSerializer,因为Redis的key都是字符串类型,所以我们将key的序列化器改为StringRedisSerializer,而Value使用默认的JdkSerializationRedisSerializer。
  9. 接下来按照上面总结的原因来更改上面配置文件:redis.xml
    <bean id="redisTemplate"
                  class="org.springframework.data.redis.core.RedisTemplate"
                  p:connection-factory-ref="connectionFactory"
                p:keySerializer-ref="stringSerializer"
                p:valueSerializer-ref="jdkSerializer"/>
        
    <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" id="stringSerializer"/>
    <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" id="jdkSerializer"/>
    
    在这里插入图片描述

    从上图来看,修改序列化器之后,key已经不再乱码了,但是值又乱码了。按照上面的分析可以将value的序列化器改为StringRedisSerializer就不会乱码了。

SpringBoot操作Redis

上面我们已经介绍了原生Jedis操作Redis与SpringData-Redis操作Redis,虽然没有讲大量的API,但是我们已经学会怎么使用了,剩下的就是大家自己来操作API实现效果了。接下来再介绍在SpringBoot中如何操作Redis。不管是SpringData-Redis,还是在SpringBoot中,都是通过RedisTemplate操作Redis,只不过SpringBoot做了更多的封装,让我们操作Redis更加简便。接下来先介绍RedisTemplate。

RedisTemplate

  1. RedisTemplate只是提供操作Redis的模板API,底层还是使用Redis Java客户端来实现。


    在这里插入图片描述
  2. 这里说一个细节,SpringBoot2.0之前默认是使用Jedis客户端底层实现,SpringBoot2.0之后就改为使用Lettuce客户端底层实现,如果需要在SpringBoot2.0之后使用Jedis,则需要排除Lettuce依赖,如下所示:
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.1.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        <exclusions>
            <exclusion>
                <groupId>io.lettuce</groupId>
                <artifactId>lettuce-core</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
  3. 接下来配置Redis连接池,SpringBoot省去了繁杂的XML配置,直接在application.properties配置即可。
    #redis配置
    #Redis服务器地址
    spring.redis.host=127.0.0.1
    #Redis服务器连接端口
    spring.redis.port=6379
    # Redis服务器连接密码(默认为空)
    spring.redis.password=
    #Redis数据库索引(默认为0)
    spring.redis.database=0  
    #连接池最大连接数(使用负值表示没有限制)
    spring.redis.jedis.pool.max-active=50
    #连接池最大阻塞等待时间(使用负值表示没有限制)
    spring.redis.jedis.pool.max-wait=3000ms
    #连接池中的最大空闲连接
    spring.redis.jedis.pool.max-idle=20
    #连接池中的最小空闲连接
    spring.redis.jedis.pool.min-idle=2
    #连接超时时间(毫秒)
    spring.redis.timeout=5000ms
    
  4. 编写测试代码,测试配置是否成功。
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class AppTest {
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @Test
        public void test(){
            stringRedisTemplate.opsForValue().set("name","zwq");
            System.out.println(stringRedisTemplate.opsForValue().get("name"));
        }
    }
    运行结果:
        zwq
    
  5. 上面使用的是StringRedisTemplate,它继承了RedisTemplate,从名字看它就是支持Value为String类型的读写操作,也就是Value使用了StringRedisSerializer序列化器,截图验证:


    在这里插入图片描述

    在这里插入图片描述
  6. 我们验证读写对象会不会出现之前使用SpriingData-Redis的乱码问题,编写一个用户实体类
    @Data
    public class User implements Serializable {
        private String name;
        private Integer age;
    }
    
  7. 上面都已经配置好了,所以直接编写测试代码即可。
    @Test
    public void saveUser(){
        User user = new User();
        user.setName("zhangsan");
        user.setAge(22);
        redisTemplate.opsForValue().set("zhangsan",user);
        User zhangsan = (User) redisTemplate.opsForValue().get("zhangsan");
        System.out.println(zhangsan);
    }
    运行结果:
        User(name=zhangsan, age=22)
    
  8. 在程序代码中读写也是没问题,但是写入到Redis服务器上就乱码了。


    在这里插入图片描述
  9. 这个原因也是序列化与反序列化过程中产生的问题,接下来开始指定序列化器。在SpringBoot中没有了xml配置,所以需要编写一个配置类来配置序列化器,如下代码:
    @Configuration
    public class RedisCnfig {
    
        @Autowired
        RedisConnectionFactory factory;
    
        @Bean
        public RedisTemplate<Object, Object> redisTemplate() {
            RedisTemplate redisTemplate = new RedisTemplate();
            redisTemplate.setConnectionFactory(factory);
            //StringRedisSerializer序列化器
            StringRedisSerializer stringRedisSerializer =new StringRedisSerializer();
            //JdkSerializationRedisSerializer序列化器
            JdkSerializationRedisSerializer jdkRedisSerializer = new JdkSerializationRedisSerializer();
            redisTemplate.setValueSerializer(jdkRedisSerializer);
            redisTemplate.setKeySerializer(stringRedisSerializer);
            redisTemplate.afterPropertiesSet();
            return redisTemplate;
        }
    }
    
  10. 再执行上面测试代码,发现key已经不乱码了,而Value还是出现乱码。


    在这里插入图片描述
  11. 通过把Value的序列化器修改为JSON序列化器,也就是对象与JSON之间的转换,这样就可以解决乱码的问题。
    @Configuration
    public class RedisCnfig {
    
        @Autowired
        RedisConnectionFactory factory;
    
        @Bean
        public RedisTemplate<Object, Object> redisTemplate() {
            RedisTemplate redisTemplate = new RedisTemplate();
            redisTemplate.setConnectionFactory(factory);
            //StringRedisSerializer序列化器
            StringRedisSerializer stringRedisSerializer =new StringRedisSerializer();
            //JdkSerializationRedisSerializer序列化器
            JdkSerializationRedisSerializer jdkRedisSerializer = new JdkSerializationRedisSerializer();
            //Json序列化器
            GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
    //        redisTemplate.setValueSerializer(jdkRedisSerializer);
            redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
            redisTemplate.setKeySerializer(stringRedisSerializer);
            redisTemplate.afterPropertiesSet();
            return redisTemplate;
        }
    }
    
    除了改配置之外,还需要引入下面依赖。
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.5.4</version>
    </dependency>
    
  12. 再执行上面的测试代码,发现Key、Value都不会乱码了。


    在这里插入图片描述

总结

  1. 这样就把Java操作Redis的技术讲完了,还是和上面说了,这里讲的API比较少,我们还是得自己敲,把API练熟悉了,才能把Redis运用自如,虽然我也还没把Redis运用自如。
  2. 大家觉得OK的话,不妨来个👍或者关注也行。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342