13.第三季--Spring+Redis

Spring内容

spring的AOP顺序

AOP的常用注解:

  • @Before 前置通知:目标方法之前执行
  • @After 后置通知:目标方法之后执行(始终执行)
  • @AfterReturning 返回后通知:执行方法结束前执行(异常不执行)
  • @AfterThrowing 异常通知:出现异常的时候执行
  • @Around 环绕通知:环绕目标方法执行

切面类:

execution表达式的解释第一个public int表示返回值类型(可以使用*来表示所有返回值),后紧跟包名.类名(类名这里也可以使用..*来表示包下的所有类),而后的.*(..)中前面的*表示所有的方法,后面的()表示参数,而..就表示所有的参数

@Aspect
@Component
public class MyASpect {
    @Before("execution(public int spring.test.demo.MyAOPImpl.*(..))")
    public void beforeNotify(){
        System.out.println("@Before方法被调用....");
    }
    @After("execution(public int spring.test.demo.MyAOPImpl.*(..))")
    public void afterNotify(){
        System.out.println("@After方法被调用....");
    }
    @AfterReturning("execution(public int spring.test.demo.MyAOPImpl.*(..))")
    public void afterReturningNotify(){
        System.out.println("@AfterReturning方法被调用....");
    }
    @AfterThrowing("execution(public int spring.test.demo.MyAOPImpl.*(..))")
    public void afterThrowingNotify(){
        System.out.println("@AfterThrowing方法被调用....");
    }
    @Around("execution(public int spring.test.demo.MyAOPImpl.*(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
        Object retValue=null;
        System.out.println("@Around环绕通知之前....");
        retValue=proceedingJoinPoint.proceed();
        System.out.println("@Around环绕通知之后....");
        return retValue;
    }
}

接口实现类

@Service
public class MyAOPImpl implements MyAOPService {
    @Override
    public int div(int x, int y) {
        int result=x/y;
        System.out.println("AOP实现方法被调用了....结果为:"+result);
        return 0;
    }
}

测试方法

    @Test
    void contextLoads() {
        myAOPService.div(3,3);
    }

以下测试为SpringBoot2.3.7的测试结果,无异常打印结果

@Around环绕通知之前....
@Before方法被调用....
AOP实现方法被调用了....结果为:1
@AfterReturning方法被调用....
@After方法被调用....
@Around环绕通知之后....

 异常打印结果

@Around环绕通知之前....
@Before方法被调用....
@AfterThrowing方法被调用....
@After方法被调用....

以下是springBoot1.5.9的打印结果

正常

@Around环绕通知之前....
@Before方法被调用....
AOP实现方法被调用了....结果为:1
@After方法被调用....

@AfterReturning方法被调用....
@Around环绕通知之后....

 异常

@Around环绕通知之前....
@Before方法被调用....
@After方法被调用....

@AfterThrowing方法被调用....

总结起来就是Spring4的After方法总在AfterThrowing和AfterReturning之前执行,Spring5的该方法在之后执行.

Spring的循环依赖

什么是循环依赖?

多个bean之间互相依赖形成了一个闭环.a依赖b,b依赖c,c依赖a,通常情况如果问spring容器里如何解决循环依赖问题,一定是指默认的单例bean中,属性相互引用的场景.如果是对于AB问题的singleton(a需要b,b需要a)的循环依赖,可以通过setter方法进行属性注入,而不是通过构造器注入.构造器循环依赖是无法解决的(这个很好理解,类似于死锁问题)

如果对于prototype的多对象类型

此时也会报BeanCurrentlyInCreationException循环依赖错误,所以对于原型场景循环依赖场景也是不适用的

spring是如何解决循环依赖问题的?

三级缓存-DefaultSingletonBeanRegistry

实例化--内存中申请一块内存空间 初始化---完成属性的各种赋值填充

  • 一级缓存:存放已经经历了完整生命周期的Bean对象(已经初始化完毕了)
  • 二级缓存:存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还没有填充完,实例化了但是没有初始化)
  • 三级缓存:存放可以生成Bean的工厂FactoryBean.如果A类实现了FactoryBean,那么依赖注入的时候注入的不是A类而是A类产生的Bean

流程说明:

  • A创建的过程中需要B,于是A把自己放到三级缓存里面去,去实例化B
  • B实例化的时候发现需要A,于是B先查一级缓存和二级缓存发现没有,再去查三级缓存,发现了A,然后将三级缓存里的A移动到二级缓存里面,并且删除三级缓存里的A
  • B初始化完毕,把自己放在一级缓存里此时B中的属性A仍然是创建中状态,接着回来接着创建A,此时B已经创建结束,直接从一级缓存里拿到B,完成创建,并且将A自己放到一级缓存里面

