分布式系统和高可用架构设计方案

目录

分布式系统

RPC 的工作原理

分布式数据存储

分布式锁

降级、熔断、限流

链路追踪

系统优化和故障处理


分布式系统

传统单体服务架构代码数量庞大,牵一发而动全身,一个很小的改动都可能影响整个服务。正所谓不要把所有的鸡蛋装在一个篮子里,代码和数据库不在一个项目里,尽可能的避免了冲突和改动带来的风险。随着集群规模大了之后,很有可能出现集群宕机和磁盘损坏,分布式系统就是将大的服务拆分成很多个微小的服务,将数据库(存储介质)和代码(计算能力)分布到不同的服务器上,目的在于解决单台机器计算和IO性能问题,以及单机存储空间不足的问题。微服务是分布式系统的一种具体落地方案。阿里的Dubbo和Spring Cloud都是解决分布式微服务架构的优秀框架。

分布式系统有以下的优点:

  • 提升系统可用性:传统的单体架构或集中式存储,如果遇到单点故障时很容易造成整个服务不可用;分布式下的微服务体系,就算出现了单台机器故障,也不致于造成整个服务不可用。
  • 提升系统并发能力:使用Nginx负载均衡可以将请求分发到不同的服务器上,并且可以根据用户访问量的增长随时增加机器,实现水平扩展;同样的,数据库也可以做成分布式集群。
  • 提升系统容错能力:对同一组服务部署在不同地方的机房,不仅能通过DNS加快响应速度,也能防止万一某个机房突发断电,流量可以自动分发到其它机房,不影响用户体验。

分布式系统有以下的缺点:

  • 分布式服务依赖网络:各个微服务之间的通讯依赖网络,如果因为网络延时、丢包、中断等问题,都可能造成本次请求失败。
  • 维护成本高:传统的单体服务只需要维护一个项目就可以,分布式服务系统被拆分后,会增加运维成本,相当于把服务内的压力转移到了服务间的压力。
  • CAP问题:在分布式系统中,服务的 一致性(C)、可用性(A)、分区容错性(P) 无法同时满足,最多只能满足两种,需要根据实际情况去调整牺牲掉其中哪个。

由于网络通信之间是不可靠的,数据交互很可能会有延迟和数据丢失,因此分区容错性(P)是前提,接下来要么选择一致性,保证数据绝对一致;要么选择可用性,保证服务可用。比如下面所示的,节点A实时同步数据到它的副本节点A1,如果由于网络问题同步失败了,那么客户端从节点A1就读取不到数据了。

RPC 的工作原理

RPC (Remote Procedure Call,远程过程调用),指的是通过网络调用另一台计算机上部署服务的技术。而 RPC 框架就封装了网络调用的细节,让你像调用本地服务一样,调用远程部署的服务。RPC的基本原理如下图所示:

   

  • 服务集成 RPC 后,服务提供者(Provider) 启动后会通过 注册模块(register),把服务的 唯一ID、IP地址、端口 等信息注册到 RPC 框架注册中心。(图中
    的 Registry 部分)
  • 调用者(Consumer) 想要调用服务的时候,通过 Provider 注册时的 服务唯一ID 去注册中心查找可供调用的服务,返回一个 IP列表(3.notify 部分)。
  • Consumer 根据一定的策略(比如随机或者轮询),从 Registry 返回的可用 IP 列表真正调用服务(4.invoke部分)。
  • RPC 框架都会提供统计和监控功能,监控服务健康状况,控制服务线上扩展和上下线(5.count部分)

【问】如果服务提供者挂了,注册中心怎么知道服务不可用了?

【答】服务掉线分为主动下线和心跳检测。主动下线通知:如果服务要发版,在重启之前先主动通知注册中心:我要重启了,有流量进来先不要分给我,等我重启成功后再放流量进来;
心跳检测:如果服务非正常下线(如断电断网),这个时候注册中心可能不知道该服务已经掉线了,一旦调用就会带来问题。为了避免出现这样的情况,注册中心可以增加一个心跳检测功能,它会对服务提供者(Provider)进行心跳检测,比如每隔 30s 发送一个心跳,如果三次心跳结果都没有返回值,就认为该服务已下线,赶紧更新 Consumer 的服务列表,告诉Consumer 调用别的机器。

