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

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 代码分支维护

相关推荐
m0_748256143 小时前
SpringBoot
java·spring boot·后端
多想和从前一样4 小时前
Django 创建表时 “__str__ ”方法的使用
后端·python·django
涛粒子6 小时前
Spring Bean 生命周期的执行流程
java·后端·spring
赵琳琅6 小时前
Java语言的云计算
开发语言·后端·golang
赵琳琅6 小时前
MDX语言的安全开发
开发语言·后端·golang
夏梓蕙7 小时前
Elixir语言的软件开发工具
开发语言·后端·golang
夏梓蕙8 小时前
R语言的Web开发
开发语言·后端·golang
敲代码的小王!8 小时前
在Java项目中跨域的解决办法
java·开发语言·微服务·springboot
绝无仅有8 小时前
Deepseek 万能提问公式:高效获取精准答案
后端·面试·架构
慕容秋瑶9 小时前
T-SQL语言的Web开发
开发语言·后端·golang