redis的学习笔记

2 篇文章 0 订阅
1. 什么是NoSql

        NoSQL,泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,出现了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,特别是大数据应用难题。

** 非关系数据库和关系型数据库之间的区别:**

 RDBMS---关系型数据
	- 高度组织化结构化数据。
	- 结构化查询语言(SQL) select
	- 数据和关系都存储在单独的表中。
	- 数据操纵语言DML,数据定义语言DDL
	- 严格的一致性. 事务 ACID 
	- 基于事务

NoSQL--非关系型数据库---缓存数据
	- 代表着不仅仅是SQL
	- 没有声明性查询语言  
	- 键 - 值对存储 key value 
	-非结构化和不可预知的数据  
	-高性能,高可用性和可伸缩性。 适合搭建集群。

NOSQL的产品
	Mongodb
	redis
	Hbase:针对大数据

2. redis

2.1 介绍
	Redis是一种开放源代码(BSD许可)的内存中数据结构存储,用作数据库,缓存和消息代理。
	Redis提供数据结构,例如字符串,哈希,列表,集合,带范围查询的排序集合,位图,超日志,地理空间索引和流。
	Redis具有内置的复制,Lua脚本,LRU驱逐,事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster自动分区提供了高可用性。
2.2 redis的优点
1.Redis读取的速度是110000次/s,写的速度是81000次/s
2.原子性:Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
3.支持多种数据结构:string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合)
4.持久化--磁盘,主从复制(集群)
5.**官方不支持window系统**,但是有第三方版本---- linux系统。
2.3 redis的安装

1.安装redis的依赖。

		yum install -y gc-c++

在这里插入图片描述
2.解压redis安装包

	进入redis解压目录

随便放在一个文件夹里 我的是把压缩包放在 /usr/local
在这里插入图片描述
3. 编译c语言,安装redis --此过程有点慢

cd /redis-7.0.4      --切换到该目录下
make                    -- 编译c语言环境
make install        --安装redis

4.启动redis

		redis-server  redis.conf     --启动该服务

在这里插入图片描述
在这里插入图片描述

5.连接redis

	redis-cli   默认连接为127.0.0.1 端口号6379
	redis-cli -h ip  -p port  远程连接其他人的redis  注意要让防火墙放行连接其他人的端口号

在这里插入图片描述

2.4 修改某些redis的配置

我们可以把redis.conf 拖到window系统下 修改比较方便 修改成功后 再拖进linux系统下覆盖即可
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这里设置成功后,可以远程连接 如果拒绝连接 就把虚拟机关了 重新开启服务 连接即可
redis-cli -h ip -p port
在这里插入图片描述

2.5 常用指令

两个网址 可以学习常用指令

http://www.redis.net.cn/order/

http://redisdoc.com/

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

在这里插入图片描述

2.5.2 对redis数据库的操作
select n: 切换redis库。n[0~databases-1]
flushdb: 清空当前所在的库的内容
flushall: 清空所有库的内容
2.5.3 redis支持的数据类型

在这里插入图片描述
使用频率最高是: Strings字符串类型,Lists列表类型,Sets集合类型,Hashes哈希类型,Sorted Sets 有序集合。这里所谓的类型是value的类型

2.5.3.1 Strings类型

它的value值为String类型,在实际开发中,它可以存储任意的数据类型。因为任何对象都可以转换为json字符串。它的默认存放的大小512M.

1.	set key value: 存储指定key和value的值。
2.  get key: 获取指定key的value值。
3.  mset key value key value...:存储多个key和value的值
4.  mget key key ...:获取多个key对应的value。
5.  setnx key value: 如果指定的key存在,则不存入。如果不存在,则存入。
6.  setex key second value: 存储指定的key和value并设置过期时间。
7.  incr key: 对指定key的value递增。----可用于点赞 收藏数 主键的递增
8.  decr key: 对指定key的value递减

在这里插入图片描述

2.5.3.2 Hash哈希类型

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

1. hset key field value: 存储指定key的field和value值。
2. hget key field: 获取指定key的field对应的value值。
3. hgetall key: 获取在哈希表中指定 key 的所有字段和值
4. hkeys key: 获取指定key所有field名称
5. hvals key: 获取指定key的所有value值。
6. hdel key field: 删除指定key对应的field值

在这里插入图片描述

2.5.3.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: 修改指定坐标的元素内容

在这里插入图片描述

2.5.3.4 set集合类型

