Spring面试3.0

本文深入探讨了Spring AOP的注解使用顺序,Spring 4到5版本中AOP的变化,以及Spring框架如何解决单例模式下的循环依赖问题。详细解析了循环依赖的三级缓存机制,并通过代码示例展示了其实现过程。此外,文章还介绍了Redis的五大数据类型及其应用场景,特别讲解了如何利用Redis实现分布式锁,包括不同版本的优化和问题解决,最后讨论了Redis的内存淘汰策略和LRU算法。
摘要由CSDN通过智能技术生成

Spring面试3.0

SpringAOP的顺序

  1. Aop的注解
    @Before
    @After
    @AfterReturning
    @AfterThrowing
    @Around

Spring4 正常
Spring 从4到5底层的aop发生了变化
在这里插入图片描述
Spring4异常
在这里插入图片描述
正常的执行顺序是这样的
在这里插入图片描述
Spring5和Spring4的对比
在这里插入图片描述

Spring的循环依赖

什么是Spring的循环依赖
说明:
多个bean之间相互依赖,形成了一个闭环。 比如:A依赖于B、B依赖于c、c依赖于A
说明图:
在这里插入图片描述

两种注入方式对循环依赖有什么影响
构造方法注入和set方法的注入
构造方法注入对循环依赖不友好

官网的循环依赖的解释

结论:
我们AB循环依赖问题只要A的注入方式是setter且singleton, 就不会有循环依赖问题

spring容器循环依赖报错演BeanCurrentlylnCreationException

ServiceA

import org.springframework.stereotype.Component;
@Component
public class ServiceA {

private ServiceB serviceB;

    public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}

ServiceB

import org.springframework.stereotype.Component;

@Component
public class ServiceB {

private ServiceA serviceA;

    public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}

ClientConstructor

在这里插入图片描述
一直无限的循环下去

下面是set方法来进行注入

ServiceA

import org.springframework.stereotype.Component;

@Component
public class ServiceA {

private ServiceB serviceB;

    public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
System.out.println("A 里面设置了B");
}
}

ServiceB

import org.springframework.stereotype.Component;

@Component
public class ServiceB {

private ServiceA serviceA;

    public void setServiceA(ServiceA serviceA) {
this.serviceA = serviceA;
System.out.println("B 里面设置了A");
}
}

ClientSet



public class ClientSet {
public static void main(String[] args) {

//创建serviceA
ServiceA serviceA = new ServiceA();

//创建serviceB
ServiceB serviceB = new ServiceB();

//将serviceA注入到serviceB中
serviceB.setServiceA(serviceA);

//将serviceB注入到serviceA中
serviceA.setServiceB(serviceB);

}
}

加上Spring容器过后两者的效果

单例模式支持循环依赖
多例模式不支持循环依赖

xml的配置

xml version="1.0" encoding="UTF-8"?>
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">



    
    


id="a" class="com.hhf.study.spring.circulardepend.A" >
        name="b" ref="b"/>
    

    id="b" class="com.hhf.study.spring.circulardepend.B">
        name="a" ref="a"/>
    



log4j.properties

### 设置###
log4j.rootLogger = debug,stdout,D,E

### 输出信息到控制抬 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

### 输出DEBUG 级别以上的日志到=E://logs/error.log ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = E://logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG 
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

### 输出ERROR 级别以上的日志到=E://logs/error.log ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =E://logs/error.log 
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR 
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r

ClientSpringContainer


import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


/**
 * nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
 * Error creating bean with name 'a': 578624778
 * Requested bean is currently in creation: Is there an unresolvable circular reference?
 *
 *
 * 只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,因为单例的时候只有一份,随时复用,那么就放到缓存里面
* 而多例的bean,每次从容器中荻取都是—个新的对象,都会重B新创建, 
* 所以非单例的bean是没有缓存的,不会将其放到三级缓存中。
*/
public class ClientSpringContainer {
public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
A a = context.getBean("a",A.class);
B b = context.getBean("b",B.class);
}
}

改变成多例模式过后

