HSF 及伙伴们:分布式服务框架(Dubbo、Thrift、gRPC、Motan)对比理解

分布式服务框架:HSF 与 Dubbo、Thrift、gRPC、Motan 齐亮相

引言

在分布式系统开发中,选择合适的服务接口框架对于构建高性能、高可用的系统起着决定性的作用。不同的框架具有独特的特性和适用场景,本文将对 HSF(High-Speed Service Framework)以及其他几个主流的分布式服务接口框架,包括 Dubbo、Thrift、gRPC 和 Motan 进行深入对比和解析,帮助更加清晰地了解它们的架构、特性和使用方式,以便根据项目需求选择最适合的框架。

一、HSF

1. 简介

HSF 是阿里巴巴开发的分布式服务框架,旨在为大规模分布式系统提供高性能、高可用的服务通信和服务治理解决方案。它通过将服务提供者的服务信息注册到服务注册中心,使服务消费者能够方便地查找和调用服务,在阿里巴巴内部的众多复杂分布式系统中发挥着重要作用。

2. 架构与特点

  • 架构
    • 服务提供者:将服务接口及其实现注册到服务注册中心,服务提供者启动时,会将自身服务的详细信息(包括接口、版本、地址等)存储到服务注册中心,以便服务消费者能够发现并调用。
    • 服务注册中心:存储服务信息,提供服务的注册和发现服务,是服务之间进行通信的桥梁,同时支持服务的动态扩展和管理。
    • 服务消费者:从服务注册中心查找所需服务的信息,根据信息建立与服务提供者的连接,发起服务调用。HSF 框架还提供了负载均衡、路由、集群容错等复杂的分布式服务调用和治理功能。
  • 特点
    • 服务治理功能强大
      • 提供了丰富的服务治理能力,如服务的注册和发现、负载均衡、限流、降级、熔断、集群容错等,能有效应对高并发、高负载以及服务异常等复杂情况。
      • 支持多种负载均衡策略,根据系统负载自动调整服务调用,确保服务的性能和稳定性。
    • 高性能
      • 采用了高效的通信协议和序列化机制,适合大规模分布式系统中的高性能服务调用,能够有效降低网络开销和提高服务响应速度。

3. 示例代码

接口定义
package com.example.hsf.service;

import com.example.hsf.model.User;

public interface UserService {
    User getUserById(String userId);
    void updateUser(User user);
}
服务提供者
package com.example.hsf.service;

import com.taobao.hsf.app.spring.util.HSFSpringProviderBean;
import com.example.hsf.service.UserService;
import com.example.hsf.model.User;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    @Override
    public User getUserById(String userId) {
        // 实现 getUserById 服务逻辑,例如从数据库或其他存储中获取用户信息
        User user = new User();
        user.setId(userId);
        user.setName("张三");
        return user;
    }

    @Override
    public void updateUser(User user) {
        // 实现 updateUser 服务逻辑,例如更新数据库中的用户信息
        System.out.println("更新用户信息:" + user.getName());
    }

    // 配置服务提供者
    @HSFSpringProviderBean(interfaceClass = UserService.class, serviceVersion = "1.0")
    public static UserService userService() {
        return new UserServiceImpl();
    }
}
服务消费者
package com.example.hsf.consumer;

