MQ入门总结(四)ActiveMQ的部署和集群

转载:成小胖学习ActiveMQ·基础篇

转载:架构设计:系统间通信(25)——ActiveMQ集群方案(上)

转载:架构设计:系统间通信(26)——ActiveMQ集群方案(下)

一、ActiveMQ的部署方案

1.单例模式

单例模式不具备高可用特性,一般用于验证和学习,结构简单此处略过。

2.无共享主从模式

这是最简单的 Provider 高可用性的方案,主从节点分别存储 Message。从节点需要配置为连接到主节点,需要特殊配置其状态。


所有消息命令(消息,确认,订阅,事务等)都从主节点复制到从节点,这种复制发生在主节点对其接收的任何命令生效之前。并且,当主节点收到持久消息,会等待从节点完成消息的处理(通常是持久化到存储),然后再自己完成消息的处理(如持久化到存储)后,再返回对 Producer 的回执

从节点不启动任何传输,也不能接受任何客户端或网络连接,除非主节点失效。当主节点失效后,从节点自动成为主节点,并且开启传输并接受连接。这时,使用 failover 传输的客户端就会连接到该新主节点。

Broker 连接配置如下:

failover://(tcp://masterhost:61616,tcp://slavehost:61616)?randomize=false

但是,这种部署模式有一些限制:

  • 1. 主节点只会在从节点连接到主节点时复制其活动状态,因此当从节点没有连接上主节点之前,任何主节点处理的 Message 或者消息确认都会在主节点失效后丢失。不过你可以通过在主节点设置 waitForSlave 来避免,这样就强制主节点在没有任何一个从节点连接上的情况下接受连接
  • 2. 就是主节点只能有一个从节点,并且从节点不允许再有其他从节点
  • 3. 把正在运行的单例配置成无共享主从,或者配置新的从节点时,你都要停止当前服务,修改配置后再重启才能生效。
在可以接受一些故障停机时间的情况下,可以使用该模式。从节点配置:
<services>
        <masterConnector remoteURI="tcp://remotehost:62001" userName="Rob" password="Davies"/>
</services>

3.共享存储主从模式

允许多个代理共享存储,但任意时刻只有一个是活动的。这种情况下,当主节点失效时,无需人工干预来维护应用的完整性。另外一个好处就是没有从节点数的限制。有两种细分模式:
(1)基于数据库
它会获取一个表上的排它锁,以确保没有其他 ActiveMQ 代理可以同时访问数据库。其他未获得锁的代理则处于轮询状态,就会被当做是从节点,不会开启传输也不会接受连接。

(2)基于文件系统
需要获取分布式共享文件锁,linux 系统下推荐用 GFS2。

二、ActiveMQ的代理网络

1. 代理网络

支持将 ActiveMQ 消息代理链接到不同拓扑,这就是被人们熟知的代理网络。
ActiveMQ 网络使用存储和转发的概念,其中消息总是存储在本地代理中,然后通过网络转发到另一个代理。

当连接建立后,远程代理将把包含其所有持久和活动消费者目的地的信息传递给本地代理,本地代理根据信息决定远程代理感兴趣的 Message并将它发送给远程代理。
如果希望网络是双向的,您可以使用网络连接器将远程代理配置为指向本地代理,或将网络连接器配置为双工,以便双向发送消息。
<networkConnectors>
        <networkConnector uri="static://(tcp://backoffice:61617)"
                              name="bridge"
                              duplex="true"
                              conduitSubscriptions="true"
                              decreaseNetworkConsumerPriority="false">
        </networkConnector>
</networkConnectors>
注意,配置的顺序很重要:
    1.网络连接——需要 在消息存储前建立好连接,对应 networkConnectors 元素
    2.消息存储——需要 在传输前配置好,对应 persistenceAdapter 元素
    3.消息传输——最后配置,对应 transportConnectors 元素

2. 网络发现

