Redis

Redis

一、现有项目的缺陷【面试题】

1、数据缓存

数据缓存

2、分布式Session

分布式Session
在这里插入图片描述

二、Redis

1、概述

  • 非关系数据库【NoSQL:Not only SQL】
  • 存储结构:key-value
    • key:字符串
    • value:5种数据类型【string,hash,list,set,zset】
  • 存储介质:内存,可以持久化
  • 单线程,NIO【同步非阻塞,多路复用】

2、安装

2.1 原始安装

安装包:redis-5.0.8.tar.gz

  • 安装步骤
  1. 安装gcc环境和make命令

    apt-get install gcc

    apt-get install make

  2. 上传redis-5.0.8.tar.gz

  3. 解压tar -zxvf redis-5.0.8.tar.gz

  4. 进入到redis-5.0.8目录,执行make

    会编译出redis-serverredis-cli

    cd redis-5.0.8

    make

  • 运行redis

进入到redis-5.0.8/src

  • 执行./redis-server
./redis-server
在这里插入图片描述
  • 连接redis

进入到redis-5.0.8/src

  • 执行./redis-cli
./redis-cli
在这里插入图片描述

2.2 docker-compose安装

  • docker-compose.yml
version: '3.1'
services:
  redis:
    image: daocloud.io/library/redis:5.0.8
    restart: always
    container_name: redis
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 6379:6379
  • 通过docker-compose up -d启动容器
    • 自动启动redis的服务端
    • 进入到容器内部,可以直接使用redis-cli连接
重点命令

3、连接redis的语法

  • ./redis-cli

    • 没有加任何参数,则连接的是本机,且端口也是默认的【6379】

    • 完整语法./redis-cli -h 127.0.0.1 -p 6379

三、Redis客户端工具

  • 命令行工具

    • redis-cli
  • 图形化工具

    • RedisDesktopManager【一个windows的安装包】
    RedisDesktopManager
    在这里插入图片描述

|

  • Java工具

    • Jedis【Redis官方推荐使用Java连接Redis的API】

四、Redis的五种数据类型

Redis是一个key-value的数据结构

五种数据类型是指value的数据类型

  • 五种数据类型

    string:字符串

    list:列表

    set:集合

    zset:有序集合【靠分数来排序】

    hash:对象

五种数据类型
在这里插入图片描述

1、string

#设置值
#set key value
set name lucy
#结果,正确就返回OK

#获取值
#get key
get name
#结果,有就返回值,没有nil[null]

#返回key对应的value,再重新设置这个key的值
#getset key value
getset name lily

#设置多个键值对
#mset k1 v1 k2 v2 ...
mset sex 1 age 20 address hangzhou
#结果:OK

#获取多对key对应的value
#mget k1 k2 k3 ...
mget sex age address
#结果:依次返回得到数据

#设置key-value:如果key不存在,则设置,如果key存在,则什么都不做
#setnx key value
#结果:成功返回1,失败返回0
setnx phone 13577889900

#设置key的同时,设置有效时间
#setex key 秒数 value
setnx gender 10 1
#gender有效时间为10S

#查看剩余有效时间
#ttl key
ttl gender
结果:返回剩余有效时间,如果已经过期:返回-2,如果没有设置有效时间:返回-1

#计算key对应的value的长度
#strlen key
strlen phone
#结果为11,phone存的值是11位

#自增1,value是数值类型才能成功。如果key不存在,则直接以0为基础,进行自增
#incr key
incr age
#age本来是20,现在返回就是21

#自增指定数量,value是数值类型才能成功
incrby age 5
#age本来是20,现在返回就是25

#自减1,value是数值类型才能成功。
#decr key
decr age
#age本来是0,现在返回就是-1

#自减指定数量,value是数值类型才能成功
#decrby key
decrby age 20
#age本来是30,现在返回就是10

#在原先的基础之上进行字符串追加
#append key value
append address xiaoshan
#本来address的值是hangzhou,现在就是hangzhouxiaoshan

#为已经存在的key的设置有效时间,单位为秒
#expire key seconds
expire address 10
#为已经存在的address设置有效时间为10S

#为已经存在的key设置过期时间,单位是毫秒
pexpire key 毫秒
#查看指定key的剩余存活时间,单位是毫秒
pttl key
案例
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、list

#左压栈
lpush key v1 v2 ...
#特征:存入的数据顺序跟读取顺序相反

#右压栈
rpush key v1 v2 ...
#特征:存入的数据顺序跟读取顺序相同

#查询列表中的数据
#下标从0开始,如果要读到最末尾,endIndex就是-1
#lrange key startIndex endIndex
lrange key 0 -1

#获取列表的长度
llen key

#从左边弹出一个元素[获取最左边的元素,再删除它]
lpop key

#从右边弹出一个元素
rpop key

#获取指定索引的元素
lindex key index

#从一个列表的右边弹出元素,再追到加另一个列表的左边
rpoplpush 源列表 目标列表

#移除count个的value值
lrem key count value
lrem names2 2 tom
#删除names2列表中2个tom
list操作
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、set

#存值
sadd myset aa bb cc
#返回存入的个数

#获取集合长度
scard myset

#获取集合元素
smembers myset

#判断元素是否在集合中
sismember myset cc

#随机弹出一个元素
spop myset

#随机弹出指定个数元素
spop myset 2

#差集
sdiff myset1 myset2

#交集
sinter myset1 myset2

#并集
sunion myset1 myset2

#将交集结果存入到另一个集合中
sdiffstore myset3 myset1 myset2

#将交集结果存入到另一个集合中
sinterstore myset3 myset1 myset2

#将并集结果存入到另一个集合中
sunionstore myset4 myset1 myset2

#将myset1中的aa元素移动到myset3
smove myset1 myset3 aa
set操作
>
在这里插入图片描述

4、zset

#向有序集合中增加元素
#zadd 集合名 分数1 元素名1 分数2 元素名2 ...
zadd paihangbang 10 liubei 80 caocao 30 sunquan
#返回插入的数量 

#获取集合中元素的数量
zcard paihangbang

#获取集合中所有的元素的名称
zrange paihangbang 0 -1
1) "liubei"
2) "sunquan"
3) "caocao"

#获取集合所有的元素,包括名称和分数
zrange paihangbang 0 -1 withscores
1) "liubei"
2) "10"
3) "sunquan"
4) "30"
5) "caocao"
6) "80"

#获取集合前两台元素的名称和分数
zrange paihangbang 0 1 withscores
1) "liubei"
2) "10"
3) "sunquan"
4) "30"

#为集合中指定的元素增加分数
zincrby paihangbang 50 liubei
#返回增加后的分数

#返回集合中指定元素的分数
zscore paihangbang sunquan

#把myzset1和myzset2根据key求出交集,并把value加在一起
ZINTERSTORE myzset3 2 myzset1 myzset2

#在不知道当前集合中最大值和最小值的情况下,可以获取前几位
#-inf:指最小值   +inf:最大值   limit 限定【跟mysql中使用一样】
#withscores是可以不写的,如果不写,则只返回元素名
#limit 0 2是可以不写的,如果不写,则按从小到大的顺序返回集合元素
zrangebyscore paihangbang -inf +inf withscores limit 0 2

#按从大到小的顺序进行排序,并获取前2位
zrevrangebyscore paihangbang -inf +inf withscores limit 0 2

#根据元素名删除元素
zrem key member [member...]

#获取集合中指定元素的索引
zrank paihangbang caocao

#删除指定分数区间的元素[10,20]
ZREMRANGEBYSCORE paihangbang 10 20