异常
在这里插入图片描述
Spring 通过建立三级缓存的方式来解决循环依赖

DefaultSingletonBeanRegistry

第一级缓存〈也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象(成品)
第二级缓存: earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整)(半成品)
第三级缓存: Map> singletonFactories,存放可以生成Bean的工厂(准备生产的原料)
在这里插入图片描述
在这里插入图片描述

讲解源码

1.实例化
内存中申请一块空间,租赁好了房子东西还没搬进去

然后进行初始化属性填充完成属性的赋值(决定该拿啥进去)

2.三级缓存+四大方法
在这里插入图片描述

1.getSingleton:希望从容器里面获得单例的bean,没有的话 2.doCreateBean: 没有就创建bean(三级缓存)
3.populateBean: 创建完了以后,要填充属性
4.addSingleton: 填充完了以后放到一级缓存,再添加到容器进行使用

第一层singletonObjects存放的是已经初始化好了的Bean,
第二层earlySingletonObjects存放的是实例化了,但是未初始化的Bean,
第三层singletonFactories存放的是FactoryBean。假如A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean

循环依赖的Spring用三级缓存解决的步骤

1 A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B
2 B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A
然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A
3 B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)
然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。

在这里插入图片描述

Bebug的使用方法
打断点的时候尽量不要将程序打通

debug的技巧

这只是在debug的细节方面的操作

  1. (Step Over)f6代表是单步,一步步走
  2. (Step into)f5代表是源码天生的自然进入,这是打debug本身JDK自带的源码,源码里面没有你自己所写的代码
  3. (Force Step Into)Alt+Shift+F7 这个是强制进入,这个一般用来debug强制进入自己所写的源代码
  4. 一般源码级别的调试, (Step into)f5足够了,但如果你要用(Force Step Into)Alt+Shift+F7 也完全可以。
  5. 如果找不到刚才停留的那一行的debug,如果断点打飞了怎么办,这个时候请选择Show Execution Point Alt+F10(也叫归位)
  6. 有用的就进去,没用的就跳出去

Spring的debug过程

特点:
就是找到程序输出的前一行代码并且打一个断点,然后继续运行一直找到程序运行结束的前一行代码,并且进入到源代码里面去一直到不能进入的时候就完成(目的就是看这个代码运行的时候经过了那些的过程和代码的流程)

Spring利用三级缓存来解决Spring循环依赖的全过程

debug图片
在这里插入图片描述
简短的语言说明全过程

1 A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B
2 B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A
然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A
3 B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)
然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。

和老师的对比一下
在这里插入图片描述

Spring三级缓存解决循环依赖的过程图解流程

redis

版本的升级说明
五大数据类型应用的情况在什么时候可以用到7
redis6.0.8Linux版本性能最好最稳定 redis.io官网地址
在这里插入图片描述
在这里插入图片描述

五大基本类型是怎么用的
细节:命令不区分大小写,而key区分大小写
help@类型名词 就是查看命令用户帮助手册

  1. String :
    set key value
    get key
    同时设置多个键值
    MSET key value [key value …]
    获取多个键值
    MGET key [key …]
    增加步长 INCR key
    INCRBY key increment
    递减数值
    DECR key
    减少指定的整数
    DECRBY key decrement
    获取字符串的长度
    STRLEN key
    用于分布式锁
    setnx key value 没有我创建有我不创建
    set key value [Ex seconds][PX milliseconds][NX|XX]
    EX:key在多少秒之后过期
    PX:key在多少毫秒之后过期
    NX:当key不存在的时候,才创建key,效果等同于setnx
    XX:当key存在的时候,覆盖key
    列子
    在这里插入图片描述
    应用场景
    商品编号,订单号采用INCR命令生成 INCR items:001 对001号的商品进行点赞点一下加一个1 get items:001是统计点赞的人数或者说是热度
    喜欢的文章