(1)动态发现
ActiveMQ使用组播来支持网络动态发现。
组播(multicast)基于UDP协议 ,它是指在一个可连通的网络中,某一个数据报发送源 向一组数据报接收目标进行操作 的过程。在这个过程中,数据报发送者只需要向这个组播地址(一个 D类IP )发送一个数据报,那么加入这个组播地址的所有接收者都可以收到这个数据报。组播实现了 网络中单点到多点的高效数据传送 ,能够节约大量网络带宽,降低网络负载。
在IP协议中,规定的D类IP地址为组播地址。224.0.0.0~239.255.255.255这个范围内的IP都是D类IP地址,其中有一些IP段是保留的有特殊含义的:
1. 224.0.0.0~224.0.0.255:这个D类IP地址段为保留地址,不建议您在开发过程中使用,因为可能产生冲突。例如224.0.0.5这个组播地址专供OSPF协议(是一种路由策略协议,用于找到最优路径)使用的组播地址;224.0.0.18这个组播地址专供VRRP协议使用(VRRP协议是虚拟路由器冗余协议)。
2. 224.0.1.0~224.0.1.255:这个D类IP地址为公用组播地址,用于在整个Internet网络上进行组播。除非您有顶级DNS的控制/改写权限,否则不建议在局域网内使用这个组播地址断。
3. 239.0.0.0~239.255.255.255:这个D类IP地址段为推荐在局域网内使用的组播地址段。注意,如果要在局域网内使用组播功能,需要局域网中的交换机/路由器支持组播功能。幸运的是,目前市面上只要不是太过低端的交换机/路由器,都支持组播功能(组播功能所使用的主要协议为IGMP协议)。

(2)静态发现
静态发现接受代理 URI 列表,并将尝试按列表中确定的顺序连接到远程代理。
<networkConnectors>
    <networkConnector uri="static:(tcp://remote-master:61617,tcp://remote-slave:61617)"/>
</networkConnectors>
相关配置如下:
  • initialReconnectDelay:默认值1000,表示尝试连接前的时延。
  • maxReconnectDelay:默认值30000,表示连接失败后到重新建立连接之间的时延,仅在 useExponentialBackOff 启用时生效。
  • useExponentialBackOff:默认值 true,如果启用,表示每次失败后增加重建连接的时延。
  • backOffMultiplier:默认值2,表示启用 useExponentialBackOff 后每次的时延增量需要注意的是,网络连接将始终尝试建立到远程代理的连接。
需要注意的是,网络连接将 始终尝试建立到远程代理的连接
(3)多连接场景

当网络负载高时,使用多连接很有意义。但是你需要确保不会重复传递消息,这可以通过过滤器来实现。
<networkConnectors>
    <networkConnector uri="static://(tcp://remotehost:61617)"
                              name="queues_only"
                              duplex="true"
        <excludedDestinations>
            <topic physicalName=">"/>
        </excludedDestinations>
    </networkConnector>
    <networkConnector uri="static://(tcp://remotehost:61617)"
                              name="topics_only"
                              duplex="true"
        <excludedDestinations>
            <queue physicalName=">"/>
        </excludedDestinations>
    </networkConnector>
</networkConnectors>

三、ActiveMQ多节点方案

集群方案主要为了解决系统架构中的两个关键问题:高可用和高性能。ActiveMQ服务的高可用性是指,在ActiveMQ服务性能不变、数据不丢失的前提下,确保当系统灾难出现时ActiveMQ能够持续提供消息服务,高可用性方案最终目的是减少整个ActiveMQ停止服务的时间
ActiveMQ服务的高性能是指,在保证ActiveMQ服务持续稳定性、数据不丢失的前提下,确保ActiveMQ集群能够在单位时间内吞吐更高数量的消息、确保ActiveMQ集群处理单条消息的时间更短、确保ActiveMQ集群能够容纳更多的客户端稳定连接

ActiveMQ的多节点集群方案,主要有动态集群静态集群两种方案。
所谓动态集群就是指,同时提供消息服务的ActiveMQ节点数量、位置(IP和端口)是不确定的,当某一个节点启动后,会通过网络组播的方式向其他节点发送通知(同时接受其他节点的组播信息)。当网络中其他节点收到组播通知后,就会向这个节点发起连接,最终将新的节点加入ActiveMQ集群。
这里写图片描述
所谓静态集群是指同时提供消息服务的多个节点的位置(IP和端口)是确定的,每个节点不需要通过广播的方式发现目标节点,只需要在启动时按照给定的位置进行连接。
这里写图片描述


