redis从入门到精通

1 什么是NOSQL?

nosql(not only sql)不仅仅是sql。NoSQL,泛指非关系型的数据库。非关系型的数据库则由于其本身的特点得到了非常迅速的发展。
非关系数据库和关系型数据库之间的区别:
RDBMS—关系型数据

  • 高度组织化结构化数据。
  • 结构化查询语言(SQL) select
  • 数据和关系都存储在单独的表中。
  • 数据操纵语言DML,数据定义语言DDL
  • 严格的一致性. 事务 ACID
  • 基于事务
    NoSQL–非关系型数据库—缓存数据
  • 代表着不仅仅是SQL
  • 没有声明性查询语言
  • 键 - 值对存储 key value
  • 非结构化和不可预知的数据
  • 高性能,高可用性和可伸缩性。 适合搭建集群。
    NOSQL的产品
    Mongodb:

redis:

Hbase:针对大数据

2.redis简介

Redis是一种开放源代码(BSD许可)的内存中数据结构存储,用作数据库,缓存和消息代理。Redis提供数据结构,例如字符串,哈希,列表,集合,带范围查询的排序集合,位图,超日志,地理空间索引和流。Redis具有内置的复制,Lua脚本,LRU驱逐,事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster自动分区提供了高可用性。

3 redis的优点?

1.Redis读取的速度是110000次/s,写的速度是81000次/s

2.原子性 。Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。

3.支持多种数据结构:string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合)

4.持久化–磁盘,主从复制(集群)

5.官方不支持window系统,但是又第三方版本。 linux系统。

4 如何安装redis

安装redis的依赖。
yum install -y gc-c++

解压redis安装包

进入redis解压目录
make 编译c语言
make install 安装redis

启动redis
redis-server  redis配置文件名

在这里插入图片描述
连接redis
redis-cli 默认连接为127.0.0.1 端口号6379

redis-cli -h ip -p port 远程连接其他人的redis

5 简单了解一下redis.conf

5.1设置redis后台启动
在这里插入图片描述5.2修改端口号

在这里插入图片描述5.3设置远程连接。
在这里插入图片描述

6.安装redis客户端界面

在这里插入图片描述切记!记得关闭防火墙哦
防火墙相关命令

1. 查看防火墙的状态: systemctl status firewalld
2. 设置防火墙允许放行哪些端口号
firewall-cmd --add-port=8080/tcp --zone=public --permanent
3. 重启防火墙
systemctl restart firewalld

4. 查看哪些端口号被放行
firewall-cmd --list-port
5. 防火墙移除端口号
firewall-cmd --remove-port=8080/tcp --zone=public --permanent
6. 关闭防火墙----慎重【关闭】 开启还会启动
systemctl stop firewalld

7. redis常用命令。

7.1 对key操作的命令

  1. 查看所有的key [keys *]
  2. 为指定的key设置过期时间。 [expire key seconds]
  3. 查看key的剩余存活时间 ttl key 返回-1表示永远存在 -2不存在该key
  4. 删除指定的key delete key… 返回结果为删除的个数
  5. 判断指定的key是否存在 exists key

7.2 对redis数据库的操作
默认redis中由16库。可以通过修改redis配置文件更改数量。
在这里插入图片描述select n: 切换redis库。n[0~databases-1]
flushdb: 清空当前所在的库。
flushall: 清空所有库的内容。

8. redis支持的数据类型。

在这里插入图片描述
我们使用频率最高是: Strings字符串类型,Lists列表类型,Sets集合类型,Hashes哈希类型,Sorted Sets 有序集合。这里所谓的类型,就是value的类型。
8.1 Strings类型
它的value值为String类型,在实际开发中,它可以存储任意的数据类型。因为任何对象可以转换为json字符串。它的默认存放的大小512M.

关于string类型的命令由哪些?

set key value: 存储指定key和value的值。
get key: 获取指定key的value值。
mset key value key value…:存储多个key和value的值
mget key key …:获取多个key对应的value。
setnx key value: 如果指定的key存在,则不存入。如果不存在,则存入。
setex key second value: 存储指定的key和value并设置过期时间。
incr key: 对指定key的value递增。----点赞 收藏数 主键的递增
decr key: 对指定key的value递减

