Redis
一、这是个啥玩意?
Redis 是一种运行在内存中的NoSql非关系型数据库。可以在1s内完成10万次的读写,具有很高的性能,现在网站应用多数功能是查询,在查询比例较大的模块中用redis 可以极大的提高性能。
二、可以干啥?
Redis 除了操作支持的数据类型功能外,还能支持事务,流水线,发布订阅和lua脚本等功能。一般我们会用来做为缓存来提高应用的性能。
三、咋用?
RedisConnectionFactory 通过驱动配置JedisPoolConfig 进行配置、RedisTemplate进行操作、
基础知识
Redis 是一种键值数据库,而且是以字符串类型为中心(Java是操作对象的,所以存储的时候需要序列化)。
支持的数据类型:字符串、散列、列表(链表)、集合、有序集合、基数和地理位置(后两种很少使用)
驱动
Java 中与Redis 连接的驱动有多种,目前比较广泛使用的是 Jedis。
Spring 中使用 Redis
Spring 提供了RedisConnectionFactory接口可生成RedisConnection对象,是对Redis底层接口的封装。
RedisConnectionFactory 配置
@Configuration
public calss RedisConfig(){
private RedisConnectionFactory connectionFactory=null;
@Bean(name="RedisConnectionFactory")
public RedisConnectionFactory initRedisConnectionFactory(){
if(this.connectionFactory != null){
return this.connectionFactory;
}
JedisPoolConfig pc = new JedisPoolConfig();
//最大空闲数
pc.setMaxIdle(30);
//最大连接数
pc.setMaxTotal(50);
//最大等待毫秒数
pc.setMaxWaitMillis(2000);
JedisConnectionFactory connectionFactory = new JedisConnectionFactory(pc);
RedisConnectionConfiguration rsCfg = connectionFactory.getStandaloneConfiguration();
rsCfg.setHostName("192.168.11.131");
rsCfg.setPort(6379);
rsCfg.setPassword("23234");
this.connectionFactory =connectionFactory;
return connectionFactory;
}
//redisTemplate
@Bean(name="redisTemplate")
public RedisTemplate<Object,Object> initRedisTemplate(){
RedisTemplate<Object,Object> redisTemplate = new RedisTemplate<>();
//RedisTemplate 会自动初始化 setringRedisSerializer,所以这里直接获取
RedisSerializer setringRedisSerializer = redisTemplate.getStringSerializer();
//设置字符串序列化器,这样spring 就会吧redis的key 当作字符串处理了
redisTemplate.setKeySerializer(StringRedisSerializer);
redisTemplate.setHashKeySerializer(StringRedisSerializer);
redisTemplate.setHashValueSerializer(StringRedisSerializer);
redisTemplate.setConnectionFactory(this.connectionFactory);
return redisTemplate;
}
}
RedisTemplate
RedisTemplate 会自动从 RedisConnectionFactory中获取连接,然后执行对应命令,最后还会关闭Redis连接。(如上代码)
Spring 提供了RedisSerializer接口,用两个方法,serialize,deserialize两个方法。
JdkSerializationRedisSerializer是RedisTemplate默认的序列器。StringRedisSerializer为常用的序列器
RedisTemplate提供的可配置的属性
属性 | 描述 | 备注 |
---|---|---|
defaultSerializer | 默认序列化器 | 如果没有设置,使用JdkSerializationRedisSerializer |
keySerializer | Redis键序列化器 | 如果没有设置,则使用默认序列化器 |
valueSerializer | Redis值序列化器 | 同上 |
hashKeySerializer | Redis散列结构field序列化器 | 同上 |
hashValueSerializer | Redis散列结构value序列化器 | 同上 |
stringSerializer | 字符串序列化器 | RedisTemplate自动赋值为StringRedisSerializer对象 |
示例:
redisTemplate.opsForValue().set("key1","value1");
redisTemplate.opsForHash().set("hash","field","hvalue");
redisTemplate 的操作, 从连接工厂获取一个连接 ——>执行对应命令——>关闭连接。这显然存在资源浪费,
为了克服这个问题,Spring提供了RedisCallback 和SessionCallback
数据类型操作封装
redisTempalte.opsForGeo();//地理位置操作接口
redisTempalte.opsForHash();//散列
redisTempalte.opsForHyperLogLog();//基数
redisTempalte.opsForList();//列表
redisTempalte.opsForSet();//集合
redisTempalte.opsForValue();//字符串
redisTempalte.opsForZSet();//有序集合
如果需要对一个键值对连续操作
redisTempalte.boundGeoOps("geo");//地理位置绑定的操作接口
redisTempalte.boundHashOps("hash");
redisTempalte.boundListOps("list");
redisTempalte.boundSetOps("set");
redisTempalte.boundValueOps("string");
redisTempalte.boundZSetOps("zset");
SessionCallback和RedisCallBack
他们的作用都是让RedisTemplate 进行回调,可以在一条连接中执行多个redis命令,但是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 useSeesionCallback(RedisTemplate redisTemplate) throws DataAccessException{
redisTemplate.execute(new SeesionCallback(){
@Override
public Object doInRedis(RedisConnection rc){
rc.set("key1","value1");
rc.hSet("hash","field","hvalue");
return null;
}
})
}
Spring Boot中配置使用Redis
配置
redis:
jedis:
pool:
min-idle: 5
max-idle: 20
max-active: 20
max-wait: 2000
port: 6379
password: 123456
timeout: 1000
host: 127.0.0.1 ##本机地址,默认地址,可在redis.conf中修改
cache:
type: redis
cache-names: redisCache
初始化
进行字符串序列化初始化
@PostConstruct
public void init(){
initRedisTemplate();
}
private void initRedisTemplate() {
//RedisTemplate 会自动初始化 setringRedisSerializer,所以这里直接获取
RedisSerializer setringRedisSerializer = redisTemplate.getStringSerializer();
//设置字符串序列化器,这样spring 就会吧redis的key 当作字符串处理了
redisTemplate.setKeySerializer(setringRedisSerializer);
redisTemplate.setHashKeySerializer(setringRedisSerializer);
redisTemplate.setHashValueSerializer(setringRedisSerializer);
}
操作数据类型
常用注解开发
spring.cache.cache-names= #如果由底层的缓存管理器支持创建,以逗号分隔的列表来缓存名称 spring.cache.redis.cache-null-values=true #是否允许Redis缓存空值
spring.cache . redis . key-prefix= # Redis 的键前缀
spring.cache.redis . time-to-live=Oms # 缓存超时时间戳,配置为 0 则不设置超时时间
spring.cache .redis.use-key-prefix=true #是否启用 Redis 的键前缀
spring. cache.type= # 缓存类型, 在默认的情况下, Spring 会自动根据上下文探测
@CachePut(value=“redisCache”, key="") 插入 修改 表示将方法结果返回存放到 缓存中。
@Cacheable(value ="redisCache”, key =”’redis_user_’+#id”) 查询 表示先从缓存中通过定义的键查询,如果可以查询到数据,则返回,否则执行该方法,返回数据,并且将返回结果保存到缓存中。
@CacheEvict(va工ue =”redisCache”, key =” ’r edis_user_’+#id”, before 工 nvocation = false) 移除 通过定义的键移除缓存,它有一个 Boolean类型的配置项 beforelnvocation,表 示在方法之前或者之后移除缓存。因为其默认值为也lse,所以默认为方法之后将缓存移除 。
事务
首先 Redis 是支持一定事务能力的 NoSQL, 在 Redis 中使用事务,通常的命令组合是 watch… multi…exec,也就是要在一个 Redis 连接中执行多个命令,这时我们可以考虑使用SessionCallback接 口来达到这个目的。其中, watch命令是可以监控 Redis的一些键: multi命令是开始事务,开始事务后,该客户端 的命 令不会马上被执行 ,而 是存放在一个队列里,这点是需要注意的地方,也就是在这时我们执行一些堪回数据的命令, Redis也是不会马上执行的,而是把命令放到一个队列里,所以 此时调用 Redis 的命令,结果都是返回 null,这是初学者容易犯的错误: exe 命令的意义在于执行事 务,只是它在队列命令执行前会判断被 watch监控的 Redis 的键的数据是否发生过变化 (即使赋予与 之前相同的值也会被认为是变化过〉,如果它认为发生了变化,那么 Redis 就会取消事务 , 否则就会 执行事务, Redis在执行事务时,要么全部执行, 要么全部不执行,而且不会被其他客户端打断,这样就保证了 Redis事务下数据的一致性。图 就是 Redis事务执行的过程。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xeWPAHkd-1618120062634)(https://raw.githubusercontent.com/shadowlgd/image/master/blogimg屏幕快照 2021-04-11 下午1.41.27.png)]
public Map<String, Ob] ect> testMulti () {
redisTemplate.opsForValue() .set(”keyl”, ”valuel”);
List
list= (List)redisTemplate.execute((RedisOperations operations) - > { //设置要监控 keyl
operations.watch (” keyl ”);
//开启事务,在 exec 命令执行前,全部都只是进入队列
operat工ons .multi();
operations.opsForV alue() . set (” key2 ” , ” value2 ” );
II operations.opsForValue () .increment (” keyl”, 1);
//获取值将为 null, 因为自由 s 只是把命令放入队列
Object value2 = operations.opsForV alue() . get (” key2 ” );
System.out .println (”命令在队列,所以 value 为 null <”+ value2 +”> ” ); operations.opsForValue() .set (” key3”, "value3” );
Object value3 = operat工ons.opsForValue() .get (”key3”);
System.out.println (”命令在队列,所以 value 为 null 【川 value3 + ” > ” ) ;
//执行 exec 命令,将先判别 keyl 是否在监控后被修改过,如果是则不执行事务,否则就执行事务
return operations exec();
});
}
持久化
持久化流程
- 客户端向服务端发送写操作(数据在客户端的内存中)。
- 数据库服务端接收到写请求的数据(数据在服务端的内存中)。
- 服务端调用write这个系统调用,将数据往磁盘上写(数据在系统内存的缓冲区中)。
- 操作系统将缓冲区中的数据转移到磁盘控制器上(数据在磁盘缓存中)。
- 磁盘控制器将数据写到磁盘的物理介质中(数据真正落到磁盘上)。
上面的5种持久化流程并不是能一直全部完成,当数据出现损毁的时候,就需要一定的恢复机制
RDB机制
RDB其实就是把数据以快照的形式保存在磁盘上,RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。
触发方式
-
save
执行save命令期间,会阻塞redis其他命令,直至完成生成二进制RDB文件。
-
bgsave
具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。基本上 Redis 内部所有的RDB操作都是采用 bgsave 命令。
-
自动化
**①save:**这里是用来配置触发 Redis的 RDB 持久化条件,也就是什么时候将内存中的数据保存到硬盘。比如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave。
默认如下配置:
表示900 秒内如果至少有 1 个 key 的值变化,则保存save 900 1;表示300 秒内如果至少有 10 个 key 的值变化,则保存save 300 10;表示60 秒内如果至少有 10000 个 key 的值变化,则保存save 60 10000
不需要持久化,那么你可以注释掉所有的 save 行来停用保存功能。
**②stop-writes-on-bgsave-error :**默认值为yes。当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。如果Redis重启了,那么又可以重新开始接收数据了
**③rdbcompression ;**默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。
**④rdbchecksum :**默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。
**⑤dbfilename :**设置快照的文件名,默认是 dump.rdb
**⑥dir:**设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。
优点
- RDB文件紧凑,全量备份,非常适合用于进行备份和灾难恢复。
- 生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
- RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
缺点
RDB快照是一次全量备份,存储的是内存数据的二进制序列化形式,存储上非常紧凑。当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据,父进程修改内存子进程不会反应出来,所以在快照持久化期间修改的数据不会被保存,可能丢失数据。
AOF机制
以追加的方式记录数据的变动,每次有写命令就会存到aof文件中
这种方式会造成文件越来越大,为了压缩aof文件,redis 提供了bgrewriteaof命令。将内存中的数据以命令的方式保存到临时文件中,同时会fork出一条新进程来将文件重写。
触发机制
- 每修改同步always:同步操作,每次数据变化都会同步,数据完整度高,性能差
- 每秒同步everysec:异步操作,每秒记录一次,如果期间宕机会有数据丢失
- 不同步no:从不同步
优点
- AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据。
- AOF日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损。
- AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。
- AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据
缺点
- 对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大
- AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的
- 以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。
两种机制配合使用,选择最合适的