5、hash

#设置值,必须指定大key和小key
hset user name lisi

#设置多值
hmset user sex 1 age 20

#获取单个小key对应的value
hget user sex

#获取多个小key对应的value们
hmget user name age

#判断小key是否存在
hexists user age

#获取所有的小key
hkeys user
1) "name"
2) "sex"
3) "age"

#获取所有的小key对应的values
hvals user
1) "lisi"
2) "1"
3) "20"

#获取所有大key中的键值对
hgetall user
1) "name"
2) "lisi"
3) "sex"
4) "1"
5) "age"
6) "20"

#求长度
hlen user

#设置小key【小key不存在,则设置成功,如果存在,则什么都不做】
hsetnx user age 25

五、通用命令

  • key相关的命令
#查找匹配规则的key, *:代表0到多个字符,?代表一个字符
keys *
keys ?tu*

#判断key是否存在
exists key

#删除key
del key

#设置已经存在的key的过期时间,单位为秒
expire key seconds

#为已经存在的key设置过期时间,单位是毫秒
pexpire key 毫秒

#移除指定key的生存时间,永久的持久化
PERSIST key

#获取指定的key的value的数据类型
type key

#移动指定key到别的数据库,dbindex是0-15
move key dbindex
  • 数据库相关操作
#redis默认有16个数据库,分别是 db0 - db15
#查看当前数据库下有多少key
dbsize

#切换数据库,dbindex是0-15
select dbindex

#清空当前数据库,慎重
flushdb

#清空所有数据库中的数据,慎重
flushall

#实时监控Redis服务接收到的命令
monitor
  • 查看redis具体指令的性能
#在src下执行
redis-benchmark set

redis-benchmark get

#set是写的性能
#gte是读的性能
#具体的性能跟redis服务器的硬件配置有关
redis-benchmark redis命令
在这里插入图片描述

六、开启原始安装Redis的访问权限

1、修改redis的核心配置文件【redis.conf】

  • 路径:redis根目录下
  • 修改内容如下
#关闭本地绑定【69行】
#bind 127.0.0.1

#关闭保护模式【88行】
把 protected-mode yes 改成 protected-mode no

#设置密码【507行】
requirepass java2107

2、启动redis【带着配置文件】

cd src
./redis-server ../redis.conf
使用RedisDesktopManager连接
在这里插入图片描述

3、使用Redis客户端连接服务器也需要指定密码

./redis-cli
在这里插入图片描述

七、Jedis操作Redis

1、快速入门

1.1 创建Maven项目

1.2 导入依赖

<!--    1、 Jedis-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
<!--    2、 Junit测试-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>
<!--    3、 Lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.20</version>
</dependency>

1.3 写代码

@Test
public void setTest() throws Exception {
    Jedis jedis = new Jedis("192.168.145.138", 6379);
    jedis.auth("java2107");

    //操作redis
    jedis.set("username", "superbaby");
    jedis.close();

}

@Test
public void getTest() throws Exception {
    Jedis jedis = new Jedis("192.168.145.138", 6379);
    jedis.auth("java2107");

    String username = jedis.get("username");
    System.out.println("username = " + username);
    jedis.close();

}

1.4 测试

  • 可以看到把值存入到了Redis,也可以获取出来

2、Jedis连接池

  • JedisPool
    @Test
    public void jedisPoolTest() throws Exception {

        //JedisPool(final GenericObjectPoolConfig poolConfig, final String host, int port,
        //      int timeout, final String password, final int database)

        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();

        JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.145.138", 6379, 3000, "java2107", 1);
        Jedis jedis = jedisPool.getResource();
        jedis.set("address", "杭州");
        jedis.close();
    }

  • 抽取工具类
package com.qf.java2107.utils;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * 工具类
 * @author ghy
 * @version 1.0
 * @date 2022-02-16
 **/
public class JedisPoolUtils {

    private static String HOST = "192.168.145.138";
    private static Integer PORT = 6379;
    private static Integer TIMEOUT = 3000;
    private static String PASSWORD = "java2107";
    private static Integer DATABASE = 1;

    private static JedisPool jedisPool = null;

    static {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPool = new JedisPool(jedisPoolConfig, HOST, PORT, TIMEOUT, PASSWORD, DATABASE);
    }

    public static Jedis getResource(){
        return jedisPool.getResource();
    }

}

3、操作其他数据类型

3.1 list

3.2 set

3.3 zset

3.4 hash

/**
 * incr、decrBy
 **/
@Test
public void incrDecrTest() throws Exception {
    Jedis jedis = JedisPoolUtils.getResource();

    Long age = jedis.incr("age");
    System.out.println(age);

    Long age1 = jedis.decrBy("age", 100);
    System.out.println(age1);

    jedis.close();
}

/**
 * 通用命令
 *
 **/
@Test
public void otherTest() throws Exception {
    Jedis jedis = JedisPoolUtils.getResource();
    Boolean flag = jedis.exists("user:1001");
    System.out.println("flag = " + flag);

    if(flag) {
        Long count = jedis.del("user:1001");
        System.out.println(count);
    }

    jedis.close();

}


/**
 * hash操作
 **/
@Test
public void hashTest() throws Exception {
    Jedis jedis = JedisPoolUtils.getResource();

    User user = new User();
    user.setId(1001);
    user.setUsername("lucy");
    user.setGender(1);
    user.setAddress("九堡");

    Map<String, String> userMap = new HashMap<>();
    userMap.put("id", user.getId().toString());
    userMap.put("username", user.getUsername());
    userMap.put("gender", user.getGender().toString());
    userMap.put("address", user.getAddress());
    jedis.hmset("user:" + user.getId(), userMap);

    Map<String, String> map = jedis.hgetAll("user:1001");
    System.out.println("map = " + map);

    jedis.close();
}


/**
 * zset操作
 **/
@Test
public void zsetTest() throws Exception {
    Jedis jedis = JedisPoolUtils.getResource();
    Map<String, Double> scoreMembers = new HashMap<>();
    scoreMembers.put("liubei", 20.0);
    scoreMembers.put("zhangfei", 2.0);
    scoreMembers.put("guanyu", 15.0);

    jedis.zadd("tuhaobang", scoreMembers);

    //Set<Tuple> tuples = jedis.zrangeWithScores("tuhaobang", 0, -1);
    Set<Tuple> tuples = jedis.zrevrangeWithScores("tuhaobang", 0, -1);
    for (Tuple tuple : tuples) {
        System.out.println(tuple.getElement() + ":" + tuple.getScore());
    }

    jedis.close();
}


/**
 * set操作
 **/
@Test
public void setTest() throws Exception {
    Jedis jedis = JedisPoolUtils.getResource();
    Long count = jedis.sadd("subjects", "java", "c", "php", "python", "html");
    System.out.println(count);

    Set<String> subjects = jedis.smembers("subjects");
    System.out.println(subjects);

    jedis.sadd("subjects2", "java", "c", "php");
    Set<String> set1 = jedis.sdiff("subjects", "subjects2");
    System.out.println("差集:" + set1);

    Set<String> set2 = jedis.sinter("subjects", "subjects2");
    System.out.println("交集:" + set2);

    jedis.close();

}


/**
 * list操作
 **/
@Test
public void listTest() throws Exception {
    Jedis jedis = JedisPoolUtils.getResource();
    jedis.rpush("names", "lucy", "tom", "jack","mark");
    jedis.expire("names", 5);
    List<String> names = jedis.lrange("names", 0, -1);
    System.out.println(names);
    Thread.sleep(5500);

    System.out.println("有效时间到了-------------");
    names = jedis.lrange("names", 0, -1);
    System.out.println(names);
    jedis.close();
}