2.hash
对应的java的数据是Map<Strinhg ,Map<Object,Object>>
设置一个字段的值
HSET key field value
获取一个字段的值
HGET key field
一次设置多个字段的值
HMSET key field value [field value …]
一次获取多个字段的值
HMGETkey field [field …]
获取所有字段值
hgetall key
获取某个key内的全部数量
hlen
删除一个key
hdel
在这里插入图片描述
应用场景:
购物车中小厂
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.list
向列表左边添加元素:LPUSH key value [value …]
向列表右边添加元素:RPUSH key value [value …]
查看列表:LRANGE key start stop
获取列表中元素的个数:LLEN key
在这里插入图片描述
应用场景:
在这里插入图片描述
在这里插入图片描述

4.set
添加元素:SADD key member[member …]
删除元素:SREM key member [member …]
获取集合中的所有元素:SMEMBERS key
判断元素是否在集合中:SISMEMBER key member
获取集合中的元素个数:SCARD key
从集合中随机弹出一个元素元素不删除:SRANDMEMBER key [数字]
从集合中随机弹出一个元素弹出一个删除一个:SPOP key[数字]
在这里插入图片描述
集合运算:
集合的差集运算A-B:属于A但不属于B的元素构成的集合
SDIFF key [key …]
集合的交集运算A∩B:属于A同时也属于B的共同拥有的元素构成的集合:SINTER key [key …]
集合的并集运算AUB:属于A或者属于B的元素合并后的集合
SUNION key [key …]
应用背景:
微信抽奖小程序:

1 用户ID,立即参与按钮
sadd key 用户ID
2 显示已经有多少人参与了,上图23208人参加
SCARD key
3 抽奖(从set中任意选取N个中奖人)
SRANDMEMBER key 2 随机抽奖2个人,元素不删除
SPOP key3 随机抽奖3个人,元素会删除

微信朋友圈点赞
pub是消息朋友圈,msgID:点赞的用户ID在这里插入图片描述

微博好友关注社交关系
在这里插入图片描述

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

QQ内推可能认识的人
在这里插入图片描述

5.zset
添加元素:ZADD key score member [score member …]
按照元素分数从小到大的顺序 返回索引从start到stop之间的所有元素
ZRANGE key start stop [WITHSCORES]
获取元素的分数: ZSCORE key member
删除元素: ZREM key member [member …]
获取指定分数范围的元素: ZRANGEBYsCORE key min max [WITHSCORES] [LIMIT offset count]
增加某个元素的分数: ZINCRBY key increment member
获取集合中元素的数量:ZCARD key
获得指定分数范围内的元素个数:9zCOUNT key min max
按照排名范围删除元素:ZREMRANGEBYRANK key start stop
获取元素的排名:从小到大:ZRANK key member
从大到小:ZREVRANK key member

运用背景1:根据商品销售对商品进行排序显示
思路:定义商品销售排行榜(sorted set集合),key为goods:sellsort,分数为商品销售数量。
商品编号1001的销量是9,商品编号1002的销量是15
zadd goods:sellsort 9 1001 15 1002
有一个客户又买了2件商品1001,商品编号1001销量加2
zincrby goods:sellsort 2 1001
求商品销量前10名
ZRANGE goods:sellsort 0 10 withscores
在这里插入图片描述
运用背景2:抖音热搜
在这里插入图片描述

redis之分布式锁
锁的场景:
JVM层面的锁,单机版的锁

分布式微服务架构,拆分后各个微服务之间为了避免冲突和数据故障而加入的一种锁,分布式锁

mysql
zookeeper
redis

锁的应用场景

一般的互联网公司,大家都习惯用redis做分布式的锁
redis–》redlock–》redisson–》 lock/unlock
分布式锁
分布式锁需要注意的问题
商品超买
在这里插入图片描述
docker启动redis
docker exec -it redis redis-cli

秒杀超买的demo演示4
环境的搭建在这里插入图片描述在这里插入图片描述