8.2 Hash哈希类型
它的value值为hash类型,hash类型由field和value组成。适合存储对象。

关于hash类型的命令由哪些?
hset key field value: 存储指定key的field和value值。
hget key field: 获取指定key的field对应的value值。
hgetall key: 获取在哈希表中指定 key 的所有字段和值
hkeys key: 获取指定key所有field名称
hvals key: 获取指定key的所有value值。
hdel key field: 删除指定key对应的field值

8.3 list列表类型
它的value类型为list列表类型,它的value可以是多个值,而且这些者可以重复,有序。一般使用在消息队列。

常用的方法
1.lpush key value value…:从左边存储指定key的对应列表值。
2.lpop key: 移出并获取列表的第一个元素
3.lrange key start end: 获取列表指定范围内的元素
4.lindex key index: 根据下标获取指定的元素
5.lset key index value: 修改指定坐标的元素内容

8.4 set集合类型
它的value类型是一个set集合类型,这个集合类型元素无需,且不能重复。求两个集合的交集
常见的命令
1.sadd key value…:向集合添加一个或多个成员
2.smembers key :返回集合中的所有成员
3.spop key: 随机获取某个元素并移除
4.sinter k1 k2.。。: 返回给定所有集合的交集

8.5 sort set有序集合
它的value类型为一个带分数的集合类型。
按照分数排序。应用在: 排行榜
zadd key score value score value…: 往redis中添加有序集合
zrange key start end: 获取指定返回的元素
ZREVRANGE k1 0 -1 WITHSCORES: 分数从高到低

9. redis的使用场景

1、热点数据的缓存: 减少对数据库的访问频率和减轻数据库的压力。
2. 限时业务的运用: 秒杀 存储登录者用户信息 存储短信验证码
3. 计数器相关问题: 点赞数 收藏数 播放量
4. 排行榜相关问题: sort set
5. 分布式锁:

10. redis的持久化

持久化:把内存中的数据库保存到磁盘上,防止数据的丢失。

redis支持的持久化方式两种:
(1)RDB:快照 其实就是把数据以快照的形式保存在磁盘上,什么是快照呢,你可以理解成把当前时刻的数据拍成一张照片保存下来。
(2)AOF :日志追加 记录服务器接受的每个写入操作,当服务器启动时再次加载该日志,会把日志中的命令重新执行一遍。
10.1 RDB快照持久化方式
10.1.1 RDB的触发方式
1.手动触发
【1】save堵塞型保存
[2]bgsave非堵塞型保存
2.自动触发
默认保存的文件名: dump.rdb 可以在redis.conf名称
在这里插入图片描述save
该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。具体流程如下:
在这里插入图片描述执行完成时候如果存在老的RDB文件,就把新的替代掉旧的。我们的客户端可能都是几万或者是几十万,这种方式显然不可取。
bgsave:
执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。
具体流程如下:
在这里插入图片描述自动触发rdb

修改redis的配置文件。

在这里插入图片描述
10.2 AOF日志追加持久化方式
默认该文件的名称:
在这里插入图片描述
默认aof不会开启。
在这里插入图片描述查看
在这里插入图片描述
在这里插入图片描述
aof模式会把每个写操作,记录到一个日志文件,当redis启动时会把该日志中每个指令重新执行一遍。 数据恢复速度慢。数据完整性高。

如果两则都使用,恢复数据时按照aof恢复。因为redis认为它的完整性比较好。大多数使用rdb。

11. redis集群模式

redis单机版,出现单机故障后,导致redis无法使用,如果程序使用redis,间接导致程序出错。

redis的集群模式:

  1. 主从复制模式
  2. 哨兵模式
  3. 集群化模式

12 主从复制模式

一主多从模式。一个主节点,多个从节点,那么主节点可以负责:读操作,写操作。 从节点只能负责读操作,不能负责写操作。 这样就可以把读的压力从主节点分摊到从节点,以减少主节点的压力。

