栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Redis入门,手把手学习笔记,超详细

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Redis入门,手把手学习笔记,超详细

文章目录

Redis入门

1.学习目标2.Redis的介绍与安装

2.1.Redis是什么?2.2.性能2.3.Redis历史简介2.4.支持语言2.5.支持的数据类型2.6.安装

2.6.1.下载地址2.6.2.安装至服务器

Xftp上传安装Linux命令安装 2.6.3.redis后台运行2.6.4.安装RDM可视化客户端 3.关系型数据库与非关系型数据库

3.1.关系型数据库

3.1.1.优点3.1.2.缺点3.1.3.数据库 3.2.非关系型数据库

3.2.1.优点3.2.2.数据库 3.3.比较 4.Redis-cli操作Redis

4.1.Redis-cli连接Redis4.2.Redis 字符串(String)

4.2.1.存一个值4.2.2.存多个值和取多个值 4.3.Redis 哈希(Hash)

4.3.1.存取单个hash4.3.2.存取多个hash4.3.3.取整个redis下的hash4.3.4.删除操作 4.4.Redis 列表(List)

4.4.1.左添加4.4.2.右添加4.4.3.查询长度4.4.4.删除 4.5.Redis 集合(Set)

4.5.1.添加4.5.2.获取4.5.3.查询长度4.5.4.删除 4.6.Redis 有序集合(sorted set)

4.6.1.增删改查 4.7.Redis-cli的通用命令

4.7.1.设置key的失效时间4.7.2.删除 5.Java操作redis

5.1.新建项目

5.1.1.IDEA新建项目5.1.2.pom.xml配置5.1.3.application.yml配置文件5.1.4.RedisConfig配置类5.1.5.测试连接5.1.6.Jedis操作string数据类型5.1.7.Jedis操作Hash数据类型5.1.8.Jedis操作List数据类型5.1.9.Jedis操作Set数据类型5.1.10.Jedis操作SortedSet数据类型5.1.11.层级目录+失效时间5.1.12.获取所有的key+事务5.1.13.Jedis获取操作byte数组5.1.14.redis持久化方案

BGSAVE机制RDB持久化AOF持久化 6.Redis主从复用

6.1.读写分离

6.1.1.启动6.1.2.进入客户端6.1.3.主从状态查看6.1.4.读写操作 6.2.哨兵配置

6.2.1.简介6.2.2.哨兵的安装6.2.3.主备切换 7.SpringDataRedis

7.1.创建项目

7.1.1.新建项目7.1.2.pom.xml配置文件7.1.3.application.yml配置文件7.1.4.测试连接 7.2.SpringDataRedis序列化模板

7.2.1.自定义模板解决序列化问题

RedisConfig配置类User实体类测试

SpringDataRedis操作string数据类型SpringDataRedis操作hash数据类型SpringDataRedis操作listSpringDataRedis操作set数据类型SpringDataRedis操作sortedset数据类型SpringData获取所有key+设置key失效时间SpringData整合哨兵模式 8.如何应对缓存穿透、缓存击穿、缓存雪崩问题

8.1.Key的过期淘汰机制

8.1.1.定期删除8.1.2.惰性删除8.1.3.内存淘汰机制 8.2.缓存击穿

8.2.1.定义8.2.2.解决方案 8.3.缓存穿透

8.3.1.定义8.3.2.解决方案: 8.4.缓存雪崩

8.4.1.定义8.4.2.解决方案

定期删除惰性删除内存淘汰机制

Redis入门 1.学习目标

2.Redis的介绍与安装 2.1.Redis是什么?

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

2.2.性能

下面是官方的bonch-mark数据:

测试完成了50个并发执行100000个请求。

设置和获取的值是一个256字节字符串。

结果:读的速度是110000次/s,写的速度是81000次/s

2.3.Redis历史简介

2008年,意大利一家创业公司Merzia的创始人Salvatore Sanfilippo为了避免MySQL的低性能,亲自定做一个数据库,并于2009年开发完成,这个就是Redis。

从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。

从2013年5月开始,Redis的开发由Pivotal赞助。

说明: Pivotal公司是由EMC和VMware联合成立的一家新公司。Pivotal希望为新一代的应用提供一个原生的基础,建立在具有领导力的云和网络公司不断转型的IT特性之上。Pivotal的使命是推行这些创新,提供给企业IT架构师和独立软件提供商。

2.4.支持语言

2.5.支持的数据类型

string、hash、list、set、sorted set

2.6.安装 2.6.1.下载地址

http://redis.io/

2.6.2.安装至服务器 Xftp上传安装

可以用xftp把下载的安装包上传导服务器,然后解压。

Linux命令安装

下载

mkdir redisfile
cd redisfile/
wget http://download.redis.io/releases/redis-5.0.3.tar.gz

解压

tar -zxvf redis-5.0.3.tar.gz

安装

cd redis-5.0.3
make
mkdir -p /usr/local/redis
make PREFIX=/usr/local/redis/ install

如果报错了,可能是少了C语言编译环境,因为redis是用C语言编写的。

配置C语言环境

yum -y install gcc-c++ automake autoconf

启动服务器

cd /usr/local/redis/bin
./redis-server

为了更方便操作,可以把redis改成后台运行

2.6.3.redis后台运行
cd redis-5.0.3
cp redis.conf /usr/local/redis/bin/
cd /usr/local/redis/bin/
vim redis.conf
#  找到daemonize no,把no改成yes

启动

./redis-server redis.conf

查看是否启动

# 进入redis-cli客户端
./redis-cli
ping
set name zhangsan
get name
# 有返回值说明启动成功
2.6.4.安装RDM可视化客户端

教程:https://blog.csdn.net/baidu_35692846/article/details/118399499

安装完后测试连接

连接不上,说明只允许本机连接,这时候需要修改conf

vim redis.conf

可以加上:bind 允许访问的ip(工作时的操作)

或者直接在bind 127.0.0.1前面加个#注释掉(学习时的操作)

然后把protected mode 保护模式的yes改成no

然后保存并退出:按esc,输入:wq,按回车。

杀掉redis-server进程

