Redis 概述
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库
在我们日常的Java Web开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题,可是一旦涉及大数据量的需求,比如一些商品抢购的情景,或者是主页访问量瞬间较大的时候,单一使用数据库来保存数据的系统会因为面向磁盘,磁盘(数据库)读/写速度比较慢的问题而存在严重的性能弊端,一瞬间成千上万的请求到来,需要系统在极短的时间内完成成千上万次的读/写操作,这个时候往往不是数据库能够承受的,极其容易造成数据库系统瘫痪,最终导致服务宕机的严重生产问题。
NoSQL 技术
为了克服上述的问题,Java Web项目通常会引入NoSQL技术,这是一种基于内存的数据库,并且提供一定的持久化功能。
Redis和MongoDB是当前使用最广泛的NoSQL,而就Redis技术而言,它的性能十分优越,可以支持每秒十几万此的读/写操作,其性能远超数据库,并且还支持集群、分布式、主从同步等配置,原则上可以无限扩展,让更多的数据存储在内存中,更让人欣慰的是它还支持一定的事务能力,这保证了高并发的场景下数据的安全和一致性。
redis持久化(万一redis突然宕机了咋办?数据会丢失吗?)
回答标题问题:不会丢失。
先来说说什么是数据持久化,为啥要进行数据持久化。
第一点:什么是数据持久化。
在缓存服务中,我们都了解,他说一种高速缓冲,存放于内存的一种服务。那么内存中的东西生命周期是什么呢?通常来说,当机器关闭的时候,内存中的数据都会清空,都不会存储,那么下次开机的时候,不会出现之前的数据。那么为了防止这种情况突然发生,,我们通常都会对缓存做持久化操作。这里以redis为例
Redis提供了2中不同形式的持久化类型:
1.rdb(redis database)
原理:将redis在内存的数据库中的数据定时dump到硬盘上,实现rdb持久化。
在rdb持久化过程中,是在规定的时间间隔之内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入到临时文件中,写入成功后,再替换之前的文件,再用二进制压缩存储。那么此操作是一种间隔存储的,类似一种快照形式。
在整个的持久化过程中,主进程是不会进行任何的IO操作的,这就确保了极高的性能,如果我们需要大规模的数据的恢复,且对于数据恢复的完整性不是非常敏感,那么rdb方式要比aof方式更加高效。但是rdb的缺点就是最后一次的持久化操作很有可能丢失,那么一发生丢失就是整个数据丢失,下次备份还是需要重头开始。
那么从上面我们就可以总结出rdb的一个优缺点了:
优点:相较aof而言:rdb是比较磁盘空间的,他只是保持更新整个redis缓存中所有的数据的一个完整性。即,相当于在一定时间间隔内对redis缓存数据的一个持久化,采取一种临时文件替换上一层保存的文件,比较节省空间的。
然后,rdb是针对数据的角度进行一个持久化的,那么当然我们进行反向导入的时候,这个时候,rdb是高效的。但是弊端也相对明显,就是在每次fork redis中全数据的时候,会因数据量的增加而增加性能的消耗。
周期进行备份,但是如果突然停止,会丢失最后一次快照的修改。临时文件会丢失。
2.aof(append of file)
原理:将redis的操作日志以追加的形式写入到文件中。
通过只增不改,不删方式。Aof持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,文本的形式记录,可以打开文件看到详细的操作记录。
那么此种操作就一种日志形式来进行一个记录各种更新操作。会对每一次操作的命令进行一个记录,那么在redis服务进行一个启动的时候,会读取日志中的命令,对redis中的数据进行一个恢复。
所以可以看出aof是一个粒度比较小的操作。精确到每一条操作。且每条操作的前后都不影响。
那么这个时候也可以对比出aof的一个优点所在了:
备份机制更加成熟稳健,对每一条操作都会有所记录,那么就会大大的降低丢失率。
这是个日志形式的备份,那么我们就可以直接读持久化文件。可以对其人为的操作。
但是就是因为他的粒度细致,并且只会增加数据,不会修改数据,那么这个时候会占用更多的磁盘空间。且正因为日志,那么恢复的时候,也要从日志中读取出数据信息,进行一个反操作。那么粒度细,也会造成一定的性能压力,每次变更都会新增日志记录。官方也做出了一些声明:存在个别bug,会造成不能恢复现象。
Redis 在 Java Web 中的应用
Redis 在 Java Web 主要有两个应用场景:
- 存储 缓存用的数据;
- 需要高速读/写的场合使用它快速读/写;
缓存
如果我们把数据放在 Redis 中,也就是直接放在内存之中,让服务端直接去读取内存中的数据,那么这样速度明显就会快上不少,并且会极大减小数据库的压力
一般而言在使用 Redis 进行存储的时候,我们需要从以下几个方面来考虑:
- 业务数据常用吗?命中率如何?如果命中率很低,就没有必要写入缓存;
- 该业务数据是读操作多,还是写操作多?如果写操作多,频繁需要写入数据库,也没有必要使用缓存;
- 业务数据大小如何?如果要存储几百兆字节的文件,会给缓存带来很大的压力,这样也没有必要;
在考虑了这些问题之后,如果觉得有必要使用缓存,那么就使用它!使用 Redis 作为缓存的读取逻辑如下图所示:
![](https://img.haomeiwen.com/i7896890/404e89059b6a96b5.png)
从上图我们可以知道以下两点:
1.当第一次读取数据的时候,读取 Redis 的数据就会失败,此时就会触发程序读取数据库,把数据读取出来,并且写入 Redis 中;
2.当第二次以及以后需要读取数据时,就会直接读取 Redis,读到数据后就结束了流程,这样速度就大大提高了。
高速读/写的场合
在如今的互联网中,越来越多的存在高并发的情况,比如天猫双11、抢红包、抢演唱会门票等,这些场合都是在某一个瞬间或者是某一个短暂的时刻有成千上万的请求到达服务器,如果单纯的使用数据库来进行处理,就算不崩,也会很慢的,轻则造成用户体验极差用户量流失,重则数据库瘫痪,服务宕机,而这样的场合都是不允许的!
所以我们需要使用 Redis 来应对这样的高并发需求的场合,我们先来看看一次请求操作的流程图:
![](https://img.haomeiwen.com/i7896890/6995e2980a8aae6a.png)
我们来进一步阐述这个过程:
- 当一个请求到达服务器时,只是把业务数据在 Redis 上进行读写,而没有对数据库进行任何的操作,这样就能大大提高读写的速度,从而满足高速响应的需求;
- 但是这些缓存的数据仍然需要持久化,也就是存入数据库之中,所以在一个请求操作完 Redis 的读/写之后,会去判断该高速读/写的业务是否结束,这个判断通常会在秒杀商品为0,红包金额为0时成立,如果不成立,则不会操作数据库;如果成立,则触发事件将 Redis 的缓存的数据以批量的形式一次性写入数据库,从而完成持久化的工作。
作者:我没有三颗心脏
链接:https://www.jianshu.com/p/56999f2b8e3b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
安装
1、Github下载地址:https://github.com/MicrosoftArchive/redis/releases(3.2.100)
2、解压打开对应解压目录的cmd,输入"redis-server redis.windows.conf"
![](https://img.haomeiwen.com/i7264030/e2c2e0fac48b368f.png)
3、接下来部署Redis为windows下的服务 首先关掉上一个窗口再打开一个新的cmd命令窗口,然后输入指令redis-server --service-install redis.windows.conf
![](https://img.haomeiwen.com/i7264030/c69ad992b1db52dc.png)
4、随后,进入右击此电脑–管理–服务和应用程序–服务 启动服务
![](https://img.haomeiwen.com/i7264030/746e1d5da8927091.png)
5、Redis常用的指令
卸载服务:redis-server --service-uninstall
开启服务:redis-server --service-start
停止服务:redis-server --service-stop
测试redis,通过cd到我们解压的目录,输入指令通过Set get指令查看是否成功
//输入:
redis-cli
//Key-Value
set zhimingzou pengsha
OK
get zhimingzou
"pengsha"
//安装成功
![](https://img.haomeiwen.com/i7264030/602e12f8e7e246f8.png)
springboot + redis
1.添加依赖
//pom.xml
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
2.配置application.properties
#port
server.port=8088
# reids
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址 (默认为127.0.0.1)
spring.redis.host=127.0.0.1
# Redis服务器连接端口 (默认为6379)
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接超时时间(毫秒)
spring.redis.timeout=2000ms
创建User.java
import lombok.Data;
import java.io.Serializable;
//注意一定要实现serializable接口。
//任何存储都需要序列化。只不过常规你在用DB一类存储的时候,这个事情DB帮你在内部搞定了
//(直接把SQL带有类型的数据转换成内部序列化的格式,存储;读取时再解析出来)
//而Redis并不会帮你做这个事情。当你用Redis的key和value时,value对于redis来讲就是个byte array
//你要自己负责把你的数据结构转换成byte array,等读取时再读出来。
@Data
public class User implements Serializable {
private String username;
private String password;
private String nickname;
private String age;
private String gender;
}
创建ObjectRedisSerializer.java(序列化和反序列化工具类)
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
//此处我们使用spring的SerializingConverter和DeserializingConverter类来实
//现java对象的序列化和反序列化,这两个类实现了Converter接口。
//使用这个类相比getBytes()方法的好处是可以将大的对象也可以序列化,性能相比getBytes()做了优化。
public class ObjectRedisSerializer implements RedisSerializer<Object> {
/**
* 定义序列化和反序列化转化类
*/
private Converter<Object, byte[]> serializer = new SerializingConverter();
private Converter<byte[], Object> deserializer = new DeserializingConverter();
/**
* 定义转换空字节数组
*/
private static final byte[] EMPTY_ARRAY = new byte[0];
@Override
public byte[] serialize(Object obj) throws SerializationException {
byte[] byteArray = null;
if (null == obj) {
System.err.println("Redis待序列化的对象为空.");
byteArray = EMPTY_ARRAY;
} else {
try {
byteArray = serializer.convert(obj);
} catch (Exception e) {
System.err.println("Redis序列化对象失败,异常:"+e.getMessage());
byteArray = EMPTY_ARRAY;
}
}
return byteArray;
}
@Override
public Object deserialize(byte[] bytes) throws SerializationException {
Object obj = null;
if((null == bytes)|| (bytes.length == 0)){
System.out.println("Redis待反序列化的对象为空.");
}else{
try {
obj = deserializer.convert(bytes);
} catch (Exception e) {
System.out.println("Redis反序列化对象失败,异常:"+e.getMessage());
}
}
return obj;
}
}
创建RedisConfig.java(redis的springboot配置类)
import com.example.redis.util.ObjectRedisSerializer;
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.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Serializable, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Serializable, Object> template = new RedisTemplate<Serializable, Object>();
template.setConnectionFactory(connectionFactory);
template.afterPropertiesSet();
// redis存取对象的关键配置
template.setKeySerializer(new StringRedisSerializer());
// ObjectRedisSerializer类为java对象的序列化和反序列化工具类
template.setValueSerializer(new ObjectRedisSerializer());
return template;
}
}
创建Controller(测试)
@RestController
public class Controller {
@Autowired
private RedisTemplate<Serializable, Object> redisTemplate;
@RequestMapping("/set")
public String setUser(){
User user = new User();
user.setAge("24");
user.setGender("男");
user.setNickname("pengsha");
user.setPassword("123456");
user.setUsername("zouzhiming");
redisTemplate.opsForValue().set("zhimingzou", user);
return "存储对象成功";
}
@RequestMapping("/get")
public Object getUser(){
return redisTemplate.opsForValue().get("zhimingzou");
}
}
![](https://img.haomeiwen.com/i7264030/a0a1b76a5e455e31.png)
http://localhost:8088/get
![](https://img.haomeiwen.com/i7264030/b8c467dff99a47f2.png)
网友评论