远程访问服务

1.远程服务调用

远程服务调用(Remote Procedure Call,RPC)已经存在超过四十年(1990s DCE)

什么是RPC?如何评价某某RPC技术,RPC更好还是REST更好?

RPC轮子:Google gRPC、Facebook Thrift等等 RPC组件库

2 进程间通信

RPC出现的最初目的,就是为了让计算机与调用本地方法一样去调用远程方法。

eg:

public static void main(String[] args){
    System.out.println("hello world");
}

调用上面本地方法时,计算机才处理:

  1. 传递方法参数
  2. 确定方法版本: 根据println签名,确定执行版本。(重载机制,静态解析,或运行时动态分派)
  3. 执行被调方法:
  4. 返回执行结果

进程间通信(Inter-Process Communication,IPC)的解决方法有以下几种:

  1. 管道(Pipe)或者具名管道(Named Pipde):eg:ps -ef | grep java
  2. 信号(Signal):用于通知目标进程有某种事件发生。还可以给进程自身发信号。eg:kill -9 pid
  3. 信号量(Semaphore):两个进程之间同步协作。相当于操作系统提供的一个特殊变量,程序可以在上面waite()和notify操作。
  4. 消息队列(Message Queue):POSIX标准中定义了可用于进程间数量较多的通信的消息队列。进程可以向队列添加消息,被赋予读权限的进程,还可以消费队列中的消息。消息队列克服了信号承载信息量少、管道只能用于无格式字节流以及缓冲区大小受限等缺点,但实时性相对受限。
  5. 共享内存(Shared Memory):允许多个进程访问同一块公共内存空间,这是效率最高的进程间通信形式。操作系统提供了让进程主动创建、映射、分离、控制某一块内存的程序接口。当一块内存被多个进程共享时,各个进程往往会和其他通信机制,譬如与信号量结合使用,来达到进程间同步及互斥的协调操作。
  6. 本地套接字接口(IPC Socket):消息队列与共享内存只适合单机多进程间的通信,套接字接口则是更普适的进程间通信机制,可用于不同机器之间的进程通信。套接字(Socket)起初是有UNIX系统的BSD分支开发出来的,现在已经移植到所有主流的操作系统上。出于效率考虑,当仅限于本机进程间通信是,套接字接口是被优化过的,不会经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答操作,只是简单的将应用层数据从一个进程复制到另一个进程,这种进程间通信方式及本地套接字接口(UNIX Domain Socket),又叫IPC Socket。

2.1 进程间通信成本

早期 RPC 作为IPC的特例。原始分布式时代早期,确实是从应用层面上看可以做到远程调用与本地的进程间通信在编码上完全一致。但这种透明的调用形式反而给程序员带来了通信无成本的假象,因而被滥用,以致于显著降低了分布式的性能。

1987年,在“透明的RPC调用”一度成为主流范式时,Andrew Tanenbaum教授曾发表论文“A critique of The Remote Procedure Call Paradigm”,对透明的RPC范式提出一系列质问:

 2. CONCEPTUAL PROBLEMS WITH RPC

  2.1. Who is the Server and Who is the Client?

  2.2. Unexpected Messages

  2.3. Single Threaded Servers

  2.4. The Two Army Problem

  2.5. Multicast

 3. TECHNICAL PROBLEMS

  3.1. Parameter Marshalling

  3.2. Parameter Passing

  3.3. Global Variables

  3.4. Timing Problems

 4. ABNORMAL SITUATIONS

  4.1. EXCEPTION HANDLING

  4.2. Repeated Execution Semantics 

  4.3. Loss of State

  4.4. Orphans

 5. HETEROGENEOUS MACHINES

  5.1. Parameter Representation

  5.2. Byte Ordering

  5.3. Structure Alignment

 6. PERFORMANCE PROBLEMS

  6.1. Lack of Parallelism

  6.2. Lack of Streaming

   6.2.1. Bad Assumptions

论文的中心观点是:把本地调用与远程调用当作同样的调用来处理,这是犯了方向性的错误,把系统间的调用透明化,反而会增加程序员工作的复杂度。