它的value类型是一个set集合类型,这个集合类型元素无需,且不能重复。可求交集和并集

1. sadd key value....:向集合添加一个或多个成员
2. smembers key :返回集合中的所有成员
3. spop key: 随机获取某个元素并移除
4. sinter k1 k2.。。:	返回给定所有集合的交集
5. smenmber key value 判断key中是否有存在value
6. sunion key key key:求并集

在这里插入图片描述

2.5.3.5 sort set有序集合

它的value类型为一个带分数的集合类型。按照分数排序。应用在: 排行榜

1. zadd key score value score value.....: 往redis中添加有序集合
2. zrange key start end: 获取指定返回的元素
3. zrevrange k1 0 -1 withscores: 分数从高到低

在这里插入图片描述

2.5.3.6 命令练习
1. 查看所有的   key keys *

2. 为指定的key设置过期时间 expire key seconds

3. 判断指定key是否存在 exists key

4. 删除指定的key  del  key

==========字符串===================
5. 存放指定key的字符串类型。 set key value

6.命令在指定的 key 不存在时,为 key 设置指定的值。 setnx key value

7.同时设置一个或多个 key-value 对。 mset key value key value

8.获取指定 key 的值。get key

9.将 key 中储存的数字值增一。incr key

10.将 key 中储存的数字值减一。 decr key

=================Redis 哈希(Hash) 命令=============
11. 将哈希表 key 中的字段 field 的值设为 value 。 hset key field value

12.获取存储在哈希表中指定字段的值 hget key field

13.获取所有哈希表中的字段 hkeys key

14.获取哈希表中所有值 hvals key

======================Redis 列表(List) 命令===========
15.在列表中添加一个或多个值  lpush key value value

16.移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
lpop key 

==================Redis 集合(Set) 命令==============
17.向集合添加一个或多个成员 sadd key value key value

18.判断 member 元素是否是集合 key 的成员 sismember key value

19.返回集合中的所有成员   smembers key

===================Redis 有序集合(sorted set) 命令
20.向有序集合添加一个或多个成员,或者更新已存在成员的分数  zadd key score value score value

21.返回有序集中指定区间内的成员,通过索引,分数从高到底 zrevrange  key v1 v2 withscores
2.6 redis的使用场景
1、热点数据的缓存: 减少对数据库的访问频率和减轻数据库的压力。
2. 限时业务的运用: 秒杀  存储登录者用户信息  存储短信验证码
3. 计数器相关问题: 点赞数 收藏数 播放量
4. 排行榜相关问题: sort set 
5. 分布式锁
2.7 redis的持久化
持久化:把内存中的数据库保存到磁盘上,防止数据的丢失。

redis支持的持久化方式两种:
(1)RDB:快照  其实就是把数据以快照的形式保存在磁盘上;快照可以理解成把当前时刻的数据拍成一张照片保存下来。
(2)AOF :日志追加 记录服务器接受的每个写入操作,当服务器启动时再次加载该日志,会把日志中的命令重新执行一遍。
2.7.1 RDB快照持久化方式
 RDB的触发方式
	 1.手动触发:
		[1]save堵塞型保存
		[2]bgsave非堵塞型保存
	 2.自动触发

默认保存的文件名: dump.rdb 可以在redis.conf名称
在这里插入图片描述
save
该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。具体流程如下:
在这里插入图片描述
执行完成时候如果存在老的RDB文件,就把新的替代掉旧的。我们的客户端可能都是几万或者是几十万,这种方式显然不可取。

bgsave:
执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。具体流程如下:在这里插入图片描述
自动触发rdb
修改redis的配置文件。

在这里插入图片描述

2.7.2 AOF日志追加持久化方式

在这里插入图片描述
在这里插入图片描述
查看
在这里插入图片描述

aof模式会把每个写操作,记录到一个日志文件,
当redis启动时会把该日志中每个指令重新执行一遍。 数据恢复速度慢。数据完整性高。


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


总结:
redis持久化方式 
    把内存中数据,持久化到磁盘的过程。
    [1]第一种RDB快照,优点:数据恢复比较快,缺点:数据完整性比较差
   	[2] 第二种AOF日志: 优点:数据完整比较高。缺点:日志文件比较大,而且恢复慢。
2.8 redis集群模式

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

redis的集群模式:

  1. 主从复制模式
  2. 哨兵模式
  3. 集群化模式
