会员登录 - 用户注册 - 设为首页 - 加入收藏 - 网站地图 Dubbo原理剖析 之 @DubboReference.version设置为!

Dubbo原理剖析 之 @DubboReference.version设置为

时间:2025-11-05 15:57:33 来源:益强数据堂 作者:系统运维 阅读:909次
*

 

本文转载自微信公众号「不送花的理剖程序猿」,作者Howinfun。理剖转载本文请联系不送花的理剖程序猿公众号。  

1 背景

Dubbo在消费端提供了一个功能,理剖即将消费者的理剖版本号指定为*,那么不管服务端的理剖接口版本是啥,都可以调用成功。理剖

2 初步猜测

dubbo接口定位逻辑:接口(全路径)+服务分组(group字段)+版本号(version字段)。理剖

Zookeeper 是理剖用树状来保存数据的,在 Zookeeper 中,理剖可以利用Dubbo接口(全路径)作为父节点,理剖再根据group和version信息写入子节点。理剖

而 Nacos,理剖在 Nacos 的理剖控制台中,我们看到可以根据服务名或服务分组来模糊查询服务列表,理剖那么在消费者订阅的时候,就根据这两个模糊查询就可以了,查出来的健康提供者都是符合的。

下面就深入一下源码,看看实际的逻辑是不是b2b供应网类似我们的猜想。

3 源码剖析

3.1 Zookeeper 作为注册中心

3.1.1 准备

弄一个服务提供者、一个服务消费者。服务提供者对外提供一个dubbo接口,版本有1.0.0和2.0.0;服务消费者引入服务提供者提供的dubbo接口,version设置为*。

启动服务提供者、接着启动消费者,观察后台日志打印:

我们可以看到,当我们将@DubboReference的version设置为*的时候,他就根据注册url(带*)去找有哪些服务提供者,然后返回的urls会有多个,其中包含版本号为1.0.0和2.0.0的url。

2021-05-01 10:24:08.561 [main] [INFO ] [o.a.d.r.z.ZookeeperRegistry] [] [] -  [DUBBO] Subscribe: consumer://127.0.0.1/com.winfun.service.DubboServiceOne?application=dubbo-service&category=providers,configurators,routers&dubbo=2.0.2&init=false&interface=com.winfun.service.DubboServiceOne&methods=sayHello&pid=14021&qos.enable=false&reference.filter=default,dubboLogFilter,sentinel.dubbo.consumer.filter&release=2.7.7&revision=*&side=consumer&sticky=false&timestamp=1619835848549&version=*, dubbo version: 2.7.7, current host: 127.0.0.1 2021-05-01 10:24:08.572 [main] [INFO ] [o.a.d.r.z.ZookeeperRegistry] [] [] -  [DUBBO] Notify urls for subscribe url consumer://127.0.0.1/com.winfun.service.DubboServiceOne?application=dubbo-service&category=providers,configurators,routers&dubbo=2.0.2&init=false&interface=com.winfun.service.DubboServiceOne&methods=sayHello&pid=14021&qos.enable=false&reference.filter=default,dubboLogFilter,sentinel.dubbo.consumer.filter&release=2.7.7&revision=*&side=consumer&sticky=false&timestamp=1619835848549&version=*, urls: [dubbo://192.168.2.10:20880/com.winfun.service.DubboServiceOne?anyhost=true&application=dubbo-provider-one&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.winfun.service.DubboServiceOne&metadata-type=remote&methods=sayHello&pid=13996&release=2.7.8&revision=1.0.0&service.filter=default,dubboLogFilter&side=provider&timestamp=1619835820516&version=1.0.0, dubbo://192.168.2.10:20880/com.winfun.service.DubboServiceOne?anyhost=true&application=dubbo-provider-one&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.winfun.service.DubboServiceOne&metadata-type=remote&methods=sayHello&pid=13996&release=2.7.8&revision=2.0.0&service.filter=default,dubboLogFilter&side=provider&timestamp=1619835820187&version=2.0.0, empty://127.0.0.1/com.winfun.service.DubboServiceOne?application=dubbo-service&category=configurators&dubbo=2.0.2&init=false&interface=com.winfun.service.DubboServiceOne&methods=sayHello&pid=14021&qos.enable=false&reference.filter=default,dubboLogFilter,sentinel.dubbo.consumer.filter&release=2.7.7&revision=*&side=consumer&sticky=false&timestamp=1619835848549&version=*, empty://127.0.0.1/com.winfun.service.DubboServiceOne?application=dubbo-service&category=routers&dubbo=2.0.2&init=false&interface=com.winfun.service.DubboServiceOne&methods=sayHello&pid=14021&qos.enable=false&reference.filter=default,dubboLogFilter,sentinel.dubbo.consumer.filter&release=2.7.7&revision=*&side=consumer&sticky=false&timestamp=1619835848549&version=*], dubbo version: 2.7.7, current host: 127.0.0.1 

