首页 > 其他 > 详细

第7章 使用性能利器——Redis

时间:2020-06-01 00:04:37      阅读:105      评论:0      收藏:0      [点我收藏+]

在现今互联网应用中,NoSQL已经广为应用,在互联网中起到加速系统的作用。有两种NoSQL使用最为广泛,那就是Redis和MongoDB。本章将介绍Redis和Spring Boot的结合。Redis是一种运行在内存的数据库,支持7种数据类型的存储。Redis是一个开源、使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、键值数据库,并提供多种语言的API。Redis是基于内存的,所以运行速度很快,大约是关系数据库几倍到几十倍的速度。在我的测试中,Redis可以在1 s内完成10万次的读写,性能十分高效。如果我们将常用的数据存储在Redis中,用来代替关系数据库的查询访问,网站性能将可以得到大幅提高。在现实中,查询数据要远远多于更新数据,一般一个正常的网站查询和更新的比例大约是1∶9到3∶7,在查询比例较大的网站使用Redis可以数倍地提升网站的性能。例如,当一个会员登录网站,我们就把其常用数据从数据库一次性查询出来存放在Redis中,那么之后大部分的查询只需要基于Redis完成便可以了,这样将很大程度上提升网站的性能。除此之外,Redis还提供了简单的事务机制,通过事务机制可以有效保证在高并发的场景下数据的一致性。Redis自身数据类型比较少,命令功能也比较有限,运算能力一直不强,所以Redis在2.6版本之后开始版本之后开始增加Lua语言的支持,这样Redis的运算能力就大大提高了,而且在Redis中Lua语言的执行是原子性的,也就是在Redis执行Lua时,不会被其他命令所打断,这样就能够保证在高并发场景下的一致性,在未来高并发的场景我们会再次看到它的威力。要使用Redis,需要先加入关于Redis的依赖,同样,Spring Boot也会为其提供stater,然后允许我们通过配置文件application.properties进行配置,这样就能够以最快的速度配置并且使用Redis了。所以下面先在Maven中增加依赖,如代码清单7-1所示。

代码清单7-1 引入spring-boot-starter-data-redis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <!--不依赖Redis的异步客户端lettuce-->
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--引入Redis的客户端驱动jedis-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

这样我们就引入了Spring对Redis的starter,只是在默认的情况下,spring-boot-starter-data-redis(版本2.x)会依赖Lettuce的Redis客户端驱动,而在一般的项目中,我们会使用Jedis,所以在代码中使用了<exclusions>元素将其依赖排除了,与此同时引入了Jedis的依赖,这样就可以使用Jedis进行编程了。那么应该如何配置它呢?关于这些,需要进一步了解Spring是如何集成Redis的。Spring是通过spring-data-redis项目对Redis开发进行支持的,在讨论SpringBoot如何使用Redis之前,很有必要简单地介绍一下这个项目的,这样才能更好地在Spring中使用Redis。

Redis是一种键值数据库,而且是以字符串类型为中心的,当前它能够支持多种数据类型,包括字符串、散列、列表(链表)、集合、有序集合、基数和地理位置等。我们将讨论字符串、散列、列表、集合和有序集合的使用,因为它们的使用率比较高。

7.1 spring-data-redis项目简介

这里我们先讨论在Spring中如何使用Redis,学习一些底层的内容还是很有帮助的。因为SpringBoot的配置虽然已经简化,但是如果弄不懂一些内容,读者很快就会备感迷茫,很多特性也将无法清晰地去讨论,所以这里先探讨在一个普通的Spring工程中如何使用Redis。这对于讨论Spring Boot中如何集成Redis是很有帮助的。

7.1.1 spring-data-redis项目的设计

在Java中与Redis连接的驱动存在很多种,目前比较广泛使用的是Jedis,其他的还有Lettuce、Jredis和Srp。Lettuce目前使用得比较少,而Jredis和Srp则已经不被推荐使用,在Spring中已经被标注@Deprecated,所以本书只讨论Spring推荐使用的类库Jedis的使用。Spring提供了一个RedisConnectionFactory接口,通过它可以生成一个RedisConnection接口对象,而RedisConnection接口对象是对Redis底层接口的封装。例如,本章使用的Jedis驱动,那么Spring就会提供RedisConnection接口的实现类JedisConnection去封装原有的Jedis(redis.clients.jedis.Jedis)对象。我们不妨看一下它们的类的关系,如图7-1所示。