2.8.1 主从复制模式
一主多从模式  ---->   一个主节点,多个从节点。
主节点负责:读操作,写操作。 
从节点负责读操作,不能负责写操作。
这样就可以把读的压力从主节点分摊到从节点,以减少主节点的压力。
当主节点执行完写命令,会把数据同步到从节点。

(1)如何搭建主从关系

① 准备

原则:配从不配主。

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

端口号7001主节点  7002和7003作为从节点

修改端口号以及rdb文件的名称.

在这里插入图片描述准备三个 修改这两处即可
在这里插入图片描述
在这里插入图片描述

② 启动服务:
在这里插入图片描述
③ 进入三个redis服务的客户端
在这里插入图片描述
④ 配置7002和7003为7001的从节点
在这里插入图片描述
在这里插入图片描述
测试:
Ⅰ 在主节点和从节点中存入数据
Ⅱ 在主节点存入数据,在从节点中读出

在这里插入图片描述
在这里插入图片描述

	通过实验: 我们在主节点上执行set k1 v1 可以发现从节点也存在该数据.证明同步到从节点。
	可以看出主节点可以读和写。但是从节点只能读。



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

由于主从模式,主节点宕机后,从节点不会自动上位,但是增加一个哨兵服务,该哨兵时刻监控master,如果master挂了,哨兵会在从节点中选举一位为主节点【哨兵投票机制】。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

启动哨兵
	redis-sentinel sentinel.conf

测试:

	shutdown---redis-cli客户端

在这里插入图片描述
查看其它两个主机----这里记得重启服务
在这里插入图片描述
当7001启动后 7001会变为从节点
在这里插入图片描述

2.8.3 集群化模式

不管上面的主从还是哨兵模式,都无法解决单节点写操作的问题。如果这时写操作的并发比较高。这是可以采用集群化模式【去中心化模式】

在这里插入图片描述
原理:

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 从节点

1.  修改端口号       port 6001   

2. 开始保护线程      daemonize yes          

3. 修改rdb文件名     dbfilename dump6001.rdb

4. 打开aof 持久化    appendonly yes    

5. 修改aof文件名     appendonly appendonly6001.aof

6. 开启集群          cluster-enabled yes      

7. 修改集群配置文件   cluster-config-file nodes-6001.conf 841行

8.  集群的超时时间    cluster-node-timeout 15000     847行

步骤图如下
在这里插入图片描述
使用可以替换文本的工具 比如 hbuilderx 直接将6001替换为6002 6003 … 注意修改文件名

② 启动这六个redis服务
在这里插入图片描述
③分配槽–分主从5.0前很复杂

redis-cli --cluster create --cluster-replicas 1 192.168.1.5:6001 192.168.1.5:6002 192.168.1.5:6003 192.168.1.5:6004 192.168.1.5:6005 192.168.1.5:6006

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

可以把写操作均摊到不同的节点上,减轻了单个主节点的压力。

3 java连接redis

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

3.1 使用Java连接jedis

创建一个maven工程即可
(1)添加依赖

 <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>repMaven.junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

(2)单元测试

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

        //对hash的操作
        jedis.hset("k4","name","zs");
        jedis.hset("k4","age","17");

        Map<String,String> map = new HashMap<>();
        map.put("name","ls");
        map.put("age","19");
        jedis.hset("k5",map);
        jedis.close();
   	 }
	}
3.2 使用连接池连接redis
 @Test
    public void test02() {
        //创建连接的配置类
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(20); //最大空闲连接数
        jedisPoolConfig.setMinIdle(5);  //最小空闲连接数
        jedisPoolConfig.setMaxWaitMillis(3000);
        JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.174.128", 6380);
        //变成jedis对象 可继续操作 字符串 hash等
        Jedis jedis = jedisPool.getResource();
        String s = jedis.set("k1", "v1");
        System.out.println(s);
        jedis.close();
        
    }

效率比较
在这里插入图片描述
在这里插入图片描述
所以连接池的效率很高

3.3 java连接redis集群模式
@Test
    public void test03() {
        HashSet<HostAndPort> nodes = new HashSet<>();
        nodes.add(new HostAndPort("192.168.1.5",6001));
        nodes.add(new HostAndPort("192.168.1.5",6002));
        nodes.add(new HostAndPort("192.168.1.5",6003));
        nodes.add(new HostAndPort("192.168.1.5",6004));
        nodes.add(new HostAndPort("192.168.1.5",6005));
        nodes.add(new HostAndPort("192.168.1.5",6006));
        JedisCluster cluster = new JedisCluster(nodes);
        String s = cluster.set("name", "zs");
        System.out.println(s);
        cluster.close();

    }

