Redis集群部署文档(centos6系统)
(要让集群正常工作至少需要3个主节点,在这里我们要创建6个redis节点,其中三个为主节点,三个为从节点,对应的redis节点的ip和端口对应关系如下)
127.0.0.1:7000
127.0.0.1:7001
127.0.0.1:7002
127.0.0.1:7003
127.0.0.1:7004
127.0.0.1:7005
首先到http://download.redis.io/releases/下载redis-3.0.5.tar.gz或者其他版本的
将下载来的文件上传到服务器然后
tar -xvf redis-3.0.5.tar.gz cd redis-3.0.5 #如果不加参数,linux下会报错 make MALLOC=libc
编译好以后启动
#启动redis src/redis-server & #关闭redis src/redis-cli shutdown
测试redis
$ src/redis-cli 127.0.0.1:6379> set a test OK 127.0.0.1:6379> get a "test" $
这样就代表成功安装了
接下来安装redis cluster集群搭建
#建立redis运行目录 mkdir -p /usr/local/redis/cluster/7000/ #复制默认的配置文档 cp redis-3.0.5/redis.conf /usr/local/redis/cluster/7000/redis.conf #把编译好的server复制到运行目录 cp redis-3.0.5/src/redis-server /usr/local/redis/cluster/7000/
修改/usr/local/redis/cluster/7000/redis.conf文件
配置文件说明:
# 守护进程模式 daemonize yes # pid file pidfile /var/run/redis.pid # 监听端口 port 7003 # TCP接收队列长度,受/proc/sys/net/core/somaxconn和tcp_max_syn_backlog这两个内核参数的影响 tcp-backlog 511 # 一个客户端空闲多少秒后关闭连接(0代表禁用,永不关闭) timeout 0 # 如果非零,则设置SO_KEEPALIVE选项来向空闲连接的客户端发送ACK tcp-keepalive 60 # 指定服务器调试等级 # 可能值: # debug (大量信息,对开发/测试有用) # verbose (很多精简的有用信息,但是不像debug等级那么多) # notice (适量的信息,基本上是你生产环境中需要的) # warning (只有很重要/严重的信息会记录下来) loglevel notice # 指明日志文件名 logfile "./redis7003.log" # 设置数据库个数 databases 16 # 会在指定秒数和数据变化次数之后把数据库写到磁盘上 # 900秒(15分钟)之后,且至少1次变更 # 300秒(5分钟)之后,且至少10次变更 # 60秒之后,且至少10000次变更 save 900 1 save 300 10 save 60 10000 # 默认如果开启RDB快照(至少一条save指令)并且最新的后台保存失败,Redis将会停止接受写操作 # 这将使用户知道数据没有正确的持久化到硬盘,否则可能没人注意到并且造成一些灾难 stop-writes-on-bgsave-error yes # 当导出到 .rdb 数据库时是否用LZF压缩字符串对象 rdbcompression yes # 版本5的RDB有一个CRC64算法的校验和放在了文件的最后。这将使文件格式更加可靠。 rdbchecksum yes # 持久化数据库的文件名 dbfilename dump.rdb # 工作目录 dir ./ # 当master服务设置了密码保护时,slav服务连接master的密码 masterauth 0234kz9*l # 当一个slave失去和master的连接,或者同步正在进行中,slave的行为可以有两种: # # 1) 如果 slave-serve-stale-data 设置为 "yes" (默认值),slave会继续响应客户端请求, # 可能是正常数据,或者是过时了的数据,也可能是还没获得值的空数据。 # 2) 如果 slave-serve-stale-data 设置为 "no",slave会回复"正在从master同步 # (SYNC with master in progress)"来处理各种请求,除了 INFO 和 SLAVEOF 命令。 slave-serve-stale-data yes # 你可以配置salve实例是否接受写操作。可写的slave实例可能对存储临时数据比较有用(因为写入salve # 的数据在同master同步之后将很容易被删除 slave-read-only yes # 是否在slave套接字发送SYNC之后禁用 TCP_NODELAY? # 如果你选择“yes”Redis将使用更少的TCP包和带宽来向slaves发送数据。但是这将使数据传输到slave # 上有延迟,Linux内核的默认配置会达到40毫秒 # 如果你选择了 "no" 数据传输到salve的延迟将会减少但要使用更多的带宽 repl-disable-tcp-nodelay no # slave的优先级是一个整数展示在Redis的Info输出中。如果master不再正常工作了,哨兵将用它来 # 选择一个slave提升=升为master。 # 优先级数字小的salve会优先考虑提升为master,所以例如有三个slave优先级分别为10,100,25, # 哨兵将挑选优先级最小数字为10的slave。 # 0作为一个特殊的优先级,标识这个slave不能作为master,所以一个优先级为0的slave永远不会被 # 哨兵挑选提升为master slave-priority 100 # 密码验证 # 警告:因为Redis太快了,所以外面的人可以尝试每秒150k的密码来试图破解密码。这意味着你需要 # 一个高强度的密码,否则破解太容易了 requirepass 0234kz9*l # redis实例最大占用内存,不要用比设置的上限更多的内存。一旦内存使用达到上限,Redis会根据选定的回收策略(参见: # maxmemmory-policy)删除key maxmemory 3gb # 最大内存策略:如果达到内存限制了,Redis如何选择删除key。你可以在下面五个行为里选: # volatile-lru -> 根据LRU算法删除带有过期时间的key。 # allkeys-lru -> 根据LRU算法删除任何key。 # volatile-random -> 根据过期设置来随机删除key, 具备过期时间的key。 # allkeys->random -> 无差别随机删, 任何一个key。 # volatile-ttl -> 根据最近过期时间来删除(辅以TTL), 这是对于有过期时间的key # noeviction -> 谁也不删,直接在写操作时返回错误。 maxmemory-policy volatile-lru # 默认情况下,Redis是异步的把数据导出到磁盘上。这种模式在很多应用里已经足够好,但Redis进程 # 出问题或断电时可能造成一段时间的写操作丢失(这取决于配置的save指令)。 # # AOF是一种提供了更可靠的替代持久化模式,例如使用默认的数据写入文件策略(参见后面的配置) # 在遇到像服务器断电或单写情况下Redis自身进程出问题但操作系统仍正常运行等突发事件时,Redis # 能只丢失1秒的写操作。 # # AOF和RDB持久化能同时启动并且不会有问题。 # 如果AOF开启,那么在启动时Redis将加载AOF文件,它更能保证数据的可靠性。 appendonly no # aof文件名 appendfilename "appendonly.aof" # fsync() 系统调用告诉操作系统把数据写到磁盘上,而不是等更多的数据进入输出缓冲区。 # 有些操作系统会真的把数据马上刷到磁盘上;有些则会尽快去尝试这么做。 # # Redis支持三种不同的模式: # # no:不要立刻刷,只有在操作系统需要刷的时候再刷。比较快。 # always:每次写操作都立刻写入到aof文件。慢,但是最安全。 # everysec:每秒写一次。折中方案。 appendfsync everysec # 如果AOF的同步策略设置成 "always" 或者 "everysec",并且后台的存储进程(后台存储或写入AOF # 日志)会产生很多磁盘I/O开销。某些Linux的配置下会使Redis因为 fsync()系统调用而阻塞很久。 # 注意,目前对这个情况还没有完美修正,甚至不同线程的 fsync() 会阻塞我们同步的write(2)调用。 # # 为了缓解这个问题,可以用下面这个选项。它可以在 BGSAVE 或 BGREWRITEAOF 处理时阻止主进程进行fsync()。 # # 这就意味着如果有子进程在进行保存操作,那么Redis就处于"不可同步"的状态。 # 这实际上是说,在最差的情况下可能会丢掉30秒钟的日志数据。(默认Linux设定) # # 如果你有延时问题把这个设置成"yes",否则就保持"no",这是保存持久数据的最安全的方式。 no-appendfsync-on-rewrite yes # 自动重写AOF文件 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb # AOF文件可能在尾部是不完整的(这跟system关闭有问题,尤其是mount ext4文件系统时 # 没有加上data=ordered选项。只会发生在os死时,redis自己死不会不完整)。 # 那redis重启时load进内存的时候就有问题了。 # 发生的时候,可以选择redis启动报错,并且通知用户和写日志,或者load尽量多正常的数据。 # 如果aof-load-truncated是yes,会自动发布一个log给客户端然后load(默认)。 # 如果是no,用户必须手动redis-check-aof修复AOF文件才可以。 # 注意,如果在读取的过程中,发现这个aof是损坏的,服务器也是会退出的, # 这个选项仅仅用于当服务器尝试读取更多的数据但又找不到相应的数据时。 aof-load-truncated yes # Lua 脚本的最大执行时间,毫秒为单位 lua-time-limit 5000 # Redis慢查询日志可以记录超过指定时间的查询 slowlog-log-slower-than 10000 # 这个长度没有限制。只是要主要会消耗内存。你可以通过 SLOWLOG RESET 来回收内存。 slowlog-max-len 128 # redis延时监控系统在运行时会采样一些操作,以便收集可能导致延时的数据根源。 # 通过 LATENCY命令 可以打印一些图样和获取一些报告,方便监控 # 这个系统仅仅记录那个执行时间大于或等于预定时间(毫秒)的操作, # 这个预定时间是通过latency-monitor-threshold配置来指定的, # 当设置为0时,这个监控系统处于停止状态 latency-monitor-threshold 0 # Redis能通知 Pub/Sub 客户端关于键空间发生的事件,默认关闭 notify-keyspace-events "" # 当hash只有少量的entry时,并且最大的entry所占空间没有超过指定的限制时,会用一种节省内存的 # 数据结构来编码。可以通过下面的指令来设定限制 hash-max-ziplist-entries 512 hash-max-ziplist-value 64 # 与hash似,数据元素较少的list,可以用另一种方式来编码从而节省大量空间。 # 这种特殊的方式只有在符合下面限制时才可以用 list-max-ziplist-entries 512 list-max-ziplist-value 64 # set有一种特殊编码的情况:当set数据全是十进制64位有符号整型数字构成的字符串时。 # 下面这个配置项就是用来设置set使用这种编码来节省内存的最大长度。 set-max-intset-entries 512 # 与hash和list相似,有序集合也可以用一种特别的编码方式来节省大量空间。 # 这种编码只适合长度和元素都小于下面限制的有序集合 zset-max-ziplist-entries 128 zset-max-ziplist-value 64 # HyperLogLog稀疏结构表示字节的限制。该限制包括 # 16个字节的头。当HyperLogLog使用稀疏结构表示 # 这些限制,它会被转换成密度表示。 # 值大于16000是完全没用的,因为在该点 # 密集的表示是更多的内存效率。 # 建议值是3000左右,以便具有的内存好处, 减少内存的消耗 hll-sparse-max-bytes 3000 # 启用哈希刷新,每100个CPU毫秒会拿出1个毫秒来刷新Redis的主哈希表(顶级键值映射表) activerehashing yes # 客户端的输出缓冲区的限制,可用于强制断开那些因为某种原因从服务器读取数据的速度不够快的客户端 client-output-buffer-limit normal 0 0 0 client-output-buffer-limit slave 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 # 默认情况下,“hz”的被设定为10。提高该值将在Redis空闲时使用更多的CPU时,但同时当有多个key # 同时到期会使Redis的反应更灵敏,以及超时可以更精确地处理 hz 10 # 当一个子进程重写AOF文件时,如果启用下面的选项,则文件每生成32M数据会被同步 aof-rewrite-incremental-fsync yes
vi /usr/local/redis/cluster/7000/redis.conf
vi redis.conf ##修改配置文件中的下面选项 port 7000 daemonize yes cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes
再把redis/src下面的redis-server启动脚本移到/usr/local/redis/cluster/7000下面
cp /home/redis/src/redis-3.0.5/src/redis-server /usr/local/redis/cluster/7000/
##修改完redis.conf配置文件中的这些配置项之后把这个配置文件分别拷贝到7001/7002/7003/7004/7005目录下面 cp /usr/local/redis/cluster/7000/ /usr/local/redis/cluster/7001/ cp /usr/local/redis/cluster/7000/ /usr/local/redis/cluster/7002/ cp /usr/local/redis/cluster/7000/ /usr/local/redis/cluster/7003/ cp /usr/local/redis/cluster/7000/ /usr/local/redis/cluster/7004/ cp /usr/local/redis/cluster/7000/ /usr/local/redis/cluster/7005/ ##注意:拷贝完成之后要修改7002/7003/7004/7005目录下面redis.conf文件中的port参数,分别改为对应的文件夹的名称
分别启动这6个redis实例 cd /usr/local/redis/cluster/7000 redis-server redis.conf cd /usr/local/redis/cluster/7001 redis-server redis.conf cd /usr/local/redis/cluster/7002 redis-server redis.conf cd /usr/local/redis/cluster/7003 redis-server redis.conf cd /usr/local/redis/cluster/7004 redis-server redis.conf cd /usr/local/redis/cluster/7005 redis-server redis.conf
启动之后用ps -aux |grep redis 查看下
[root@hadoop1 7000]# ps -aux |grep redis Warning: bad syntax, perhaps a bogus '-'? See /usr/share/doc/procps-3.2.8/FAQ root 3123 0.1 0.0 129340 2616 ? Ssl 11:11 0:02 ./redis-server *:7000 [cluster] root 3135 0.1 0.0 129372 2644 ? Ssl 11:11 0:01 ./redis-server *:7001 [cluster] root 3141 0.1 0.0 129372 2680 ? Ssl 11:12 0:01 ./redis-server *:7002 [cluster] root 3145 0.1 0.0 128360 2588 ? Ssl 11:12 0:01 ./redis-server *:7003 [cluster] root 3149 0.1 0.0 128356 2600 ? Ssl 11:12 0:01 ./redis-server *:7004 [cluster] root 3153 0.1 0.0 128352 2596 ? Ssl 11:12 0:01 ./redis-server *:7005 [cluster] root 3244 0.0 0.0 103256 848 pts/0 S+ 11:34 0:00 grep redis
如果是这样的则代表成功
执行redis的创建集群命令创建集群 cd /home/redis/src/redis-3.0.5/src ./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 执行上面的命令的时候会报错,因为是执行的ruby的脚本,需要ruby的环境 错误内容:/usr/bin/env: ruby: No such file or directory 所以需要安装ruby的环境,这里推荐使用yum install ruby安装 yum install ruby 然后再执行第6步的创建集群命令,还会报错,提示缺少rubygems组件,使用yum安装 错误内容: ./redis-trib.rb:24:in `require': no such file to load -- rubygems (LoadError) from ./redis-trib.rb:24 yum install rubygems 再次执行第6步的命令,还会报错,提示不能加载redis,是因为缺少redis和ruby的接口,使用gem 安装 错误内容: /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require': no such file to load -- redis (LoadError) from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require' from ./redis-trib.rb:25 gem install redis
如果再执行还报
Connecting to node 127.0.0.1:7000: [ERR] Sorry, can't connect to node 127.0.0.1:7000
这个错误的话那就查看下你6个端口是否都启动了,没启动的话会报上面的错误
命令输入后
[root@hadoop1 src]# ./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 >>> Creating cluster Connecting to node 127.0.0.1:7000: OK Connecting to node 127.0.0.1:7001: OK Connecting to node 127.0.0.1:7002: OK Connecting to node 127.0.0.1:7003: OK Connecting to node 127.0.0.1:7004: OK Connecting to node 127.0.0.1:7005: OK >>> Performing hash slots allocation on 6 nodes... Using 3 masters: 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 Adding replica 127.0.0.1:7003 to 127.0.0.1:7000 Adding replica 127.0.0.1:7004 to 127.0.0.1:7001 Adding replica 127.0.0.1:7005 to 127.0.0.1:7002 M: ed3a2dc06dd2b07a7df2f5036c8a5c73407125e1 127.0.0.1:7000 slots:0-5460 (5461 slots) master M: b99d5eeb2122f86f8da149cab291d821316d2a61 127.0.0.1:7001 slots:5461-10922 (5462 slots) master M: cecf3694476229d11dfc9ae9117f381c84b43ab6 127.0.0.1:7002 slots:10923-16383 (5461 slots) master S: c99e2ff123d19444363a9ee4444f5b139dd1fe41 127.0.0.1:7003 replicates ed3a2dc06dd2b07a7df2f5036c8a5c73407125e1 S: e4b3b0fe2ffdfdb705f49bfb62265840aca4055b 127.0.0.1:7004 replicates b99d5eeb2122f86f8da149cab291d821316d2a61 S: 440e8743f546aa9a0d0f44d660eca577de6f1218 127.0.0.1:7005 replicates cecf3694476229d11dfc9ae9117f381c84b43ab6 Can I set the above configuration? (type 'yes' to accept): yes >>> Nodes configuration updated >>> Assign a different config epoch to each node >>> Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join... >>> Performing Cluster Check (using node 127.0.0.1:7000) M: ed3a2dc06dd2b07a7df2f5036c8a5c73407125e1 127.0.0.1:7000 slots:0-5460 (5461 slots) master M: b99d5eeb2122f86f8da149cab291d821316d2a61 127.0.0.1:7001 slots:5461-10922 (5462 slots) master M: cecf3694476229d11dfc9ae9117f381c84b43ab6 127.0.0.1:7002 slots:10923-16383 (5461 slots) master M: c99e2ff123d19444363a9ee4444f5b139dd1fe41 127.0.0.1:7003 slots: (0 slots) master replicates ed3a2dc06dd2b07a7df2f5036c8a5c73407125e1 M: e4b3b0fe2ffdfdb705f49bfb62265840aca4055b 127.0.0.1:7004 slots: (0 slots) master replicates b99d5eeb2122f86f8da149cab291d821316d2a61 M: 440e8743f546aa9a0d0f44d660eca577de6f1218 127.0.0.1:7005 slots: (0 slots) master replicates cecf3694476229d11dfc9ae9117f381c84b43ab6 [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.
然后再测试下
[root@hadoop1 src]# ./redis-cli -c -p 7000 127.0.0.1:7000> set foo bbb -> Redirected to slot [12182] located at 127.0.0.1:7002 OK 127.0.0.1:7002> set foo ccc OK 127.0.0.1:7002> get foo "ccc" 127.0.0.1:7002> set foo1 aaa OK 127.0.0.1:7002> get foo1 "aaa" 127.0.0.1:7002> set foo 222 OK 127.0.0.1:7002>
再到
cd /usr/local/redis/cluster/7002 cat appendonly.aof *2 $6 SELECT $1 0 *3 $3 set $3 foo $3 bbb *3 $3 set $3 foo $3 ccc *3 $3 set $4 foo1 $3 aaa *3 $3 set $3 foo $3 222
这样则代表集群安装成功了,接下来将接受通过Jedis来连接集群
1. redis查看当前所有的key
KEYS *
2. 查看当前redis的配置信息
CONFIG GET *
3. MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.
强制停止redis快照导致,redis运行用户没有权限写rdb文件或者磁盘空间满了,解决办法:
config set stop-writes-on-bgsave-error no
例如:
set 'name' 'shenhui' -MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error. config set stop-writes-on-bgsave-error no +OK set 'name' 'shenhui' +OK
4.(error) OOM command not allowed when used memory >
设置了maxmemory的选项,redis内存使用达到上限。
可以通过设置LRU算法来删除部分key,释放空间。
默认是按照过期时间的,如果set时候没有加上过期时间就会导致数据写满maxmemory。
如果不设置maxmemory或者设置为0 64位系统不限制内存,32位系统最多使用3GB内存。
volatile-lru -> 根据LRU算法生成的过期时间来删除。
allkeys-lru -> 根据LRU算法删除任何key。
volatile-random -> 根据过期设置来随机删除key。
allkeys->random -> 无差别随机删。
volatile-ttl -> 根据最近过期时间来删除(辅以TTL)
noeviction -> 谁也不删,直接在写操作时返回错误。
5.增加新节点
新增一个节点,就增加一个空的节点到集群。有两种情况:如果新增的是主节点,则是从集群的其他节点中转移部分数据给它;如果新增的是从节点,则告诉它从一个已知的节点中同步复制集。 我们2种情况都试试。首先是新增一个新的主节点到集群中。 两种情况,都是需要先加入一个空的节点到集群中 鉴于我们前面已经启动了6个节点,端口号7000-7005已经用了,新增节点的端口号就用7006吧。新增一个新的空节点,就跟上面启动前面6个节点的步骤一样(记得改配置文件的端口号): *在终端打开一个新的页面 *进入到cluster-test目录 *创建名为“7006”的目录 *在该目录下创建redis.conf文件,内容跟其他节点的内容一致,只是端口号改成7006. *最后启动它:../redis-server ./redis.conf 现在该节点应该运行起来了。 现在,我们使用redis-trib来增加一个新节点到集群中: ./redis-trib.rb add-node 127.0.0.1:7006 127.0.0.1:7000 使用add-node指令来新增节点,第一个地址是需要新增的节点地址,第二个地址是集群中任意一个节点地址。
6.添加一个从节点
新增从节点有两种方法,第一个是使用上面的redis-trib脚本,增–slave选项,类似这样: ./redis-trib.rb add-node --slave 127.0.0.1:7006 127.0.0.1:7000 注意到上面的命令行跟我们加主节点的命令行类似,所以没有没有指定新增的从节点的主节点是哪个,这时候redis-trib会在拥有最少从节点的主节点中随机选一个作为新增节点的主节点。 当然也可以通过如下的命令指定新增从节点的主节点: ./redis-trib.rb add-node --slave --master-id 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 127.0.0.1:7006 127.0.0.1:7000 用上面的指令,我们可以指定新的从节点是那个主节点的副本集。 另一个方法,先把新节点以主节点的形式加入到集群,然后再用“CLUSTER REPLICATE”指令把它变为从节点。这个方式也适用于给从节点更换主节点。 比如,已有主节点127.0.0.1:7005,它存储的哈希槽范围是11423-16383,节点ID为3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e,我们希望给它新增从节点。首先用之前的方法新增一个空的主节点,然后连上该新节点,发送如下指令: redis 127.0.0.1:7006> cluster replicate 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 这样,新的从节点添加成功,而且集群中其他所有节点都已经知道新节点了(可能需要一些时间来更新配置)。我们可以通过以下指令来验证: $ redis-cli -p 7000 cluster nodes | grep slave | grep 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e f093c80dde814da99c5cf72a7dd01590792b783b 127.0.0.1:7006 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385543617702 3 connected 2938205e12de373867bf38f1ca29d31d0ddb3e46 127.0.0.1:7002 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385543617198 3 connected 现在节点3c3a0c…有2个从节点,分别是原来的运行在7002端口的节点和刚刚新增的7006端口的节点。
7.删除节点
使用redis-trib的指令”del-node”可以删除节点: ./redis-trib del-node 127.0.0.1:7000 `node-id` 第一个参数是集群的任意一个节点,第二个参数是需要删除的节点的ID。 同样的方法可以删除主节点,但是在删除之前,需要通过重新分片把数据都移走。 另一个删除主节点的方式是通过手动故障转移,让它的其中一个从节点升级成主节点后再把此节点删除。但这样并不会减少集群的主节点数,如果需要减少主节点数,重新分片在所难免。