八股小知识

八股小知识

Springboot

过滤器和拦截器的区别?

  • 过滤器依赖于servlet容器,它的拦截范围更广,可以拦截所有的请求和静态资源访问,它是在请求进入servlet容器之后,进入servlet之前进行拦截。需要实现javax.servlet.filter接口,并重写init,doFilter,doDestory方法,可以访问servletRequest和responsse对象,但不能访问spring容器的bean。
  • 拦截器依赖于Spring,只能拦截springMvc控制器的请求,无法拦截静态资源访问。它是在请求进入控制器之前,控制器处理请求之后以及视图渲染之前处理。需要实现handlerInterceptor接口,并重写perHandler,postHandler和aftercondition方法,可以访问servletRequest,response对象以及Spring容器的bean

Spring有哪些设计模式?

我了解的有工厂模式,单例模式和代理模式

  • 工厂模式:常见的工厂模式主要就是BeanFactory(延迟注入)和ApplicationContext(完全注入),我们无需知道类如何创建,直接从工厂中获取即可
  • 单例模式:ioc默认的就是单例模式,每次获取相同类时,获取的对象都是同一个。单例模式还分需要时创建和自动创建两种
  • 代理模式:aop就是采用代理模式来实现的,当要代理的对象实现接口时,就会通过JDK Proxy来生成代理对象。如果代理的对象没有实现接口,就会使用CGLIb生成一个被代理对象的子类作为代理对象

什么是AOP

AOP是面向切面编程,它将一些与业务无关但是复用性比较高的代码抽离出来,降低代码耦合度

Spring中的事务是怎么实现的?

本质是通过AOP实现的,通过环绕通知的方式进行前后拦截,在执行方法前开启事务,在执行后提交业务,会对此过程进行try/catch,如果报错直接回滚

Spring中事务失效有哪些场景?

  • try/catch捕获异常后,并没有手动抛出异常就会导致事务失效。解决办法:在catch中手动抛出异常
  • 抛出检查时异常,Spring的事务只会对runtime异常进行回滚,比如not found exception就是一个检查异常。解决方法:在@transacional中添加属性rollbackFor=Exception.class,对所有异常进行回滚
  • 非public方法,解决方法:将方法改为publc
  • 在非事务方法中调用事务方法

@Transactional的原理有了解过吗

事务注解是基于AOP来实现的,通过环绕通知,在执行方法前开启事务,执行后提交事务,如果try/catch捕获异常,则回滚

什么是SpringTask

SpringTask是Spring框架提供的一个任务调度工具,它可以在约定时间自动实行一段程序

使用时,只需要在启动类添加@EnableScheduling注解,然后通过@Scheduled注解写定时方法即可

什么时SpringCache

SpringCache是Spring提供的一种缓存的解决方案,基于AOP原理,实现了基于注解的缓存功能,对代码的入侵性更小

它提供了@EnableCaching,@Cachable,@CacheEvict,@CachePut等注解实现了开启SpringCache,查询缓存,修改缓存,更新缓存功能

数据结构

HashMap

HashMap底层是哈希表,jdk1.8之前是由数组+链表组成,jdk1.8之后由数组+链表+红黑树实现

元素加入HashMap,先计算出元素的哈希值,其映射出元素对应在数组的下标。如果哈希值重复,则存放在数组对应的链表上。jdk1.8之后,如果链表长度大于阈值,则自动转化为红黑树,同样的,如果长度小于阈值,就会由红黑树转化为链表。

在什么情况下HashMap会扩容?(HashMap扩容机制)

HashMap初始化大小为16,在内容达容量0.75时自动扩容,扩容大小为2倍。扩容后会创建一个新的数组,需要把旧数据挪到新数组中

为什么HashMap是线程不安全的?怎么实现一个安全的HashMap?

HashMap的所有方法不是同步的。在多线程环境下,多个线程同时修改HashMap可能导致数据不一致或出现死循环。

如果想使用线程安全的HashMap,可通过**Collections.synchronizedMap(new HashMap<>())**创建一个线程安全的HashMap

HashMap什么样的类适合作为键?

不可变性是必要的,且需要实现方法equals和hashcode。因为key的存取要一致,如果不一致就取不到数据了

HashMap和HashTable的区别

主要区别有,线程安全性和可存入Null值

  1. HashTable是线程安全的,HashMap是非线程安全的
  2. HashTable不能存入以Null为值的key和value ,而HashMap可以
  3. 虽然HashTable是线程安全的,但不建议在多线程环境下使用,因为它是一个古老的API,性能不好,如果要在多线程下使用,推荐用ConcurrentMap