如果连接不上 记得关闭防火墙 或者把6001-6006端口号放行

3.3 springboot整合redis
springboot对redis的操作封装了StringRedisTemplate和RedisTemplate类,
StringRedisTemplate是RedisTemplate的子类,
StringRedisTemplate它只能存储字符串类型,无法存储对象类型。
若用StringRedisTemplate存储对象必须把对象转为json字符串。

建立一个springboot工程,勾选 web和nosql中的redis即可
在这里插入图片描述
上述创建springboot项目的引入了相关依赖,若仅仅创建springboot项目 可手动在pom.xml中引入redis配置文件
(1)在application.properties中配置redis 端口号和ip 注意要开启redis

spring.redis.host=192.168.174.128
spring.redis.port=6380

(2)注入StringRedisTemplate类对象

 @Autowired
private StringRedisTemplate redisTemplate;

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

 @Test
    void contextLoads() {
        //删除指定的key
        Boolean k1 = redisTemplate.delete("k1");
        System.out.println(k1);
        //查看所有的key
        Set<String> keys = redisTemplate.keys("*");
        System.out.println(keys);
        //是否存在指定的key
        Boolean k11 = redisTemplate.hasKey("k1");
        System.out.println(k11);
    }
    /*对字符串类型的操作*/
    @Test
    void testString(){
        //对字符串数据类型的操作ValueOperations
        ValueOperations<String, String> forValue = redisTemplate.opsForValue();
        //存储字符串  ==> 等价于redis命令 setex
        forValue.set("k1","v1",30, TimeUnit.SECONDS);
        //等价于setnx 存在则不存入 不存在则存入
        forValue.setIfAbsent("k3", "v3", 30, TimeUnit.SECONDS);
        //在v3后面追加
        forValue.append("k3","33");
        //获取k1的value
        String k1 = forValue.get("k1");
        forValue.set("k5","2");
        //将k5的value 减1 ==>等价于decr
        Long k5 = forValue.decrement("k5");
        System.out.println(k5);

        //将k5的value 加1 ==>等价于incr
        Long k51 = forValue.increment("k5");
        System.out.println(k51);
    }
    /*对hash类型的操作*/
    @Test
    void testHash(){
        HashOperations<String, Object, Object> forHash = redisTemplate.opsForHash();
        //存储数据
        forHash.put("k6","name","zs");
        forHash.put("k6","age","18");

        HashMap<String, String> hashMap = new HashMap<>();
        hashMap.put("name","ls");
        hashMap.put("age","19");
        forHash.putAll("k7",hashMap);

        //获取元素
        Object o = forHash.get("k6", "name");
        System.out.println(o);

        //获取 k6的全部field
        Set<Object> k6 = forHash.keys("k6");
        System.out.println(k6);

        //获取k6的全部values
        List<Object> k61 = forHash.values("k6");
        System.out.println(k61);

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

    }

(4)使用RedisTemplate操作
该操作的类型的value不仅仅是字符串 可以是任意类型

@Autowired
    private RedisTemplate redisTemplate;
    
    /*对字符串类型的操作*/
    @Test
    void testString(){
        ValueOperations forValue = redisTemplate.opsForValue();
        forValue.set("k1","zs");
        
    }

当我们使用RedisTemplate 类存储字符串的时候会发现redis乱码
在这里插入图片描述
因为这里没有指定序列化,默认序列化为jdk 如下图
在这里插入图片描述
当我们指定序列化方式后就可以解决乱码问题

 //指定key的序列化方式
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        //指定value的序列化方式 因为redisTemplate可以存储任意类型的value 所以 value类型不确定 所以用object反射类
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
        ValueOperations forValue = redisTemplate.opsForValue();
        forValue.set("k1","zs");

在这里插入图片描述

//存储自定义类
        Student st1 = new Student(1, "zs", 18);
        forValue.set("k2",st1);

在这里插入图片描述

如果需要很多业务用到redis 则在很多地方使用重复代码,所以可自定义一个配置类解决该问题

@Configuration
public class RedisConf {
        @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(jackson2JsonRedisSerializer);
            //value序列化
            template.setValueSerializer(jackson2JsonRedisSerializer);
            //value hashmap序列化  filed value
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            template.setHashKeySerializer(redisSerializer);
            return template;
        }
}

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

在这里插入图片描述
在这里插入图片描述

3.4 redis的使用场景
3.4.1 作为缓存

