【深入问题,拓展广度】JAVA框架问答

50 阅读12分钟

用过什么微服务组件

SpringCloud组件有注册中心/配置中心nacos、网关Gateway、远程调用Feign、服务熔断Hystrix、负载均衡Ribbon

AOP,在哪里用到了,有什么注解

AOP的全名为面向切片编程,我在使用事务管理、异步管理、日志管理、安全检查等情况用到了,注解有@After、@Before、@AfterReturning、@AfterThrowing、@Around

springboot有什么注解

SpringBoot的常用注解有

  • @Controller声明控制类
  • @RestController用于声明控制类并标记为RESTful风格的,是ResponseBody与Controller的结合体、
  • @Data声明数据类
  • @Service声明服务类
  • @Transaction用于事务管理、
  • @Bean、@Autowired、@Resourse用于bean的管理与注入、
  • @EnableAsync、@Async用于事务管理
  • @Component通用注解,用于标记为Spring的组件
  • @Configuration声明配置类
  • @SpringBootApplication用于启动SpringBoot程序,包含了Configuration、EnableAutoConfiguration、ComponentScan
  • @PutMapping、GetMapping、PostMapping、DeleteMapping处理HTTP请求类型
  • @ResquestBody用于PostMapping中json字符串转化为Java对象
  • @Value将配置文件的属性读出来
  • @NotEmpty、@Email、@NotNull、@Min(value)、@Max(value)等用于JSR校验的注解

spring boot和springMVC区别

Spring Boot和SpringMVC都是基于Spring延伸出来的,SpringMVC是用于WEB开发的框架,需要自己对xml和tomcat进行配置,而SpringBoot是一个开发脚手架,可以自动配置。而且SpringBoot的拓展更简单,只需要对maven添加依赖,只需要maven打包就可以运行起来了。

spring中的设计模式

  • 工厂设计模式:使用工程模式可以通过beanFactory和ApplicationContext创建Bean对象
  • 单例设计模式:bean的作用域默认就是单例singlten,他还有protoType、request、session、global-session、socket
  • 代理设计模式:AOP就是基于动态代理
  • 模板方法模式:像redisStringTemplate、MongoDBTemplate用的就是模板方法模式
  • 装饰器方法模式:对于Spring中支持我们动态的切换数据库的时候使用
  • 观察者模式:对于spring的时间驱动功能使用的就是观察者模式
  • 适配器模式:SpringBoot中AOP的增强或通知就使用到了

工厂模式具体使用场景

BeanFactory和ApplicationContext中创建对象。

nacos作为配置中心的作用

可以将项目的配置放到Nacos中,可以实现配置与项目的解耦,使用Nacos后可以看到服务的历史配置,并且支持服务修改后的推送功能,若服务提供者修改了信息,会主动推送给服务消费者。

作为注册中心,Nacos提供服务注册、服务发现、服务监控功能,其中服务监控对于临时实例采用心跳监控,如果60s没有心跳消息则剔除服务器,对于非临时实例会采用主动拉取心跳消息,即使没有心跳消息也不会剔除服务器。

openfeign和feign的区别

feign是基于JAVA的声明式HTTP框架,OpenFeign是Feign的升级版,是SpringCloud的组件,集成了配置中心和服务注册发现功能,而Feign需要手动配置

nacos作为注册中心如何判断服务存活

对于临时实例,采用心跳监控,若不正常则剔除,对于非临时实例,主动检测模式,不正常也不会剔除服务

用过哪些中间件

使用过Redis、MongoDB、ElasticSearch、Kafka

Redis 具体使用细节

滚动分页功能: 我在精准巡田的飞手任务清单中,使用redis的Zset类型实现了滚动分页的功能,因为它有可能随时更新,所以数据的下标有可能变化,从而导致第二页与第一页有重复的情况,所以使用sortedSet进行范围查询还可以获取当前最小时间戳。

  1. 在每次发布任务后,获得该任务的飞手,把飞手id作为value,系统当前时间为用于比较的值推送到对应的redis的Zset中,
  2. 在获取分页的时候,需要传入上次传输的最小时间戳为起始,分页中的条数为offset。 因为Zset有排序功能,这样就保证了每次上传新任务时,从传入的最小时间戳作为起始进行limit分页。

签到功能:使用BitMap类型,在该月份对应的天数的位置赋值为true,由此实现了统计连续签到的天数的功能