当主节点执行完写命令,会把数据同步到从节点。
(1)如何搭建主从关系
原则:配从不配主。

准备: 一主二从-----3台----开三个虚拟机–为了节省虚拟机,在一台主机开启三个redis服务。

7001主节点 7002和7003作为从节点

修改端口号以及rdb文件的名称.
在这里插入图片描述
启动redis服务
在这里插入图片描述进入三个redis服务的客户端
在这里插入图片描述查看三个redis服务的主从关系
在这里插入图片描述配置7002和7003为7001的从节点
在这里插入图片描述
在这里插入图片描述通过实验: 我们在主节点上执行set k1 v1 可以发现从节点也存在该数据.证明同步到从节点。

可以看出主节点可以读和写。但是从节点只能读。

思考: 1. 如果主节点挂了,从节点会不会上位? 不会
2. 如果增加一个新的从节点,新从节点会不会把之前的数据同步过来。会

13 哨兵模式

由于主从模式,主节点单机后,从节点不会自动上位。 增加一个哨兵服务,该哨兵时刻监控master,如果master挂了,哨兵会在从节点中选举一位为主节点【哨兵投票机制】。
在这里插入图片描述
修改配置
在这里插入图片描述启动哨兵
redis-sentinel sentinel.conf

在这里插入图片描述测试:

主节点挂掉:
shutdown—redis-cli客户端

思考:
老大归位–不在是老大。

14 集群化模式

不管上面的主从还是哨兵模式,都无法解决单节点写操作的问题。如果这时写操作的并发比较高。这是可以实验集群化模式【去中心化模式】
在这里插入图片描述原理:
redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value时,redis 先对 key 使用 crc16 算法算出一个整数结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。

当你往Redis Cluster中加入一个Key时,会根据crc16(key) mod 16384计算这个key应该分布到哪个hash slot中,一个hash slot中会有很多key和value。你可以理解成表的分区,使用单节点时的redis时只有一个表,所有的key都放在这个表里;改用Redis Cluster以后会自动为你生成16384个分区表,你insert数据时会根据上面的简单算法来决定你的key应该存在哪个分区,每个分区里有很多key。

搭建:
三主三从:
6001 6002 6003 主节点
6004 6005 6006 从节点

修改redis.conf配置文件

Redis-1 防火墙–6379
bind 0.0.0.0
port 6001
daemonize yes
dbfilename dump6001.rdb
打开aof 持久化
appendonly yes
appendonly appendonly6001.aof

开启集群
cluster-enabled yes

集群的配置文件,该文件自动生成

cluster-config-file nodes-6001.conf

集群的超时时间
cluster-node-timeout 15000
在这里插入图片描述启动这六个redis服务
在这里插入图片描述分配槽
redis-cli --cluster create --cluster-replicas 1 192.168.231.130:6001 192.168.231.130:6002 192.168.231.130:6003 192.168.231.130:6004 192.168.231.130:6005 192.168.231.130:6006
在这里插入图片描述注意: 一定要保证每个节点都没有数据。

测试:
redis-cli -c -h 192.168.223.155 -p 6001
在这里插入图片描述可以把写操作均摊到不同的节点上,减轻了单个主节点的压力。

15. java连接redis.

redis支持哪些语言可以操作
在这里插入图片描述
在这里插入图片描述

16.使用jedis

(1)添加jedis依赖
(2)代码测试

@Test
    public void test01(){
        //连接redis--->必须保证你的redis服务运行远程连接。
        //该对象中把每个redis命令封装成对应的方法了。
        Jedis jedis=new Jedis("192.168.223.155",6380);
        //对于字符串操作的命令
        String s = jedis.set("k1", "v1");
        System.out.println(s);
        String setex = jedis.setex("k2", 30l, "v2");
        System.out.println(setex);
        Long aLong = jedis.setnx("k3", "v11");
        System.out.println(aLong);

        //对于hash操作
        jedis.hset("k4","name","刘德华");
        jedis.hset("k4","age","15");

        Map<String,String> map=new HashMap();
        map.put("name","张需要");
        map.put("age","28");
        jedis.hset("k5",map);


        jedis.close();

    }

