前言
当下在设计大型系统或网站时,为了满足系统的灵活性、扩展性、模块化、松耦合、高可用等特性,在技术架构选择时往往会选用微服务架构。独立服务的拆分会增加部署时机器资源的消耗。在轻量化部署场景的催化下,需要考虑中间件的缩减以及微服务应用的合并部署,已达到降低对服务器资源的依赖。
项目结构
我们的项目工程结构如下所示,其中xxx
代表一个独立的微服务 ,整个工程由多个独立的微服务模块组成,这里只举例说明,没有列举完整的项目结构,api-xxx
模块表示某个独立的微服务的后台管理能力,provider-xxx
模块表示某个独立微服务对其它服务提供能力的模块。
- project-parent
- api-xxx
- provider-xxx
应用合并需要考虑的问题
因为系统整体基于微服务构建,在进行应用合并实现资源减配时,主要考虑将api
和provider
应用进行合并,遇到的主要问题如下:
api
和provider
从业务角度属于同一个,所以重名的类较多,因此会导致Spring
容器中的beanName
重复- ORM框架用的是
JPA
,Hibernate
中的实体只有类名没有包路径,类名重复会导致JPA
中的实体重复 SpringMVC
中注册的接口请求路径重复的问题- 将
api
和provider
合并为一个服务后,其它应用通过RPC调用provider
服务的服务名需要调整 - 其它一些由业务和技术特性决定的不具备普遍性的问题,这里不加赘述
面临上面的问题,如果在一个SpringBoot模块
中,直接通过Maven
将api-xxx模块
和provider-xxx模块
引入后启动肯定会报错的。
应用合并合并
基于以上问题,理想状态是在一个JVM里面启动两个Spring容器,分别对应api
和provider
,减少对服务器资源需求的同时最大程度保留原有的技术架构。
支持多个应用同时启动的容器类,这是一个抽象类,需要由具体启动的应用继承后设置应用名称和SpringBoot的Application类:
public abstract class MultipleServiceRunner {
private ConfigurableApplicationContext applicationContext;
private final String applicationName;
private final Class<?>[] applicationClasses;
private String[] args;
private final static Object lock = new Object();
private Boolean wait = Boolean.FALSE;
public MultipleServiceRunner(String applicationName, Class<?>... applicationClasses) {
this.applicationName = applicationName;
this.applicationClasses = applicationClasses;
}
public void setArgs(String[] args) {
this.args = args;
}
public void run() {
if(applicationContext != null) {
throw new IllegalStateException("AppContext must be null to run this backend");
}
runBackendInThread();
waitUntilBackendIsStarted();
}
private void waitUntilBackendIsStarted() {
try {
synchronized (lock) {
if(wait) {
lock.wait();
}
}
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
private void runBackendInThread() {
final Thread runnerThread = new ApplicationRunner(applicationName);
wait = Boolean.TRUE;
runnerThread.setContextClassLoader(applicationClasses[0].getClassLoader());
runnerThread.start();
}
public void stop() {
if (Optional.ofNullable(applicationContext).isPresent()) {
SpringApplication.exit(applicationContext);
applicationContext = null;
}
}
protected class ApplicationRunner extends Thread {
public ApplicationRunner(String name) {
super(name