import com.taobao.hsf.app.spring.util.HSFSpringConsumerBean;
import com.example.hsf.service.UserService;
import com.example.hsf.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceConsumer {
    // 自动注入 UserService 服务
    @Autowired
    @HSFSpringConsumerBean(interfaceClass = UserService.class, serviceVersion = "1.0")
    private UserService userService;

    public void consumeService() {
        // 调用 getUserById 方法
        User user = userService.getUserById("123");
        System.out.println("获取到的用户信息:" + user.getName());
        // 调用 updateUser 方法
        User newUser = new User("123", "李四");
        userService.updateUser(newUser);
    }

    public static void main(String[] args) {
        // 加载 Spring 上下文
        // 此处假设使用 Spring 容器启动应用,根据实际情况可能有所不同
        // ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        // UserServiceConsumer consumer = context.getBean(UserServiceConsumer.class);
        UserServiceConsumer consumer = new UserServiceConsumer();
        consumer.consumeService();
    }
}
代码逻辑
  • 在服务提供者端,使用 @HSFSpringProviderBean 注解将 UserServiceImpl 作为 UserService 接口的服务提供者,并将其注册到服务注册中心,同时指定服务版本为 1.0
  • 在服务消费者端,使用 @HSFSpringConsumerBean 注解自动注入 UserService 服务,并调用服务方法。@Autowired 注解确保服务实例的注入,使服务消费者能够方便地调用服务提供者提供的服务。

二、Dubbo

1. 简介

Dubbo 是阿里巴巴开源的一款高性能、轻量级的 Java RPC 框架,专注于提供透明化的 RPC 服务调用和强大的服务治理功能,旨在为分布式服务的开发和管理提供便利,适用于构建大规模分布式系统。

2. 架构与特点

  • 架构
    • 服务提供者:将自身提供的服务注册到注册中心,使用 ZooKeeper 等作为注册中心存储服务信息,服务启动时发布服务接口、地址等信息。
    • 注册中心:存储服务信息,实现服务的动态注册和发现,支持服务的动态感知和路由。
    • 服务消费者:从注册中心获取服务信息,发起服务调用,并支持负载均衡、集群容错等服务治理功能。
  • 特点
    • 服务治理功能强大
      • 提供了丰富的服务治理功能,包括服务的注册和发现、负载均衡、集群容错等,能够有效管理分布式服务。
      • 支持多种负载均衡策略,如随机、轮询、最少活跃调用数、一致性哈希等,可根据不同业务场景灵活调整。
      • 提供多种集群容错模式,如失败重试、快速失败、故障转移等,保障服务调用的可靠性。
    • 配置灵活
      • 支持多种配置方式,包括 XML 配置和注解,方便开发人员根据不同的开发环境和项目需求进行灵活配置。

3. 示例代码

接口定义
package com.example.dubbo.service;

import com.example.dubbo.model.User;

public interface UserService {
    User getUserById(String userId);
    void updateUser(User user);
}
服务提供者
<!-- dubbo-provider.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://code.alibabatech.com/dubbo
        http://code.alibabatech.com/dubbo/dubbo.xsd">

    <!-- 服务实现类 -->
    <bean id="userService" class="com.example.dubbo.service.impl.UserServiceImpl" />

    <!-- 服务提供者配置 -->
    <dubbo:service interface="com.example.dubbo.service.UserService" ref="userService" />
</beans>
package com.example.dubbo.service.impl;

import com.example.dubbo.service.UserService;
import com.example.dubbo.model.User;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    @Override
    public User getUserById(String userId) {
        // 服务逻辑:从数据库或其他存储中获取用户信息
        User user = new User();
        user.setId(userId);
        user.setName("张三");
        return user;
    }

    @Override
    public void updateUser(User user) {
        // 服务逻辑:更新用户信息,如更新数据库中的用户信息
        System.out.println("更新用户信息:" + user.getName());
    }
}
服务消费者
<!-- dubbo-consumer.xml -->
<?xml version="1.0" encoding="2.0"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://code.alibabatech.com/dubbo
        http://code.alibabatech.com/dubbo/dubbo.xsd">

    <!-- 服务消费者配置 -->
    <dubbo:reference interface="com.example.dubbo.service.UserService" id="userService" />
</beans>
package com.example.dubbo.consumer;

