面试小结_20240225

1.事务

1.1本地事务,

本地事务一般交给spring处理,分为编程式事务,声明式事务,一般用注解声明式事务@T处理,spring事务依赖于数据库事务,
数据库事务特性ACID:
原子性(Atomicity)。事务是一个不可分割的工作单位,要么全部执行成功,要么全部回滚到事务开始前的状态。
一致性(Consistency)。事务执行前后,数据库的数据必须保持一致性。事务的执行不能破坏数据库中的完整性约束,如唯一性约束、外键约束等。
隔离性(Isolation)。事务的执行应该相互隔离,每个事务的操作应该与其他事务的操作相互独立,互不干扰。即使多个事务同时并发执行,每个事务也应该感觉不到其他事务的存在。
持久性(Durability)。事务一旦提交(或者说已经成功执行),其对数据库的修改应该是永久性的,即使系统发生故障,数据也不会丢失。数据库系统需要提供恢复机制,以确保事务的持久性

1.2分布式事务

在不同的服务中、不同的数据库中、不同的网络中完成的事务。
单服务多数据库、多服务单数据库
先说一下CAP理论
CAP是 Consistency、Availability、Partition tolerance三个词语的缩写,分别表示一致性、可用性、分区容忍性。
C:一致性,用户在多个节点中拿到的数据一致,不要求时间
A:可用性是指任何时候查询用户信息都可以查询到结果,但不保证查询到最新的数据。要求时间
C:分区容忍性也叫分区容错性,当系统采用分布式架构时由于网络通信异常导致请求中断、消息丢失,但系统依然对外提供服务
用户小明通过结点1添加信息,然后同步到节点2
如果要满足C一致性,必须等待小明的信息同步完成系统才可用(否则会出现请求到结点2时查询不到数据,违反了一致性),在信息同步过程中系统是不可用的,所以满足C的同时无法满足A。
如果要满足A可用性,要时刻保证系统可用就不用等待信息同步完成,此时系统的一致性无法满足。
所以在分布式系统中进行分布式事务控制,要么保证CP、要么保证AP。
在实际业务中分析需求,满足CP还是AP。判断需求是否需要时间来保持数据一致
转账必须要求数据一直,时间不能有延迟,退款可以延迟。
解决方案
消息队列
分布式事务框架seta
Xxl-job定时任务处理延时如退款,不用满足一致性

2.xxl-job框架

任务调度框架,xxl-job和quart xxljob是专门处理分布式任务调度,所以分布式业务一般选择xxljob。自带web页面方便监控,quart需要和其他组件搭配。调度和执行分开方便维护。

3.分片大文件上传

前端分片,通过文件的MD5值确认,先确认文件是否上传,上传到第几片,上传全部完成之后分片合并。

4.分布式锁

客户端1 先执行查询余票,发现剩余 1 张,在即将执行 剩余票数 -= 1 过程之前,客户端2 也执行了查询余票,发现也是剩余 1 张,客户端2 也会执行 剩余票数 -= 1. 的过程.
具体的,往 redis 上设置一个特殊的 key - value,接着完成买票操作,再把这个 key - value 删除掉. 如果在 客户端1 买票的期间,客户端2 也想去买票,就也会尝试设置 key - value,如果发现 key - value 已经存在,就分为 “加锁失败”(是放弃还是阻塞,就要看具体的实现策略了)
Redis:Setnx
数据库唯一索引
Redission,tryLock

5.Es搜索

mapping常见属性有哪些?
type:数据类型
index:是否创建索引 默认为true创建
analyzer:选择分词器
properties:子字段
type常见的有哪些
字符串:text、keyword
数值:long、integer、short、byte、double、float
布尔:boolean
日期:date
对象:object
RestHighLevelClient.search

  1. 创建SearchRequest对象,指定索引库名
  2. 利用request.source()构建DSL,DSL中可以包含查询、分页、排序、高亮等
  3. 利用client.search()发送请求,得到响应

6. Docker命令

