redis知识点总结

HyperLogLog :是一种概率数据结构,它使用概率算法来统计集合的近似基数。而它算法的最本源则是伯努利过程 + 分桶 + 调和平均数。具体实现可看 HyperLogLog 讲解。

功能:误差允许范围内做基数统计 (基数就是指一个集合中不同值的个数) 的时候非常有用,每个HyperLogLog的键可以计算接近2^64不同元素的基数,而大小只需要12KB。错误率大概在0.81%。所以如果用做 UV 统计很合适。

HyperLogLog底层 一共分了 2^14 个桶,也就是 16384 个桶。每个(registers)桶中是一个 6 bit 的数组,这里有个骚操作就是一般人可能直接用一个字节当桶浪费2个bit空间,但是Redis底层只用6个然后通过前后拼接实现对内存用到了极致,最终就是 16384*6/8/1024 = 12KB。

1.8、bitmap

BitMap 原本的含义是用一个比特位来映射某个元素的状态。由于一个比特位只能表示 0 和 1 两种状态,所以 BitMap 能映射的状态有限,但是使用比特位的优势是能大量的节省内存空间。

在 Redis 中BitMap 底层是基于字符串类型实现的,可以把 Bitmaps 想象成一个以比特位为单位的数组,数组的每个单元只能存储0和1,数组的下标在 Bitmaps 中叫做偏移量,BitMap 的 offset 值上限 2^32 - 1图片

  1. 用户签到

key = 年份:用户id offset = (今天是一年中的第几天) % (今年的天数)

  1. 统计活跃用户

使用日期作为 key,然后用户 id 为 offset 设置不同offset为0 1 即可。

PS : Redis 它的通讯协议是基于TCP的应用层协议 RESP(REdis Serialization Protocol)。

1.9、Bloom Filter

使用布隆过滤器得到的判断结果:不存在的一定不存在,存在的不一定存在

布隆过滤器 原理:

当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点(有效降低冲突概率),把它们置为1。检索时,我们只要看看这些点是不是都是1就知道集合中有没有它了:如果这些点有任何一个为0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。

想玩的话可以用Google的guava包玩耍一番。图片

1.10 发布订阅

redis提供了发布、订阅模式的消息机制,其中消息订阅者与发布者不直接通信,发布者向指定的频道(channel)发布消息,订阅该频道的每个客户端都可以接收到消息。不过比专业的MQ(RabbitMQ RocketMQ ActiveMQ Kafka)相比不值一提,这个功能就算球了。

图片

2、持久化

===============================================================

因为Redis数据在内存,断电既丢,因此持久化到磁盘是必须得有的,Redis提供了RDB跟AOF两种模式。

2.1、RDB

RDB 持久化机制,是对 Redis 中的数据执行周期性的持久化。更适合做冷备。优点:

1、压缩后的二进制文,适用于备份、全量复制,用于灾难恢复加载RDB恢复数据远快于AOF方式,适合大规模的数据恢复。

2、如果业务对数据完整性和一致性要求不高,RDB是很好的选择。数据恢复比AOF快。

缺点:

1、RDB是周期间隔性的快照文件,数据的完整性和一致性不高,因为RDB可能在最后一次备份时宕机了。

2、备份时占用内存,因为Redis 在备份时会独立fork一个子进程,将数据写入到一个临时文件(此时内存中的数据是原来的两倍哦),最后再将临时文件替换之前的备份文件。所以要考虑到大概两倍的数据膨胀性。

注意手动触发及COW:

1、SAVE 直接调用 rdbSave ,阻塞 Redis 主进程,导致无法提供服务。2、BGSAVE 则 fork 出一个子进程,子进程负责调用 rdbSave ,在保存完成后向主进程发送信号告知完成。在BGSAVE 执行期间仍可以继续处理客户端的请求

3、Copy On Write 机制,备份的是开始那个时刻内存中的数据,只复制被修改内存页数据,不是全部内存数据。

4、Copy On Write 时如果父子进程大量写操作会导致分页错误。

图片

2.2、AOF

AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,因为这个模式是只追加的方式,所以没有任何磁盘寻址的开销,所以很快,有点像 Mysql 中的binlog。AOF更适合做热备。

优点:

AOF是一秒一次去通过一个后台的线程fsync操作,数据丢失不用怕。

缺点:

1、对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

2、根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的。

AOF整个流程分两步:第一步是命令的实时写入,不同级别可能有1秒数据损失。命令先追加到aof_buf然后再同步到AO磁盘,如果实时写入磁盘会带来非常高的磁盘IO,影响整体性能

第二步是对aof文件的重写,目的是为了减少AOF文件的大小,可以自动触发或者手动触发(BGREWRITEAOF),是Fork出子进程操作,期间Redis服务仍可用。

图片

1、在重写期间,由于主进程依然在响应命令,为了保证最终备份的完整性;它依然会写入旧的AOF中,如果重写失败,能够保证数据不丢失。

2、为了把重写期间响应的写入信息也写入到新的文件中,因此也会为子进程保留一个buf,防止新写的file丢失数据。

3、重写是直接把当前内存的数据生成对应命令,并不需要读取老的AOF文件进行分析、命令合并。

4、无论是 RDB 还是 AOF 都是先写入一个临时文件,然后通过rename完成文件的替换工作

关于Fork的建议:

1、降低fork的频率,比如可以手动来触发RDB生成快照、与AOF重写;

2、控制Redis最大使用内存,防止fork耗时过长;

3、配置牛逼点,合理配置Linux的内存分配策略,避免因为物理内存不足导致fork失败。

4、Redis在执行BGSAVEBGREWRITEAOF命令时,哈希表的负载因子>=5,而未执行这两个命令时>=1。目的是尽量减少写操作,避免不必要的内存写入操作。

5、哈希表的扩展因子:哈希表已保存节点数量 / 哈希表大小。因子决定了是否扩展哈希表。

2.3、恢复

启动时会先检查AOF(数据更完整)文件是否存在,如果不存在就尝试加载RDB。

图片

2.4、建议

既然单独用RDB会丢失很多数据。单独用AOF,数据恢复没RDB来的快,所以出现问题了第一时间用RDB恢复,然后AOF做数据补全才说王道。

3、Redis为什么那么快

=======================================================================

3.1、 基于内存实现:

数据都存储在内存里,相比磁盘IO操作快百倍,操作速率很快。

3.2、高效的数据结构:

Redis底层多种数据结构支持不同的数据类型,比如HyperLogLog它连2个字节都不想浪费。

3.3、丰富而合理的编码:

Redis底层提供了 丰富而合理的编码 ,五种数据类型根据长度及元素的个数适配不同的编码格式。

1、String:自动存储int类型,非int类型用raw编码。

2、List:字符串长度且元素个数小于一定范围使用 ziplist 编码,否则转化为 linkedlist 编码。

3、Hash:hash 对象保存的键值对内的键和值字符串长度小于一定值及键值对。

4、Set:保存元素为整数及元素个数小于一定范围使用 intset 编码,任意条件不满足,则使用 hashtable 编码。

5、Zset:保存的元素个数小于定值且成员长度小于定值使用 ziplist 编码,任意条件不满足,则使用 skiplist 编码。

3.4、合适的线程模型:

I/O 多路复用模型同时监听客户端连接,多线程是需要上下文切换的,对于内存数据库来说这点很致命。

图片

3.5、 Redis6.0后引入多线程提速:

要知道 读写网络的read/write系统耗时 >> Redis运行执行耗时,Redis的瓶颈主要在于网络的 IO 消耗, 优化主要有两个方向:

1、提高网络 IO 性能,典型的实现比如使用 DPDK 来替代内核网络栈的方式

2、使用多线程充分利用多核,典型的实现比如 Memcached。

协议栈优化的这种方式跟 Redis 关系不大,支持多线程是一种最有效最便捷的操作方式。所以Redis支持多线程主要就是两个原因:

1、可以充分利用服务器 CPU 资源,目前主线程只能利用一个核

2、多线程任务可以分摊 Redis 同步 IO 读写负荷

