spring cloud netflix笔记

Ribbon 可以通过配置文件制定负载均衡的规则  默认使用轮训算法来实现负载均衡  根据响应时间做权重 

    两个自动装配类 RibbonAutoConfiguration 和 LoadBalancerAutoConfiguration
    
        装配类会让 加了LoadBalanced 注解的 RestTemplate 加上一个拦截器 LoadBalancerInterceptor

    关键类 LoadBalancerInterceptor RibbonClientConfiguration  ZoneAwareLoadBalancer DynamicServerListLoadBalancer 
    
        LoadBalancerInterceptor 通过 LoadBalancerClient 会生成一个 ZoneAwareLoadBalancer 这个里面包含了 Irule Iping 一些信息
        
        LoadBalancerInterceptor 继承自 DynamicServerListLoadBalancer  
        
        DynamicServerListLoadBalancer 的构造方法里面加载了几个定时任务去更新实例表
        
        LoadBalancerAutoConfiguration 会加载 SpringClientFactory
        
        SpringClientFactory 继承 NamedContextFactory 上下文 会在 getInstance 也就是加载上下文的时候加载配置类  RibbonClientConfiguration
        
        RibbonClientConfiguration  是利用spring的容器进行构建的  在 RibbonLoadBalancerClient 的execute 执行 getLoadBalancer  最后执行 getInstance 通过spring容器 获取 RibbonClientConfiguration
    
        
    

    应用初始化的时候会自动装配 RibbonAutoConfiguration 和 LoadBalancerAutoConfiguration
        RibbonAutoConfiguration 先自动装配于 LoadBalancerAutoConfiguration
        
        LoadBalancerAutoConfiguration 
            加载 LoadBalancerClient 类 然后还会把加了 LoadBalanced 注解的RestTemplate 加上一个拦截器
            
            加载 LoadBalancerInterceptor
    
        RibbonAutoConfiguration  
        
            加载 RibbonLoadBalancerClient 类 然后在 LoadBalancerInterceptor.intercept 进入 RibbonLoadBalancerClient 的 execute 方法 然后在 RibbonLoadBalancerClient 获取 ILoadBalancer 均衡器和 Server
        
            加载 BaseLoadBalancer 
        

    客户端的负载均衡 LoadBalanced LoadBalancerInterceptor 
        @LoadBalanced 注解  加了Qualifier注解作为标记 
            
            
        
            LoadBalancerAutoConfiguration 自动装配 RestTemplateCustomizer、 LoadBalancerInterceptor
                LoadBalancerInterceptor    为每一个RestTemplate设置一个拦截器 LoadBalancerInterceptor
                RestTemplateCustomizer     对修饰了LoadBalanced注解的RestTemplate的实例进行添加
        
        LoadBalancerInterceptor 拦截器
        
            intercept 方法 执行loadBalancer.execute  
            
            execute
                先获得一个负载均衡器 getLoadBalancer  从 RibbonClientConfiguration  得到 继承IClientConfig的 DefaultClientConfigImpl   里面获取 IClientConfig  根据 IClientConfig 返回一个 PollingServerListUpdater
                    getInstance 根据spring容器本身实现工厂得到 RibbonClientConfiguration 然后得到 ZoneAwareLoadBalancer 
                        调用 ZoneAwareLoadBalancer 会执行 DynamicServerListLoadBalancer 构造方法 里面有一个 restOfInit 方法
                        
                        restOfInit 进入 
                        
                        执行 updateListOfServers  
                        
                        会执行 enableAndInitLearnNewServersFeature 方法 会开启一个执行 UpdateAction 的doUpdate的线程 并把这个线程放入 ScheduledFuture 里面定时执行 获取服务地址列表
                
                获取服务地址  getServer
            
            ZoneAwareLoadBalancer.chooseServer 会根据负载均衡器的规则进行地址的选择 ( 默认使用轮训算法)
            
        
        InterceptingClientHttpRequest  -> AbstractBufferingClientHttpRequest  -> AbstractClientHttpRequest   -> ClientHttpRequest  接口实现关系路径
                
            RestTemplate 在 InterceptingClientHttpRequest 中进入拦截器 LoadBalancerInterceptor 的 intercept 方法
            
        
        
        
        
        BaseLoadBalancer 
            
            allServerList  服务列表  循环更新列表  cas操作枷锁  
            
            LoadBalancerInterceptor 是拿到allServerList列表然后进行负载均衡 
            
            DynamicServerListLoadBalancer 是在执行远程调用的时候加一个scheduledFuture线程去动态从本地或者服务中心获取allServerList 服务列表
            
            setPingInterval  设置时间不断的ping 服务列表的地址 确认服务有没有挂 通讯时间长度
            
            setupPingTask 起了一个任务 ping注册中心检查服务存活,然后刷新本地可用服务的缓存
            
            
        
        RibbonClientConfiguration  是利用spring的容器进行构建的  在 RibbonLoadBalancerClient 的execute 执行 getLoadBalancer  最后执行 getInstance 通过spring容器 获取 RibbonClientConfiguration  debug链路追踪
        
        接口                认实现类                        描述
        IClientConfig        efaultClientConfigImpl            管理配置接口
        IRule                ZoneAvoidanceRule                均衡策略接口
        IPing                DummyPing                        检查服务可用性接口
        ServerList<Server>    ConfigurationBasedServerList    获取服务列表接口
        ILoadBalancer        ZoneAwareLoadBalancer            负载均衡接口
        ServerListUpdater    PollingServerListUpdater        定时更新服务列表接口
        ServerIntrospector    DefaultServerIntrospector        安全端口接口