/**
 *
 **/
@Test
public void jedisPoolTest() throws Exception {
    Jedis jedis = JedisPoolUtils.getResource();
    String address = jedis.get("address");
    System.out.println("address = " + address);
    jedis.close();
}

4、保存对象的方式

4.1 String

  • json数据
/**
 * 存json
 **/
@Test
public void saveObjectTest() throws Exception {
    Jedis jedis = JedisPoolUtils.getResource();

    User user = new User();
    user.setId(1001);
    user.setUsername("lucy");
    user.setGender(1);
    user.setAddress("九堡");

    ObjectMapper objectMapper = new ObjectMapper();
    String userString = objectMapper.writeValueAsString(user);
    jedis.set("user:" + user.getId(), userString);

    String json = jedis.get("user:1001");
    System.out.println(json);

    jedis.close();

}

4.2 字节数组

/**
 * 取字节数组
 **/
@Test
public void getByteArrayTest() throws Exception {
    Jedis jedis = JedisPoolUtils.getResource();

    byte[] bytes = jedis.get("user:1002".getBytes());
    User user = (User) SerializationUtils.deserialize(bytes);
    System.out.println("user = " + user);

    jedis.close();

}

/**
 * 存字节数组
 **/
@Test
public void saveByteArrayTest() throws Exception {
    Jedis jedis = JedisPoolUtils.getResource();

    User user = new User();
    user.setId(1002);
    user.setUsername("李雷");
    user.setGender(1);
    user.setAddress("九堡");

    //序列化
    byte[] userBytes = SerializationUtils.serialize(user);

    jedis.set(("user:" + user.getId()).getBytes(), userBytes);

    jedis.close();

}
字节数组存储底层

5、批量存储之管道操作

没用管道
在这里插入图片描述

|

用了管理
在这里插入图片描述
/**
 * 使用管道提升了近1000倍
 * 442
 * @throws Exception
 */
@Test
public void pipelinedSaveTest() throws Exception {
    Jedis jedis = JedisPoolUtils.getResource();
    ObjectMapper objectMapper = new ObjectMapper();
    Pipeline pipelined = jedis.pipelined();

    long start = System.currentTimeMillis();

    for (int i = 0; i < 100000; i++) {
        User user = new User();
        user.setId(i);
        user.setUsername("李雷" + i);
        user.setGender(i % 2);
        user.setAddress("九堡" + i);

        String userString = objectMapper.writeValueAsString(user);
        pipelined.set("user:" + user.getId(), userString);

    }

    System.out.println("--->" + (System.currentTimeMillis()-start));

    jedis.close();
}


/**
 * 442501
 **/
@Test
public void manySaveTest() throws Exception {
    Jedis jedis = JedisPoolUtils.getResource();
    ObjectMapper objectMapper = new ObjectMapper();

    long start = System.currentTimeMillis();

    for (int i = 0; i < 100000; i++) {
        User user = new User();
        user.setId(i);
        user.setUsername("李雷" + i);
        user.setGender(i % 2);
        user.setAddress("九堡" + i);

        String userString = objectMapper.writeValueAsString(user);
        jedis.set("user:" + user.getId(), userString);

    }

    System.out.println("--->" + (System.currentTimeMillis()-start));

    jedis.close();
}

八、SpringBoot整合Redis

1、实现步骤

  • 创建SpringBoot项目
  • 导入依赖【spring-boot-starter-data-redis】
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 配置文件
spring:
  redis:
    host: 192.168.145.136
    port: 6379
    jedis:
      pool:
        max-active: 10
        max-wait: 3000
        min-idle: 5
        enabled: true
        max-idle: 10
  • 操作
package com.qf.java2107.springbootredis.demo;

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 java.util.concurrent.TimeUnit;

@SpringBootTest
class SpringbootRedisDemoApplicationTests {

    /**
     * redisTemplate.opsForXxx().操作
     *      Xxx : 数据类型
     */
    @Autowired
    RedisTemplate redisTemplate;

    @Test
    void stringTest() throws Exception {
        //参数一:key
        //参数二:值
        //参数三:有效时间
        //参数四:有效时间单位
        redisTemplate.opsForValue().set("username", "lilei", 3, TimeUnit.SECONDS);

        //setnx
        Boolean flag1 = redisTemplate.opsForValue().setIfAbsent("username", "lilei", 3, TimeUnit.SECONDS);
        System.out.println(flag1);

        String username = (String) redisTemplate.opsForValue().get("username");
        System.out.println(username);

        Thread.sleep(3500);

        username = (String) redisTemplate.opsForValue().get("username");
        System.out.println("3秒后:" + username);

        Boolean flag2 = redisTemplate.opsForValue().setIfAbsent("username", "lilei", 30, TimeUnit.SECONDS);
        System.out.println(flag2);

    }

}

2、操作其他数据类型

list、set、zset、hash

package com.qf.java2107.springbootredis.demo;

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.ZSetOperations;

import java.util.*;
import java.util.concurrent.TimeUnit;

@SpringBootTest
class SpringbootRedisDemoApplicationTests {

    /**
     * redisTemplate.opsForXxx().操作
     *      Xxx : 数据类型
     */
    @Autowired
    RedisTemplate redisTemplate;

    /**
     *
     **/
    @Test
    public void otherTest() throws Exception {
        Long age = redisTemplate.opsForValue().increment("age");
        System.out.println(age);

    }

    /**
     * hash操作
     **/
    @Test
    public void hashTest() throws Exception {
        Map<String,Object> userMap = new HashMap<>();
        userMap.put("id", 1111);
        userMap.put("username", "Hello");
        userMap.put("gender", 1);
        redisTemplate.opsForHash().putAll("user:" + userMap.get("id"), userMap);

        Set keys = redisTemplate.opsForHash().keys("user:1111");
        System.out.println("keys = " + keys);

        List list = redisTemplate.opsForHash().multiGet("user:1111", keys);
        System.out.println("list = " + list);
        List values = redisTemplate.opsForHash().values("user:1111");
        System.out.println("values = " + values);

        Map map = redisTemplate.opsForHash().entries("user:1111");
        System.out.println("map = " + map);

        Boolean flag = redisTemplate.opsForHash().hasKey("user:1111", "gender");
        System.out.println("flag = " + flag);

    }


    /**
     * zset操作
     *
     **/
    @Test
    public void zsetTest() throws Exception {
        redisTemplate.opsForZSet().add("tuhaobang", "sunwukong", 60);
        redisTemplate.opsForZSet().add("tuhaobang", "zhubajie", 50);
        redisTemplate.opsForZSet().add("tuhaobang", "laosha", 70);

        Set<ZSetOperations.TypedTuple<String>> set = redisTemplate.opsForZSet().reverseRangeWithScores("tuhaobang", 0, -1);
        for (ZSetOperations.TypedTuple<String> tuple : set) {
            String name = tuple.getValue();
            Double score = tuple.getScore();
            System.out.println(name + ":" + score);
        }

    }


    /**
     * 操作set
     *
     **/
    @Test
    public void setTest() throws Exception {
        Long count = redisTemplate.opsForSet().add("hobbies", "java", "c", "swim", "game", "douxin");
        System.out.println("count = " + count);
        Set set = redisTemplate.opsForSet().members("hobbies");
        System.out.println(set);

        Long count2 = redisTemplate.opsForSet().remove("hobbies", "c", "swim");
        System.out.println("count2 = " + count2);

    }