源码分析总结:

  • Spring创建bean主要分为两个步骤,创建原始bean对象,接着去填充对象和初始化
  • 每次创建bean之前我们都会从缓存中查看是否有该bean,因为是单例,只能有一个
  • 当我们创建A的原始对象之后,把它放到三级缓存中,接着该去填充对象属性了,这时候发现依赖了B,接着又去创建B对象,同样的流程创建完B填充属性的时候又发现了A又是同样的流程,然而不同的是这个时候可以在三级缓存中查到刚刚放进去的A,所以不需要继续创建,用三级缓存中的A完成了B的创建
  • B创建完成之后,A就可以完成填充属性的步骤了,闭环完成

Redis内容

redis的传统五大数据类型和落地应用(redis的命令不区分大小写,但是key值是区分的):

string:(商品编号采用incr命令生成,点赞数和踩数)

  • 同时设置/获取多个建值:mset key value key value.../mget key key key...
  •  递增/递减数字 incr/decr key
  • 增加/减少指定数字 incrby/decrby key increment
  • 获取字符串长度吧 strlen key
  • 分布式锁 setnx key value/set key value[EX seconds][PX milliseconds][NX|XX] ex代表秒,px代表毫秒,nx代表key不存在的时候才创建key,效果等同于setnx,xx表示在key存在时,覆盖key

list:(分页查询)

  • 向列表左边/右边添加元素:lpush/rpush key value
  • 查看列表 lrange key start stop(stop为-1的时候全部遍历)
  • 获取列表中元素的个数 llen key

hash: <string,Map<Object,Object>>(购物车早期)

  • 一次设置/获取一个字段值:hset key field value/hget key field
  • 一次设置/获取多个:hmset key field value.../hmget key field...
  • 获取所有字段值:hegetall key
  • 获取某个key内的全部数量 hlen key
  • 删除一个key hdel key

set:(并集:共同好友,随机抽奖.用户点赞)

  • 添加/删除元素 sadd/srem key member
  • 获取集合中的所有元素 smembers key
  • 判断元素是否在集合中sismember key member
  • 获取集合中的元素个数 scard key
  • 从集合中随机弹出数字个元素,不删除元素,默认不写数字就是一个 srandmember key [数字]
  • 从集合中随机弹出数字个元素,删除元素 spop key [数字]
  • 集合的差集运算A-B sdiff key
  • 集合的交集运算A∩B sinter key
  • 集合的并集运算A∪B sunion key

zset(带排序的set):(根据商品销售对商品进行排序显示,热搜等)

  • 添加一个元素和该元素的分数: zadd key score member
  • 按照从小到大的顺序返回range中的元素:zrange key start stop
  • 获取指定分数范围的元素:zrangebyscore key min max
  • 增加某个元素的分数 zincrby key increment member
  • 获取集合中元素的数量 zcard key
  • 获得指定分数范围内的元素个数:zcount key min max
  • 按照排名范围删除元素 zremrangebyrank key start stop

获取元素的排名从小到大/从大到小:zrank key member/zrevrank key member

----额外数据类型还有bitmap(位图),hyperloglog(统计),GEO(地理),stream.

知道分布式锁吗?有哪些实现方案?删除key的时候有什么问题?

分布式锁的使用场景:多个服务+同一时刻内+同一个用户的一个请求

初始情况的购买商品例子,这样的代码在单机版本是没有错误的,但是在多线程高并发的情况是不行

改良1:使用synchronized或者reentrantLock:在分布式情况下,每个锁都只能锁住本地的服务代码块,所以每个服务都可以进入一个请求,有多少个微服务就可以进入多少个请求

改良2:使用分布式锁--setIfAbsent占位锁

 改良3:解锁过程必须包裹在finally代码块中

 改良4,如果程序宕机了怎么办,需要加入过期时间

改良5,该操作不是原子操作,如果在刚刚设置的时候就宕机了怎么办?变成一行语句的原子操作

改良6,如果业务冗长,卡顿,超过了设定的过期时间怎么办?锁会自动删除,导致其他线程一瞬间冲进来,冲进来的线程获得了锁,这个时候卡顿的线程恢复,就会删掉别人的锁---设置每把锁都不相同,在解锁的时候判断是自己的锁才去删除

改良7, 现在删除锁的操作和判断自己锁的操作并非原子操作,有可能在刚刚判断完的时候,锁就过期了,那么就会误删别人的锁--改成LUA脚本来解决/如果不能使用LUA脚本怎么做(使用redis自身事务,此时如果事务失败的话队列会为空)

 LUA脚本方式

改良8:如果业务冗长,依然会有锁被先删除,其他线程涌入的情况.Redis分布式锁实现缓存续期---看门狗,然而在集群问题情况下,其实我们无论怎么写代码也无法解决集群主从复制的时候丢失数据的问题(相对于zookeeper实现CP保证可靠性来说Redis保证的是高可用AP)--要保证这两点就只能使用Redisson分布式锁了

