前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring-基于Spring使用自定义注解及Aspect实现数据库切换

Spring-基于Spring使用自定义注解及Aspect实现数据库切换

作者头像
小小工匠
发布2021-08-16 16:29:52
4540
发布2021-08-16 16:29:52
举报
文章被收录于专栏:小工匠聊架构

文章目录

实现思路

重写Spring的AbstractRoutingDataSource抽象类的determineCurrentLookupKey方法。

我们来看下Spring-AbstractRoutingDataSource的源码

AbstractRoutingDataSource获取数据源之前会先调用determineCurrentLookupKey方法查找当前的lookupKey。

代码语言:javascript
复制
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
.......
return dataSource;

lookupKey为数据源标识,因此通过重写这个查找数据源标识的方法就可以让spring切换到指定的数据源.

从变量定义中可以知道resolvedDataSources为Map类型的对象。

代码语言:javascript
复制
private Map<Object, DataSource> resolvedDataSources; 

示例

在这里插入图片描述
在这里插入图片描述

步骤一 新建Maven工程

依赖如下: pom.xml

代码语言:javascript
复制
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0modelVersion>

	<groupId>com.artisangroupId>
	<artifactId>dynamicDataSourceartifactId>
	<version>0.0.1-SNAPSHOTversion>
	<packaging>jarpackaging>

	<name>dynamicDataSourcename>
	<url>http://maven.apache.orgurl>

	<properties>
		<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
		<file.encoding>UTF-8file.encoding>
		<spring.version>4.3.9.RELEASEspring.version>
		<servlet.version>3.1.0servlet.version>
		<aspectj.version>1.8.1aspectj.version>
		<commons-dbcp.version>1.4commons-dbcp.version>
		<jetty.version>8.1.8.v20121106jetty.version>
		<log4j.version>1.2.17log4j.version>
		<log4j2.version>2.8.2log4j2.version>
		<testng.version>6.8.7testng.version>
		<oracle.version>11.2.0.4.0oracle.version>
		<jstl.version>1.2jstl.version>
	properties>

	<dependencies>
		
		<dependency>
			<groupId>org.springframeworkgroupId>
			<artifactId>spring-beansartifactId>
			<version>${spring.version}version>
		dependency>
		<dependency>
			<groupId>org.springframeworkgroupId>
			<artifactId>spring-contextartifactId>
			<version>${spring.version}version>
		dependency>
		<dependency>
			<groupId>org.springframeworkgroupId>
			<artifactId>spring-context-supportartifactId>
			<version>${spring.version}version>
		dependency>
		<dependency>
			<groupId>org.springframeworkgroupId>
			<artifactId>spring-jdbcartifactId>
			<version>${spring.version}version>
		dependency>
		<dependency>
			<groupId>org.springframeworkgroupId>
			<artifactId>spring-webmvcartifactId>
			<version>${spring.version}version>
		dependency>

		<dependency>
			<groupId>commons-dbcpgroupId>
			<artifactId>commons-dbcpartifactId>
			<version>${commons-dbcp.version}version>
		dependency>


		<dependency>
			<groupId>org.aspectjgroupId>
			<artifactId>aspectjweaverartifactId>
			<version>${aspectj.version}version>
		dependency>


		<dependency>
			<groupId>org.testnggroupId>
			<artifactId>testngartifactId>
			<version>${testng.version}version>
			<scope>testscope>
		dependency>
		<dependency>
			<groupId>org.springframeworkgroupId>
			<artifactId>spring-testartifactId>
			<version>${spring.version}version>
			<scope>testscope>
		dependency>

		
		<dependency>
			<groupId>com.oraclegroupId>
			<artifactId>ojdbc6artifactId>
			<version>${oracle.version}version>
		dependency>

		<dependency>
			<groupId>org.testnggroupId>
			<artifactId>testngartifactId>
			<version>${testng.version}version>
			<scope>testscope>
		dependency>

		<dependency>
			<groupId>org.springframeworkgroupId>
			<artifactId>spring-testartifactId>
			<version>${spring.version}version>
			<scope>testscope>
		dependency>
		
		<dependency>
			<groupId>org.apache.logging.log4jgroupId>
			<artifactId>log4j-apiartifactId>
			<version>${log4j2.version}version>
		dependency>
		<dependency>
			<groupId>org.apache.logging.log4jgroupId>
			<artifactId>log4j-coreartifactId>
			<version>${log4j2.version}version>
		dependency>
		
	dependencies>

	<build>
		
		<plugins>
			<plugin>
				<groupId>org.apache.maven.pluginsgroupId>
				<artifactId>maven-compiler-pluginartifactId>
				<version>3.1version>
				<configuration>
					<source>1.7source>
					<target>1.7target>
				configuration>
			plugin>
		plugins>
	build>