(1)数据存储在内存中,数据查询速度快,可以分担数据库压力。
在这里插入图片描述
(2)什么样的数据适合放入缓存

查询频率比较高,修改频率比较低。

安全系数低的数据

(3)使用redis作为缓存 - 连接数据库
数据库:

/*
 Navicat Premium Data Transfer

 Source Server         : Michinaish
 Source Server Type    : MySQL
 Source Server Version : 80011
 Source Host           : localhost:3306
 Source Schema         : mp

 Target Server Type    : MySQL
 Target Server Version : 80011
 File Encoding         : 65001

 Date: 02/08/2022 21:01:17
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for tbl_dept
-- ----------------------------
DROP TABLE IF EXISTS `tbl_dept`;
CREATE TABLE `tbl_dept`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `deptname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of tbl_dept
-- ----------------------------
INSERT INTO `tbl_dept` VALUES (1, '研发部');
INSERT INTO `tbl_dept` VALUES (2, '市场部');
INSERT INTO `tbl_dept` VALUES (3, '财务部');

SET FOREIGN_KEY_CHECKS = 1;

① 引入sql相关的依赖

		<dependency>
            <groupId>repMaven.org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
            <version>2.6.7</version>
        </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.2</version>
        </dependency>

② 在application.properities配置数据库

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mp?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

③ 写实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tbl_dept")
public class Dept {
    @TableId
    private Integer id;

    @TableField(value = "deptname")
    private String deptName;
}

④ 业务代码

@RestController
@RequestMapping("/dept")
public class DeptController {

    /*查询*/
    @Autowired
    DeptService deptService;

    //通过id查询
    @GetMapping("getById/{id}")
    public Dept getById(@PathVariable Integer id){
        return deptService.findById(id);
    }
}
public interface DeptService {

    public Dept findById(Integer id);
}
@Service
public class DeptServiceImpl implements DeptService {

    @Autowired
    DeptMapper deptMapper;

    @Autowired
    private RedisTemplate redisTemplate;


    @Override
    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;
    }
}
@Mapper
public interface DeptMapper extends BaseMapper<Dept> {
}

这里手动设置缓存 运行
在这里插入图片描述

使用springboot自带的缓存机制 修改service层代码
实体类序列化 ‘
主启动类开启缓存注解

@Service
public class DeptServiceImpl implements DeptService {

    @Autowired
    DeptMapper deptMapper;


    @Override
    @Cacheable(cacheNames = "dept",key = "#id")
    public Dept findById(Integer id) {
        Dept dept = deptMapper.selectById(id);
        return dept;
    }
}

在这里插入图片描述
完整代码
controller层

@RestController
@RequestMapping("/dept")
public class DeptController {

    /*查询*/
    @Autowired
    DeptService deptService;

    //通过id查询
    @GetMapping("getById/{id}")
    public Dept getById(@PathVariable Integer id){
        return deptService.findById(id);
    }

    //通过id删除
    @GetMapping("delById/{id}")
    public String delById(@PathVariable Integer id){
        return deptService.delById(id)>0?"删除成功":"删除失败";
    }

    @RequestMapping("update")
    public Dept updateDept(Dept dept){
        System.out.println(dept);
        return deptService.update(dept);
    }

    @GetMapping("add/{dept}")
    public Dept addDept(@PathVariable Dept dept){
        System.out.println(dept.getDeptName());
        return deptService.insert(dept);
    }
}

Service层

public interface DeptService {

    public Dept findById(Integer id);

    public int delById(Integer id);

    public Dept update(Dept dept);

    public Dept insert(Dept dept);
}
@Service
public class DeptServiceImpl implements DeptService {

    @Autowired
    DeptMapper deptMapper;

    @Override
    //使用查询注解:cacheNames表示缓存的名称 key:唯一标志---dept::key
    //先从缓存中查看key为(cacheNames::key)是否存在,如果存在则不会执行方法体,如果不存在则执行方法体并把方法的返回值存入缓存中
    @Cacheable(cacheNames = "dept",key = "#id")
    public Dept findById(Integer id) {
        Dept dept = deptMapper.selectById(id);
        return dept;
    }

    //先删除缓存在执行方法体。
    @CacheEvict(cacheNames = "dept",key="#id")
    public int delById(Integer id){
        int row = deptMapper.deleteById(id);
        return row;
    }