int和Integer的区别

int是java中的 基本对象,integer是java对int的一个 封装类,大小和默认值不同,int默认值为0,Integer默认值为null

ArrayList,Vector,LinkedList的区别

ArrayList和Vector底层由数组实现,支持随机索引,查找快而修改慢。

ArrayList可以自动扩容,线程不安全

Vector可以自动扩容,线程安全,但是性能太低已经弃用了

LinkedList底层由双向链表实现,不支持随即索引,查找需要向前或向后遍历,修改快。线程不安全。

如果要多线程访问,可以用Collections.synchronizedList()

Collection和Collections的区别

Collection是集合类的上级接口,继承它的主要是List和Set

Collections是集合类的一个帮助类,提供了集合的搜索,排序等方法

List和Map的区别

  • list存储 单列数据集合,存储循序有序且可重复
  • Map存储 键值对双列数据集合,存储顺序无需且键不可重复值可重复

Sleep()和wait()的区别

  • sleep是Thread类的,睡眠时不需要释放锁,可以在任意地方使用
  • wait是Object类的,睡眠时释放锁,必须在synchronized修饰的代码块或方法中使用

抽象类和接口类的区别

抽象类内部可以有属性,构造方法,静态方法等,而接口不可以。

一个类可以继承一个类,但是可以实现多个接口

OverLoad(重载)和Override(重写)的区别

OverLoad重载可以使一个方法名有多个不同参数的是西安

Override用在继承方面,方法名和参数完全一致,子类在使用时优先使用子类的方法

什么是序列化?

序列化是一种处理对象流的机制,就是将对象的内容进行流化,可以对流化后的对象进行读写操作,也可以将流化后的对象传输于网络之间。通过实现接口Serializable接口来实现序列化

String,StringBuilder,StringBuffer的区别

  • String,内容不可变,每次拼接操作都会创建一个新的对象。线程安全,但是由于每次拼接字符都会创建一个新的对象,所以性能不高。适用于字符串直接量,少量的字符拼接操作
  • StringBuilder,内容可变,功能与StingBuffer类似,但线程不安全,适用于单线程下的大量字符串拼接操作
  • SringBuffer,内容可变,内部通过char[]动态扩容,自动修改内容。所有方法由Synchronized修饰,线程安全,适用于多线程下的字符串操作

==与equals()的区别

  • ==比较基本数据类型时,比较值是否相等,比较引用对象时,比较的是其在内存中的位置是否相等。但是如果用于比较引用对象,就算两个对象内容相同,结果也不一定为true,因为地址可能不一样。
  • equals()默认比较内存地址,但可以重写方法为比较内容相等

hashCode()和equals()的区别,为什么重写equals()就要重写hashCode()

  • hashCode()计算哈希码,equals()比较两对象是否相等
  • 为什么两个要同时重写?因为这是java对象契约规定的,重写equals()就一定要重写hashCode(),否则在操作哈希表时可能会出现一些逻辑错误。
  • java对象契约核心规定,两个对象equals()返回true,那么他们的hashCode方法返回值一定相等,反之则不成立
  • 如果两个方法不同时重写,因为哈希表是通过哈希码来判断数据的存储位置的。内容相同的两个对象,由于哈希码不同,所以在哈希表中的位置就不一样,如果存储在hashSet,就会被认为是两个不同的对象,就导致hashSet存入重复值

java的异常处理机制

  • java的异常处理机制可分为三部分,异常处理,抛出异常,异常跟踪栈
  • 处理异常的语句由try,catch,finally三部分组成,try包裹业务代码,catch捕获异常,处理异常,finally用于释放资源,无论什么情况下都会执行finally
  • 程序出现异常,系统自动生成一个异常对象,并交给JVM处理,JVM寻找处理该异常的chatch块并交给它处理,如果没找到,则终止程序
  • 此外,java还允许程序手动抛出异常

说说你对java反射的了解

java反射就是在程序运行期间动态的获取类的方法和属性。反射的操作内容主要是:

  • 获取Class对象,可以通过类.class、对象.class或forName(全限定名)来获取类对象
  • 创建对象实例,获取到类对象后,通过getDeclaredConstructor()来实现它的无参或有参构造方法,创建实例
  • 获取方法,通过getDeclaredMethod来获取方法,用invoke调用方法

说一说多线程