project>

步骤二 继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法获取特定数据源

代码语言:javascript
复制
package com.artisan.dynamicDB;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 
 * 
 * @ClassName: DynamicDataSource
 * 
 * @Description: 
 *               AbstractRoutingDataSource中的抽象方法determineCurrentLookupKey是实现数据源的route的核心
 *               .需要重写该方法
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年7月24日 下午8:28:46
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

	@Override
	protected Object determineCurrentLookupKey() {
		return DynamicDataSourceHolder.getDataSource();
	}
}

步骤三 创建DynamicDataSourceHolder用于持有当前线程中使用的数据源标识

代码语言:javascript
复制
package com.artisan.dynamicDB;

/**
 * 
 * 
 * @ClassName: DynamicDataSourceHolder
 * 
 * @Description:创建DynamicDataSourceHolder用于持有当前线程中使用的数据源标识
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年7月24日 下午8:23:50
 */
public class DynamicDataSourceHolder {

	/**
	 * 数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
	 */
	private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<String>();

	/**
	 * 
	 * 
	 * @Title: setDataSource
	 * 
	 * @Description: 设置数据源
	 * 
	 * @param dataSource
	 * 
	 * @return: void
	 */
	public static void setDataSource(String dataSource) {
		dataSourceHolder.set(dataSource);
	}

	/**
	 * 
	 * 
	 * @Title: getDataSource
	 * 
	 * @Description: 获取数据源
	 * 
	 * @return
	 * 
	 * @return: String
	 */
	public static String getDataSource() {
		return dataSourceHolder.get();
	}

	/**
	 * 
	 * 
	 * @Title: clearDataSource
	 * 
	 * @Description: 清除数据源
	 * 
	 * 
	 * @return: void
	 */
	public static void clearDataSource() {
		dataSourceHolder.remove();
	}
}

步骤四 配置多个数据源和DynamicDataSource的bean