docker pull 拉取镜像
docker save [参数] 镜像 将镜像保存为文件
docker load -i oracle.tar 把文件加载为镜像
docker ps [参数] 查看容器id
docker logs -f 容器id 查看日志
Dockerfile就是用来构建 docker 镜像的构建文件(命令脚本,每个命令都会形成一个新的镜像层并对镜像进行提交)。 通过这个脚本可以生成镜像!

7.缓存穿透

使用缓存后代码的性能有了很大的提高,虽然性能有很大的提升但是控制台打出了很多“从数据库查询”的日志,明明判断了如果缓存存在课程信息则从缓存查询,为什么要有这么多从数据库查询的请求的?
这是因为并发数高,很多线程会同时到达查询数据库代码处去执行。
我们分析下代码:

如果存在恶意攻击的可能,如果有大量并发去查询一个不存在的课程信息会出现什么问题呢?
比如去请求/content/course/whole/181,查询181号课程,该课程并不在课程发布表中。
进行压力测试发现会去请求数据库。
大量并发去访问一个数据库不存在的数据,由于缓存中没有该数据导致大量并发查询数据库,这个现象要缓存穿透。
如何解决缓存穿透?
1、对请求增加校验机制
比如:课程Id是长整型,如果发来的不是长整型则直接返回。
2、使用布隆过滤器
什么是布隆过滤器,以下摘自百度百科:
布隆过滤器可以用于检索一个元素是否在一个集合中。如果想要判断一个元素是不是在一个集合里,一般想到的是将所有元素保存起来,然后通过比较确定。链表,树等等数据结构都是这种思路. 但是随着集合中元素的增加,我们需要的存储空间越来越大,检索速度也越来越慢(O(n),O(logn))。不过世界上还有一种叫作散列表(又叫哈希表,Hash table)的数据结构。它可以通过一个Hash函数将一个元素映射成一个位阵列(Bit array)中的一个点。这样一来,我们只要看看这个点是不是1就可以知道集合中有没有它了。这就是布隆过滤器的基本思想。
布隆过滤器的特点是,高效地插入和查询,占用空间少;查询结果有不确定性,如果查询结果是存在则元素不一定存在,如果不存在则一定不存在;另外它只能添加元素不能删除元素,因为删除元素会增加误判率。
比如:将商品id写入布隆过滤器,如果分3次hash此时在布隆过滤器有3个点,当从布隆过滤器查询该商品id,通过hash找到了该商品id在过滤器中的点,此时返回1,如果找不到一定会返回0。
所以,为了避免缓存穿透我们需要缓存预热将要查询的课程或商品信息的id提前存入布隆过滤器,添加数据时将信息的id也存入过滤器,当去查询一个数据时先在布隆过滤器中找一下如果没有到到就说明不存在,此时直接返回。
实现方法有:
Google工具包Guava实现。
redisson 。

2、缓存空值或特殊值
请求通过了第一步的校验,查询数据库得到的数据不存在,此时我们仍然去缓存数据,缓存一个空值或一个特殊值的数据。
但是要注意:如果缓存了空值或特殊值要设置一个短暂的过期时间。

Java
public CoursePublish getCoursePublishCache(Long courseId) {

    //查询缓存
   Object  jsonObj = redisTemplate.opsForValue().get("course:" + courseId);
    if(jsonObj!=null){
    String jsonString = jsonObj.toString();
        if(jsonString.equals("null"))
            CoursePublish coursePublish = JSON.parseObject(jsonString, CoursePublish.class);
        return coursePublish;
    } else {
        //从数据库查询
        System.out.println("从数据库查询数据...");
        CoursePublish coursePublish = getCoursePublish(courseId);
        //设置过期时间300秒
        redisTemplate.opsForValue().set("course:" + courseId, JSON.toJSONString(coursePublish),30, TimeUnit.SECONDS);
        return coursePublish;
    }
}

再测试,虽然还存在个别请求去查询数据库,但不是所有请求都去查询数据库,基本上都命中缓存。

