Giter Site home page Giter Site logo

jushata's Introduction

JuShaTa容器

JuShaTa是一个Java容器,提供模块隔离及模块热加载能力。

一、前言

大部分Java开发者,对Tomcat都不会太陌生,毕竟当时主流的开发方式还是将应用打包成war包,部署在webapps目录下随Tomcat运行。在Tomcat中,每个Web应用都有一个对应的类加载器实例,从而对每个应用的class和jar包进行隔离,避免应用之间类或者jar包冲突。

当然现在有了已经成为Java事实标准的Spring/SpringBoot框架后,可以直接在应用中集成内嵌的Tomcat或Jetty等Web容器,应用即可运行,这种开发方式带来的好处不言而喻交付即运维,这也更加符合devops理念。

但是在一个Java应用里面包含多个SpringBoot服务时,这些服务可能会调用不同版本的jar包,那么在程序启动或者运行过程中可能会报NoSuchMethodException这样的异常信息。特别是有的jar包版本不一样,在测试环境或者本地环境都没有问题,到了线上偶现由于加载不同版本jar包顺序不一样引起的异常,难以排查和重现。也有可能这些不同的服务里面有名字相同的bean,则会在启动时候报bean冲突。

基于此,我们提供了一个类似于Tomcat的Java容器JuShaTa,在JuShaTa容器中每个SpringBoot服务都是一个独立的模块。通过自定义ClassLoader,不同模块使用不同的ClassLoader进行加载,解决jar包冲突;使用Spring Context进行上下文隔离,每个模块对应一个Context,解决bean冲突。

二、背景

在日常开发环境中,经常会遇到团队协作,多人同时开发一个或多个大型项目。通常做法是将业务按照领域进行拆分成多个子项目或者微服务,拆分过程中不可避免的会出现以下的问题:

  • 服务粒度拆分过粗,一些关联度较低的功能放在一个模块,不符合高内聚低耦合的设计原则。
  • 服务粒度拆分过细,会出现大量模块,有些模块或微服务只包含少量功能,独立部署上线后访问量也会比较低,但也要占用一个java进程会比较浪费机器资源。因此会比较倾向于将这些模块进行独立开发,合并部署。至少在开发阶段,能够保证模块之间会比较独立解耦。但合并部署也会带来新的问题,模块独立运行时正常,合并一起就会启动不了,各种冲突接踵而至。

三、解决方案

基于以上问题,JuShaTa提供一种容器化的思路来解决,正于之前提到的Tomcat一样,将模块放到JuShaTa容器中,由容器来进行生命周期管理,容器提供模块隔离以及模块动态装载卸载的能力。

1、开箱即用

  • 基于SpringBoot开发,兼容原生SpringBoot-Starter模块,切换成本低。

2、模块隔离

容器内部署多个模块时,将会带来以下问题:

  • jar包冲突:如A模块依赖于fastjson-1.1.x,B模块依赖于fastjson-1.2.x,运行时很容易会报NoSuchMethodException。
  • bean冲突:A模块和B模块都定义了xxxService,则会报bean冲突(当然可以通过设置allowBeanDefinitionOverriding属性解决,但会引来新的问题)。

因此,容器必须解决这两个问题:

  • 解决jar包冲突,通过自定义ClassLoader来实现,不同模块使用不同的ClassLoader进行加载。
  • 解决bean冲突,使用Spring Context进行隔离,每个模块对应一个Context,Context间可以有父子依赖关系。

3、动态加载

  • 容器提供热加载的能力,定时检查指定目录下的模块,或者通过容器暴露的API来进行模块管理。

四、整体架构:

JuShaTa容器整体架构如下,模块间通过ClassLoader及SpringContext进行隔离。

image

五、开发示例:

1、在项目的pom文件中引入依赖,将你的SpringBoot应用打成fat-jar后放到某个目录,将配置项“jushata.modules-file”值设置为该目录地址即可启动项目运行。

<dependency>
    <groupId>com.didiglobal.jushata</groupId>
    <artifactId>jushata-boot</artifactId>
    <version>1.0.0</version>
</dependency>

2、提供 jushata-samples 模块进行示例演示(测试前先运行mvn package生成模块打包):

  • jushata-sample-standard 演示对原生 springboot-starter 模块的兼容的能力
  • jushata-sample-child1 和 jushata-sample-child2 演示fat-jar模块上下文隔离及类隔离的能力

更多开发文档见 开发手册

六、 协议

Apache-2.0 license

JuShaTa 基于 Apache-2.0 协议进行分发和使用,更多信息参见 [协议文件](LICENSE)。

