【一图流思维导图】Redis设计与实现 包括( 数据类型-数据结构) 及应用场景(登录次数校验,在线人数统计,分布式session,redis分页,判断重复注册,社交领域共同喜好,排行榜 ,topN)

参照
Redis详解(一)------ redis的简介与安装
Redis详解(二)------ redis的配置文件介绍
Redis详解(三)------ redis的六大数据类型详细用法
Redis详解(四)------ redis的底层数据结构
Redis详解(五)------ redis的五大数据类型实现原理

请添加图片描述

请添加图片描述

请添加图片描述

Redis设计与实现

安装和启动

redis-server:Redis服务器

  • /etc/redis/redis.conf

redis-cli:Redis命令行客户端

redis-benchmark:Redis性能测试工具

redis-check-aof:AOF文件修复工具

redis-check-rdb:RDB文件检查工具

配置文件redis.conf

INCLUDES

MODULES

  • loadmodule 引入自定义模块

NETWORK

  • bind

  • port

  • timeout

    • 客户端连接时的超时时间,单位为秒
  • tcp-keepalive

    • 周期性的使用SO_KEEPALIVE检测客户端是否还处于健康状态,避免服务器一直阻塞

GENERAL

  • daemonize

  • pidfile

  • loglevel

    • debug
    • verbose
    • notice
    • warning
  • logfile

  • databases

    • select 命令选择一个不同的数据库

SNAPSHOTTING

  • 持久化操作

  • .save 300 10

    • 表示300 秒内如果至少有 10 个 key 的值变化,则保存
  • stop-writes-on-bgsave-error

    • 默认值为yes
  • rdbcompression

    • 默认值是yes 压缩存储

      • LZF算法进行压缩
  • rdbchecksum

    • 默认值是yes 使用CRC64算法来进行数据校验
  • dbfilename

    • 快照的文件名,默认是 dump.rdb
  • dir

REPLICATION

  • slave-serve-stale-data

    • 当一个 slave 与 master 失去联系,
      或者复制正在进行的时候,是否应答客户端请求

    • 默认值为yes

      • slave 仍然会应答客户端请求
      • 返回的数据可能是过时,或者是空的
    • no

      • info he salveof 操作之外
      • 返回一个 “SYNC with master in progress” 的错误
  • slave-read-only

    • 默认值为yes

      • Slave是否为只读Redis
  • repl-diskless-sync

    • 默认值为no

      • 不使用无硬盘复制功能
  • repl-diskless-sync-delay

    • 当启用无硬盘备份

    • 服务器等待一段时间后才会通过套接字
      向从站传送RDB文件

    • 默认5秒

      • 等待时间是可配置的
  • repl-disable-tcp-nodelay

    • 默认值为no

      • 同步之后是否禁用从站上的TCP_NODELAY
  • repl-ping-slave-period

    • 节点超时时间

SECURITY

  • rename-command 命令重命名

    • flushdb

    • flushall

      • rename-command FLUSHALL “”
    • config

    • keys

  • requirepass

CLIENTS

  • maxclients

    • 客户端最大并发连接数,默认0无限制

    • 当客户端连接数到达限制时

      • redis会关闭新的连接并向客户端返回max number of clients reached错误

MEMORY MANAGEMENT

  • maxmemory

    • Redis的最大内存,如果设置为0 。表示不作限制
  • maxmemory-policy

    • 内存清除策略

    • volatile-lru 利用LRU算法移除设置过过期时间的key (LRU:最近使用 Least Recently Used )

    • allkeys-lru 利用LRU算法移除任何key

    • volatile-random 移除设置过过期时间的随机key

    • allkeys-random 移除随机key

    • volatile-ttl 移除即将过期的key(minor TTL)

    • noeviction noeviction 不移除任何key

      • 默认