import com.example.dubbo.service.UserService;
import com.example.dubbo.model.User;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class UserServiceConsumer {
    public static void main(String[] args) {
        // 加载 Spring 上下文
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("dubbo-consumer.xml");
        // 从 Spring 容器中获取 UserService 的实例
        UserService userService = (UserService) context.getBean("userService");
        // 调用 getUserById 方法
        User user = userService.getUserById("123");
        System.out.println("获取到的用户信息:" + user.getName());
        // 调用 updateUser 方法
        User newUser = new User("123", "李四");
        userService.updateUser(newUser);
    }
}
代码逻辑
  • 在服务提供者端,通过 <dubbo:service> 元素将 UserServiceImpl 作为 UserService 接口的服务提供者,并将其注册到注册中心。
  • 在服务消费者端,通过 <dubbo:reference> 元素从注册中心获取 UserService 的引用,使用 getBean 方法从 Spring 容器中获取服务实例,进而实现服务调用。

三、Thrift

1. 简介

Apache Thrift 是由 Facebook 开发的可伸缩的跨语言服务开发框架,它使用接口定义语言(IDL)来定义服务接口,可根据定义好的 IDL 自动生成不同语言的客户端和服务端代码,方便实现跨语言的服务调用。

2. 架构与特点

  • 架构
    • IDL 文件:使用 Thrift 的 IDL 定义服务接口和数据结构,作为不同语言之间通信的基础,确保服务的一致性和兼容性。
    • 代码生成器:根据 IDL 文件生成不同语言的服务端和客户端代码,使不同语言编写的服务和客户端之间能够无缝通信。
    • 传输层和协议层:支持多种传输协议(如 TCP、HTTP 等)和通信协议(如 TBinaryProtocol、TCompactProtocol 等),以满足不同的性能和兼容性需求。
  • 特点
    • 跨语言支持
      • 支持多种编程语言,包括 Java、C++、Python、Ruby 等,非常适合多语言开发的分布式系统。
    • 高效的通信协议和序列化机制
      • 提供多种通信协议和序列化机制,开发人员可根据性能和兼容性需求灵活选择,例如,使用二进制序列化的 TBinaryProtocol 可显著减少数据传输量,提高通信性能。

3. 示例代码

接口定义(IDL 文件)
namespace java com.example.thrift

struct User {
    1: required string id;
    2: required string name;
}

service UserService {
    User getUserById(1: string userId);
    void updateUser(1: User user);
}
服务端代码
package com.example.thrift.service;

import com.example.thrift.User;
import com.example.thrift.UserService;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TSimpleServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;
import org.apache.thrift.transport.TTransportException;

public class UserServiceHandler implements UserService.Iface {
    @Override
    public User getUserById(String userId) throws TException {
        // 服务逻辑:实现 getUserById 服务
        User user = new User();
        user.setId(userId);
        user.setName("张三");
        return user;
    }

    @Override
    public void updateUser(User user) throws TException {
        // 服务逻辑:实现 updateUser 服务
        System.out.println("更新用户信息:" + user.getName());
    }

    public static void main(String[] args) throws TTransportException {
        // 创建处理器
        UserService.Processor<UserService.Iface> processor = new UserService.Processor<>(new UserServiceHandler());
        // 服务传输层,监听端口 9090
        TServerTransport serverTransport = new TServerSocket(9090);
        // 使用 TBinaryProtocol 协议
        TServer server = new TSimpleServer(new TServer.Args(serverTransport)
                                 .processor(processor)
                                 .protocolFactory(new TBinaryProtocol.Factory()));
        // 启动服务
        server.serve();
    }
}
客户端代码
package com.example.thrift.client;

import com.example.thrift.User;
import com.example.thrift.UserService;
import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;