四、ActiveMQ的集群

1. 基于组播实现的节点发现

为了实现ActiveMQ集群的横向扩展要求和高稳定性要求,ActiveMQ集群提供了Network Bridges功能。通过Network Bridges功能,技术人员可以将多个ActiveMQ服务节点连接起来。并让它们通过配置好的策略作为一个整体对外提供服务。
这样的服务策略主要包括两种:主/从模式和负载均衡模式

这里写图片描述

要配置基于组播发现的ActiveMQ负载均衡模式,其过程非常简单。开发人员只需要在每一个ActiveMQ服务节点的主配置文件中(activemq.xml),添加/更改 以下配置信息即可:
......
<transportConnectors>
    <!-- 在transportConnector中增加discoveryUri属性,表示这个transportConnector是要通过组播告知其它节点的:使用这个transportConnector位置连接我 -->
    <transportConnector name="auto" uri="auto+nio://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600&org.apache.activemq.transport.nio.SelectorManager.corePoolSize=20&org.apache.activemq.transport.nio.SelectorManager.maximumPoolSize=50&consumer.prefetchSize=5" discoveryUri="multicast://239.0.0.5" />
</transportConnectors>

......

<!-- 关键的networkConnector标签, uri属性标示为组播发现-->
<networkConnectors>
    <networkConnector uri="multicast://239.0.0.5" duplex="false"/>
</networkConnectors>

......

1.1 networkConnector标签

如果使用ActiveMQ的组播发现功能,请在networkConnector标签的uri属性中添加如下格式的信息:
multicast://[组播地址][:端口]
例如,您可以按照如下方式使用ActiveMQ默认的组播地址来发现网络种其他ActiveMQ服务节点:
#ActiveMQ集群默认的组播地址(239.255.2.3):
multicast://default
也可以按照如下方式,指定一个组播地址——这在高安全级别的网络中很有用,因为可能其他的组播地址已经被管理员禁用。注意组播地址只能是D类IP地址段:
#使用组播地址239.0.0.5
multicast://239.0.0.5

1.2 transportConnector标签的关联设置

任何一个ActiveMQ服务节点A,要连接到另外的ActiveMQ服务节点,都需要使用当前节点A已经公布的transportConnector连接端口,例如以下配置中,能够供其它服务节点进行连接的就只有两个transportConnector连接中的任意一个:
......
<transportConnectors>
    <!-- 其它ActiveMQ服务节点,只能使用以下三个连接协议和端口进行连接 -->
    <!-- DOS protection, limit concurrent connections to 1000 and frame size to 100MB -->
    <transportConnector name="tcp" uri="tcp://0.0.0.0:61614?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
    <transportConnector name="nio" uri="nio://0.0.0.0:61618?maximumConnections=1000" />
    <transportConnector name="auto" uri="auto://0.0.0.0:61617?maximumConnections=1000" />   
</transportConnectors>
......
那么要将哪一个连接方式通过UDP数据报向其他ActiveMQ节点进行公布,就需要在transportConnector标签上使用 discoveryUri属性进行标识,如下所示:
......
<transportConnectors>
    ......
    <transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
    <transportConnector name="auto" uri="auto+nio://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600" discoveryUri="multicast://239.0.0.5" />
</transportConnectors>

......
<networkConnectors>
    <networkConnector uri="multicast://239.0.0.5"/>
</networkConnectors>
......
1.3:其他注意事项

  • 关于防火墙:请记得关闭您Linux服务器上对需要公布的IP和端口的限制;
  • 关于hosts路由信息:由于基于组播的动态发现机制,能够找到的是目标ActiveMQ服务节点的机器名,而不是直接找到的IP。所以请设置当前服务节点的hosts文件,以便当前ActiveMQ节点能够通过hosts文件中的IP路由关系,得到机器名与IP的映射:
# hosts文件