线程是执行程序的最小单位,一个进程可以有多个线程。各个线程之间共享程序的内存空间和系统资源分配,但是各个线程都有自己的栈空间。多线程的优点:减少程序响应时间,提高cpu利用率,创建和切换开销小。

说说线程的创建

我了解到的创建线程的方法有两种

一种是继承Thread类,并重写run方法,通过start方法运行

另一种是实现Runnable接口,并重写run方法,通过start方法运行

说说你对集合的了解

java中的集合主要包含Collection和Map两大类,其中collection又派生出了List,Set,Queue。所有集合类都是List,Set,Queue,Map这四个接口的实现类。List代表有序,可重复,Set代表无序,不可重复,Queue代表现先进先出队列,Map代表键值对关系集合。

Redis

Redis提供了哪几种持久化方式

Redis提供了两种持久化方式,即RDB和AOF

  • RDB,在不同的时间点,将redis存储的数据生成快照存储在磁盘中
  • AOF,将redis执行过程中的所有命令记录下来,在下次redis重启时,再重新执行一遍所有程序
  • RDb和AOF这两种可以同时使用,但是重启Redis,会优先采用AOF,因为AOF恢复的数据完整度更高

RDB和AOF的区别

RDB是对整个内存做一个快照,而AOF是记录所有运行的指令。

RDB在可能会导致数据完整性低,而AOF的完整性高

RDB的文件比较小,而AOF会记录所有执行命令,文件比较大

RDB恢复数据速度快,而AOF需要一条一条的执行命令,所以速度比较慢

Redis的key过期后,会立即删除吗

Redis有两种数据过期策略,惰性删除和定期删除

  • 惰性删除:只有在数据被访问时,才会去判断数据的ttl是否过期,过期就删除,否则直接返回,这意味着,就算数据已经过期,但只要数据一直没有被访问,就会占用内存空间,会造成内存资源浪费
  • 定期删除:定期去判断一定数量的key是否过期,如果过期则删除。优点是内存占用低,缺点是查询key需要占用一些性能

加入缓存过多,内存有限,内存满了怎么办?

Redis有八种数据淘汰策略,默认是不淘汰数据直接报错,但可以修改配置文件来选择淘汰策略

淘汰策略中有两种重要的思想

  • LRU:淘汰当前最久不适用的数据
  • LFU:淘汰当前使用最不频繁的数据

如果数据库有100W条数据,而Redis只能存储20W条数据怎么办?

选择LRU淘汰策略,删除最久未使用的数据,保证近期热点访问的数据缓存在内存中

了解过双写一致性吗?

当数据库的数据发生修改时,需要更新缓存中的数据,使缓存数据与数据库数据保持一致,在写操作时,会采用延迟双删

在延迟双删中为什么要延迟删除缓存?

因为数据库采用主从模式,遵循读写分离,需要一些时间将数据库中的数据同步到从数据库,但还是有可能出现脏数据

在延迟双删中,为什么要删除两次缓存?

如果只删除一次缓存,可能出现两种情况

1.先删除缓存再更新数据。线程A删除缓存,再更新数据库的时候,线程B访问缓存,缓存没有数据,去访问数据库,获取旧数据并且更新缓存,此时线程A更新数据库成功,就会导致数据库数据与缓存数据不一致

2.先更新数据再删除缓存。当缓存中没有数据时,线程A访问缓存失败,去访问数据库的时候,线程B修改数据库,删除缓存(此时缓存没有数据),然后线程A将访问到的旧数据添加到缓存,还会造成数据不一致

Redis和Mysql是如何保证数据一致性的

Mysql可以通过事务来确保数据一致性。通过事务,确保所有的数据库操作要么全部成功执行,要么全部失败回滚

Redis和Mysql保持数据一致,需要延迟双删。

先删除缓存,再更新数据库,在这期间,所有访问都去数据库,而非旧数据。数据库更新后,再让线程休眠一段时间,这个时间是给数据库充足的时间去修改数据。最后,再次删除缓存,这是因为在线程休眠期间,可能有别的线程读取到了旧数据并写入缓存,造成数据不一致。但是延迟双删并不能完全保证数据的一致性。

延迟双删是没法保证强一致性的,有什么强一致性的方法吗?

1.分布式锁。在对数据库进行写操作的时候,添加分布式锁,保证同一时刻只有一个线程访问修改数据库,但是这样会影响性能。