APPEND ONLY MODE

  • appendonly

    • 默认no,使用rdb方式持久化

      • redis如果中途宕机,会导致可能有几分钟的数据丢
    • Append Only File是另一种持久化方式

      • 每次写入的数据在接收后都写入appendonly.aof文件
  • appendfilename

    • aof文件名,默认是"appendonly.aof"
  • appendfsync

    • aof持久化策略的配置
    • no 表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快
    • always 表示每次写入都执行fsync,以保证数据同步到磁盘
    • everysec 表示每秒执行一次fsync,可能会导致丢失这1s数据
  • no-appendfsync-on-rewrite

    • 默认设置为no

      • aof重写或者写入rdb文件的时候 执行fsync
      • 对持久化特性来说这是更安全的选择
    • yes

      • rewrite期间对新写操作不fsync,
        暂时存在内存中,等rewrite完成后再写入
      • Linux的默认fsync策略是30秒
        可能丢失30秒数据
  • auto-aof-rewrite-percentage

    • 即当aof文件增长到一定大小的时候,
      Redis能够调用bgrewriteaof对日志文件进行重写

    • 设置为100

      • 二倍
  • auto-aof-rewrite-min-size

    • 重写的最小aof文件大小
  • aof-load-truncated

    • redis宕机或者异常终止不会造成尾部不完整现象,可以选择让redis退出

    • 默认值为 yes

      • 当截断的aof文件被导入的时候,
        会自动发布一个log给客户端然后load
    • no

      • 用户必须手动redis-check-aof修复AOF文件才可以。默认值为 yes

LUA SCRIPTING

  • lua-time-limit

    • 默认值为5000

      • 一个lua脚本执行的最大时间,单位为ms

REDIS CLUSTE

  • cluster-enabled

    • 集群开关,默认是不开启集群模式
  • cluster-config-file

    • 集群配置文件的名称

    • 这个配置文件有Redis生成并更新

      • 默认配置为nodes-6379.conf
  • cluster-node-timeout

    • 集群节点超时毫秒数 15000
  • cluster-slave-validity-factor

    • 该参数用来判断slave节点与master断线的时间是否过长

    • 故障转移的时候,全部slave都会请求申请为master

    • 配置值为10

      • 表示对于断开时间比较长的slave排除掉,
        防止 数据过于陈旧
    • (node-timeout * slave-validity-factor) + repl-ping-slave-period

      • 30*10+10
  • cluster-migration-barrier

    • 配置值为1

      • master的slave数量大于该值,slave才能迁移到其他孤立master上
  • cluster-require-full-coverage

    • 默认为yes,集群全部的slot有节点负责,集群状态才为ok

    • no 不建议

      • 可以在slot没有全部分配的时候提供服务

六大数据类型

string

  • 介绍

    • string 类型是二进制安全的

      • 可以包含任何数据,比如图片或者序列化的对象
    • 一个 redis 中字符串 value 最多可以是 512M

  • 命令

    • set

      • get

        • set 如果已存在,覆盖,无视类型
    • mset

      • mget

        • 能够极大的提高操作效率
    • setex

      • 设置值和超时

        • 原子操作
    • setnx

      • 当key不存在时,成功

      • 如果存在,不做任何操作

      • Redis单线程的特点

        • 可以用于实现分布式锁
    • ttl

    • 自增自减操作

      • incr

        • decr

          • incr 如果不存在,先赋值0,再执行incr
      • incrby

        • decrby
  • 使用场景

    • 计数

      • 统计系统的在线人数
      • 不用考虑并发造成计数不准的问题,
        通过 incrby 命令,我们可以正确的得到我们想要的结果
    • 限制次数

      • 登录次数校验

        • 错误超过三次5分钟内就不让登录了
        • 每次登录设置key自增一次
        • 并设置该key的过期时间为5分钟后
        • 每次登录检查一下该key的值来进行限制登录
      • 秒杀接口限流

hash

  • 键值对集合

    • key /field

    • value

      • (key-value)
    • 类似Map<String,Map<String,Object>> 集合

  • 命令

    • hset

      • hget

        • hdel

          • hexits
    • hmget

      • hmset
    • hincrby

    • hgetall

      • hkeys

        • hlen

          • hvals
  • 使用场景

    • 查询的时间复杂度是O(1),用于缓存一些信息