3.1.2 源码分析

上面我们看到,version=*可以成功订阅,并且服务提供者有两个,分别是version=1.0.0和version=2.0.0。

结果是看得出来了,但是我们还是需要看看Zookeeper是怎么的判断逻辑。

3.1.2.1 服务消费者订阅过程

我们都知道,正常发布Dubbo的b2b信息网消费者,需要配置ReferenceConfig,然后调用export方法;当然了,我们这里就不过于深入了,直接从日志的入口来开始:org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#doSubscribe

我们服务消费者的订阅url:

consumer://127.0.0.1/com.winfun.service.DubboServiceOne?application=dubbo-service&category=providers,configurators,routers&dubbo=2.0.2&init=false&interface=com.winfun.service.DubboServiceOne&methods=sayHello&pid=16215&qos.enable=false&reference.filter=default,dubboLogFilter,sentinel.dubbo.consumer.filter&release=2.7.7&revision=*&side=consumer&sticky=false&timestamp=1619842106726&version=* 

第一步:获取dubbo接口全路径

url.getServiceInterface() -> com.winfun.service.DubboServiceOne 

接着判断是否等于“*”,明显不是,跳到else分支

第二步:根据url获取path

获取根节点:

toCategoriesPath(url) -> /dubbo/com.winfun.service.DubboServiceOne/providers、/dubbo/com.winfun.service.DubboServiceOne/configurators、/dubbo/com.winfun.service.DubboServiceOne/consumers 

第三步:遍历第二步的path、创建父节点

重点在path=/dubbo/com.winfun.service.DubboServiceOne/providers,其他忽略即可

根据path创建节点(非持久化):zkClient.create(root, false);

给path添加子节点监听器:zkClient.addChildListener(path, zkListener) 并返回子节点列表

dubbo://192.168.2.10:20880/com.winfun.service.DubboServiceOne?anyhost=true&application=dubbo-provider-one&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.winfun.service.DubboServiceOne&metadata-type=remote&methods=sayHello&pid=13996&release=2.7.8&revision=1.0.0&service.filter=default,dubboLogFilter&side=provider×tamp=1619835820516&version=1.0.0 dubbo://192.168.2.10:20880/com.winfun.service.DubboServiceOne?anyhost=true&application=dubbo-provider-one&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.winfun.service.DubboServiceOne&metadata-type=remote&methods=sayHello&pid=13996&release=2.7.8&revision=2.0.0&service.filter=default,dubboLogFilter&side=provider×tamp=1619835820187&version=2.0.0

configurators 和 consumers 不存在子节点,所以子节点是根据规则生成的url,前缀为empty

第四步:对上面获取到的urls进行监听

调用org.apache.dubbo.registry.support.FailbackRegistry#notify方法。

最后会去到 org.apache.dubbo.registry.support.AbstractRegistry#notify(org.apache.dubbo.common.URL, org.apache.dubbo.registry.NotifyListener, java.util.List)

重点:在监听前,会先匹配根据path查询的所有子节点中,匹配符合当前消费者的子节点(根据group和version判断),利用org.apache.dubbo.common.utils.UrlUtils#isMatch判断。

判断中最重要的逻辑:

String ANY_VALUE = "*"; String consumerGroup = consumerUrl.getParameter(GROUP_KEY); String consumerVersion = consumerUrl.getParameter(VERSION_KEY); String consumerClassifier = consumerUrl.getParameter(CLASSIFIER_KEY, ANY_VALUE); String providerGroup = providerUrl.getParameter(GROUP_KEY); String providerVersion = providerUrl.getParameter(VERSION_KEY); String providerClassifier = providerUrl.getParameter(CLASSIFIER_KEY, ANY_VALUE); return (ANY_VALUE.equals(consumerGroup) || StringUtils.isEquals(consumerGroup, providerGroup) || StringUtils.isContains(consumerGroup, providerGroup))         && (ANY_VALUE.equals(consumerVersion) || StringUtils.isEquals(consumerVersion, providerVersion))         && (consumerClassifier == null || ANY_VALUE.equals(consumerClassifier) || StringUtils.isEquals(consumerClassifier, providerClassifier)); 

上面完全可以体现出:当版本号等于*号,dubbo接口根节点下的服务都会作为当前消费者的服务提供者。

好了,云服务器提供商到这里,我们可以知道Zookeeper是怎么为version=*的消费者订阅服务的,直接根据接口全路径名到Zookeeper里获取所有子节点,并都可以作为服务提供者。

其实这里会有一个扩展点:多个服务提供者,调用的时候是怎么负载的,其实在@DubboReference中的loadbance属性中看得出,默认的负载策略是随机。

/**  * Load balance strategy, legal values include: random, roundrobin, leastactive  * <p>  * see Constants#DEFAULT_LOADBALANCE  */ String loadbalance() default ""; org.apache.dubbo.common.constants.CommonConstants#DEFAULT_LOADBALANCE="random"; 

3.1.2.2 服务消费者执行过程

proxy执行入口

我们可以通过debug模式,进入到Dubbo方法执行的入口:org.apache.dubbo.rpc.proxy.InvokerInvocationHandler#invoke

第一步:初步判断

如果是 Object类 或者 toString、destory、hashCode等方法,直接执行

第二步:创建RpcInvocation

根据执行方法、参数等信息创建RpcInvocation

获取serviceKey:-> serviceKey = dubbo-api-path/group:version

RpcInvocation设置TargetServiceUniqueName

第三步:调用invoker的invoke方法

来到org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker#invoke

判断是否设置了 mock 或 force

如果是调用org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker#doMockInvoke方法 否则来到org.apache.dubbo.rpc.cluster.support.wrapper.AbstractCluster.InterceptorInvokerNode#invoke

第四步:AbstractClusterInvoker#invoke

调用AbstractClusterInvoker#list获取invoker列表,可以看到拿到的就是1.0.0版本和2.0.0版本的服务提供者

接着调用 initLoadBalance 方法来初始化负载均衡策略,从订阅url里面获取loadbalance的值,如果没有设置,返回默认值“random”

/**  * Init LoadBalance.  * <p>  * if invokers is not empty, init from the first invokes url and invocation  * if invokes is empty, init a default LoadBalance(RandomLoadBalance)  * </p>  *  * @param invokers   invokers  * @param invocation invocation  * @return LoadBalance instance. if not need init, return null.  */ protected LoadBalance initLoadBalance(List<Invoker<T>> invokers, Invocation invocation) {     if (CollectionUtils.isNotEmpty(invokers)) {         return ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()                 .getMethodParameter(RpcUtils.getMethodName(invocation), LOADBALANCE_KEY, DEFAULT_LOADBALANCE));     } else {         return ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(DEFAULT_LOADBALANCE);     } } 

第五步:根据集群策略执行方法

由于Dubbo默认的集群策略是 failover,所以会来到来到:org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker#doInvoker

首先会从注册url里面的retries字段获取重试次数(如果为空,默认重试次数为2),此次取的是默认值,所以最后最大调用次数为3.

循环retries+1次

来到:org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker#select 选择Invoker

在RandomLoadBalance#doSelect 中,首先会根据服务提供者的权重判断,如果权重没赋值,最后会利用ThreadLocalRandom.current().nextInt(invokers.size())随机选择一个invoker。

下一步:org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker#doSelect 下一步:org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance#select由于默认是random的负载均衡策略,所以最后来到:org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance#doSelect 执行invoke方法,返回结果 如果有错误,记录着,下次循环打印warn日志 如果超过retries+1次调用失败,往外抛出RpcException异常