OpenFegin :  

    关键类 FeignClientFactoryBean  ReflectiveFeign  FeignInvocationHandler

    FeignAutoConfiguration 自动装配 把 FeignContext、Targeter 注入到IOC容器中

    FeignRibbonClientAutoConfiguration  -> DefaultFeignLoadBalancedConfiguration  返回一个 LoadBalancerFeignClient 的client 解析 Feign 客户端
    

    EnableFeignClients 注解和 FeignClient 注解

    1: 自动装配期间把 FeignClient注册成一个 FeignClientFactoryBean
    
        EnableFeignClients 放在springboot的启动类  EnableFeignClients注解里面会Import FeignClientsRegistrar 类
            
            FeignClientsRegistrar 会扫描有 FeignClient 的注解 得到一个 FeignClientFactoryBean 注入到IOC容器中  
                BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class); registerFeignClient 方法

            FeignClientFactoryBean.getObject 方法里在 targeter.target 方法里面 build.newInstance 获得一个 ReflectiveFeign 类 ReflectiveFeign会生成一个代理对象

            最终调用 FeignInvocationHandler.invoke  (FeignInvocationHandler 是针对方法级别的Handle)
            
            
            

    
    2:执行handle方法
        在 ReflectiveFeign 的静态内部类 FeignInvocationHandler 里面执行逻辑  执行 SynchronousMethodHandler.invoke 
            
            由该 Handler 完成后续的 HTTP 转换, 发送, 接收, 翻译HTTP响应的工作
        

    NamedContextFactory 上下文概念也就是NamedContextFactory
        
        子context或者叫子容器,子context维护自身的所有bean  例如 FeignContext 就是 feign客户端的上下文 通过名称获取相对应的上下文来进行 客户端对服务端的访问
    
    
        继承NamedContextFactory 类的构造方法 例如 FeignContext 需要添加 super(FeignClientsConfiguration.class, "feign", "feign.client.name");
    
            FeignClientsConfiguration context 需要加载的自动化配置类
    