【追问】服务提供者(Provider)挂了可以依靠注册中心解决,如果注册中心(比如Zookeeper)自己挂了怎么办?

【答】 ZK本身就是集群部署的,如果一台机器挂了,ZK 会选举出集群中的其他机器作为 Master 继续提供服务;如果整个集群都挂了也没问题,因为调用者本地会缓存注册中心获取的服务列表,会省略和注册中心的交互,Consumer 和 Provider 采用直连方式,这些策略都是可配置的。

【问】已经有 http 协议接口,或者说 RestFul 接口,为什么还要使用 RPC 技术?
【答】在接口不多的情况下,使用 http 确实是一个明智的选择。使用 http 协议优点:开发简单、测试直接、部署方便。当业务逐渐变大,并发量上升,RPC 框架的好处就显示出来了:首先 RPC 支持长链接,不需要3次握手,减少了网络开销。其次就是 RPC 框架一般都有注册中心模块,有完善的监控管理功能,服务注册发现、服务下线、服务动态扩展等都方便操作,服务化治理效率大大提高。

分布式数据存储

一般的关系型数据库(如MySQL),当单表数据在 1000万左右的时候,为了提高查询的性能,就该考虑分表了。如果业务数据量增长的比较快,就要提前预估现有单库单表的数据量读写速度能支撑多久,提前规划时间改造。

常见的拆分策略如下:

  • 对 key 取模: 比如根据 userId 把 3000万的用户数据 最终分 6 张表,可以哈希后再取模 Hash(userId)% n ,此种方法数据分布较均匀;但是后期如果取模的值变了会很麻烦,因此建议参考一致性哈希算法,点这里查看:MySQL分区分库分表和分布式集群
  • 根据数据范围拆分:userId 从 0 - 500万 一张表,5000001 - 1000万 一张表,依此类推。这个方法在增量表场景中会造成数据分布不均匀,而且根据其它字段查询时效率较低,因此不太推荐;
  • 时间分区:适用于主要通过时间查询的数据拆分,比如按天分表、按月分表、按年分表,时间越久的数据被查询的概率就会越低,类似冷热数据分离。其实很多APP查询历史订单的时候默认只能查询最近一年的,如果要查询更早的,需要手动点击“查询上一年”,就是这个道理。
  • 按照其它字段分区:根据自己的业务场景,可以指定某个字段来拆分,和上面的逻辑类似。
  • 建议单个物理分表的容量不超过 1000 万行数据。

分库分表后会带来哪些问题:

  • 分布式 ID 问题:在分库分表后,不能再使用 MySQL 的自增主键,因为在插入记录的时候,不同的库生成的自增 ID 会冲突,因此需要有一个全局的 ID 生成器。推荐使用雪花算法搭建发号器。
  • 分布式事务问题:因为涉及到了同时更新多个数据库,如何保证要么同时成功,要么同时失败。关于分布式事务,MySQL 支持 XA 事务,但是效率较低。柔性事务是目前比较主流的方案,柔性事务包括:最大努力通知型、可靠消息最终一致性方案以及 TCC 两阶段提交。但是无论 XA 事务还是柔性事务,实现起来都是非常复杂的。
  • 分库分表后的 Join 操作:一般情况下,分库分表后就不能再和单库单表一样进行 Join 操作,应尽量避免这样的查询,可以采用字段冗余,这样有些字段就不用 join 查询了。

分库分表常用中间件:MyCAT、TDDL、Sharding 系列(包括 Sharding JDBC,Sharding-Proxy,Sharding-Sidecar)。

另外,分布式数据存储还会涉及关于Redis和消息队列的使用,我单独整理了两篇文章,点击查看:Redis面试核心技术点和缓存相关问题_浮尘笔记的博客-CSDN博客Redis单线程有一个最大好处就是 节省线程切换的开销,更不用考虑并发读写带来的复杂操作场景,大大节省了线程间切换的时间。单线程模型避免了多线程的频繁上下文切换,这也避免了多线程可能产生的竞争问题。Reids 核心是基于非阻塞的 IO 多路复用机制。优先查询缓存,如果缓存未命中则查询数据库,将结果写入缓存;数据更新时先更新数据库,再删除缓存,然后一段时间后再延迟删缓存(防止并发场景下操作出现问题)。https://blog.csdn.net/rxbook/article/details/131036867