list

  • 是简单的字符串列表

    • 添加一个元素到列表的头部(左边)或者尾部(右边)
    • 底层实际上是个链表
  • 特点

    • 有序
    • 可以重复
  • 命令

    • lpush

      • 插入到表头
      • 如果不是列表类型,报错
      • 要求列表key要存在
    • lpop

      • rpush

        • rpop
    • rpoplpush

      • 将source列表最后一个元素弹出 返回
      • 将source弹出的元素插入到destinate列表的表头
      • 原子
    • lindex

    • llen

    • lrange

      • start stop区间
    • ltrim

      • start stop区间
    • lset

      • 依据index
    • lrem

      • 依据count 查询

        • 0 全部移除
        • 0 从左依次移除指定数量

        • <0 从右依次移除指定数量
  • 使用场景

      • 通过命令 lpush+lpop
    • 队列

      • 命令 lpush+rpop
    • 有限集合

      • 命令 lpush+ltrim
    • 消息队列

      • 命令 lpush+brpop

set

  • set 是 string 类型的无序集合

  • 特点

    • 无序
    • 不可重复
  • 命令

    • sadd

      • srem
    • scard

      • smenbers

        • sismenber
    • 创建set1 set2

      • sadd s1 1 2 3
      • sadd s2 4 5 6
    • sdiff

      • sdiff set1 set2

        • 1 2 3
    • sinter

      • sinter set1 set2

        • empty
    • sunion

      • sunion set1 set2

        • 1 2 3 4 5 6
    • smove

      • smove set1 set2 1

        • 把1从set1移到set2,set1中删除
      • smove set1 set2 8

        • 8不在set1里面 不做任何事情 返回0
    • spop

      • spop set1

        • 移除并返回set1里面随机元素
      • spop set1 5

        • 3>set1个个数 全部返回
        • 0<count<len 返回count个元素,元素不相同
        • count <0 返回个数为count绝对值的元素 元素可能重复
    • srandmember

      • 相比spop 返回一个随机元素 不移除
    • sunionstore

    • sdiffstore

    • sinterstore

  • 场景

    • 集合的交并集特性
      社交领域的相关业务

      • 我们可以很方便的求出多个用户的共同好友,
        共同感兴趣的领域等

zset

  • sorted set 有序集合)

    • 排序依据add时的 score
  • 特点

    • 有序
    • 不可重复
  • 命令

    • zadd

      • zrem
    • zscard

    • zcount

      • score>=min且score<=max
    • zrange

      • 成员位置按照score从小到大排序
      • -1 表示最后一个元素
    • zrank

      • 返回元素在集合中的排名

        • 按从小到大排序
    • zrevrank

      • 返回元素在集合中的排名

        • 按从大到小排序
    • zremrangebyrank

      • rank>=start且rank<=end
    • zremrangebyscore

      • score>=min且score<=max
  • 使用场景

    • 社交领域的相关业务

    • 利用zset 的有序特性

      • 排行榜

stream

  • 介绍

    • Redis Stream 的内部,其实也是一个队列,每一个不同的key,对应的是不同的队列

    • 每个队列的元素,也就是消息,都有一个msgid

      • 需要保证msgid是严格递增的
    • 消息是默认持久化的,即便是Redis重启,也能够读取到消息

    • stream可以做到多播的呢

      • 消费者Group

        • 不同的消费组,可以消费同一个消息
        • 每个消费组,都维护一个Idx下标
        • 每次进行消费,都会更新一下这个下标,
          往后面一位进行偏移

系统相关命令

  • client list

    • config get
  • debug object key

    • dbsize
  • flushall

    • flushdb id

      • select
  • info

    • lastsave

      • minitor
  • BGSAVE

    • BGREWRITEAOF

key相关命令

  • del

    • ttl

      • pttl

        • type
  • exits

    • randomkey
  • keys

  • expire

    • expireat

      • persis
  • migtate

    • move
  • rename

    • 会覆盖
  • OBJECT ENCODING key

    • set k1 str

      • 显示类型 embstr
    • set k2 123

      • 显示类型 int

6种底层数据结构

Redis 是用 C 语言写的

SDS

  • 简单动态字符串(simple dynamic string,SDS) 不是简单的C 字符串

  • 结构

    • struct sdshdr

      • int len;

      • int free

      • char buf[];

        • ‘\0’
  • 特性

    • 常数复杂度获取字符串长度

      • 读取 len 属性,时间复杂度为 O(1)
    • 杜绝缓冲区溢出

      • 依据len空间扩展,不会出现缓冲区溢出
    • 减少修改字符串的内存重新分配次数

      • 空间预分配策略

        • 对字符串进行空间扩展的时候
        • 扩展的内存比实际需要的多
        • 可以减少连续执行字符串增长操作所需的内存重分配次数
      • 惰性空间释放策略

        • 对字符串进行缩短操作时
        • 不立即使用内存重新分配来回收缩短后多余的字节
        • 使用 free 属性将这些字节的数量记录下来,等待后续使用
    • 二进制安全

    • 兼容部分 C 字符串函数