    //这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存与数据库的同步更新。
    @CachePut(cacheNames = "dept",key="#dept.id")
    public Dept update(Dept dept){
        int row = deptMapper.updateById(dept);
        return dept;
    }

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

mapper层

@Mapper
public interface DeptMapper extends BaseMapper<Dept> {
}
3.4.2 分布式锁

使用压测工具测试高并发下带来线程安全问题

案例:卖票

数据库:

/*
 Navicat Premium Data Transfer

 Source Server         : Michinaish
 Source Server Type    : MySQL
 Source Server Version : 80011
 Source Host           : localhost:3306
 Source Schema         : mp

 Target Server Type    : MySQL
 Target Server Version : 80011
 File Encoding         : 65001

 Date: 03/08/2022 19:42:52
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for tbl_stock
-- ----------------------------
DROP TABLE IF EXISTS `tbl_stock`;
CREATE TABLE `tbl_stock`  (
  `productId` int(11) NOT NULL AUTO_INCREMENT,
  `num` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`productId`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of tbl_stock
-- ----------------------------
INSERT INTO `tbl_stock` VALUES (1, 10);

SET FOREIGN_KEY_CHECKS = 1;

controller层

@RestController
@RequestMapping("productStock")
public class ProductStockController {
    @Autowired
    private ProductStockService productStockService;
    //减库存
    @RequestMapping("decreaseStock/{productId}")
    public String decreaseStock(@PathVariable("productId") Integer productId){
        return productStockService.decreaseStock(productId);
    }

    @RequestMapping("aa")
    public String aa(){
        return "aa";
    }
}

service层和实现类

public interface ProductStockService {
    //减少库存
    public String decreaseStock( Integer productId);
}
@Service
public class ProductStockServiceImpl2 implements ProductStockService {
    @Autowired
    private ProductStockDao productStockDao;

    @Override
    public  String decreaseStock(Integer productId) {
              
                  //查看该商品的库存数量
                  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";
                  }
    }
}

mapper层

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wx.dao.ProductStockDao">

  <select id="findStockByProductId" resultType="integer">
     select num from tbl_stock where productId=#{productId}
  </select>

  <update id="updateStockByProductId">
      update tbl_stock set num=num-1  where productId=#{productId}
  </update>
</mapper>

配置类

server.port=1000
spring.redis.host=192.168.174.128
spring.redis.port=6380
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mp?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

使用idea开启两个项目
在这里插入图片描述
在这里插入图片描述

使用压力测试工具
在这里插入图片描述
在这里插入图片描述

发现线程不安全

解决方式: 使用 synchronized 或者lock锁

使用synchronized解决

注:加锁不能解决项目集群,此时锁无效 如下

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

此时我们要使用redis解决分布锁的问题

修改serviceImpl层的代码

package com.wx.service.impl;

import com.wx.dao.ProductStockDao;
import com.wx.service.ProductStockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

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

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public  String decreaseStock(Integer productId) {
        ValueOperations<String, String> forValue = redisTemplate.opsForValue();
        Boolean flag = forValue.setIfAbsent("aaa::" + productId, "~~~~~~~~~~~~~~~~~~~~~~");
            if(flag) {
                try {
                    //查看该商品的库存数量
                    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 {
                    redisTemplate.delete("aaa::" + productId);
                }
            }
        return "服务器正忙,请稍后在试......";
    }
}

在这里插入图片描述
注意:要开启虚拟机 连接配置类中的redis 还要开启nginx 在其配置类中加集群配置的端口号 如下图

在这里插入图片描述

3.4.3 redis的解决分布式锁的bug

redis解决集群项目 看似无懈可击 但是存在问题
redis分布式锁不能解决超时问题,分布式锁有一个超时问题,程序的执行如果超出了锁的超时时间就会出现问题

解决方案:
可以使用:redission依赖,redission解决redis超时问题的原理

在这里插入图片描述
为持有锁的线程开启一个守护线程,守护线程会每隔10秒检查当前线程是否还持有锁,如果持有则延迟生存时间。

(1) 引入依赖

		 <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.4</version>
        </dependency>

(2)配置类

@Bean
    public Redisson redisson(){
        Config config =new Config();
        config.useSingleServer().
                setAddress("redis://localhost:6379").
                //redis默认有16个数据库
                setDatabase(0);
        return (Redisson) Redisson.create(config);
    }

@Service
public class ProductStockServiceImpl_redission implements ProductStockService {

@Autowired
private ProductStockDao productStockDao;

@Autowired
Redisson redisson;

@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();
    }
}

}
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值