消息队列kafka使用技巧和常见问题_浮尘笔记的博客-CSDN博客消息队列主要解决应用耦合、异步消息、流量削锋等问题,是大型分布式系统不可缺少的中间件。消息生产者 只管把消息发布到 MQ 中而不用管谁来取,消息消费者 只管从 MQ 中取消息而不管是谁生产的,这样生产者和消费者都不用知道对方的存在。对于一些流转步骤较多,或者耗费时间过长的场景,就可以使用消息队列。比如用户下订单成功后,可以通过消息队列异步发送信息,异步处理赠送积分等等。https://blog.csdn.net/rxbook/article/details/131053014

分布式锁

分布式锁是解决分布式系统之间同步访问共享资源的一种方式。在分布式环境下,多个客户端同时更新某个服务端资源的时候,防止出现问题,可以加上分布式锁,在同一时刻只能有一个客户端拿到锁,然后更新数据,其它客户端需要等待之前的客户端释放锁之后才能操作。分布式锁的特点是多进程,多个物理机器上无法共享内存,常见的解决办法是基于内存层的干涉,落地方案就是基于 Redis 的分布式锁 或者 ZooKeeper 分布式锁。

怎么设计分布式锁?需要从以下方面考虑:

  • 可用性问题:无论何时都要保证锁服务的可用性;
  • 死锁问题:客户端一定可以获得锁,需要考虑如果锁住某个资源的客户端在释放锁之前崩溃或者网络不可达的情况下的解决方案;
  • 集群同步时产生的数据不一致,导致新的进程有可能拿到锁,但之前的进程以为自己还有锁,那么就出现两个进程拿到了同一个锁的问题。

可以使用下面几种方式实现分布式锁:

(1)使用关系型数据库(如MySQL)实现分布式锁,性能一般。

先查询数据库是否存在记录,为了防止幻读,通过数据库行锁 select for update 锁住这行数据,然后将查询和插入的SQL在同一个事务中提交。比如下面的SQL语句:

select name from user where user_id = 100 for update

使用SQL行锁可能会出现交叉死锁,比如事务1和事物2分别获取到了记录1和记录2的排他锁,然后事务1又要申请获取记录2的排他锁,事务2要申请获取记录1的排他锁,那么这两个事务就会因为相互锁等待产生死锁。

当然可以通过超时控制解决交叉死锁的问题,但是在高并发场景下会出现大部分请求排队等待的情况,所以这个方案在性能上存在缺陷。关于MySQL锁的更多内容请查看:深入理解MySQL中的事务和锁

(2)使用分布式缓存(如Redis)实现分布式锁,性能不错。 

由于Redis是在内存中运行,效率远高于MySQL,使用基于Redis的分布式锁可以避免大量请求直接访问数据库,从而提高系统的响应能力。如下面的命令:

SET my_key my_value NX PX 3000
#my_value 是客户端生成的唯一的标识, NX代表只在 my_key 不存在时才设置 my_value
#PX 3000 表示设置my_key的过期时间为3s,防止客户端发生异常而导致无法释放锁

Redis如何解决集群模式分布式锁的可靠性:假设目前有N个独立的Redis实例,客户端先按顺序依次向N个Redis实例执行加锁操作,这里的加锁操作和在单实例上执行的加锁操作一样,但是Redlock 算法设置了加锁的超时时间,为了避免因为某个Redis实例发生故障而一直等待的情况。客户端完成了和所有Redis实例的加锁操作之后,如果有超过半数的Redis实例成功的获取到了锁,并且总耗时没有超过锁的有效时间,那么就是加锁成功。

Redlock是redis官方实现的分布式锁(Redis Distributed Lock),使用场景:多个服务间保证同一时刻同一时间段内同一用户只能有一个请求(防止关键业务出现并发攻击);这个锁的算法实现了多redis实例的情况,相对于单redis节点来说,优点在于 防止了 单节点故障造成整个服务停止运行的情况;并且在多节点中锁的设计,及多节点同时崩溃等各种意外情况有自己独特的设计方法。Redlock(redis分布式锁)原理分析

