轻服务是反微服务吗?-- 微服务的轻量化

1. 背景

  • 对外交付项目多为微服务架构,全量部署需要占用较多的内存资源。
  • 私有化交付时,客户对投入的硬件成本有预算限制,前期太多的硬件投入影响客户体验,不利于成单。

2.目标

  • 不改变目前SAAS环境微服务部署架构,避免SAAS环境出现性能问题,进而影响系统的稳定性。
  • 私有化交付项目通过部署、打包等技术,将多个微服务部署到一个JVM进程或整合为一个单体项目,进而缩减硬件成本。
  • 只针对SpringBoot项目做改造。

3.【Docker】融合部署

3.1 docker单容器多应用

3.2 dockerfile改造

  • 端口映射改造

    一个docker容器同时映射多个应用服务端口

  • 应用jar文件包含改造

    一个docker镜像需将多个springboot jar文件打包进去

  • ENTRYPOINT启动脚本改造

    ENTRYPOINT启动脚本需包含多个应用的启动过程

4.【Tomcat】融合部署

4.1 Tomcat单实例多应用

4.2 原微服务应用改造

  • 修改pom 为war
xml 复制代码
原:
<artifactId>web-provider</artifactId>
<name>web-provider</name>
<packaging>jar</packaging>

新:
<artifactId>web-provider</artifactId>
<name>web-provider</name>
<packaging>war</packaging>
  • 排除SpringBoot内嵌容器tomcat
xml 复制代码
pom新增坐标:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>
  • 排除三方组件,减少war包大小
xml 复制代码
调整spring-boot-maven-plugin插件只把业务代码及依赖的业务组件打包进war中:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <includes>        
            <include> <!-- 根据实际情况修改,<includes>下可以包含多个<include> -->
                <groupId>com.lw.micro</groupId> <!-- 业务组件groupId -->
                <artifactId>web-provider-api</artifactId><!-- 业务组件artifactId -->
            </include>
        </includes>
    </configuration>
</plugin>
  • 改造启动类
scala 复制代码
原启动类继承SpringBootServletInitializer并实现configure方法:

@ComponentScan
@SpringBootApplication
@EnableFeignClients({"com.web.provider.api"})
public class Application extends SpringBootServletInitializer{
   public static void main(String[] args) {
      SpringApplication.run(Application.class, args);
   }

   @Override
   protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
      return builder.sources(Application.class);
   }
}

4.3抽取全量三方组件

  • 新建项目为pom
  • 中需包含各个微服务war包共享的全量三方组件
  • 利用maven-dependency-plugin插件将依赖组件抽取到指定目录中
xml 复制代码
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <executions>
        <execution>
            <id>copy-dependencies</id>
            <phase>package</phase>
            <goals>
                <goal>copy-dependencies</goal>
            </goals>
            <configuration>
                <outputDirectory>${project.build.directory}/lib</outputDirectory><!-- 依赖包输出目录 -->
                <excludeTransitive>false</excludeTransitive>
                <stripVersion>false</stripVersion>
                <includeScope>runtime</includeScope>
                <excludeGroupIds>com.lw.micro</excludeGroupIds><!-- 需要排除的业务组件groupId,多个以逗号分割 -->
            </configuration>
        </execution>
    </executions>
</plugin>

4.4 Tomcat部署

4.4.1 tomcat配置

4.4.1.1server.xml配置

  • 根据实际情况修改
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <!-- Prevent memory leaks due to use of particular java/javax APIs-->
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <!-- Global JNDI resources
       Documentation at /docs/jndi-resources-howto.html
  -->
  <GlobalNamingResources>
    <!-- Editable user database that can also be used by
         UserDatabaseRealm to authenticate users
    -->
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="lw-micro-demo">
    <Connector port="8280" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8243"
               maxParameterCount="1000"
               />
    <Engine name="lw-micro-demo" defaultHost="www.demo.com">
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>
      <Host name="www.demo.com"  appBase="webapps"
            unpackWARs="true" autoDeploy="false" deployOnStartup="false">
        <Context path="web-provider" docBase="web-provider"/>
        <Context path="web-consumer" docBase="web-consumer"/>
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

      </Host>
    </Engine>
  </Service>  
</Server> 

4.4.1.2 catalina.properties配置

  • 根据实际情况修改