public class UserServiceClient {
    public static void main(String[] args) {
        try (TTransport transport = new TSocket("localhost", 9090)) {
            // 打开传输通道
            transport.open();
            // 使用 TBinaryProtocol 协议
            UserService.Client client = new UserService.Client(new TBinaryProtocol(transport));
            // 调用 getUserById 方法
            User user = client.getUserById("123");
            System.out.println("获取到的用户信息:" + user.getName());
            // 调用 updateUser 方法
            User newUser = new User();
            newUser.setId("123");
            newUser.setName("李四");
            client.updateUser(newUser);
        } catch (TException e) {
            e.printStackTrace();
        }
    }
}
代码逻辑
  • 首先,使用 Thrift 的 IDL 文件定义了 UserService 接口和 User 数据结构。
  • 服务端代码中,UserServiceHandler 实现了 UserService.Iface 接口,实现了服务逻辑。通过 TSimpleServer 启动服务,使用 TBinaryProtocol 协议进行通信。
  • 客户端代码使用 TSocket 打开到服务端的连接,使用 TBinaryProtocol 协议创建客户端,调用服务端的服务方法。

四、gRPC

1. 简介

gRPC 是由 Google 开发的高性能、通用的开源 RPC 框架,采用 Protocol Buffers 作为接口定义语言和数据序列化格式,并支持 HTTP/2 协议,为服务调用提供了高效、跨语言的解决方案。

2. 架构与特点

  • 架构
  • Protocol Buffers(protobuf):使用 .proto 文件定义服务接口和消息类型,作为服务的契约,确保服务的一致性和跨语言通信。
  • 服务端和客户端代码生成:根据 .proto 文件生成不同语言的服务端和客户端代码,方便不同语言开发的系统之间的通信。
  • HTTP/2 支持:利用 HTTP/2 的多路复用、头部压缩等特性,提高服务通信的性能。
  • 特点
  • 高性能
    - 采用 Protocol Buffers 进行序列化,具有更高的数据压缩比和性能,降低网络传输开销。
    - 支持 HTTP/2,提升服务调用的性能和并发处理能力,适合高并发和低延迟场景。
  • 语言无关性
    - 支持多种编程语言,使不同语言开发的系统可以方便地进行服务调用。

3. 示例代码

接口定义(.proto 文件)
syntax = "3";

package com.example.grpc;

message UserRequest {
  string id = 1;
}

message UserResponse {
  string name = 1;
}

service UserService {
  rpc GetUserById(UserRequest) returns (UserResponse);
}
服务端代码
package com.example.grpc.service;

import com.example.grpc.UserRequest;
import com.example.grpc.UserResponse;
import com.example.grpc.UserServiceGrpc;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
    @Override
    public void getUserById(UserRequest request, StreamObserver<UserResponse> responseObserver) {
        // 实现 getUserById 服务逻辑
        UserResponse response = UserResponse.newBuilder()
                                    .setName("张三")
                                    .build();
        // 发送响应
        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }

    public static void main(String[] args) throws Exception {
        // 启动服务,监听端口 50051
        Server server = ServerBuilder.forPort(50051)
                              .addService(new UserServiceImpl())
                              .build();
        server.start();
        server.awaitTermination();
    }
}
客户端代码
package com.example.grpc.client;

import com.example.grpc.UserRequest;
import com.example.grpc.UserResponse;
import com.example.grpc.UserServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

public class UserServiceClient {
    public static void main(String[] args) {
        // 创建连接通道
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051)
                                            .usePlaintext()
                                            .build();
        // 创建客户端 stub
        UserServiceGrpc.UserServiceBlockingStub stub = UserServiceGrpc.newBlockingStub(channel);
        // 创建请求
        UserRequest request = UserRequest.newBuilder()
                                 .setId("123")
                                 .build();
        // 调用服务
        UserResponse response = stub.getUserById(request);
        System.out.println("获取到的用户信息:" + response.getName());
        channel.close();
    }
}
代码逻辑
  • 首先,通过 .proto 文件定义了 UserService 服务,其中包含 GetUserById 方法,它接收 UserRequest 并返回 UserResponse
  • 在服务端代码中,UserServiceImpl 继承自 UserServiceGrpc.UserServiceImplBase,重写了 getUserById 方法。该方法创建一个包含用户信息的 UserResponse 对象,并使用 StreamObserver 发送响应。服务通过 ServerBuilder 启动,监听端口 50051。
  • 在客户端代码中,使用 ManagedChannelBuilder 建立到服务端的连接,使用 UserServiceGrpc.newBlockingStub 创建阻塞式客户端 stub。然后构建 UserRequest,调用 getUserById 方法,获取响应并输出用户信息。