(3)使用Zookeeper实现分布式锁。

​一台机器接收到了请求之后,先获取 zookeeper 上的一把分布式锁(zk 会创建一个 znode)执行操作;然后另外一台机器也尝试去创建那个 znode,结果发现自己创建不了,因为被别人创建了,那只能等待,等第一个机器执行完了才能拿到锁。更多资料可以参考:zookeeper实现分布式锁

降级、熔断、限流

在分布式系统中,很多服务之间有依赖关系,并且可能有一定的调用顺序,如果其中有一个环节出现问题,就会带来一系列连锁反应。比如,突如其来的流量导致部分服务突然宕机,会不会影响到整个服务都不可用?针对这样的情况应该怎么解决?思路就是尽量给每个服务找一个备用方案。

比如有个电商平台中有商品、促销、积分 三个系统,出现流量高峰期时,虽然商品系统很容易扩容,但对于商品系统依赖的其他服务,就不会有实时性的响应。那么促销或积分系统就可能因为无法承担大流量,请求处理缓慢,直到所有线程资源被占满,无法处理后续的请求。

在分布式系统中,当检测到某一个系统或服务响应时长出现异常时,要想办法停止调用该服务,快速返回失败,从而释放此次请求持有的资源,这就是架构设计中经常提到的降级和熔断机制。假设某个系统因为一个热点事件突然涌入了大量的请求,导致QPS剧增,服务无法响应。这个时候可以从以下几个方面考虑:

限流包括单机限流和集群限流,给核心系统的某一环节加一个开关。比如将系统 QPS 控制在最高 2000,后面的 1000 用户告诉他 “系统繁忙,请稍后再试”,这样一来最起码前面的2000次请求可以正常响应,后面的响应排队处理,而不至于让所有用户都无法使用系统。

降级:当资源和访问量出现矛盾时,在有限的资源下放弃部分非核心功能或者服务,保证整体的可用性。降级的实现手段是:在请求流量突增的情况下,放弃一些非核心流程或非关键业务,释放系统资源,让核心业务正常运行,或者对非核心业务采用备用方案。比如商品列表查询,默认查询的是 Redis 集群,各种故障赶在一起,Redis 所有集群都挂了不能用了,这个时候可以计一个备用方案,比如使用 Elasticsearch来返回查询结果,虽然查询速度可能没 Redis 快,但最起码还能用。

(1)服务降级:取舍非核心服务;以及把强一致性转换为最终一致性。

  • 读操作降级:做数据兜底服务,将兜底数据提前存储在缓存中,当系统触发降级时,读操作直接降级到缓存,从缓存中读取兜底数据,如果此时缓存中也不存在查询数据,则返回默认值,
    不在请求数据库。
  • 写操作降级:将之前直接同步调用写数据库的操作,降级为先写缓存,然后再异步写入数据库。

(2)功能降级:产品功能上的取舍,可以通过开关控制某些非核心功能不可用。

  • 一般是通过参数化配置的方式存储在配置中心,手动或自动开启开关,实现系统降级。

熔断: 熔断也是降级的一种手段。在服务A调用服务B时,如果B返回错误或超时的次数超过一定阈值,服务A的后续请求将不再调用服务B。这种设计方式就是断路器模式。

断路器实现熔断的设计原理:服务调用方为每一个调用的服务维护一个有限状态机,在这个状态机中存在三种状态:(1)关闭:正常调用远程服务; (2)半打开:尝试调用远程服务; (3)打开:直接返回错误,不调用远程服务。

  • 关闭 --> 打开: 当服务调用失败的次数累积到一定的阈值时,服务熔断状态从关闭切换到打开;
  • 打开 --> 半打开: 当熔断处于打开状态时,会启动一个超时计时器,当计时器超时后,状态切换到半打开;
  • 半打开 --> 关闭: 在熔断处于半打开状态时,请求可以达到后端服务,如果累计一定成功次数后,状态切换到关闭;

在秒杀和抢购场景下,或者容易被爬虫的页面,可以使用限流来限制并发请求量,如果没有配置限流,在遇到上游系统频繁调用的情况下可能会导致下游系统被击垮。如果配置了限流,但是限流算法不合理,也有可能导致正常访问被拒绝。所以限流算法不能乱用,要充分评估系统是否需要限流,如果要限流,流量大小如何评估。使用限流的目的就是为了保证服务正常运行,也是为了下游系统不会轻易被拖垮,流量合理放行。