......
192.168.61.139          activemq1
192.168.61.138          activemq2
......
  • 关于哪些协议能够被用于进行Network Bridges连接:根据笔者以往的使用经验,只有tcp头的uri格式(openwire协议)能够被用于Network Bridges连接;当然您可以使用auto头,因为其兼容openwire协议;另外,您还可以指定为附加nio头。

2. 静态Network Connectors

相比于基于组播发现方式的动态Network Connectors而言,虽然静态Network Connectors没有那样灵活的横向扩展性,但是却可以适用于网络环境受严格管理的情况。例如:管理员关闭了交换机/路由器的组播功能、端口受到严格管控等等。
配置静态Network Connectors的ActiveMQ集群的方式也很简单,只需要更改networkConnectors标签中的配置即可,而无需关联改动transportConnectors标签。但是配置静态Network Connectors的ActiveMQ集群时,需要注意非常关键的细节:每一个节点都要配置其他所有节点的连接位置
......
<transportConnectors>
    <transportConnector name="auto" uri="auto+nio://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600&consumer.prefetchSize=5"/>
</transportConnectors>
......

<!-- 请注意,一定需要192.168.61.139(activemq2)提供了这样的连接协议和端口 -->
<networkConnectors>
    <networkConnector uri="static:(auto+nio://192.168.61.139:61616)"/>
</networkConnectors>
......
为了演示配置过程,我们假设ActiveMQ集群由两个节点构成,分别是activemq1:192.168.61.138 和 activemq2:192.168.61.139。那么配置情况如下所示:
......
<transportConnectors>
    <transportConnector name="auto" uri="auto+nio://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600&consumer.prefetchSize=5"/>
</transportConnectors>

......
<!-- 请注意,一定需要192.168.61.138(activemq1)提供了这样的连接协议和端口 -->
<networkConnectors>
   <networkConnector uri="static:(auto+nio://192.168.61.138:61616)"/>
</networkConnectors>
......
同理,如果您的ActiveMQ集群规划中有三个ActiveMQ服务节点,那么 任何一个节点都应该配置其它两个服务节点的连接方式。在配置格式中使用“,”符号进行分割:
......
<networkConnectors>
    <networkConnector uri="static:(tcp://host1:61616,tcp://host2:61616,tcp://..)"/>
</networkConnectors>
......
以下是配置完成后可能的效果:
192.168.61.138(activemq1):
这里写图片描述
192.168.61.139(activemq2):
这里写图片描述

3. 其他配置属性
下表列举了在networkConnector标签中还可以使用的属性以及其意义。请特别注意其中的duplex属性。如果只从字面意义理解该属性,则被称为“双工模式”;如果该属性为true,当这个节点使用Network Bridge连接到其它目标节点后,将强制目标也建立Network Bridge进行反向连接。其目的在于让消息既能发送到目标节点,又可以通过目标节点接受消息,但实际上大多数情况下是没有必要的,因为目标节点一般都会自行建立连接到本节点。所以,该duplex属性的默认值为false。
属性名称 默认值 属性意义
name bridge 名称
dynamicOnly false 如果为true, 持久订阅被激活时才创建对应的网路持久订阅。
decreaseNetworkConsumerPriority false 如果为true,网络的消费者优先级降低为-5。如果为false,则默认跟本地消费者一样为0.
excludedDestinations empty 不通过网络转发的destination
dynamicallyIncludedDestinations empty 通过网络转发的destinations,注意空列表代表所有的都转发。
staticallyIncludedDestinations empty 匹配的都将通过网络转发-即使没有对应的消费者,如果为默认的“empty”,那么说明所有都要被转发
duplex false 已经进行详细介绍的“双工”属性。
prefetchSize 1000 设置网络消费者的prefetch size参数。如果设置成0,那么就像之前文章介绍过的那样:消费者会自己轮询消息。显然这是不被允许的。
suppressDuplicateQueueSubscriptions false 如果为true, 重复的订阅关系一产生即被阻止(V5.3+ 的版本中可以使用)。
bridgeTempDestinations true 是否广播advisory messages来创建临时destination。
alwaysSyncSend false 如果为true,非持久化消息也将使用request/reply方式代替oneway方式发送到远程broker(V5.6+ 的版本中可以使用)。
staticBridge false 如果为true,只有staticallyIncludedDestinations中配置的destination可以被处理(V5.6+ 的版本中可以使用)。
以下这些属性,只能在静态Network Connectors模式下使用
属性名称 默认值 属性意义
initialReconnectDelay 1000 重连之前等待的时间(ms) (如果useExponentialBackOff为false)
useExponentialBackOff true 如果该属性为true,那么在每次重连失败到下次重连之前,都会增大等待时间
maxReconnectDelay 30000 重连之前等待的最大时间(ms)
backOffMultiplier 2 增大等待时间的系数
请注意这些属性,并不是networkConnector标签的属性,而是在uri属性中进行设置的,例如:
uri="static:(tcp://host1:61616,tcp://host2:61616)?maxReconnectDelay=5000&useExponentialBackOff=false"