# 查看进程
ps -ef|grep redis
# 杀掉redis-server进程,注意,端口不一定是6588
kill -9 6588

启动

./redis-server redis.conf

客户端尝试连接,如果还是无法连接则关闭防火墙

systemctl stop firewalld.service

没有密码不太安全,所以需要加上密码

vim redis.conf

找到requirepass ,然后取消注释,foobared就是密码,可以更改

# 查端口
ps aux|grep redis
# 杀进程
kill -9 进程占用的端口号
# 再次启动
./redis-server redis.conf

这时候我们已经连上了redis,我们发现有16个数据库,如果想增加可以据需更改conf文件,找到databases修改

修改完我们要在客户端删除原来的连接,然后重启redis,然后重新连接。
3.关系型数据库与非关系型数据库 3.1.关系型数据库

采用关系模型来组织数据的数据库,关系模型就是二维表格模型。一张二维表的表名就是关系,二维表中的一行就是—条记录,二维表中的一列就是一个字段。

3.1.1.优点

容易理解使用方便,通用的sql语言易于维护,丰富的完整性(实体完整性、参照完整性和用户定义的完整性)大大降低了数据冗余和数据不一致的
概率 3.1.2.缺点

磁盘I/O是并发的瓶颈海量数据查询效率低横向扩展困难,无法简单的通过添加硬件和服务节点来扩展性能和负载能力,当需要对数据库进行升级和扩展时,需要停机维护和数据迁移多表的关联查询以及复杂的数据分析类型的复杂sql查询,性能欠佳。因为要保证acid,必须按照三范式设计。
数据库 3.1.3.数据库

OrcaleSql ServerMySqlDB2 3.2.非关系型数据库

非关系型,分布式,一般不保证遵循ACID原则的数据存储系统。键值对存储,结构不固定。

3.2.1.优点

根据需要添加字段,不需要多表联查。仅需id取出对应的value适用于SNS(社会化网络服务软件。比如facebook,微博)严格上讲不是一种数据库,而是—种数据结构化存储方法的集合缺点只适合存储一些较为简单的数据。不合适复杂查询的数据不合适持久荏储海量数据 3.2.2.数据库

K-V: Redis,Memcache文档:MongoDB搜索:Elasticsearch,Solr可扩展性分布式:Hbase 3.3.比较

内容关系型数据库非关系型数据库
成本有些需要收费(Orcale)基本都是开源
查询数据存储存于硬盘中,速度慢数据存于缓存中,速度快
存储格式只支持基础类型K-V,文档,图片等
扩展性有多表查询机制,扩展困难数据之间没有耦合,容易扩展
持久性适用持久存储,海量存储不适用持久存储,海量存储
数据一致性事务能力强,强调数据的强—致性事务能力弱,强调数据的最终—致性
4.Redis-cli操作Redis 4.1.Redis-cli连接Redis

先开redis服务器

cd /usr/local/redis/bin/
./redis-server redis.conf

redis-clli连接redis

# 最后一个是redis的密码	
./redis-cli -p 6379 -a password
4.2.Redis 字符串(String)

Redis 字符串数据类型的相关命令用于管理 redis 字符串值,

4.2.1.存一个值
#字段名为name,值为zhangsan
set name zhangsan
# 取名为name的字段
get name

4.2.2.存多个值和取多个值
# 存多组值
mset sex 1 address shanghai
# 取多组值
mget name sex address

4.3.Redis 哈希(Hash)

Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。

Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。

4.3.1.存取单个hash
#这里的user是redis的key,name是hash的key,zhangsan是hash的value
hset user name zhangsan
# 取值
hget user name

4.3.2.存取多个hash
# 存
hmset user age 18 sex 1
# 取
hmget user name age sex

4.3.3.取整个redis下的hash
# 取key为user的redis的hash值
hgetall user

4.3.4.删除操作
# 删除user下的name和age
hdel user name age

4.4.Redis 列表(List)

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

4.4.1.左添加
# 添加 lis在前zhangsan在后,因为是左添加,每次添加都从左边开始
lpush students zhjangsan lis
# 取
lrange students 0 2

4.4.2.右添加
# wwangwu在前zhaoliu在后,因为是右添加,每次添加都从右边开始
rpush students wangwu zhaoliu
# 取
lrange students 0 3

4.4.3.查询长度
# 查students的长度
llen students

4.4.4.删除
# 删除名为students的list的2条lisi(从左往右删)
lrem students 2 lisi

4.5.Redis 集合(Set)

Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

集合对象的编码可以是 intset 或者 hashtable。

Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

4.5.1.添加
# 添加名为letters的set,值分别为aaa、bbb...eee
sadd letters aaa bbb ccc ddd eee
4.5.2.获取
# 查询名为letters的set的数据
smembers letters

4.5.3.查询长度
# 查询名为letters的set的长度
scard letters
4.5.4.删除
# 删除letters的aaa,ccc
srem letters aaa ccc
4.6.Redis 有序集合(sorted set)

Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。

有序集合的成员是唯一的,但分数(score)却可以重复。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 232 - 1(4294967295, 每个集合可存储40多亿个成员)。

4.6.1.增删改查
# score为名称
zadd score 7 zhangsan 3 si 6 wangwu 10 zhaoliu 1 tianqi
# 查值
zrange score
# 删除score下的zhangsan,lisi
zrem score zhangsan lisi
# 查长度
zcard score

4.7.Redis-cli的通用命令
set cart:user01:item01 apple
get cart:user01:item01

RDM客户端的显示

4.7.1.设置key的失效时间

Redis有四个不同的命令可以用于设置键的生存时间(键可以存在多久)或过期时间(键什么时候会被删除):

EXP1RE :用于将键key的生存时间设置为tt1秒。

PEXPIRE :用于将键key的生存时间设置为tt1毫秒。

EXPIREAT < timestamp>:用于将键key的过期时间设置为timestamp所指定的秒数时间戳。

PEXPIREAT :用于将键key的过期时间设置为timestamp所指定的毫秒数时间戳

TTL:获取的值为-1说明此key没有设置有效期,当值为-2时证明过了有效期。

方法一