技术分享图片

从图7-1可以看出,在Spring中是通过RedisConnection接口操作Redis的,而RedisConnection则对原生的Jedis进行封装。要获取RedisConnection接口对象,是通过RedisConnectionFactory接口去生成的,所以第一步要配置的便是这个工厂了,而配置这个工厂主要是配置Redis的连接池,对于连接池可以限定其最大连接数、超时时间等属性。下面开发一个简单的RedisConnectionFactory接口对象,如代码清单7-2所示。

代码清单7-2 创建RedisConnectionFactory对象

 

/**** imports ****/
@Configuration
public class RedisConfig {

    private RedisConnectionFactory connectionFactory = null;

    @Bean(name = "RedisConnectionFactory")
    public RedisConnectionFactory initRedisConnectionFactory() {
        if (this.connectionFactory != null) {
            return this.connectionFactory;
        }
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        // 最大空闲数
        poolConfig.setMaxIdle(30);
        // 最大连接数
        poolConfig.setMaxTotal(50);
        // 最大等待毫秒数
        poolConfig.setMaxWaitMillis(2000);
        // 创建Jedis连接工厂
        JedisConnectionFactory connectionFactory = new JedisConnectionFactory(poolConfig);
        // 获取单机的Redis配置
        RedisStandaloneConfiguration rsCfg = connectionFactory.getStandaloneConfiguration();
        ConnectionFactory.setHostName("192.168.11.131");
        ConnectionFactory.setPort(6379);
        ConnectionFactory.setPassword("123456");
        this.connectionFactory = connectionFactory;
        return connectionFactory;
    }
    ......
}

这里我们通过一个连接池的配置创建了RedisConnectionFactory,通过它就能够创建RedisConnection接口对象。但是我们在使用一条连接时,要先从RedisConnectionFactory工厂获取,然后在使用完成后还要自己关闭它。Spring为了进一步简化开发,提供了RedisTemplate。

7.1.2 RedisTemplate

应该说RedisTemplate是使用得最多的类,所以它也是Spring操作Redis的重点内容。RedisTemplate是一个强大的类,首先它会自动从RedisConnectionFactory工厂中获取连接,然后执行对应的Redis命令,在最后还会关闭Redis的连接。这些在RedisTemplate中都被封装了,所以并不需要开发者关注Redis连接的闭合问题。只是为了更好地使用RedisTemplate,我们还需要掌握它内部的一些细节。不过,无论如何我们需要先创建它,在代码清单7-2的基础上加入代码清单7-3。

代码清单7-3 创建RedisTemplate

@Bean(name="redisTemplate")
public RedisTemplate&lt;Object, Object&gt; initRedisTemplate() {
    RedisTemplate&lt;Object, Object&gt; redisTemplate = new RedisTemplate&lt;&gt;();
    redisTemplate.setConnectionFactory(initConnectionFactory());
    return redisTemplate;
}

然后测试它,如代码清单7-4所示。

代码清单7-4 测试RedisTemplate

package com.springboot.chapter7.main;
/**** imports ****/
public class Chapter7Main {
    public static void main(String[] args) {
        ApplicationContext ctx 
             = new AnnotationConfigApplicationContext(RedisConfig.class);
        RedisTemplate redisTemplate = ctx.getBean(RedisTemplate.class);
        redisTemplate.opsForValue().set("key1", "value1");
        redisTemplate.opsForHash().put("hash", "field", "hvalue");
    }
}

这里使用了Java配置文件RedisConfig来创建Spring IoC容器,然后从中获取RedisTemplate对象,接着设置一个键为"key1"而值为"value1"的键值对。运行这段代码后,可以在Redis客户端输入命令keys *key1,如图7-2所示。

 