17. 使用连接池连接redis

public void test02(){
        //创建连接池的配置类
        JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(20);
        jedisPoolConfig.setMinIdle(5);
        jedisPoolConfig.setMaxWait(Duration.ofMillis(3000));
        JedisPool jedisPool=new JedisPool(jedisPoolConfig,"192.168.223.155",6380);

        long start=System.currentTimeMillis();

        for (int i = 0; i <1000 ; i++) {
            //从jedis连接池获取资源
            Jedis jedis = jedisPool.getResource();
            String ping = jedis.ping();
            jedis.close();//是否资源到池子
        }

        long end=System.currentTimeMillis();

        System.out.println("总耗时:"+(end-start));

    }

18 java连接redis集群模式

 @Test
    public void test03(){
        Set<HostAndPort> nodes=new HashSet<>();
        nodes.add(new HostAndPort("192.168.223.155",6001));
        nodes.add(new HostAndPort("192.168.223.155",6002));
        nodes.add(new HostAndPort("192.168.223.155",6003));
        nodes.add(new HostAndPort("192.168.223.155",6004));
        nodes.add(new HostAndPort("192.168.223.155",6005));
        nodes.add(new HostAndPort("192.168.223.155",6006));
        JedisCluster jedisCluster=new JedisCluster(nodes);

        jedisCluster.set("k6","刘德华和闫克起");

        jedisCluster.close();

    }

19. springboot整合redis

springboot对redis的操作封装了两个StringRedisTemplate和RedisTemplate类,StringRedisTemplate是RedisTemplate的子类,StringRedisTemplate它只能存储字符串类型,无法存储对象类型。要想用StringRedisTemplate存储对象必须把对象转为json字符串。
在这里插入图片描述(1) 引入相关的依赖

   <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

(2)注入StringRedisTemplate该类对象

 @Autowired
 private StringRedisTemplate redisTemplate;

(3)使用StringRedisTemplate
该类把对每种数据类型的操作,单独封了相应的内部类。

package com.ykq.qy151redisspringboot;

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.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@SpringBootTest
class Qy151RedisSpringbootApplicationTests {

    //里面所有的key还是value field它的类型必须都是String类型。
    //因为key和value获取field他们使用的都是String的序列化方式
    @Autowired
    private StringRedisTemplate redisTemplate;


    @Test
    public void test02(){
        //对hash类型的操作。
        HashOperations<String, Object, Object> forHash = redisTemplate.opsForHash();
        forHash.put("k1","name","张三");
        forHash.put("k1","age","15");
        Map<String,String> map=new HashMap<>();
        map.put("name","李四");
        map.put("age","25");
        forHash.putAll("k2",map);

        Object o = forHash.get("k1", "name");
        System.out.println(o);

        Set<Object> k1 = forHash.keys("k1");
        System.out.println(k1);
        List<Object> k11 = forHash.values("k1");
        System.out.println(k11);


        //获取k1对于的所有的field和value
        Map<Object, Object> k12 = forHash.entries("k1");
        System.out.println(k12);

    }

    @Test
    void contextLoads() {
        //删除指定的key
//        redisTemplate.delete();
        //查看所有的key
//        redisTemplate.keys()
        //是否存在指定的key
//         redisTemplate.hasKey()

        //对字符串数据类型的操作ValueOperations
        ValueOperations<String, String> forValue = redisTemplate.opsForValue();
        //存储字符串类型--key  value long unit  setex();
        forValue.set("k1","张三",30, TimeUnit.SECONDS);
        //等价于setnx  存入成功返回true,失败返回false
        Boolean absent = forValue.setIfAbsent("k11", "李四", 30, TimeUnit.SECONDS);
        System.out.println(absent);
        Integer append = forValue.append("k11", "真帅");
        String key = forValue.get("k11");



    }

}