8. 缓存雪崩

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。
造成缓存雪崩问题的原因是是大量key拥有了相同的过期时间,比如对课程信息设置缓存过期时间为10分钟,在大量请求同时查询大量的课程信息时,此时就会有大量的课程存在相同的过期时间,一旦失效将同时失效,造成雪崩问题。
如何解决缓存雪崩?
1、使用同步锁控制查询数据库的线程
使用同步锁控制查询数据库的线程,只允许有一个线程去查询数据库,查询得到数据后存入缓存。
Java
synchronized(obj){
//查询数据库
//存入缓存
}
2、对同一类型信息的key设置不同的过期时间
通常对一类信息的key设置的过期时间是相同的,这里可以在原有固定时间的基础上加上一个随机时间使它们的过期时间都不相同。
示例代码如下:

Java
   //设置过期时间300秒
  redisTemplate.opsForValue().set("course:" + courseId, JSON.toJSONString(coursePublish),300+new Random().nextInt(100), TimeUnit.SECONDS);

3、缓存预热
不用等到请求到来再去查询数据库存入缓存,可以提前将数据存入缓存。使用缓存预热机制通常有专门的后台程序去将数据库的数据同步到缓存。

9.缓存击穿

缓存击穿是指大量并发访问同一个热点数据,当热点数据失效后同时去请求数据库,瞬间耗尽数据库资源,导致数据库无法使用。
比如某手机新品发布,当缓存失效时有大量并发到来导致同时去访问数据库。
如何解决缓存击穿?
1、使用同步锁控制查询数据库的线程
使用同步锁控制查询数据库的代码,只允许有一个线程去查询数据库,查询得到数据库存入缓存。
Java
synchronized(obj){
//查询数据库
//存入缓存
}
2、热点数据不过期
可以由后台程序提前将热点数据加入缓存,缓存过期时间不过期,由后台程序做好缓存同步。
下边使用synchronized对代码加锁。

Java
public  CoursePublish getCoursePublishCache(Long courseId){
    synchronized(this){
        //查询缓存
        String jsonString = (String) redisTemplate.opsForValue().get("course:" + courseId);
        if(StringUtils.isNotEmpty(jsonString)){
            if(jsonString.equals("null"))
                return null;
            CoursePublish coursePublish = JSON.parseObject(jsonString, CoursePublish.class);
            return coursePublish;
        }else{
            System.out.println("=========从数据库查询==========");
            //从数据库查询
            CoursePublish coursePublish = getCoursePublish(courseId);
           //设置过期时间300秒
        redisTemplate.opsForValue().set("course:" + courseId, JSON.toJSONString(coursePublish),300, TimeUnit.SECONDS);
            return coursePublish;
        }
    }

}

测试,吞吐量有1300左右
分布式锁避免缓存击穿

小结

1)缓存穿透:
去访问一个数据库不存在的数据无法将数据进行缓存,导致查询数据库,当并发较大就会对数据库造成压力。缓存穿透可以造成数据库瞬间压力过大,连接数等资源用完,最终数据库拒绝连接不可用。
解决的方法:
缓存一个null值。
使用布隆过滤器。
2)缓存雪崩:
缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。
造成缓存雪崩问题的原因是是大量key拥有了相同的过期时间。
解决办法:
使用同步锁控制
对同一类型信息的key设置不同的过期时间,比如:使用固定数+随机数作为过期时间。
3)缓存击穿
大量并发访问同一个热点数据,当热点数据失效后同时去请求数据库,瞬间耗尽数据库资源,导致数据库无法使用。
解决办法:
使用同步锁控制
设置key永不过期

无中生有是穿透,布隆过滤null隔离。
缓存击穿key过期, 锁与非期解难题。
大量过期成雪崩,过期时间要随机。
面试必考三兄弟,可用限流来保底。
限流技术方案:
alibaba/Sentinel
nginx+Lua

10.分布式锁的实现方案

10.1分布式锁应用场景