技术分享图片

图7-2 使用Redis命令查询键信息

可以看到,Redis存入的并不是"key1"这样的字符串,这是怎么回事呢?首先需要清楚的是,Redis是一种基于字符串存储的NoSQL,而Java是基于对象的语言,对象是无法存储到Redis中的,不过Java提供了序列化机制,只要类实现了java.io.Serializable接口,就代表类的对象能够进行序列化,通过将类对象进行序列化就能够得到二进制字符串,这样Redis就可以将这些类对象以字符串进行存储。Java也可以将那些二进制字符串通过反序列化转为对象,通过这个原理,Spring提供了序列化器的机制,并且实现了几个序列化器,其设计如图7-3所示。

 

 

技术分享图片

 

从图7-3可以看出,对于序列化器,Spring提供了RedisSerializer接口,它有两个方法。这两个方法,一个是serialize,它能把那些可以序列化的对象转换为二进制字符串;另一个是deserialize,它能够通过反序列化把二进制字符串转换为Java对象。图7-3中的JacksonJsonRedisSerializer因为API过时,已经不推荐使用,我们这里主要讨论StringRedisSerializer和JdkSerializationRedisSerializer,其中JdkSerializationRedisSerializer是RedisTemplate默认的序列化器,代码清单的"key1"这个字符串就是被它序列化变为一个比较奇怪的字符串的,其原理如图7-4所示。

技术分享图片

 

图7-4 spring-data-redis序列化器原理示意图

RedisTemplate提供了如表7-1所示几个可以配置的属性。

表7-1 RedisTemplate中的序列化器属性

技术分享图片

通过上述讲解我们可以看到,在代码清单7-4中,由于我们什么都没有配置,因此它会默认使用JdkSerializationRedisSerializer对对象进行序列化和反序列化。这就是图7-2得到那些复杂字符串的原因,只是这样使用会给我们查询Redis数据带来很大的困难。为了克服这个困难,我们希望RedisTemplate可以将Redis的键以普通字符串保存。为了达到这个目的,可以将代码清单7-3修改为代码清单7-5。

代码清单7-5 使用字符串序列化器

@Bean(name = "redisTemplate")
public RedisTemplate&lt;Object, Object&gt; initRedisTemplate() {
    RedisTemplate&lt;Object, Object&gt; redisTemplate = new RedisTemplate&lt;&gt;();
    // RedisTemplate会自动初始化StringRedisSerializer,所以这里直接获取
    RedisSerializer stringRedisSerializer = redisTemplate.getStringSerializer();
    // 设置字符串序列化器,这样Spring就会把Redis的key当作字符串处理了
    redisTemplate.setKeySerializer(stringRedisSerializer);
    redisTemplate.setHashKeySerializer(stringRedisSerializer);
    redisTemplate.setHashValueSerializer(stringRedisSerializer);
    redisTemplate.setConnectionFactory(initConnectionFactory());
    return redisTemplate;
}

这里,我们通过主动将Redis的键和散列结构的field和value均采用了字符串序列化器,这样把它们转换出来时就会采用字符串了。运行代码清单7-4中的代码后,再次查询Redis的数据,就可以看到图7-5了。

技术分享图片

从图7-5可以看到Redis的键已经从复杂的编码变为简单的字符串了,而hash数据类型则全部采用了字符串的形式,这是因为我们设置了使用StringRedisSerializer序列化器操作它们。值得注意的是代码清单7-4中的如下两行代码:

redisTemplate.opsForValue().set("key1", "value1");
redisTemplate.opsForHash().put("hash", "field", "hvalue");