list

  • 链表

  • 结构

    • listNode

      • struct listNode *prev;
      • struct listNode *next;
      • void *value;
    • list

      • listNode *head;

      • listNode *tail;

      • unsigned long len;

      • void (*free) (void *ptr);

        • 节点值复制函数
      • void (*free) (void *ptr);

        • 节点值释放函数
      • int (*match) (void *ptr,void *key);

        • 节点值对比函数
  • 特性

    • 双端

      • 链表具有前置节点和后置节点的引用,获取这两个节点时间复杂度都为O(1)
    • 无环

      • 表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问都是以 NULL 结束。
    • 带链表长度计数器

      • 通过 len 属性获取链表长度的时间复杂度为 O(1)
    • 多态

      • 链表节点使用 void* 指针来保存节点值,可以保存各种不同类型的值。

dictht

  • 字典

    • 符号表或者关联数组、或映射(map)
    • 是一种用于保存键值对的抽象数据结构
    • 字典中的每一个键 key 都是唯一的,通过 key 可以对值来进行查找或修改
    • Redis 的字典使用哈希表作为底层实现
  • 结构

    • dictht

      • dictEntry **table;

        • 哈希表数组
      • unsigned long size;

        • 哈希表大小
      • unsigned long sizemask;

        • 哈希表大小掩码,用于计算索引值
      • unsigned long used;

        • 哈希表已有节点的数量

          • size 4,有2个节点dictEntry
    • dictEntry

      • void *key;

      • union{
        void *val;
        uint64_tu64;
        int64_ts64;
        }v

          • 值可以是一个指针,也可以是uint64_t整数,也可以是int64_t整数
      • struct dictEntry *next;

        • 指向下一个哈希表节点,形成链表

        • hash冲突

          • 开放地址法
          • 这里用的是 链地址法
  • 特性

    • 哈希算法

    • 解决哈希冲突

    • 扩容和收缩

      • rerehash(重新散列)

      • 扩展

        • 基于原哈希表创建一个大小等于 ht[0].used*2n 的哈希表
        • 已使用的空间扩大一倍创建另一个哈希表
      • 收缩

        • 每次收缩是根据已使用空间缩小一倍创建一个新的哈希表
    • 触发扩容的条件

      • 载因子 = 哈希表已保存节点数量 / 哈希表大小

      • 没有执行 BGSAVE 命令或者 BGREWRITEAOF 命令

        • 负载因子大于等于1
      • 前正在执行 BGSAVE 命令或者 BGREWRITEAOF 命令

        • 负载因子大于等于5
    • 渐近式 rehash

      • 扩容和收缩操作不是一次性、集中式完成的,而是分多次、渐进式完成的
      • 字典的删除查找更新等操作可能会在两个哈希表上进行
      • 增加操作,一定是在新的哈希表上进行的。

zskiplist

  • 跳跃表

    • 跳跃表(skiplist)是一种有序数据结构,
      它通过在每个节点中维持多个指向其它节点的指针,
      从而达到快速访问节点的目的
  • 结构

    • zskiplistNode

      • 跳跃表节点

      • zskiplistLevel{} level[]

        • struct zskiplistNode *forward;

          • 前进指针
        • unsigned int span;

          • 跨度
      • truct zskiplistNode *backward;

        • 后退指针
      • double score;

        • 分值
      • robj *obj;

        • 成员对象
    • zskiplist

      • 多个跳跃表节点构成一个跳跃表

      • structz skiplistNode *header, *tail;

        • 表头节点和表尾节点
      • unsigned long length;

        • 节点的数量
      • int level;

        • //表中层数最大的节点的层数
  • 性质

    • 由很多层结构组成

    • 每一层都是一个有序的链表,
      排列顺序为由高层到底层

      • 都至少包含两个链表节点,
        分别是前面的head节点和后面的nil节点
    • 最底层的链表包含了所有的元素

    • 上一层的元素是当前层的元素的子集

    • 链表中的每个节点都包含两个指针

      • 一个指向同一层的下一个链表节点
      • 另一个指向下一层的同一个链表节点
  • 功能

    • 搜索

      • 从最高层的链表节点开始
    • 插入

      • 首先确定插入的层数

        • 抛一枚硬币方法

          • 正面就累加,直到遇见反面为止
      • 则需要将新元素插入到从底层到k层

    • 删除

      • 各个层中找到包含指定值的节点,然后将节点从链表中删除即可
      • 如果删除以后只剩下头尾两个节点,则删除这一层

