目录
一、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
|