设计模式学习笔记 - 开源实战五(中):如何利用职责链与代理模式实现Mybatis Plugin

概述

上篇文章对 Mybatis 框架做了简单的背景介绍,并通过对比各种 ORM 框架,学习了代码的易用性、性能、灵活性之间的关系。一般来讲,框架提供的高级功能越多,那性能损耗就越大;框架使用起来越简单,那灵活性就越低。

接下来的两篇文章,再学习下 Mybatis 用到的一些经典的设计模式。本章主要讲解 Mybatis Plugin

尽管名字叫 Plugin(插件),但它实际上和 Servlet Filter(过滤器)、Spring Interceptor(拦截器)类似,设计的初衷都是为了框架的扩展性,用到的主要设计模式是职责链模式。

不过相对于 Servlet Filter、Spring Interceptor,Mybatis Plugin 中职责链模式的代码实现稍微有点复杂。它是借助动态代理模式来实现的职责链。本章就带你看下,如何利用这两个模式实现 Mybatis Plugin。


Mybatis Plugin 功能介绍

实际上,Mybatis Plugin 跟 Servlet Filter、Spring Interceptor 的功能是类似的,都是在不修改流程代码的情况下,拦截某些方法调用,在拦截的方法调用后,执行一些额外的代码逻辑。它们的唯一区别在于拦截的位置是不同的。Servlet Filter 主要拦截 Servlet 请求,Spring Interceptor 主要拦截 Spring 管理的 Bean 方法(比如 Controller 类的方法等),而 Mybatis Plugin 主要拦截的是 Mybatis 在执行 SQL 的过程中涉及的一些方法。

Mybatis Plugin 使用起来比较简单,下面通过一个例子来快速看下。

假设我们需要统计应用中每个 SQL 的执行耗时,如果使用 Mybatis Plugin 来实现的话,只需要定义一个 SqlCostTimeInterceptor 类,让他实现 Mybatis 的 Interceptor 接口,并且在 Mybatis 的全局配置文件中,简单声明一下就可以了。具体代码如下所示:

@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class}),})
public class SqlCostTimeInterceptor implements Interceptor {private static Logger logger = LoggerFactory.getLogger(SqlCostTimeInterceptor.class);@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object target = invocation.getTarget();long startTime = System.currentTimeMillis();StatementHandler statementHandler = (StatementHandler) target;try {return invocation.proceed();} finally {long costTime = System.currentTimeMillis() - startTime;BoundSql boundSql = statementHandler.getBoundSql();String sql = boundSql.getSql();logger.info("执行 SQL:[ {} ],执行耗时[ {} ms]", sql, costTime);}}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {System.out.println("插件配置信息: " + properties);}
}<!-- Mybatis全局配置文件:mybatis-config.xml -->
<plugins><plugin interceptor="com.example.SqlCostTimeInterceptor"><property name="someProperty" value="100"/></plugin>
</plugins>

待会会详细的介绍 Mybatis Plugin 底层的实现原理,所以,这里暂时不对上面的代码做详细地解释。现在,只关注下 @Intercepts 这个注解。

不管是拦截器、过滤器还是插件,都需要明确地标明拦截的目标方法。@Intercepts 注解实际上就是这个作用。其中,@Intercepts 注解又可以嵌套 @Signature 注解。一个 @Signature 注解标明要拦截的方法。如果要拦截多个方法,我们可以像例子中那样,编写多条 @Signature 注解。

@Signature 注解包含三个元素:typemethodargs。其中,type 指明要拦截的类、method 指明方法名、args 指明方法的参数列表。通过这三个元素,就能完全确定要拦截的方法。

默认情况下,Mybatis Plugin 允许拦截的方法有下面这样几个:

方法
Executorupdate、query、flushStatements、commit、rollback、getTransaction、clos、isClosed
ParameterHandlergetParameterObject、setParameters
ResultSetHandlerhandleResultSets、handleOutputParameters
StatementHandlerprepare、parameterize、batch、update、query

为什么默认允许拦截的是这样几个类的方法呢?

Mybatis 底层是通过 Executor 类来执行 SQL 的。Executor 会创建 StatementHandlerResultSetHandlerParameterHandler 三个对象,并且,首先使用 ParameterHandler 设置 SQL 中的占位符参数,然后使用 StatementHandler 执行 SQL 语句,最后使用 ResultSetHandler 封装执行结果。所以,我们需要拦截 ExecutorStatementHandlerResultSetHandlerParameterHandler 这几个类的方法,基本上就能满足我们对整个 SQL 执行流程的拦截了。