关于多线程须知:

  1. Redis 6.0 版本 默认多线程是关闭的 io-threads-do-reads no
  1. Redis 6.0 版本 开启多线程后 线程数也要 谨慎设置。
  1. 多线程可以使得性能翻倍,但是多线程只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行

4、常见问题

================================================================

4.1、缓存雪崩

雪崩定义:

Redis中大批量key在同一时间同时失效导致所有请求都打到了MySQL。而MySQL扛不住导致大面积崩塌。

雪崩解决方案:

1、缓存数据的过期时间加上个随机值,防止同一时间大量数据过期现象发生。

2、如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。

3、设置热点数据永远不过期。

4.2、缓存穿透

穿透定义:

缓存穿透 是 指缓存和数据库中都没有的数据,比如ID默认>0,黑客一直 请求ID= -12的数据那么就会导致数据库压力过大,严重会击垮数据库。

穿透解决方案:

1、后端接口层增加 用户鉴权校验参数做校验等。

2、单个IP每秒访问次数超过阈值直接拉黑IP,关进小黑屋1天,在获取IP代理池的时候我就被拉黑过。

3、从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null 失效时间可以为15秒防止恶意攻击

4、用Redis提供的 Bloom Filter 特性也OK。

4.3、缓存击穿

击穿定义:

现象:大并发集中对这一个热点key进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库。

击穿解决:

设置热点数据永远不过期 加上互斥锁也能搞定了

4.4、双写一致性

双写:缓存数据库均更新数据,如何保证数据一致性?

1、先更新数据库,再更新缓存

安全问题:线程A更新数据库->线程B更新数据库->线程B更新缓存->线程A更新缓存。导致脏读

业务场景:读少写多场景,频繁更新数据库而缓存根本没用。更何况如果缓存是叠加计算后结果更浪费性能

2、先删缓存,再更新数据库

A 请求写来更新缓存。

B 发现缓存不在去数据查询旧值后写入缓存。

A 将数据写入数据库,此时缓存跟数据库不一致

因此 FackBook 提出了 Cache Aside Pattern

失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。

命中:应用程序从cache中取数据,取到后返回。

更新:先把数据存到数据库中,成功后,再让缓存失效

4.5、脑裂

脑裂是指因为网络原因,导致master节点、slave节点 和 sentinel集群处于不用的网络分区,此时因为sentinel集群无法感知到master的存在,所以将slave节点提升为master节点 此时存在两个不同的master节点就像一个大脑分裂成了两个。其实在HadoopSpark集群中都会出现这样的情况,只是解决方法不同而已(用ZK配合强制杀死)。

集群脑裂问题中,如果客户端还在基于原来的master节点继续写入数据那么新的master节点将无法同步这些数据,当网络问题解决后sentinel集群将原先的master节点降为slave节点,此时再从新的master中同步数据将造成大量的数据丢失。

Redis处理方案是redis的配置文件中存在两个参数

min-replicas-to-write 3 表示连接到master的最少slave数量

min-replicas-max-lag 10 表示slave连接到master的最大延迟时间

如果连接到master的slave数量 < 第一个参数 且 ping的延迟时间 <= 第二个参数那么master就会拒绝写请求,配置了这两个参数后如果发生了集群脑裂则原先的master节点接收到客户端的写入请求会拒绝就可以减少数据同步之后的数据丢失。

4.6、事务

MySQL 中的事务还是挺多道道的还要,而在Redis中的事务只要有如下三步:

图片

关于事务具体结论:

1、redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令

2、Redis事务没有隔离级别的概念:批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到

3、Redis不保证原子性:Redis中单条命令是原子性执行的,但事务不保证原子性。

4、Redis编译型错误事务中所有代码均不执行,指令使用错误。运行时异常是错误命令导致异常,其他命令可正常执行。

5、watch指令类似于乐观锁,在事务提交时,如果watch监控的多个KEY中任何KEY的值已经被其他客户端更改,则使用EXEC执行事务时,事务队列将不会被执行。

4.7、正确开发步骤

上线前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。