1994年至1997年间,由ACM和Sun院士Peter Deutsch、套接字接口发明者Bill Joy、Java之父James Gosling等一众在Sun公司工作的专家们共同总结了通过网络进行分布式运算的八宗罪(8 Fallacies of Distributed Computing)

1. The network is reliable. 网络是可靠的
2. Latency is zero. 延迟是不存在的
3. Bandwidth is infinite. 带宽是无限的
4. The network is secure. 网络是安全的
5. Topology doesn't change. 拓扑结构是一成不变的
6. There is one administrator. 总会有一个管理员
7. Transport cost is zero. 不必考虑传输成本
8. The network is homogeneous. 网络都是同质化的

上面的问题说明,如果远程服务调用透明化,就必须为这些罪过买单,这算是给RPC能否等同于IPC来暂时定下了一个具有公信力的结论。

至此,“RPC应该是一种高层次的或者说语言层次的特征,而不是像IPC那样,是此层次的或者系统层次的特征”的观点成为工业界、学术界的主流观点。

20世纪80年代初期(1980s),传奇的施乐Palo Alto研究中心发布了基于Cedar语言的RPC框架---Lupine,并实现了世界上第一个基于RPC的商业应用-----Courier。一般认为RPC的概念最早是由施乐公司提出的。

2.2 三个基本问题

三个基本问题:

  1. 如何表示数据
  2. 如何传递数据
  3. 如何表示方法

如何表示数据

不同操作系统,不同硬件指令集,同样的数据类型也完全有可能有不一样的表现细节,譬如数据宽度、字节序的差异等。

因此需要数据的序列化,反序列化。每种RPC协议都有对应的序列化协议:eg:

  • ONC RPC的外部数据表示( External Data Representation,XDR)
  • CORBA的通用数据表示(Common Data Representation,CDR)
  • Java RMI的Java对象序列化流协议(Java Object Serialization Stream Protocol)
  • gRPC的Protocol Buffers
  • Web Service的XML序列化
  • 众多轻量级RPC支持的JSON序列化

如何传递数据

如何通过网络,在两个服务的Endpoint之间相互操作、交换数据。这里的“交换数据”通常指的是应用层协议,实际传输一般是基于TCP、UDP等标准的传输协议来完成的。

包括,序列化参数、结果、异常、超时、安全、认证、授权、事务等都可能产生交换信息的需求。

计算机科学中使用“Wire Protocol ”(线路协议)来表示这种两个Endpoint之间需要交换数据的行为。

常见的Wire Protocol如下:

Java RMI的java远程消息交换协议(Java Remote Message Protocol,JRMP,也支持RMI-IIOP)

CORBA的互联网ORB间协议(Internet Inter ORB Protocol (IIOP)),是GIOP协议在IP协议上实现的版本)

When GIOP is sent over TCP/IP, it is called Internet Inter ORB Protocol (IIOP). IIOP is designed to allow different ORB vendors to interoperate with one another. An example of this interoperability occurs when there is communication between an enterprise designed ORB, and a smaller real-time application, utilizing a real-time ORB.

DDS的实时发布订阅协议(Real Time Publish Subscribe Protocol,RTPS)

Web Service的简单对象访问协议(Simple Object Access Protocol,SOAP)

如果要求足够坚定,双方都是HTTP Endpoint,直接使用HTTP协议也是可以的(如JSON-RPC)

如何表示方法

eg:

Android的AIDL

CORBA的OMG接口定义语言 OMG IDL

Web Service的Web服务描述语言,WSDL

JSON-RPC的JSON Web服务协议(JSON Web Service Protocol,JSON-WSP)

2.3 统一的RPC

从早期DCE/RPC与ONC RPC 主要在UNIX系统中使用,没有在UNIX系统外大规模流行。

ONC RPC 可以序列化结构体,但结构体毕竟不是对象,这种RPC都是面向C语言的。

