目录

一、Redis初体验

二、Spring Boot整合Redis

三、Redis事务管理

四、Redis持久化


一、Redis初体验

        Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API

【Key-Value数据库】

        Redis是一款基于键值对的NoSQL(非关系型)数据库,它的值支持多种数据结构,例如:字符串(Strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等。
        这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。
        在Command reference – Redis页面有对于不同数据类型的详细命令操作。

【可基于内存亦可持久化】

        Redis将所有的数据都存放在内存当中,所以它的读写性能十分惊人。同时,Redis还可以将内存中中的数据以快照(RDB)或日志(AOF)的形式保存到硬盘上,以保证数据的安全性。这点后面会详细讲。

【Redis典型应用场景】

        缓存、排行榜、计数器、社交网络、消息队列等。

【Redis的下载】

        Redis官网上,只提供了以.tar.gz后缀的包,此文件是针对于Linux系统的,官网并未提供针对Windows系统的安装包。好在,微软为我们提供了针对Windows系统的安装包:Releases · microsoftarchive/redis · GitHub,点击.msi后缀即可下载。

二、Spring Boot整合Redis

【导入pom依赖】

Maven Repository: org.springframework.boot » spring-boot-starter-data-redis (mvnrepository.com)

<dependencies>

    <!--序列化-->
    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>fastjson</artifactId>
		<version>1.2.79</version>
	</dependency>

    <!--集成Redis-->
	<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-redis</artifactId>
		<version>2.6.3</version>
	</dependency>

</dependencies>

【配置Propertices文件】

# redisProperties
spring.redis.database=11        # 选择使用Redis的哪个库
spring.redis.host=localhost     # 主机ip
spring.redis.port=6379          # 端口号

【编写配置类】

        实际上,Spring Boot已经在RedisAutoConfiguration.class类中为我们自动配置了相关属性,Key-Value自动配置为<Object , Object>的数据类型,但一般情况下我们习惯key为String类型,所以我们需要编写一个配置类,重新配置key-value的数据类型、以及key-value的序列化方式,并注入Spring容器中去。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        // 设置key的序列化方式
        template.setKeySerializer(RedisSerializer.string());
        // 设置value的序列化方式
        template.setValueSerializer(RedisSerializer.json());
        // 设置hash的key的序列化方式
        template.setHashKeySerializer(RedisSerializer.string());
        // 设置hash的value的序列化方式
        template.setHashValueSerializer(RedisSerializer.json());

        template.afterPropertiesSet();// 在配置过之后触发生效
        return template;
    }

}

【访问Redis】

 访问前需要输入RedisTemplate实体类

    @Autowired
    private RedisTemplate redisTemplate;

测试全局命令

    @Test
    public void testKeys(){
        // 删除指定key
        redisTemplate.delete("test:user");
        // 是否存在指定的key
        System.out.println(redisTemplate.hasKey("test:user"));
        // 设置存货时间
        redisTemplate.expire("test:students",10, TimeUnit.SECONDS);
    }

测试Strings数据类型

    @Test
    public void testStrings(){
        String redisKey = "test:count";

        redisTemplate.opsForValue().set(redisKey,1);

        System.out.println(redisTemplate.opsForValue().get(redisKey));
        System.out.println(redisTemplate.opsForValue().increment(redisKey));
        System.out.println(redisTemplate.opsForValue().decrement(redisKey));
    }

测试Hashes数据类型

    @Test
    public void testHashes(){
        String redisKey = "test:user";

        redisTemplate.opsForHash().put(redisKey,"id",1);
        redisTemplate.opsForHash().put(redisKey,"username","zhangsan");

        System.out.println(redisTemplate.opsForHash().get(redisKey,"id"));
        System.out.println(redisTemplate.opsForHash().get(redisKey,"username"));
    }