场景:多个进程(多台机器)并发更新多个资源时,同时更新数据库和Redis时
方案:需要对多个资源进行加锁,控制多个资源的并发更新,使用分布式锁
1.最常见扣减库存,防止超卖
2.缓存击穿/缓存雪崩(也可以采用分布式锁)

  1. 并发任务调度
    在分布式系统中,可能需要对某个任务进行调度,如果多个节点同时发起任务调度请求,那么就需要使用分布式锁来保证只有一个节点能够成功获取到任务调度的锁,从而避免任务被多次执行
  2. 分布式事务
    在分布式事务中,需要保证不同节点对共享资源的访问是互斥的,否则可能会导致数据不一致的问题,此时可以使用分布式锁来保证在事务执行过程中,各个节点之间对共享资源的访问是互斥
  3. 分布式缓存
    在分布式缓存中,需要保证各个节点对缓存的访问是互斥的,否则可能会导致缓存中的数据不一致,此时可以使用分布式锁来保证各个节点对缓存的访问是互斥的

1、基于数据库实现分布锁

利用数据库主键唯一性的特点,或利用数据库唯一索引的特点,多个线程同时去插入相同的记录,谁插入成功谁就抢到锁。

2、基于redis实现锁

redis提供了分布式锁的实现方案,比如:SETNX、set nx、redisson等。
拿SETNX举例说明,SETNX命令的工作过程是去set一个不存在的key,多个线程去设置同一个key只会有一个线程设置成功,设置成功的的线程拿到锁。

3、使用zookeeper实现

zookeeper是一个分布式协调服务,主要解决分布式程序之间的同步的问题。zookeeper的结构类似的文件目录,多线程向zookeeper创建一个子目录(节点)只会有一个创建成功,利用此特点可以实现分布式锁,谁创建该结点成功谁就获得锁。

10.2 Redis NX实现分布式锁

redis实现分布式锁的方案可以在redis.cn网站查阅,地址http://www.redis.cn/commands/set.html
使用命令: SET resource-name anystring NX EX max-lock-time 即可实现。
NX:表示key不存在才设置成功。
EX:设置过期时间
这里启动三个ssh客户端,连接redis: docker exec -it redis redis-cli
先认证: auth redis
同时向三个客户端发送测试命令如下:
表示设置lock001锁,value为001,过期时间为30秒
Plain Text
SET lock001 001 NX EX 30
命令发送成功,观察三个ssh客户端发现只有一个设置成功,其它两个设置失败,设置成功的请求表示抢到了lock001锁。
如何在代码中使用Set nx去实现分布锁呢?
使用spring-boot-starter-data-redis 提供的api即可实现set nx。
添加依赖:

Java
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.6.2</version>
</dependency>
添加依赖后,在bean中注入restTemplate。
我们先分析一段伪代码如下:
Java
if(缓存中有){

  返回缓存中的数据
}else{

  获取分布式锁
  if(获取锁成功){
       try{
         查询数据库
      }finally{
         释放锁
      }
  }
 
}

1、获取分布式锁
使用redisTemplate.opsForValue().setIfAbsent(key,vaue)获取锁
这里考虑一个问题,当set nx一个key/value成功1后,这个key(就是锁)需要设置过期时间吗?
如果不设置过期时间当获取到了锁却没有执行finally这个锁将会一直存在,其它线程无法获取这个锁。
所以执行set nx时要指定过期时间,即使用如下的命令
SET resource-name anystring NX EX max-lock-time
具体调用的方法是:redisTemplate.opsForValue().setIfAbsent(K var1, V var2, long var3, TimeUnit var5)
2、如何释放锁
释放锁分为两种情况:key到期自动释放,手动删除。
1)key到期自动释放的方法
因为锁设置了过期时间,key到期会自动释放,但是会存在一个问题就是 查询数据库等操作还没有执行完时key到期了,此时其它线程就抢到锁了,最终重复查询数据库执行了重复的业务操作。
怎么解决这个问题?
可以将key的到期时间设置的长一些,足以执行完成查询数据库并设置缓存等相关操作。
如果这样效率会低一些,另外这个时间值也不好把控。
2)手动删除锁
如果是采用手动删除锁可能和key到期自动删除有所冲突,造成删除了别人的锁。
比如:当查询数据库等业务还没有执行完时key过期了,此时其它线程占用了锁,当上一个线程执行查询数据库等业务操作完成后手动删除锁就把其它线程的锁给删除了。
要解决这个问题可以采用删除锁之前判断是不是自己设置的锁,伪代码如下:

JavaScript
if(缓存中有){

  返回缓存中的数据
}else{

  获取分布式锁: set lock 01 NX
  if(获取锁成功){
       try{
         查询数据库
      }finally{
         if(redis.call("get","lock")=="01"){
            释放锁: redis.call("del","lock")
         }
         
      }
  }
 
}

以上代码第11行到13行非原子性,也会导致删除其它线程的锁。
在调用setnx命令设置key/value时,每个线程设置不一样的value值,这样当线程去删除锁时可以先根据key查询出来判断是不是自己当时设置的vlaue,如果是则删除。
这整个操作是原子的,实现方法就是去执行上边的lua脚本。
Lua 是一个小巧的脚本语言,redis在2.6版本就支持通过执行Lua脚本保证多个命令的原子性。
什么是原子性?
这些指令要么全成功要么全失败。

以上就是使用Redis Nx方式实现分布式锁,为了避免删除别的线程设置的锁需要使用redis去执行Lua脚本的方式去实现,这样就具有原子性,但是过期时间的值设置不存在不精确的问题。

10.3 Redisson实现分布式锁

Redisson底层采用的是Netty 框架。支持Redis 2.8以上版本,支持Java1.6+以上版本。Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) 。

使用Redisson可以非常方便将Java本地内存中的常用数据结构的对象搬到分布式缓存redis中。
也可以将常用的并发编程工具如:AtomicLong、CountDownLatch、Semaphore等支持分布式。
使用RScheduledExecutorService 实现分布式调度服务。
支持数据分片,将数据分片存储到不同的redis实例中。
支持分布式锁,基于Java的Lock接口实现分布式锁,方便开发。
下边使用Redisson将Queue队列的数据存入Redis,实现一个排队及出队的接口。

添加redisson的依赖

Java
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.11.2</version>
</dependency>
从课程资料目录拷贝singleServerConfig.yaml到config工程下
在redis配置文件中添加:
Java
spring:
  redis:
    redisson:
      #配置文件目录
      config: classpath:singleServerConfig.yaml
      #config: classpath:clusterServersConfig.yaml

redis集群配置clusterServersConfig.yaml.
Redisson相比set nx实现分布式锁要简单的多,工作原理如下:

• 加锁机制
线程去获取锁,获取成功: 执行lua脚本,保存数据到redis数据库。
线程去获取锁,获取失败: 一直通过while循环尝试获取锁,获取成功后,执行lua脚本,保存数据到redis
• WatchDog自动延期看门狗机制
第一种情况:在一个分布式环境下,假如一个线程获得锁后,突然服务器宕机了,那么这个时候在一定时间后这个锁会自动释放,你也可以设置锁的有效时间(当不设置默认30秒时),这样的目的主要是防止死锁的发生
第二种情况:线程A业务还没有执行完,时间就过了,线程A 还想持有锁的话,就会启动一个watch dog后台线程,不断的延长锁key的生存时间。
• lua脚本-保证原子性操作
主要是如果你的业务逻辑复杂的话,通过封装在lua脚本中发送给redis,而且redis是单线程的,这样就保证这段复杂业务逻辑执行的原子性
具体使用RLock操作分布锁,RLock继承JDK的Lock接口,所以他有Lock接口的所有特性,比如lock、unlock、trylock等特性,同时它还有很多新特性:强制锁释放,带有效期的锁,。

Java
public interface RRLock {
    