Eureka AP特性

    服务注册的顶层接口为 ServiceRegistry -> Registration  只要是springcloud的服务注册组件  都会实现这个类 例如 nacos eureka 

    EurekaClient 客户端
        1:EurekaClientAutoConfiguration 自动装配类 
            
            EurekaAutoServiceRegistration 在自动装配类里面 继承了 SmartLifecycle  会执行 onApplicationEvent 最后调用start方法
            
                SmartLifecycle 在容器所有bean加载和初始化完毕执行
                
                继承了 SmartLifecycle 的类会在springboot的run方法里面的 refresh 调用到 AbstractApplicationContext 的 finishRefresh getLifecycleProcessor().onRefresh(); 
                    在这里进行调用 继承了 SmartLifecycle 的类的start方法  
                    
                    AbstractApplicationContext.finishRefresh  调用 this.getLifecycleProcessor().onRefresh(); 会执行实现了 SmartLifecycle 接口的类的 start 方法
            
                    EurekaAutoServiceRegistration 会实现 SmartLifecycle 智能生命周期接口 然后在bean 加载完之后执行 EurekaAutoServiceRegistration.start 方法

                
                    最后在start方法里面 this.serviceRegistry.register(this.registration); 更改实例状态 方法最终会调用 EurekaServiceRegistry 类中的register方法 监听 notify 方法实现 注册监听事件
                    
                    start 方法
                        更改实例状态 
                        reg.getApplicationInfoManager().setInstanceStatus(reg.getInstanceConfig().getInitialStatus());   
                            
                            ApplicationInfoManager  ApplicationInfoManager.StatusChangeListener.notify()
                        
                        注册健康机制
                        reg.getHealthCheckHandler().ifAvailable((healthCheckHandler) -> {reg.getEurekaClient().registerHealthCheck(healthCheckHandler);});  HealthCheckHandler
                        
            
            
            EurekaClientConfiguration内部类里面撞在了一个 EurekaClient = new CloudEurekaClient    
                
                装载的时候CloudEurekaClient的父类 DiscoveryClient 会初始化 
                
        
        2:DiscoveryClient 
            本地服务缓存为 AtomicReference<Applications> localRegionApps = new AtomicReference<Applications>();
                如果集成了ribbon的话 ribbon从 localRegionApps 获取服务地址信息
            
            DiscoveryClient 在 initScheduledTasks 方法会开启多个线程池  
            
                scheduler 线程池 用来定时执行心跳的线程池任务和刷新缓存的线程池任务
                
                heartbeatExecutor 心跳线程池 想服务端发送请求 向服务端表示客户端的应用正常运行  30秒发送一次
                     
                    检测服务的心跳的线程池
                
                cacheRefreshExecutor 缓存线程池  刷新需要调用的服务地址的缓存  CacheRefreshThread 执行run方法里面的 fetchRegistry 方法刷新缓存
                
                    定时同步服务端的实例列表信息
                    
                statusChangeListener = new ApplicationInfoManager.StatusChangeListener()  匿名内部类  实现 EurekaServiceRegistry 的 notify方法
                
                    applicationInfoManager.registerStatusChangeListener(statusChangeListener) 然后发布事件
                    
                
                instanceInfoReplicator.start 会执行 InstanceInfoReplicator 类的 run 方法
                
                    在run方法里面 执行 discoveryClient.register(); 进行服务的注册
                    
                    AbstractJerseyEurekaHttpClient 进行Eureka客户端到服务端的通信
                    
            
            

            statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {  监听上面更改实例状态
            
            注册时取全量数据 然后就交给缓存线程池获取
            getAndStoreFullRegistry();  全量获取服务接口  AbstractJerseyEurekaHttpClient 
            
            getAndUpdateDelta        增量获取服务接口
        

    EurekaServer 服务端 
        ApplicationResource ApplicationsResource 类接收请求
    
        ApplicationResource.addInstance 里面有一个 registry.register(info, "true".equals(isReplication)); 
        
        进入到 PeerAwareInstanceRegistryImpl.register
            
            super.register(info, leaseDuration, isReplication); 调用了 AbstractInstanceRegistry.register 先进行注册 
            
                最终所有的服务都会注册到 registry 对象里面 这就是Eureka的一级缓存 如下:
                    ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();        
                
                
                invalidateCache() 添加服务之后进行缓存的失效 在 ResponseCacheImpl 的invalidate 方法 执行对应key的缓存失效
                
                    ResponseCacheImpl
                    
                        readWriteCacheMap 二级缓存(写缓存) LoadingCache<Key, Value> readWriteCacheMap
                        
                        readOnlyCacheMap  一级缓存(读缓存) ConcurrentMap<Key, Value> readOnlyCacheMap = new ConcurrentHashMap<Key, Value>()
            
            replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication); 然后同步到其他的服务端节点
    
     
    
    
    衰减重试 和Eureka自我保护机制相关
        
        客户端 DiscoveryClient类 会维护一个 TimedSupervisorTask的定时心跳任务 向注册中心去发送心跳请求  
        
        如果遇到网络抖动的话 会延长心跳的访问时间 是用初始时间乘以2 来延长心跳时间   
        
        这个时间当访问正常的时候会回置为30秒 
        
    Eureka自我保护机制
        
        默认如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制
        
            Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。

            Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用。

            当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。


        


    EurekaServer存在3个变量:registry、readWriteCacheMap、readOnlyCacheMap,用来保存服务注册信息。
    
        默认情况下,定时任务每30s将readWriteCacheMap中的服务信息同步到readOnlyCacheMap。
        
        每60s,通过EvictTask将超过90s未续约的服务从registry、readWriteCacheMap中剔除。