分布式锁1:在高并发和多线程的影响下这段代码有什么问题?

        String result = stringRedisTemplate.opsForValue().get("goods:001");//看看库存的数量够不够
        int goodNumber = result == null ? 0 : Integer.parseInt(result);
        if (goodNumber > 0) {
            int realNumber = goodNumber - 1;
            //有商品获取结果存回redis
            stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
            System.out.println("成功买到商品,库存还剩下" + realNumber + "件" + "\t服务端口是" + serverPort);
            return "成功买到商品,库存还剩下" + realNumber + "件" + "\t服务端口是" + serverPort;


        } else {
            System.out.println("商品已经售完" + serverPort);

        }
        return "商品已经售完" + serverPort;

1,2两行存在没有原子性所以在多线程和高并发的状态下会出现超买的现象 单机版会存在的问题 synsynchronize(关键字) 和ReentrantLock(类)
synsynchronize:不见不散:就是里面的线程不释放会造成线程积压一大堆的线程卡在外面(加上synsynchronize代码块包裹代码)
ReentrantLock:过期不候:lock.trylock()设置时间过期过后就不候

分布式锁第二:分布式微服务架构有什么问题
Nginx的轮循操作
在这里插入图片描述
在高并发的环境下出现商品超买,就是一个优惠券卖了两次但是只支付了一次的钱公司的利益就亏空了(买一赠一)
在这里插入图片描述
在这里插入图片描述
jemter进行压测出现严重的商品超买现象
解决:分布式锁的命令setnx
加锁成功返回true 失败返回false

3.0加了setnx

 谁进来谁加锁
        //保证当前的线程的名字是不一样的
        String value=UUID.randomUUID().toString()+Thread.currentThread().getName();

        //flag 加锁成功为true则不进去  不成功为false则进去
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);//setNX
        //为false抢锁失败 则true进入 返回抢锁失败
        if (!flag){
            return "抢锁失败";
        }
        String result = stringRedisTemplate.opsForValue().get("goods:001");//看看库存的数量够不够
            int goodNumber = result == null ? 0 : Integer.parseInt(result);
            if (goodNumber > 0) {
                int realNumber = goodNumber - 1;
                //有商品获取结果存回redis
                stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
                System.out.println("成功买到商品,库存还剩下" + realNumber + "件" + "\t服务端口是" + serverPort);
                stringRedisTemplate.delete(REDIS_LOCK);//解锁
                return "成功买到商品,库存还剩下" + realNumber + "件" + "\t服务端口是" + serverPort;


            } else {
                System.out.println("商品已经售完" + serverPort);

            }
            return "商品已经售完" + serverPort;

4.0:1.0出现异常后可能无法释放锁必须要在代码层面finally释放锁,要保证lock和unlock必须要同时保证调用

5.0部署了微服务jar包的机器宕机了代码根本执行不到finally这块没办法保证解锁,这个key没有被删除,需要加入一个过期时间限定key到时间就删除

6.0:5.0问题加锁和设置过期时间要是原子性操作才可以

7.0在业务层面的话如果出现远程调用其他的微服务调用的时间大于当前业务锁的失效的临界时间,这个时候当前业务锁删除,但是当前的业务线程还在执行,执行完成删除锁,但是当前业务的锁已经过期,新的线程进来执行业务获取锁这时候当前的业务线程会删除掉新线程的锁(key)即张冠李戴删除别人的锁
在这里插入图片描述

 public String buy_Goods() {
        谁进来谁加锁
        //保证当前的线程的名字是不一样的
        String value=UUID.randomUUID().toString()+Thread.currentThread().getName();
        try {


            //flag 加锁成功为true则不进去  不成功为false则进去
            Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK,value,10L,TimeUnit.SECONDS);
            //为false抢锁失败 则true进入 返回抢锁失败
            if (!flag){
                return "抢锁失败";
            }
            String result = stringRedisTemplate.opsForValue().get("goods:001");//看看库存的数量够不够
            int goodNumber = result == null ? 0 : Integer.parseInt(result);
            if (goodNumber > 0) {
                int realNumber = goodNumber - 1;
                //有商品获取结果存回redis
                stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
                System.out.println("成功买到商品,库存还剩下" + realNumber + "件" + "\t服务端口是" + serverPort);
                return "成功买到商品,库存还剩下" + realNumber + "件" + "\t服务端口是" + serverPort;
            } else {
                System.out.println("商品已经售完" + serverPort);

            }
            return "商品已经售完" + serverPort;
        } finally {
            //保证删除的时候是自己的锁
            if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)){
                stringRedisTemplate.delete(REDIS_LOCK);//解锁
            }

        }
    }