上线时:本地 ehcache 缓存 + Hystrix 限流 + 降级,避免MySQL扛不住。上线后:Redis 持久化采用 RDB + AOF 来保证断点后自动从磁盘上加载数据,快速恢复缓存数据。

5、分布式锁

================================================================

日常开发中我们可以用 synchronizedLock 实现并发编程。但是Java中的锁只能保证在同一个JVM进程内中执行。如果在分布式集群环境下用锁呢?日常一般有两种选择方案。

5.1、 Zookeeper实现分布式锁

你需要知道一点基本zookeeper知识:

1、持久节点:客户端断开连接zk不删除persistent类型节点 2、临时节点:客户端断开连接zk删除ephemeral类型节点 3、顺序节点:节点后面会自动生成类似0000001的数字表示顺序 4、节点变化的通知:客户端注册了监听节点变化的时候,会调用回调方法

大致流程如下,其中注意每个节点监控它前面那个节点状态,从而避免羊群效应。关于模板代码百度即可。图片

缺点:

频繁的创建删除节点,加上注册watch事件,对于zookeeper集群的压力比较大,性能也比不上Redis实现的分布式锁。

5.2、 Redis实现分布式锁

本身原理也比较简单,Redis 自身就是一个单线程处理器,具备互斥的特性,通过setNX,exist等命令就可以完成简单的分布式锁,处理好超时释放锁的逻辑即可。

SETNX

SETNX 是SET if Not eXists的简写,日常指令是SETNX key value,如果 key 不存在则set成功返回 1,如果这个key已经存在了返回0。

SETEX

SETEX key seconds value 表达的意思是 将值 value 关联到 key ,并将 key 的生存时间设为多少秒。如果 key 已经存在,setex命令将覆写旧值。并且 setex是一个原子性(atomic)操作。

加锁:

一般就是用一个标识唯一性的字符串比如UUID 配合 SETNX 实现加锁。

解锁:

这里用到了LUA脚本,LUA可以保证是原子性的,思路就是判断一下Key和入参是否相等,是的话就删除,返回成功1,0就是失败。

缺点:

这个锁是无法重入的,且自己实心的话各种边边角角都要考虑到,所以了解个大致思路流程即可,工程化还是用开源工具包就行

5.3、 Redisson实现分布式锁

Redisson 是在Redis基础上的一个服务,采用了基于NIO的Netty框架,不仅能作为Redis底层驱动客户端,还能将原生的RedisHash,List,Set,String,Geo,HyperLogLog等数据结构封装为Java里大家最熟悉的映射(Map),列表(List),集(Set),通用对象桶(Object Bucket),地理空间对象桶(Geospatial Bucket),基数估计算法(HyperLogLog)等结构。

这里我们只是用到了关于分布式锁的几个指令,他的大致底层原理:图片

Redisson加锁解锁 大致流程图如下:图片

6、Redis 过期策略和内存淘汰策略

=============================================================================

6.1、Redis的过期策略

Redis中 过期策略 通常有以下三种:

1、定时过期

每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即对key进行清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。

2、惰性过期

只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。

3、定期过期

每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

expires字典会保存所有设置了过期时间的key的过期时间数据,其中 key 是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。

Redis采用的过期策略:惰性删除 + 定期删除。memcached采用的过期策略:惰性删除

6.2、6种内存淘汰策略

Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。

1、volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

2、volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

3、volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

4、allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

5、allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰 6、no-enviction(驱逐):禁止驱逐数据,不删除的意思。

面试常问常考的也就是LRU了,大家熟悉的LinkedHashMap中也实现了LRU算法的,实现如下:

class SelfLRUCache<K, V> extends LinkedHashMap<K, V> {

private final int CACHE_SIZE;

/**

  • 传递进来最多能缓存多少数据

  • @param cacheSize 缓存大小

*/

public SelfLRUCache(int cacheSize) {

// true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。

super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);

CACHE_SIZE = cacheSize;

}

@Override

protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {

// 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。

return size() > CACHE_SIZE;

}

}

6.2、总结