代码语言:javascript
复制
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" 
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx 
       http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/util 
       http://www.springframework.org/schema/util/spring-util.xsd">
    
    
    <context:component-scan base-package="com.artisan"/>
    
    
    <context:property-placeholder location="classpath:jdbc.properties" />
    
      
    
    
	<bean id="dataSourcePR" class="org.apache.commons.dbcp.BasicDataSource" 
		destroy-method="close"
		p:driverClassName="${jdbc.driverClassNamePR}"
		p:url="${jdbc.urlPR}"
		p:username="${jdbc.usernamePR}"
		p:password="${jdbc.passwordPR}" />
		
 	
	<bean id="dataSourceDR" class="org.apache.commons.dbcp.BasicDataSource" 
		destroy-method="close"
		p:driverClassName="${jdbc.driverClassNameDR}"
		p:url="${jdbc.urlDR}"
		p:username="${jdbc.usernameDR}"
		p:password="${jdbc.passwordDR}" /> 
		
	
	<bean id="dataSourceCC" class="org.apache.commons.dbcp.BasicDataSource" 
		destroy-method="close"
		p:driverClassName="${jdbc.driverClassNameCC}"
		p:url="${jdbc.urlCC}"
		p:username="${jdbc.usernameCC}"
		p:password="${jdbc.passwordCC}" />


	<bean id="dynamicDataSource" class="com.artisan.dynamicDB.DynamicDataSource">
		<property name="targetDataSources" ref="dynamicDatasourceMap" />
		
		<property name="defaultTargetDataSource" ref="dataSourcePR" />
	bean>
	
	
	<util:map id="dynamicDatasourceMap" key-type="java.lang.String">
		<entry key="dataSourcePR" value-ref="dataSourcePR" />
		<entry key="dataSourceDR" value-ref="dataSourceDR" />
		<entry key="dataSourceCC" value-ref="dataSourceCC" />
	util:map>
		
	
	
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
		p:dataSource-ref="dynamicDataSource" />
		
		
	 <bean id="dataSourceAspect" class="com.artisan.dynamicDB.DataSourceAspect" />
     <aop:config>
         <aop:aspect ref="dataSourceAspect">
             
             <aop:pointcut id="dataSourcePointcut" expression="execution(* com.artisan..*(..))"/>
             <aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
         aop:aspect>
     aop:config>
		
	
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
		p:dataSource-ref="dynamicDataSource" />
		
	
	<aop:config proxy-target-class="true">
		<aop:pointcut id="serviceMethod"
			expression="(execution(* com.artisan..*(..))) and (@annotation(org.springframework.transaction.annotation.Transactional))" />
		<aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice" />
	aop:config>
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<tx:method name="*" />
		tx:attributes>
	tx:advice>
beans>

配置到这里,我们就可以使用多个数据源了,只需要在操作数据库之前只要DynamicDataSourceHolder.setDataSource(“dataSourcePR”)即可切换到数据源dataSourcePR并对数据库dataSourcePR进行操作了。


问题:每次使用都需要调用DynamicDataSourceHolder#setDataSource,十分繁琐,并且难以维护。

我们可以通过Spring的AOP和注解, 直接通过注解的方式指定需要访问的数据源。 继续改进下吧


步骤五 定义名为@DataSource的注解

代码语言:javascript
复制
package com.artisan.dynamicDB;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 
 * 
 * @ClassName: DataSource 
 * 
 * 
 * @Description: 注解@DataSource既可以加在方法上,也可以加在接口或者接口的实现类上,优先级别:方法>实现类>接口。
 *               如果接口、接口实现类以及方法上分别加了@DataSource注解来指定数据源,则优先以方法上指定的为准。
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年7月24日 下午9:59:29
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
	// 和配置文件中 dynamicDatasourceMap中的key保持一致
	public static String PR_RB = "dataSourcePR";

	public static String DR_RB = "dataSourceDR";

	public static String PR_CC = "dataSourceCC";

	/**
	 * 
	 * 
	 * @Title: name
	 * 
	 * @Description: 如果仅标注@DataSource 默认为PR_RB数据库实例
	 * 
	 * @return
	 * 
	 * @return: String
	 */
	String name() default DataSource.PR_RB;

}

步骤六 定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中

代码语言:javascript
复制
package com.artisan.dynamicDB;

import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

/**
 * 
 * 
 * @ClassName: DataSourceAspect
 * 
 * @Description: 
 *               定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DBContextHolder的线程变量中
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年7月25日 上午10:51:41
 */
public class DataSourceAspect {

	/**
	 * 
	 * 
	 * @Title: intercept
	 * 
	 * @Description: 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
	 * 
	 * @param point
	 * @throws Exception
	 * 
	 * @return: void
	 */
	public void intercept(JoinPoint point) throws Exception {
		Class<?> target = point.getTarget().getClass();
		MethodSignature signature = (MethodSignature) point.getSignature();
		// 默认使用目标类型的注解,如果没有则使用其实现接口的注解
		for (Class<?> clazz : target.getInterfaces()) {
			resolveDataSource(clazz, signature.getMethod());
		}
		resolveDataSource(target, signature.getMethod());
	}