2.使用RabbitMQ,在修改数据时,通过MQ发送信息给Radis,异步的修改缓存。因为MQ的消息是按照顺序消费的,如果事务发生了回滚,那么消息会被重新放入队列。

Redis为什么快?

Redis将内容存储在内存,而内存的读写速度远大于硬盘,所以快

另外,Redis还对一些数据结构做了优化比如String,Redis采用单线程模式来处理客户端请求,这看似限制了Redis的并发处理能力,但是Redis是操作内存的,且避免了多线程的上下文切换以及抢锁环节,使得Redis处理单个请求时非常高效

Redis是单线程的,为什么那么快呢?

Redis虽然是单线程的,这看似限制了它的高并发能力,但也得益于单线程,省去了多线程的上下文切换和争抢锁的时间,使其对于单个服务的处理效率很高

简单说说布隆过滤器

布隆过滤器类似hashmap,存储二进制数据,1代表存在,0代表不存在。通过hash函数计算存储位置,在查询缓存前,先查询布隆过滤器中数据是否存在,如果存在,在去查询缓存,否则直接返回空

布隆过滤器的优点是,存储二进制数据,占用空间小,查询速度快,但是可能存在误判,不能做删除操作。

因为布隆过滤器是根据哈希值来存储的,可能存在相同的哈希值,所以为了降低误判率,可以采用多哈希值。

简单说说分布式锁

Redisson分布式锁底层方法主要是通过Lua脚本实现,确保了操作的原子性。如果给锁添加了时间,则分布式锁会有watch dog来自动给锁续期。

还可以用Redis的setnx来实现,但是由于不知道具体要给锁添加多长时间,所以不常用。

介绍一下Redis的主从复制流程

主从复制主要分为全量同步和增量同步

两个Redis,slave向master发送同步请求,携带上自己的applicationId和offset给master,master判断appID与自己是否一致,如果不一致,则认为slave是第一次发送同步请求,则进行全量同步,master运行bgsave程序,生成RDB文件发送给slave进行全量同步,在此过程中,master可能进行新的指令,master会将这些指令存储在日志文件中,等slave同步完成后再发送给slave进行最终同步,然后master同步自己的appid和offset给slave。

如果appID一致,则认为不是第一次同步,则进行增量同步,master从日志文件中获取offset位置,将之后的数据发送给slave进行同步

哨兵模式是怎样的?Redis有几种模式?

我了解到的模式有主从模式、哨兵模式

哨兵模式是为了解决主从复制的缺点,即哨兵模式还是基于主从模式,只不过多了一个”哨兵“,当master挂掉后,烧饼会从salve中选出一个作为master,若master重新启动,那么该master则会转化为slave,并通过发布订阅方式,更改所有slave的master。

但是哨兵也存在单点故障问题,所以在一个一主多从的Redis系统中,可以创建多个哨兵

主节点宕机了怎么办?

可以设置哨兵模式,当主节点宕机后,哨兵模式自动从其他节点中选出一个节点作为主节点

说说你对哨兵模式的理解

哨兵模式的实现是通过sentinel的心跳机制,sentinel每秒会向所有节点发送一个ping命令,当master失效后,就会选择出新的master

Redis的分片集群有什么用?

  • 可以有多个master,这些master存储不同的数据,多个master可以解决并发写的问题
  • 一个master可以有多个slave,可以解决并发读的问题
  • 分片集群中,不再通过sentinel检测节点的状态,而是master之间ping状态,判断各master的健康状态,实现了哨兵模式

Mybatis

有了解过Mybatis的一级缓存,二级缓存吗?

一级缓存:基于PrepetualCache的HashMap的本地缓存,默认是开启的,其作用域是session,当session进行了close,flush后缓存从session中删除

二级缓存:其作用域是namespace和mapper,基于PrepetualCache,hashmap进行缓存,默认不开启,可以在Mybatis的配置文件中配置CacheEnable设置为true,mapper中添加标签cache开启

Mybatis的二级缓存什么时候会被清除?

当某个作用域(一级缓存的session/二级缓存的namespace)进行了增删改查后,就会清楚select中的缓存

#{}和${}的区别?

  • #{}主要就是占用符的替换,mysql中用?进行占位,而${}主要做文本替换
  • #{}发生在数据库访问层之内,${}数据替换发生在数据库访问层之外
  • #{}可以防止sql注入,而${}替换的文本数据存在sql注入的风险

SpringCloud

了解过SpringCloud的五大件吗?