它们还存在一些值得我们探讨的细节。例如,上述的两个操作并不是在同一个Redis的连接下完成的,什么意思?让我们更加详细地阐述代码运行的过程,首先在操作key1时,redisTemplate会先从连接工厂(RedisConnectionFactory)中获取一个连接,然后执行对应的Redis命令,再关闭这条连接;其次在操作hash时,它也是从连接工厂中获取另一条连接,然后执行命令,再关闭该连接。所以我们可以看到这个过程是两条连接的操作,这样显然存在资源的浪费,我们更加希望的是在同一条连接中就执行两个命令。为了克服这个问题,Spring为我们提供了RedisCallback和SessionCallback两个接口。不过在此之前我们需要了解Spring对Redis数据类型的封装。Redis使用得最多的是字符串,因此在spring-data-redis项目中,还提供了一个StringRedisTemplate类,这个类继承RedisTemplate,只是提供了字符串的操作而已,对于复杂Java对象还需要自行处理。

7.1.3 Spring对Redis数据类型操作的封装

Redis能够支持7种类型的数据结构,这7种类型是字符串、散列、列表(链表)、集合、有序集合、基数和地理位置。为此Spring针对每一种数据结构的操作都提供了对应的操作接口,如表7-2所示。

 

技术分享图片

它们都可以通过RedisTemplate得到,得到的方法很简单,如代码清单7-6所示。

代码清单7-6 获取Redis数据类型操作接口

// 获取地理位置操作接口
redisTemplate.opsForGeo();
// 获取散列操作接口
redisTemplate.opsForHash();
// 获取基数操作接口
redisTemplate.opsForHyperLogLog();
// 获取列表操作接口
redisTemplate.opsForList();
// 获取集合操作接口
redisTemplate.opsForSet();
// 获取字符串操作接口
redisTemplate.opsForValue();
// 获取有序集合操作接口
redisTemplate.opsForZSet();

这样就可以通过各类的操作接口来操作不同的数据类型了,当然这需要你熟悉Redis的各种命令。有时我们可能需要对某一个键值对(key-value)做连续的操作,例如,有时需要连续操作一个散列数据类型或者列表多次,这时Spring也提供支持,它提供了对应的BoundXXXOperations接口,如表7-3所示。

技术分享图片

 

同样地,RedisTemplate也对获取它们提供了对应的方法,如代码清单7-7所示。代码清单7-7 获取绑定键的操作类

// 获取地理位置绑定键操作接口
redisTemplate.boundGeoOps("geo");
// 获取散列绑定键操作接口
redisTemplate.boundHashOps("hash");
// 获取列表(链表)绑定键操作接口
redisTemplate.boundListOps("list");
// 获取集合绑定键操作接口
redisTemplate.boundSetOps("set");
// 获取字符串绑定键操作接口
redisTemplate.boundValueOps("string");
// 获取有序集合绑定键操作接口
redisTemplate.boundZSetOps("zset");

获取其中的操作接口后,我们就可以对某个键的数据进行多次操作,这样我们就知道如何有效地通过Spring操作Redis的各种数据类型了。

7.1.4 SessionCallback和RedisCallback接口

在7.1.2节的最后,我们谈到了SessionCallback接口和RedisCallback接口,它们的作用是让RedisTemplate进行回调,通过它们可以在同一条连接下执行多个Redis命令。其中SessionCallback提供了良好的封装,对于开发者比较友好,因此在实际的开发中应该优先选择使用它;相对而言,RedisCallback接口比较底层,需要处理的内容也比较多,可读性较差,所以在非必要的时候尽量不选择使用它。下面使用这两个接口实现代码清单7-4的功能,如代码清单7-8所示。

代码清单7-8 使用RedisCallback和SessionCallback接口

 

// 需要处理底层的转换规则,如果不考虑改写底层,尽量不使用它
public void useRedisCallback(RedisTemplate redisTemplate) {
    redisTemplate.execute(new RedisCallback() {
        @Override
        public Object doInRedis(RedisConnection rc) 
                throws DataAccessException {
            rc.set("key1".getBytes(), "value1".getBytes());
            rc.hSet("hash".getBytes(), "field".getBytes(), "hvalue".getBytes());
            return null;
        }
    });
}

// 高级接口,比较友好,一般情况下,优先使用它
public void useSessionCallback(RedisTemplate redisTemplate) {
    redisTemplate.execute(new SessionCallback() {
        @Override
        public Object execute(RedisOperations ro) 
                throws DataAccessException {
            ro.opsForValue().set("key1", "value1");
            ro.opsForHash().put("hash", "field", "hvalue");
            return null;
        }
    });
}