intset

  • 整数集合 保存整数值的集合抽象数据类型

    • 可以保存类型为int16_t、int32_t 或者int64_t 的整数值
    • 并且保证集合中不会出现重复元素
  • 结构

    • uint32_t encoding;

    • uint32_t length;

    • int8_t contents[];

      • 保存元素的数组

        • 按照从小到大的顺序排列
        • 并且不包含任何重复项
        • 由真正类型有 encoding 来决定
  • 特性

    • 升级

      • 当新增的元素类型比原集合元素类型的长度要大时
      • 升级能极大地节省内存
    • 降级

      • 整数集合不支持降级操作
      • 一旦对数组进行了升级,编码就会一直保持升级后的状态

ziplist

  • 压缩列表(ziplist)是Redis为了节省内存而开发的

  • 结构

    • 压缩表

      • zlbytes

      • zltail

      • zllen

        • 节点数量
      • entry1 entry2…

        • 节点列表
      • zlend

        • 0xFF
    • 压缩表节点

      • previous_entry_ength

        • 记录压缩列表前一个字节的长度
        • 当前节点位置减去上一个节点的长度即得到上一个节点的起始位置
        • 压缩列表可以从尾部向头部遍历。这么做很有效地减少了内存的浪费
      • encoding

        • 类型一共有两种

          • 一种字节数组
          • 一种是整数
      • content

        • 节点内容类型和长度由encoding决定

总结

  • 简单字符串SDS

  • 链表可以保存各种不同类型的值

    • 除了用作列表键
    • 还在发布与订阅、慢查询、监视器等方面发挥作用
  • 字典底层使用哈希表实现

    • 每个字典通常有两个哈希表

      • 一个平时使用
      • 另一个用于rehash时使用
    • 使用链地址法解决哈希冲突

  • 跳跃表通常是有序集合的底层实现之一,表中的节点按照分值大小进行排序

  • 整数集合是集合键的底层实现之一,底层由数组构成,升级特性能尽可能的节省内存。

  • 压缩列表是Redis为节省内存而开发的顺序型数据结构,通常作为列表键和哈希键的底层实现之一。

数据类型-数据结构

简介

  • Redis中,并没有直接使用这些数据结构来实现键值对数据库

  • 而是基于这些数据结构创建了一个对象系统(五大数据类型)

  • 每一种数据类型都至少用到了一种数据结构

  • 通过这五种不同类型的对象

    • Redis可以在执行命令之前,根据对象的类型判断一个对象是否可以执行给定的命令
    • 而且可以针对不同的场景,为对象设置多种不同的数据结构,从而优化对象在不同场景下的使用效率。

redisObject

  • 每次在Redis数据库中创建一个键值对时,至少会创建两个对象,一个是键对象,一个是值对象

  • 而Redis中的每个对象都是由 redisObject 结构来表示

  • 结构

    • unsigned type:4;

      • 类型
    • unsigned encoding:4;

      • 编码
    • void *ptr;

      • 指向底层数据结构的指针
    • int refcount;

      • 引用计数

      • 内存回收

        • 创建一个新对象,属性 refcount 初始化为1
        • 对象被一个新程序使用,属性 refcount 加 1
        • 对象不再被一个程序使用,属性 refcount 减 1
        • 当对象的引用计数值变为 0 时,对象所占用的内存就会被释放。
      • 内存共享

        • set k1 100,

          • set k2 100,
        • 目前只支持整数值的字符串对象

        • 将数据库键的值指针指向一个现有值的对象

        • 将被共享的值对象引用refcount 加 1

    • unsigned lru:22;

      • 记录最后一次被程序访问的时间

      • 对象的空转时长

        • OBJECT IDLETIME 命令可以打印给定键的空转时长
        • 通过将当前时间减去值对象的 lru 时间计算得到
      • 可以配合前面内存回收配置使用

  • 键总是一个字符串对象

  • 而值可以是字符串、列表、集合等对象

  • 我们说一个键为集合键时,表示的是这个键对应的值为集合对象。