	/**
	 * 
	 * 
	 * @Title: resolveDataSource
	 * 
	 * @Description: 提取目标对象方法注解和类型注解中的数据源标识
	 * 
	 * @param clazz
	 * @param method
	 * 
	 * @return: void
	 */
	private void resolveDataSource(Class<?> clazz, Method method) {
		try {
			Class<?>[] types = method.getParameterTypes();
			// 默认使用类型注解
			if (clazz.isAnnotationPresent(DataSource.class)) {
				DataSource source = clazz.getAnnotation(DataSource.class);
				DynamicDataSourceHolder.setDataSource(source.name());
			}
			// 方法注解可以覆盖类型注解
			Method m = clazz.getMethod(method.getName(), types);
			if (m != null && m.isAnnotationPresent(DataSource.class)) {
				DataSource source = m.getAnnotation(DataSource.class);
				DynamicDataSourceHolder.setDataSource(source.name());
			}
		} catch (Exception e) {
			System.out.println(clazz + ":" + e.getMessage());
		}
	}
}

步骤七 在spring配置文件中配置拦截规则

代码语言:javascript
复制
	 <bean id="dataSourceAspect" class="com.artisan.dynamicDB.DataSourceAspect" />
     <aop:config>
         <aop:aspect ref="dataSourceAspect">
             
             <aop:pointcut id="dataSourcePointcut" expression="execution(* com.artisan..*(..))"/>
             <aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
         aop:aspect>
     aop:config>

步骤八 使用注解切换多数据源

ExtractDataService.java

代码语言:javascript
复制
package com.artisan.extractService;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.stereotype.Service;

import com.artisan.dynamicDB.DataSource;

/**
 * 
 * 
 * @ClassName: ExtractDataService
 * 
 * @Description: 业务类,这里暂时作为测试多数据源切换用
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年7月24日 下午9:07:38
 */

@Service
public class ExtractDataService {

	private static final Logger logger = LogManager
			.getLogger(ExtractDataService.class.getName());

	private JdbcTemplate jdbcTemplate;

	@Autowired
	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	/**
	 * 
	 * 
	 * @Title: selectDataFromPR
	 * 
	 * @Description:
	 * 
	 * 
	 * @return: void
	 */
	@DataSource(name = DataSource.PR_RB)
	public void selectDataFromPR_RB() {
		String sql = "select  subs_id from owe_event_charge  where event_inst_id = 10229001 ";

		jdbcTemplate.query(sql, new RowCallbackHandler() {

			@Override
			public void processRow(ResultSet rs) throws SQLException {
				logger.info(rs.getInt("subs_id"));
			}
		});
	}

	@DataSource(name = DataSource.DR_RB)
	public void selectDataFromDR_RB() {
		// 改为通过注解指定DB
		// DynamicDataSourceHolder.setDataSource(DBContextHolder.DATA_SOURCE_DR);
		String sql = " select  a.task_comments  from nm_task_type a  where a.task_name = 'ALARM_LOG_LEVEL' ";
		jdbcTemplate.query(sql, new RowCallbackHandler() {

			@Override
			public void processRow(ResultSet rs) throws SQLException {
				logger.info(rs.getString("task_comments"));
			}
		});
	}

	@DataSource(name = DataSource.PR_CC)
	public void selectDataFromPR_CC() {
		// DBContextHolder.setDataSource(DBContextHolder.DATA_SOURCE_CC);
		String sql = "select  acc_nbr  from acc_nbr  where  acc_nbr_id = 82233858 ";
		jdbcTemplate.query(sql, new RowCallbackHandler() {

			@Override
			public void processRow(ResultSet rs) throws SQLException {
				logger.info(rs.getString("acc_nbr"));
			}
		});

	}
}

步骤九 测试

代码语言:javascript
复制
package com.artisan;

import java.io.IOException;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import com.artisan.extractService.ExtractDataService;

/**
 * 
 * 
 * @ClassName: App
 * 
 * @Description: 入口类
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年7月24日 下午8:50:25
 */