    /**
     * 操作list
     **/
    @Test
    public void listTest() throws Exception {
        List<String> names = new ArrayList<>();
        names.add("Lucy");
        names.add("Tom");
        names.add("Lucy");
        names.add("Jack");
        Long count = redisTemplate.opsForList().rightPushAll("names", names);
        System.out.println("count = " + count);

        List<String> list = redisTemplate.opsForList().range("names", 0, -1);
        System.out.println(list);

        String value = (String) redisTemplate.opsForList().index("names", 2);
        System.out.println("value = " + value);

    }

}

3、序列化器

SpringBoot提供默认的RedisTemplate这个bean,继承一个RedisAccessor类,该类实现了一个接口InitializingBean,所以会自动调用afterPropertiesSet方法,在该方法中设置了如果没有指定具体使用哪个序列化器,那么Redis在存储数据时,采用的都默认的序列化器

3.1 修改默认的序列化器

  • 增加配置类
@Configuration
public class MyRedisConfig {

    @Bean
    public RedisTemplate<String, Object> myRedisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(factory);
        return redisTemplate;
    }

}
  • 测试
@Autowired
RedisTemplate myRedisTemplate;

/**
 *
 **/
@Test
public void myRedisTemplateTest() throws Exception {
    myRedisTemplate.opsForValue().set("name", "张三");
}

/**
 *
 **/
@Test
public void Test() throws Exception {
    Object name = myRedisTemplate.opsForValue().get("name");
    System.out.println("name = " + name);
}
RedisDesktopManager
在这里插入图片描述
Redis
在这里插入图片描述

3.2 使用RedisTemplate可以直接存储对象

/**
 *
 **/
@Test
public void getObjectTest() throws Exception {
    Product product = (Product) redisTemplate.opsForValue().get("product:1011");
    System.out.println(product);
}


/**
 * RedisTemplate是可以直接存对象的,存储的key-value不是普通的字符串,而是经过jdk序列化器序列化后的东西
 **/
@Test
public void saveObjectTest() throws Exception {
    Product product = new Product();
    product.setId(1011);
    product.setProName("冰箱666");
    product.setProDate(new Date());
    product.setPrice(7777.77);
    redisTemplate.opsForValue().set("product:" + product.getId(), product);
}

4、使用管道

@Test
public void saveManyTest() throws Exception {

    long start = System.currentTimeMillis();

    redisTemplate.executePipelined(new SessionCallback<String>() {
        @Override
        public String execute(RedisOperations redisOperations) throws DataAccessException {

            for (int i = 0; i < 200000; i++) {
                redisOperations.opsForValue().set("name" + i, "value"+i);
            }

            return null;
        }
    });

    long end = System.currentTimeMillis();

    System.out.println("-->" + (end - start) );

}

九、Redis的事务

1、MySQL中事务

  • 开启事务
  • 执行操作
  • 提交或回滚事务

事务中所有的操作都成功,事务才会提交,一旦由某个操作失败,那么就回滚事务,回到事务开始前的状态

2、Redis中的事务

2.1 事务操作

  • 开启事务【multi】
  • 执行操作【执行的命令会被放到一个队列中,队列中的所有操作是在提交事务时执行的】
  • 提交或回滚事务【exec/discard】

事务中Redis操作数据执行的命令是放在一个队列中,队列中的命令在事务提交时执行,这些命令在没有语法错误的情况下,是该成功的成功,该失败的失败

事务开启与提交
在这里插入图片描述
事务开启与回滚【什么都不做】
在这里插入图片描述
语法问题,会造成事务直接取消
在这里插入图片描述

2.2 Java实现Redis事务

/**
 * 事务操作
 **/
@Test
public void txTest() throws Exception {
    redisTemplate.execute(new SessionCallback() {
        @Override
        public Object execute(RedisOperations redisOperations) throws DataAccessException {

            //开事务
            redisOperations.multi();
            redisOperations.opsForValue().set("k1", "v1");
            redisOperations.opsForList().rightPush("names", "lucy");
            redisOperations.opsForList().rightPush("names", "lily");
            redisOperations.opsForValue().increment("k1");//失败
            //提交事务
            redisOperations.exec();
            return null;
        }
    });
}

十、在Docker搭建Redis时使用Redis的配置文件

  • 配置文件名称:redis.conf
  • 配置文件路径:redis根目录下\redis.conf【原始安装Redis方式】

1、修改docker-compose.yml文件

version: '3.1'
services:
  redis:
    image: daocloud.io/library/redis:5.0.8
    restart: always
    container_name: redis
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 6379:6379
    volumes:
      - ./conf/redis.conf:/usr/local/redis/redis.conf
    command: ["redis-server","/usr/local/redis/redis.conf"]
  • 在docker-compose.yml文件同目录下创建conf目录,再在conf目录下创建redis.conf文件
redis.conf文件
在这里插入图片描述
requirepass java2107
  • 需要把原生的redis容器给down掉,重新up
docker-compose down
docker-compose up -d

2、在SpringBoot整合Redis的配置文件中需要加入密码

配置文件
在这里插入图片描述

十一、Redis持久化【面试题】

1、持久化概述

  • 把Redis内存中的临时数据写入到磁盘中,永久的保存下来,以免数据丢失,这个过程被称为叫持久化

2、持久化方式

  • RDB:基于快照的方式
  • AOF:基于日志的方式
  • 混合持久化【两种一起用】

3、RDB快照持久化机制

3.1 概述

  • 基于快照的方式进行数据持久化,也是redis默认使用的持久化机制【4.0之前的版本】

3.2 redis.conf

Redis对RDB机制进行了综合性的测试

使用RDB机制既能保证redis的功能,同时也保证了redis的性能

3.3 触发RDB机制的规则

################################ SNAPSHOTTING  ################################
#
# Save the DB on disk:
#
#   save <seconds> <changes>
#   在一定时间范围内【以秒为单位】,发生变化的redis的key达到了一定的数量,就会进行一次保存【快照】
#   Will save the DB if both the given number of seconds and the given number of write operations against the DB occurred.
#   In the example below the behaviour will be to save:
#   900秒内,至少有一个key被改变了
#   after 900 sec (15 min) if at least 1 key changed
#   300秒内,至少有10key被改变了
#   after 300 sec (5 min) if at least 10 keys changed
#   60秒内,至少有10000个key被改变了
#   fter 60 sec if at least 10000 keys changed
#   
#   It is also possible to remove all the previously configured save
#   points by adding a save directive with a single empty string argument
#   like in the following example:
#
#   save ""

save 900 1
save 300 10
save 60 10000
  • 900秒内,至少有一个key被改变了
  • 300秒内,至少有10key被改变了
  • 60秒内,至少有10000个key被改变了

手动触发RDB机制:当redis正常停止时,就会触发RDB机制

3.4 RDB触发后文件的保存在哪里?

  • 文件名:dump.rdb,被保存在根目录下\src目录下
# By default Redis will stop accepting writes if RDB snapshots are enabled
# (at least one save point) and the latest background save failed.
# This will make the user aware (in a hard way) that data is not persisting
# on disk properly, otherwise chances are that no one will notice and some
# disaster will happen.
#
# If the background saving process will start working again Redis will
# automatically allow writes again.
#
# However if you have setup your proper monitoring of the Redis server
# and persistence, you may want to disable this feature so that Redis will
# continue to work as usual even if there are problems with disk,
# permissions, and so forth.
stop-writes-on-bgsave-error yes