RedisTemplate

package com.ykq.qy151redisspringboot;

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.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@SpringBootTest
class Qy151RedisSpringbootApplicationTests02 {

     //当你存储的value类型为对象类型使用redisTemplate
    //存储的value类型为字符串。StringRedisTemplate  验证码
     @Autowired
     private RedisTemplate redisTemplate;

     @Test
     public void test01(){
         //必须认为指定序列化方式
         redisTemplate.setKeySerializer(new StringRedisSerializer());
         redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));

         //对String类型操作类
         ValueOperations forValue = redisTemplate.opsForValue();
         //redis中key和value都变成乱码了。
         //key和value都没有指定序列化方式,默认采用jdk的序列化方式。
         forValue.set("k1","张三");


         //value默认采用jdk,类必须实现序列化接口
         forValue.set("k2",new User(1,"刘德华",22));
     }

}

上面的RedisTemplate需要每次都指定key value以及field的序列化方式,能不能搞一个配置类,已经为RedisTemplate指定好序列化。以后再用就无需指定。

package com.ykq.qy151redisspringboot.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;


@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化  filed value
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.setHashKeySerializer(redisSerializer);
        return template;
    }
}

20. redis的使用场景

20.1 作为缓存
(1)数据存储在内存中,数据查询速度快。可以分摊数据库压力。
在这里插入图片描述
(2)什么样的数据适合放入缓存
查询频率比较高,修改频率比较低。
安全系数低的数据

(3)使用redis作为缓存
先导入相关依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

在数据库创建相关表
在这里插入图片描述Controller层

@RestController
@RequestMapping("order")
public class DeptController {
    @Autowired
    private DeptServer deptServer;

    @GetMapping("getById/{id}")
    public Dept getById(@PathVariable Integer id) {
        return deptServer.findById(id);
    }
    @GetMapping("deleteById/{id}")
    public String deleteById(@PathVariable Integer id){
        int i = deptServer.deleteById(id);
        return i>0?"删除成功":"删除失败";
    }
}

Dao层

@Mapper
public interface DeptMapper extends BaseMapper<Dept> {

}

实体类

@Data
@TableName(value = "tb_dept")
public class Dept {
    @TableId
    private Integer id;
    private String name;
    private String address;
}

Service层

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;


@Service
public class DeptService {

    @Autowired
    private DeptMapper deptMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    //业务代码
    public Dept findById(Integer id){
        ValueOperations forValue = redisTemplate.opsForValue();
        //查询缓存
        Object o = forValue.get("dept::" + id);
        //缓存命中
        if(o!=null){
            return (Dept) o;
        }
        Dept dept = deptMapper.selectById(id);
        if(dept!=null){
            //存入缓存中
            forValue.set("dept::"+id,dept,2, TimeUnit.HOURS);
        }
        return dept;
    }

    public int deleteById(Integer id){
        redisTemplate.delete("dept::"+id);
        int row = deptMapper.deleteById(id);
        return row;
    }

    public Dept insert(Dept dept){
        int insert = deptMapper.insert(dept);
        return dept;
    }

    public Dept update(Dept dept){
        ValueOperations forValue = redisTemplate.opsForValue();
        forValue.set("dept::"+dept.getId(),dept,2, TimeUnit.HOURS);
        int insert = deptMapper.updateById(dept);
        return dept;
    }
}

再创建一个config类处理缓存问题和序列化
查看的缓存: 前部分代码相同@before通知,后部分代码也相同后置通知。 我们可以AOP完成缓存代码和业务代码分离。
spring框架它应该也能想到。–使用注解即可完成。解析该注解。

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
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.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化  filed value
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.setHashKeySerializer(redisSerializer);
        return template;
    }
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(600)) //缓存过期10分钟 ---- 业务需求。
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))//设置key的序列化方式
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) //设置value的序列化
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
}

配置完记得开启注解
在这里插入图片描述资源文件相关配置

#redis
spring.redis.port=6379
spring.redis.host=192.168.231.130
#mysql
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/zuoye?characterEncoding=utf-8&serverTimezone=Asia/Shanghai

