使用Redis做MyBatis的二级缓存

分布式系统架构——使用Redis做MyBatis的二级缓存 使用Redis做MyBatis的二级缓存  通常为了减轻数据库的压力,我们会引入缓存。在Dao查询数据库之前,先去缓存中找是否有要找的数据,如果有则用缓存中的数据即可,就不用查询数据库了。如果没有才去数据库中查找。这样就能分担一下数据库的压力。另外,为了让缓存中的数据与数据库同步,我们应该在该数据发生变化的地方加入更新缓存的逻辑代码。这样无形之中增加了工作量,同时也是一种对原有代码的入侵。这对于 阅读详情

使用Redis做MyBatis的二级缓存

 通常为了减轻数据库的压力,我们会引入缓存。在Dao查询数据库之前,先去缓存中找是否有要找的数据,如果有则用缓存中的数据即可,就不用查询数据库了。如果没有才去数据库中查找。这样就能分担一下数据库的压力。另外,为了让缓存中的数据与数据库同步,我们应该在该数据发生变化的地方加入更新缓存的逻辑代码。这样无形之中增加了工作量,同时也是一种对原有代码的入侵。这对于有着代码洁癖的程序员来说,无疑是一种伤害。

MyBatis框架早就考虑到了这些问题,因此MyBatis提供了自定义的二级缓存概念,方便引入我们自己的缓存机制,而不用更改原有的业务逻辑。下面就让我们了解一下MyBatis的缓存机制。

一、缓存概述

正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持;

  • 一级缓存基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。

  • 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache、Hazelcast等。

  • 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。

  • MyBatis 的缓存采用了delegate机制 及 装饰器模式设计,当put、get、remove时,其中会经过多层 delegate cache 处理,其Cache类别有:BaseCache(基础缓存)、EvictionCache(排除算法缓存) 、DecoratorCache(装饰器缓存):

    1. BaseCache :为缓存数据最终存储的处理类,默认为 PerpetualCache,基于Map存储;可自定义存储处理,如基于EhCache、Memcached等;
    2. EvictionCache :当缓存数量达到一定大小后,将通过算法对缓存数据进行清除。默认采用 Lru 算法(LruCache),提供有 fifo 算法(FifoCache)等;
    3. DecoratorCache:缓存put/get处理前后的装饰器,如使用 LoggingCache 输出缓存命中日志信息、使用 SerializedCache 对 Cache的数据 put或get 进行序列化及反序列化处理、当设置flushInterval(默认1/h)后,则使用 ScheduledCache 对缓存数据进行定时刷新等。
  • 一般缓存框架的数据结构基本上都是 Key-Value 方式存储,MyBatis 对于其 Key 的生成采取规则为:

    [hashcode : checksum : mappedStementId : offset : limit : executeSql : queryParams]。

  • 对于并发 Read/Write 时缓存数据的同步问题,MyBatis 默认基于 JDK/concurrent中的ReadWriteLock,使用 ReentrantReadWriteLock 的实现,从而通过 Lock 机制防止在并发 Write Cache 过程中线程安全问题。

二、源码剖析

2.1 执行流程分析

接下来将结合 MyBatis 序列图进行源码分析。在分析其Cache前,先看看其整个处理过程。

这里写图片描述

  1. 通常我们在service层最终都会调用Mapper的接口方法,实现对数据库的操作,本例中是通过id查询product对象。
  2. 我们知道Mapper是一个接口,接口是没有对象的,更不能调用方法了,而我们调用的其实是mybatis框架的mapper动态代理对象MapperProxy,而MapperProxy中有封装了配置信息的DefaultSqlSession中的Configuration。动态代理的具体实现请戳这里。调用mapper方法的具体代码如下。 
    这里写图片描述
  3. 在执行mapperMethod的execute的时候,不仅传递了方法参数,还传递了sqlSession。在执行execute,其实是通过判断配置文件的操作类型,来调用sqlSession的对应方法的。本例中,由于是select,而返回值不是list,所以下一步执行的是sqlSession的selectOne方法具体代码如下: 
    这里写图片描述
  4. selectOne其实调用了selectList,只不过是取了第一个。具体代码如下: 
    这里写图片描述
  5. selectList经过层层调用,最终交给执行器执行。具体执行器的结构待会我们会分析。注意这里的ms参数,其实就是从Configration中得到的一些配置信息,包括mapper文件里的sql语句。具体代码如下: 
    这里写图片描述
  6. 这里的执行器execute,其实是spring注入的。excute是一个接口,而到时候具体是哪个execute执行,是看配置文件的。而我们的一级缓存和二级缓存其实都是execute中的一种。接下来,我们遍分析一下执行器(Executor)。