8.0:在7.0的时候在finally块中判断和删除不是原子的操作
判断加锁和解锁的客户端不是一个客户端
lua脚本保证原子性删除
不用lua脚本你还有什么想法
可以用Redis自身的事务
redis事务的说明
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.1

finally {
            //保证删除的时候是自己的锁
//            if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)){
//                stringRedisTemplate.delete(REDIS_LOCK);//解锁
//            }
            while (true){
                //乐观锁我希望我删除的时候别人不动 如果别人动了,我再来尝试删除直到删除到我自己为止开启监控
                stringRedisTemplate.watch(REDIS_LOCK);
                if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value));
                {

                    //是否支持事务
                    stringRedisTemplate.setEnableTransactionSupport(true);
                    //开启事务
                    stringRedisTemplate.multi();
                    //删除有可能成功有可能失败 删除的返回值是key的数量是保存到一个对列里面
                    stringRedisTemplate.delete(REDIS_LOCK);
                    //提交 相当于存到对列里面
                    List<Object> list = stringRedisTemplate.exec();
                    if (list == null) {
                        continue;
                    }
                }
                stringRedisTemplate.unwatch();
                break;
            }

        }

8.2用lua脚本的处理

 finally {
            Jedis jedis= RedisUtils.getJedis();

            String script="if redis.call('get',KEYS[1]) == ARGV[1]"+
            "then"+
            "return redis.call('del',KEYS[1])"+ "else"+
            "   return 0"+
            "end";
            try {

                //相当于exec过后返回key的数量,有返回就解锁没有就解锁不成功
                Object o = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));
                if ("1".equals(o.toString())){
                    System.out.println("-----del redis  lock ok ");
                }else {
                    System.out.println("-----del redis  lock error" +
                            " ");
                }
            } finally {
                if (null!=jedis){
                    jedis.close();
                }
            }

        }

确保redisLock过期时间大于业务执行时间的问题?分布式情况下的远程调用会导致业务时间大于过期时间。
redis如何进行续期?

9.0
redis集群+CAP对比zookeeper
redis

AP:redis异步复制造成的锁丢失, 比如:主节点没来的及把刚刚set进来这条数据给从节点,就挂了。保证数据的高可用性而牺牲数据的一致性
主节点获取就返回
自己写的redis在redis集群的环境下不OK所以才用RedLock之Redisson落地的实践

zookiper

CP:保证数据的一致性牺牲数据的高可用性
主节点和其他的一起获取到再返回

Redisson

分布式锁的连接

使用优化

 谁进来谁加锁
        //保证当前的线程的名字是不一样的
        String value= UUID.randomUUID().toString()+Thread.currentThread().getName();
        ///++++++++
        RLock redissonLock = redisson.getLock(REDIS_LOCK);

        //+++++++++++++
        redissonLock.lock();
        try {



            String result = stringRedisTemplate.opsForValue().get("goods:001");//看看库存的数量够不够
            int goodNumber = result == null ? 0 : Integer.parseInt(result);
            if (goodNumber > 0) {
                int realNumber = goodNumber - 1;
                //有商品获取结果存回redis
                stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
                System.out.println("成功买到商品,库存还剩下" + realNumber + "件" + "\t服务端口是" + serverPort);
                return "成功买到商品,库存还剩下" + realNumber + "件" + "\t服务端口是" + serverPort;
            } else {
                System.out.println("商品已经售完" + serverPort);

            }
            return "商品已经售完" + serverPort;
        } finally {

            //+++++++++
            redissonLock.unlock();
        }
    }

9.1版本
容易报锁找不到的异常
在这里插入图片描述