实际上,除了统计 SQL 的执行耗时,利用 Mybatis Plugin,还可以做很多事情,比如分库分表、自动分页、数据脱敏、加密解密等等。如果感兴趣,你可以自己实现下。

Mybatis Plugin 的设计与实现

刚刚简单介绍了 Mybatis Plugin 是如何使用的。现在在剖析下源码,看看如此简洁的使用方式,底层是如何实现的,隐藏了哪些复杂的设计。

相对于 Servlet Filter、Spring Interceptor 中职责链的代码实现,Mybatis Plugin 的代码实现还是蛮有技巧的,因为它是借助动态代理来实现职责链的。

在《责任链模式(上)》和《责任链模式(下)》中,我们讲到,职责链模式的实现一般包括处理器(Handler)和处理器链(HandlerChain)两部分。这两个部分对应到 Servlet Filter 源码就是 FilterFilterChain,对应到 Spring Interceptor 源码就是 HandlerInterceptorHandlerExecutionChain对应到 Mybatis Plugin 的源码就是 InterceptorInterceptorChain。此外,Mybatis Plugin 还包含另外一个非常重要的类:PluginPlugin用来生成被拦截对象的动态代理

集成了 Mybatis 的应用在启动时,Mybatis 框架会读取全局配置文件,解析出 Interceptor,并将它们注入到 Configuration 类的 InterceptorChain 对象中。这部分逻辑对应到源码如下所示。

public class XMLConfigBuilder extends BaseBuilder {// ...// 解析配置private void parseConfiguration(XNode root) {try {// ...this.pluginElement(root.evalNode("plugins"));// ...} catch (Exception var3) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);}}// ...// 解析插件private void pluginElement(XNode parent) throws Exception {if (parent != null) {Iterator var2 = parent.getChildren().iterator();while(var2.hasNext()) {XNode child = (XNode)var2.next();String interceptor = child.getStringAttribute("interceptor");Properties properties = child.getChildrenAsProperties();// 创建Interceptor类对象Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).getDeclaredConstructor().newInstance();// 调用Interceptor上的setProperties()方法设置propertiesinterceptorInstance.setProperties(properties);// 下面这行代码会调用InterceptorChain.addInterceptor()方法this.configuration.addInterceptor(interceptorInstance);}}}// ...
}public class Configuration {// ...public void addInterceptor(Interceptor interceptor) {this.interceptorChain.addInterceptor(interceptor);}// ...
}

再来看下 InterceptorInterceptorChain 这两个类的代码,如下所示。InterceptorsetProperties() 方法就是单纯的一个 setter 方法,主要是为了方便通过配置文件配置 Interceptor 的一些属性值。Interceptor 类中的 intercept()plugin() 函数,以及 InterceptorChainpluginAll() 函数,是最核心的三个函数,待会再详细解释。

public class Invocation {private final Object target;private final Method method;private final Object[] args;// 省略构造函数和getter方法...public Object proceed() throws InvocationTargetException, IllegalAccessException {return this.method.invoke(this.target, this.args);}
}public interface Interceptor {Object intercept(Invocation var1) throws Throwable;Object plugin(Object target);void setProperties(Properties properties);
}public class InterceptorChain {private final List<Interceptor> interceptors = new ArrayList();public InterceptorChain() {}public Object pluginAll(Object target) {Interceptor interceptor;for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {interceptor = (Interceptor)var2.next();}return target;}public void addInterceptor(Interceptor interceptor) {this.interceptors.add(interceptor);}public List<Interceptor> getInterceptors() {return Collections.unmodifiableList(this.interceptors);}
}

解析完配置后,所有的 Interceptor 都加载到了 InterceptorChain 中。接下来,再看下,这些拦截器是在什么时候被触发执行的?又是如何被触发执行的?

前面提到,在执行 SQL 的过程中,Mybatis 会创建 ExecutorStatementHandlerResultSetHandlerParameterHandler 这个几个类的对象,对应的创建代码在 Configuration 类中,如下所示:

public class Configuration {// ...public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? this.defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Object executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}if (this.cacheEnabled) {executor = new CachingExecutor((Executor)executor);}Executor executor = (Executor)this.interceptorChain.pluginAll(executor);return executor;}// ...public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);parameterHandler = (ParameterHandler)this.interceptorChain.pluginAll(parameterHandler);return parameterHandler;}public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);ResultSetHandler resultSetHandler = (ResultSetHandler)this.interceptorChain.pluginAll(resultSetHandler);return resultSetHandler;}public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);return statementHandler;}// ...
}