上述代码中,我们采用了匿名类的方式去使用它们。从代码中可以看出,RedisCallback接口并不是那么友好,但是它能够改写一些底层的东西,如序列化的问题,所以在需要改写那些较为底层规则时,可以使用它。使用SessionCallback接口则比较友好,这也是我在大部分情况下推荐使用的接口,它提供了更为高级的API,使得我们的使用更为简单,可读性也更佳。如果采用的是Java 8或者以上的版本,则还可以使用Lambda表达式改写上述代码,这样代码就会更加清爽。代码清单7-9就是对代码清单7-8的改写。

代码清单7-9 使用Lambda表达式

public void useRedisCallback(RedisTemplate redisTemplate) {
    redisTemplate.execute((RedisConnection rc) -&gt; {
        rc.set("key1".getBytes(), "value1".getBytes());
        rc.hSet("hash".getBytes(), "field".getBytes(), "hvalue".getBytes());
        return null;
    });
}

public void useSessionCallback(RedisTemplate redisTemplate) {
    redisTemplate.execute((RedisOperations ro) -&gt; {
        ro.opsForValue().set("key1", "value1");
        ro.opsForHash().put("hash", "field", "hvalue");
        return null;
    });
}

显然这样就更为清晰明朗了,看起来也更为简单。它们都能够使得RedisTemplate使用同一条Redis连接进行回调,从而可以在同一条Redis连接下执行多个方法,避免RedisTemplate多次获取不同的连接。在后面的流水线和事务内容介绍中我们还会看到它们的身影。

 7.2 在Spring Boot中配置和使用Redis

通过上述spring-data-redis项目的讨论,相信读者对Spring如何集成Redis有了更为深入的理解,虽然在Spring Boot中配置没有那么烦琐,但是了解更多的底层细节能更好地处理问题。本节开始讨论Spring Boot如何整合和使用Redis。

7.2.1 在Spring Boot中配置Redis

在Spring Boot中集成Redis更为简单。例如,要配置代码清单7-2中的Redis服务器,我们只需要在配置文件application.properties中加入代码清单7-10所示的代码即可。

代码清单7-10 在Spring Boot中配置Redis

#配置连接池属性
spring.redis.jedis.pool.min-idle=5
spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.max-wait=2000
#配置Redis服务器属性
spring.redis.port=6379
spring.redis.host=192.168.11.131
spring.redis.password=123456
#Redis连接超时时间,单位毫秒
spring.redis.timeout=1000

这里我们配置了连接池和服务器的属性,用以连接Redis服务器,这样Spring Boot的自动装配机制就会读取这些配置来生成有关Redis的操作对象,这里它会自动生成RedisConnectionFactory、RedisTemplate、StringRedisTemplate等常用的Redis对象。我们知道,RedisTemplate会默认使用JdkSerializationRedisSerializer进行序列化键值,这样便能够存储到Redis服务器中。如果这样,Redis服务器存入的便是一个经过序列化后的特殊字符串,有时候对于我们跟踪并不是很友好。如果我们在Redis只是使用字符串,那么使用其自动生成的StringRedisTemplate即可,但是这样就只能支持字符串了,并不能支持Java对象的存储。为了克服这个问题,可以通过设置RedisTemplate的序列化器来处理。下面我们在Spring Boot的启动文件中修改RedisTemplate的序列化器,如代码清单7-11所示。

代码清单7-11 修改RedisTemplate的序列化器

package com.springboot.chapter7.main;
/**** imports ****/
@SpringBootApplication(scanBasePackages = "com.springboot.chapter7")
public class Chapter7Application {

    // 注入RedisTemplate
    @Autowired
    private RedisTemplate redisTemplate = null;

    // 定义自定义后初始化方法
    @PostConstruct

    public void init() {
        initRedisTemplate();
    }

    // 设置RedisTemplate的序列化器
    private void initRedisTemplate() {
        RedisSerializer stringSerializer = redisTemplate.getStringSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
    }