秒杀功能:首先过去我的练手的项目中使用Redis实现了秒杀,首先需要对秒杀是否开始、商品是否有库存进行一个判断,这之中会存在集群中多台机器、多个线程同步的问题。我的第一版解决方法使用到了cas思想每次判断是否成功扣减库存时,比较版本号是否一致,不过在实际使用中失败率过高,所以就退化成判断库存>0就可以进行扣减库存进而解决库存超卖问题,使用setNx实现分布式锁,其中实现获取锁种使用UUID+锁id作为value值防止误删问题,并设置过期时限防止死锁。在释放锁的时候判断id是否一致,一致就释放。然后再使用lua脚本语言实现多个redis命令的原子性。不过最后由于setNx实现存在重入问题、不重试、超时释放、主从一致问题,最后还是选择了redission进行实现。然后又使用redis的stream类型实现了消息队列的功能,进行实现异步下单的功能,在lua脚本语言中,发送消息到队列中,然后在线程启动时开启一个线程获取stream.orders中的消息,完成下单,其中需要注意的是,在发生异常时需要处理pendingList中的订单消息,如果pendingList中的消息也异常了,那么sleep一段时间再继续。

对于项目中的缓存雪崩,使用随机过期时间解决,缓存穿透使用若数据库中也没值,则将redis中的值赋为null进而缓存空对象,对于缓存击穿问题,使用逻辑过期解决,具体的方案为,若某个热点key失效了,则获取互斥锁,开启新线程执行重写缓存,在这个过程中若有其他线程访问且没获得锁则返回过期数据,直到释放锁后返回新数据。

Kafka 相关概念与具体使用

Kafka本身是个分布式的流处理平台,主要有两个应用场景,一个是消息队列、一个是数据处理,我在项目中使用到的是消息队列的功能。因为他相对于其他消息队列中间件来说,性能与生态兼容性最好,所以我选择他为我的消息队列实现方式。

在项目中文章上架时,使用了Kafka异步调用,因为每次上架,需要对文章属性进行构建索引,以方便于后续使用ES+MongoDB实现搜索功能。

ES 相关概念与具体使用

ES的全称是ElasticSearch,他是分布式的搜索引擎,他有一些独特的概念,如索引,文档,字段,映射,他是面向文档存储的。在项目中,首先我导入了与elasticSearch对应版本的ik分词器,然后使用postman发送put请求对字段进行映射,再查询所有的库中文章导入es库中。然后修改文章相关功能接口,在发布、更新、删除完后,使用kafka异步地对其更新索引与映射。在查询时,使用es构造器BoolQueryBuilder设置查询条件,在进行分页,然后使用highlightBuilder设置高亮

在实现历史搜索的功能中搭配了MongoDB使用,选择MongoDB原因是需要给每个用户都建立历史记录,数据量大,加载快,而Redis只支持KeyValue结构,而MongoDB是结构化的数据

ES与DB做数据同步的方式,具体实现细节

然后修改文章相关功能接口,在发布、更新、删除完后,使用kafka异步地对其更新索引与映射。

多级缓存的数据同步

缓存与缓存之间,若未命中一级缓存,则去查二级缓存,若查到了,则同步到一级缓存中,若都没查到,在数据库查到了,则同步到一级和二级缓存汇总

redis基本数据结构、底层数据结构

redis的基本数据结构有String、List、Hash、Set、ZSet、BitMap、Stream、GEO

  • String底层用的是简单动态字符串SDS
  • List在Redis过去的旧版本用的是双向链表+压缩列表,在个数不超过512个,每个大小都不超过64字节时用压缩列表存储,超过后使用双向链表,在新版本中使用quickList实现
  • Hash在Redis过去的旧版本使用的是哈希表+压缩列表,与List一样,超过了512就用hash表,在新版本中压缩列表被listPack所代替
  • Set使用的是哈希表+整数集合实现,与Hash、List一样,超过了使用哈希表。
  • Zset在过去版本中使用压缩数组+跳表,超过了使用Hash表,在新版本中用listPack代替压缩列表

创建索引的命令

一共有三种;

  1. 创建表的时候使用Key创建索引,Primary Key创建主键索引
  2. Alter TABLE table_name Add INDEX sname(1, 2, 3)可以创建主键索引、普通索引和UNIQUE索引
  3. CREATE INDEX sname ON table_name(1, 2, 3)只能创建普通索引和UNIQUE索引