public String buy_Goods(){

        String value = UUID.randomUUID().toString()+Thread.currentThread().getName();

RLock redissonLock = redisson.getLock(REDIS_LOCK_KEY);
redissonLock.lock();
        try{
            String result = stringRedisTemplate.opsForValue().get("goods:001");
            int goodsNumber = result == null ? 0 : Integer.parseInt(result);

            if (goodsNumber > 0){
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001",realNumber + "");
System.out.println("你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort);
                return "你已经成功秒杀商品,此时还剩余:" + realNumber + "件"+"\t 服务器端口: "+serverPort;
}else {
                System.out.println("商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort);
}
return "商品已经售罄/活动结束/调用超时,欢迎下次光临"+"\t 服务器端口: "+serverPort;

}finally {
//还在持有锁的状态,并且是当前线程持有的锁再解锁
if (redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()){
                redissonLock.unlock();
}

        }
    }

分布式锁的总结
自动延迟机制
看门狗的机制:

客户端1加锁的锁key默认生存时间才30秒,如果超过了30秒,客户端1还想一直持有这把锁,怎么办呢?
简单!只要客户端1一旦加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端1还持有锁key,那么就会不断的延长锁key的生存时间。

总结:
在这里插入图片描述
redis缓存过期淘汰策略
在这里插入图片描述
redis内存满了怎么办?

redis.conf的配置文件
在这里插入图片描述
内存的默认
在这里插入图片描述
一般配置redis设置内存为最大物理内存的四分之三
设置redis的内存大小
在这里插入图片描述
命令窗口修改
在这里插入图片描述
查看生产上的配置内存的大小info memory
在这里插入图片描述
redis打满了怎么办
在这里插入图片描述
结论:没有加上过期时间就会导致数据写满maxmemory 为了避免类似情况,引出下一章内存淘汰策略

缓存过期淘汰策略

淘汰的策略的类型
在这里插入图片描述
默认使用的策略类型是noeviction不再驱逐
redis过期键的删除策略
如果一个键是过期的过期之后是不是马上就被删除呢?
如果不是到底是什么时候被删除?是什么操作
定时删除
在这里插入图片描述
总结:对CPU不友好,用处理器性能换取存储空间(拿时间换空间)
懒性删除
在这里插入图片描述
总结:对memory不友好,用存储空间换取处理器性能(拿空间换时间)
取二者的折中:定期删除
在这里插入图片描述
仍然有漏洞:
1 定期删除时,从来没有被抽查到
2 惰性删除时,也从来没有被点中使用过
上述2步骤======> 大量过期的key堆积在内存中,导致redis内存空间紧张或者很快耗尽
必须要有一个更好的兜底方案…
即内存淘汰策略

内存淘汰策略

在这里插入图片描述
LRU:最近最少使用 时间上
LFU:最近最少使用 频率上
在这里插入图片描述
一般用allkeys-lru
怎么配置和修改呢?
命令:
在这里插入图片描述
设置,查看 ,查看内存信息
配置文件:redis.conf
在这里插入图片描述

lru算法

解释:LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,
选择最近最久未使用的数据予以淘汰。
来源:来源
思考
在这里插入图片描述
查询用hash+增删用链表
在这里插入图片描述
比如说当有人要调用时key2时直接将key2拉动到前面去

手写lru
案例1LinkedHashMap 依赖jdk
在这里插入图片描述

public class LRUCacheDemoK,V> extends LinkedHashMapK, V> {

private int capacity;//缓存坑位

public LRUCacheDemo(int capacity) {
super(capacity,0.75F,false);
        this.capacity = capacity;
}

@Override
protected boolean removeEldestEntry(Map.EntryK, V> eldest) {
return super.size() > capacity;
}

public static void main(String[] args) {
        LRUCacheDemo lruCacheDemo = new LRUCacheDemo(3);

lruCacheDemo.put(1,"a");
lruCacheDemo.put(2,"b");
lruCacheDemo.put(3,"c");
System.out.println(lruCacheDemo.keySet());

lruCacheDemo.put(4,"d");
System.out.println(lruCacheDemo.keySet());

lruCacheDemo.put(3,"c");
System.out.println(lruCacheDemo.keySet());
lruCacheDemo.put(3,"c");
System.out.println(lruCacheDemo.keySet());
lruCacheDemo.put(3,"c");
System.out.println(lruCacheDemo.keySet());
lruCacheDemo.put(5,"x");
System.out.println(lruCacheDemo.keySet());
}
}

案例二 不依赖jdk
在这里插入图片描述
代码

//map负责查找,构建一个虚拟的双向链表,它里面安装的就是一个个Node节点,作为数据载体。

//1.构造一个node节点作为数据载体
class NodeK, V>
    {
K key;
V value;
NodeK,V> prev;
NodeK,V> next;

        public Node(){
this.prev = this.next = null;
}

public Node(K key, V value)
        {
this.key = key;
            this.value = value;
            this.prev = this.next = null;
}

    }

//2 构建一个虚拟的双向链表,,里面安放的就是我们的Node
class DoubleLinkedListK, V>
    {
        NodeK, V> head;
NodeK, V> tail;

        public DoubleLinkedList(){
head = new Node();
tail = new Node();
head.next = tail;
tail.prev = head;
}

//3. 添加到头
public void addHead(NodeK,V> node)
        {
            node.next = head.next;
node.prev = head;
head.next.prev = node;
head.next = node;
}

//4.删除节点
public void removeNode(NodeK, V> node) {
            node.next.prev = node.prev;
node.prev.next = node.next;
node.prev = null;
node.next = null;
}

//5.获得最后一个节点
public Node getLast() {
return tail.prev;
}
    }

private int cacheSize;
Map,Node,Integer>> map;
DoubleLinkedList,Integer> doubleLinkedList;

    public LRUCacheDemo(int cacheSize)
    {
this.cacheSize = cacheSize;//坑位
map = new HashMap();//查找
doubleLinkedList = new DoubleLinkedList();
}

public int get(int key){
if (!map.containsKey(key)){
return -1;
}

        Node, Integer> node = map.get(key);
doubleLinkedList.removeNode(node);
doubleLinkedList.addHead(node);

        return node.value;
}

public void put(int key, int value)
    {
if (map.containsKey(key)){  //update
Node, Integer> node = map.get(key);
node.value = value;
map.put(key, node);

doubleLinkedList.removeNode(node);
doubleLinkedList.addHead(node);
}else {
if (map.size() == cacheSize)  //坑位满了
{
                Node,Integer> lastNode = doubleLinkedList.getLast();
map.remove(lastNode.key);
doubleLinkedList.removeNode(lastNode);
}

//新增一个
Node, Integer> newNode = new Node(key, value);
map.put(key,newNode);
doubleLinkedList.addHead(newNode);

}
    }

public static void main(String[] args) {

        LRUCacheDemo lruCacheDemo = new LRUCacheDemo(3);

lruCacheDemo.put(1,1);
lruCacheDemo.put(2,2);
lruCacheDemo.put(3,3);
System.out.println(lruCacheDemo.map.keySet());

lruCacheDemo.put(4,1);
System.out.println(lruCacheDemo.map.keySet());

lruCacheDemo.put(3,1);
System.out.println(lruCacheDemo.map.keySet());
lruCacheDemo.put(3,1);
System.out.println(lruCacheDemo.map.keySet());
lruCacheDemo.put(3,1);
System.out.println(lruCacheDemo.map.keySet());
lruCacheDemo.put(5,1);
System.out.println(lruCacheDemo.map.keySet());

}
}

/**
 * true
 * [1, 2, 3]
 * [2, 3, 4]
 * [2, 4, 3]
 * [2, 4, 3]
 * [2, 4, 3]
 * [4, 3, 5]
 * */

/**
 [1, 2, 3]
 [2, 3, 4]
 [2, 3, 4]
 [2, 3, 4]
 [2, 3, 4]
 [3, 4, 5]
 */


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值