    ......
}

首先通过@Autowired注入由Spring Boot根据配置生成的RedisTemplate对象,然后利用Spring Bean生命周期的特性使用注解@PostConstruct自定义后初始化方法。在这个方法里,把RedisTemplate中的键序列化器修改为StringRedisSerializer。因为之前我们讨论过,在RedisTemplate中它会默认地定义了一个StringRedisSerializer对象,所以这里我并没有自己创建一个新的StringRedisSerializer对象,而是从RedisTemplate中获取。然后把RedisTemplate关于键和其散列数据类型的filed都修改为了使用StringRedisSerializer进行序列化,这样我们在Redis服务器上得到的键和散列的field就都以字符串存储了。

7.2.2 操作Redis数据类型

上面的内容主要是讨论如何让Spring Boot集成Redis,下面来增加实践能力。这节主要演示常用Redis数据类型(如字符串、散列、列表、集合和有序集合)的操作,但是主要是从RedisTemplate的角度,而不是从SessionCallback和RedisCallback接口的角度。这样做的目的,是让读者更加熟悉RedisTemplate的使用方法,因为在大部分的场景下,并不需要很复杂地操作Redis,而仅仅是很简单地使用而已,也就是只需要操作一次Redis,这个时候使用RedisTemplate的操作还是比较多的。如果需要多次执行Redis命令,可以选择使用SessionCallback或者RedisCallback接口。在后面介绍Redis特殊用法时,我们会再次看到这两个接口。首先开始操作字符串和散列,这是Redis最为常用的数据类型,如代码清单7-12所示。

代码清单7-12 操作Redis字符串和散列数据类型

package com.springboot.chapter7.controller;
/**** imports ****/
@Controller
@RequestMapping("/redis")
public class RedisController {

    @Autowired
    private RedisTemplate redisTemplate = null;

    @Autowired
    private StringRedisTemplate stringRedisTemplate = null;

    @RequestMapping("/stringAndHash")
    @ResponseBody
    public Map&lt;String, Object&gt; testStringAndHash() {
        redisTemplate.opsForValue().set("key1", "value1");
        // 注意这里使用了JDK的序列化器,所以Redis保存时不是整数,不能运算
        redisTemplate.opsForValue().set("int_key", "1");
        stringRedisTemplate.opsForValue().set("int", "1");
        // 使用运算
        stringRedisTemplate.opsForValue().increment("int", 1);
        // 获取底层Jedis连接
        Jedis jedis = (Jedis) stringRedisTemplate.getConnectionFactory()
                   .getConnection().getNativeConnection();
        // 减1操作,这个命令RedisTemplate不支持,所以我先获取底层的连接再操作
        jedis.decr("int");
        Map&lt;String, String&gt; hash = new HashMap&lt;String, String&gt;();
        hash.put("field1", "value1");
        hash.put("field2", "value2");
        // 存入一个散列数据类型
        stringRedisTemplate.opsForHash().putAll("hash", hash);
        // 新增一个字段
        stringRedisTemplate.opsForHash().put("hash", "field3", "value3");
        // 绑定散列操作的key,这样可以连续对同一个散列数据类型进行操作
        BoundHashOperations hashOps = stringRedisTemplate.boundHashOps("hash");
        // 删除两个字段
        hashOps.delete("field1", "field2");
        // 新增一个字段
        hashOps.put("filed4", "value5");
        Map&lt;String, Object&gt; map = new HashMap&lt;String, Object&gt;();
        map.put("success", true);
        return map;
    }
    ....
}

这里的@Autowired注入了Spring Boot为我们自动初始化RedisTemplate和StringRedisTemplate对象。看到testStringAndHash方法,首先是存入了一个“key1”的数据,然后是“int_key”。但是请注意这个“int_key”存入到Redis服务器中,因为采用了JDK序列化器,所以在Redis服务器中它不是整数,而是一个被JDK序列化器序列化后的二进制字符串,是没有办法使用Redis命令进行运算的。为了克服这个问题,这里使用StringRedisTemplate对象保存了一个键为“int”的整数,这样就能够运算了。接着进行了加一运算,但是因为RedisTemplate并不能支持底层所有的Redis命令,所以这里先获取了原始的Redis连接的Jedis对象,用它来做减一运算。然后是操作散列数据类型,在插入多个散列的field时可以采用Map,然后为了方便对同一个数据操作,这里代码还获取了BoundHashOperations对象进行操作,这样对同一个数据操作就方便许多了。