type

  • type key

    • 判断对象类型
  • string

    • int
    • embstr
    • raw
  • list

    • ziplist(压缩列表)

      • 编码转换

        • 列表保存元素个数小于512个

          • list-max-ziplist-entries
        • 每个元素长度小于64字节

          • list-max-ziplist-value
    • rpush numbers 1 “three” 5

    • linkedlist(双端链表)

  • hash

    • ziplist

      • 新增的键值对是保存到压缩列表的表尾

      • 编码转换

        • 列表保存元素个数小于512个

          • set-max-intset-entries
        • 每个元素长度小于64字节

    • hset profile name “Tom”

      • hset profile age 25
    • hashtable

      • 底层使用字典数据结构
      • 哈希对象中的每个键值对都使用一个字典键值对
  • set

    • intset

      • 集合对象包含的所有元素都被保存在整数集合中

      • 编码转换

        • 集合对象中所有元素都是整数

        • 集合对象所有元素数量不超过512

          • set-max-intset-entries
    • SADD Dfruits “apple” “banana” “cherry”

    • hashtable

      • 字典作为底层实现

        • 字典的每个键都是一个字符串对象
        • 字典的值则全部设置为 null
        • 可以对比java HashSet 与HashTable
  • zset

    • 有序集合对象是有序的

      • 与列表使用索引下标作为排序依据不同,有序集合为每个元素设置一个分数(score)作为排序依据
    • ziplist

      • 每个集合元素使用两个紧挨在一起的压缩列表节点来保存

      • 压缩列表内的集合元素按分值从小到大的顺序进行排列

      • 编码转换

        • 保存的元素数量小于128

          • zset-max-ziplist-entries
        • 保存的所有元素长度都小于64字节

          • zset-max-ziplist-value
    • ZADD price 8.5 apple 5.0 banana 6.0 cherry

    • skiplist+dict

      • 一个 zset 结构同时包含一个字典和一个跳跃表

      • dict

        • 字典的键保存元素的值
        • 字典的值则保存元素的分值
      • skiplis

        • 跳跃表节点的 object 属性保存元素的成员
        • 跳跃表节点的 score 属性保存元素的分值
      • 这两种数据结构会通过指针来共享相同元素的成员和分值

      • 假如我们单独使用 字典

        • 虽然能以 O(1) 的时间复杂度查找成员的分值
        • 字典是以无序的方式来保存集合元素,
          所以每次进行范围操作的时候都要进行排序
      • 假如我们单独使用跳跃表来实现

        • 虽然能执行范围操作
        • 但是查找操作有 O(1)的复杂度变为了O(logN)。

encoding

  • OBJECT ENCODING key

    • 查看值对象的编码:
  • int

    • 整数类型
  • embstr

    • embstr编码的简单字符串
  • raw

    • 简单动态字符串
  • ht

    • 字典
  • linkedlist

    • 双端链表
  • ziplist

    • 压缩列表
  • intset

    • 整数集合
  • skiplist

    • 跳跃表和字典

五大数据类型 应用场景

string

  • string 类型是二进制安全的,可以用来存放图片,视频等内容

  • 另外由于Redis的高性能读写功能,而string类型的value也可以是数字,可以用作计数器(INCR,DECR)

    • 比如分布式环境中统计系统的在线人数,秒杀等
    • 登录时密码输入失败次数限制

hash

  • value 存放的是键值对 查询速度快

    • 比如可以做单点登录存放用户信息

list

  • 队列
  • 有限集合
  • 可以实现简单的消息队列
  • 另外可以利用lrange命令,做基于redis的分页功能

set

  • 由于底层是字典实现的,查找元素特别快

  • 另外set 数据类型不允许重复,
    利用这两个特性我们可以进行全局去重

    • 比如在用户注册模块,判断用户名是否注册
  • 另外就是利用交集、并集、差集等操作

    • 可以计算共同喜好,全部的喜好,自己独有的喜好等 社交领域功能

zset

  • 有序的集合,可以做范围查找

    • 排行榜应用,取 TOP N 操作等

XMind - Trial Version

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值