常用的限流算法:

  •  计数器方法:设定一个固定时间窗口,比如 1min或1h,设置一个计数器,统计单位时间内一个请求的访问量,超过计数器最大值的请求放入等待队列或直接拒接访问,这种方法简单粗暴,但是易造成突刺现象。
  • 漏斗算法:可以理解成一个固定容量的漏桶,不管上游流量多还是少,我都按照常量固定速率流出水滴,如果流入水滴超出了桶的容量,就让水溢出。这种算法优点是稳定速率,缺点是无法面对突发流量。

  • 令牌桶算法:每次请求需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择等待可用的令牌,或者直接拒绝。优点是系统发放令牌的速率是可变的,能够面对突发流量,缺点是实现起来有点复杂。

对于核心服务限流的值可以通过以下方法来设置合理的值:

  • 观察评估法:观察系统的 QPS数据,看看流量环比最大值,最小值,平均值,是个很好的参考;
  • 压力测试法:在晚上业务低峰期,看看系统能承受的最大 QPS,同时,限流还有和重试、降级、熔断等作为组合方法一起使用。

断路器Hystrix:是一个用于处理分布式系统的延迟和容错的一个开源库,能保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的稳定性。更多用法参考:Hystrix - 简书

链路追踪

对于复杂的分布式系统,一个请求可能调用几十到几百个服务,经过很多业务层,而每个业务又是多个机器集群,一个请求具体被随机到哪台机器上又无法确定,如果最后用户的请求失败,只返回一个错误提示,作为开发人员,该如何定位解决问题?

分布式系统中针对上述问题,需要一套链路追踪系统来解决,这个系统主要的任务就是收集各服务的上报日志,然后分析并存储,在需要的时候方便查询。关键核心在于调用链,为每个请求生成全局唯一的 ID(Traceld),通过 Traceld 将不同系统的调用信息关联在一起。通过这个链路追踪系统,可以清楚的知道服务调用深度、涉及服务个数、每个服务调用的时间及状态,到底是哪个服务出现异常,可以具体到方法名。

成熟的调用链开源工具:

  • Google Dapper:开始是一个自包含的跟踪工具,后来发展成为一个监控平台,具有高性能,代码侵入性低,支持集群扩展特性。dapper 处理日志分为 3 个阶段:(1)各个服务将日志数据写到本机日志上;(2) dapper 守护进程进行拉取日志文件,将文件读到 dapper 收集器里;(3) dapper 收集器将结果写到 bigtable 中,一次跟踪被记录为一行。
  • 鹰眼(EagleEye):是阿里巴巴的分布式调用跟踪系统,通过收集、存储、分析 分布式系统中的调用事件参数,协同开发人员进行故障定位,容量预估,性能瓶颈定位,系统请求链路梳理等,EagleEye 也是基于 Google Dapper 的设计思想。
  • MTrace:是美团内部的分布式会话跟踪系统,也借鉴了 Google Dapper,通过一个全局ID 将分布在各个服务节点上的同一次请求串联起来,还原原有的调用关系、追踪系统问题、分析调用
    数据、统计系统指标,MTrace 支持美团内部 RPC 中间件,HTTP 中间件,MySQL,Tair,MQ 等中间件的数据埋点。

系统优化和故障处理

一般情况下会从以下维度去衡量系统的性能优劣:

  • 接口响应时间: 有 AVG、TOP99、MAX 等多个指标,衡量一段特定时间内不同程度的请求处理时长;
  • 吞吐量: 反映单位时间内处理请求的能力,有 QPS、TPS 等多个指标,衡量单位时间内能够处理请求的数量;
  • 延迟:从客户端发送请求到接收响应的时间;
  • 资源消耗 :有 CPU 使用率、平均负载、磁盘 I/O 等多个指标;
  • 错误率: 一个系统的吞吐量或者响应时间的提升不能以错误率上升为代价。