# Compress string objects using LZF when dump .rdb databases?
# For default that's set to 'yes' as it's almost always a win.
# If you want to save some CPU in the saving child set it to 'no' but
# the dataset will likely be bigger if you have compressible values or keys.
rdbcompression yes

# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
# This makes the format more resistant to corruption but there is a performance
# hit to pay (around 10%) when saving and loading RDB files, so you can disable it
# for maximum performances.
#
# RDB files created with checksum disabled have a checksum of zero that will
# tell the loading code to skip the check.
rdbchecksum yes

# The filename where to dump the DB
dbfilename dump.rdb

# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
dir ./

触发RDB机制后,就会把数据写入到一个在当前目录下名为dump.rdb的文件中

redis/src/dump.rdb
在这里插入图片描述
  • 可以查看dump.rdb文件的内容,发现是经过压缩的,存储的是二进制数据

3.5 使用docker-compose的方式来实现RDB

  • docker-compose.yml
version: '3.1'
services:
  redis:
    image: daocloud.io/library/redis:5.0.8
    restart: always
    container_name: redis
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 6379:6379
    volumes:
      - ./conf/redis.conf:/usr/local/redis/redis.conf
      - ./data/:/data/
    command: ["redis-server","/usr/local/redis/redis.conf"]
  • 停止并删除原先的redis容器,再重开一个
  • 进入redis-cli,执行写入【set】操作再执行bgsave指令【可以手动触发RDB机制】,会生成一个dump.rdb文件在data目录下,这个就是RDB机制进行快照保存后的文件
手动触发RDB【bgsave】
在这里插入图片描述

3.6 bgsave的执行逻辑

bgsave的执行逻辑
在这里插入图片描述

4、AOF日志持久化机制

4.1、概述

  • redis会把发生变化的key的命令以日志的形式保存到一个文件中,默认AOF机制是关闭的

4.2、redis.conf

############################## APPEND ONLY MODE ###############################

# By default Redis asynchronously dumps the dataset on disk. This mode is
# good enough in many applications, but an issue with the Redis process or
# a power outage may result into a few minutes of writes lost (depending on
# the configured save points).
#
# The Append Only File is an alternative persistence mode that provides
# much better durability. For instance using the default data fsync policy
# (see later in the config file) Redis can lose just one second of writes in a
# dramatic event like a server power outage, or a single write if something
# wrong with the Redis process itself happens, but the operating system is
# still running correctly.
#
# AOF and RDB persistence can be enabled at the same time without problems.
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.
#
# Please check http://redis.io/topics/persistence for more information.

appendonly no

# The name of the append only file (default: "appendonly.aof")

appendfilename "appendonly.aof"

#aof默认是关闭的,如果要开启,下面这个no改成yes

appendonly no

#aof机制保存数据的文件名:appendonly.aof

appendfilename “appendonly.aof”

4.3、触发AOF时机

# The fsync() call tells the Operating System to actually write data on disk
# instead of waiting for more data in the output buffer. Some OS will really flush
# data on disk, some other OS will just try to do it ASAP.
#
# Redis supports three different modes:
#
# no: don't fsync, just let the OS flush the data when it wants. Faster.
# always: fsync after every write to the append only log. Slow, Safest.
# everysec: fsync only one time every second. Compromise.
#
# The default is "everysec", as that's usually the right compromise between
# speed and data safety. It's up to you to understand if you can relax this to
# "no" that will let the operating system flush the output buffer when
# it wants, for better performances (but if you can live with the idea of
# some data loss consider the default persistence mode that's snapshotting),
# or on the contrary, use "always" that's very slow but a bit safer than
# everysec.
#
# More details please check the following article:
# http://antirez.com/post/redis-persistence-demystified.html
#
# If unsure, use "everysec".

# appendfsync always
appendfsync everysec
# appendfsync no

#总是触发:每当一个key发生了变化 ,就会触发一次。触发的频率太高了,读写次数太频繁

appendfsync always

#每秒触发一次,是redis默认选择的方式

appendfsync everysec

#根据当前系统决定如何触发,一般是重启

appendfsync no

4.4、AOF文件重写的规则

  • AOF持久化机制是以日志的形式来保存当前redis发生了变化的key的命令,当命令写入到AOF文件中时,随着命令的增加,文件会越来越大,大到一定时候,文件需要被重写【要对文件进行压缩】
# When the AOF fsync policy is set to always or everysec, and a background
# saving process (a background save or AOF log background rewriting) is
# performing a lot of I/O against the disk, in some Linux configurations
# Redis may block too long on the fsync() call. Note that there is no fix for
# this currently, as even performing fsync in a different thread will block
# our synchronous write(2) call.
#
# In order to mitigate this problem it's possible to use the following option
# that will prevent fsync() from being called in the main process while a
# BGSAVE or BGREWRITEAOF is in progress.
#
# This means that while another child is saving, the durability of Redis is
# the same as "appendfsync none". In practical terms, this means that it is
# possible to lose up to 30 seconds of log in the worst scenario (with the
# default Linux settings).
#
# If you have latency problems turn this to "yes". Otherwise leave it as
# "no" that is the safest pick from the point of view of durability.

no-appendfsync-on-rewrite no

# Automatic rewrite of the append only file.
# Redis is able to automatically rewrite the log file implicitly calling
# BGREWRITEAOF when the AOF log size grows by the specified percentage.
#
# This is how it works: Redis remembers the size of the AOF file after the
# latest rewrite (if no rewrite has happened since the restart, the size of
# the AOF at startup is used).
#
# This base size is compared to the current size. If the current size is
# bigger than the specified percentage, the rewrite is triggered. Also
# you need to specify a minimal size for the AOF file to be rewritten, this
# is useful to avoid rewriting the AOF file even if the percentage increase
# is reached but it is still pretty small.
#
# Specify a percentage of zero in order to disable the automatic AOF
# rewrite feature.

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

是否开启重写

no-appendfsync-on-rewrite no

#重写的比例:当重写过后的文件需要再次重写时,就会以100%比例重新设置重写的规则

#第一次重写【64mb】—重写后—> 60m — 第二次重写【到达120m后触发】----> 120m —> 240m

auto-aof-rewrite-percentage 100

#当AOF文件超过64mb时,会进行重写

auto-aof-rewrite-min-size 64mb

4.5 AOF演示

  • 修改redis.conf配置文件
#开启AOF
appendonly yes
  • 重启redis
docker-compose restart
  • 监听appendonly.aof
#进入data目录,使用tail命令
cd data
tail -f appendonly.aof
  • 另开一个客户端连接,向redis中写入数据
appendonly.aof文件中产生了内容

文件内容

*2       #下面的指令由2个单词组成,经过分析,就是执行了【select 0】选择0号数据库
$6		 #第一个单词由6个字符组成
SELECT
$1       #第二个单词由1个字符组成
0
*3       #下面的指令由3个单词组成
$3       #第一个单词由3个字符组成
set
$2       #第二个单词由2个字符组成
k1
$2       #第三个单词由2个字符组成
v1     
*3       #下面的指令由3个单词组成
$3       #第一个单词由3个字符组成
set     
$4       #第二个单词由4个字符组成
name     
$7       #第二个单词由7个字符组成
zhansan

4.6 压缩AOF文件

  • 只需要在redis的客户端输入命令
bgrewriteaof
  • aof文件被压缩了,压缩后的格式跟dump.rdb文件是一致,使用的是二进制存储

5、混合持久化

5.1 概述

  • 同时开启了RDB和AOF
  • Redis4.0版本后默认采用的就是混合持久化