jushata's People

Contributors

wbtiger avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jushata's Issues

感觉JuShaTa貌似并未解决README中阐述的问题

READEME中阐述了两个很主要的问题:

容器内部署多个模块时,将会带来以下问题:

  • jar包冲突:如A模块依赖于fastjson-1.1.x,B模块依赖于fastjson-1.2.x,运行时很容易会报NoSuchMethodException。
  • bean冲突:A模块和B模块都定义了xxxService,则会报bean冲突(当然可以通过设置allowBeanDefinitionOverriding属性解决,但会引来新的问题)。
    因此,容器必须解决这两个问题:
  • 解决jar包冲突,通过自定义ClassLoader来实现,不同模块使用不同的ClassLoader进行加载。
  • 解决bean冲突,使用Spring Context进行隔离,每个模块对应一个Context,Context间可以有父子依赖关系。

但实际上我看了下源码,发现“通过自定义ClassLoader来实现”只是说给每一个单体服务分配一个classloader实例,每个单体服务本身就是独立的,只不过此处是将其部署到同一个JVM当中,这并没有和上文中“NoSuchMethodException”相对应。我理解的是NoSuchMethodException问题是出现在一个单体服务执行的一个进程中,如果这个单体服务本身存在多版本不兼容问题,JuShaTa并不能解决。

另外,spring context隔离,这个也是将多个服务独立了,本身就是单独的spring context,并且没有建立父子关系。

所以我感觉这个项目更像是在一个JVM中混合动态部署不同的服务,但并没有真正的做到对单体服务的类隔离及context隔离。
不知道我这么理解对不对,希望作者有时间可以解释一下,谢谢!

原理补充

看了下代码,主要还是基于可编程式的SpringApplication

com.didiglobal.jushata.springboot.JushataSpringBootModule

 DefaultResourceLoader resourceLoader = new JushataResourceLoader(this.classLoader);
            SpringApplication app = new SpringApplication(resourceLoader, sources.toArray(new Class[0]));
            app.setBannerMode(Banner.Mode.OFF);
            app.setLogStartupInfo(false);
            app.addInitializers(new JushataApplicationContextInitializer(this.parent, httpPort));
            app.addListeners((ApplicationListener<ApplicationEnvironmentPreparedEvent>) event -> {

                final String port = event.getEnvironment().getProperty("server.port", String.valueOf(httpPort));
                event.getSpringApplication().setWebEnvironment(Integer.valueOf(port) > 0);
            });

            return app.run();

执行外部jar目录报错

java.lang.ClassCastException: class org.springframework.boot.context.event.ApplicationStartingEvent cannot be cast to class org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent (org.springframework.boot.context.event.ApplicationStartingEvent and org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent are in unnamed module of loader 'app')
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:127)
at org.springframework.boot.context.event.EventPublishingRunListener.starting(EventPublishingRunListener.java:68)
at org.springframework.boot.SpringApplicationRunListeners.starting(SpringApplicationRunListeners.java:48)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:313)
at com.didiglobal.jushata.springboot.JushataSpringBootModule.refreshContext(JushataSpringBootModule.java:146)
at com.didiglobal.jushata.springboot.JushataSpringBootModule.loadJushataModule(JushataSpringBootModule.java:118)
at com.didiglobal.jushata.springboot.JushataSpringBootModule.start(JushataSpringBootModule.java:60)
at com.didiglobal.jushata.springboot.JushataSpringBootModuleLoader.start(JushataSpringBootModuleLoader.java:132)
at com.didiglobal.jushata.springboot.JushataSpringBootModuleLoader.load(JushataSpringBootModuleLoader.java:46)
at com.didiglobal.jushata.springboot.JushataApplication.load(JushataApplication.java:55)
at com.didiglobal.jushata.springboot.JushataApplication.run(JushataApplication.java:41)
at com.didiglobal.jushata.springboot.JushataApplicationReadyEventListener.onApplicationEvent(JushataApplicationReadyEventListener.java:12)
at com.didiglobal.jushata.springboot.JushataApplicationReadyEventListener.onApplicationEvent(JushataApplicationReadyEventListener.java:6)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:400)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:354)
at org.springframework.boot.context.event.EventPublishingRunListener.running(EventPublishingRunListener.java:103)
at org.springframework.boot.SpringApplicationRunListeners.running(SpringApplicationRunListeners.java:78)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:343)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1255)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1243)
at com.didiglobal.jushata.sample.SampleApplication.main(SampleApplication.java:15)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.