# 设置个失效时间为10秒的code,值为test
set code test ex 10
# 10s内可以取到
get code
# 查看是否过了失效期
ttl code
# 10s后失效了
get code

方法2

# 10000毫秒失效,最后一个参数,xx表示如果code存在就会设置成功
set code test px 10000 xx
# 最后一个参数,nx表示如果code不存在就会设置成功
set code test px 10000 nx

4.7.2.删除
# 这里的address就是想删除的key
del address

5.Java操作redis 5.1.新建项目 5.1.1.IDEA新建项目


5.1.2.pom.xml配置


    4.0.0
    com.xmjj
    redisdemo
    0.0.1-SNAPSHOT
    redisdemo
    Demo project for Spring Boot

    
        1.8
        UTF-8
        UTF-8
        2.3.7.RELEASE
    
    
        

            org.springframework.boot
            spring-boot-starter-data-redis


            
                
                    io.lettuce
                    lettuce-core
                
            
        

        
            redis.clients
            jedis
        

        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.junit.vintage
                    junit-vintage-engine
                
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-dependencies
                ${spring-boot.version}
                pom
                import
            
        
    


5.1.3.application.yml配置文件
# 应用名称
spring:
  redis:
    #Redis服务器地址
    host: 你的Redis服务器地址
    #Redis服务器姗口
    port: 6379
    #Redis服务器密码
    password: root
    #选择哪个库,默认e库
    database: 0
    #连接超时时间
    timeout: 10000ms
    jedis:
      pool:
        #最大连接数,默认8
        max-active: 1024
        #最大连接阻塞等待时间,单位毫秒,默认-1ms
        max-wait: 10000ms
        #最大空闲连接,默认8
        max-idle: 200
        #最小空闲连接,默认e
        min-idle: 5
5.1.4.RedisConfig配置类
package com.xmjj.redisdemo.cofig;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;


@Configuration
public class RedisConfig {

    //服务器地址
    @Value("${spring.redis.host}")
    private String host;

    //服务器端口
    @Value("${spring.redis.port}")
    private int port;

    //服务器密码
    @Value("${spring.redis.password}")
    private String password;

    //连接超时时间
    @Value("${spring.redis.timeout}")
    private String timeout;

    //最大连接数
    @Value("${spring.redis.jedis.popl.max-active}")
    private int maxTotal;

    //最大连接阻塞等待时间,单位毫秒,默认-1ms
    @Value("${spring.redis.jedis.popl.max-wait}")
    private String maxWaitMillis;

    //最大空闲连接
    @Value("${spring.redis.jedis.popl.max-idle}")
    private int maxIdle;

    //最小空闲连接
    @Value("${spring.redis.jedis.popl.min-idle}")
    private int minIdle;

    @Bean
    public JedisPool getJedisPool(){
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        //最大连接数
        jedisPoolConfig.setMaxTotal(maxTotal);
        //最大阻塞时间
        jedisPoolConfig.setMaxWaitMillis(Long.valueOf(maxWaitMillis.substring(0,maxWaitMillis.length()-2)));
        //最大空闲连接
        jedisPoolConfig.setMaxIdle(maxIdle);
        //最小空闲连接
        jedisPoolConfig.setMinIdle(minIdle);

        JedisPool jedisPool = new JedisPool(jedisPoolConfig,host,port,Integer.valueOf(timeout.substring(0,timeout.length()-2)));
        return jedisPool;

    }
}

5.1.5.测试连接
package com.xmjj.redisdemo;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.List;

@SpringBootTest
@RunWith(SpringRunner.class)
public class RedisdemoApplicationTests {

//    @Test
//    public void initConnt() {
//        //创建Jedis对象,连接redis服务器
//        Jedis jedis = new Jedis("192.168.77.134", 6379);
//        //设置密码
//        jedis.auth("root");
//        //指定数据库,默认为0
//        jedis.select(1);
//        //使用ping命令测试连接是否成功
//        String result = jedis.ping();
//        System.out.println(result);
//        //添加一条数据
//        jedis.set("name", "zhangsan");
//        //获取数据
//        String name = jedis.get("name");
//        System.out.println(name);
//        //释放资源
//        if (jedis != null) {
//            jedis.close();
//        }
//    }
//    @Test
//    public void initConnt2(){
//        //初始化redis连接对象
//        JedisPool jedisPool = new JedisPool(new JedisPoolConfig(),"192.168.77.134",6379,10000,"root");
//        //从连接池获取jedis对象
//        Jedis jedis = jedisPool.getResource();
//        //指定数据库,默认为0
//        jedis.select(2);
//        //使用pingml测试连接是否成功
//        String result = jedis.ping();
//        System.out.println(result);
//        //添加数据
//        jedis.set("name","zhangsan");
//        //读取数据
//        String name = jedis.get("name");
//        System.out.println(name);
//        //释放资源
//        if(jedis!=null){
//            jedis.close();
//        }
//    }

    @Autowired
    private JedisPool jedisPool;
    private Jedis jedis = null;

    @Before
    public void initConnt(){
        try {
            jedis = jedisPool.getResource();
        }catch (Exception e){
            System.out.println(e);
        }
    }

    @Test
    public void testString(){
        //使用ping,测试是否正常连接
        String ping = jedis.ping();
        System.out.println(ping);
        //添加一条数据
        jedis.set("name","zhangsan");
        //获取一条数据
        String name = jedis.get("name");
        System.out.println(name);
        //添加多条数据
        jedis.mset("address","sh","sex","1");
        //获取多条数据、参数的奇数位是key,偶数位是value
        List list = jedis.mget("address","name","sex");
        list.forEach(System.out::println);
        //删除
        jedis.del("name");

    }

    //释放资源
    @After
    public void closeConnt(){
        if(null!=jedis){
            jedis.close();
        }
    }
}

5.1.6.Jedis操作string数据类型
   public void testString(){
        //使用ping,测试是否正常连接
        String ping = jedis.ping();
        System.out.println(ping);
        //添加一条数据
        jedis.set("name","zhangsan");
        //获取一条数据
        String name = jedis.get("name");
        System.out.println(name);
        //添加多条数据
        jedis.mset("address","sh","sex","1");
        //获取多条数据、参数的奇数位是key,偶数位是value
        List list = jedis.mget("address","name","sex");
        list.forEach(System.out::println);
        //删除
        jedis.del("name");

    }