5.2 redis.conf

# When rewriting the AOF file, Redis is able to use an RDB preamble in the
# AOF file for faster rewrites and recoveries. When this option is turned
# on the rewritten AOF file is composed of two different stanzas:
#
#   [RDB file][AOF tail]
#
# When loading Redis recognizes that the AOF file starts with the "REDIS"
# string and loads the prefixed RDB file, and continues loading the AOF
# tail.
aof-use-rdb-preamble yes

#开启混合持久化

aof-use-rdb-preamble yes

  • 混合持久化,会优先加载appendonly.aof 的配置文件,再加载RDB,使用RDB加载时只会加载不同的数据。

6、RDB和AOF的选择

  • RDB:dump.rdb文件小【因为存储的是二进制】,加载慢,读取效率低
    • 如果追求性能,对数据安全要求不高,可以选择RDB
  • AOF:appendonly.aof文件大【因为在没重写前,使用的是命令存储】,如果开启AOF,会优加载appendonly.aof,且读取效率比较高
    • 如果性能和安全双追求,那么选择混合持久化【开启AOF,RDB开不开都行】

十二、Redis高可用【集群搭建】

1、高可用概述

  • 在分布式系统中,保证我们的应用能够做到4个9,甚至5个9

    • 一天中,我们的应用99.99%可用,甚至99.999%可用
  • 单节点在分布式系统中,会存在如下问题

    • 单点故障
    • 性能瓶颈
    • 数据安全
单节点【一台服务器】
在这里插入图片描述

2、主从架构

主从架构
在这里插入图片描述
全量复制跟增量复制
在这里插入图片描述
  • 全量复制:从节点通过RDB把主节点数据全部接收, 并写入到自己内存中。如果中间断掉了,那么重连后,会续传
  • 增量复制:从节点跟主节点之间如果断开了,再次连接上的时候,就会执行增量复制,是把主节点新增加的数据接收并写入到内存中,而不是把全部数据再接收一遍。

2.1 搭建主从架构【docker】

2.1.1 docker-compose.yml
  • 存放目录:/opt/docker/redis_master_slave/
version: "3.1"
services:
  redis1:
    image: daocloud.io/library/redis:5.0.8
    restart: always
    container_name: redis1
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7001:6379
    volumes:
      - ./conf/redis1.conf:/usr/local/redis/redis.conf
    command: ["redis-server","/usr/local/redis/redis.conf"]
  redis2:
    image: daocloud.io/library/redis:5.0.8
    restart: always
    container_name: redis2
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7002:6379
    volumes:
      - ./conf/redis2.conf:/usr/local/redis/redis.conf
    links:
      #在从节点的配置文件中的master就相当于是redis1服务
      - redis1:master
    command: ["redis-server","/usr/local/redis/redis.conf"]
  redis3:
    image: daocloud.io/library/redis:5.0.8
    restart: always
    container_name: redis3
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7003:6379
    volumes:
      - ./conf/redis3.conf:/usr/local/redis/redis.conf
    links:
      - redis1:master
    command: ["redis-server","/usr/local/redis/redis.conf"]
2.2.2 三个节点的配置文件

存放目录:/opt/docker/redis_master_slave/conf

  • redis1.conf
#bind 127.0.0.1
#关闭保护模式
protected-mode no
  • redis2.conf 和 redis3.conf
#bind 127.0.0.1
#关闭保护模式
protected-mode no

#replicaof 主节点的ip 主节点的端口,这里的master是在yml文件中配置的主节点别名
replicaof master 6379
2.2.3 启动三个节点
  • docker-compose up -d
2.2.4 分别进入到三个节点的容器内部,查看其信息
  • docker exec -it 容器名称 bash
主节点【info replication】
在这里插入图片描述
在这里插入图片描述
redis2从节点【info replication】
在这里插入图片描述
在这里插入图片描述
redis3从节点【info replication】
在这里插入图片描述

3、哨兵机制

3.1 问题

  • 在主从架构中,主节点负责读写,从节点只负责读
    • 那么,如果主节点宕机了,那么整个Redis架构就不能执行写操作了
    • 使用哨兵就可以解决这个问题

3.2 哨兵概述

哨兵工作原理
在这里插入图片描述
  • 搭建哨兵
    • https://www.cnblogs.com/JulianHuang/p/12650721.html

4、redis-cluster

4.1 工作原理

原理图
在这里插入图片描述
集群特点
在这里插入图片描述

4.2 搭建

4.2.1 准备docker-compose.yml文件
version: "3.1"
services:
  redis1:
    image: daocloud.io/library/redis:5.0.8
    restart: always
    container_name: redis1
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7001:7001
      - 17001:17001
    volumes:
      - ./conf/redis1.conf:/usr/local/redis/redis.conf
    command: ["redis-server","/usr/local/redis/redis.conf"]
  redis2:
    image: daocloud.io/library/redis:5.0.8
    restart: always
    container_name: redis2
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7002:7002
      - 17002:17002
    volumes:
      - ./conf/redis2.conf:/usr/local/redis/redis.conf
    command: ["redis-server","/usr/local/redis/redis.conf"]  
  redis3:
    image: daocloud.io/library/redis:5.0.8
    restart: always
    container_name: redis3
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7003:7003
      - 17003:17003
    volumes:
      - ./conf/redis3.conf:/usr/local/redis/redis.conf
    command: ["redis-server","/usr/local/redis/redis.conf"]  
  redis4:
    image: daocloud.io/library/redis:5.0.8
    restart: always
    container_name: redis4
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7004:7004
      - 17004:17004
    volumes:
      - ./conf/redis4.conf:/usr/local/redis/redis.conf
    command: ["redis-server","/usr/local/redis/redis.conf"]  
  redis5:
    image: daocloud.io/library/redis:5.0.8
    restart: always
    container_name: redis5
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7005:7005
      - 17005:17005
    volumes:
      - ./conf/redis5.conf:/usr/local/redis/redis.conf
    command: ["redis-server","/usr/local/redis/redis.conf"]  
  redis6:
    image: daocloud.io/library/redis:5.0.8
    restart: always
    container_name: redis6
    environment:
      - TZ=Asia/Shanghai
    ports:
      - 7006:7006
      - 17006:17006
    volumes:
      - ./conf/redis6.conf:/usr/local/redis/redis.conf
    command: ["redis-server","/usr/local/redis/redis.conf"]  
4.2.2 准备配置文件
  • redis1.conf
# redis.conf
# 指定redis的端口号
port 7001
# 开启Redis集群
cluster-enabled yes
# 集群信息的文件
cluster-config-file nodes-7001.conf
# 集群的对外ip地址
cluster-announce-ip 192.168.145.136
# 集群的对外port
cluster-announce-port 7001
# 集群的总线端口
cluster-announce-bus-port 17001
其他5个配置文件,跟redis1.conf类似,只需要修改文件名及相关端口号即可
在这里插入图片描述
4.2.3 执行docker-compose up -d
4.2.4 进入任何一台redis节点,执行搭建集群的命令
docker exec -it redis5 bash

redis-cli --cluster create 192.168.145.136:7001 192.168.145.136:7002 192.168.145.136:7003 192.168.145.136:7004 192.168.145.136:7005 192.168.145.136:7006 --cluster-replicas 1

#192.168.145.136 redis节点的ip
4.2.5 需要输入yes,后面就会自动搭建

在这里插入图片描述

4.3 redis-cli连接redis-cluster