2.2 执行器(Executor)

Executor:执行器接口。也是最终执行数据获取及更新的实例。其结构如下:

这里写图片描述

  1. BaseExecutor:基础执行器抽象类。实现一些通用方法,如createCacheKey之类的。并采用模板模式将具体的数据库操作逻辑交由子类实现。另外,可以看到变量localCache:PerpetualCache,在该类采用perpetualCache实现基于map存储的一级缓存,其query方法如下: 
    这里写图片描述
    一级缓存和二级缓存很相似,都是实现Cache缓存接口,然后等待调用。其中的一级缓存具体实现其实使用了Map存储,原理非常简单。PerPetualCache具体结构如下: 
    这里写图片描述

  2. BatchExcutor、ReuseExcutor、SimpleExcutor:这三个就是简单的继承了BaseExcutor,实现了doQuery、doUpdate等发放,同样都是采用了JDBC对数据库进行操作;三者的区别在于,批量执行、重用Statement执行、普通方法执行。具体应用及长江在mybatis文档上都有详细的说明。

  3. CachingExecutor:二级缓存执行器。其中使用了静态代理模式,当二级缓存中没有数据的时候,就使用BaseExecutor做代理,进行下一步执行。具体代码如下: 
    这里写图片描述

2.3 Cache的设计

像之前所说,Cache是一个缓存接口,运行时用到的其实是在解析mapper文件的时候根据配置文件生成的对应Cahce实现类。另外这个实现类的构造过程使用了建造者(Builder)模式。在build的过程中,将所有设计到的cache放入基础缓存中,并使用装饰器模式将cache进行装饰。具体设计如下:

1. 从配置文件中获取节点,将配置信息提取出来初始化生成Cache

2. useNewCache方法中使用了建造者(Builder)模式,将从配置文件中读取出来的各个元素组装起来。其中最主要的是build方法。

这里写图片描述
3. 在build方法中,值得注意的是使用了装饰器模式,将几个基本的Cache装饰了一下。因为我们的Cache只是加入了自定义的缓存功能和逻辑,日志功能、同步功能等其实并没有。所以需要装饰一下,具体代码如下: 
这里写图片描述 
这里写图片描述 
4. 最终的缓存实例对象结构: 
这里写图片描述

2.4 总结

 总体上看,我们可以把MyBatis关于缓存的这一部分分为三个部分:

  1. 解析器:结合mybatis-spring框架,读取spring关于mybatis的配置文件。具体看是否开启缓存(这里指二级缓存),如果开启,生成的执行器为CachingExecutor。 
    这里写图片描述
    这里写图片描述
  2. 动态代理:实现调用mapper接口的时候执行mybatis逻辑
  3. 执行器:执行缓存处理逻辑。在这里二级缓存和一级缓存有所区别。

三、具体实现