从上面的代码可以看出,这几个类对象的创建构成都调用了 InterceptorChainpluginAll() 方法。这个方法的代码前面已经给出了。它的代码实现很简单,嵌套调用 InterceptorChain 上每个 Interceptorplugin() 方法。plugin() 是一个接口方法,需要由用户给出具体的实现代码。在之前的例子中, SQLTimeCostInterceptorplugin() 方法通过直接调用 Pluginwrap() 方法来实现。wrap() 方法的代码实现如下所示:

// 借助Java InvocationHandler实现动态代理模式
public class Plugin implements InvocationHandler {private final Object target;private final Interceptor interceptor;private final Map<Class<?>, Set<Method>> signatureMap;private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {this.target = target;this.interceptor = interceptor;this.signatureMap = signatureMap;}// Wrap()静态方法,用来生成 target 对象的动态代理// 动态代理对象 = target对象 + interceptor对象public static Object wrap(Object target, Interceptor interceptor) {Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);Class<?> type = target.getClass();Class<?>[] interfaces = getAllInterfaces(type, signatureMap);if (interfaces.length > 0) {return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}return target;}// 调用target上的f()方法,会触发执行下面这个方法// 这个方法阿包含:执行interceptor的intecept()方法 + 执行targetshangf()方法。@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {Set<Method> methods = signatureMap.get(method.getDeclaringClass());if (methods != null && methods.contains(method)) {return interceptor.intercept(new Invocation(target, method, args));}return method.invoke(target, args);} catch (Exception e) {throw ExceptionUtil.unwrapThrowable(e);}}private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);// issue #251if (interceptsAnnotation == null) {throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());}Signature[] sigs = interceptsAnnotation.value();Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();for (Signature sig : sigs) {Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());try {Method method = sig.type().getMethod(sig.method(), sig.args());methods.add(method);} catch (NoSuchMethodException e) {throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);}}return signatureMap;}private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {Set<Class<?>> interfaces = new HashSet<>();while (type != null) {for (Class<?> c : type.getInterfaces()) {if (signatureMap.containsKey(c)) {interfaces.add(c);}}type = type.getSuperclass();}return interfaces.toArray(new Class<?>[0]);}
}

实际上,Plugin 是借助 Java InvocationHandler 实现的动态代理。用来代理给 target 对象添加 interceptor 功能。其中,要代理的对象就是 ExecutorStatementHandlerResultSetHandlerParameterHandler 这个四个类的对象。wrap() 静态方法是一个工具函数,用来生成 target 对象的动态代理对象。

当然,只有 interceptortarget 互相匹配时,wrap() 方法才会返回代理对象,否则就返回 target 对象本身。怎么才能算匹配呢? 那就是 interceptor 通过 @Signature 朱姐拦截到的类包含 target 对象,具体可以看 wrap() 函数的代码实现。

Mybatis 中的职责链模式的实现方式比较特殊。它对同一个目标对象嵌套多次代理(也就是 InterceptorChainpluginAll() 函数要执行的任务)。每个代理对象(Plugin 对象)代理一个拦截器(Interceptor 对象)功能。为了方便查看,我把 pluginAll() 函数的代码又拷贝到下面。

public class InterceptorChain {private final List<Interceptor> interceptors = new ArrayList<>();public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);}return target;}// 上面这行代码等于下面代码,target(代理对象) = target(目标对象) + interceptpor(拦截器功能)// target=Plugin.wrap(target, interceptor);// ...
}// mybatis像下面这样创建target(Executor、StatementHandler、ResultSetHandler、ParameterHandler),
// 相当于多次嵌套代理
Object target = interceptor.pluginAll(target);

当执行 ExecutorStatementHandlerResultSetHandlerParameterHandler 这四个类方法时,Mybatis 会嵌套执行每层代理对象(Plugin 对象)上的 invoke() 方法,而 invoke() 方法会先执行对象中的 interceptor.intercept() 函数,然后再执行被代理对象上的方法。就这样,一层一层地把代理对象上的 intercept() 函数执行完后,Mybatis 才最终执行那 4 个原始类对象上的方法。

总结

本章剖析了如何利用职责链模式和动态代理模式来实现 Mybatis Plugin。至此,我们学习了三种职责链常用的应用场景:过滤器(Servlet Filter)、拦截器(Spring Interceptor)、插件(Mybatis Plugin)。