了解过SpringCloudAlibaba

  • nacos:注册中心,配置中心
  • openFeign:远程调用
  • gateWay:网关
  • sentinel:服务保护
  • 负载均衡:了解过nacos的负载均衡

服务的注册与发现是什么意思?SpringCloud是如何实现服务的注册与发现的

拿Nacos举例

每一个服务的提供者,都会向Nacos发送一份自己的信息,Nacos会将这些信息,比如服务名,ip,端口号存储到一个信息表中,这就是服务注册

每一个消费者,如果想要获取某一个服务,会先向Nacos发送服务请求,Nacos根据它的请求服务,去信息表中查询相关信息并返回给消费者,消费者通过这些信息再去获取服务,这就是服务发现。

服务的提供者每30秒还会向Nacos发送心跳,来证明自己的健康状态,如果一个信息超过90秒都没有发送心跳,那么Nacos就会移除这个服务

你了解到的负载均衡有哪几种方法?

  • 第一种,通过RestTemplate和Nacos实现,需要自定义RestTemplate并且添加注解@LoadBalanced,然后添加Nacos的负载均衡依赖,之后使用RestTeamplate就会实现负载均衡
  • OpenFeign的负载均衡默认是开启的,只需要配置好feign的客户端,负载均衡就会自动生效

什么是服务雪崩?解决办法是什么

当某个服务调用失败或者长时间未响应时,可能导致一系列的服务调用都发生失败, 造成服务雪崩

解决办法:熔断降级

当服务长时间未响应或者调用失败,应该立刻断开连接,并发送一个错误数据。通过OpenFeign的兜底返回方法,返回一个默认数据

分布式事务的解决方案?

Seata分布式事务

Seata实现分布式事务,得益于它的二阶提交协议

分布式事务开始前,添加@GlobalTransactional注解,向seataSetver注册一个全局事务id,开启全局事务

第一阶段,开启分支事务。分支事务中,先解析sql语句,然后从数据库中获取到要修改数据的前镜像,然后执行sql语句,获取数据的后镜像,然后将前后镜像插入到undo_log回滚日志中,准备提交事务,但是为了防止外界其他事务也操作这个数据,所以本地事务提交前会向seateServer注册一个分支事务,并且申请一个全局锁,防止外界去修改这个数据,然后再去提交事务,也就是提交数据跟undo_log日志,然后向tc发送自己的完成状态。

第二阶段,所有的分支事务都完成后,都向tc发送一个完成的信息,如果所有分支事务都完成了,那么就添加异步任务,删除第一阶段生成的undo_log日志记录

但是,如果某一个任务发生了回滚,tc就会通知所有的事务开启回滚。

本地事务收到回滚通知,开启本地事务,去undo_log日志中查找到自己的前后镜像,先判断后镜像数据与当前数据是否一致,如果不值一,说明有别的操作修改了数据,此时需要在配置文件中设置策略。如果一致,则回滚,执行结束后,删除undo_log日志。

简单介绍一下OpenFeign

OpenFeign的作用主要是远程调用,它不用显示的写出调用的地址,而是写出调用的微服务的名字和方法即可。

OpenFeign还可以配置超时信息,包括连接超时和响应超时等。

OpenFeign还提供了重试机制,默认连接一次,失败就不再尝试,可以通过配置文件,进行失败后重试。

另外,还提供了兜底返回机制,通过实现ProduceFeignClient接口,自定义兜底返回方法,在调用失败后返回默认数据

简单介绍一下Sentinel

sentinel功能主要有熔断降级,流量控制,资源监控等功能。

服务保护就比如它的异常处理机制,如下

熔断降级

如果服务A访问B,迟迟没有得到响应,那么就可能造成服务雪崩,为了避免这一情况,当服务迟迟未响应时,应该断开连接并返回一个错误数据。

熔断降级的关键组件是断路器

断路器有三种状态,开,半开,关

默认是关,服务A可以直接访问服务B,但如果B处理效率不佳,比如一段时间内大量请求都失败了,那么断路器就会打开,阻止所有请求。等待一段时间后,断路器变为半开,再发送一条探路数据,如果此时效果还是不好,就继续断开,否则就关上

流量限制

分为流控模式和流控效果

流控模式分为直接、关联、链路

流控效率分为快速失败,warmUp,排队等待

Sentinel的异常处理机制?

Sentinel在发生异常时,会抛出一个BlockException异常,这个异常包括了流控、热点参数、熔断降级、权限控制、系统阻塞异常等。