Eureka多级缓存的意义  为了读写分离

    当大规模服务注册、更新时,如果只是修改一个ConcurrenHashMap(registry),势必存在大量的锁竞争,影响性能。
    Eureka是AP模型,设计之初就是为了满足高可用,只需要满足最终可用。所以用到多级缓存来实现读写分离,注册时直接写入registry,同时失效readWriteCacheMap中对应的key,
    获取注册信息时,如果使用use-read-only-response-cache开启了读缓存,则先从readOnlyCacheMap中读取,如果readOnlyCacheMap中不存在,则先用readWriteCacheMap中获取,将其放入readOnlyCacheMap,
    若readWriteCacheMap中不存在,则返回空

Spring-cloud-config

    MutablePropertySources 属性资源类
        
        List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>(); 用来保存服务配置中心的配置信息。
    
    http://e58e836c5a68.ngrok.io/actuator/bus-refresh
    
    http://e58e836c5a68.ngrok.io/monitor?path="*"    gitee 的WebHooks 不起作用  postman可以刷新

    负责解析文件的组件 ConfigurableEnvironment  关键在于 PropertySource
    
    ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); 加载spring的配置
    
    1:ConfigurableEnvironment environment = getOrCreateEnvironment(); 在SpringApplication.run 方法中 在 prepareEnvironment方法 会生成一个 StandardServletEnvironment 的 ConfigurableEnvironment 环境类
        
        在 ConfigurableEnvironment 里面配置 configurePropertySources 和 configureProfiles  放入 MutablePropertySources
        
            ConfigurableEnvironment = StandardServletEnvironment ->  在StandardServletEnvironment会加载下列配置  
            
                因为父类 AbstractEnvironment 的this.customizePropertySources(this.propertySources); 会直接调用StandardServletEnvironment的 customizePropertySources
                    
                StandardServletEnvironment(基于servlet环境的):customizePropertySources
                  
                    SERVLET_CONFIG_PROPERTY_SOURCE_NAME:servlet的配置信息,也就是在中配置的 例如 DispatcherServlet                     ServletConfigPropertySource
                    
                    SERVLET_CONTEXT_PROPERTY_SOURCE_NAME: 这个是servlet初始化的上下文,也就是以前我们在web.xml中配置的 context-param。    ServletContextPropertySource
                    
                    JNDI_PROPERTY_SOURCE_NAME: 加载jndi.properties配置信息。
                    
                StandardEnvironment(标准的配置源):customizePropertySources     
                
                    SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME: 系统变量,通过System.setProperty设置的变量,默认可以看到java.version、os.name等。     PropertiesPropertySource
                    
                    SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME: 系统环境变量,也就是我们配置JAVA_HOME的地方。                                        SystemEnvironmentPropertySource


    2:configureEnvironment(environment, applicationArguments.getSourceArgs());
        
        addConversionService  添加一个类型转换器
        
        configurePropertySources   (可选项)
        
            defaultProperties 加载默认的配置文件信息  有就放在 MutablePropertySources 最后面
            
            CompositePropertySource    加载命令行参数 复合的PropertySource  
        
            没有addCommandLineProperties 就在 MutablePropertySources 首位放一个 SimpleCommandLinePropertySource
    
        configureProfiles
            
            把环境设置的profiles 加载至环境里面  例如yml文件中的 profiles.active
            
    
    3:listeners.environmentPrepared(environment);  加载yml文件(springboot的功能)
        
        
        listener.environmentPrepared 方法 进入到 SimpleApplicationEventMulticaster.multicastEvent -> listener.onApplicationEvent(event);
        
        进入到 ConfigFileApplicationListener.onApplicationEvent 方法
            
            onApplicationEvent -> postProcessEnvironment -> addPropertySources方法 new Loader(environment, resourceLoader).load();
        
            onApplicationEnvironmentPreparedEvent:循环 EnvironmentPostProcessor
                
                进入 ConfigFileApplicationListener 的 postProcessEnvironment 
                
                new Loader(environment, resourceLoader).load();
                
                    new Loader()
                        加载文件路径
                        this.resourceLoader = (resourceLoader != null) ? resourceLoader: new DefaultResourceLoader(getClass().getClassLoader()); 
                        
                        spi机制 加载文件类型
                        this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,getClass().getClassLoader());  
                    
                    load()方法
                        
                        FilteredPropertySource.apply
                            进入 load方法  没有配置会默认从下面目录便利某些文件 
                            
                            DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/";
                            
                    最终调用到 YamlPropertySourceLoader 执行类的 load 方法来解析yml文件信息 放入 MutablePropertySources 的 List<PropertySource<?>> propertySourceList 
                     
            
            onApplicationPreparedEvent
    

    总结 遍历指定目录/默认目录下的文件 生成PropertySource 加载到 MutablePropertySources 里面 可以通过 environment.getProperty
            
                
    springcloud提供了
        PropertySourceLocator接口支持扩展自定义配置加载到spring Environment中。
        
        也可以实现 PropertySourceLoader 然后加载配置文件  在ConfigFileApplicationListener 里面通过spi机制循环实现PropertySourceLoader类的 load 方法
        
        弊端  因为是file 加载类  所以需要有相对应的file 才会进行load方法 
            
    所以 Springcloud 是用 ConfigServicePropertySourceLocator 在准备环境时 加载 远程配置信息        
        
        在run方法里面 prepareContext 方法 applyInitializers 里面 调用 PropertySourceBootstrapConfiguration 的initialize方法时候会遍历实现了 PropertySourceLocator 类
            然后 locateCollection 方法里面调用 locate 方法 加载配置信息


            ConfigServicePropertySourceLocator  Spring-cloud-config 远程加载配置信息的类

            