3.1 配置文件

  1. 开启缓存:修改spring中关于mybatis的配置文件,将cacheEnabled设置为true。 
    这里写图片描述
  2. 添加实现Cache接口的实现类。重写方法会在查询数据库前后调用,查询、更新、删除、创建缓存需要在这几个方法中实现。值得注意的是,getObject方法,当返回的是null时,就会接着查询。如果不为null,则返回,不再查询了。

    /**
    * 使用redis做mybatis二级缓存
    * @Description
    * @file_name MyBatisRedisCache.java
    * @time 2016-07-26 下午4:49:13
    * @author muxiaocao
    */
    public class MyBatisRedisCache implements Cache{
    
    @Value("#{config['redis.ip']}")
    protected String redisIp;
    
    @Value("#{config['redis.port']}")
    protected Integer redisPort;
    
    private static Log logger =  LogFactory.getLog(MyBatisRedisCache.class);
    
    private Jedis redisClient = createClient();
    
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    
    private String id;
    
    public MyBatisRedisCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("缓存没有初始化id");
        }
        logger.debug("==================MyBatisRedisCache:id=" + id);
        this.id = id;
    }
    
    @Override
    public String getId() {
        return this.id;
    }
    
    @Override
    public int getSize() {
        return Integer.valueOf(redisClient.dbSize().toString());
    }
    
    @Override
    public void putObject(Object key, Object value) {
        logger.debug("==================pubObject:" + key + "=" + value);
        redisClient.set(SerializeUtil.serialize(key),SerializeUtil.serialize(value));
    }
    @Override
    public Object getObject(Object key) {
        Object value = SerializeUtil.unserialize(redisClient.get(SerializeUtil.serialize(key.toString())));
        logger.debug("==================getObjec:" + key + "=" + value);
        return value;
    }
    
    @Override
    public Object removeObject(Object key) {
        return redisClient.expire(SerializeUtil.serialize(key.toString()), 0);
    }
    
    @Override
    public void clear() {
        redisClient.flushDB();
    }
    
    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }
    
    private Jedis createClient() {
        try {
            RedisUtil.initRedis(redisIp);
            return RedisUtil.getRedis();
        } catch (Exception e) {
            e.printStackTrace();
        }
        throw new RuntimeException("初始化redis错误");
    }
    }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
  3. 在相应的Mapper文件中,加入Cache节点,将自定义的cache实现类加进去。 

四、总结

 MyBatis的整体思路其实很简单,但是跟着源码发现,一个好的框架需要考虑的问题很多,从可扩展性、功能维护等角度考虑,如果没有一个好的设计思路会把代码设计的很乱很乱。MyBatis充分利用了动态代理、建造模式、装饰器模式,使他们结合在一起,让整个框架变得简单易用,其实是很难得的。

 这就好比读一本书,需要先读厚再度薄一样,框架的设计最开始需要考虑到各种各样的问题,然后把一个简单的思路变得复杂。然后通过合理的设计,将复杂的问题简单的设计出来,使得代码很整洁,易于维护和读,这才是一个好的框架应该有的样子。

 真的很感谢能有这么一个机会研究一下mybatis,并从中学到了许多。希望有朝一日,也能写出像mybatis一样的框架。 
  
在这里很非常感谢http://denger.iteye.com/blog/1126423/。


注意:转载请标明,转自itboy-木小草。 
尊重原创,尊重技术。

高德地图轨迹回放,消息框内展示车辆速度且随车辆移动-升级支持调整速度、回放进度 使用高德地图回放车辆GPS轨迹,并添加速度和回放进度条,并支持调整,效果如下: 新增的功能: 在基础版本,有如下升级 速度条:可以拖动以调整车辆回放速度,且从当前位置继续回放 进度条:进度条表示车辆已回放的百分比,且拖动进度条车辆定位到相应的位置并继续回放 ###思路分析 高德和marker回放相关的API //主要靠他回放 moveAlong(path:Array,speed:Number,f:Function,circlable:Boolean) //点移动到另一个位置,移动进度条车辆跟随移动 阅读详情

相关推荐

Android13配置selinux让system应用可读sys,proc,SN号

Android13预置的system应用,需要读/sys, /proc目录,读(SN)serial number号, 需要修改selinux配置,否则会报avc错.其修改方法会比Android11复杂一些.。

zmlovelx(帅得不敢出门 程序员群31843264) 2655

Spring Boot+Mybatis Plus 使用Redis实现二级缓存

目录 一、Mybatis的缓存 二、 使用Redis实现Mybatis二级缓存 配置application.yml文件 在启动类上添加@EnableCaching注解 设置RedisTemplate,在这可以对Redis的存储类型进行序列化,但是这里我没有使用 创建MybatisRedisCache类重写Mybatis二级缓存的Cache接口的实现 最后我们只需要在Mapper接口上添加@CacheNamespace注解,就完成了 一、Mybatis的缓存 一级缓存是Sql...

Cd_Air的博客 6163

从GitHub到海浪模拟:手把手教你配置WAVEWATCHⅢ 6.07.0的完整开发与测试环境

本文详细指导如何在Ubuntu系统中配置WAVEWATCHⅢ 6.07.0的开发与测试环境,涵盖虚拟机设置、依赖安装、源码编译及案例验证的全流程。通过步骤化教程,帮助科研人员快速搭建高效的海浪模拟平台,提升海洋数值模拟的研究效率。

weixin_42568801的博客 326

【Mybatis扩展】Mybatis-Plus整合Redis实现二级缓存