ini 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />

  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />

    
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <GlobalNamingResources>
    <!-- Editable user database that can also be used by
         UserDatabaseRealm to authenticate users
    -->
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>
  <Service name="lw-micro-demo">
    <Connector port="8280" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8243"
               maxParameterCount="1000"
               />
    <Engine name="lw-micro-demo" defaultHost="www.demo.com">
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>
      <Host name="www.demo.com"  appBase="webapps"
            unpackWARs="true" autoDeploy="false" deployOnStartup="false">
        <Context path="web-provider" docBase="web-provider"/>
        <Context path="web-consumer" docBase="web-consumer"/>
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
    </Engine>
  </Service>  
</Server>

4.4.2 部署目录

1、war包部署目录为webapps 2、webapps目录为默认自带目录

1、各个war包共享三方组件部署目录为shared

2、shared目录为catalina.properties中shared.loader自定义目录,需自建

5【Maven】融合打包

5.1 打包方案

5.2 原微服务应用改造

  • 修改SpringBoot打包类型为普通jar包
xml 复制代码
注释SpringBoot打包插件:
<!--            <plugin>-->
<!--                <groupId>org.springframework.boot</groupId>-->
<!--                <artifactId>spring-boot-maven-plugin</artifactId>-->
<!--                <configuration>-->
<!--                    <fork>false</fork>-->
<!--                </configuration>-->
<!--            </plugin>-->
  • 命名冲突改造

    1. 包名冲突改造
    2. IOC容器Bean类名冲突改造 禁用如下配置解决Bean类名冲突问题spring.main.allow-bean-definition-overriding: true
  • 数据源配置改造

    1. 单数据源,抽取各个微服务的数据源到公共包中,各个微服务依赖公共包,配置类及配置项key不需要调整

    2. 多数据源,需要按照各个微服务的命名区分配置类及配置项key,以保障集成为单体时不冲突,如:

      • 配置项key

        bash 复制代码
            当前配置
           druid.datasource.url
           druid.datasource.username
           druid.datasource.password
        
           # 改后配置
           xxx.druid.datasource.url
           xxx.druid.datasource.username
           xxx.druid.datasource.password
      • 配置类

      less 复制代码
         @Configuration
         @Slf4j
         @MapperScan(basePackages = {"com.xxx.dao"}, sqlSessionFactoryRef = "xxxSqlSessionFactory")
         public class XXXDataSourceConfiguration extends DruidDataSoureConfiguration {
             @Value("com.alibaba.druid.pool.DruidDataSource")
             private Class<? extends DataSource> dataSourceType;
      
             @Bean(name = "xxxDataSource")
             @ConfigurationProperties(prefix = "xxx.druid.datasource")
             public DataSource dataSource() {
                 DataSource dataSource = DataSourceBuilder.create().type(dataSourceType).build();
                 return buildDataSourcePool(dataSource);
             }
      
             @Bean(name = "xxxSqlSessionFactory")
             public SqlSessionFactory sqlSessionFactory(@Qualifier("xxxDataSource") DataSource dataSource) throws Exception {
                 SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
                 bean.setDataSource(dataSource);
                 bean.setMapperLocations(resolveMapperLocations());
                 return bean.getObject();
             }
      
             @Bean(name = "xxxTransactionManager")
             public DataSourceTransactionManager transactionManager(@Qualifier("xxxDataSource") DataSource dataSource) {
                 return new DataSourceTransactionManager(dataSource);
             }
      
             @Bean(name = "xxxSqlSessionTemplate")
             public SqlSessionTemplate sqlSessionTemplate(@Qualifier("xxxSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
                 return new SqlSessionTemplate(sqlSessionFactory);
             }
      
             private Resource[] resolveMapperLocations() {
                 ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
                 List<String> mapperLocations = new ArrayList<>();
                 mapperLocations.add("classpath*:com/xxx/dao/**/*.xml");
      
                 List<Resource> resources = new ArrayList<>();
                 if (CollectionUtils.isNotEmpty(mapperLocations)) {
                     for (String mapperLocation : mapperLocations) {
                         try {
                             Resource[] mappers = resourcePatternResolver.getResources(mapperLocation);
                             resources.addAll(Arrays.asList(mappers));
                         } catch (IOException e) {
                             log.error("resourcePatternResolver getResources error", e);
                         }
                     }
                 }
      
                 return resources.toArray(new Resource[resources.size()]);
             }
         ```
  • HTTP客户端改造

    • 建议HTTP客户端统一改造为OpenFeign
    • 建议OpenFeign接口类按单一职责定义
    • HTTP客户端情况

5.3 新建单体项目

  • 新建一个SpringBoot项目,打包插件为spring-boot-maven-plugin
  • 添加原各个微服务普通jar组件依赖
xml 复制代码
<dependencies>
    <dependency>
        <groupId>com.lw.micro</groupId>
        <artifactId>web-provider</artifactId>
        <version>${project.parent.version}</version>
    </dependency>
    <dependency>
        <groupId>com.lw.micro</groupId>
        <artifactId>web-consumer</artifactId>
        <version>${project.parent.version}</version>
    </dependency>
</dependencies>
  • 添加原各个微服务application.yml配置项(删除重复项)
  • 启动类增加@ComponentScan、@MapperScan注解
less 复制代码
@MapperScan(basePackages = {
      "com.web.provider.dao",
      "com.web.consumer.dao"
})
@ComponentScan({
      "com.web.provider.*",
      "com.web.consumer.*"
})
@SpringBootApplication
public class Application{
   public static void main(String[] args) {
      SpringApplication.run(Application.class, args);
   }
}
  • 合并mybatis-plus.mapper-locations配置
ruby 复制代码
mybatis-plus:
  mapper-locations: classpath*:com/web/provider/**/mapping/*.xml,classpath*:com/web/consumer/**/mapping/*.xml
  • OpenFeign本地调用改造
less 复制代码
1、新建的单体项目启动类@EnableFeignClients注解只指定依赖的外部服务接口
@MapperScan(basePackages = {
      "com.web.provider.dao",
      "com.web.consumer.dao"
})
@ComponentScan({
      "com.web.provider.*",
      "com.web.consumer.*"
})
@EnableFeignClients(basePackages = {
    "依赖的外部服务接口"
})
@SpringBootApplication
public class Application{
   public static void main(String[] args) {
      SpringApplication.run(Application.class, args);
   }
}

2、新建的单体项目对应Controller类实现@FeignClient注解的接口
@FeignClient(name="web-provider", url = "${web.provider.url}")
public interface ProviderTestApi{

    @GetMapping("/provider-test/getTest")
    Response getTest();

}

@RestController
@RequestMapping("/provider-test")
@Slf4j
public class ProviderTestController implements ProviderTestApi{
    @Autowired
    private IProviderTestService providerTestService;

    @Override
    @GetMapping("/getTest")
    public Response getTest() {
        log.info("remote obj addr={}",this.toString());
        Test test = providerTestService.getById(1);
        log.info(test.toString());
        return Response.ok("调用provider成功");
    }

}

6 方案选型

6.1 微服务拆分方式

*备注:较少冲突*

*备注:较多冲突*

6.2 方案选型

  • 方案对比
方案 优点 缺点 适用场景 ffff 改造Demo
【Maven】融合打包 1. 全部SpringBoot部署 2. 部署运维方案一致 1. 命名冲突较多时改造成本高 2. http客户端需要改造 1. 各个微服务包名及类名有少量冲突2. 改造风险可控3. 改造成本可控 具体见5.2~5.3 xxx
【Tomcat】融合部署 1. 不涉及命名冲突改造。2.不涉及http客户端改造 1. SaaS SpringBoot部署,OP Tomcat部署 2. 部署运维方案不一致 1. 各个微服务包名及类名有大量冲突 2.改造风险高。3.改造成本高。4.缩减JVM内存部署资源 具体见4.2~4.3 xxx
【Docker】融合部署 1. 无需代码变动2. 需要部署的镜像减少,部署运维过程简化 1. JVM内存资源未明显缩减 1. 各个微服务包名及类名有大量冲突 具体见3.2
  • 改造难度

    Maven】融合打包>【Tomcat】融合部署>【Docker】融合部署

  • 改造收益

【Maven】融合打包>【Tomcat】融合部署>【Docker】融合部署

6.3 代码分支维护

相关推荐
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
Yaml42 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
小码编匠3 小时前
一款 C# 编写的神经网络计算图框架
后端·神经网络·c#
AskHarries3 小时前
Java字节码增强库ByteBuddy
java·后端
佳佳_4 小时前
Spring Boot 应用启动时打印配置类信息
spring boot·后端
掘金-我是哪吒4 小时前
微服务mysql,redis,elasticsearch, kibana,cassandra,mongodb, kafka
redis·mysql·mongodb·elasticsearch·微服务
许野平5 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
茶馆大橘5 小时前
微服务系列六:分布式事务与seata
分布式·docker·微服务·nacos·seata·springcloud