5.1.7.Jedis操作Hash数据类型
@Test
    public void testHash(){
        
        jedis.hset("user","name","zhangsan");
        
        String name = jedis.hget("user", "name");
        System.out.println(name);
        
        Map map = new HashMap<>();
        map.put("age","20");
        map.put("sex","1");
        jedis.hmset("user",map);
        
        List list = jedis.hmget("user", "age", "sex");
        list.forEach(System.out::println);
        
        Map user = jedis.hgetAll("user");
        user.entrySet().forEach(e->{
            System.out.println(e.getKey()+"----->"+e.getValue());
        });
        
        jedis.hdel("user","name","sex");

    }

5.1.8.Jedis操作List数据类型
 @Test
    public void testList(){
        //左添加
        jedis.lpush("students","zhangsan","lisi");
        //右添加
        jedis.rpush("students","wangwu","zhaoliu");
        
        List students = jedis.lrange("students", 0, 3);
        students.forEach(System.out::println);
        //获取总套数
        Long total = jedis.llen("students");
        System.out.println(total);
        
        jedis.lrem("students",1,"lisi");
        //获取总套数
        total = jedis.llen("students");
        System.out.println(total);
        //左弹出
        System.out.println(jedis.lpop("students"));
        //右弹出
        System.out.println(jedis.rpop("students"));
        //获取总套数
        total = jedis.llen("students");
        System.out.println(total);
    }

5.1.9.Jedis操作Set数据类型
    @Test
    public void testSet(){
        //添加数据
        jedis.sadd("letters","aaa","bbb","ccc","ddd","eee");
        //获取数据
        Set set = jedis.smembers("letters");
        set.forEach(System.out::println);
        //获取总条数
        Long total = jedis.scard("letters");
        //删除数据
        jedis.srem("letters","aaa","ccc");
        System.out.println(jedis.scard("letters"));
    }

5.1.10.Jedis操作SortedSet数据类型
 @Test
    public void testSortedSet(){
        //添加数据
        Map map = new HashMap<>();
        map.put("zhangsan",7D);
        map.put("lisi",3D);
        map.put("wangwu",5D);
        map.put("zhaoliu",6D);
        map.put("tianqi",1D);
        jedis.zadd("score",map);
        
        Set set = jedis.zrange("score", 0, 4);
        set.forEach(System.out::println);
        //获取总条数
        Long total = jedis.zcard("score");
        System.out.println(total);
        //删除
        jedis.zrem("score","zhangsan","wangwu");
        System.out.println(jedis.zcard("score"));
    }

5.1.11.层级目录+失效时间

层级目录

    @Test
    public void testDDir(){
        jedis.set("cart:user01:item01","apple");
        System.out.println(jedis.get("cart:user01:item01"));
    }

失效时间

    @Test
    public void testExpire(){
        //给已经存在在key设置失效时间
//        jedis.set("code","test");
        //设置失效时间,单位秒
//        jedis.expire("code",30);
        //设置失效时间,单位毫秒
//        jedis.pexpire("code",30000);
//        //查看失效时间,-1为未失效,-2为已失效
//        Long ttl = jedis.ttl("code");
//        System.out.println(ttl);
        //设置失效时间,单位秒
        jedis.setex("code",30,"test");
        //设置失效时间,单位毫秒
        jedis.psetex("code",30000,"test");
        //查看失效时间,单位毫秒
        Long pttl = jedis.pttl("code");
        System.out.println(pttl);

//        SetParams setParams = new SetParams();
//        //不存在则设置成功
//        setParams.nx();
//        //存在你则设置成功
        setParams.xx();
//        //设置失效时间,单位秒
        setParams.ex(30);
//        //设置失效时间,单位毫秒
//        setParams.px(30000);
//        jedis.set("code","test",setParams);

 }

5.1.12.获取所有的key+事务

获取key

    @Test
    public void testAllKey(){
        //当前数据库key的数量
        Long size = jedis.dbSize();
        System.out.println(size);
        //查询当前数据库所有的key
        Set set = jedis.keys("*");
        set.forEach(System.out::println);
    }

	

事务

    @Test
    public void testMulti(){
        //开启事务
        Transaction tx = jedis.multi();
        tx.set("tel","10086");
        //提交事务
        tx.exec();
        //回滚事务
//        tx.discard();
    }
5.1.13.Jedis获取操作byte数组

系列化工具类

package com.xmjj.redisdemo.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;