【Mybatis扩展】Mybatis-Plus整合Redis实现二级缓存

秋装什么的博客 664

Agent + 监控平台:自动诊断告警根因

根因分析、智能监控、AI Agent、告警风暴、异常检测、可观测性、故障诊断在现代分布式系统中,快速定位和解决问题是维持系统稳定性的关键。传统的监控系统虽然能够生成大量告警,但往往缺乏对告警根因的自动诊断能力,导致运维团队在告警风暴中难以快速定位问题。本文深入探讨了将AI Agent技术与监控平台结合,实现告警根因自动诊断的技术架构、理论基础和实现方法。我们将从第一性原理出发,分析根因诊断问题的本质,介绍基于Agent的智能监控系统设计,并通过实际案例展示其应用价值。文章同时涵盖了从理论到实践的全链路,包括

AIGC应用创新大全的博客 305

树莓派驱动DLPDLCR230NPEVM+framebuffer显示图片

DLPDLCR230NPEVM DLP® LightCrafter™ Display 230NP 是一个易于使用的紧凑型即插即用评估平台,适用于消费类产品、工业、医疗和物联网 (IoT) 市场中的各种超便携显示应用。此评估模块 (EVM) 采用 DLP230NP 芯片组,该芯片组由DLP230NP.23 1080p DMD、DLPC3436显示控制器和DLPA2005PMIC/LED 驱动器组成。此 EVM 外形小巧,配备生产就绪型光学引擎以及支持 18 位 RGB 并行视频接口...

L_Y000的博客 2621

Springboot - 15.二级分布式缓存集成-Caffeine

在测试中,当涉及基于时间的缓存驱逐策略时,不需要等待实际时间的推进。你可以使用 Caffeine 的Ticker接口和方法来定义自定义的时间源,而不必等待系统时钟。Caffeine 通过Executor来执行周期维护、移除通知和异步生成操作。你可以通过配置一个自定义的执行器来更好地控制这些操作。

yueerba126的博客 1399

SAP PS模块预算控制实战:为什么你的采购订单金额和预算占用对不上?

本文深入解析SAP PS模块中采购订单金额与预算占用偏差的常见问题,揭示承诺成本与实际成本的分流处理机制。通过实战案例和六步定位法,提供发票拆分、跨年度预算等典型场景的解决方案,并分享预防性配置框架和最佳实践,帮助企业有效控制预算偏差。

weixin_30363981的博客 516

Mybatis整合Redis实现二级缓存

1、二级缓存开启,既可以缓存单条,也可以缓存多条2、可以通过mapper.xml的useCache属性控制是否使用缓存面试前一定少不了刷题,为了方便大家复习,我分享一波个人整理的面试大全宝典Java核心知识整理Java核心知识Spring全家桶(实战系列)其他电子书资料Step3:刷题。

2401_84153038的博客 1380

【DDSCAT——离散偶极近似仿真程序04】参数理解和模型设计

在这一部分,我们开始理解DDSCAT程序设计中的一些参数问题。 承接第二部分内容中的ddscat.par文件,我们来聊一聊偶极子、实际体积、有效半径等关键词。 一、偶极子点阵 手册中说,我们需要设计模型(target/目标),它是有偶极子点阵组成的。偶极子,顾名思义,是由一对电荷组成的,但我们不用设计一对,我们设计一个点就行,...

XD_Yangf的博客 7552

MetaERP 的管理单元在设计哲学和实现逻辑上,确实与 Oracle EBS 的业务实体(OU)一脉相承,你可以放心地在已知的 Oracle 多组织架构框架下进行理解。它并没有推翻这套经典的核算模型

MetaERP的管理单元设计继承了Oracle EBS业务实体(OU)的核心思想,并进行了现代技术重构。管理单元(MU)作为OU的超集,延续了业务数据隔离与流程执行边界的定位,但通过元数据驱动的多租户架构实现更灵活的数据隔离。MetaERP采用三层解耦设计(核算单元、自然科目、核算维度),解决了EBS科目组合爆炸问题,并实现事件驱动的实时核算。相较于EBS的批处理模式,MetaERP通过云原生架构和元数据驱动配置,显著提升了灵活性、实时性和扩展性,支持跨法人的业务统一管理视图。

金牌架构师 577

使用redis做mybatis的二级缓存

