redis与SpringBoot的集成及原理
1.概述
redis是一款非关系型数据库,是一款开源的Key-Value数据库,运行在内存中,由ANSI C编写。企业开发通常采用Redis来实现缓存。redis支持的5款数据类型有如,字符串(strings), 散列(hashes), 列 表 ( lists ) , 集 合 ( sets ) , 有 序 集 合 ( sorted set)。
传统的数据库缺点:
1.磁盘 I/O 是并发的瓶颈(内存的运行速度远远超过磁盘)
2.海量数据查询效率低 横向扩展困难,无法简单的通过添加硬件和服务节点来扩展性能和负载能力,当需要对数据库进行升级和扩展时,需要停机维护和数据迁移
3. 多表的关联查询以及复杂的数据分析类型的复杂 sq 查询,性能欠佳。因为要 保证 ACID.
**为了优化这些问题引入非关系型数据库**
1.非关系型数据库常常在内存中运行,运行速度快
2.分布式,易于扩招
但非关系型数据库也在某些方面不足
1.只适合存储一些较为简单的数据
2.不适合复杂查询的数据
3.不适合持久存储海量数据
所以在开发中我们常用非关系型数据库和关系型数据库结合
2.springBoot集成redis以及使用
1.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.建立配置类让解除中文乱码
``
package com.item.yuan.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
/**
* 序列化键,值
* @param connectionFactory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
StringRedisSerializer redisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
redisTemplate.setHashKeySerializer(redisSerializer);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}
}
注意:在如果存入的值时自定义的实体类时应实现Serializable接口
例如:
``
package com.item.yuan.beans;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
import java.io.Serializable;
/**
* @program: yuan
* @ClassName User
* @description:
* @author: Mr.Yuan
* @create: 2021-07-07 11:24
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
@ApiModel(value = "用户实体类",description = "用于封装用户信息")
public class User implements Serializable {
//如果可以根据序列号找到这个类
private static final long serialVersionUID = 4987896076643238099L;
@ApiModelProperty(value = "用户主键" )
private Integer id;
@ApiModelProperty(value = "用户账号")
private Integer account;
private String password;
private Integer status;
}
在使用redis时直接注入RedisTemplate
Redis的5种形式的储存
1.字符串型
`public class TestValue {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void setValue(){
//以键值对的形式存值
redisTemplate.boundValueOps(“name”).set(“yuan”);
}
@Test
public void getName() {
String name=(String) redisTemplate.boundValueOps("name").get();
System.out.println(name);
}
@Test
public void deleteValue(){
redisTemplate.delete("name");
}
}`
2.set型
`public class TestSet {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void setValue(){
redisTemplate.boundSetOps(“nameset”).add(“zhang”);
redisTemplate.boundSetOps(“nameset”).add(“ding”);
redisTemplate.boundSetOps(“nameset”).add(“yuan”);
}
@Test
public void getValue(){
Set set=redisTemplate.boundSetOps("nameset").members();
System.out.println(set);
}
//删除set集合的一个元素
@Test
public void removeValue(){
redisTemplate.boundSetOps("nameset").remove("yuan");
}
//删除set集合所有元素
@Test
public void removeAll(){
redisTemplate.delete("nameset");
}
}`
3.list型
`public class TestList {
@Autowired
private RedisTemplate redisTemplate;
//右压栈
@Test
public void testRightSetValue(){
redisTemplate.boundListOps("nameRightList").rightPush("zhang");
redisTemplate.boundListOps("nameRightList").rightPush("ding");
redisTemplate.boundListOps("nameRightList").rightPush("yuan");
}
//左压栈
@Test
public void testLeftSetValue(){
redisTemplate.boundListOps("nameLeftList").leftPush("zhang");
redisTemplate.boundListOps("nameLeftList").leftPush("ding");
redisTemplate.boundListOps("nameLeftList").leftPush("yuan");
}
@Test
public void testGetValue(){
//显示右压栈
//List list=redisTemplate.boundListOps("nameRightList").range(0, 2);
//System.out.println(list);
//显示左压栈
List list=redisTemplate.boundListOps("nameLeftList").range(0, 2);
System.out.println(list);
}
//查询某一个元素按照索引位置
@Test
public void searchByIndex(){
String string=(String) redisTemplate.boundListOps("nameLeftList").index(1);
System.out.println(string);
}
//删除
@Test
public void removeValue(){
//删掉两个张
redisTemplate.boundListOps("nameRightList").remove(2, "zhang");
}
//删除全部
@Test
public void removeAll(){
redisTemplate.delete("nameRightList");
redisTemplate.delete("nameLeftList");
}
}`
4.map型
`public class TestHash {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testSetValue(){
//两个key
TbPayLog payLog=new TbPayLog();
redisTemplate.boundHashOps("payLog").put("kouzhu", payLog);
}
//找到所有的小key
@Test
public void testGetKeys(){
Set keys=redisTemplate.boundHashOps("seckillGoods").keys();
System.out.println(keys);
}
//获取所有值
@Test
public void testGetValues(){
List list=redisTemplate.boundHashOps("seckillGoods").values();
System.out.println(list);
}
@Test
//通过键获取值
public void searchValueByKey(){
List<Cart> list=(List<Cart>) redisTemplate.boundHashOps("cartList").get("kouzhu");
System.out.println(list);
System.out.println( redisTemplate.boundHashOps("payLog").get("kouzhu"));
}
//通过小key删除
@Test
public void removeValueByKey(){
redisTemplate.boundHashOps("smsCode").delete("code");
}
@Test
//删除大key的值
public void removeAll() {
redisTemplate.delete("seckillGoods");
}
}`
在开发中使用redis的逻辑时
1.查询数据的时,先去redis里查如果redis查询到了直接返回,没查询到则去数据库查询,数据库查完存入redis,
2.若对数据库进行增删改操作则操作完数据库需要对redis进行操作
3.主从复制
主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。 前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能 由主节点到从节点。
使用一个 Redis 实例作为主机,其余的作为备份机。主机和备份机的数据完 全一致,主机支持数据的写入和读取等各项操作,而从机则只支持与主机数据的 同步和读取。也就是说,客户端可以将数据写入到主机,由主机自动将数据的写 入操作同步到从机。主从模式很好的解决了数据备份问题,并且由于主从服务数 据几乎是一致的,因而可以将写入数据的命令发送给主机执行,而读取数据的命 令发送给不同的从机执行,从而达到读写分离的目的。
主从复制的作用主要包括:
1.数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
2.故障恢复:当主节点出现问题后可以由从节点提供服务
3.负载均衡:由主节点提供写的服务,写完后复制到从节点,多个从节点提供读的服务,多个从节点分担读的负载,可提高redis服务器的并发量
4.高可用(集群)基石:是哨兵机制能够实施的基础
4.哨兵机制
哨兵在redis中起监控的作用,监控redis的主从节点,当访问量很多使主节点崩掉时,哨兵会使从节点代替主节点,从而使系统在短时间内不会崩溃
5.redis中的可能出现的问题及其解决方案
1.缓存穿透
key 对应的数据在数据库中并不存在,每次针对此 key 的请求从缓存获取不 到,请求都会到数据库,从而可能压垮数据库。比如用一个不存在的用户 id 获 取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮 数据库。
解决方案:
1. 使用布隆过滤器或者压缩 filter 提前拦截.
2.将这个空对象设置到缓存里边去。下次再请求的时候,就可以从缓存里边获取 了。这种情况我们一般会将空对象设置一个较短的过期时间.
3.拉黑该 IP 地址.
4.对参数进行校验,不合法参数进行拦截
2.缓存击穿
缓存中存在某个key,但就在访问时这个key过期了被销毁了,大量的key请求过期,同时一起访问数据库,数据库是磁盘io,读写速度较为慢,可能会压跨数据库,例如:大量数据设置的销毁实践一致,同时失效,访问时会造成数据库压力。
解决方案:
1.设置销毁实践时用Math.Radom设置随机时间,使得key的销毁时间各不相同,减小数据库压力
2.热点数据永不过期
3.缓存雪崩·
特别高的并发一起访问缓存,而缓存的数据又大部分失效,使得缓存层崩塌,所有数据一起访问数据库,造成数据库瘫痪。
解决方案:
1.搭建redis集群进行负载均衡
2.定期清理缓存
6.redis如何处理过期数据
在当前的大数据时代访问量及其之大
如果当数据一失效就去清理。那么当数据失效则需要cpu,而cpu一直被占用,会产生很多问题
所以在redis里有了解决数据过期的办法
Redis keys过期有两种方式:被动和主动方式。
当一些客户端尝试访问它时,key会被发现并主动的过期。当然,这样是不够的,因为有些过期的keys,永远不会访问他们。 无论如何,这些keys应该过期,所以定时随机测试设置keys的过期时间。所有这些过期的keys将会从密钥空间删除。
具体就是Redis每秒10次做的事情:
1.测试随机的20个keys进行相关过期检测。
2.删除所有已经过期的keys。
3.如果有多于25%的keys过期,重复步奏1.
这是一个平凡的概率算法,基本上的假设是,我们的样本是这个密钥控件,并且我们不断重复过期检测,直到过期的keys的百分
百低于25%,这意味着,在任何给定的时刻,最多会清除1/4的过期keys。