职责链模式一般包含处理器和处理器链两部分。这两个部分对应到 Servlet Filter 源码就是 FilterFilterChain,对应到 Spring Interceptor 源码就是 HandlerInterceptorHandlerExecutionChain,对应到 Mybatis Plugin 的源码就是 InterceptorInterceptorChain。此外,Mybatis Plugin 还包含另外一个非常重要的类:PluginPlugin用来生成被拦截对象的动态代理。

这三种应用场景中,职责链模式的实现思路都不打一样。其中,Servlet Filter 采用递归来实现拦截方法前后添加逻辑。Spring Interceptor 的实现比较简单,把拦截方法前后要添加的逻辑放到两个方法中去实现。Mybatis Plugin 采用嵌套动态代理的方法来实现,实现思路很有技巧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/4488.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

59岁郑浩南罕见与索爆女儿合体,曾自曝婚变暗示妻子出轨人财两空

59岁的郑浩南&#xff0c;拍过不少脍炙人口的电影&#xff0c;尤其是在《古惑仔》中饰演奸角司徒浩南&#xff0c;近作有《黑社会》的「加钱哥」&#xff0c;虽然将近「登六」&#xff0c;却保养得宣&#xff0c;操出一身健硕肌肉。 跟前妻鲍爱玲离婚后&#xff0c;郑浩南独自在…

即插即用Mamba模块全新突破!无缝集成,无痛涨点

Mamba入局图像复原了&#xff01;基于Mamba的图像复原基准模型MambaIR性能超越SwinIR&#xff0c;达成新SOTA&#xff01; MambaIR是一种引入通道注意力和局部增强的即插即用Mamba模块。这类高效、创新的模块在写论文时可以帮助我们简化模型的构建过程&#xff0c;通过将这些模…

Android 11 bindService 流程分析

我们可以使用bindService来跨进程通信&#xff0c;其使用方法如下 Intent intent new Intent("xxx"); intent.setPackage("xxx"); boolean result bindService(intent,new ServiceConn(),BIND_AUTO_CREATE);private class ServiceConn implements Servi…

使用lua时一个愚蠢的错误

之前看luaL_openlibs()&#xff0c;感觉直接调打开库的函数好像也没差别&#xff0c;所以将 LUALIB_API void luaL_openlibs (lua_State *L) {const luaL_Reg *lib lualibs;for (; lib->func; lib) {lua_pushcfunction(L, lib->func);lua_pushstring(L, lib->name);…

学习100个Unity Shader (15) ---透明+双面渲染

