微服务与SOA
微服务(Microservices)和面向服务的架构(Service-Oriented Architecture,SOA)都是用于设计大型软件系统的架构风格,但它们在目标、设计原则和实现方式上有所不同。以下是它们之间的主要区别:
微服务(Microservices)和面向服务的架构(Service-Oriented Architecture,SOA)都是用于设计大型软件系统的架构风格,但它们在目标、设计原则和实现方式上有所不同。以下是它们之间的主要区别:
1. 设计粒度
微服务:
- 微服务通常具有更精细的粒度。每个微服务通常只负责一个很小的功能。
- 微服务之间相互独立,每个服务都可以独立部署、升级和扩展。
SOA:
- SOA中的服务粒度通常较大,一个服务可能包含多个子功能。
- 服务可能更加集中,侧重于可重用性。
2. 组件隔离
微服务:
- 微服务强调服务之间的完全隔离。每个服务有自己的数据库和数据管理机制,确保服务之间的松耦合。
SOA:
- SOA中的服务可能会共享数据库和数据模型,这在某种程度上增加了组件之间的耦合。
3. 交互方式
微服务:
- 交互主要通过轻量级的API(通常是RESTful API或消息队列)进行。
SOA:
- 通常使用企业服务总线(ESB)来处理服务之间的通信,可能包括复杂的消息路由、转换和协议中介。
4. 技术多样性
微服务:
- 在微服务架构中,各个服务可以使用不同的编程语言和数据存储技术,增加了技术的多样性。
SOA:
- SOA倾向于使用统一的技术栈,以简化开发和管理。
5. 部署
微服务:
- 微服务支持独立部署。任何单个服务的更新不需要重新部署整个系统。
SOA:
- 更新单个服务可能需要整个系统的重新部署,或至少是较大范围的重新配置。
6. 目标和范围
微服务:
- 主要关注快速开发、部署和优化单个服务的能力,适合持续交付和持续集成的环境。
SOA:
- 更多地关注在不同的业务领域之间实现功能的重用和整合,适合大型企业的需求。
结论
尽管微服务和SOA在某些方面有相似之处,如都是服务为中心的架构风格,但微服务提供了更高的灵活性和敏捷性,特别适合云计算和DevOps文化。而SOA则更适合需要大规模服务整合和复用的传统企业环境。
Spring事务与回滚
Spring 框架通过其提供的事务管理接口,支持在 Java 应用中简化事务管理和数据访问。在 Spring 中,事务管理可以是声明式的(通过注解和 XML 配置)或编程式的。这里主要介绍声明式事务管理和相关的回滚处理。
声明式事务管理
声明式事务管理是利用注解或者 XML 配置来管理事务,这种方法减少了编程的工作量。
1. 使用 @Transactional
注解
在 Spring 中,你可以通过将 @Transactional
注解添加到一个类或者具体的方法上来开启事务管理。例如:
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class MyService {
public void myMethod() {
// 方法实现
}
}
当 myMethod
被调用时,Spring 将确保该方法在一个事务的上下文中执行。
属性设置
@Transactional
注解提供多个属性来定制事务行为,其中一些关键属性包括:
- propagation:事务的传播行为。
- isolation:事务的隔离级别。
- readOnly:是否为只读事务。
- timeout:事务的超时时间。
- rollbackFor:指定可触发回滚的异常类。
- noRollbackFor:指定不触发回滚的异常类。
回滚处理
事务的回滚意味着如果在事务中发生异常,那么所有在该事务中进行的操作都会被撤销。
自动回滚
默认情况下,如果在事务中抛出了运行时异常(RuntimeException
)或错误(Error
),Spring 会自动回滚事务。例如:
@Transactional
public void updateData() {
// 修改数据库中的数据
throw new RuntimeException("Something went wrong!"); // 这将触发事务回滚
}
自定义回滚
如果需要自定义触发回滚的异常类型,可以使用 rollbackFor
属性:
@Transactional(rollbackFor = Exception.class)
public void updateData() throws Exception {
// 修改数据库中的数据
throw new Exception("Something went wrong!"); // 这也将触发事务回滚
}
在上面的例子中,即使抛出的是检查型异常 (Exception
),事务也会回滚。
不回滚例外
使用 noRollbackFor
属性,你可以定义不应触发回滚的异常类型:
@Transactional(noRollbackFor = SomeCustomException.class)
public void updateData() throws SomeCustomException {
// 修改数据库中的数据
throw new SomeCustomException("This will not cause rollback");
}
在此例中,尽管发生了 SomeCustomException
异常,事务仍会尝试提交。
总结
Spring 的事务管理非常灵活,支持广泛的配置选项,适合各种不同需求的应用场景。正确地理解和使用事务及其回滚策略,对于开发稳定、可靠的企业级应用至关重要。
Spring IOC AOP
Spring框架的两大核心特性是控制反转(IoC, Inversion of Control)和面向切面编程(AOP, Aspect-Oriented Programming)。这两种技术共同支持了Spring在企业级应用开发中的灵活性与强大功能
控制反转(IoC)
控制反转是一种设计原则,用于减少计算机代码之间的耦合关系。其中,最常见的实现方式称为依赖注入(DI, Dependency Injection)。Spring通过其IoC容器实现了依赖注入。
IoC容器
Spring框架有一个强大的IoC容器,它管理Java对象的生命周期和对象之间的依赖关系。IoC容器可以通过XML配置文件、Java注解或Java代码来配置。
-
Bean定义:在Spring中,被IoC容器管理的对象被称为beans。这些beans的定义包含了bean的元数据,如创建bean的类名、bean的行为信息如作用域和生命周期回调等。
-
依赖注入:IoC容器通过依赖注入将依赖关系自动注入到相关的beans中,无需手动构造对象。依赖注入主要有三种方式:
- 构造函数注入
- Setter注入
- 字段注入
示例:使用注解进行依赖注入
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class MyService {
@Resource
private MyRepository myRepository; // 自动注入MyRepository的实例
public void performAction() {
myRepository.doAction();
}
}
面向切面编程(AOP)
面向切面编程是一种编程范式,用于隔离程序中的横切关注点(cross-cutting concerns),例如事务管理、安全检查、日志记录等。
AOP基本概念
- 切面(Aspect):模块化的横切关注点。例如,日志记录可能会散布在多个模块中,但在AOP中它们会被统一处理。
- 连接点(Join Point):在程序执行过程中插入切面的点,例如方法调用或字段访问。
- 通知(Advice):切面在特定连接点采取的动作,分为前置通知、后置通知、环绕通知等。
- 切入点(Pointcut):匹配连接点的表达式。
实现AOP
Spring AOP是通过代理模式实现的,它支持基于JDK的动态代理和CGLIB代理。默认情况下,Spring会根据目标bean的类型自动选择代理策略。
示例:声明一个简单的切面
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class LoggingAspect {
@Before("execution(* com.example.service.MyService.performAction(..))")
public void logBefore() {
System.out.println("Before performing action");
}
}
在此示例中,logBefore
方法将在 MyService
的 performAction
方法执行之前被自动调用。
通过IoC,Spring帮助开发者从繁琐的程序依赖管理中解放出来,极大地增加了程序的模块化和可测试性。通过AOP,Spring允许开发者有效地抽离出横切关注点,使系统的设计更加清晰。两者结合,为构建可维护、可扩展的企业级应用提供了强有力的支持。
concurrentHashMap原理
ConcurrentHashMap
是 Java 中提供的一个线程安全的哈希表,用于在多线程环境下提供高效的并发访问。与 Hashtable
和同步的 HashMap
(通过 Collections.synchronizedMap(new HashMap<...>())
创建) 相比,ConcurrentHashMap
提供了更高的读写性能和更细的锁粒度。它是 Java 并发包的一部分(java.util.concurrent
包)。
ConcurrentHashMap 的核心原理:
1. 分段锁(Segmentation)
在 Java 8 之前,ConcurrentHashMap
使用分段锁技术来实现高效的并发控制。每个分段锁管理哈希表的一部分数据。这允许多个更新操作可以并行进行,只要它们落在不同的分段上。具体来说,它内部含有一个 Segment
数组,每个 Segment
本质上就是一个小的 hash table,它有自己的锁。只有当需要对某个 key-value 对进行操作时,才会锁定相应的 Segment
,从而减少了锁竞争。
2. 锁的粒度
Java 8 以后的版本中,放弃了原有的 Segment 的方式,转而使用了 Node 节点加锁的方式,从而进一步减小了锁的粒度。Node 节点主要是哈希结构的基本单元,每个节点代表一个键值对。
3. 锁策略
- 非阻塞读取:
ConcurrentHashMap
允许完全并行的读取,这是通过使用 volatile 类型的 Node 引用来实现的,确保读取总是获取到最新写入的数据。 - CAS 操作:
ConcurrentHashMap
在其内部结构中大量使用了 CAS 操作(Compare-And-Swap),特别是在节点添加或替换时,这些操作通常无需锁定。 - 锁分离:在 Java 8 中,
ConcurrentHashMap
还引入了红黑树来处理链表较长的情况。当链表中的元素个数超过一定阈值时,链表会转换为红黑树,从而优化搜索效率。同时,在红黑树的相关节点上用 synchronized 关键字来保证线程安全。
4. 数据结构
- 数组 + 链表 + 红黑树:在 Java 8 以后,
ConcurrentHashMap
内部是一个Node<K,V>[]
数组;每个 bucket 可以是链表头、红黑树的根或者单独的节点。这样既提高了查找效率,也使得并发操作变得更加高效。
总结
ConcurrentHashMap
是基于高效的多线程控制设计的,允许多个读操作和一定数量的并行写操作,通过使用 volatile 读、CAS 操作、细粒度的锁机制等技术手段达到在多线程环境中高效地执行。它避免了 Hashtable
和同步 HashMap
使用的方法级别锁,提供了更好的并发性能。
设计模式
在软件工程中,设计模式是针对常见问题的标准解决方案。它们被用来组织代码以便于理解和重用。在面试中,问到设计模式通常是为了评估候选人是否能有效地利用这些模式来解决编程问题、优化代码结构或提升代码的维护性。下面是一些面试中常被询问的设计模式:
1. 单例模式(Singleton Pattern)
单例模式确保一个类只有一个实例,并提供一个全局访问点。
-
应用场景:配置管理器、线程池。
public class Singleton { private static Singleton instance; private Singleton() {} // 私有构造函数,防止外部实例化 public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
2. 工厂模式(Factory Pattern)
工厂模式定义了一个创建对象的接口,允许子类决定实例化哪一个类。工厂方法让类的实例化延迟到子类中进行。
- 应用场景:不同的日志记录器(文件日志记录器、数据库日志记录器)。
interface Logger {
void log(String message);
}
class FileLogger implements Logger {
public void log(String message) {
System.out.println("Log message to a file: " + message);
}
}
class ConsoleLogger implements Logger {
public void log(String message) {
System.out.println("Log message to console: " + message);
}
}
class LoggerFactory {
public Logger getLogger(String type) {
if (type.equalsIgnoreCase("file")) {
return new FileLogger();
} else if (type.equalsIgnoreCase("console")) {
return new ConsoleLogger();
}
throw new IllegalArgumentException("Unknown logger type");
}
}
3. 观察者模式(Observer Pattern)
观察者模式定义了对象间的一种一对多的依赖关系,当一个对象改变状态时,所有依赖于它的对象都会得到通知。
- 应用场景:事件驱动系统(如 GUI 工具),监听器模式。
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(String event);
}
class ConcreteObserver implements Observer {
public void update(String event) {
System.out.println("Received event: " + event);
}
}
class Subject {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void notifyObservers(String event) {
for (Observer observer : observers) {
observer.update(event);
}
}
}
4. 策略模式(Strategy Pattern)
策略模式定义了一系列算法,并将每个算法封装起来,使它们可以互相替换,且算法的改变不会影响到使用算法的客户。
- 应用场景:验证策略、路由策略、任何需要从多种算法中选择一种的情形。
interface SortingStrategy {
void sort(int[] array);
}
class BubbleSort implements SortingStrategy {
public void sort(int[] array) {
System.out.println("Sorting using bubble sort");
// Add bubble sort logic here
}
}
class QuickSort implements SortingStrategy {
public void sort(int[] array) {
System.out.println("Sorting using quick sort");
// Add quick sort logic here
}
}
class Context {
private SortingStrategy strategy;
public Context(SortingStrategy strategy) {
this.strategy = strategy;
}
public void setStrategy(SortingStrategy strategy) {
this.strategy = strategy;
}
public void sortArray(int[] array) {
strategy.sort(array);
}
}
5. 装饰器模式(Decorator Pattern)
装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。
- 应用场景:图形用户界面组件的装饰、文件流的增强。
interface Coffee {
double getCost();
String getDescription();
}
class SimpleCoffee implements Coffee {
public double getCost() {
return 1.0;
}
public String getDescription() {
return "Simple coffee";
}
}
class MilkDecorator implements Coffee {
protected Coffee decoratedCoffee;
public MilkDecorator(Coffee c) {
this.decoratedCoffee = c;
}
public double getCost() {
return decoratedCoffee.getCost() + 0.5;
}
public String getDescription() {
return decoratedCoffee.getDescription() + ", milk";
}
}
6. 适配器模式(Adapter Pattern)
适配器模式作为两个不兼容接口之间的桥梁,让原本由于接口不兼容而不能一起工作的那些类可以一起工作。
- 应用场景:系统的数据和行为都正确但接口不符时,可以考虑使用适配器,让旧组件适用于新系统,或使新组件可以与旧系统交互。
7. 命令模式(Command Pattern)
命令模式将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
- 应用场景:GUI 中的按钮和菜单项、智能家居控制等。
8. 外观模式(Facade Pattern)
外观模式提供了一个统一的接口来访问子系统中的一群接口。外观定义了一个高层接口,使得子系统更易使用。
- 应用场景:系统复杂度高,需要一个简单的接口进行区域性控制时,或者需要分层次构造系统时。