public class SerializeUtil {

    
    public static byte[] serialize(Object object) {
        ObjectOutputStream oos = null;
        ByteArrayOutputStream baos = null;
        try {
            // 序列化
            baos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(baos);
            oos.writeObject(object);
            byte[] bytes = baos.toByteArray();
            return bytes;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    
    public static Object unserialize(byte[] bytes) {
        ByteArrayInputStream bais = null;
        try {
            // 反序列化
            bais = new ByteArrayInputStream(bytes);
            ObjectInputStream ois = new ObjectInputStream(bais);
            return ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

User实体类

package com.xmjj.redisdemo.pojo;

import java.io.Serializable;


public class User implements Serializable {

    private static final long serivalVersionUID = 9148937431079191022L;
    private Integer id;

    public User() {
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + ''' +
                ", password='" + password + ''' +
                '}';
    }

    public static long getSerivalVersionUID() {
        return serivalVersionUID;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public User(Integer id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    private String username;
    private String password;

}

测试

    @Test
    public void testByte(){
        User user = new User();
        user.setId(2);
        user.setUsername("zhangsan");
        user.setPassword("123456");
        //序列化为byte数组
        byte[] userKey = SerializeUtil.serialize("user:" + user.getId());
        byte[] userValue = SerializeUtil.serialize(user);
//        存入redis
        jedis.set(userKey,userValue);
        //取出
        byte[] bytes = jedis.get(userKey);
        //反序列化
        User user1 = (User) SerializeUtil.unserialize(bytes);
        System.out.println(user1);
    }

5.1.14.redis持久化方案

redis作为一个内存数据库,最担心的,就是万一机器死机宕机,数据就会消失掉,为了缓解这个问题,我们有三个方案。

BGSAVE机制
# 在后台异步保存当前数据库的数据到磁盘。
bgsave

优点:简单,缺点:繁琐


Redis 提供了 RDB 和 AOF 两种持久化方案,将内存中的数据自动保存到磁盘中

AOF持久化以独立日志的方式记录每次写命令,并在 Redis 重启时在重新执行。AOF 文件中的命令以达到恢复数据的目的。AOF 的主要作用是解决数据持久化的实时性
RDB把当前 Redis 进程的数据生成时间点快照( point-in-time snapshot ) 保存到存储设备的过程。
RDB持久化

RDB是Redis用来进行持久化的一种方式,是把当前内存中的数据集快照写入磁盘,也就是 Snapshot 快照(数据库中所有键值对数据)。恢复时是将快照文件直接读到内存里。

RDB 有两种触发方式,分别是自动触发和手动触发。

进入redis.conf配置文件,修改save

save 900 1:表示900 秒内如果至少有 1 个 key 的值变化,则保存
save 300 10:表示300 秒内如果至少有 10 个 key 的值变化,则保存
save 60 10000:表示60 秒内如果至少有 10000 个 key 的值变化,则保存

修改完保存重启即可。

AOF持久化
AOF通过4点实现持久化
写入缓存每次执行命令后,进行append操作写入AOF缓存
同步磁盘AOF 缓冲区根据对应的策略向硬盘进行同步操作
AOF重写随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的
重启加载当 Redis 重启时,可以加载 AOF 文件进行数据恢复

进入redis.conf配置文件,修改appendonly,no改成yes

重启 Redis 之后就会进行 AOF 文件的载入。

异常修复命令:

redis-check-aof --fix 
6.Redis主从复用 6.1.读写分离
#引用公共配置
include /opt/redis/conf/redis-common.conf
#进程编号记录文件
pidfile "/var/run/redis-6380.pid"
#进程端口号
port 6380
#日志记录文件
logfile "/opt/redis/log/redis-6380.log"
#数据记录文件
dbfilename "dump-6380.rdb"
#追加文件名称
appendfilename "appendonly-6380.aof"
#下面的配置无需在 6379 里配置
#备份服务器从属于 6379 推荐配置配局域网 IP
slaveof 192.168.40.100 6379
6.1.1.启动
cd /usr/local/redis/bin
./redis-server /opt/redis/conf/redis-6379.conf
./redis-server /opt/redis/conf/redis-6380.conf
./redis-server /opt/redis/conf/redis-6381.conf
6.1.2.进入客户端
#分别在不同会话打开
./redis-cli -p 6379 -a root
./redis-cli -p 6380 -a root
./redis-cli -p 6381 -a root
6.1.3.主从状态查看
info replication

6.1.4.读写操作
主服务器可读可写
从服务器只读

在主服务器设置的值,在从服务器可以读取。

但是从服务器不能写,只能读。

6.2.哨兵配置 6.2.1.简介

当主服务器宕机后,需要手动把一台从服务器切换为主从服务器,这就需要人工干预,既费时费力,还会造成一段时间内服务不可用,这不是一种推荐的方式,因此笔者没有介绍主从切换技术。

更多的时候,我们优先考虑哨兵模式,它是当前企业应用的主流方式。

Redis可以存在多台服务器,并且实现了主从复制的功能。哨兵模式是一种特殊的模式,首先 Redis 提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。

其原理是哨兵通过发送命令,等待 Redis 服务器响应,从而监控运行的多个 Redis 实例

这里的哨兵有两个作用:

通过发送命令,让 Redis 服务器返回监测其运行状态,包括主服务器和从服务器。当哨兵监测到 master 宕机,会自动将 slave 切换成 master,然后通过发布订阅模式通知到其他的从服务器,修改配置文件,让它们切换主机。

6.2.2.哨兵的安装
cd /root/redisfile/redis-5.0.3

# 把sentinel.conf复制到/opt/redis/conf/目录下
cp sentinel.conf /opt/redis/conf/

cd /opt/redis/conf
#改名
mv sentinel.conf sentinel-common.conf
#编辑配置文件
vim sentinel-common.conf

把port注释掉

把daomonize后台启动设置为yes

把pidfile进程文件注释掉

把logfile日志文件注释掉

修改哨兵模式的iip改成redis服务器的ip

主服务器的密码,这里的root是密码

在主服务器30秒没有ping-pong响应时,重新选取

当选取服务器超过180秒都没有超过。那么将放弃此次选取。进行新的选取


touch sentinel-26379.conf
touch sentinel-26380.conf
touch sentinel-26381.conf
#分别配置
vim sentinel-26379.conf
vim sentinel-26380.conf
vim sentinel-26381.conf

sentinel-26379.conf

#引用公共配置
include /opt/redis/conf/sentinel-common.conf
#进程端口号
port 26379
#进程编号记录文件
pidfile /var/run/sentinel-26379.pid
#日志记录文件(为了方便查看日志,先注释掉,搭好环境后再打开)
logfile "/opt/redis/log/sentinel-26379.log"

sentinel-26380.conf

#引用公共配置
include /opt/redis/conf/sentinel-common.conf
#进程端口号
port 26380
#进程编号记录文件
pidfile /var/run/sentinel-26380.pid
#日志记录文件(为了方便查看日志,先注释掉,搭好环境后再打开)
logfile "/opt/redis/log/sentinel-26380.log"

sentinel-26381.conf

#引用公共配置
include /opt/redis/conf/sentinel-common.conf
#进程端口号
port 26381
#进程编号记录文件
pidfile /var/run/sentinel-26381.pid
#日志记录文件(为了方便查看日志,先注释掉,搭好环境后再打开)
logfile "/opt/redis/log/sentinel-26381.log"
6.2.3.主备切换
cd /usr/local/redis/bin
# 启动
./redis-sentinel /opt/redis/conf/sentinel-26379.conf
./redis-sentinel /opt/redis/conf/sentinel-26380.conf
./redis-sentinel /opt/redis/conf/sentinel-26381.conf
# 查看是否启动成功
ps -ef|grep redis

tail -f /opt/redis/log/sentinel-26379.log

将主服务器杀死

ps -ef|grep redis
kill 3113

查看6379的log

# 查看服务器状态
./redis-cli -p 6379 -a root
info replication

重启6379服务器

./redis-server /opt/redis/conf/redis-6379.conf

6379的状态

6380的状态

此时6380是主服务器

在6380设置一个值,在6379可以读取,但是6379设置一个值显示ERROR,因为此时6379是从服务器,6379是只读的状态了。


7.SpringDataRedis

SpringDataRedis是Spring大家族中的一个成员,提供了在srping应用中通过简单的配置访问redis服务,对reids底层开发包(Jedis, JRedis, and RJC)进行了高度封装,RedisTemplate提供了redis各种操作、异常处理及序列化,支持发布订阅,并对spring 3.1 cache进行了实现。 spring-data-redis针对jedis提供了如下功能:

连接池自动管理,提供了一个高度封装的“RedisTemplate”类

针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口

官网:https://spring.io/projects/spring-data-redis

7.1.创建项目 7.1.1.新建项目

7.1.2.pom.xml配置文件


    4.0.0
    com.xmj
    springdataredis-demo
    0.0.1-SNAPSHOT
    springdataredis-demo
    Demo project for Spring Boot

    
        1.8
        UTF-8
        UTF-8
        2.3.7.RELEASE
    

    

        
            org.springframework.boot 
            spring-boot-starter-data-redis
        

        
            org.apache.commons
            commons-pool2
        
        

            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.junit.vintage
                    junit-vintage-engine
                
            
        
    

    
        
            
                org.springframework.boot
                spring-boot-dependencies
                ${spring-boot.version}
                pom
                import
            
        
    

    
        
            
                org.apache.maven.plugins
                maven-compiler-plugin
                3.8.1
                
                    1.8
                    1.8
                    UTF-8
                
            
            
                org.springframework.boot
                spring-boot-maven-plugin
                2.3.7.RELEASE
                
                    com.xmj.springdataredisdemo.SpringdataredisDemoApplication
                
                
                    
                        repackage
                        
                            repackage
                        
                    
                
            
        
    


7.1.3.application.yml配置文件
spring:
  redis:
    # Redis服务器地址
    host: 192.168.77.134
    # Redis服务器端口
    port: 6380
    # Redis服务器密码
    password: root
    # Redis服务器数据库
    database: 0
    # 连接超时时间
    timeout: 10000ms
    lettuce:
      pool:
        #最大连接数
        max-active: 1024
        # #最大连接阻塞等待时间,单位毫秒,默认-1ms
        max-wait: 10000ms
        ##最大空闲连接,默认8
        max-idle: 200
        ##最小空闲连接,默认0
        min-idle: 5
7.1.4.测试连接
package com.xmj.springdataredisdemo;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

@SpringBootTest
class SpringdataredisDemoApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    @Test
    public void initCoon() {
        ValueOperations ops = redisTemplate.opsForValue();
        ops.set("name","zhangsan");
        System.out.println((String) ops.get("name"));
        ValueOperations stringStringValueOperations = stringRedisTemplate.opsForValue();
        stringStringValueOperations.set("age","20");
        String age = stringStringValueOperations.get("age");
        System.out.println(age);
    }
}


7.2.SpringDataRedis序列化模板 7.2.1.自定义模板解决序列化问题

默认情况下的模板RedisTemplate,默认序列化使用的是JdkserializationRedisSerializer,存储二进制字节码。这时需要自定义模板,半自定义模板后又想存储String 字符串时,可以使StringRedisTemplate的方式,他们俩并不冲突。

序列化问题:
要把 domain object做为key-value对保存在redis 中,就必须要解决对象的序列化问题。Spring Data Redis给我们提供了一些现成的方案:

jdkserializationRedisSerializer使用JDK提供的序列化功能。优点是反序列化时不需要提供类型信息(class),但缺点是序列化后的结果非常庞大,是]SON格式的5倍左右,这样就会消耗Redis 服务器的大星内存。

lackson23sonRedisSerializer使用Jackson库将对象序列化为SON字符串。优点是速度快,序列化后的字符串短小精悍。但缺点也非常致命,那就是此类的构造函数中有一个类型参数,必须提供要序列化对象的类型信息(.class对象)。通过查看源代码,发现其只在反序列化过程中用到了类型信息。

Generic]ackson2]sonRedisserializer通用型序列化,这种序列化方式不用自己手动指定对象的Class。

RedisConfig配置类
package com.xmj.springdataredisdemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnection;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;


@Configuration

public class RedisConfig {
    @Bean
    public RedisTemplate redisTemplate(LettuceConnectionFactory lettuceConnectionFactory){
        RedisTemplate redisTemplate = new RedisTemplate<>();
        //为String类型的key设置序列化
        redisTemplate.setKeySerializer(new StringRedisSerializer());
       //为string类型的value设置序列化
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        //为hash类型的key设置序列化
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        //为hash类型的value设置序列化
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        return redisTemplate;
    }
}
User实体类
package com.xmj.springdataredisdemo.pojo;

import java.io.Serializable;


public class User implements Serializable {
    private Integer id;
    private String name;
    private Integer age;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + ''' +
                ", age=" + age +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
测试
    @Test
    public void testSerial(){
        User user = new User();
        user.setId(1);
        user.setName("zhangsan");
        user.setAge(20);
        ValueOperations ops = redisTemplate.opsForValue();
        ops.set("user",user);
        Object user1 = ops.get("user");
        System.out.println(user1);

    }
SpringDataRedis操作string数据类型
@Test
    public void testString(){
        ValueOperations ops = redisTemplate.opsForValue();
        //添加一条数据
        ops.set("name","zhangsan");
        //获取一条数据
        Object name = ops.get("name");
        System.out.println(name);
        //层级关系,目录形式存储数据
        ops.set("user:01","list");
        //添加多条数据
        Map map= new HashMap<>();
        map.put("age","20");
        map.put("address","sh");
        ops.multiSet(map);
        //
        List keys = new ArrayList<>();
        keys.add("name");
        keys.add("age");
        keys.add("address");
        List list = ops.multiGet(keys);
        list.forEach(System.out::println);
        //删除数据
        redisTemplate.delete("name");
        list = ops.multiGet(keys);
        list.forEach(System.out::println);

    }


SpringDataRedis操作hash数据类型
    @Test
    public void testHash(){
        HashOperations hashOperations = redisTemplate.opsForHash();
        
        hashOperations.put("user","name","zhangsan");
        
        hashOperations.get("user", "name");
        //添加多条数据
        Map map= new HashMap<>();
        map.put("age","20");
        map.put("address","sh");
        hashOperations.putAll("user",map);
        //获取多条数据
        List keys = new ArrayList<>();
        keys.add("name");
        keys.add("age");
        keys.add("address");
        List user =  hashOperations.multiGet("user", keys);
        user.forEach(System.out::println);
        //获取hash类型的所有数据
        Map entries = hashOperations.entries("user");
        entries.entrySet().forEach(e->{
            System.out.println(e.getKey()+"--->"+e.getValue());
        });
        //hash的删除
        hashOperations.delete("user2","name","age");
    }

SpringDataRedis操作list
    @Test
    public void testList(){
        ListOperations listOperations = redisTemplate.opsForList();
        //左添加
        listOperations.leftPush("students","wangwu");
        listOperations.leftPush("students","lisi");
        
        listOperations.leftPush("students","wangwu","zhangsan");
        //右添加
        listOperations.rightPush("students","zhaoliu");
        listOperations.rightPush("students","tianqi");
        //获取数据
        List list = listOperations.range("students", 0, 2);
        list.forEach(System.out::println);
        //获取总条数
        Long size = listOperations.size("students");
        System.out.println(size);

        //删除数据
        listOperations.remove("students",1,"lisi");
        //左弹出
        listOperations.leftPop("students");
        //右弹出
        listOperations.rightPop("students");

        list = listOperations.range("students", 0, 2);
        list.forEach(System.out::println);
    }

SpringDataRedis操作set数据类型
    @Test
    public void testSet(){
        SetOperations setOperations = redisTemplate.opsForSet();
        //添加数据
        String[] letters = new String[]{"aaa","bbb","ccc","ddd"};
        setOperations.add("letters",letters);
        setOperations.add("letters",letters);
        //获取数据
        Set set = setOperations.members("letters");
        set.forEach(System.out::println);
        //删除数据
        setOperations.remove("letters","aaa","bbb");
        set = setOperations.members("letters");
        set.forEach(System.out::println);
    }

SpringDataRedis操作sortedset数据类型
    @Test
    public void testSortedSet(){
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();
        //添加数据
        ZSetOperations.TypedTuple objectTypedTuple1 = new DefaultTypedTuple<>("zhangsan",3D);
        ZSetOperations.TypedTuple objectTypedTuple2 = new DefaultTypedTuple<>("lisi",4D);
        ZSetOperations.TypedTuple objectTypedTuple3 = new DefaultTypedTuple<>("wangwu",5D);
        ZSetOperations.TypedTuple objectTypedTuple4 = new DefaultTypedTuple<>("zhaoliu",6D);
        ZSetOperations.TypedTuple objectTypedTuple5 = new DefaultTypedTuple<>("tianqi",7D);
        Set> tuples = new HashSet<>();
        tuples.add(objectTypedTuple1);
        tuples.add(objectTypedTuple2);
        tuples.add(objectTypedTuple3);
        tuples.add(objectTypedTuple4);
        tuples.add(objectTypedTuple5);
        zSetOperations.add("score",tuples);
        Set score = zSetOperations.range("score", 0, 4);
        score.forEach(System.out::println);
        Long size = zSetOperations.size("score");
        System.out.println(size);
        zSetOperations.remove("score","zhangsan","lisi");
        score = zSetOperations.range("score", 0, 4);
        score.forEach(System.out::println);
    }
 

SpringData获取所有key+设置key失效时间
    
    @Test
    public void testAllKey(){
        Set keys = redisTemplate.keys("*");
        keys.forEach(System.out::println);
    }

 
    @Test
    public void testExpire(){
//        ValueOperations ops = redisTemplate.opsForValue();
//        ///方法一,添加key的时候设置失效时间
//        ops.set("code","test",30, TimeUnit.SECONDS);
        redisTemplate.expire("name",30,TimeUnit.SECONDS);
        //查看失效时间
        Long expire = redisTemplate.getExpire("code");
        System.out.println(expire);
        expire = redisTemplate.getExpire("name");
        System.out.println(expire);
    }

SpringData整合哨兵模式

appllication.yml配置

spring:
  redis:
    # Redis服务器地址
    host: 192.168.77.134
    # Redis服务器端口
    port: 6380
    # Redis服务器密码
    password: root
    # Redis服务器数据库
    database: 0
    # 连接超时时间
    timeout: 10000ms
    lettuce:
      pool:
        #最大连接数
        max-active: 1024
        # #最大连接阻塞等待时间,单位毫秒,默认-1ms
        max-wait: 10000ms
        ##最大空闲连接,默认8
        max-idle: 200
        ##最小空闲连接,默认0
        min-idle: 5
    # 哨兵模式
    sentinel:
      # 主节点名称
      master: mymaster
      # 节点
      nodes: 192.168.77.134:26379,192.168.77.134:26380,192.168.77.134:26381

RedisConfig配置类的RedisSentinelConfiguration方法

    @Beadn
    public RedisSentinelConfiguration redisSentinelConfiguration(){
        RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration()
                //主节点名称
                .master("mymaster")
                //哨兵
                .sentinel("192.168.77.134",26379)
                .sentinel("192.168.77.134",26380)
                .sentinel("192.168.77.134",26381);
        redisSentinelConfiguration.setPassword("root");
        return redisSentinelConfiguration;
    }
8.如何应对缓存穿透、缓存击穿、缓存雪崩问题 8.1.Key的过期淘汰机制

Redis可以对存储在Redis中的缓存数据设置过期时间,比如我们获取的短信验证码一般十分钟过期,我们这时候就需要在验证码存进Redis时添加一个key的过期时间,但是这里有一个需要格外注意的问题就是:并非key过期时间到了就一定会被Redis给删除。

8.1.1.定期删除

Redis默认是每隔100ms就随机抽取一些设置了过期时间的Key,检查其是否过期,如果过期就删除。为什么是随机抽取而不是检查所有key?因为你如果设置的key成千上万,每100毫秒都将所有存在的key检查一遍,会给CPU带来比较大的压力。

8.1.2.惰性删除

定期删除由于是随机抽取可能会导致很多过期Key到了过期时间并没有被删除。所以用户在从缓存获取数据的时候,redis会检查这个key是否过期了,如果过期就删除这个key。这时候就会在查询的时候将过期key从缓存中清除。

8.1.3.内存淘汰机制

仅仅使用定期删除+惰性删除机制还是会留下一个严重的隐患:如果定期删除留下了很多已经过期的key,而且用户长时间都没有使用过这些过期key,导致过期key无法被惰性删除,从而导致过期key一直堆积在内存里,最终造成Redis内存块被消耗殆尽。那这个问题如何解决呢?这个时候Redis内存淘汰机制应运而生了。Redis内存淘汰机制提供了6种数据淘汰策略:

volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。allkeys-lru:当内存不足以容纳新写入数据时移除最近最少使用的key。allkeys-random:从数据集中任意选择数据淘汰。no-enviction:当内存不足以容纳新写入数据时,新写入操作会报错。

一般情况下,推荐使用volatile-lru策略,对于配置信息等重要数据,不应该设置过期时间,这样Redis就永远不会淘汰这些重要数据。对于一般数据可以添加一个缓存时间,当数据失效则请求会从DB中获取并重新存入Redis中。

8.2.缓存击穿

首先我们来看下请求是如何取到数据的:当接收到用户请求,首先先尝试从Redis缓存中获取到数据,如果缓存中能取到数据则直接返回结果,当缓存中不存在数据时从DB获取数据,如果数据库成功取到数据,则更新Redis,然后返回数据,如果DB无数

8.2.1.定义

高并发的情况下,某个热门key突然过期,导致大星请求在Redis未找到缓存数据,进而全部去访问DB请求数据,引起DB压力瞬间增大。

8.2.2.解决方案

缓存击穿的情况下一般不容易造成DB的宕机,只是会造成对DB的周期性压力。对缓存击穿的解决方案—般可以这样:

Redis中的数据不设置过期时间,然后在缓存的对象上添加一个属性标识过期时间,每次获取到数据时,校验对象中的过期时间属性,如果数据即将过期,则异步发起一个线程主动更新缓存中的数据。但是这种方案可能会导致有些请求会拿到过期的值,就得看业务能否可以接受,如果要求数据必须是新数据,则最好的方案则为热点数据设置为永不过期,然后加一个互斥锁保证缓存的单线程写。 8.3.缓存穿透 8.3.1.定义

缓存穿透是指查询缓存和DB中都不存在的数据。比如通过id查询商品信息,id一般大于0,攻击者会故意传id为-1去查询,由于缓存是不命中则从DB中获取数据,这将会导致每次缓存都不命中数据导致每个请求都访问DB,造成缓存穿透。

8.3.2.解决方案:

利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠段时间重试采用异步更新策略,无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回。如果从数据库查询的对象为空,也放入缓存,只是设定的缓存过期时间较短,比如设置为60秒 8.4.缓存雪崩 8.4.1.定义

缓存中如果大量缓存在一段时间内集中过期了,这时候会发生大量的缓存击穿现象,所有的请求都落在了DB上,由于查询数据量巨大,引起DB压力过大甚至导致DB宕机。

8.4.2.解决方案

给缓存的失效时间,加上一个随机值,避免集体失效。如果Redis是集群部署,将热点数据均匀分布在不同的Redis库中也能避免全部失效的问题使用互斥锁,但是该方案吞吐量明显下降了。设置热点数据永远不过期。双缓存。我们有两个缓存,缓存A和缓存B。缓存A的失效时间为20分钟,缓存B不设失效时间。自己做缓存预热操作。然后细分以下几个小点

1.从缓存A读数据库,有则直接返回2.A没有数据,直接从B读数据,直接返回,并且异步启动一个更新线程。3.更新线程同时更新缓存A和缓存B。
到了就一定会被Redis给删除。 定期删除

Redis默认是每隔100ms就随机抽取一些设置了过期时间的Key,检查其是否过期,如果过期就删除。为什么是随机抽取而不是检查所有key?因为你如果设置的key成千上万,每100毫秒都将所有存在的key检查一遍,会给CPU带来比较大的压力。

惰性删除

定期删除由于是随机抽取可能会导致很多过期Key到了过期时间并没有被删除。所以用户在从缓存获取数据的时候,redis会检查这个key是否过期了,如果过期就删除这个key。这时候就会在查询的时候将过期key从缓存中清除。

内存淘汰机制

仅仅使用定期删除+惰性删除机制还是会留下一个严重的隐患:如果定期删除留下了很多已经过期的key,而且用户长时间都没有使用过这些过期key,导致过期key无法被惰性删除,从而导致过期key一直堆积在内存里,最终造成Redis内存块被消耗殆尽。那这个问题如何解决呢?这个时候Redis内存淘汰机制应运而生了。Redis内存淘汰机制提供了6种数据淘汰策略:

volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。allkeys-lru:当内存不足以容纳新写入数据时移除最近最少使用的key。allkeys-random:从数据集中任意选择数据淘汰。no-enviction:当内存不足以容纳新写入数据时,新写入操作会报错。

一般情况下,推荐使用volatile-lru策略,对于配置信息等重要数据,不应该设置过期时间,这样Redis就永远不会淘汰这些重要数据。对于一般数据可以添加一个缓存时间,当数据失效则请求会从DB中获取并重新存入Redis中。

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号