改良9:在超高并发下会报这样一个错误attempt to unlock lock,notlocked by current thread by node id,解锁的时候添加如下操作

redis内存调整默认查看

查看内存情况info memory

修改在redis的配置文件859行,设置maxmemory参数,其参数单位是byte字节类型,一般推荐redis内存设置为最大物理内存的四分之三.也可以通过命令修改 config set maxmemory 数值

 redis内存淘汰策略

  •  惰性删除--数据达到了过期时间不做处理,等下次访问该数据的时候删除,对CPU友好,对内存不友好
  •  定时删除--在指定时间间隔遍历一次数据,有过期的就直接删除,对CPU不友好,对内存友好

上面两种方案都走极端,这就需要内存淘汰策略

通过config set maxmemory-policy 策略来设置

 默认的使用的是noevication:不会删除任何key等着内存打满爆炸

LRU算法是什么? --选择最近最久没有使用的数据予以淘汰

 投机取巧:

class LRUCache extends LinkedHashMap<Integer, Integer>{
    private int capacity;
    //第三个参数表示是否开启插入后重新排序
    public LRUCache(int capacity) {
        super(capacity, 0.75F, true);
        this.capacity = capacity;
    }

    public int get(int key) {
        return super.getOrDefault(key, -1);
    }

    public void put(int key, int value) {
        super.put(key, value);
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
        return size() > capacity; 
    }
}

手写一个lua

addHead方法

public class LuaDemo {
    private int cacheSize;
    Map<Integer,Node<Integer,Integer>> map;
    DoubleLinkedList<Integer,Integer> doubleLinkedList;

    public LuaDemo(int cacheSize) {
        this.cacheSize = cacheSize;
        this.map = new HashMap<>();
        this.doubleLinkedList = new DoubleLinkedList<Integer,Integer>();
    }

    public void print(){
        map.forEach((k,v)->{
            System.out.println("key:"+k+" value:"+v.value);
        });
    }
    public int get(int num){
        if(!map.containsKey(num))return -1;
        Node<Integer,Integer> node = map.get(num);
        doubleLinkedList.removeNode(node);
        doubleLinkedList.addHead(node);
        return node.value;
    }
    public void put(int num,int value){
        if(map.containsKey(num)){
            Node<Integer,Integer> node = map.get(num);
            node.value=value;
            map.put(num,node);
            doubleLinkedList.removeNode(node);
            doubleLinkedList.addHead(node);
        }else{
            if(map.size()==cacheSize){
                Node<Integer,Integer> last = doubleLinkedList.getLast();
                map.remove(last.key);
                doubleLinkedList.removeNode(last);
            }
           Node<Integer,Integer> newNode=new Node<>(num,value);
            map.put(num,newNode);
            doubleLinkedList.addHead(newNode);
        }
    }
    //构造一个节点作为数据载体
    class Node<K,V>{
        K key;
        V value;
        Node<K,V> prev;
        Node<K,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;
        }
    }
    //构建一个虚拟双向链表
    class DoubleLinkedList<K,V>{
        Node<K,V> head;
        Node<K,V> tail;
        //头尾节点相连
        public DoubleLinkedList() {
            this.head = new Node<>();
            this.tail = new Node<>();
            head.next=tail;
            tail.prev=head;
        }
        //增加一个节点
        public void addHead(  Node<K,V> node){
            //后继连接尾节点
            node.next=head.next;
            //前驱连接头节点
            node.prev=head;
            //尾节点的前驱节点连向node
            head.next.prev=node;
            //头节点的后继节点指向node
            head.next=node;
        }
        //删除一个节点
        public void removeNode(Node<K,V> node){
            //node后继节点的前驱等于当前node的前驱
            node.next.prev=node.prev;
            //node前驱节点的后继等于当前节点的后继
            node.prev.next=node.next;
            //断开前驱后继
            node.prev=null;
            node.next=null;
        }
        //获得最后一个节点
        public Node getLast(){
            return tail.prev;
        }
    }

    public static void main(String[] args) {
        LuaDemo luaDemo = new LuaDemo(3);
        luaDemo.put(1,1);
        luaDemo.put(2,2);
        luaDemo.put(3,3);
        luaDemo.print();
        System.out.println("-------");
        luaDemo.put(2,6);
        luaDemo.print();
        System.out.println("-------");
        luaDemo.put(4,5);
        luaDemo.print();
    }
}

 打印结果:

key:1 value:1
key:2 value:2
key:3 value:3
-------
key:1 value:1
key:2 value:6
key:3 value:3
-------
key:2 value:6
key:3 value:3
key:4 value:5

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值