文章目录 效果shader理解参考 效果 shader Shader "Example/AlphaBlendBothSided" {Properties{_Color ("Main Tint", Color) (1, 1, 1, 1)_MainTex ("Texture", 2D) "white" {}_AlphaScale ("Alpha Scale", Range(0, 1)…

leetcode多个测试用例之间相互影响导致提交失败

背景 在做一道easy题&#xff0c;二叉树的中序遍历&#xff0c;我提交的代码如下 from typing import (Optional,List )# Definition for a binary tree node. class TreeNode:def __init__(self, val0, leftNone, rightNone):self.val valself.left leftself.right right…

无脑入单向无头链表的实现| ArrayList和LinkedList的区别

1. ArrayList的缺陷 上节课已经熟悉了ArrayList的使用&#xff0c;并且进行了简单模拟实现。通过源码知道&#xff0c;ArrayList底层使用数组来存储元素。 由于其底层是一段连续空间&#xff0c;当 在 ArrayList 任意位置插入或者删除元素时&#xff0c;就需要将后序元素整体往…

掼蛋入门口诀

1、有王打单张&#xff0c;无王打一夯 2、想使坏&#xff0c;三不带 3、情况不明&#xff0c;对子先行 4、两个小单张&#xff0c;不打不健康 5、顺子打到头&#xff0c;对手没想头 6、双贡出单张&#xff0c;头游响当当 7、逢五出两张&#xff0c;逢六出三张 8、炸五不炸四&am…

pytest测试基础

assert 验证关键字 需要pahton版本大于3.6&#xff0c;因为有个工具pip3;因为做了映射&#xff0c;所以下面命令pip3即pip pip install -U pytest -U参数可选&#xff0c;是如果已安装可更新。 如果上述demo变化 通过验证代码&#xff0c;测试环境没问题。…

详细谈电脑ip、域名、内网、外网、localhost、127.0.0.1、网关等通讯基础知识(易懂)

1. ip地址与域名的定义以及其关系 ip地址的定义&#xff1a; IP地址&#xff08;Internet Protocol Address&#xff09;是指互联网协议地址&#xff0c;又译为网际协议地址。 IP地址是IP协议提供的一种统一的地址格式&#xff0c;它为互联网上的每一个网络和每一台主机分配一…

ChatGPT记忆功能终于上线了, OpenAI 官方:用得越久越聪明!

原文 ChatGPT记忆功能终于上线了, OpenAI 官方&#xff1a;用得越久越聪明&#xff01; Aitrainee | 公众号&#xff1a;AI进修生 &#x1f31f; 记得今年2月份OpenAI发布过ChatGPT上线记忆功能的消息&#xff0c;我记得当时还弹出过这个窗口给我&#xff0c;但是仅仅体验了几…

Git--分布式版本控制系统

目录 一、理解分布式版本控制系统二、远程仓库三、克隆远程仓库四、向远程仓库推送五、拉取远程仓库六、配置Git七、给命令配置别名八、创建标签九、操作标签 一、理解分布式版本控制系统 我们⽬前所说的所有内容&#xff08;⼯作区&#xff0c;暂存区&#xff0c;版本库等等&a…

在智慧城市的建设中智能电表发挥什么作用

在智慧城市的建设中&#xff0c;智能电表扮演着至关重要的角色。智慧城市是一个利用信息技术手段提升城市运行效率和质量的新型城市模式&#xff0c;旨在通过信息和通信技术的应用&#xff0c;提高城市管理、公共服务、环境保护等方面的质量和效率&#xff0c;促进城市的可持续…

基于MSP430F249的电子钟仿真(源码+仿真)

目录 1、前言 2、仿真 3、程序 资料下载地址&#xff1a;基于MSP430F249的电子钟仿真(源码仿真&#xff09; 1、前言 基于MSP430F249的电子钟仿真&#xff0c;数码管显示时分秒&#xff0c;并可以通过按键调节时间。 2、仿真 3、程序 #include <MSP430x24x.h> #def…

搭建Springboot Admin 服务端和客户端

Spring Boot Admin 是一个网络应用程序&#xff0c;用于管理和监控 Spring Boot 应用程序。每个应用程序都被视为客户端&#xff0c;并向管理服务器注册。在幕后&#xff0c;Spring Boot Actuator 端点发挥着神奇的作用。 一、搭建Springboot Admin服务端 新建一个Maven模块&a…

短视频账号矩阵系统===4年技术源头打磨

短视频矩阵系统技术源头打磨需要从多个方面入手&#xff0c;以下是一些建议&#xff1a; 1. 基础技术研发&#xff1a;不断投入资金和人力进行基础技术研发&#xff0c;包括但不限于视频处理、人工智能、大数据等技术&#xff0c;以提高短视频矩阵系统的性能和稳定性。 2. 优化…

03-JAVA设计模式-策略模式

策略模式 什么是策略模式 策略模式&#xff08;Strategy Pattern&#xff09;是行为设计模式之一&#xff0c;它使你能在运行时改变对象的行为。在策略模式中&#xff0c;一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为模式。 在策略模式中&#xff0c;…

如何瓜分1.35亿美元的园艺市场?赛盈分销圈货活动即将开启!

庭院一直以来是欧美国家大多数家庭的重要使用场景&#xff0c;尤其在每年夏季都会进行庭院爆改。 人们通过修剪草坪、花草种植、庭院装饰、野营烧烤等形式增添户外庭院另一种更加休闲、新颖的生活方式。 Google Trends预测到2035年全球居家园艺市场达到1.35万亿美元&#xff0c…

500强企业如何打造高效的差旅供应链?

在构建差旅管理平台的过程中&#xff0c;很多企业差旅管理负责人常常面临一个关键问题&#xff1a;差旅供应链怎么搭建&#xff1f; 企业差旅供应链的建设是一个复杂的系统工程&#xff0c;涉及多个方面&#xff0c;包括但不限于供应商的选择与管理、技术平台的搭建、流程设计、…

Docker资源控制管理

目录 一.CPU 资源控制 1.定义 2.cgroups四大功能 &#xff08;1&#xff09;资源限制&#xff1a;可以对任务使用的资源总额进行限制 &#xff08;2&#xff09;优先级分配&#xff1a;通过分配的cpu时间片数量以及磁盘IO带宽大小&#xff0c;实际上相当于控制了任务运行优…