然而,20世纪90年代(1990s)正是面向对象编程(Object-Oriented Programming,OOP)风头正盛的年代。1991年对象管理组织(Object Management Gropu,OOP)发布了跨进程、面向异构语言的、支持面向对象的服务调用协议:CORBA 1.0(Common Object Request Broker Architecture),

      CORBA1.0,1.1 只支持C、C++、Java、Object Pascal、Python、Ruby等主流编程语言,还支持Lisp、Smaltalk、Ada、COBOL等非主流语言,阵营不可谓不强大。

     但,CORBA本身设计的太过于繁琐,甚至到荒谬的程度。200行代码,170行废话。另外,制定规范的专家脱落实际,使得CORBA规范晦涩难懂,各家语言的厂商都有自己的解读,导致CORBA实现不互相兼容。

     1998年,XML 1.0发布,并称为W3C (World Wide Web Consortium, W3C)推荐标准。 

     1999年末,SOAP 1.0(Simple Object Access Protocol)规范的发布,标准着“Web Service”的全新RPC协议的诞生。Web Service 是由微软和DevelopMentor公司共同起草的远程服务协议,随后提交给W3C 投票成为国际标准。当时XML是最新的“银弹”。只要是XML的都认为是好东西。微软都主动放弃DCOM,迅速转投Web Service的怀抱。

        Web Service 从技术角度来看,设计的并不优秀,有显著缺陷。

        Web Service的最大的缺点是过于严格的数据和接口定义。

        另外,还有需要客户端解析,XML数据信息量小,传输性能慢。

        Web Service过于贪婪,制定了庞大的协议家族。

2.4 分裂的RPC

朝着面向对象发展:RMI,.NET Remoting

朝着性能发展:gRPC,Thrift

朝着简化方向发展:JSON-RPC

很难有一个框架满足所有需求

最近,RPC框架向更高层次(负责调用远程服务,还管理远程服务)与插件化方向发展趋势,不再追求独立的解决RPC的全部问题(表示数据、传递数据、表示方法),而是将一部分功能设计成扩展点,让用户自己选择。

框架聚集于提供核心的、更高层次的能力,譬如提供负载均衡、服务注册、可观察性等方面的支持。代表有FaceBook的Thrift于阿里的Dubbo。Dubbo默认有自己的传输协议(Dubbo协议),同时也支持其他协议;默认采用Hession2作为序列化器,如果你有JSON需求,可以替换为Fastjson,还可以使用Kryo、FST、Protocol Buffers等效率更好的序列化器,还可以使用JDK自带的序列化器。

3 REST设计风格

3.1 理解REST

 REST源于论文:Architectural Styles and the Design of Network-based Software Architectures

REST概念:

资源

表征(Representation)

状态

转移

一些概念:

统一接口

超文本驱动

自描述消息

3.2 RESTful系统

Fielding 认为,一套理想的,完全满足REST风格的系统应该满足六大原则:

  1. 客户与服务端分离
  2. 无状态
  3. 可缓存
  4. 分层系统
  5. 统一接口
  6. 按需代码

REST提出以资源为风格,可以带来的好处。

降低服务接口的学习成本

资源天然具有集合与层次结构

REST绑定与HTTP协议。

3.3 RMM (Richardson Maturity Model (RMM) )

Richardson Maturity Model. Source: Sandoval. 2018. 

REST (REpresentational State Transfer) is a popular architectural style for designing web services to fetch or alter remote data. APIs conforming to the REST framework are considered more mature because they offer ease, flexibility and interoperability. Richardson Maturity Model (RMM) is a four-level scale that indicates extent of API conformity to the REST framework.

After analysing hundreds of web service designs, Leonard Richardson comes up with a model that helps to distinguish between good and bad designs. His yardstick is purely based on API maturity. This model is popularly referred to as Richardson Maturity Model (RMM).