五、ActiveMQ的热备

ActiveMQ热备方案,主要保证ActiveMQ的高可用性。这种方案并不像上节中我们主要讨论的ActiveMQ高性能方案那样,同时有多个节点都处于工作状态,也就是说这种方案并不提高ActiveMQ集群的性能;而是从集群中的多个节点选择一个,让其处于工作状态,集群中其它节点则处于待命状态。当主要的工作节点由于各种异常情况停止服务时,保证处于待命的节点能够无缝接替其工作。
Network Bridge方式连接的ActiveMQ服务器之间为了网络流量考虑只会传播被订阅的消息,因此如下图:
这里写图片描述

Broker B中没有消息的消费者时,Broker A和Broker B之间的消息不同步,当没有任何消费者在任何服务节点订阅ActiveMQ A中队列的消息时,一旦ActiveMQ A由于各种异常退出,后来的消费者就再也收不到消息,直到ActiveMQ A恢复工作。所以我们需要一种高可用方案,让某一个服务节点能够7 * 24小时的稳定提供消息服务。

1. 基于共享文件系统的热备方案

基于共享文件系统的热备方案可以说是ActiveMQ消息中间件中最早出现的一种热备方案。它的工作原理很简单:让若干个ActiveMQ服务节点,共享一个文件系统。当某一个ActiveMQ服务抢占到了这个文件系统的操作权限,就给文件系统的操作区域加锁;其它服务节点一旦发现这个文件系统已经被加锁(并且锁不属于本进程),就会自动进入Salve模式。
ActiveMQ早期的文件存储方案、KahaDB存储方案、LevelDB存储方案都支持这个工作模式。当某个ActiveMQ节点获取了文件系统的操作权限后,首先做的事情就是从文件系统中恢复内存索引结构:KahaDB恢复BTree结构;LevelDB恢复memTable结构。
这里写图片描述
因为本专题讲解的技术体系都是工作在Linux操作系统上,所以为多个ActiveMQ提供共享文件系统方案的第三方文件系统都必须支持POSIX协议,这样Linux操作系统才能实现远程挂载。
幸运的是,这样的第三方系统多不胜举,例如:基于网络文件存储的NFS、NAS;基于对象存储的分布式文件系统Ceph、MFS、Swift(不是ios的编程语言)、GlusterFS(高版本);以及ActiveMQ官方推荐的网络块存储方案:SAN(就是成本有点高)。

2. 基于共享关系型数据库的热备方案

基于关系型数据库的热备方案它的工作原理实际上和基于共享文件系统的热备方案相似:
  • 1. 首先使用关系型数据库作为ActiveMQ的持久化存储方案时,在指定的数据库中会有三张数据表:activemq_acks,activemq_lock,activemq_msgs(有的情况下您生成的数据表名会是大写的,这是因为数据库自身设置的原因)。
  • 2. 其中“activemq_lock”这张数据表记录了当前对数据库拥有操作权限的ActiveMQ服务的ID信息、Name信息。各个ActiveMQ服务节点从这张数据表识别当前哪一个节点是Master状态。
  • 3. 当需要搭建热备方案时,两个或者更多的ActiveMQ服务节点共享同一个数据服务。首先抢占到数据库服务的ActiveMQ节点,会将数据库中“activemq_lock”数据表的Master状态标记为自己,这样其它ActiveMQ服务节点就会进入Salve状态。