   //----------------------Lock接口方法-----------------------
    /**
     * 加锁 锁的有效期默认30秒
     */
    void lock();
    
     /**
     * 加锁 可以手动设置锁的有效时间
     *
     * @param leaseTime 锁有效时间
     * @param unit      时间单位 小时、分、秒、毫秒等
     */
    void lock(long leaseTime, TimeUnit unit);
    
    /**
     * tryLock()方法是有返回值的,用来尝试获取锁,
     * 如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false .
     */
    boolean tryLock();
    
    /**
     * tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,
     * 只不过区别在于这个方法在拿不到锁时会等待一定的时间,
     * 在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
     *
     * @param time 等待时间
     * @param unit 时间单位 小时、分、秒、毫秒等
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    
    /**
     * 比上面多一个参数,多添加一个锁的有效时间
     *
     * @param waitTime  等待时间
     * @param leaseTime 锁有效时间
     * @param unit      时间单位 小时、分、秒、毫秒等
     * waitTime 大于 leaseTime
     */
    boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;
    
    /**
     * 解锁
     */
    void unlock();
}

lock():
• 此方法为加锁,但是锁的有效期采用默认30秒
• 如果主线程未释放,且当前锁未调用unlock方法,则进入到watchDog机制
• 如果主线程未释放,且当前锁调用unlock方法,则直接释放锁

10.4 分布式锁避免缓存击穿

下边使用分布式锁修改查询课程信息的接口。

Java
//Redisson分布式锁
public  CoursePublish getCoursePublishCache(Long courseId){
        //查询缓存
        String jsonString = (String) redisTemplate.opsForValue().get("course:" + courseId);
        if(StringUtils.isNotEmpty(jsonString)){
            if(jsonString.equals("null")){
                return null;
            }
            CoursePublish coursePublish = JSON.parseObject(jsonString, CoursePublish.class);
            return coursePublish;
        }else{
            //每门课程设置一个锁
            RLock lock = redissonClient.getLock("coursequerylock:"+courseId);
            //获取锁
            lock.lock();
            try {
                jsonString = (String) redisTemplate.opsForValue().get("course:" + courseId);
                if(StringUtils.isNotEmpty(jsonString)){
                    CoursePublish coursePublish = JSON.parseObject(jsonString, CoursePublish.class);
                    return coursePublish;
                }
                System.out.println("=========从数据库查询==========");
                //从数据库查询
                CoursePublish coursePublish = getCoursePublish(courseId);
                redisTemplate.opsForValue().set("course:" + courseId, JSON.toJSONString(coursePublish),1,TimeUnit.DAYS);
                return coursePublish;
            }finally {
                //释放锁
                lock.unlock();
            }
        }


}

启动多个内容管理服务实例,使用JMeter压力测试,只有一个实例查询一次数据库。

测试Redisson自动续期功能。
在查询数据库处添加休眠,观察锁是否会自动续期。

JavaScript
try {
    Thread.sleep(60000);
} catch (InterruptedException e) {
    throw new RuntimeException(e);
}

11.mq message queue

11.1Mq消息中间件作用

削峰限流,解耦,异步
吞吐量:单位时间内接受和处理消息的速度, 例:每秒处理一百条消息
1.rabbitmq erlang语言 吞吐量一般 功能丰富
2.rocketmq java语言 吞吐量高 功能丰富
3.kafka scala语言 吞吐量高 功能单一,用于日志和大数据,只保证数据读写
消费模式:CLUSTERING 为负载均衡模式
BROADCASTING 为广播模式

11.2死信队列

当消息消费失败或者发送失败会触发生产者重发机制,当重发达到上限时就会把消息丢到死信队列。死信队列的消息将不会再被消费。可以利用RocketMQ Admin工具或者RocketMQ Dashboard上查询到对应死信消息的信息。我们也可以去监听死信队列,然后进行自己的业务上的逻辑处理。

11.3重复消费