RESTful architecture. Source: Vincy 2019

  1. Level-0: Swamp of POX(POX stands for Plain Old XML:Least conforming to REST architecture style. Usually exposes just one URI for the entire application. Uses HTTP POST for all actions, even for data fetch. SOAP or XML-RPC-based applications come under this level. POX stands for Plain Old XML.
  2. Level-1: Resource-Based Address/URI: These services employ multiple URIs, unlike in Level 0. However, they use only HTTP POST for all operations. Every resource is identifiable by its own unique URI, including nested resources. This resource-based addressing can be considered the starting point for APIs being RESTful.
  3. Level-2: HTTP Verbs: APIs at this level fully utilize all HTTP commands or verbs such as GET, POST, PUT, and DELETE. The request body doesn’t carry the operation information at this level. The return codes are also properly used so that clients can check for errors.
  4. Level-3: HyperMedia/HATEOAS: Most mature level that uses HATEOAS (Hypertext As The Engine Of Application State). It's also known as HyperMedia, which basically consists of resource links and forms. Establishing connections between resources becomes easy as they don’t require human intervention and aids client-driven automation.

  • RMM Level 0的特点是什么?

    RMM级别 0 相当于使用SOAP。资料来源:Lithmee 2018。 

    此级别的API通常由将服务器对象和接口名称传递给服务器对象的函数组成。这是使用 HTTP post 方法以 XML 格式发送到服务器的。服务器分析请求并将结果以 XML 格式发送给客户端。如果操作失败,回复正文中将出现某种错误消息。

    在级别 0,您可以在客户端和服务器之间进行实际的对象传输,而不仅仅是其表示状态(如在 REST 中)。对象数据可以是标准格式(JSON、YAML等)或自定义格式。如果是SOAP,则规范在随附的 WSDL 文件中。

    HTTP 以非常原始的方式使用,就像客户端自己的远程操作的隧道机制一样。为此,也可以使用其他应用层协议,例如 FTP 或 SMTP。

  • RMM Level 1的特点是什么?

    级别 1 使用了几个URI,每个URI都指向一个特定的资源并充当服务器端的入口点。这方面在 2 级和 3 级中很常见。

    请求被传递到相关的特定资源,而不是简单地在每个单独的服务端点之间交换数据。虽然这似乎是一个很小的差异,但就功能而言,这是一个关键的变化。特定对象的标识被建立和调用。只有与其功能和形式相关的参数才会传递给每个对象/资源。

    从这个级别开始,它始终是 HTTP 作为应用层协议。在这个级别中,与 REST 的主要不符合之处在于无视 HTTP 的语义。只有 HTTP POST 用于所有CRUD(创建、读取、更新、删除)操作。返回状态和检索到的数据都在 HTTP 响应的正文中。

  • RMM Level 2的特点是什么?

    这个级别的重点是正确使用各种 HTTP 动词,尤其是 GET 和 POST 命令的使用。通过将所有获取操作限制为 GET,应用程序可以保证服务器端数据的安全性和神圣性。在客户端缓存数据以加快访问速度也成为可能。

    该模型将此级别称为HTTP Verbs。这意味着只能使用 HTTP。实际上,REST 架构模式并没有强制要求使用 HTTP。它完全是协议中立的。

    在服务器端,应该正确使用 200 系列的 HTTP 响应代码来表示成功,并使用 400 系列来表示不同类型的错误。

    总而言之,级别 1 和级别 2 在较高级别(例如在服务器日志中)阐明了正在访问哪些资源、出于什么目的以及发生了什么。

    这里的不符合是相当微妙的。此级别的API缺乏客户端在没有一些外部知识的情况下导航应用程序状态的透明度,通常存在于提供的文档中。

  • RMM Level 3的特点是什么?

    为了确保完全符合 RESTful 架构,最后的障碍是在整个应用程序中为客户端提供流畅的导航。这是通过超媒体控件支持的。

    HTTP 响应包括API访问的资源的一个或多个逻辑链接。这样,客户就不必依赖外部文档来解释内容。因为响应是不言自明的,它鼓励容易发现。这是使用HATEOAS(超文本作为应用程序状态引擎,Hypermedia as the Engine of Application State, HATEOAS)完成的,它在服务器和客户端之间形成了一个动态接口。

    一个明显的好处是它允许服务器在不破坏客户端的情况下更改其URI方案。只要面向客户端的URI保持完整,服务器就可以在内部处理其资源和内容,而不会影响客户端。应用程序中的服务更新变得无缝而不会中断服务。

    它还允许服务器团队通过将新链接放入客户端可以发现的响应中来通知客户端新功能。

  • 什么样的应用程序仍然可以使用 Level 0 或 Level 1 API设计?

    许多在线服务属于RMM 0 级或 1 级,例如 Google 搜索服务(现已弃用)和基于 Flash 的网站。然而,这些正在慢慢被淘汰。

    如果你正在设计一个只执行一个主要功能的单体服务,那么可能一个 0 级API就足够了,因为不需要多个URI。级别 0 足以用于不打算扩展或升级的短期服务。以考试成绩申报网站为例。它的使用仅限于结果出来的几天。在那之后,它就会变得不活跃和无关紧要。Web 服务只执行一个功能:它返回单个学生的通过/失败状态。如果要访问一些资源,则可以使用级别 1。

    在不需要 HTTP 作为传输机制的情况下,使用SOAP编写的0 级API甚至可以使用FTP 或 SMTP 等替代方案。

  • 什么时候需要在 Level 2 或 Level 3 设计API?

    Web 产品和解决方案越来越多地使用SaaS分发模型进行部署。为了支持这一点,设计和制造模型也是面向服务的 ( SOA )。它们被暴露为微服务,一个松散耦合的服务集合。

    流行的基于云的订阅,如移动服务、流媒体网络内容、办公自动化产品,都以这种方式部署。此类服务本质上是使用符合模型第 2 级和第 3 级的API设计的。

    应用程序选择 2 级或 3 级是因为一些额外的因素——(1) 需要在有限的带宽和计算资源的情况下工作 (2) 依靠缓存来获得更高的性能 (3) 无状态操作

    像SOAP这样的低级API可以比作信封。它里面有内容,外面写着地址。内容从公众视野中隐藏。但是,符合 REST 的更高级别就像一张明信片。整个内容直接可见。尽管这方面带来了很多透明度和可发现性,但存在与数据相关的安全威胁感知。应用程序使用诸如 Spring 框架中的安全特性来处理这方面的问题。

  • 用于判断远程应用程序接口设计标准的其他模型有哪些?

    Richardson 成熟度模型根据与 REST 框架的一致性对具有不同API成熟度级别的应用程序进行分类。

    还有另一个模型叫做Amundsen Maturity Model,它根据API的数据模型抽象对API进行分类。在此模型的更高级别,API与内部模型或实现细节更加分离。

代码示例:

Level-0

<!--
Query employee data for a given employee last name
Request: Use of a single URI for all actions
POST /employeeData HTTP/1.1
-->
<empRequest lastName = "jones"/>
 
<!--
Response: Success status with 3 employee records retrieved
HTTP/1.1 200 OK
-->
<empList>
    <emp firstName = "Dean">
        <empId = "232"/>
        <joinDate = "2010-01-04"/>
    </emp>
    <emp firstName = "Mark">
        <empId = "1129"/>
        <joinDate = "2014-06-05"/>
    </emp>
    <emp firstName = "Lucy">
        <empId = "2132"/>
        <joinDate = "2017-03-08"/>
    </emp>
</empList>

第0级是RPC风格,如上使用一个URI对应全部的资源操作。

Level-1

<!--
Request: Every employee is accessible by a unique URI, with employee ID
POST /employeeData/2132 HTTP/1.1
-->
 
<!--
Response: Success status with 1 employee record retrieved
HTTP/1.1 200 OK
-->
<emp firstName = "Lucy">
    <empId = "2132"/>
    <joinDate = "2017-03-08"/>
</emp>

通过资源ID作为主要线索与服务交互

Level-2

<!--
Request: Query uses the appropriate GET command
GET /employeeData?empId=2132 HTTP/1.1
-->
 
<!--
Response: Success status with 1 employee record retrieved
HTTP/1.1 200 OK
-->
<emp firstName = "Lucy">
    <empId = "2132"/>
    <joinDate = "2017-03-08"/>
</emp>

把不同业务需求抽象为对资源的增加、修改、删除等操作;

使用Http协议的Status Code,它可以涵盖大多数资源操作可能出现的异常,也可以自定义扩展

依靠HTTP Header中携带的额外认证、授权信息来解决

Level-3

<!--
Request: Response contains direct access link to resource
GET /employeeData?firstName=Lucy HTTP/1.1
-->
 
<!--
Response: Success status with direct link to 1 employee record retrieved
HTTP/1.1 200 OK
-->
<emp firstName = "Lucy">
    <empId = "2132"/>
    <joinDate = "2017-03-08"/>
    <link url = "/empData/2132"/>
</emp>

支持“超文本驱动”,包含可以直接访问资源的链接。

Richardson Maturity Model

3.4 不足与争议

  1. 面向资源的编程思想只适合CRUD,面向过程、面向对象编程才能处理真正复杂的逻辑
  2. REST不利于事务支持
  3. REST没有传输可靠性支持
  4. REST缺乏对资源进行“部分”和“批量”处理的能力。

面向资源的编程思想只适合CRUD,面向过程、面向对象编程才能处理真正复杂的逻辑

Http的4个基础命令POST、GET、PUT和DELETE容易让人联想到CRUD操作。

用户还可以使用自定义方法,按Google推荐的REST API 风格,自定义方法应该放在资源的路径末尾,嵌入冒号家自定义动词的后缀。

e.g.:

POST /user/user_id/cart/book_id:undelete

面向资源的编程思想与另外两种主流编程思想只是抽象问题时所处的立场不同,只有选择不同,没有高下之分。

  面向过程编程时,为什么要以算法和处理过程为中心,输入数据,输出结果?

     当然是为了符合计算机世界中的主流的交互方式。

  面向对象编程时,为什么要将数据和行为统一起来、封装成对象?

     当然是为了符合现实世界的主流交互方式。

  面向资源编程时,为什么要将数据(资源)作为抽象的主体,把行为看作统一的接口?

     当然是为了符合网络世界的主流交互方式。

REST不利于事务支持

不支持2PC/3PC,譬如WS-Atomic Transaction、WS-Coordination这样的功能。Web Service是比较好的选择。

如果事务只是希望保障数据的最终一致性,放弃刚性事务,使用REST没有什么阻碍。

REST没有传输可靠性支持

应对传输可靠性最简单粗暴的做法是把消息在重发一遍。

这种简单的做法可以成立的条件是服务应该具有幂等性。Http协议要求GET、PUT、和DELETE应具有幂等性。

对POST的重复提交,浏览器会出现相关警告。对服务端,应该做预校验,如果发现重复返回HTTP/1.1 425 Too Early。

Web Service中ReliableMessaging功能协议用于支持消息的可靠投递。

REST没有采用额外的Wire Protocol,所以除了事务、可靠传输这些功能以外,一定还可以在WS-*协议中找到很多REST不支持的特性。

REST缺乏对资源进行“部分”和“批量”处理的能力。

部分:

e.g.如果仅仅想获取用户姓名,如果是RPC风格可以设计“getUsernameById”的服务,返回一个字符串。

如果是REST风格,将向服务请求整个用户对象,然后丢弃其他属性。这是一种过度获取(Overfetching)

REST的应对手段是通过位于中间节点或客户端的缓存来解决这种问题。但此缺陷的本质是由于HTTP协议完全没有对请求资源结构化描述的能力,所以返回资源的那些内容,什么数据类型返回等,都不可能得到协议层面的支持,要做只能自己在GET方法的Endpoint上设计各种参数来实现。

批量:

如果是批量操作1000个用户,需要调用1000次PUT,流量器会返回HTTP/1.1 429 Too Many Requests。

此时不得不创建一个任务资源,把1000个用户提交给这个任务,让任务执行。

有譬如 去网店买东西,下单、冻结库存、支付、加积分、扣减库存这一系列任务步骤会涉及多个资源的变化,不得不创建一种“事务”的抽象资源,或者用某种具体的资源(譬如“结算单”)贯穿这个过程的始终,每次操作其他资源是都带上事务或者结算单的ID。HTTP协议的无状态性,会相对不适合(并非不能够)处理这类业务场景。

目前,一种理论上可以解决上面几类问题的解决方案是GraphQL,它是Facebook提出并开源的一种面向资源API的数据查询语言。如同SQL一样,挂上“查询语言”的名字,其实CRUD都做。

比起依赖HTTP无协议的REST,GraphQL是另一种有协议的、更彻底地面向资源的服务方式。单它又面临几乎所有RPC框架所遇到的那个如何推广交互接口的问题。

参考:

What is CORBA? | Introduction to CORBA | Products

https://en.wikipedia.org/wiki/Distributed_Computing_Environment

Richardson Maturity Model

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值