Redis 简介
Redis 提供数据缓存服务,内部数据都存在内存中,所以访问速度非常快。
早期,Redis 单应用服务亦能满足企业的需求。之后,业务量的上升,单机的读写能力满足不了业务的需求,技术上实现主从服务,并读写分离,分担主 Master 的读负担。再之后,出现了哨兵集群,和现在的 Cluster 集群。
如图,首先简单介绍与了解下各阶段的服务方式:
主从模式:
以下为哨兵模式:
简单介绍下哨兵模式:
哨兵本身是一个小集群,Redis 本身为一主多从。哨兵模式只提供单一节点(主节点)对外服务,当主节点出现问题时,将出现瞬断问题,在进行选举时,不能提供服务;
哨兵两两通讯,Redis 两两通讯,哨兵与 Redis 间两两通讯。哨兵与 Redis 节点间,都是通过心跳来监控状态,哨兵本身就是一个小集群;
每个哨兵监控所有主从节点。哨兵监控着 Redis 的运行状况,确定主节点以及从节点,保存清单,当主节点挂掉时,进行主节点的选举,更新服务列表;
Server 首先通过哨兵(Sentinel)来确定访问那台 Redis 服务器 服务端使用 Redis 时,优先访问哨兵集群,获取可访问的 Redis 服务的 IP 和端口。
接下来主要讲的集群模式,Cluster(Redis 3.0 新特性):
首先简单介绍下 Cluster 模式的集群,后续详细讲解:
集群对外统一。在使用集群时,只需要关注 Redis 各个节点的 IP 和端口,至于读写节点、主从等无需关注;
集群内部协调。主从节点在集群部署时已选举完成,除非节点挂掉,会进行重新选举。其次删除相关配置项时,也会重新选举主节点;
去中心化。本模式不再如哨兵、Codise 等,需要第三方监控。Cluster自行加入选举,完成主节点选举,以及读写访问控制。
Redis 详解
Redis 单服务的安装
基本操作,若已熟悉,可跳过此节
系统环境:CentOS 7
安装内容:
安装编译工具 yum -y install gcc
官网下载 Redis 源码,Wget 也可直接安装(Yum): Wget http://download.redis.io/releases/redis-5.0.2.tar.gz 解压 tar xzf redis-5.0.2.tar.gz 进入文件夹 cd redis-5.0.2 编译与安装 make & make install
编译完成后,在文件夹内会生成src目录,启动等脚本都在改目录内。
进入目录:cd src 启动 Redis,注意,此时都使用默认配置,后续会详细介绍配置文件:./redis-server ../redis.conf 通过命令查看是否成功:ps -ef|grep redis默认端口为 6379
可通过 Redis 工具连接服务,并进行设置与取值。
./redis-cli 可直接连接 Redis 服务端(此处未设置密码) set key value get key 退出:quit
Redis 存储数据结构
Redis 总的以 HashTable 存储,内部存储 5 种数据结构。(我们此次使用的 Redis 版本为 5.0.2)
Key-String 字符串存储;
Key-List 列表存储;
Key-Hash 哈希存储;
Key-Set 集合存储;
Key-Zset 有序集合存储。
首先来看下数据结构:
按顺序展示存储方式:
字符串存储,这个就没必要多说了,直接设置值,取值;
-
列表存取,可参考文章 《Redis 中 List 常用命令》。可以看做一个双端队列的值,可从左右插入值,左右弹出值。具体命令可以搜索文章使用,当然在实际项目中,使用代码操作,命令不常用,不必死记硬背(可保存在自己能快速查看的地方,便于突发情况下使用)。 如:
LPUSH key value [value ...] 左端插入 RPUSH key value [value ...] 右端插入 LPOP key 左端弹出 RPOP key 右端弹出
-
哈希存取 可看做对象的使用,key 为对象整体,内部字段用作第二属性存取。 如:
设置一本书的作者为mayun; hset book author mayun; 得到作者名称mayun; hget book author
-
集合存取 与 Java 内 HashSet 相同,无序唯一的键值对。
sadd key value sadd key value1 value2 value3 smembers key 无序结果集 sismember key value 相当于 contains scard key 获取长度 count spop key 弹出
-
有序集合存取 zset 似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。 可用于需要排序的数据,如分数、时间等。
zadd key score value zrange key s1 s2 按 score 排序列出,s1、s2 为排名范围,第 s1 至 s2 名 zrevrange key s1 s2 逆序 zscore key value 获取 score zrank key value 获取排名 zrangebyscore key s1 s2 遍历范围内分值的 zset zrangebyscore key -inf s2 withscores 根据分值范围遍历(负无穷,s2),inf 代表 infinite zrem key value 删除 value。
Redis5.0 新特性:Stream 参考此文
其他高级命令: keys 全量遍历键(谨慎使用) scan 渐进式遍历键 >scan 0 match * count 1000 从游标位置,匹配 1000 条记录,返回游标加集合 >scan p1 match p2 count p3 其中,3 个参数,p1:游标,p2:匹配内容,p3:查询数量。
Redis 特点、实现&部分配置
大家都知道 Redis 服务速度快,有一点是因为在内存操作,那么还有什么地方体现么?
连接请求使用单线程,无线程间切换损耗 此处注意,单线程只是接收命令的入口使用单线程。还有其他线程用于处理持久化,AOF 重写以及其他操作。
-
I/O 多路复用 下一节介绍网络编程的几种模型。
内存数据,基本操作在纳秒级别。缓存数据被存在内存中,访问速度非常迅速。但需要注意的是,避免使用消耗资源较大的命令,如 keys ,若数据量小时没问题,如果数据量非常庞大时,将会影响 Redis 的性能,比如数据的传输变慢,占用内存飙高,CPU 上升等问题。
Redis 服务重启后数据会如何?
-
快照持久化:默认开启,配置快照持久化后,会生成一个快照文件(相当于将数据打包并且压缩备份好,默认:dump.rdb),并可以配置生成快照文件的频率。启动加载 RDB 文件。 配置文件名与文件保存地址。
配置触发方式,看到注释内说明,在 N 秒内发生 X 次变化,则会 dump 一次,生成快照文件。 默认开启压缩:
rdbcompression yes
生成在配置的目录。
-
AOF 持久化:默认关闭,开启此配置后,将会生成一个 AOF 文件,内部记录的 Redis 启动后执行的所有命令。启动执行 AOF 文件内的命令。
首先开启配置:
appendonly yes 配置文件名: appendfilename "appendonly.aof" 生成文件目录与 RDB 路径相同。
那么这个 AOF 文件的生成有什么规则呢?这样的配置,涉及持久化文件的重写,配置:
- 文件扩大 100% 的时候重写;
- 文件大于 64M 的时候才能触发重写动作。 这里所说的重写是指,Redis 启动后,除了生成快照的持久化,还有 AOF 记录命令的持久化,当 AOF 文件过大,且达到重写的条件后,将 AOF 内的命令与 RDB 文件结合,重新生成 RDB,而 AOF 内的命令将会清除,开始记录新的命令。
持久化文件有了,并且理论上大小不限,那么 Redis 的数据就要收到内存的限制了,那么是否会将内存撑爆呢?
看图说话:
查看内存管理模块,可以看到一大部分注释,最后一行,默认是注释掉的,就相当于不限制 Redis 的内存使用,那么如果数据了持久增加的情况下,内存会爆掉。
可以适当设置内存的大小,比如设置 8GB,在集群的情况下,不需要设置太高,保持良好性能。
设置好了内存大小限制,那么当内存满了之后,Redis 将会如何处理呢?
此时,需要提供缓存淘汰策略了,配置当内存达到最大时,按照一定规则去删除内存中旧的值。
可以看到,Redis 提供了多种缓存淘汰策略,含义在注释中说的很清楚。
LRU:表示最近最少使用; LFU:表示最不常用的。
区别在于:LFU 是一定时间内访问最少的,比如 10 分钟内访问最少的,而 LRU 则是指服务启动后,访问量最少的内容。 下面按顺序说明下淘汰策略:
- 筛选出设置了有效期的,最近最少使用的 key;
- 所有 key 中,筛选出最近最少使用的 key;
- 筛选出设置了有效期的,最不常用的 key;
- 所有 key 中,筛选出最不常用的 key ;
- 随机筛选出设置了有效期的 key;
- 所有 key 中,随机筛选出 key进行删除;
- 筛选出所有设置有效期的 key 中,有效期最短的 key;
- 拒绝策略,当内存满了之后,服务不做任何处理,直接返回一个错误 Redis 默认是拒绝策略,可根据实际情况做出设置。
这样做的目的是什么呢?
在 Redis 服务重启时,快照数据恢复的时间,比 AOF 一行行执行命令快;
AOF 是实时记录 Redis 执行的命令的,不会造成命令丢失,而快照是要满足一定条件才能出发(如,记录变动了 1 次,不满足更新快照的条件,此时服务宕机了,那么重启时,这次变动的数据就丢失了,而 AOF 记录下了执行的命令)。这样就防止了数据的丢失;
快速恢复快照的内容,之后再执行剩余的 AOF 内的命令,完整恢复数据。
Redis 的密码如何设置呢?
找到安全模块,设置自己的密码,不需要可注释掉。
最后就是端口的设置,找 port 的地方,修改即可。
此处修改端口,另外注意,上面有个 bind,就是绑定 IP 地址(哪些 IP 可访问服务),默认是注释掉的。
以上为一些基础介绍与配置,接下来我们先来了解下网络模型的知识,有利于理解各类框架间的通讯。
网络编程模型介绍
阻塞式 I/O 模型,单一通道直接发起请求,等待返回信息,期间一直阻塞(等待通道可用,以及处理业务,其他通讯请求挂起),处理后,返回完成;
非阻塞式 I/O 模型 (NIO),多通道检查通道是否可用,选择可用通道,发送请求,阻塞(此时直接处理业务,该通道挂起),返回完成;
I/O 复用模型,检查通道是否可用,就绪,发送请求,阻塞(处理业务),返回完成与 NIO 类似,主要区别在于就绪这一步骤,体现在复用上,就绪的通道可以同时发送多个请求命令;
信号驱动模型,等待通知,得到通知后获取可用通道,发送请求,阻塞(处理业务),返回完成;
异步 I/O 模型,发情请求,不需要等待返回通知(回调)完成。
大概了解通讯模型,助于理解网络间的通讯方式。关于通讯方面的知识,可学习分布式内容体系,涉及到 NIO,Netty,以及各类框架(此处不介绍了,有机会分享再见)。
Cluster 集群配置&部署
注意:本人是在单台服务器上,使用了 Docker 进行安装。如果大家也在单机上部署,可以使用 KVM、Docker,或者 Windows 上使用 VMware 安装 3 个实例。(建议使用 CentOS 或者 Linux)
由于分多次编辑,所以会出现不在同一电脑截图,上图片为 MAC 截图,其中 rds1-3 为 3 个安装了 Redis 的容器。
为了选举,Redis 集群至少 3 主 3 从,当一个主从都挂掉后,将不能提供服务(也可配置 2 台提供服务):
配置介绍
首先,使用守护进行,在后台运行程序:在配置文件内将 daemonize 设置为 true。(查找某个内容:首先输入:/,然后输入要搜索的内容,回车即可;后续介绍一些简单命令)
设置事项: >端口,每一个主从节点的端口修改。 >储存地址,每个主从节点的文件存放地址,查找 dir,修改文件地址,上文已提到。 >启动模式:cluster-enabled yes(启动集群模式) >节点信息:cluster-config-file nodes-80XX.conf,启动后存储各个节点的信息,下文查看生成的文件内容>集群超时时间:cluster-node-timeout 5000 ,发生网络抖动时,判断节点是否在线 >关闭保护模式:protected-mode no,否则集群连接会失败 >密码设置:masterauth xxxxzj,之前的秘密为连接密码,这里就是配置连接时需要使用的密码,认证使用。 >其他几台相同配置,修改不同端口,不同文件存储目录,否则可能数据丢失
bind 192.168.31.142
protected-mode no
port 8002
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize yes
supervised no
pidfile /var/run/redis_6379.pid
loglevel notice
logfile ""
databases 16
always-show-logo yes
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir ./
masterauth cluster-master
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
replica-priority 100
requirepass redis-pass
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes
lua-time-limit 5000
cluster-enabled yes
cluster-config-file nodes-8002.conf
cluster-node-timeout 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
stream-node-max-bytes 4096
stream-node-max-entries 100
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
dynamic-hz yes
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes
启动
第一步: 启动每个 Redis 实例,一共启动 6 个。
进入 Redis 的脚本目录,启动服务,选中配置文件。
相同的方式启动其他 5 个实例。
启动后的样子:
可以看到,此时的服务,后面比之前普通启动多了一个标示:[cluster],表示已 Cluster 集群模式启动,都启动成功后,即可执行集群创建的命令。
第二步: 随意连接一个实例
./redis-cli -c -a xxxxzj -h ip -p port -c:表示集群 -h:表示ip -p:表示端口 查看是否能连接,然后退出quit。
此时可以执行创建集群的命令了。
./redis-cli -a xxxxzj --cluster create --cluster-replicas 1 172.18.0.5:8101 172.18.0.6:8102 172.18.0.7:8103 172.18.0.5:8104 172.18.0.6:8105 172.18.0.7:8106
其中,--cluster create 表示创建集群,--cluster -replicas 1 表示备份因子,指明一个主节点拥有一个(1 的意义)从节点。
执行命令,看下精彩部分:
自动分配主从节点,并询问是否可按该配置执行,此处必须输入yes完整单词。
回车,系统开始配置集群:
开始集群的创建。
创建完成后,如图:
创建前后都可以看到主从的信息,m 为 master,s 为 slave。
之后,连接 Redis,使用
./redis-cli -c -a XXXXzj -h 172.18.0.5 -p 8101 cluster nodes 查看节点信息 172.18.0.5:8101> cluster nodes
第一列为节点 ID,之后为 IP,端口,主从节点信息,如果是从节点,会给出主节点的 ID。
cluster info
此处可以看到:
clustersize:3 集群节点,3 个主从节点 clustercurrentepoch:6 当前集群经过了 6 轮选举 clusterknownodes:6 当前集群内的实例节点,有 6 个实例 clusterslots***:16384 集群内的 hash 槽位,下文有详解 clusterstate:ok 集群状态为 ok
这里要说明下,在通讯端口之后有个更大的端口需要打开,比如 8101 端口,会有个 18101 端口同时打开,所以如果需要给外网的时候,需要开通防火墙端口,防止集群创建失败。
原理介绍
选举
集群启动后,主从已分配完成,经过了 N 轮的选举。当某一个主节点宕机,那么从节点需要经过选举成为主节点。
简单介绍选举过程:
所有子节点向其他节点发送请求,请求自身成为主节点,其他节点收到请求后,返回投票信息,只有主节点 master 有权投票,且只能投一次,当获取到的票数大于一半人数时(master 个数),就当选 master。
期间,所有子节点发送请求的时机有所有不同。所以基本都有先后顺序,所以很少会出现票数相同情况,如果相同,则重新选举,直到选出 master 为止。
故,需要至少 3 主 3 从,否则节点出现问题,将选举失败。
槽位
在 Redis 集群中,定义了 16384 个逻辑上的槽位。将这些槽位均匀分配给 N 个节点(一主一从为一个节点),此文 3 个节点,自动均匀分配。意思为,0-5460 个槽位分配给第一个节点。
当用户 set 一个值时,除了计算 key 本身的 hashCode 之外,还会调用 C 语言的一个 CRC16 算法,将 key 当 hash 值再计算出一个数字,然后与 16384 取模,得到的数字落在哪个槽位,则会将数据放在对应的节点。
比如,计算出的数字为 16387,则取模 16384 后,得到 3,在 0-5460 之间,则放入对于的第一个节点。其他以此类推。
跳转
大家都知道,主从模式中,只有主节点可以写入数据,而从节点只能读取数据。
在 Cluster 集群中,设置值时,如果计算出的槽位在另一台服务器上,则集群连接会自动跳转至相应服务器。取值类似:
看到,redirected to slot xxxx located at IP:port 表示该 key 不在当前服务器的槽位上,重定向到了 8102 的服务器上去处理命令。
get 命令也是如此:
将会自动跳转至相应的服务器。
网络抖动
网络抖动,表示网络很不稳定。当出现这样的情况,可能一小段时间连接不上,可能就认为了节点挂了。这里就涉及到连接到超时时间,在网络不稳定的情况下,可以将超时时间设置长一些。
代码示例
Java 中 Jedis 对与 Redis 的 Cluster 支持较好。
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
引用该版本。
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
/**
* 访问Redis集群
*/
public class RedisCluster
{
public static void main(String[] args) throws IOException
{
Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
// 添加集群地址
jedisClusterNode.add(new HostAndPort("172.18.0.5", 8101));
jedisClusterNode.add(new HostAndPort("172.18.0.6", 8102));
jedisClusterNode.add(new HostAndPort("172.18.0.7", 8103));
jedisClusterNode.add(new HostAndPort("172.18.0.5", 8104));
jedisClusterNode.add(new HostAndPort("172.18.0.6", 8105));
jedisClusterNode.add(new HostAndPort("172.18.0.7", 8106));
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100);
config.setMaxIdle(10);
config.setTestOnBorrow(true);
//实例化集群
JedisCluster jedisCluster = new JedisCluster(jedisClusterNode,6000, 5000, 10,"xxxxzj", config);
System.out.println(jedisCluster.set("student", "zj"));
System.out.println(jedisCluster.set("age", "30"));
System.out.println(jedisCluster.get("student"));
System.out.println(jedisCluster.get("age"));
jedisCluster.close();
}
}
基本与命令都有对应的方法,可自行查看 API 的使用,引入的包内直接查看即可。
常用命令
locate fileName 查询文件位置(安装:yum -y mlocate) find / -name fileName 查询文件位置 which redis 查询服务的目录信息 cat file 打印文件内容(大文件慎用) tail -300f fileName 动态查看文件内容,第一次打开时显示最后的 300 行 wget http://xxxxxx 下载文件(安装:yum -y wget) curl http://xxxxxx 请求路径,打印返回内容,查看是否能请求通 ps -ef | grep serverName 查看服务的信息 jps -lv 此为 JDK 工具,查看所有 Java 程序,列出信息 ip addr 查看 IP 信息(新版系统)
2、Redis 底层原理:Cluster 集群部署与详解的更多相关文章
-
redis cluster 集群 安装 配置 详解
redis cluster 集群 安装 配置 详解 张映 发表于 2015-05-01 分类目录: nosql 标签:cluster, redis, 安装, 配置, 集群 Redis 集群是一个提供在 ...
-
t持久化与集群部署开发详解
Quartz.net持久化与集群部署开发详解 序言 我前边有几篇文章有介绍过quartz的基本使用语法与类库.但是他的执行计划都是被写在本地的xml文件中.无法做集群部署,我让它看起来脆弱不堪,那是我 ...
-
Quartz.net持久化与集群部署开发详解
序言 我前边有几篇文章有介绍过quartz的基本使用语法与类库.但是他的执行计划都是被写在本地的xml文件中.无法做集群部署,我让它看起来脆弱不堪,那是我的罪过. 但是quart.net是经过许多大项 ...
-
centos6下redis cluster集群部署过程
一般来说,redis主从和mysql主从目的差不多,但redis主从配置很简单,主要在从节点配置文件指定主节点ip和端口,比如:slaveof 192.168.10.10 6379,然后启动主从,主从 ...
-
Redis 3.0 Cluster集群配置
Redis 3.0 Cluster集群配置 安装环境依赖 安装gcc:yum install gcc 安装zlib:yum install zib 安装ruby:yum install ruby 安装 ...
-
Centos 7 下 Mysql 5.7 Galera Cluster 集群部署
一.介绍 传统架构的使用,一直被人们所诟病,因为MySQL的主从模式,天生的不能完全保证数据一致,很多大公司会花很大人力物力去解决这个问题,而效果却一般,可以说,只能是通过牺牲性能,来获得数据一致性 ...
-
MySQL Cluster 集群部署
前言 此篇博客用以介绍 MySQL Cluster 集群部署方法 一.节点规划 序号 IP地址 节点名称 1 172.16.1.201 mysql-manage 2 172.16.1.202 mysq ...
-
深入浅出—Redis集群的相关详解
前言: 这篇文章主要介绍了Redis集群的相关,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值. 注意!要求使用的都是redis3.0以上的版本,因为3.0以上增加了red ...
-
hadoop1.2.1+zk-3.4.5+hbase-0.94.1集群安装过程详解
hadoop1.2.1+zk-3.4.5+hbase-0.94.1集群安装过程详解 一,环境: 1,主机规划: 集群中包括3个节点:hadoop01为Master,其余为Salve,节点之间局域网连接 ...
随机推荐
-
(转)基于socket的TCP和UDP编程
一.概述 TCP(传输控制协议)和UDP(用户数据报协议是网络体系结构TCP/IP模型中传输层一层中的两个不同的通信协议. TCP:传输控制协议,一种面向连接的协议,给用户进程提供可靠的全双工的字节流 ...
-
jquery动态创建节点
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
-
C++杂谈(一)const限定符与const指针
const限定符 c++有了新的const关键字,用来定义常变量,可以替C语言中的#define.关于const限定符,有以下需要注意: 1.创建后值不再改变 2.作用范围在文件内有效 3.添加ext ...
-
第一堂java web课
一天的课程上完,虽然很累,但是因为自己的收获,所以我很开心. 第一节课老师带我们大家学习了HTML,学了很多标签,比如:html,head,body <html></html> ...
-
Java中的数组问题
java.util.Arrays This class deals with 'real' arrays in java, in the form of T[]. Thus it doesn't d ...
-
nginx本地的测试环境添加SSL
要在本地添加SSL,首先要做的是防火墙是不是放开了443端口,同时,在nginx安装时是不是支持了ssl模块,这个安装网上很容易找到相关资料 防火墙,个人还是用iptables比较直观 先将selin ...
-
LESS使用方法简介(装逼神器)
LESS 做为 CSS 的一种形式的扩展,它并没有阉割 CSS 的功能,而是在现有的 CSS 语法上,添加了很多额外的功能,所以学习 LESS 是一件轻而易举的事情,果断学习之! 变量 很容易理解: ...
-
第三方 XListview 上拉加载、下拉刷新、分页加载和Gson解析
注意:此Demo用的是第三方的Xlistview.jar,需要复制me文件夹到项目中,两个XML布局文件和一张图片 把下面的复制到String中 <string name="xlist ...
-
[HNOI 2012]集合选数
Description 题库链接 对于任意一个正整数 \(n\) ,求出集合 \(\{1,2,\cdots,n\}\) 的满足约束条件"若 \(x\) 在该子集中,则 \(2x\) 和 \( ...
- UINavigationController 、navigationBar 、 UINavigationItem、UIBarButtonItem之间的关系