Redis的内存淘汰策略的选取并不会影响过期的key的处理。内存淘汰策略用于处理内存不足时的需要申请额外空间的数据,过期策略用于处理过期的缓存数据

7、Redis 集群高可用

=======================================================================

单机问题有机器故障、容量瓶颈、QPS瓶颈。在实际应用中,Redis的多机部署时候会涉及到redis主从复制Sentinel哨兵模式Redis Cluster

| 模式 | 优点 | 缺点 |

| :-- | :-- | :-- |

| 单机版 | 架构简单,部署方便 | 机器故障、容量瓶颈、QPS瓶颈 |

| 主从复制 | 高可靠性,读写分离 | 故障恢复复杂,主库的写跟存受单机限制 |

| Sentinel 哨兵 | 集群部署简单,HA | 原理繁琐,slave存在资源浪费,不能解决读写分离问题 |

| Redis Cluster | 数据动态存储solt,可扩展,高可用 | 客户端动态感知后端变更,批量操作支持查 |

7.1、redis主从复制

该模式下 具有高可用性且读写分离, 会采用 增量同步全量同步 两种机制。

7.1.1、全量同步

图片

Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份:

1、slave连接master,发送psync命令。

2、master接收到psync命名后,开始执行bgsave命令生成RDB文件并使用缓冲区记录此后执行的所有写命令。

3、master发送快照文件到slave,并在发送期间继续记录被执行的写命令。4、slave收到快照文件后丢弃所有旧数据,载入收到的快照。

5、master快照发送完毕后开始向slave发送缓冲区中的写命令。

6、slave完成对快照的载入,开始接收命令请求,并执行来自master缓冲区的写命令。

7.1.2、增量同步

也叫指令同步,就是从库重放在主库中进行的指令。Redis会把指令存放在一个环形队列当中,因为内存容量有限,如果备机一直起不来,不可能把所有的内存都去存指令,也就是说,如果备机一直未同步,指令可能会被覆盖掉。

Redis增量复制是指Slave初始化后开始正常工作时master发生的写操作同步到slave的过程。增量复制的过程主要是master每执行一个写命令就会向slave发送相同的写命令。

7.1.3、Redis主从同步策略:

1、主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

2、slave在同步master数据时候如果slave丢失连接不用怕,slave在重新连接之后丢失重补

3、一般通过主从来实现读写分离,但是如果master挂掉后如何保证Redis的 HA呢?引入Sentinel进行master的选择。

7.2、高可用之哨兵模式

在这里插入图片描述

Redis-sentinel 本身是一个独立运行的进程,一般sentinel集群 节点数至少三个且奇数个,它能监控多个master-slave集群,sentinel节点发现master宕机后能进行自动切换。Sentinel可以监视任意多个主服务器以及主服务器属下的从服务器,并在被监视的主服务器下线时,自动执行故障转移操作。这里需注意sentinel也有single-point-of-failure问题。大致罗列下哨兵用途:

集群监控:循环监控master跟slave节点。

消息通知:当它发现有redis实例有故障的话,就会发送消息给管理员

故障转移:这里分为主观下线(单独一个哨兵发现master故障了)。客观下线(多个哨兵进行抉择发现达到quorum数时候开始进行切换)。

配置中心:如果发生了故障转移,它会通知将master的新地址写在配置中心告诉客户端。

7.3、Redis Cluster

RedisCluster是Redis的分布式解决方案,在3.0版本后推出的方案,有效地解决了Redis分布式的需求。

在这里插入图片描述

7.3.1、分区规则

在这里插入图片描述

  1. 节点取余:hash(key) % N
  1. 一致性哈希:一致性哈希环
  1. 虚拟槽哈希:CRC16[key] & 16383

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

在这里插入图片描述

  1. 节点取余:hash(key) % N
  1. 一致性哈希:一致性哈希环
  1. 虚拟槽哈希:CRC16[key] & 16383

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-lRYVDxJK-1714948744309)]

[外链图片转存中…(img-WkuwSvsx-1714948744310)]

[外链图片转存中…(img-qCwLn1ZP-1714948744310)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值