BROADCASTING(广播)模式下,所有注册的消费者都会消费,而这些消费者通常是集群部署的一个个微服务,这样就会多台机器重复消费,当然这个是根据需要来选择。
CLUSTERING(负载均衡)模式下,如果一个topic被多个consumerGroup消费,也会重复消费。
消息中间间很难一次性完成消息的读写,在大多数情况下mq会多次发送消息保证消息的成功传递
解决方案:

  1. 布隆过滤器,消费时把数据插入过滤器中,后续查询过滤器
  2. 消息唯一key消息消费成功把唯一key插入数据库在消费前查询key是否存在
  3. 保证消息的幂等性,多次消费不会对业务产生影响

11.4保证消息的可靠性需要保证以上过程的可靠性

1、生产者确认机制
发送消息前使用数据库事务将消息保证到数据库表中,成功发送到交换机将消息从数据库中删除
2、mq持久化
mq收到消息进行持久化,当mq重启即使消息没有消费完也不会丢失。
需要配置交换机持久化、队列持久化、发送消息时设置持久化。
3、消费者确认机制
消费者消费成功自动发送ack,否则重试消费。

11.5 springboot RocketMQTemplate

发送消息
// 同步
rocketMQTemplate.syncSend("bootTestTopic", "我是boot的一个消息");
// 异步
rocketMQTemplate.asyncSend("bootAsyncTestTopic", "我是boot的一个异步消息", new SendCallback() {
    @Override
    public void onSuccess(SendResult sendResult) {
        System.out.println("成功");
    }

    @Override
    public void onException(Throwable throwable) {
        System.out.println("失败" + throwable.getMessage());
    }
});
// 单向
rocketMQTemplate.sendOneWay("bootOnewayTopic", "单向消息");
// 延迟
Message<String> msg = MessageBuilder.withPayload("我是一个延迟消息").build();
rocketMQTemplate.syncSend("bootMsTopic", msg, 3000, 3);
// 顺序消息 发送者放 需要将一组消息 都发在同一个队列中去  消费者 需要单线程消费
List<MsgModel> msgModels = Arrays.asList(
        new MsgModel("qwer", 1, "下单"),
        new MsgModel("qwer", 1, "短信"),
        new MsgModel("qwer", 1, "物流"),
        new MsgModel("zxcv", 2, "下单"),
        new MsgModel("zxcv", 2, "短信"),
        new MsgModel("zxcv", 2, "物流")
);
msgModels.forEach(msgModel -> {
    // 发送  一般都是以json的方式进行处理
    rocketMQTemplate.syncSendOrderly("bootOrderlyTopic", JSON.toJSONString(msgModel), msgModel.getOrderSn());
});
接受消息
1.	加上注解@RocketMQMessageListener(topic = "bootTestTopic", consumerGroup = "boot-test-consumer-group")
2.	重写onMessage方法

12.spring ioc和aop原理

1.Spring 提供 IOC 容器实现两种方式

(1)BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用。
在加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象
(2)ApplicationContext:BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人员进行使用。在加载配置文件时候就会把在配置文件对象进行创建

2.Aop原理

Aop实现方式是动态代理和字节码文件,把切面编译成字节码文件,在目前方法执行的前后通过动态代理把切面织入到程序中,实现对目标方法的增强。
3.Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
将 Advice 应用于目标对象后创建的对象称为代理。在客户端对象的情况下,目标对象和代理对象是相同的。
Advice + Target Object = Proxy
JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。

如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

13.反射原理

什么是反射机制?
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。

14.springboot自动装配原理

什么是自动装配
在springboot项目中引入redis,只需要引入maven以来,yml文件写入配置就可以生效了,不用像spring那样写xml文件配置。非常的简洁,方便。
自动装配怎么实现的
@EnableAutoConfiguration 注解,@Configuration注解
Spring Boot通过条件注解实现了自动配置。当应用启动时, AutoConfigurationImportSelector 类会根据类路径下的META-INF/spring.factories文件中的配置,加载自动配置类

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值