redis与SpringBoot的集成及原理

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。


版权声明:本文为weixin_45989389原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。