五、spring全家桶
1.Spring
什么是 Spring
Spring是一个开源的轻量级控制反转和面向切面编程的容器框架。轻量级是说它开发使用简单,功能强大。控制反转是指将对象的创建,销毁控制交给ioc容器,方便解耦合,降低维护难度,面向切面编程是指将相同的逻辑横向抽取出来,可以对一些通用业务如事务,日志进行集中管理。
Spring的优缺点
优点:
方便解耦,简化开发
AOP编程的支持
声明式事务的支持
方便程序的测试
方便集成各种优秀框架
降低JavaEE API的使用难度
缺点:
配置相对复杂
性能考虑 :Spring依赖反射机制来实现IOC和AOP等功能,反射操作相比直接调用方法会有一定的性能损耗
学习曲线较陡峭
Spring 用到了哪些设计模式
单例模式:Spring 中的 Bean 默认情况下都是单例的。
工厂模式:Spring使用了工厂模式来创建和管理Bean实例。BeanFactory和ApplicationContext作为工厂类,根据配置信息生成并返回相应的Bean实例。
代理模式:Spring AOP主要使用了代理模式来实现面向切面编程。
Spring通过JDK动态代理或CGLIB代理为目标对象创建代理对象,代理对象在方法执行前后添加额外的逻辑,如日志记录、事务管理等。这种方式实现了业务逻辑与通用逻辑的解耦,使得代码更加清晰和易于维护。
模板方法模式:模板方法模式常用于实现通用的业务逻辑和流程。JdbcTemplate就是一个典型的例子,它定义了数据库操作的通用流程(如建立连接、执行查询、关闭连接等),而将具体的SQL语句和执行逻辑留给子类或调用者来实现。这种方式提高了代码的复用性和扩展性。
IOC
IOC(控制反转)是一种设计思想,它改变了对象之间依赖关系的获取方式。在传统的编程中,对象会自行创建(通过new关键字来创建)和管理依赖。但在IOC中,这些工作都由一个外部容器(如Spring的BeanFactory或ApplicationContext)来负责。容器负责创建对象,并管理它们之间的依赖关系,然后在需要时自动注入。
好处
- 解耦:减少了代码间的直接依赖,使代码更简洁、易维护。
- 灵活性:依赖关系在运行时动态确定,方便替换和扩展。
- 易测试:可以模拟依赖对象,简化测试流程。
BeanFactory和ApplicationContext的区别
来源与功能
BeanFactory:作为Spring框架的底层接口,它主要用于定义和管理Bean对象,提供基本的IOC容器功能。它可以通过配置文件或注解来创建、配置、初始化和销毁Bean实例,并允许获取和操作这些实例。
ApplicationContext:作为BeanFactory的子接口,它不仅继承了BeanFactory的所有功能,还增加了许多高级特性,如支持国际化、资源文件访问以及加载多个上下文配置文件等。
加载方式
BeanFactory:采用延迟加载的方式。这意味着在容器启动时,它不会立即创建所有的Bean。只有当实际需要使用某个Bean时,它才会进行加载和实例化。
ApplicationContext:则是迫切加载。在容器启动时,它会一次性创建所有的Bean。这种加载方式使得ApplicationContext在运行时的速度相对BeanFactory更快。
AOP
AOP即面向切面编程
Spring AOP主要基于动态代理技术实现。如果目标对象实现了接口,Spring会使用JDK动态代理;否则,会使用CGLIB代理。动态代理能够在运行时为目标对象创建一个代理对象,代理对象会拦截目标对象的方法调用,并在调用前后添加额外的逻辑(即增强)。
Aspect(切面):定义了要织入到目标对象中的增强逻辑。
JoinPoint(连接点):表示切面可以被织入的位置,通常是方法调用、异常抛出等。
PointCut(切入点):用于定义切面的织入位置,即哪些连接点会被增强。
Advice(通知):定义了增强逻辑的具体内容,包括前置通知、后置通知、环绕通知、异常通知和最终通知等。
通过使用AOP,我们可以将诸如日志记录、事务管理等横切关注点与业务逻辑代码分离,使代码更加清晰、易于维护,并提高了代码的模块化能力。
Spring Bean的生命周期
Spring Bean 生命周期简单概括为4个阶段:
-
实例化:Spring通过反射创建一个Bean对象
-
填充属性:Spring自动为Bean的属性赋值,通常是通过配置文件或注解来指定的。
-
初始化
如果Bean实现了xxxAware接口,Spring会调用相应的setter方法,为Bean注入额外的信息或资源。如果实现了BeanPostProcessor接口,Spring会在初始化前后调用postProcessBeforeInitialization()和postProcessAfterInitialization()方法,允许开发者在Bean初始化过程中添加自定义逻辑。
如果配置了init-method方法,Spring会在初始化结束时调用该方法,执行一些自定义的初始化操作。
-
销毁
当Spring容器关闭时,如果Bean实现了DisposableBean接口,Spring会调用其destroy()方法来释放资源。此外,如果配置了destroy-method方法,Spring也会在销毁Bean之前调用该方法。
BeanFactory和FactoryBean的区别
BeanFactory接口是IOC容器的核心接口,定义了管理bean的最基本方法,比如实例化,配置,管理,获取bean的方法
FactoryBean提供了一种创建特殊或复杂Bean的机制,使得Bean的创建过程可以更加灵活和自定义。
说说SpringBoot自动装配
自动装配机制是SpringBoot框架的一个核心特性,它大大简化了Spring应用的配置和初始化过程。
具体来说,自动装配机制通过扫描特定的配置文件,找到并加载需要进行自动装配的类,然后创建相应的Bean对象,并将其交给Spring的IOC容器进行管理。
在SpringBoot应用中,我们通常在主启动类上添加@SpringBootApplication注解来启用自动装配。这个注解是一个复合注解,它包含了以下三个主要的子注解:
-
@SpringBootConfiguration:声明当前类是一个配置类
-
@ComponentScan:开启组件扫描,Spring会扫描当前包及其子包下的所有类,并将标注了@Component、@Service、@Repository、@Controller等注解的类作为候选组件。这些候选组件随后会被Spring容器作为Bean进行管理。excludeFilters属性可以用来排除一些不需要被扫描的类或包。
-
@EnableAutoConfiguration:是自动装配的核心,它告诉Spring Boot根据添加的jar依赖来自动配置你的应用,也是复合注解:
3.1@AutoConfigurationPackage:记住扫描的起始包名,使得自动配置的类能够正确地被加载。
3.2@lmport(AutoConfigurationImportSelector.class) 用来加载META-INF/spring.factories中的自动配置类
Spring自动装配的方式
三种主要方式:byType(按类型),byName(按名称)、constructor(按构造函数)。
byType
Spring根据属性类型自动找到匹配的bean并注入。若找到多个相同类型的bean,会抛出异常(除非使用集合接收)。通过@Autowired注解实现,可应用于setter方法、字段或构造函数。
byName
根据属性名与bean的id或name匹配进行注入。若属性名与某个bean的id或name相同,则注入该bean。
constructor
根据构造函数的参数类型自动注入bean。若存在多个同类型bean,需通过@Qualifier注解指定具体bean名称以避免歧义。
@Autowired和@Resource的区别
来源
@Autowired是Spring框架特有的注解,而@Resource则是Java EE规范中的注解,不仅适用于Spring,也适用于其他支持Java EE的容器。
查找顺序
@Autowired默认按类型(byType)装配依赖对象。当存在多个相同类型的bean时,如果无法通过类型注入,它会尝试按名称(byName)注入。而@Resource注解如果指定了名称,它会首先按名称(byName)进行装配;如果没有指定名称,则按照类型(byType)进行查找。
@RequiredArgsConstructor和@Resource的区别
@RequiredArgsConstructor是Lombok库中的一个注解,用于自动为类中所有被final修饰或标记为@NonNull的字段生成一个构造函数。这个构造函数会接收这些字段作为参数,并在构造函数体中将它们赋值给相应的字段。
@RequiredArgsConstructor
public class FriendServiceImpl {
private final UserMapper userMapper;
}
Lombok会自动为UserMapper字段生成一个构造函数,类似于:
public FriendServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
这样,当Spring容器创建FriendServiceImpl的实例时,它会查找一个合适的UserMapper bean并将其注入到FriendServiceImpl的构造函数中。
@Resource是Java EE规范中的注解,不仅适用于Spring,也适用于其他支持Java EE的容器。@Resource注解如果指定了名称,它会首先按名称(byName)进行装配;如果没有指定名称,则按照类型(byType)进行查找。
public class FriendServiceImpl {
@Resource
private UserMapper userMapper;
}
Spring容器会查找一个UserMapper类型的bean,并将其注入到userMapper字段中。如果Spring容器中有多个UserMapper类型的bean,你可能需要通过name属性来指定要注入的bean的名称,例如:@Resource(name = “specificUserMapper”)。
使用方式:@RequiredArgsConstructor是Lombok提供的,它生成构造函数来接受依赖项;而@Resource是Java EE和Spring提供的,它直接标记字段或setter方法以进行依赖注入。
控制:使用@RequiredArgsConstructor时,你完全依赖于Lombok生成的构造函数,并且必须确保Spring能够找到并注入正确的bean。使用@Resource时,你可以通过属性(如name)来更精细地控制注入哪个bean。
灵活性:@RequiredArgsConstructor主要适用于final字段,而@Resource可以应用于任何字段或setter方法,无论字段是否是final的。
错误处理:如果Spring容器找不到与@Resource匹配的bean,它通常会在运行时抛出异常。而Lombok生成的构造函数不会进行任何运行时检查,它仅仅是一个普通的构造函数。
@Bean和@Component的区别
都是使用注解定义 Bean。
@Bean 是使用 Java 代码装配 Bean,@Component 是自动装配 Bean。
@Component 注解用在类上,表明一个类会作为组件类,并告知Spring要为这个类创建bean,每个类对应一个 Bean。
@Bean 注解用在方法上,表示这个方法会返回一个 Bean。@Bean 需要在配置类中使用,即类上需要加上@Configuration注解。
@Component
@Component
public class Student {
private String name = "lkm";
public String getName() {
return name;
}
}
@Bean
@Configuration
public class WebSocketConfig {
@Bean
public Student student(){
return new Student();
}
}
@Bean 注解更加灵活。当需要将第三方类装配到 Spring 容器中,因为没办法源代码上添加@Component注解,只能使用@Bean 注解的方式,当然也可以使用 xml 的方式。
@Component、@Controller、@Repositor和@Service 的区别
@Component:是Spring框架中用于标识一个普通的Spring组件的注解。
当一个类被标记为@Component时,Spring容器会在启动时自动扫描并实例化这个类,并将其注册到Spring上下文中。
@Controller:是Spring MVC框架中用于标识一个控制器的注解。
@Service:是Spring框架中用于标识业务逻辑层的组件的注解
@Repository:是Spring框架中用于标识数据访问对象(DAO)的注解。
bean注入容器的方法
-
使用 @Configuration与@Bean 注解
-
使用@Controller @Service @Repository @Component 注解标注该类,然后启用@ComponentScan自动扫描
-
使用@Import ,把bean导入到当前容器中.@Import(Dog.class)
bean的作用域
singleton(单例)
Spring容器在整个应用程序生命周期中只创建一个bean实例。所有对该bean的请求(通过getBean方法)都返回同一个实例。单例bean在容器启动时被创建,并在容器关闭时销毁。
prototype(原型)
每次通过getBean方法请求该bean时,Spring都会创建一个新的bean实例。
request(请求)
对于每个HTTP请求,Spring会创建一个新的bean实例,并且该bean实例仅在当前请求的范围内有效。当请求结束时,bean实例将被销毁。这个作用域仅在 web 环境下有效。
session(会话)
对于每个HTTP会话,Spring会创建一个新的bean实例,并且该bean实例在整个会话期间都有效。会话结束时,bean实例将被销毁。这个作用域仅在 web 环境下有效。
application()
这个作用域限定在ServletContext的生命周期内。它与singleton类似,但是是基于web应用程序的,而不是Spring容器。因此,在整个web应用程序中,只会有一个bean实例存在。
配置bean的作用域
在XML配置中,可以使用标签的scope属性来指定bean的作用域,例如:
使用<bean>标签的scope属性来指定一个Bean的作用域。
<bean id="accountDao" class="com.zejian.spring.springIoc.dao.impl.AccountDaoImpl" scope="prototype"/>
在注解配置中,可以使用@Scope注解来声明bean的作用域,例如:
@Scope("prototype")
public class AccountDaoImpl {
}
注意:request和session作用域通常只在Spring MVC的web应用程序中使用,并且它们需要依赖于Servlet容器来管理HTTP请求和会话。
Spring是怎么解决循环依赖的
Spring 解决循环依赖有两个前提条件
-
循环依赖的Bean不全是通过构造器注入的
-
循环依赖的Bean必须是单例的
三级缓存策略是Spring用来解决循环依赖的关键机制
-
第一级缓存:保存完全初始化好的Bean实例(Bean的生命周期中的“完全初始化”状态。)
-
第二级缓存:保存早期暴露的Bean实例(Bean实例化完成但属性尚未填充完毕的状态)
-
第三级缓存:保存能够创建Bean实例的工厂对象,这些工厂对象通常是一个lambda表达式,用于延迟创建Bean实例,直到真正需要时才执行。
在循环依赖的场景中,当A依赖于B,而B又依赖于A时,Spring会利用这三级缓存来避免死锁和无限递归具体过程:
-
创建A对象时,首先会创建一个匿名内部类(即单例工厂),并将其放入第三级缓存。
-
当A需要注入B时,如果B尚未创建,则开始创建B。
-
创建B对象时,同样会创建一个匿名内部类放入第三级缓存。当B需要注入A时,由于A尚未完全初始化,Spring会查看第三级缓存,发现A的单例工厂,通过它来获取一个尚未完全初始化的A对象实例,并将其放入第二级缓存,同时从第三级缓存中移除A的单例工厂。
-
接着B继续完成初始化,并将自己放入第一级缓存。
-
然后回到A的初始化过程,从第二级缓存中获取到B的实例,完成A的属性注入和初始化,最后将A也放入第一级缓存。
-
最后,一级缓存中保存着实例化、初始化都完成的A、B对象
通过三级缓存,Spring成功解决了单例Bean之间的循环依赖问题。同时,由于Spring的这种解决策略依赖于Bean的生命周期管理,因此如果是通过构造器注入的方式,由于构造器必须在实例化Bean时立即执行,所以无法解决构造器注入引起的循环依赖。
为什么要三级缓存,二级不行吗
三级缓存中存储的是能够生成Bean实例的工厂对象(通常是lambda表达式),这些工厂对象能够延迟Bean的创建,支持AOP代理对象的创建,直到真正需要时才执行。这种延迟创建的能力使得Spring能够在Bean的完全初始化之前将其暴露给其他Bean,从而解决循环依赖问题。
而二级缓存虽然可以在一定程度上解决循环依赖问题,但它在支持代理对象创建和复杂场景下的容错性方面可能不如三级缓存。
Spring支持两种方式的事务管理
编程式事务管理
通过TransactionTemplate或PlatformTransactionManager等类来帮助实现编程式事务管理。
声明式事务管理
声明式事务管理将事务管理的逻辑从业务代码中分离出来,通过AOP技术实现。在Spring中,声明式事务管理通常通过注解和配置来实现。
-
@Transactional:是Spring声明式事务管理的核心。通过在方法或类上添加此注解,可以指定该方法或类中的所有方法需要事务管理。
-
@EnableTransactionManagement:标识开启事务支持。在Spring Boot项目中,通常将这个注解放在启动类上,以启用事务支持。
Spring事务失效的几种场景及原因
事务管理器默认只在遇到未捕获的RuntimeException及其子类,或者标记了@Transactional(rollbackFor = Exception.class)的异常时,才会触发事务回滚
1.数据库不支持事务
Spring事务生效的前提是所连接的数据库要支持事务,如果底层的数据库都不支持事务,则Spring的事务肯定会失效。例如,如果使用的数据库为MySQL,并且选用了MyISAM存储引擎(本身不支持事务),则Spring的事务就会失效。
2.事务方法未被Spring管理
事务方法所在的类没有被Spring管理,则Spring事务会失效。示例:
public class ProductService {
@Autowired
private ProductDao productDao;
@Transactional(rollbackFor = Exception.class)
public void updateProductStockCountById(Integer stockCount, Long id){
productDao.updateProductStockCountById(stockCount, id);
}
}
ProductService类上没有标注@Service注解,ProductService的实例没有加载到Spring IOC容器中,就会造成updateProductStockCountById()方法的事务在Spring中失效。
3.方法没有被public修饰
如果事务所在的方法没有被public修饰,此时Spring的事务会失效。示例
@Service
public class ProductService {
@Autowired
private ProductDao productDao;
@Transactional(rollbackFor = Exception.class)
private void updateProductStockCountById(Integer stockCount, Long id){
productDao.updateProductStockCountById(stockCount, id);
}
}
虽然ProductService上标注了@Service注解,同时updateProductStockCountById()方法上标注了@Transactional(rollbackFor = Exception.class)。
但是updateProductStockCountById()方法为内部的私有方法(使用private修饰),那么此时updateProductStockCountById()方法的事务在Spring中会失效。
4.同一类中方法调用
如果同一个类中的两个方法分别为A和B,方法A上没有添加事务注解,方法B上添加了 @Transactional事务注解,方法A调用方法B,则方法B的事务会失效。
5.未配置事务管理器
如果在项目中没有配置Spring的事务管理器,即使使用了Spring的事务管理功能,Spring的事务也不会生效。
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
6.方法的事务传播类型不支持事务
如果内部方法的事务传播类型为不支持事务的传播类型,则内部方法的事务在Spring中会失效。示例:
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private ProductDao productDao;
@Transactional(propagation = Propagation.REQUIRED)
public void submitOrder(){
//生成订单
Order order = new Order();
long number = Math.abs(new Random().nextInt(500));
order.setId(number);
order.setOrderNo("order_" + number);
orderDao.saveOrder(order);
//减库存
this.updateProductStockCountById(1, 1L);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void updateProductStockCountById(Integer stockCount, Long id){
productDao.updateProductStockCountById(stockCount, id);
}
}
由于updateProductStockCountById()方法的事务传播类型为NOT_SUPPORTED,不支持事务,则updateProductStockCountById()方法的事务会在Spring中失效。
7.不正确的捕获异常
不正确的捕获异常也会导致Spring的事务失效,示例:
@Service
public class OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private ProductDao productDao;
@Transactional(propagation = Propagation.REQUIRED)
public void submitOrder(){
//生成订单
Order order = new Order();
long number = Math.abs(new Random().nextInt(500));
order.setId(number);
order.setOrderNo("order_" + number);
orderDao.saveOrder(order);
//减库存
this.updateProductStockCountById(1, 1L);
}
@Transactional(propagation = Propagation.REQUIRED)
public void updateProductStockCountById(Integer stockCount, Long id){
try{
productDao.updateProductStockCountById(stockCount, id);
int i = 1 / 0;
}catch(Exception e){
logger.error("扣减库存异常:", e.getMesaage());
}
}
}
解释:updateProductStockCountById()方法中使用try-catch代码块捕获了ArithmeticException异常,正常执行完毕,并没有抛出任何异常到submitOrder方法中。因此,submitOrder方法的事务管理器同样不会认为有异常发生,所以submitOrder方法的事务也会正常提交。
8.错误的标注异常类型
如果在@Transactional注解中标注了错误的异常类型,则Spring事务的回滚会失效,示例
@Transactional(propagation = Propagation.REQUIRED)
public void updateProductStockCountById(Integer stockCount, Long id){
try{
productDao.updateProductStockCountById(stockCount, id);
}catch(Exception e){
logger.error("扣减库存异常:", e.getMesaage());
throw new Exception("扣减库存异常");
}
}
因为Spring中对于默认回滚的事务异常类型为RuntimeException,上述代码抛出的是Exception异常,所以此时updateProductStockCountById()方法事务的回滚会失效。
//解决方式
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void updateProductStockCountById(Integer stockCount, Long id){
try{
productDao.updateProductStockCountById(stockCount, id);
}catch(Exception e){
logger.error("扣减库存异常:", e.getMesaage());
throw new Exception("扣减库存异常");
}
}
SpringMVC执行流程
SpringMVC的执行流程划分为三个阶段:初始化阶段、匹配阶段、执行阶段
初始化阶段
当Web容器启动时,它会创建DispatcherServlet对象,并调用其init方法。在这个过程中,Spring Web容器也会被创建,并调用容器的refresh方法。这个方法负责初始化SpringMVC中的一系列关键组件,包括:
- MultipartResolver(文件上传解析器):负责处理文件上传。
- HandlerMapping:根据请求的URL找到对应的处理器(通常是Controller)。
- HandlerAdapter(处理适配器):适配不同的处理器,使得DispatcherServlet能够调用它们处理请求。
- HandlerExceptionResolver(异常解析器):处理在请求处理过程中出现的异常。
- ViewResolver:将逻辑视图名解析为具体的视图实现,以便进行渲染。
这些组件初始化完成后,会被赋值给DispatcherServlet(前端控制器)的成员变量,供后续使用。
匹配阶段
-
用户发送的请求首先会到达DispatcherServlet。
-
DispatcherServlet会遍历所有已注册的HandlerMapping,根据请求的URL找到匹配的Handler(通常是Controller中的某个方法)。
-
找到Handler后,DispatcherServlet会将其与相关的Interceptor(拦截器)一起,构造成一个HandlerExecutionChain执行链。
-
然后,DispatcherServlet会遍历所有HandlerAdapter((处理适配器)),找到能够处理这个Handler的适配器,准备调用。
执行阶段
-
在调用Handler之前,会首先执行所有拦截器的preHandle方法,进行预处理。
-
然后,通过HandlerAdapter调用Handler,执行具体的业务逻辑。
-
Handler处理完请求后,会返回一个ModelAndView对象(其中包含了视图名称和模型数据),然后会执行拦截器的postHandle方法,进行后处理。
-
接着DispatcherServlet会请求ViewResolver(视图解析器)解析ModelAndView中的视图名称,得到具体的View对象。
-
View对象会负责将模型数据渲染到视图中,生成最终的响应内容。
-
最后执行拦截器的afterCompletion方法,完成整个请求的处理流程。
2.Spring Boot
理念:习惯优于配置,内置习惯性配置,无需手动进行配置。使用Spring boot可以很快创建一个独立运行、准生产级别的基于Spring框架的项目,不需要或者只需很少的Spring配置。
特点:内置servlet容器,不需要在服务器部署 tomcat。只需要将项目打成 jar 包,使用 java -jar xxx.jar一键式启动项目;
SpringBoot提供了starter,把常用库聚合在一起,简化复杂的环境配置,快速搭建spring应用环境。
优点
-
可以快速构建项目
-
实现对主流开发框架的无配置集成
-
项目可独立运行,无需外部依赖Servlet容器
-
可以提供运行时的应用监控
-
可以极大地提高开发、部署效率
-
可以与云计算天然集成
核心功能
-
自动配置 针对很多Spring应用程序常见的应用功能,Spring Boot能自动提供相关配置。
-
起步依赖 Spring Boot通过起步依赖为项目的依赖管理提供帮助。起步依赖其实就是特殊的Maven依赖和Gradle依赖,利用了传递依赖解析,把常用库聚合在一起,组成了几个为特定功能而定制的依赖。
-
端点监控 Spring Boot可以对正在运行的项目提供监控。
3. SpringCloud
微服务是将一个原本独立的系统拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间通过基于HTTP的RESTFUL API进行通信协作。
从单体应用到微服务
单体系统的缺点:
修改一个小功能,就需要将整个系统重新部署上线,影响其他功能的运行;
功能模块互相依赖,强耦合,扩展困难。如果出现性能瓶颈,需要对整体应用进行升级,虽然影响性能的可能只是其中一个小模块;
单体系统的优点:
容易部署,程序单一,不存在分布式集群的复杂部署环境;
容易测试,没有复杂的服务调用关系。
微服务的优点:
不同的服务可以使用不同的技术;
隔离性。一个服务不可用不会导致其他服务不可用;
可扩展性。某个服务出现性能瓶颈,只需对此服务进行升级即可;
简化部署。服务的部署是独立的,哪个服务出现问题,只需对此服务进行修改重新部署;
微服务的缺点:
网络调用频繁。性能相对函数调用较差。
运维成本增加。系统由多个独立运行的微服务构成,需要设计一个良好的监控系统对各个微服务的运行状态进行监控。
Spring Cloud是一个基于Spring Boot实现的微服务架构开发工具。spring cloud包含多个子项目:
Spring Cloud Config:配置管理工具,支持使用Git存储配置内容, 可以使用它实现应用配置的外部化存储, 并支持客户端配置信息刷新、加密/解密配置内容等。
Spring Cloud Netflix:核心 组件,对多个Netflix OSS开源套件进行整合。
Eureka: 服务治理组件, 包含服务注册中心、服务注册与发现机制的实现。
Hystrix: 容错管理组件,实现断路器模式, 帮助服务依赖中出现的延迟和为故障提供强大的容错能力。
Ribbon: 客户端负载均衡的服务调用组件。
Feign: 基于Ribbon 和Hystrix 的声明式服务调用组件。
Zuul: 网关组件, 提供智能路由、访问过滤等功能。
Archaius: 外部化配置组件。
Spring Cloud Bus: 事件、消息总线, 用于传播集群中的状态变化或事件, 以触发后续的处理, 比如用来动态刷新配置等。
Spring Cloud Cluster: 针对ZooKeeper、Redis、Hazelcast、Consul 的选举算法和通用状态模式的实现。
Spring Cloud Consul: 服务发现与配置管理工具。
Spring Cloud ZooKeeper: 基于ZooKeeper 的服务发现与配置管理组件。
spring-boot-starter-actuator:该模块能够自动为Spring Boot 构建的应用提供一系列用于监控的端点
SpringCloud常见组件有哪些
SpringCloud包含的组件很多,有很多功能是重复的。其中最常用组件包括:
•注册中心组件:Eureka、Nacos等
•负载均衡组件:Ribbon
•远程调用组件:OpenFeign
•网关组件:Zuul、Gateway
•服务保护组件:Hystrix、Sentinel
•服务配置管理组件:SpringCloudConfig、Nacos
Nacos的服务注册表结构
Nacos采用了数据的分级存储模型,最外层是Namespace,用来隔离环境。然后是Group,用来对服务分组。接下来就是服务(Service)了,一个服务包含多个实例,但是可能处于不同机房,因此Service下有多个集群(Cluster),Cluster下是不同的实例(Instance)。
对应到Java代码中,Nacos采用了一个多层的Map来表示。结构为Map<String, Map<String, Service>>,其中最外层Map的key就是namespaceId,值是一个Map。内层Map的key是group拼接serviceName,值是Service对象。Service对象内部又是一个Map,key是集群名称,值是Cluster对象。而Cluster对象内部维护了Instance的集合。
Nacos如何支撑阿里内部数十万服务注册压力
Nacos内部接收到注册的请求时,不会立即写数据,而是将服务注册的任务放入一个阻塞队列就立即响应给客户端。然后利用线程池读取阻塞队列中的任务,异步来完成实例更新,从而提高并发写能力。
Nacos如何避免并发读写冲突问题
Nacos在更新实例列表时,会采用CopyOnWrite技术,首先将旧的实例列表拷贝一份,然后更新拷贝的实例列表,再用更新后的实例列表来覆盖旧的实例列表。
这样在更新的过程中,就不会对读实例列表的请求产生影响,也不会出现脏读问题了。
Nacos与Eureka的区别
Nacos与Eureka有相同点,也有不同之处,可以从以下几点来描述:
- 接口方式:Nacos与Eureka都对外暴露了Rest风格的API接口,用来实现服务注册、发现等功能
- 实例类型:Nacos的实例有永久和临时实例之分;而Eureka只支持临时实例
- 健康检测:Nacos对临时实例采用心跳模式检测,对永久实例采用主动请求来检测;Eureka只支持心跳模式
- 服务发现:Nacos支持定时拉取和订阅推送两种模式;Eureka只支持定时拉取模式。
Sentinel与Gateway的限流差别
限流算法常见的有三种实现:滑动时间窗口、令牌桶算法、漏桶算法。Gateway则采用了基于Redis实现的令牌桶算法。
而Sentinel内部却比较复杂:
- 默认限流模式是基于滑动时间窗口算法
- 排队等待的限流模式则基于漏桶算法
- 而热点参数限流则是基于令牌桶算法
Sentinel与Hystix的线程隔离的区别
Hystix默认是基于线程池实现的线程隔离,每一个被隔离的业务都要创建一个独立的线程池,线程过多会带来额外的CPU开销,性能一般,但是隔离性更强。
Sentinel是基于信号量(计数器)实现的线程隔离,不用创建线程池,性能较好,但是隔离性一般。