全链路分析系统性能的指标:

  • DNS解析:用户在浏览器输入URL按回车,请求会进行DNS查找,浏览器通过DNS解析查到域名映射的IP地址,查找成功后,浏览器会和该IP地址建立连接。对应的性能指标为: DNS解析时间。
  • TCP连接:由于HTTP是应用层协议, TCP是传输层协议,所以HTTP是基于TCP协议基础上进行数据传输的,所以可以用TCP的连接时间来衡量浏览器与Web服务器建立的请求连接时间。
  • 服务器响应:服务器端的延迟和吞吐能力,可以细分为基础设施性能指标、数据库性能指标、系统应用性能指标。
    1. 基础设施性能指标:主要针对 CPU利用率、磁盘IO、网络带宽、内存利用率等;
    2. 数据库的性能指标:主要有SQL查询时间、并发数、连接数、缓存命中率等;
    3. 系统应用性能指标:和系统业务有关,因为业务场景影响架构设计。
  • 前台渲染:当浏览器与Web服务器建立连接后,就可以进行数据通信了。
    1. 由于浏览器自上而下显示HTML,同时渲染顺序也是自上而下的,所以当用户在浏览器地址栏输入URL按回车,到他看到网页的第一个视觉标志为止,这段白屏时间可以作为一个性能的衡量指标,优化手段为减少首次文件的加载体积。
    2. 首屏时间是指:用户在浏览器地址输入URL按回车,然后看到当前窗口的区域显示完整页面的时间。一般情况下,一个页面总的白屏时间在2秒以内,用户会认为系统响应快,2~5秒,用户会觉得响应慢,超过5秒很可能造成用户流失。

系统优化的思路:

  • 提升服务器硬件性能:也就是服务器扩容,简单粗暴有效,增加 CPU 的核心数量,增大内存,增加机房数量和服务器数量。
  • 代码优化:首先在代码中尝试找出性能瓶颈,再来考虑具体的优化策略。有一些性能问题,完全是由于代码写的不合理,通过直接修改一下代码就能解决问题,如线程使用不合理,造成线程大量阻塞,是通过代码优化的关键点。
  • 数据库和缓存优化:比如慢查询优化、分库分表等。具体策略可以参考:MySQL优化方案和explain详解_浮尘笔记的博客-CSDN博客

系统可用性指标:

如果要设计一个系统可用性达到4个9(99.99%)的监控报警体系,需要从以下三个方面考虑:

(1)基础设施监控,判断系统的基础环境是否为高可用。

  • 系统指标:CPU、内存、磁盘。
  • 网络指标:宽带、网络IO、CDN、DNS、安全策略、负载均衡。

监控工具常用的有ZABBIX、Open-Falcon、 Prometheus,这些工具基本都能监控所有系统的CPU、内存、磁盘、网络带宽、网络IO 等基础关键指标,再结合一些运营商提供的监控平台,就可以覆盖整个基础设施监控。

监控报警策略一般由时间维度、报警级别、阈值设定三部分组成,假设系统的监控指标有CPU、内存、磁盘,监控的时间维度是分钟级,监控的阈值设置为占比。那么可以定义出如下的监控报警策略:

(2)系统应用监控,主要关注系统自身状态是否健康。 

  • 监控指标:流量、耗时、错误、心跳、客户端数、连接数。
  • 监控工具:CAT、SkyWalking、Pinpoint、Zipkin

(3)存储服务监控。

常用的第三方存储有 MySQL、Redis、ES、MQ 等。对于存储服务的监控,除了基础指标监控之外,还有一些比如集群节点、分片信息、存储数据信息等 相关特有存储指标的监控。

如果生产环境出现了以上的报警信息,应该怎么处理?

高性能架构实现需要关注以下几个方面:

  • 做好系统限流:通过流量控制来保证系统的稳定性,当实际并发压力超过系统性能设计指标的时候,就拒绝新的请求的连接,让用户排队等待。
  • 做好快速扩容:一般要储备额外的计算资源,用于不时之需,事先通过预估留出一部分资源池。
  • 做好系统优化:性能设计要贯穿于系统建设的始终,包括需求阶段、设计阶段、研发阶段、测试阶段、上线阶段、 运行阶段、数据增长与维护。

更多内容请查看:后端系统高并发解决方案分析

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

浮尘笔记

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

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

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

打赏作者

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

抵扣说明:

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

余额充值