spring-cloud-Hystrix    

    继承 HystrixCommand 类
    
    使用 HystrixCommand 注解
    
    
    HystrixCommandProperties 配置信息
    
    HystrixCommand
        getExecutionObservable
        
        getFallbackObservable
        
        executeCommandAndObserve
            
    
    HystrixCommandAspect 使用注解 HystrixCommand/HystrixCollapser 的时候 会经过 HystrixCommandAspect 拦截 
    
    实现 HystrixCommand 注解  最终都是调用 execute 方法 通过 toObservable().toBlocking().toFuture(); 获取一个 Future  然后get 获取结果
    
    
    Observable<R> hystrixObservable =
                        Observable.defer(applyHystrixSemantics)
                                .map(wrapWithAllOnNextHooks);
                                
    AbstractCommand 的 toObservable 方法  返回  handleSemaphoreRejectionViaFallback / handleShortCircuitViaFallback(默认)
        
        executeCommandWithSpecifiedIsolation 熔断和信号量判断的 Observable
            
            getUserExecutionObservable -> HystrixCommand.getExecutionObservable 执行继承了 GenericCommand 的 run 方法
        
        Observable<R> fallbackExecutionChain;
    
    
    
    执行  GenericCommand 的run方法 和 getFallback 方法
    
    
    
    滑动窗口: 流量控制技术
    
    信号量隔离
        
        如果使用了开启了信号量隔离进行限流熔断的话
            executionSemaphore.tryAcquire()  使用 TryableSemaphoreActual 然后用 AtomicInteger count 去进行累加 直到大于设置的请求数量 
        
            小于等于请求数量 :执行 executeCommandAndObserve -> executeCommandWithSpecifiedIsolation
                
                进去 getUserExecutionObservable 然后是真正调用run方法的方法 getExecutionObservable
                
                然后调用 GenericCommand 的方法 执行对应的方法

            
            大于请求数量 执行 handleSemaphoreRejectionViaFallback 直接报错并拒绝请求
            
            ratelimiter的令牌桶算法和漏桶算法,都是直接对请求量来计数。只是令牌桶算法可以将前面一段时间没有用掉的请求量允许余额拿过继续用。而漏桶算法一段时间就允许这么多,前面没用掉的也不能用了。
            
            hystrix信号量隔离限制的是tomcat等Web容器线程数,一段时间仅仅能支持这么多。多余的请求再来请求线程资源,就被拒绝了。
                所以是一种“曲径通幽”的限流方式。因为实际是通过隔离了部分容器线程资源,也算是一种隔离方式。
            
        
        信号量隔离模式:使用一个原子计数器(或信号量)记录当前有多少个线程在运行。
        
        当有新的请求时,先判断计数器的数值,若超过了设置的最大线程数,则丢弃该请求,反之,计数器+1,
        
        请求返回时计数器-1.这种方式是严格的控制线程且立即返回模式,无法应对突发流量,好处是没有线程切换带来的资源消耗。
        
        1、不会使用Hystrix管理的线程池处理请求。使用容器(Tomcat)的线程处理请求逻辑

        2、不涉及线程切换,资源调度,上下文的转换等,相对效率高

        3、信号量隔离也会启动熔断机制。如果请求并发数超标,则触发熔断,返回fallback数据。

        4、设置信号量隔离后,线程池相关配置失效
        
        
    线程池隔离 
        
        Hystrix默认使用了线程池模式
            
            executionSemaphore.tryAcquire()  使用 TryableSemaphoreNoOp  直接返回true
            
            执行 executeCommandAndObserve -> executeCommandWithSpecifiedIsolation
                
            executeCommandWithSpecifiedIsolation 判断 executionIsolationStrategy 属性是线程池还是信号量熔断
            
            进去 getUserExecutionObservable 然后是真正调用run方法的方法 getExecutionObservable
            
            然后调用 GenericCommand 的方法 执行对应的方法
        
        
        使用一个线程池来存储当前的请求,线程池对请求做处理,设置任务的超时时间,堆积的请求先进入队列。
        
        这种方式需要对每一个依赖服务申请线程池资源,有一定的资源消耗,线程切换,好处是可以应对突发流量。
        
        通过threadPoolKey指定依赖服务的线程池key,创建命令时,创建线程池并缓存到ConcurrentHashMap<String, HystrixThreadPool> threadPools 中。
        
        这样操作,即使某个远程依赖服务出现异常,也不会影响其他服务调用。        
    
    
    hystrix请求合并如何实现?
        微服务架构中通常需要依赖多个远程的微服务,而远程调用中最常见的问题就是通信消耗与连接数占用。在高并发的情况之下,因通信次数的增加,总的通信时间消耗将会变得越来越长。
        
        同时,因为依赖服务的线程池资源有限,将出现排队等待与响应延迟的清况。

        为了优化这两个问题,Hystrix 提供了HystrixCollapser来实现请求的合并,以减少通信消耗和线程数的占用。

        HystrixCollapser 实现了在 HystrixCommand之前放置一个合并处理器,将处于一个很短的时间窗(默认10毫秒)内对同一依赖服务的多个请求进行整合,并以批量方式发起请求的功能(前提是服务提供方提供相应的批量接口)。
        
        HystrixCollapser的封装多个请求合并发送的具体细节,开发者只需关注业务上将单次请求合并成多次请求即可。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值