测试Lists数据类型

    @Test
    public void testLists(){
        String redisKey = "test:ids";

        redisTemplate.opsForList().leftPush(redisKey,101);
        redisTemplate.opsForList().leftPush(redisKey,102);
        redisTemplate.opsForList().leftPush(redisKey,103);

        System.out.println(redisTemplate.opsForList().size(redisKey));// 查看元素个数
        System.out.println(redisTemplate.opsForList().index(redisKey,0));// 查看索引为0的元素
        System.out.println(redisTemplate.opsForList().range(redisKey,0,-1));// 查看指定区间的元素
        System.out.println(redisTemplate.opsForList().leftPop(redisKey));// 从左边弹出一个元素
        System.out.println(redisTemplate.opsForList().leftPop(redisKey));
        System.out.println(redisTemplate.opsForList().leftPop(redisKey));
    }

测试Sets数据类型

    @Test
    public void testSets(){
        String redisKey = "test:teachers";

        redisTemplate.opsForSet().add(redisKey,"刘备","关羽","张飞","赵云","诸葛亮");

        System.out.println(redisTemplate.opsForSet().size(redisKey));// 查看元素个数
        System.out.println(redisTemplate.opsForSet().pop(redisKey));// 随机弹出一个元素
        System.out.println(redisTemplate.opsForSet().members(redisKey));// 查看所有元素
    }

测试Sorted Sets数据类型

    @Test
    public void testSortedSets(){
        String redisKet = "test:students";

        redisTemplate.opsForZSet().add(redisKet,"唐僧",80);
        redisTemplate.opsForZSet().add(redisKet,"悟空",90);
        redisTemplate.opsForZSet().add(redisKet,"八戒",50);
        redisTemplate.opsForZSet().add(redisKet,"沙僧",70);
        redisTemplate.opsForZSet().add(redisKet,"白龙马",60);

        System.out.println(redisTemplate.opsForZSet().zCard(redisKet));// 查看元素数量
        System.out.println(redisTemplate.opsForZSet().score(redisKet,"八戒"));// 查看指定元素的分数
        System.out.println(redisTemplate.opsForZSet().reverseRank(redisKet,"八戒"));// 查看'八戒'的排名(倒序)
        System.out.println(redisTemplate.opsForZSet().reverseRange(redisKet,0,-1));// 查看指定区间的元素(倒序)
    }

绑定key

        当我们需要多次访问同一个key时,可以在创建访问Redis内部对象的时候绑定key,产生一个绑定key的对象,利用这个对象就可以反复地访问同一个key

    @Test
    public void testBoundOperations(){
        String redisKey = "test:count";
        BoundValueOperations operations = redisTemplate.boundValueOps(redisKey);
        operations.increment();
        operations.increment();
        operations.increment();
        operations.increment();
        operations.increment();
        System.out.println(operations.get());
    }

三、Redis事务管理

Redis事务管理机制
        与关系型数据库不同的是,当我们开启事务后,再去执行Redis命令时,命令不会被立即执行,而是把命令放进一个队列里,先暂存。直至提交事务时,才会把所有暂存的命令一股脑地发给Redis服务器,一起执行。

这样的机制就引发了一个问题:
        因为在一个事务之内的命令不会立即执行,而是提交事务是批量执行,所以当我们在事务过程中做了一个查询,这个查询不会立刻返回结果,所以我们就不能在事务中间进行查询的操作。

声明式事务管理是生命在一个方法上,而如果我们需要在此方法内做查询,就会导致查询的是此方法执行之前的数据,就可能导致错误。因此,我们异常编程式事务管理。

    @Test
    public void testTransaction(){
        Object obj = redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                String redisKey = "test:tx";

                operations.multi();// 启用事务

                operations.opsForSet().add(redisKey,"zhangsan");
                operations.opsForSet().add(redisKey,"lisi");
                operations.opsForSet().add(redisKey,"wangwu");

                // 在开启事务和提交事务之间,会把命令放在队列里,不会执行,所以此次查询是提交事务之前的结果
                System.out.println(operations.opsForSet().members(redisKey));

                return operations.exec();// 返回提交事务
            }
        });
        System.out.println(obj);//[1, 1, 1, [wangwu, zhangsan, lisi]]
    }

}

四、Redis持久化

【Redis两种持久化方式】

redis提供了两种持久化的方式,分别是RDB(Redis DataBase)和AOF(Append Only File)。

RDB,简而言之,就是在不同的时间点,将redis存储的数据生成快照并存储到磁盘等介质上;

AOF,则是换了一个角度来实现持久化,那就是将redis执行过的所有写指令记录下来,在下次redis重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了。

