iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >Dubbo中@DubboReference.version如何设置为*
  • 192
分享到

Dubbo中@DubboReference.version如何设置为*

2024-04-02 19:04:59 192人浏览 八月长安
摘要

dubbo中@DubboReference.version如何设置为*,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。Dubbo在消费端提供

dubbo中@DubboReference.version如何设置为*,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

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

1 初步猜测

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

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

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

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

2 源码剖析

2.1 Zookeeper 作为注册中心

2.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

2.1.2 源码分析

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

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

2.1.2.1 服务消费者订阅过程

我们都知道,正常发布Dubbo的消费者,需要配置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属性中看得出,默认的负载策略是随机。

 String loadbalance() default "";  org.apache.dubbo.common.constants.CommonConstants#DEFAULT_LOADBALANCE="random";

2.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”

 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设置为*,并且方法调用时是如何选择服务提供者。

2.2 Nacos 作为注册中心

2.2.1 准备

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

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

2.2.2 源码分析

2.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的数据结构本身主要的就是服务名+分组名。

Dubbo中@DubboReference.version如何设置为*

2.2.2.2 服务消费者调用过程

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

关于Dubbo中@DubboReference.version如何设置为*问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注编程网JavaScript频道了解更多相关知识。

--结束END--

本文标题: Dubbo中@DubboReference.version如何设置为*

本文链接: https://www.lsjlt.com/news/81569.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Dubbo中@DubboReference.version如何设置为*
    Dubbo中@DubboReference.version如何设置为*,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。Dubbo在消费端提供...
    99+
    2024-04-02
  • centos如何设置为中文
    centos设置为中文的方法:1、打开centos终端;2、在命令行中输入“sudo yum groupinstall chinese-support”命令安装中文包;3、输入“sudo vim ~/.bashrc”命令打开系统配置文件并修...
    99+
    2024-04-02
  • ChatGPTAI如何设置为中文
    这篇“ChatGPTAI如何设置为中文”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Cha...
    99+
    2023-02-22
  • css中如何将div设置为居中
    这篇文章主要介绍css中如何将div设置为居中,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!css中将div设置为居中的方法是设置定位,例如【position:absolute;】或【margin:auto;】。当我...
    99+
    2023-06-15
  • php如何设置时区为中国
    本文小编为大家详细介绍“php如何设置时区为中国”,内容详细,步骤清晰,细节处理妥当,希望这篇“php如何设置时区为中国”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。php设置时区为中国的方法:1、打开配置文件“...
    99+
    2023-07-05
  • 如何在PHP中设置headers为200
    在编写Web应用程序的过程中,设置正确的HTTP headers非常重要。HTTP headers是包含在HTTP请求中的元数据,它提供了与Web服务器之间通信的基础。如果headers设置不正确,会导致页面响应缓慢、无法加载、错误的搜索引...
    99+
    2023-05-14
    php
  • dubbo负载均衡如何配置
    在Dubbo中,负载均衡可以通过配置来进行调整。可以通过在提供者和消费者端的Dubbo配置文件中进行相应的配置。在提供者端的配置文件...
    99+
    2023-09-02
    负载均衡
  • 如何将eclipse语言设置为中文
    本篇内容介绍了“如何将eclipse语言设置为中文”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!eclipse语言设置为中文的方法:1、打开...
    99+
    2023-07-04
  • jquery如何设置单选框为选中
    这篇文章主要介绍“jquery如何设置单选框为选中”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“jquery如何设置单选框为选中”文章能帮助大家解决问题。方法1:使用.prop()方法.prop()...
    99+
    2023-07-06
  • 如何将gitlab设置为中文界面
    GitLab是流行的代码托管和协作平台。它提供了一个易于使用和强大的平台,用于管理代码版本控制和开发项目。尽管GitLab是一个英文平台,但是许多人可能更喜欢使用中文。这就是GitLab设置中文的原因。在本文中,我们将介绍如何在GitLab...
    99+
    2023-10-22
  • SpringBoot中如何整合Dubbo zookeeper
    本篇内容介绍了“SpringBoot中如何整合Dubbo zookeeper”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!docker pu...
    99+
    2023-06-08
  • MySQL中如何为字段设置默认值?
    在MySQL中,我们可以为表的字段设置默认值,以确保在插入新记录时,如果没有为该字段指定值,将使用默认值。这在许多情况下都非常有用,特别是当我们希望在插入数据时自动填充某些字段,或者当我们想要为字段提...
    99+
    2023-10-23
    mysql 数据库
  • css中background如何设置图片为背景
    这篇文章主要为大家展示了“css中background如何设置图片为背景”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“css中background如何设置图片为...
    99+
    2024-04-02
  • Netty-在-Dubbo-中如何应用
    前言众所周知,国内知名框架 Dubbo 底层使用的是 Netty 作为网络通信,那么内部到底是如何使用的呢?今天我们就来一探究竟。 dubbo 的 Consumer 消费者如何使用 Netty注意:此次代码使用了从 github 上 clo...
    99+
    2023-06-05
  • Listener如何在dubbo中实现
    本篇文章给大家分享的是有关Listener如何在dubbo中实现,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。拿ProtocolListenerWrapper为例子,看源码的时...
    99+
    2023-05-31
    dubbo listener lis
  • 使用spring-boot如何实现整合dubbo中的Spring-boot-dubbo-starter
    使用spring-boot如何实现整合dubbo中的Spring-boot-dubbo-starter?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。在application.p...
    99+
    2023-05-31
    springboot art dubbo
  • dubbo负载均衡策略如何配置
    Dubbo提供了多种负载均衡策略,可以根据具体的业务需求进行配置。一般情况下,可以在服务提供者和消费者的dubbo配置文件中配置负载...
    99+
    2023-06-13
    dubbo负载均衡策略 负载均衡
  • Netty如何设置为Https访问
    目录Netty设置为Https访问SSLContextFactory处理类 Netty实现Http协议maven依赖的包1.netty启动入口2.编写NettyHttpSe...
    99+
    2024-04-02
  • JavaScript如何为XMLHttpRequests设置超时
    这篇文章将为大家详细讲解有关JavaScript如何为XMLHttpRequests设置超时,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。为 XMLHttpRequest...
    99+
    2024-04-02
  • mysql如何设置表为只读
    这篇文章主要介绍“mysql如何设置表为只读”,在日常操作中,相信很多人在mysql如何设置表为只读问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”mysql如何设置表为只读”...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作