索引的数据结构

MyIsAm的结构是B+树,数据只存放在叶子节点中,非叶子只存放索引值,并且父节点的索引值都会出现在子节点的索引值中,所以叶子节点中包含了所有的索引值,顺序是按照索引值排序的,每个叶子节点都有两个指针,一个指向上一个节点,一个指向下一个节点,形成双向链表。选择B+树而不选择B树和二叉树的原因是,即便是遇到千万级的数据,B+树也可以通过3-4次就能找到数据

#和$的区别

可能会造成Sql注入,#可以防止,一般来说一般{}用在我们能够确定值的地方,也就是我们程序员自己赋值的地方,#用于用户输入值的地方

MyBatis的缓存机制

Mybatis的查询缓存有一级缓存和二级缓存,一级缓存是默认开启的是sqlSession级别的缓存,在数据发生更改,查询条件变化,手动清空缓存后,缓存失效。二级缓存即全局缓存是需要手工开启的,基于mapper级别的

线程池的优缺点、使用场景

线程池的优点:方便管理,减少资源浪费,提高响应效率 线程池的缺点:每次启动项目需要初始化线程池,增加了代码的复杂度,对于某些业务还是需要手工的使用更高级的调度算法

Spring事务的传播机制

  • Required:如果当前存在事务,则加入该事务,如果不存在则创建新事务
  • Supported:如果当前存在事务,则加入该事务,如果不存在则以非事务的方式执行
  • MondaTory:如果当前存在事务,则加入该事务,如果不存在则抛出异常
  • NEW_Requires:如果当前存在事务,则挂起事务,如果不存在则创建新事物
  • NOT_Supported:如果当前存在事务,则挂起事务,如果不存在则以非事务的方式执行
  • NEVER:如果当前存在事务则抛出异常,不使用事务
  • NESTED:如果当前存在事务则以嵌套事务的方式执行,否则创建一个新事务

Mybatis的四个对象

  • 执行器Executor:主要负责SQL的整体控制
  • 语句处理器StatementHandler:负责与JDBC层的具体交互
  • 参数处理器ParameterHandler:负责Preparedment入参设置
  • 结果集处理器ResultSetHandler:负责将查询到的结果映射成java对象

Spring中@Autowired和@Resource的区别

Autowired是Spring提供的注解,Resource是Jdk提供的注解,Autowired通过ByType注入,Resource通过ByName注入,当一个接口存在多个实现类时,两个都需要指定名称,Autowired使用@Qualifier指定,Resource使用Name属性值执行

反射

反射可以让我在运行时分析类,执行类中方法的能力。可以通过反射获取类中的所有的属性与方法,并且可以调用这些属性与方法。他可以让我们的代码更加灵活,但是会产生一些安全问题比如泛型参数的安全检查。

比如注解就是使用反射的方式根据注解标记去调用注解解释器执行行为。AOP中的动态代理也是使用了反射机制。

Spring中拦截器

拦截器也是基于反射机制实现的,因为基于了动态代理,是AOP的具体实现,他可以用于日志记录进行信息监控,权限检查,要实现handlerInterceptor的接口或者继承handlerInterceptorAdapter,重写preHanlder、postHandler,afterCompletion方法。他是Spring的组件,由Spring所管理,可以用于Applicationg程序中

Spring中过滤器

过滤器是基于Servlet的,通过函数回调实现,缺点是只能在容器启动时初始化一次。可以使用@WebFilter注解进行配置。因为他是要依赖于tomcat的所以只能在web程序使用。

Spring过滤器与拦截器的不同点

  • 过滤器是在进入容器后,进入Servlet之前预处理的,拦截器是进入Servlet后,进入Controller前预处理的
  • 过滤器是基于回调函数实现的,拦截器是根据动态代理反射实现的
  • 过滤器是依赖于Servlet容器的,只能用于Web程序,而拦截器是独立存在的,可以在任何情况使用
  • 过滤器无法获得ioc中的bean,因为拦截器是由Spring管理的,spring的功能可以被拦截器使用
  • 过滤器一般可以用于Request中不良信息的过滤,拦截器一般用于日志检查、权限检查
  • 过滤器的声明周期由Servlet管理,拦截器的声明周期由IOC管理