BlockExcetion会根据发生异常的资源类型,进行不同的异常处理

Web接口异常

如果是Web接口抛出异常,它会执行SentinelWebInterceptor方法,这个方法继承了AbstractSentinelInterceptor方法,它是一个web拦截器,里面有一个preHandler方法,在进入Web接口前,先获取到了接口信息,然后调用Sphu.entry方法去校验这个方法是否违背规则,如果没有违背,则返回true,如果违反规则,那么就会抛出一个BlockException异常,最后通过一个默认的BlockExceptionHandler处理异常。如果要自定义web异常,可以定义类去实现BlockExceptionHandler,自定义异常处理类。

@SentinelResource异常

如果是@SentinelResource异常,系统调用SentinelResourceAspect切面方法,切点是所有添加了@SentinelResource注解的方法,在切面方法中,获取方法名,然后通过Sphu.entry方法判断是否违规,如果没有返回true,如果有,抛出BlockException异常,并调用handlerBlockException方法去处理,如果注解里标注了blockHandler方法,则默认执行blockHandler方法,如果没有,则去看看有没有添加兜底返回的fallback方法,同样的,如果都没有,那么就会向上抛出异常,把异常交给springboot处理。

OpenFeign异常

如果OpenFeign发生异常,就调用OpenFeign的兜底回调方法,如果没有,就将异常交给springboot处理

Sphu.entry

用try/catch手动处理异常

说一下GateWay

功能有:统一入口,请求路由,负载均衡,流量控制,身份认证,系统监控等功能

所有接口都要经过网关,网关通过请求路径再转发请求到对应的服务。

因此,可以再网关层做一些身份校验,或者是日志记录等等

RabbitMQ

什么是MQ

MQ是一种消息中间件,用于跨线程信息传递,传递上下游信息。

MQ的优点

异步、削峰、解耦

  • 异步,通过异步操作
  • 削峰:把消息放到消息队列,至于之后每秒消费多少消息,就看服务器的消费能力,流量高峰下去了,服务也就没有压力了
  • 解耦:每添加一个下游,都不必修改上游代码,下游直接订阅MQ即可

为什么要使用MQ

当业务增加时,比如支付功能,有可能支付后要进行添加积分等操作,如果都写到支付功能中,可能在造成响应时间过长,影响用户体验。这时通过MQ发送消息去异步执行,用户只需要等待支付功能完成即可,剩下的添加积分的业务异步执行即可

RabbitMQ如何保证数据的不丢失?

可以从三个方面进行考虑

  • 生产者确认机制:生产者发送信息给MQ,MQ根据情况返回ack和nack
  • 消费者确认机制:将消费者确认机制设置为auto,在spring成功处理消息后返回ack,删除消息,如果返回nack,则开启重试机制,如果重试多次仍然失败,则将消息投递到异常消息队列,由人工处理异常消息
  • 消息持久化:创建持久化的交换机,队列,消息

RabbitMQ的消息重复消费如何解决?

  • 设置唯一id,在消费消息时,先查数据库是否存在id,如果不存在,说明没有消费,则消费信息,反之不消费
  • 设置分布式锁,确保只有一个线程消费信息,但是性能比较低

RabbitMQ的死信交换机有了解过吗(延迟发送机制?)

通过死信队列和ttl实现

给消息队列通过属性dead-letter-exchange绑定一个死信交换机,自定义死信交换机以及绑定的死信消息队列,给需要延迟发送的信息设置ttl,当消息队列中的消息过期,或者消息被拒绝消费,队列满时,消息进入死信队列。消费者去消费死信队列,以此达到延迟发送的目的。

如果消息队列中的数据太多了怎么办?

说明消息的消费速度远小于生产速度,可以多添加几个消费者或者开启线程池加快消费速度

Dubbo

什么是Dubbo?

Dubbo是一种RPC框架,用于解决微服务架构下的远程通信

RPC是一种远程调用协议