#-c : 代表连接的是集群
redis-cli -c -h ip -p port
操作集群【会根据key计算出槽位,然后跳转到指定槽位对应的节点进入对应的操作】
在这里插入图片描述

4.4 Java连接redis-cluster

  • application.yml
spring:
  redis:
    cluster:
      nodes: 192.168.145.136:7001,192.168.145.136:7002,192.168.145.136:7003
  • 测试
@SpringBootTest
class SpringbootRedisDemoApplicationTests {

    @Autowired
    RedisTemplate redisTemplate;

    /**
     *
     **/
    @Test
    public void clusterTest() throws Exception {

        redisTemplate.opsForValue().set("address", "李四");

        String value = (String) redisTemplate.opsForValue().get("address");
        System.out.println(value);

    }
    
}

十三、Redis淘汰策略

1、概述

  • 当Redis内存达到一定程序时【内存分配可以在配置文件指定】,Redis会对内存进入优先,以此来满足能够存入新的数据。这种行为就是Redis淘汰策略【机制】

2、内存管理

############################## MEMORY MANAGEMENT ################################


# Set a memory usage limit to the specified amount of bytes.
# When the memory limit is reached Redis will try to remove keys
# according to the eviction policy selected (see maxmemory-policy).

#设置内存使用限制到指定的字节数。当内存达到限制时,Redis将尝试删除键,根据选择的淘汰策略(参见maxmemory-policy)。

# If Redis can't remove keys according to the policy, or if the policy is
# set to 'noeviction', Redis will start to reply with errors to commands
# that would use more memory, like SET, LPUSH, and so on, and will continue
# to reply to read-only commands like GET.

#如果没有明确指定淘汰策略,Redis默认采用的是'noeviction', 只可以读,不能再写

# This option is usually useful when using Redis as an LRU or LFU cache, or to
# set a hard memory limit for an instance (using the 'noeviction' policy).
#
# WARNING: If you have replicas attached to an instance with maxmemory on,
# the size of the output buffers needed to feed the replicas are subtracted
# from the used memory count, so that network problems / resyncs will
# not trigger a loop where keys are evicted, and in turn the output
# buffer of replicas is full with DELs of keys evicted triggering the deletion
# of more keys, and so forth until the database is completely emptied.
#
# In short... if you have replicas attached it is suggested that you set a lower
# limit for maxmemory so that there is some free RAM on the system for replica
# output buffers (but this is not needed if the policy is 'noeviction').
#
# maxmemory <bytes>

#如果指定最大内存,那么redis就会选择相应的淘汰策略【这里的淘汰策略是指LRU或者LFU】来进行key的删除,直到能够满足redis的最大内存够用

# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select among five behaviors:
#
# volatile-lru -> Evict using approximated LRU among the keys with an expire set.【一般会选择】
# allkeys-lru -> Evict any key using approximated LRU.
# volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# volatile-random -> Remove a random key among the ones with an expire set.
# allkeys-random -> Remove a random key, any key.
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# noeviction -> Don't evict anything, just return an error on write operations.
#

- volatile-lru:在内存不足时,Redis会在设置过了生存时间的key中干掉一个最近最少使用的key。key1:10m 只用1次 key2:10m只用了2次(建议使用这种)
- allkeys-lru:在内存不足时,Redis会在全部的key中干掉一个最近最少使用的key。

- volatile-lfu:在内存不足时,Redis会在设置过了生存时间的key中干掉一个最近最少频次使用的key。使用10个key,key1用了2次 key2 用了1次
- allkeys-lfu:在内存不足时,Redis会在全部的key中干掉一个最近最少频次使用的key。

- volatile-random:在内存不足时,Redis会在设置过了生存时间的key中随机干掉一个。
- allkeys-random:在内存不足时,Redis会在全部的key中随机干掉一个。

- volatile-ttl:在内存不足时,Redis会在设置过了生存时间的key中干掉一个剩余生存时间最少的key。
- noeviction:(默认)在内存不足时,直接报错。只读不写,写会报错

# LRU means Least Recently Used
# LFU means Least Frequently Used

- LRU 算法(Least Recently Used,最近最少使用)淘汰很久没被访问过的数据,以最近一次访问时间作为参考。
- LFU 算法(Least Frequently Used,最不经常使用)淘汰最近一段时间被访问次数最少的数据,以次数作为参考。

#
# Both LRU, LFU and volatile-ttl are implemented using approximated
# randomized algorithms.
#
# Note: with any of the above policies, Redis will return an error on write
#       operations, when there are no suitable keys for eviction.
#
#       At the date of writing these commands are: set setnx setex append
#       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
#       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
#       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
#       getset mset msetnx exec sort
#
# The default is:
#
# maxmemory-policy noeviction

# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated
# algorithms (in order to save memory), so you can tune it for speed or
# accuracy. For default Redis will check five keys and pick the one that was
# used less recently, you can change the sample size using the following
# configuration directive.
#
# The default of 5 produces good enough results. 10 Approximates very closely
# true LRU but costs more CPU. 3 is faster but not very accurate.
#
# maxmemory-samples 5
# 取样基数,也就是在5key中去选择要删除的key

# Starting from Redis 5, by default a replica will ignore its maxmemory setting
# (unless it is promoted to master after a failover or manually). It means
# that the eviction of keys will be just handled by the master, sending the
# DEL commands to the replica as keys evict in the master side.
#
# This behavior ensures that masters and replicas stay consistent, and is usually
# what you want, however if your replica is writable, or you want the replica to have
# a different memory setting, and you are sure all the writes performed to the
# replica are idempotent, then you may change this default (but be sure to understand
# what you are doing).
#
# Note that since the replica by default does not evict, it may end using more
# memory than the one set via maxmemory (there are certain buffers that may
# be larger on the replica, or data structures may sometimes take more memory and so
# forth). So make sure you monitor your replicas and make sure they have enough
# memory to never hit a real out-of-memory condition before the master hits
# the configured maxmemory setting.
#
# replica-ignore-maxmemory yes

3、Redis在什么时候会使用淘汰策略

  • 惰性删除:当读取一个key,发现这个key已经过期了,那么redis会去删除这个key
  • 主动删除:redis会执行一个后台线程,在一定时间范围内根据淘汰策略去删除key

4、会删除哪些key

  • 会删除过期的key
  • 会不会删除没过期的key
    • 根据淘汰策略有可能会删除没过期的key

5、redis如何选择要删除的key

  • 可以指定删除的基数,默认值是5

根据淘汰策略,把redis的key分为5个一组,然后在5个key找到要删除的key,然后把它删掉

  • Redis经过测试,发现5是效率还不错,定位删除key的精准度也还高

十四、使用Redis存在的问题

  • 使用缓存的步骤

    1. 用户发送查询请求,去查询缓存
    2. 缓存
      1. 有:直接返回
      2. 没有:查询数据库
        1. 有:存入缓存,返回数据给用户
        2. 没有:返回null

1、穿透

1.1 概述

  • 查询一个缓存跟数据库都不存在的数据【恶意攻击】
    • 先查询缓存,再查询数据库
    • 那么就会发现无论如何,最终一定要去访问数据库
  • 模拟缓存穿透
/**
 * 模拟缓存穿透
 * @author ghy
 * @version 1.0
 * @date 2022-02-18
 **/
@SpringBootTest
public class RedisChuanTouTest {


    @Autowired
    RedisTemplate redisTemplate;