列表也是常用的数据类型。在Redis中列表是一种链表结构,这就意味着查询性能不高,而增删节点的性能高,这是它的特性。在Redis中存在从左到右或者从右到左的操作,为了方便测试,我们在代码清单7-12中插入代码清单7-13所示。

代码清单7-13 使用Spring操作列表(链表)

@RequestMapping("/list")
@ResponseBody
public Map&lt;String, Object&gt; testList() {
    // 插入两个列表,注意它们在链表的顺序
    // 链表从左到右顺序为v10,v8,v6,v4,v2
    stringRedisTemplate.opsForList().leftPushAll(
          "list1", "v2", "v4", "v6", "v8", "v10");
    // 链表从左到右顺序为v1,v2,v3,v4,v5,v6
    stringRedisTemplate.opsForList().rightPushAll(
          "list2", "v1", "v2", "v3", "v4", "v5", "v6");
    // 绑定list2链表操作
    BoundListOperations listOps = stringRedisTemplate.boundListOps("list2");
    // 从右边弹出一个成员
    Object result1 = listOps.rightPop();
    // 获取定位元素,Redis从0开始计算,这里值为v2
    Object result2 = listOps.index(1);
    // 从左边插入链表
    listOps.leftPush("v0");
    // 求链表长度
    Long size = listOps.size();
    // 求链表下标区间成员,整个链表下标范围为0到size-1,这里不取最后一个元素
    List elements = listOps.range(0, size-2);
    Map&lt;String, Object&gt; map = new HashMap&lt;String, Object&gt;();
    map.put("success", true);
    return map;
}

上述操作是基于StringRedisTemplate的,所以保存到Redis服务器的都是字符串类型,只是这里有两点需要注意。首先是列表元素的顺序问题,是从左到右还是从右到左,这是容易弄糊涂的问题;其次是下标问题,在Redis中是以0开始的,这与Java中的数组类似。接着是集合。对于集合,在Redis中是不允许成员重复的,它在数据结构上是一个散列表的结构,所以对于它而言是无序的,对于两个或者以上的集合,Redis还提供了交集、并集和差集的运算。为了进行测试,我们可以在代码清单7-12的基础上加入代码清单7-14的程序片段。

代码清单7-14 使用Spring操作集合

@RequestMapping("/set")
@ResponseBody
public Map&lt;String, Object&gt; testSet() {
    // 请注意:这里v1重复两次,因为集合不允许重复,所以只是插入5个成员到集合中
    stringRedisTemplate.opsForSet().add("set1", 
         "v1","v1","v2","v3","v4","v5");
    stringRedisTemplate.opsForSet().add("set2", "v2","v4","v6","v8");
    // 绑定set1集合操作
    BoundSetOperations setOps = stringRedisTemplate.boundSetOps("set1");
    // 增加两个元素
    setOps.add("v6", "v7");
    // 删除两个元素
    setOps.remove("v1", "v7");
    // 返回所有元素
    Set set1 = setOps.members();
    // 求成员数
    Long size = setOps.size();
    // 求交集
    Set inter = setOps.intersect("set2");
    // 求交集,并且用新集合inter保存
    setOps.intersectAndStore("set2", "inter");
    // 求差集
    Set diff = setOps.diff("set2");
    // 求差集,并且用新集合diff保存
    setOps.diffAndStore("set2", "diff");
    // 求并集
    Set union = setOps.union("set2");
    // 求并集,并且用新集合union保存
    setOps.unionAndStore("set2", "union");
    Map&lt;String, Object&gt; map = new HashMap&lt;String, Object&gt;();
    map.put("success", true);
    return map;
}