0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
只想摆烂
作者分享
Day 8 昨天把面试鸭上背八股又复习了一下加深印象,有很多基本都快忘完了。 今天把手写rpc的注解驱动写完了,算是完结了,但是之前的哪个tcp协议有报错,暂时搁置了,还得把那个写一下。 注解驱动,感觉也不是很难,定义好注解,配置好注解范围,然后定义spring容器初始化时的行为(初始化rpc,提供者启动服务器,消费者不需要启动)。 对于服务提供者,我们期望的是,给想要提供的服务添加上注解@RpcService ,就可以实现服务注册的功能,即,消费者可以通过注解来调用我们的服务。通过Spring的Bean监听机制,在Bean加载完成后,扫描含有@RpcService 注解的bean并获取注解的内容进行服务注册。 对于消费者,我们期望的是,添加注解注入对象后,可以直接调用对象的方法。这实际上是利用注解生成代理对象后注入到创建的对象上。在Bean加载完后,根据注解里的信息生成代理对象。 在这里我做了一些改进,也是从评论区看到的思路。在RpcReference注解中我们定义了很多属性比如重试机制,负载均衡的类别,但是在实际代码中是直接通过rpcConfig读取的默认配置,如果我们允许用户在注解中选中某些属性值,比如不用默认的负载均衡类型,而改用其他的,就需要将注解中的内容读出来,存储到一个map中,然后在动态代理时根据map里的信息从工厂中获取对应的实现类。 思路是, 1.创建一个新的生成代理对象方法,另外接收一个参数map,里面存放内容为注解内容 2.自定义一个RpcReference对象,对象属性为@RpcReference 内的内容,这里不能直接从该注解获取内容,因为如果导入starter依赖,就会造成循环依赖,所以我的解决方法是创建一个class对象。 3.在ServiceProxy中创建一个RpcReference属性,创建有参构造方法,接收map,将map中的值赋值给RpcReference,不必担心用户填了哪些值,哪些值没填,因为在@RocReference注解中已经对这些值写了默认值 4.在invoke方法中,将原来的从rpcConfig中获取字符串,从工厂实例化改为直接从RpcReference中获取字符串然后工厂创建。
3
Day 7 ✅ 今天做了: 面试鸭: 1.springboot的自动配置是如何实现的? 简单总结一下,@EnableAutoConfiguration注解下的@Import注解会引入一个AutoConfigurationImportSelector加载器,它会扫描META-INF/spring.factories下定义的所有要自动配置的类路径。还提供了一些条件加载注释比如@ConditionalOnClass,@ConditionOnMissingBean,以及优先级自动配置注解@AutoConfigurationBeFore,@AutoConfigurationAfter等注解 2.Mysql的主从同步机制 主从同步就是将主库中的数据同步到一个或多个从数据库中。 分为三种:异步复制,同步复制,半同步复制。 手写RPC框架-重试机制 没什么难点,失败了就重试,学了两种重试算法:1.不重试,2.固定重试间隔 主要就是创建一个重试类,编写doRetry方法,接收参数为一个Callable<返回参数>的任务对象,并在内部写入重试机制(如使用RetryerBuilder类定制重写机制),最后.call()提交。
1
Day 6 每天学完累了就像休息,总是忘了打卡。。。 ✅ 今天做了: 面试鸭: 1.HashTable,HashMap,TreeMap的区别? HashTable线程安全,不可存入null的key或value,比较古老,性能不好。 HashMap非线程安全,可存入一个null的key和多个null的value,多线程可用concurrentHashMap TreeMap非线程安全,底层由红黑树实现,支持key的自然排序或者自定义排序,不可存入null的key,但可存入null的value 2.Redis如何实现分布式锁 Redis通过setnx和Lua脚本实现分布式锁,上锁通过setnx获取锁,并设置过期时间,解锁通过lua脚本实现,解锁时还需要判断锁是否是自己的,避免释放别人的锁。 Redis设置锁的过期时间要合理,即不能太长,占用资源,也不能太短,业务还未执行结束就提前释放。 并且,在主从模式下,如果主节点获取锁,在还未进行主从同步时,就宕机了,哨兵选举出新的主节点,由于没有主从同步,新的主节点又创建了锁,而此时主节点恢复了,此时就出现了两把锁,可能造成数据不一致。 Redis为了解决这一问题,推出了红锁。 红锁的实现基于多台Redis,客户端获取锁,需要向所有Redis发送上锁请求,只有当过半的Redis同意上锁,才可以上锁,否则获取锁失败。但是红锁开发成本较高。 手写RPC框架-负载均衡 在之前的代码中,获取服务,直接从serverList中get(0),获取第一个服务,然后直接调用,而经过增加负载均衡服务,可以从serverList中选择一个调用。 随机负载:创建一个随机数,每次都从serverList中随机获取一个返回。 轮询负载:创建一个轮询数,每次都从serverLIst中获取轮询id对应的数据,轮询数+1。 Hash一致负载:将Hash值空间划分成一个圆环,所有服务节点都映射在环的某个位置,每个请求根据哈希值,也映射在环的某个位置,然后顺时针寻找第一个哈希值大于该哈希值的节点,将请求路由发送到该节点。 Hash一致的优点是,就算某个节点宕机了,请求仍然可以发送到其他节点。 但是Hash一致存在一个问题,那就是如果服务节点过少,可能造成资源分配不均匀的问题,所以同一个服务可以映射在环的多个哈希值节点位置。 然后在Rpcconfig创建属性,添加默认负载均衡机制。在invoke调用服务时添加负载均衡机制。 太伤心了,前两天在写自定义协议,很难,写了一天,到最后程序一直出错,就是找不到问题在哪里,不得已直接copy鱼皮的代码,发现还是存在同样的问题,compeleteFuture的get方法阻塞获取返回结果,一直获取不到,不知道怎么弄,只好把代码恢复了,白写了,只能等其他写完后,再去琢磨琢磨了,哭了
4
Day 5 ✅ 今天做了: 面试鸭: 1.Mysql如何调优? 。设计合理的联合索引,尽量实现索引覆盖 。避免使用select * 。避免使用左Like模糊查询 。避免对查询字段进行函数操作,导致索引无法命中 。尽量符合最左前缀匹配原则 2.count(1),count(*),count(字段)有什么区别? count(1),count(字段)都可以行数量,不包括null值 count(*)查询行数量,包括Null 3.Mysql的乐观锁和悲观锁 乐观锁: 通过比较操作数据前后版本号,或某个字段值是否一致来保证操作期间没有别的线程修改数据,如果不同则回滚。适用于并发冲突少,读多写少的情景。 悲观锁: 在操作数据时对数据加锁,可以通过行级锁或表级锁实现,如select...for update。适用于并发冲突多,且写多读少的情景
3
Day 4 ✅ 今天做了: 面试鸭: cookie,session,token的区别? 为什么要用消息队列? Mysql的默认隔离级别是什么? 常见的几种设计模式? Mysql的事务二阶段提交是什么? Mysql是如何实现业务的? 手写RPC框架-第五节-实现注册中心简易版 包括功能:服务注册,服务发现。 创建服务中心配置类,包含服务中心地址,类别等信息。 创建服务信息类,包含服务名,版本号,地址和端口号等信息,提供获取服务key的方法(服务名+版本号)。 分布式储存器选择etcd,键值对存储,键定义为服务名+版本号,值存储服务信息类对象。 添加默认版本号字段,设值为1.0 为了提供多种注册中心实现方法,跟序列器一样,创建一个注册中心接口,定义方法:初始化,服务注册,服务发现,关闭服务,并进行如下操作: 1.在META-INF目录下创建以注册中心接口全引用为名的文件,内含注册中心类以及对应的实现接口地址。方便之后的读取以及 反射实例化。 2.定义注册中心工厂类,静态代码块,调用spiLoader,读取并记录META-INF目录下定义的注册中心信息。 3.提供getInstance方法,接受注册中心类型名key,通过spiLoader获取key对应的注册中心实现类。 实现各注册中心实现类,继承注册中心接口。 初始化:接受注册中心配置类,根据配置类信息定义注册中心地址,以及创建KVclient 服务注册:接受一个注册信息类,获取服务名等信息,根据默认标识+服务名+版本号为key,注册信息类为value。 定义一个租期LeaseClient,通过grand设置有效时长,获取其id。 最后通过PutOption存入key,value,id添加到etcd。 服务发现:在invoke调用方法中,先获取注册中心配置类,获取注册中心类型,然后通过注册中心工厂的getInstance,传入注册中心类型,获取实现类,创建注册信息类,填入服务名,版本信息,再通过注册信息类的getKey方法获取key,然后调用注册中心的getService,服务发现。在内部,通过前缀匹配来查询,通过输入的key,通过GetOption并设置isPrefix(true),设置一个查询条件,然后通过kvClient的get方法,输入前缀key,以及Getoption查询条件,获取查询到的数据列表,然后以流的形式,讲每一个值转化为注册信息类对象(因为服务注册时,value直接存入的就是信息注册类),然后返回list。在调用时,先用用list的第一个对象。获取到的信息注册类,包含地址,端口号,还要在进行一下地址和端口号的拼接,如果地址包含http,说明地址host本就是一个http接口,则直接拼接host和port,如果不包含,则说明是localhost,需要拼接一个http://然后再放入http请求中
2
下载 APP