为了便于各位读者进行这种方案的配置实践,这里给出了关键的配置信息:实际上真的很简单,一定要首先确保您的数据库是可用的,并且每一个ActiveMQ节点都这样配置
......
<broker xmlns="http://activemq.apache.org/schema/core">
    ......
    <persistenceAdapter>
        <!-- 设置使用的数据源 -->
        <jdbcPersistenceAdapter dataSource="#mysql-ds"/>
    </persistenceAdapter>
    ......
</broker>
......

<!-- 一定要确保数据库可用,且在ActiveMQ的lib目录中有必要的jar文件 -->
<bean id="mysql-ds" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://您的mysql连接url信息?relaxAutoCommit=true"/>
    <property name="username" value="activemq"/>
    <property name="password" value="activemq"/>
    <property name="poolPreparedStatements" value="true"/>
</bean>

3. LevelDB + Zookeeper的热备方案

从ActiveMQ V5.9.0+ 版本开始,ActiveMQ为使用者提供了一种新的Master/Salve热备方案。这个方案中,我们可以让每个节点都有自己独立的LevelDB数据库(不是像3-2小节那样共享LevelDB的工作目录),并且使用Zookeeper集群控制多个ActiveMQ节点的工作状态,完成Master/Salve状态的切换。工作模式如下图所示(摘自官网):
这里写图片描述
在这种新的工作模式下,Master节点和各个Salve节点通过Zookeeper进行工作状态同步,即使某个Salve节点新加入也没有问题。具体配置实例见:架构设计:系统间通信(26)——ActiveMQ集群方案(下)

4. ActiveMQ客户端的故障转移

以上三种热备方案,都已向各位读者介绍。细心的读者会发现一个问题,因为我们没有使用类似Keepalived那样的第三方软件支持浮动IP。那么无论以上三种热备方案的哪一种,虽然服务端可以无缝切换提供连续的服务,但是对于客户端来说连接服务器的IP都会发生变化。也就是说客户端都会因为连接异常脱离正常工作状态。
为了解决这个问题,AcitveMQ的客户端连接提供了配套的解决办法:连接故障转移。开发人员可以预先设置多个可能进行连接的IP位置(这些位置不一定同时都是可用的),ActiveMQ的客户端会从这些连接位置选择其中一个进行连接,当连接失败时自动切换到另一个位置连接。使用方式类似如下:
......
//这样的设置,即使在发送/接收消息的过程中出现问题,客户端连接也会进行自动切换
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("failover:(tcp://192.168.61.138:61616,tcp://192.168.61.139:61616)");
......

六、ActiveMQ生产环境方案

在实际工作中将ActiveMQ应用到生产环境时,除非您的业务环境有特殊要求的情况,一般建议将ActiveMQ的高性能方案和高可用方案进行结合。以下向各位读者提供一种ActiveMQ高性能和高可用性结合的方案:
  1. 将9个ActiveMQ节点分为三组(brokerName1、brokerName2、brokerName3),每组都有三个ActiveMQ服务节点。另外准备三个节点的zookeeper服务集群,所有三个组九个ActiveMQ服务都共享这三个zookeeper服务节点(只是每组ActiveMQ所设置的zkPath属性不一样)
  2. 将每组的三个ActiveMQ服务节点做LevelDB + Zookeeper的热备方案(且设置replicas=2)。保证每组只有一个节点在一个时间内为Master状态。这样整个集群中的九个ActiveMQ服务节点就同时会有三个ActiveMQ服务节点处于Master状态
  3. 将整个集群中所有ActiveMQ服务节点的高性能方案设置为“组播发现”,并都采用一个相同的组播地址(可以采用默认的组播地址)。这样三个处于Master状态的ActiveMQ服务节点就会形成一个高性能方案(处于Salve状态的节点不会发出组播消息)。
整个设计结构如下图所示:
这里写图片描述


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值