到这里,我们已经非常清楚Zookeeper 是如何支持消费者将 version设置为*,并且方法调用时是如何选择服务提供者。

3.2 Nacos 作为注册中心

3.2.1 准备

弄一个服务提供者、一个服务消费者,这次不再是Zookeeper作为注册中心,而是Nacos作为注册中心。服务提供者对外提供一个dubbo接口,版本有1.0.0和2.0.0;服务消费者引入服务提供者提供的dubbo接口,version设置为*。

启动服务提供者、接着启动消费者,观察后台日志打印:

3.2.2 源码分析

3.2.2.1 服务消费者订阅过程

Nacos 源码分析也是直接从 NacosRegistry#doSubscribe 入口开始:

org.apache.dubbo.registry.nacos.NacosRegistry#doSubscribe(org.apache.dubbo.common.URL, org.apache.dubbo.registry.NotifyListener) 

消费者的注册url:

consumer://192.168.3.3/com.winfun.service.DubboServiceOne?application=dubbo-consumer-nacos&category=providers,configurators,routers&dubbo=2.0.2&init=false&interface=com.winfun.service.DubboServiceOne&metadata-type=remote&methods=sayHello&pid=39203&qos.enable=false&reference.filter=default,dubboLogFilter&release=2.7.8&revision=*&side=consumer&sticky=false&timestamp=1620177626113&version=* 

第一步、根据url获取serviceName集合:

org.apache.dubbo.registry.nacos.NacosRegistry#getServiceNames0 

1、创建 NacosServiceName:

providers:com.winfun.service.DubboServiceOne:*: 

2、接着到:org.apache.dubbo.registry.nacos.NacosRegistry#filterServiceNames(org.apache.dubbo.registry.nacos.NacosServiceName)

根据上面的servicename过滤出所有的serviceName

2.1、 先利用NamingProxy查询:

com.alibaba.nacos.client.naming.net.NamingProxy#getServiceList(int, int, java.lang.String, com.alibaba.nacos.api.selector.AbstractSelector) 

利用接口全路径名+group查询,没有带版本号

2.2、最后到:

com.alibaba.nacos.common.http.client.NacosRestTemplate#exchangeForm 

http 请求:

url:

http://127.0.0.1:8848/nacos/v1/ns/service/list params:{app=unknown, pageSize=2147483647, groupName=DEFAULT_GROUP, namespaceId=public, pageNo=1} 

返回结果:

RestResult{code=200, message=null, data={"doms":["providers:com.winfun.service.DubboServiceOne:1.0.0:","providers:com.winfun.service.DubboServiceOne:2.0.0:"],"count":2}} 

明显包含两个版本的service

第二步、根据条件过滤合适的 service