public class App {
	public static void main(String[] args) {
		try {
			// 加载日志框架 log4j2
			LoggerContext context = (LoggerContext) LogManager
					.getContext(false);
			ResourceLoader loader = new PathMatchingResourcePatternResolver();
			Resource resource = loader.getResource("classpath:log4j2.xml");

			context.setConfigLocation(resource.getFile().toURI());

			// 加载spring配置信息
			ApplicationContext ctx = new ClassPathXmlApplicationContext(
					"classpath:spring-context.xml");
			// 从容器中获取Bean
			ExtractDataService service = ctx.getBean("extractDataService",
					ExtractDataService.class);
			// 从PR的RB实例中获取数据
			service.selectDataFromPR_RB();
			// 从DR的RB实例中获取数据
			service.selectDataFromDR_RB();
			// 从PR的CC实例中获取数据
			service.selectDataFromPR_CC();

		} catch (IOException e) {
			e.printStackTrace();
		}

	}
}

其他代码

log4j2.xml

代码语言:javascript
复制
<configuration status="info" monitorInterval="180">

	
    <Properties>
    	<Property name="backupFilePatch">D:/workspace/workspace-sts/backupOracle/log/Property>
        <Property name="fileName">backupOracle.logProperty>
      Properties>
    
    <appenders>
        
        <Console name="Console" target="SYSTEM_OUT">
             
            <ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY" />
            
            <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n" />
        Console>
  	
        
        <RollingFile name="RollingFile" fileName="${backupFilePatch}${fileName}"
            filePattern="${backupFilePatch}$${date:yyyy-MM}/app-%d{yyyyMMddHHmmssSSS}.log.gz">
            <PatternLayout
                pattern="%d{yyyy.MM.dd 'at' HH:mm:ss.SSS z} %-5level %class{36} %L %M - %msg%xEx%n" />
            
            <SizeBasedTriggeringPolicy size="20MB" />
            
            <DefaultRolloverStrategy max="20"/>
        RollingFile>
    appenders>
    
    
    <loggers>
    	 
 		 <logger name="org.springframework" level="INFO">logger>
   		 <logger name="org.mybatis" level="INFO">logger>
    	 <root level="trace">  
            <appender-ref ref="RollingFile"/>  
            <appender-ref ref="Console"/>  
         root>  
    loggers>
configuration>

jdbc.properties

代码语言:javascript
复制
##########################
##
##
## dbcp datasource pool ,basic configuration first.
## the other parameters keep default for now , you can change them if you want 
##
##
##########################

#Database in Lapaz
jdbc.driverClassNamePR=oracle.jdbc.driver.OracleDriver
jdbc.urlPR=jdbc:oracle:thin:@172.25.243.4:1521:xx
jdbc.usernamePR=xxx
jdbc.passwordPR=xxxxxxxx



#Database in Scluz
jdbc.driverClassNameDR=oracle.jdbc.driver.OracleDriver
jdbc.urlDR=jdbc:oracle:thin:@172.25.246.1:1521:xx
jdbc.usernameDR=xxx
jdbc.passwordDR=xxxxxxx

#Database in Lapaz
jdbc.driverClassNameCC=oracle.jdbc.driver.OracleDriver
jdbc.urlCC=jdbc:oracle:thin:@172.25.243.3:1521:xx
jdbc.usernameCC=xxx
jdbc.passwordCC=xxxxxx

运行结果:

这里写图片描述
这里写图片描述

代码

https://github.com/yangshangwei/DynamicDataSource

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017/07/28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 实现思路
  • 示例
    • 步骤一 新建Maven工程
      • 步骤二 继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法获取特定数据源
        • 步骤三 创建DynamicDataSourceHolder用于持有当前线程中使用的数据源标识
          • 步骤四 配置多个数据源和DynamicDataSource的bean
            • 步骤五 定义名为@DataSource的注解
              • 步骤六 定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中
                • 步骤七 在spring配置文件中配置拦截规则
                  • 步骤八 使用注解切换多数据源
                    • 步骤九 测试
                    • 其他代码
                      • log4j2.xml
                        • jdbc.properties
                        • 运行结果:
                        • 代码
                        相关产品与服务
                        容器服务
                        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档