五、Motan

1. 简介

Motan 是由新浪微博开源的轻量级分布式服务框架,专注于 Java 开发,提供了服务的注册、发现、调用、负载均衡等功能,帮助开发人员构建高性能、高可用的分布式系统。

2. 架构与特点

  • 架构
    • 服务提供者:将服务信息注册到注册中心,支持 ZooKeeper、Consul 等作为注册中心。服务提供者将自身的服务信息发布,包括服务接口、实现类和端口等信息。
    • 服务消费者:从注册中心获取服务信息,发起服务调用,并支持负载均衡和集群容错,确保服务调用的可靠性和性能。
    • 注册中心:存储服务信息,为服务的注册和发现提供支持,是服务提供者和服务消费者之间的桥梁。
  • 特点
    • 轻量级和易于使用
      • 核心功能简洁实用,易于集成到 Java 项目中,尤其适合 Java 开发的分布式系统。
    • 服务治理功能
      • 提供服务的注册和发现,以及负载均衡和集群容错功能,保障服务的高可用性。

3. 示例代码

接口定义
package com.example.motan.service;

import com.example.motan.model.User;

public interface UserService {
    User getUserById(String userId);
    void updateUser(User user);
}
服务提供者
package com.example.motan.service;

import com.example.motan.service.UserService;
import com.example.motan.model.User;
import com.weibo.api.motan.config.springsupport.annotation.MotanService;
import org.springframework.stereotype.Service;

@MotanService(export = "8002")
@Service
public class UserServiceImpl implements UserService {
    @Override
    public User getUserById(String userId) {
        // 实现 getUserById 服务逻辑,例如从数据库或其他存储中获取用户信息
        User user = new User();
        user.setId(userId);
        user.setName("张三");
        return user;
    }

    @Override
    public void updateUser(User user) {
        // 实现 updateUser 服务逻辑,例如更新数据库中的用户信息
        System.out.println("更新用户信息:" + user.getName());
    }
}
服务消费者
package com.example.motan.consumer;

import com.example.motan.service.UserService;
import com.example.motan.model.User;
import com.weibo.api.motan.config.springsupport.annotation.MotanReferer;
import org.springframework.stereotype.Service;

@Service
public class UserServiceConsumer {
    // 注入 UserService 服务
    @MotanReferer(check = false, directUrl = "localhost:8002")
    private UserService userService;

    public void consumeService() {
        // 调用 getUserById 方法
        User user = userService.getUserById("123");
        System.out.println("获取到的用户信息:" + user.getName());
        // 调用 updateUser 方法
        User newUser = new User("123", "李四");
        userService.updateUser(newUser);
    }

    public static void main(String[] args) {
        UserServiceConsumer consumer = new UserServiceConsumer();
        consumer.consumeService();
    }
}
代码逻辑
  • 在服务提供者端,使用 @MotanService 注解将 UserServiceImpl 作为服务提供者,并将服务导出到端口 8002。@Service 注解将其作为 Spring 管理的 Bean。
  • 在服务消费者端,使用 @MotanReferer 注解注入 UserService 服务。check = false 表示不检查服务是否可用,directUrl 指定服务地址。通过该服务实例,调用服务方法。

六、对比总结