这里在添加集合set1时,存在两个v1一样的元素。因为集合不允许重复,所以实际上在集合只算是一个元素。然后可以看到对集合各类操作,在最后还有交集、差集和并集的操作,这些是集合最常用的操作。在一些网站中,经常会有排名,如最热门的商品或者最大的购买买家,都是常常见到的场景。对于这类排名,刷新往往需要及时,也涉及较大的统计,如果使用数据库会太慢。为了支持集合的排序,Redis还提供了有序集合(zset)。有序集合与集合的差异并不大,它也是一种散列表存储的方式,同时它的有序性只是靠它在数据结构中增加一个属性——score(分数)得以支持。为了支持这个变化,Spring提供了TypedTuple接口,它定义了两个方法,并且Spring还提供了其默认的实现类DefaultTypedTuple,其内容如图7-6所示

技术分享图片

在TypedTuple接口的设计中,value是保存有序集合的值,score则是保存分数,Redis是使用分数来完成集合的排序的,这样如果把买家作为一个有序集合,而买家花的钱作为分数,就可以使用Redis进行快速排序了。下面我们把代码清单7-15插到代码清单7-12中。

代码清单7-15 操作Redis有序集合

@RequestMapping("/zset")
@ResponseBody
public Map&lt;String, Object&gt; testZset() {
    Set&lt;TypedTuple&lt;String&gt;&gt; typedTupleSet = new HashSet&lt;&gt;();
    for (int i=1; i&lt;=9; i++) {
        // 分数
        double score = i*0.1;
        // 创建一个TypedTuple对象,存入值和分数
        TypedTuple&lt;String&gt; typedTuple 
            = new DefaultTypedTuple&lt;String&gt;("value" + i, score);
        typedTupleSet.add(typedTuple);
    }
    // 往有序集合插入元素
    stringRedisTemplate.opsForZSet().add("zset1", typedTupleSet);
    // 绑定zset1有序集合操作
    BoundZSetOperations&lt;String, String&gt; zsetOps 
          = stringRedisTemplate.boundZSetOps("zset1");
    // 增加一个元素
    zsetOps.add("value10", 0.26);
    Set&lt;String&gt; setRange = zsetOps.range(1, 6);
    // 按分数排序获取有序集合
    Set&lt;String&gt; setScore = zsetOps.rangeByScore(0.2, 0.6);
    // 定义值范围
    Range range = new Range();
    range.gt("value3");// 大于value3
    // range.gte("value3");// 大于等于value3
    // range.lt("value8");// 小于value8
    range.lte("value8");// 小于等于value8
    // 按值排序,请注意这个排序是按字符串排序
    Set&lt;String&gt; setLex = zsetOps.rangeByLex(range);
    // 删除元素
    zsetOps.remove("value9", "value2");
    // 求分数
    Double score = zsetOps.score("value8");
    // 在下标区间下,按分数排序,同时返回value和score
    Set&lt;TypedTuple&lt;String&gt;&gt; rangeSet = zsetOps.rangeWithScores(1, 6);
    // 在分数区间下,按分数排序,同时返回value和score
    Set&lt;TypedTuple&lt;String&gt;&gt; scoreSet = zsetOps.rangeByScoreWithScores(1, 6);
    // 按从大到小排序
    Set&lt;String&gt; reverseSet = zsetOps.reverseRange(2, 8);
    Map&lt;String, Object&gt; map = new HashMap&lt;String, Object&gt;();
    map.put("success", true);
    return map;
}

代码中使用了TypedTuple保存有序集合的元素,在默认的情况下,有序集合是从小到大地排序的,按下标、分数和值进行排序获取有序集合的元素,或者连同分数一起返回,有时候还可以进行从大到小的排序,只是在使用值排序时,我们可以使用Spring为我们创建的Range类,它可以定义值的范围,还有大于、等于、大于等于、小于等于等范围定义,方便我们筛选对应的元素。地理位置和基数不是我们常用的功能,所以这里不再赘述了。

第7章 使用性能利器——Redis

原文:https://www.cnblogs.com/jonrain0625/p/13022143.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!