#sql
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

21.redis分布式锁

使用压测工具测试高并发下带来线程安全问题
在这里插入图片描述1. 解决方案: 使用 synchronized 或者lock锁

@Service
public class ProductStockServiceImpl2 implements ProductStockService {
    @Autowired
    private ProductStockDao productStockDao;

    @Override
    public  String decreaseStock(Integer productId) {
              synchronized (this) {
                  //查看该商品的库存数量
                  Integer stock = productStockDao.findStockByProductId(productId);
                  if (stock > 0) {
                      //修改库存每次-1
                      productStockDao.updateStockByProductId(productId);
                      System.out.println("扣减成功!剩余库存数:" + (stock - 1));
                      return "success";
                  } else {
                      System.out.println("扣减失败!库存不足!");
                      return "fail";
                  }
              }

    }
}

在这里插入图片描述
使用idea开集群项目
在这里插入图片描述在这里插入图片描述

在这里插入图片描述

22.redis的解决分布式锁的bug

在这里插入图片描述可以使用:redission依赖,redission解决redis超时问题的原理。
在这里插入图片描述为持有锁的线程开启一个守护线程,守护线程会每隔10秒检查当前线程是否还持有锁,如果持有则延迟生存时间。
使用:

 <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.4</version>
        </dependency>
        
         //获取redisson对象并交于spring容器管理
    @Bean
    public Redisson redisson(){
        Config config =new Config();
        config.useSingleServer().
                setAddress("redis://localhost:6379").
                //redis默认有16个数据库
                setDatabase(0);
        return (Redisson) Redisson.create(config);
    }
@Override
    public String decreaseStock(Integer productId) {
        //获取锁对象
        RLock lock = redisson.getLock("aaa::" + productId);
        try {
            lock.lock(30, TimeUnit.SECONDS);
            //查看该商品的库存数量
            Integer stock = productStockDao.findStockByProductId(productId);
            if (stock > 0) {
                //修改库存每次-1
                productStockDao.updateStockByProductId(productId);
                System.out.println("扣减成功!剩余库存数:" + (stock - 1));
                return "success";
            } else {
                System.out.println("扣减失败!库存不足!");
                return "fail";
            }
        } finally {
            lock.unlock();
        }
    }

23. redis中常见的面试题

23.1 什么是缓存穿透?怎么解决?

  1. 数据库中没有该记录,缓存中也没有该记录,这时由人恶意大量访问这样的数据。这样就会导致该请求绕过缓存,直接访问数据,从而造成数据库压力过大。

2.解决办法:
[1]在controller加数据校验。
[2]我们可以在redis中存入一个空对象,而且要设置过期时间不能太长。超过5分钟
[3]我们使用布隆过滤器。底层:有一个bitmap数组,里面存储了该表的所有id.

//伪代码
String get(String key) { //布隆过滤器钟存储的是数据库表钟对应的id
    String value = redis.get(key);  //先从缓存获取。  
    if (value  == null) { //缓存没有命中
        if(!bloomfilter.mightContain(key)){//查看布隆过滤器钟是否存在
            return null; 
        }else{
            value = db.get(key); //查询数据库
            redis.set(key, value); 
        }    
    }
    return value;
}

23.2 什么是缓存雪崩?如何解决?
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至宕机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
1.什么下会发生缓存雪崩:
[1]项目刚上线,缓存中没有任何数据
[2]缓存出现大量过期。
[3]redis宕机

2.解决办法:
1.上线前预先把一些热点数据放入缓存。
2.设置过期时间为散列值
3.搭建redis集群
23.3 什么是缓存击穿?如何解决?
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

缓存击穿解决方案:
1.设置永久不过期。【这种只适合内存】
2.使用互斥锁(mutex key)业界比较常用的做法。
在这里插入图片描述
23.4 Redis 淘汰策略有哪些?
在这里插入图片描述
至此redis告一段落了,看到这里的小伙伴恭喜你们挺不容易的!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿Ada

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值