1. 性能

  • HSF
    • 阿里巴巴内部使用,性能出色,尤其在大规模分布式系统中展现出高吞吐量和低延迟,得益于其优化的通信协议和序列化机制,适用于对性能要求苛刻的业务场景。
  • Dubbo
    • 性能良好,在大量并发请求下,通过不同的负载均衡和集群容错策略,能较好地平衡服务的负载和保证服务的稳定性,适合中大规模分布式系统。
  • Thrift
    • 性能方面,通过二进制序列化协议(如 TBinaryProtocol)可达到较高性能,适合对性能有一定要求且跨语言通信的分布式系统。
  • gRPC
    • 凭借 Protocol Buffers 的高效序列化和 HTTP/2 的性能优势,在性能上表现优秀,尤其适合处理大量数据传输和高并发的服务调用。
  • Motan
    • 在 Java 生态系统中,性能较好,能满足一般分布式系统的性能需求,对于简单的分布式架构较为适用。

2. 服务治理

  • HSF
    • 服务治理功能完善,具备丰富的治理能力,包括限流、降级、熔断等高级特性,适合复杂的分布式系统。
  • Dubbo
    • 提供强大的服务治理功能,如服务注册、发现、负载均衡和集群容错,对于大规模分布式系统的服务管理较为成熟。
  • Thrift
    • 服务治理能力相对较弱,更侧重于跨语言通信和服务调用,对于复杂的服务治理需求,可能需要额外的开发。
  • gRPC
    • 服务治理功能相对基础,需要结合其他工具实现更复杂的服务治理功能,如服务注册和发现需要额外的配置。
  • Motan
    • 具备基本的服务治理功能,满足常见的服务注册、发现、负载均衡需求,适合轻量级分布式系统。

3. 跨语言支持

  • HSF
    • 主要服务于阿里巴巴内部,通常用于 Java 环境,对跨语言支持相对较弱。
  • Dubbo
    • 主要为 Java 语言设计,但通过扩展可以支持多语言,不过在跨语言支持上相对不是其主要优势。
  • Thrift
    • 强大的跨语言支持,支持多种编程语言,适用于多语言分布式环境。
  • gRPC
    • 具有出色的跨语言支持,通过 Protocol Buffers 可方便地在不同语言间实现服务接口和消息定义。
  • Motan
    • 主要为 Java 开发,在跨语言方面支持较弱,更适合 Java 生态系统内的分布式系统开发。

4. 开发和使用复杂度

  • HSF
    • 由于其在阿里巴巴内部使用,外部使用可能会受到一定限制,且对于初学者来说,可能需要深入了解阿里巴巴的生态系统,开发和使用相对复杂。
  • Dubbo
    • 对于 Java 开发者相对友好,使用 XML 或注解配置,有一定的学习曲线,但相对易于掌握。
  • Thrift
    • 需要使用 IDL 定义服务接口,开发过程相对复杂,涉及服务端和客户端代码的生成,但一旦掌握,可以高效开发跨语言服务。
  • gRPC
    • 开发过程需要掌握 Protocol Buffers 和相关配置,对于不熟悉该技术的开发者有一定学习成本,但文档和社区支持丰富。
  • Motan
    • 对于 Java 开发者较为简单,使用注解配置,易于集成到 Spring 项目中,开发和使用较为方便。

5. 适用场景

  • HSF
    • 适用于阿里巴巴内部大规模分布式系统,尤其是电商等对性能和服务治理要求极高的场景。
  • Dubbo
    • 适合构建大规模分布式服务架构,尤其是基于 Java 的企业级应用开发。
  • Thrift
    • 适用于跨语言服务调用,多语言开发团队的分布式系统。
  • gRPC
    • 适用于微服务架构,对性能和跨语言有要求的系统,特别是涉及大量数据传输和实时通信的场景。
  • Motan
    • 适用于轻量级分布式 Java 应用,开发简单,适合快速构建分布式服务。

在选择分布式服务接口框架时,需要综合考虑项目的具体需求,包括性能、服务治理、跨语言支持、开发和使用复杂度以及适用场景等多个方面。不同的框架各有优劣,可以根据自己的具体项目需求,如开发语言、系统规模、性能要求、服务治理需求等因素,综合评估和选择最适合自己的分布式服务接口框架。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进一步有进一步的欢喜

您的鼓励将是我创作的最大动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值