    /**
     *
     **/
    @Test
    public void chuanTouTest() throws Exception {

        //数据库有10条记录,id分别为1-10

        for (int i = 11; i <= 20; i++) {
            Product product = (Product) redisTemplate.opsForValue().get("k" + i);
            if(ObjectUtils.isEmpty(product)) {
                System.out.println("从数据库查询");
                //缓存中没有,去查询数据库【模拟查询数据库】
                Product productDB = selectFromDB(i);
                //放入缓存
                redisTemplate.opsForValue().set("k" + i, productDB);
                System.out.println(productDB);
                continue;
            }
            System.out.println("从缓存中查询");
        }

    }

    /**
     * 数据库有10条记录,id分别为1-10
     *
     * 模拟从数据库查询
     * @param id
     * @return
     */
    private Product selectFromDB(Integer id) {
        if(id > 10) {
            return null;
        }
        return new Product(id, id + "_pro", new Date(), 100D * id);
    }


}

1.2 解决方案

  • 方案一:如果数据库没有,就放一个无用的对象到缓存中,并设置过期时间,以便后期数据库会出现准备的数据放入到缓存中
方案一
在这里插入图片描述
  • 方案二:布隆过滤器
布隆过滤器
在这里插入图片描述

向缓存中增加数据时,会通过布隆过滤器经过多个无偏hash函数对key进行计算,并对位数组长度取模,把每个得到值作为位数组的索引找到对应的位置,把值改成1

向查询时,通过布隆过滤器经过多个无偏hash函数对key进行计算,并对位数组长度取模,找到对应索引的值,判断是否至少有一个为0,那么就可以判断该key在缓存中一定不存在。那么如果都是1,也不代表在缓存一定存在,只是有可能存在。因为这些1有可能是别的那些key正为因为hash计算得到了相同索引位置进行修改为1的操作

  • 具体实现

先向布隆过滤器中存储Redis的key,用于hash计算

@Test
public void chuanTouTest() throws Exception {

    //1.创建Redisson的config对象
    Config config = new Config();
    //2.设置redis服务器信息
//        config.useClusterServers().addNodeAddress("redis://172.16.253.67:6379","redis://172.16.253.67:6380")
    config.useSingleServer().setAddress("redis://192.168.145.136:6379");
    config.useSingleServer().setPassword("java2107");
    //3.获得redisson对象
    RedissonClient redisson = Redisson.create(config);
    //4.使用布隆过滤器
    RBloomFilter<Object> myBloom = redisson.getBloomFilter("myBloom");
    //5.初始化布隆过滤器:预计元素为100000000L,误差率为3%,根据这两个参数会计算出底层的bit数组大小
    myBloom.tryInit(100000000L,0.03);
    //6.模拟从数据库获取数据向布隆过滤器中插入数据,只插入redis中存取数据的key
    for (int i = 1; i <= 10; i++) {
        Product product = selectFromDB(i);
        myBloom.add("k" + product.getId());
    }

    //数据库有10条记录,id分别为1-10
    for (int i = 11; i <= 20; i++) {
        //布隆过滤器判断缓存中是否有数据
        boolean result = myBloom.contains("k" + i);
        if(result) {
            //1.1 缓存中有可能有
            Product product = (Product) redisTemplate.opsForValue().get("k" + i);
            if(ObjectUtils.isEmpty(product)) {
                System.out.println("从数据库查询---------->");
                //缓存中没有,去查询数据库【模拟查询数据库】
                Product productDB = selectFromDB(i);

                if(!ObjectUtils.isEmpty(productDB)) {
                    //放入缓存
                    redisTemplate.opsForValue().set("k" + i, productDB);
                }
                continue;
            } else {
                System.out.println("从缓存中获取------------------");
            }

        } else {
            //缓存中一定不存在
            System.out.println("缓存中不存在----->");
        }
    }

}

2、击穿【失效】

2.1 概述

  • 在同一时间点,缓存中有大量的key过期了,那么在这个时间节点,如果有很多的请求同时访问缓存时,发现缓存并没有数据,则会直接访问数据库,感觉就像缓存被击穿了

2.2 解决方案

  • 热点数据永不过期
  • 不同的key设置随机过期时间

3、雪崩

3.1 概述

  • 缓存由于某些原因,导致在某个时间点不可用,那么这个时间点的所有请求都会直接打到数据库,可能会造成数据库宕机
  • 集群

十五、应用场景

1、缓存中间件

  • 秒杀【库存】
  • 热点数据【京东首页】

2、分布式Session

  • 单点登录【SSO】:是一个独立的系统,所有节点的登录与认证都是由这个节点来完成的
    • 在单个节点进行过登录,那么在分布式的其他节点上也会直接登录

3、计数器

  • 播放数、点击量、点赞数、关注数、阅读量

  • 自增【incr】

#关注数
#关注
incr like:用户ID 

#取消关注
decr like:用户ID

4、分布式ID

  • 电商平台的订单号必须是唯一的

    • UUID:有字母,没办法排序
    • Redis的分布式ID
  • 自增【incr】

抢购中【订单号生成】
在这里插入图片描述

5、排行榜

  • 热点新闻

6、社交类需求

  • Set 可以实现交集,从而实现共同好友等功能,Set通过求差集,可以进行好友推荐,文章推荐。

    微博微信点赞、收藏、标签
    在这里插入图片描述

|

##  点赞
SADD  like:{消息ID}  {用户ID}
## 取消点赞
SREM like:{消息ID}  {用户ID}
## 检查用户是否点过赞
SISMEMBER  like:{消息ID}  {用户ID}
## 获取点赞的用户列表
SMEMBERS like:{消息ID}
## 获取点赞用户数 
SCARD like:{消息ID}
微博微关系
在这里插入图片描述
## A关注的人: ASet存在于redis数据库中的Set的key
ASet-> {B, D}
## C关注的人:
CSet--> {A, F, B, D}
## B关注的人: 
BSet-> {A, C, F, D, E)
## A和C共同关注: 
SINTER ASet CSet--> {B, D}
## A关注的人也关注他(C): 
SISMEMBER BSet C 
SISMEMBER DSet C
## A可能认识的人: 
SDIFF CSet ASet->(A, F}

7、购物车

可以使用hash来存储
在这里插入图片描述
  • 分为两类购物车

    • 用户登录后的购物车

      • cart:1
        • 商品ID【联想拯救者Y7000 1001】 数量 2
        • 商品ID【香奈尔2022款 1051】 数量 200
    • 用户未登录的购物车

      • cart:uuid
        • 商品ID【联想拯救者Y7000 1001】 数量 1
        • 商品ID【海尔冰箱 1097】 数量 1

      uuid:生成后,会把这个值以cookie的形式发给客户端,以cart:uuid值为key存入到redis。将来就可以获取对应的cookie【得到uuid】,然后加cart:前缀到redis中去查找。找到:有这个车,没找到:就没这个车

  • 有两个车了

    • 合并操作
      • 未登录的车合并到用户登录购物车
        • 怎么合?
          • 商品ID相同:数量相加,更新用户登录购物车中这个商品ID对应的数量
          • 商品ID不同,增加这个商品对应的数据到已登录的车中
    • 什么时候合并
      • 登录后
        • 异步合并【消息队列】
# 购物车操作
## 添加商品
hset cart:1 1001 1
## 增加数量
hincrby cart:1 1001 1
## 几类总数
hlen cart:1
## 删除商品
hdel cart:1 1001
## 获取购物车所有商品
hgetall cart:1

8、分布式锁

传统锁
在这里插入图片描述
分布式锁
在这里插入图片描述
Redisson实现分布式锁的原理
在这里插入图片描述
  • https://www.cnblogs.com/qdhxhz/p/11046905.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值