其实RDB和AOF两种方式也可以同时使用,在这种情况下,如果redis重启的话,则会优先采用AOF方式来进行数据恢复,这是因为AOF方式的数据恢复完整度更高。

如果你没有数据持久化的需求,也完全可以关闭RDB和AOF方式,这样的话,redis将变成一个纯内存数据库,就像memcache一样。

【RDB】

RDB方式,是将redis某一时刻的数据持久化到磁盘中,是一种快照式的持久化方法。

redis在进行数据持久化的过程中,会先将数据写入到一个临时文件中,待持久化过程都结束了,才会用这个临时文件替换上次持久化好的文件。正是这种特性,让我们可以随时来进行备份,因为快照文件总是完整可用的。

对于RDB方式,redis会单独创建(fork)一个子进程来进行持久化,而主进程是不会进行任何IO操作的,这样就确保了redis极高的性能。

如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。

虽然RDB有不少优点,但它的缺点也是不容忽视的。如果你对数据的完整性非常敏感,那么RDB方式就不太适合你,因为即使你每5分钟都持久化一次,当redis故障时,仍然会有近5分钟的数据丢失。所以,redis还提供了另一种持久化方式,那就是AOF。

【AOF】

AOF,英文是Append Only File,即只允许追加不允许改写的文件。

如前面介绍的,AOF方式是将执行过的写指令记录下来,在数据恢复时按照从前到后的顺序再将指令都执行一遍,就这么简单。

我们通过配置redis.conf中的appendonly yes就可以打开AOF功能。如果有写操作(如SET等),redis就会被追加到AOF文件的末尾。

默认的AOF持久化策略是每秒钟fsync一次(fsync是指把缓存中的写指令记录到磁盘中),因为在这种情况下,redis仍然可以保持很好的处理性能,即使redis故障,也只会丢失最近1秒钟的数据。

如果在追加日志时,恰好遇到磁盘空间满、inode满或断电等情况导致日志写入不完整,也没有关系,redis提供了redis-check-aof工具,可以用来进行日志修复。

因为采用了追加方式,如果不做任何处理的话,AOF文件会变得越来越大,为此,redis提供了AOF文件重写(rewrite)机制,即当AOF文件的大小超过所设定的阈值时,redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集。举个例子或许更形象,假如我们调用了100次INCR指令,在AOF文件中就要存储100条指令,但这明显是很低效的,完全可以把这100条指令合并成一条SET指令,这就是重写机制的原理。

在进行AOF重写时,仍然是采用先写临时文件,全部完成后再替换的流程,所以断电、磁盘满等问题都不会影响AOF文件的可用性,这点大家可以放心。

AOF方式的另一个好处,我们通过一个“场景再现”来说明。某同学在操作redis时,不小心执行了FLUSHALL,导致redis内存中的数据全部被清空了,这是很悲剧的事情。不过这也不是世界末日,只要redis配置了AOF持久化方式,且AOF文件还没有被重写(rewrite),我们就可以用最快的速度暂停redis并编辑AOF文件,将最后一行的FLUSHALL命令删除,然后重启redis,就可以恢复redis的所有数据到FLUSHALL之前的状态了。是不是很神奇,这就是AOF持久化方式的好处之一。但是如果AOF文件已经被重写了,那就无法通过这种方法来恢复数据了。

虽然优点多多,但AOF方式也同样存在缺陷,比如在同样数据规模的情况下,AOF文件要比RDB文件的体积大。而且,AOF方式的恢复速度也要慢于RDB方式。

如果你直接执行BGREWRITEAOF命令,那么redis会生成一个全新的AOF文件,其中便包括了可以恢复现有数据的最少的命令集。

如果运气比较差,AOF文件出现了被写坏的情况,也不必过分担忧,redis并不会贸然加载这个有问题的AOF文件,而是报错退出。这时可以通过以下步骤来修复出错的文件:

1.备份被写坏的AOF文件
2.运行redis-check-aof –fix进行修复
3.用diff -u来看下两个文件的差异,确认问题点
4.重启redis,加载修复后的AOF文件

最后一趴:http://www.h5min.cn/article/56448.htm

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