public boolean isCompatible(NacosServiceName concreteServiceName) {     if (!concreteServiceName.isConcrete()) { // The argument must be the concrete NacosServiceName         return false;     }     // Not match comparison     if (!StringUtils.isEquals(this.category, concreteServiceName.category)             && !matchRange(this.category, concreteServiceName.category)) {         return false;     }     if (!StringUtils.isEquals(this.serviceInterface, concreteServiceName.serviceInterface)) {         return false;     }     // wildcard condition     // 重点在这里     if (isWildcard(this.version)) {         return true;     }     if (isWildcard(this.group)) {         return true;     }     // range condition     if (!StringUtils.isEquals(this.version, concreteServiceName.version)             && !matchRange(this.version, concreteServiceName.version)) {         return false;     }     if (!StringUtils.isEquals(this.group, concreteServiceName.group) &&             !matchRange(this.group, concreteServiceName.group)) {         return false;     }     return true; } private boolean isWildcard(String value) {     return WILDCARD.equals(value); } public static final String WILDCARD = "*"; 

过滤后的 service 有两个,分别是1.0.0和2.0.0

那么继续深一步的订阅流程:org.apache.dubbo.registry.nacos.NacosRegistry#doSubscribe(org.apache.dubbo.common.URL, org.apache.dubbo.registry.NotifyListener, java.util.Set)

第三步、遍历serviceNames,根据serviceName+group查询所有实例列表并且进行实例监听

List<Instance> instances = new LinkedList<>(); for (String serviceName : serviceNames) {     instances.addAll(namingService.getAllInstances(serviceName             , getUrl().getParameter(GROUP_KEY, Constants.DEFAULT_GROUP)));     notifySubscriber(url, listener, instances);     subscribeEventListener(serviceName, url, listener); } 

到这里,整个订阅流程已经结束,主要是看version=*如何判断哪些服务实例可提供服务,再深入的就没有了。

Nacos 作为注册中心,查询服务实例主要是根据 serviceName(接口全路径名)和group(分组),这是因为Nacos的数据结构本身主要的就是服务名+分组名。

3.2.2.2 服务消费者调用过程

这个就不再深入讲解了,调用过程和 Zookeeper 上基本一致。

(责任编辑:应用开发)

最新内容
推荐内容
  • apt-get和apt-cache是Ubuntu Linux中的命令行下的包管理工具。 apt-get的GUI版本是Synaptic包管理器。该文中我们会展示apt-get和apt-cache命令的15个不同例子。示例:1 列出所有可用包复制代码代码如下:linuxtechi@localhost:~$ apt-cache pkgnames    account-plugin-yahoojp    ceph-fuse    dvd+rw-tools    e3    gnome-commander-data    grub-gfxpayload-lists    gweled    .......................................示例:2 用关键字搜索包这个命令在你不确定包名时很有用,只要在apt-cache(LCTT 译注:这里原文是apt-get,应为笔误)后面输入与包相关的关键字即可。复制代码代码如下:linuxtechi@localhost:~$ apt-cache search web server    apache2 - Apache HTTP Server    apache2-bin - Apache HTTP Server (binary files and modules)    apache2-data - Apache HTTP Server (common files)    apache2-dbg - Apache debugging symbols    apache2-dev - Apache HTTP Server (development headers)    apache2-doc - Apache HTTP Server (on-site documentation)    apache2-utils - Apache HTTP Server (utility programs for web servers)    ......................................................................注意: 假如你安装了“apt-file”包,我们就可以像下面那样用配置文件搜索包。复制代码代码如下:linuxtechi@localhost:~$ apt-file search nagios.cfg    ganglia-nagios-bridge: /usr/share/doc/ganglia-nagios-bridge/nagios.cfg    nagios3-common: /etc/nagios3/nagios.cfg    nagios3-common: /usr/share/doc/nagios3-common/examples/nagios.cfg.gz    pnp4nagios-bin: /etc/pnp4nagios/nagios.cfg    pnp4nagios-bin: /usr/share/doc/pnp4nagios/examples/nagios.cfg示例:3 显示特定包的基本信息复制代码代码如下:linuxtechi@localhost:~$ apt-cache show postfix    Package: postfix    Priority: optional    Section: mail    Installed-Size: 3524    Maintainer: LaMont Jones     Architecture: amd64    Version: 2.11.1-1    Replaces: mail-transport-agent    Provides: default-mta, mail-transport-agent    .....................................................示例:4 列出包的依赖复制代码代码如下:linuxtechi@localhost:~$ apt-cache depends postfix    postfix     Depends: libc6     Depends: libdb5.3     Depends: libsasl2-2     Depends: libsqlite3-0     Depends: libssl1.0.0     |Depends: debconf     Depends:      cdebconf     debconf     Depends: netbase     Depends: adduser     Depends: dpkg    ............................................示例:5 使用apt-cache显示缓存统计复制代码代码如下:linuxtechi@localhost:~$ apt-cache stats     Total package names: 60877 (1,218 k)    Total package structures: 102824 (5,758 k)     Normal packages: 71285     Pure virtual packages: 1102     Single virtual packages: 9151     Mixed virtual packages: 1827     Missing: 19459    Total distinct versions: 74913 (5,394 k)    Total distinct descriptions: 93792 (2,251 k)    Total dependencies: 573443 (16.1 M)    Total ver/file relations: 78007 (1,872 k)    Total Desc/File relations: 93792 (2,251 k)    Total Provides mappings: 16583 (332 k)    Total globbed strings: 171 (2,263 )    Total dependency version space: 2,665 k    Total slack space: 37.3 k    Total space accounted for: 29.5 M示例:6 使用 “apt-get update” 更新仓库使用命令“apt-get update”, 我们可以重新从源仓库中同步文件索引。包的索引从“/etc/apt/sources.list”中检索。复制代码代码如下:linuxtechi@localhost:~$ sudo apt-get update     Ign http://extras.ubuntu.com utopic InRelease     Hit http://extras.ubuntu.com utopic Release.gpg     Hit http://extras.ubuntu.com utopic Release     Hit http://extras.ubuntu.com utopic/main Sources     Hit http://extras.ubuntu.com utopic/main amd64 Packages     Hit http://extras.ubuntu.com utopic/main i386 Packages     Ign http://in.archive.ubuntu.com utopic InRelease     Ign http://in.archive.ubuntu.com utopic-updates InRelease     Ign http://in.archive.ubuntu.com utopic-backports InRelease     ................................................................示例:7 使用apt-get安装包复制代码代码如下:linuxtechi@localhost:~$ sudo apt-get install icinga上面的命令会安装叫“icinga”的包。示例:8 升级所有已安装的包复制代码代码如下:linuxtechi@localhost:~$ sudo apt-get upgrade示例:9 更新特定的包在apt-get命令中的“install”选项后面接上“-only-upgrade”用来更新一个特定的包,如下所示:复制代码代码如下:linuxtechi@localhost:~$ sudo apt-get install filezilla --only-upgrade示例:10 使用apt-get卸载包复制代码代码如下:linuxtechi@localhost:~$ sudo apt-get remove skype上面的命令只会删除skype包,假如你想要删除它的配置文件,在apt-get命令中使用“purge”选项。如下所示:复制代码代码如下:linuxtechi@localhost:~$ sudo apt-get purge skype我们可以结合使用上面的两个命令:复制代码代码如下:linuxtechi@localhost:~$ sudo apt-get remove --purge skype示例:11 在当前的目录中下载包复制代码代码如下:linuxtechi@localhost:~$ sudo apt-get download icinga    Get:1 http://in.archive.ubuntu.com/ubuntu/ utopic/universe icinga amd64 1.11.6-1build1 [1,474 B]    Fetched 1,474 B in 1s (1,363 B/s)上面的目录会把icinga包下载到你的当前工作目录。示例:12 清理本地包占用的磁盘空间复制代码代码如下:linuxtechi@localhost:~$ sudo apt-get clean上面的命令会清空apt-get所下载的包占用的磁盘空间。我们也可以使用“autoclean”选项来代替“clean”,两者之间主要的区别是autoclean清理不再使用且没用的下载。复制代码代码如下:linuxtechi@localhost:~$ sudo apt-get autoclean    Reading package lists... Done    Building dependency tree    Reading state information... Done示例:13 使用“autoremove”删除包当在apt-get命令中使用“autoremove”时,它会删除为了满足依赖而安装且现在没用的包。复制代码代码如下: linuxtechi@localhost:~$ sudo apt-get autoremove icinga示例:14 显示包的更新日志复制代码代码如下: linuxtechi@localhost:~$ sudo apt-get changelog apache2    Get:1 Changelog for apache2 (http://changelogs.ubuntu.com/changelogs/pool/main/a/apache2/apache2_2.4.10-1ubuntu1/changelog) [195 kB]    Fetched 195 kB in 3s (60.9 kB/s)上面的命令会下载apache2的更新日志,并在你屏幕上分页显示。示例:15 使用 “check” 选项显示损坏的依赖关系复制代码代码如下:linuxtechi@localhost:~$ sudo apt-get check    Reading package lists... Done    Building dependency tree    Reading state information... Done
  • 网络安全行业倦怠危机:CISO 如何保护团队与自己
  • 基于K8S的StatefulSet部署MySQL集群
  • 80后聊架构:3小时搞透数据库扩展性架构实践 | 架构师之路
  • 电脑机箱开着的致命错误(如何避免电脑机箱开着带来的隐患)
  • 云安全:数据库权限的分配与回收