使用redis做mybatis的二级缓存 application.properties 在application.properties文件中配置Redis,Mybatis,开启Mybatis二级缓存等 server.port=8084 spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&charact...

haoxiaoyong1014的博客 1706

华为OD机试真题 2026-9-2【查找幸运数】

华为OD机试真题 新系统【查找幸运数】,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。华为OD机试真题 【查找幸运数】多语言题解。

qq_45776114的博客 506

使用Redis做Mybatis的二级缓存

本篇记录怎么使用Redis做Mybtais的缓存。本篇简单记录一下springboot整合mybatis使用redis做二级缓存。

Java学习博客 1758

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

mybatis使用redis做二级缓存

redis安装 springboot+mybatis搭建 maven <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version&gt...

qq_37266528的博客 339

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

使用redis做mysql二级缓存_使用Redis做MyBatis的二级缓存

1. 介绍使用mybatis时可以使用二级缓存提高查询速度,进而改善用户体验。使用redis做mybatis的二级缓存可是内存可控,管理方便。2. 使用思路2.1 配置redis.xml 设置redis服务连接各参数2.1 在配置文件中使用 标签,设置开启二级缓存;2.2 在mapper.xml 中使用 将cache映射到指定的RedisCacheClass类中;2.3 映射类RedisCach...

weixin_30874365的博客 337

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

使用 Redis 做 MyBatis 二级缓存

1. 首先安装 Redis Server,设置好用户名密码,然后 pom 里添加依赖: <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.7.1</version> </de...

Allen_jinjie 的技术空间 389

SpringBoot中使用redis做Mybatis的二级缓存

文章目录 在mybatis中有一级缓存、二级缓存 一级缓存:该缓存是基于SqlSession的,mybatis默认开启一级缓存。 二级缓存:该缓存是Mapper级别的,默认没有开启二级缓存,需要在配置文件中开启 <!-- 开启二级缓存,默认是false --> <setting name="cacheEnabled" value="true"/> package com....

454

jedis使用_Mybatis的二级缓存、使用Redis做二级缓存

什么是二级缓存?二级缓存和一级缓存的原理是一样的,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是一级缓存是基于的sqlSession,而二级缓存是基于mapper文件的namespace的,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如何两个mapper的namespace相同,即使两个mapper,那这两个mapper中执行sql查询到的...

weixin_39633452的博客 143

使用protostuff + redis做mybatis二级缓存

实操 &amp;amp;amp;amp;lt;dependency&amp;amp;amp;amp;gt; &amp;amp;amp;amp;lt;groupId&amp;amp;amp;amp;gt;redis.clients&amp;amp;amp;amp;lt;/groupId&amp;amp;amp;amp;gt; &amp;amp;amp;amp;lt;artifactId&amp;amp;amp;amp;gt;

Hello World ^—^ 825

Mybatis使用Redis做二级缓存

由于redis是非关系型数据库,数据是储存到内存中的,而从内存中读取数据要比从硬盘中读取数据的速度要快很多,并且redis可以持久性化数据,所以可以用redis做数据的缓存。首先导入jar包:Spring-data-redis.jar配置文件:redis.host=192.168.81.128 redis.port=6379 redis.pass=redis redis.maxIdle=300 re

优快云3436的专栏 1758

开启注解缓存_Mybatis的二级缓存、使用Redis做二级缓存

什么是二级缓存?二级缓存和一级缓存的原理是一样的,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是一级缓存是基于的sqlSession,而二级缓存是基于mapper文件的namespace的,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如何两个mapper的namespace相同,即使两个mapper,那这两个mapper中执行sql查询到的...

weixin_30069911的博客 266

SpringBoot使用Redis做缓存,MyBatis二级缓存采用Redis

gradle依赖 compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis', version: '2.1.0.RELEASE' pom.xml依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-sta

qq_42623400的博客 8640

SpringBoot+Mybatis项目使用Redis做Mybatis的二级缓存

SpringBoot+Mybatis项目使用redis做Mybatis的二级缓存1.在pom.xml文件中引入redis依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId>

Mazhitaoooo 3570
上一篇: 关于mybatis里面的Executor
下一篇: 《深入理解mybatis原理(六)》 MyBatis缓存机制的设计与实现如何细粒度地控制你的MyBatis二级缓存
hxpjava1
博客等级 码龄19年 1365粉丝 511原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值