Redis server启动源码
入口main函数
src/redis.c文件main函数
int main(int argc, char **argv) {struct timeval tv;/* We need to initialize our libraries, and the server configuration. */// 初始化库
#ifdef INIT_SETPROCTITLE_REPLACEMENTspt_init(argc, argv);
#endif//设置本地时间setlocale(LC_COLLATE,"");zmalloc_enable_thread_safeness();zmalloc_set_oom_handler(redisOutOfMemoryHandler);srand(time(NULL)^getpid());gettimeofday(&tv,NULL);//设置随机数种子dictSetHashFunctionSeed(tv.tv_sec^tv.tv_usec^getpid());// 检查服务器是否以 Sentinel 模式启动server.sentinel_mode = checkForSentinelMode(argc,argv);// 初始化服务器initServerConfig();/* We need to init sentinel right now as parsing the configuration file* in sentinel mode will have the effect of populating the sentinel* data structures with master nodes to monitor. */// 如果服务器以 Sentinel 模式启动,那么进行 Sentinel 功能相关的初始化// 并为要监视的主服务器创建一些相应的数据结构if (server.sentinel_mode) {initSentinelConfig();initSentinel();}// 检查用户是否指定了配置文件,或者配置选项if (argc >= 2) {int j = 1; /* First option to parse in argv[] */sds options = sdsempty();char *configfile = NULL;/* Handle special options --help and --version */// 处理特殊选项 -h 、-v 和 --test-memoryif (strcmp(argv[1], "-v") == 0 ||strcmp(argv[1], "--version") == 0) version();if (strcmp(argv[1], "--help") == 0 ||strcmp(argv[1], "-h") == 0) usage();if (strcmp(argv[1], "--test-memory") == 0) {if (argc == 3) {memtest(atoi(argv[2]),50);exit(0);} else {fprintf(stderr,"Please specify the amount of memory to test in megabytes.\n");fprintf(stderr,"Example: ./redis-server --test-memory 4096\n\n");exit(1);}}/* First argument is the config file name? */// 如果第一个参数(argv[1])不是以 "--" 开头// 那么它应该是一个配置文件if (argv[j][0] != '-' || argv[j][1] != '-')configfile = argv[j++];/* All the other options are parsed and conceptually appended to the* configuration file. For instance --port 6380 will generate the* string "port 6380\n" to be parsed after the actual file name* is parsed, if any. */// 对用户给定的其余选项进行分析,并将分析所得的字符串追加稍后载入的配置文件的内容之后// 比如 --port 6380 会被分析为 "port 6380\n"while(j != argc) {if (argv[j][0] == '-' && argv[j][1] == '-') {/* Option name */if (sdslen(options)) options = sdscat(options,"\n");options = sdscat(options,argv[j]+2);options = sdscat(options," ");} else {/* Option argument */options = sdscatrepr(options,argv[j],strlen(argv[j]));options = sdscat(options," ");}j++;}if (configfile) server.configfile = getAbsolutePath(configfile);// 重置保存条件resetServerSaveParams();// 载入配置文件, options 是前面分析出的给定选项loadServerConfig(configfile,options);sdsfree(options);// 获取配置文件的绝对路径if (configfile) server.configfile = getAbsolutePath(configfile);} else {redisLog(REDIS_WARNING, "Warning: no config file specified, using the default config. In order to specify a config file use %s /path/to/%s.conf", argv[0], server.sentinel_mode ? "sentinel" : "redis");}// 将服务器设置为守护进程if (server.daemonize) daemonize();// 创建并初始化服务器数据结构initServer();// 如果服务器是守护进程,那么创建 PID 文件if (server.daemonize) createPidFile();// 为服务器进程设置名字redisSetProcTitle(argv[0]);// 打印 ASCII LOGOredisAsciiArt();// 如果服务器不是运行在 SENTINEL 模式,那么执行以下代码if (!server.sentinel_mode) {/* Things not needed when running in Sentinel mode. */// 打印问候语redisLog(REDIS_WARNING,"Server started, Redis version " REDIS_VERSION);#ifdef __linux__// 打印内存警告linuxOvercommitMemoryWarning();#endif// 从 AOF 文件或者 RDB 文件中载入数据loadDataFromDisk();// 启动集群?if (server.cluster_enabled) {if (verifyClusterConfigWithData() == REDIS_ERR) {redisLog(REDIS_WARNING,"You can't have keys in a DB different than DB 0 when in ""Cluster mode. Exiting.");exit(1);}}// 打印 TCP 端口if (server.ipfd_count > 0)redisLog(REDIS_NOTICE,"The server is now ready to accept connections on port %d", server.port);// 打印本地套接字端口if (server.sofd > 0)redisLog(REDIS_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);} else {sentinelIsRunning();}/* Warning the user about suspicious maxmemory setting. */// 检查不正常的 maxmemory 配置if (server.maxmemory > 0 && server.maxmemory < 1024*1024) {redisLog(REDIS_WARNING,"WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?", server.maxmemory);}// 运行事件处理器,一直到服务器关闭为止aeSetBeforeSleepProc(server.el,beforeSleep);aeMain(server.el);// 服务器关闭,停止事件循环aeDeleteEventLoop(server.el);return 0;
}
1、本地配置基本初始化
包括设置 server 运行的时区、设置哈希函数的随机种子。
//设置本地时间setlocale(LC_COLLATE,"");zmalloc_enable_thread_safeness();zmalloc_set_oom_handler(redisOutOfMemoryHandler);srand(time(NULL)^getpid());gettimeofday(&tv,NULL);//设置随机数种子dictSetHashFunctionSeed(tv.tv_sec^tv.tv_usec^getpid());
2、检查哨兵模式
检查是不是以哨兵模式启动
/* Returns 1 if there is --sentinel among the arguments or if* argv[0] is exactly "redis-sentinel". */
int checkForSentinelMode(int argc, char **argv) {int j;if (strstr(argv[0],"redis-sentinel") != NULL) return 1;for (j = 1; j < argc; j++)if (!strcmp(argv[j],"--sentinel")) return 1;return 0;
}/* We need to init sentinel right now as parsing the configuration file* in sentinel mode will have the effect of populating the sentinel* data structures with master nodes to monitor. */// 如果服务器以 Sentinel 模式启动,那么进行 Sentinel 功能相关的初始化// 并为要监视的主服务器创建一些相应的数据结构if (server.sentinel_mode) {initSentinelConfig();initSentinel();}
3、运行参数解析
解析options,加载配置文件
// 检查用户是否指定了配置文件,或者配置选项if (argc >= 2) {int j = 1; /* First option to parse in argv[] */sds options = sdsempty();char *configfile = NULL;/* Handle special options --help and --version */// 处理特殊选项 -h 、-v 和 --test-memoryif (strcmp(argv[1], "-v") == 0 ||strcmp(argv[1], "--version") == 0) version();if (strcmp(argv[1], "--help") == 0 ||strcmp(argv[1], "-h") == 0) usage();if (strcmp(argv[1], "--test-memory") == 0) {if (argc == 3) {memtest(atoi(argv[2]),50);exit(0);} else {fprintf(stderr,"Please specify the amount of memory to test in megabytes.\n");fprintf(stderr,"Example: ./redis-server --test-memory 4096\n\n");exit(1);}}/* First argument is the config file name? */// 如果第一个参数(argv[1])不是以 "--" 开头// 那么它应该是一个配置文件if (argv[j][0] != '-' || argv[j][1] != '-')configfile = argv[j++];/* All the other options are parsed and conceptually appended to the* configuration file. For instance --port 6380 will generate the* string "port 6380\n" to be parsed after the actual file name* is parsed, if any. */// 对用户给定的其余选项进行分析,并将分析所得的字符串追加稍后载入的配置文件的内容之后// 比如 --port 6380 会被分析为 "port 6380\n"while(j != argc) {if (argv[j][0] == '-' && argv[j][1] == '-') {/* Option name */if (sdslen(options)) options = sdscat(options,"\n");options = sdscat(options,argv[j]+2);options = sdscat(options," ");} else {/* Option argument */options = sdscatrepr(options,argv[j],strlen(argv[j]));options = sdscat(options," ");}j++;}
loadServerConfig(configfile,options);
4、初始化 server
初始化数据库数据结构和pool,网络交互初始化(线程初始化、工作队列初始化)
void initServer() {int j;// 设置信号处理函数signal(SIGHUP, SIG_IGN);signal(SIGPIPE, SIG_IGN);setupSignalHandlers();// 设置 syslogif (server.syslog_enabled) {openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT,server.syslog_facility);}// 初始化并创建数据结构server.current_client = NULL;server.clients = listCreate();server.clients_to_close = listCreate();server.slaves = listCreate();server.monitors = listCreate();server.slaveseldb = -1; /* Force to emit the first SELECT command. */server.unblocked_clients = listCreate();server.ready_keys = listCreate();server.clients_waiting_acks = listCreate();server.get_ack_from_slaves = 0;server.clients_paused = 0;// 创建共享对象createSharedObjects();adjustOpenFilesLimit();server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR);server.db = zmalloc(sizeof(redisDb)*server.dbnum);/* Open the TCP listening socket for the user commands. */// 打开 TCP 监听端口,用于等待客户端的命令请求if (server.port != 0 &&listenToPort(server.port,server.ipfd,&server.ipfd_count) == REDIS_ERR)exit(1);/* Open the listening Unix domain socket. */// 打开 UNIX 本地端口if (server.unixsocket != NULL) {unlink(server.unixsocket); /* don't care if this fails */server.sofd = anetUnixServer(server.neterr,server.unixsocket,server.unixsocketperm, server.tcp_backlog);if (server.sofd == ANET_ERR) {redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr);exit(1);}anetNonBlock(NULL,server.sofd);}/* Abort if there are no listening sockets at all. */if (server.ipfd_count == 0 && server.sofd < 0) {redisLog(REDIS_WARNING, "Configured to not listen anywhere, exiting.");exit(1);}/* Create the Redis databases, and initialize other internal state. */// 创建并初始化数据库结构for (j = 0; j < server.dbnum; j++) {server.db[j].dict = dictCreate(&dbDictType,NULL);server.db[j].expires = dictCreate(&keyptrDictType,NULL);server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);server.db[j].ready_keys = dictCreate(&setDictType,NULL);server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);server.db[j].eviction_pool = evictionPoolAlloc();server.db[j].id = j;server.db[j].avg_ttl = 0;}// 创建 PUBSUB 相关结构server.pubsub_channels = dictCreate(&keylistDictType,NULL);server.pubsub_patterns = listCreate();listSetFreeMethod(server.pubsub_patterns,freePubsubPattern);listSetMatchMethod(server.pubsub_patterns,listMatchPubsubPattern);server.cronloops = 0;server.rdb_child_pid = -1;server.aof_child_pid = -1;aofRewriteBufferReset();server.aof_buf = sdsempty();server.lastsave = time(NULL); /* At startup we consider the DB saved. */server.lastbgsave_try = 0; /* At startup we never tried to BGSAVE. */server.rdb_save_time_last = -1;server.rdb_save_time_start = -1;server.dirty = 0;resetServerStats();/* A few stats we don't want to reset: server startup time, and peak mem. */server.stat_starttime = time(NULL);server.stat_peak_memory = 0;server.resident_set_size = 0;server.lastbgsave_status = REDIS_OK;server.aof_last_write_status = REDIS_OK;server.aof_last_write_errno = 0;server.repl_good_slaves_count = 0;updateCachedTime();/* Create the serverCron() time event, that's our main way to process* background operations. */// 为 serverCron() 创建时间事件if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {redisPanic("Can't create the serverCron time event.");exit(1);}/* Create an event handler for accepting new connections in TCP and Unix* domain sockets. */// 为 TCP 连接关联连接应答(accept)处理器// 用于接受并应答客户端的 connect() 调用for (j = 0; j < server.ipfd_count; j++) {if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,acceptTcpHandler,NULL) == AE_ERR){redisPanic("Unrecoverable error creating server.ipfd file event.");}}// 为本地套接字关联应答处理器if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,acceptUnixHandler,NULL) == AE_ERR) redisPanic("Unrecoverable error creating server.sofd file event.");/* Open the AOF file if needed. */// 如果 AOF 持久化功能已经打开,那么打开或创建一个 AOF 文件if (server.aof_state == REDIS_AOF_ON) {server.aof_fd = open(server.aof_filename,O_WRONLY|O_APPEND|O_CREAT,0644);if (server.aof_fd == -1) {redisLog(REDIS_WARNING, "Can't open the append-only file: %s",strerror(errno));exit(1);}}/* 32 bit instances are limited to 4GB of address space, so if there is* no explicit limit in the user provided configuration we set a limit* at 3 GB using maxmemory with 'noeviction' policy'. This avoids* useless crashes of the Redis instance for out of memory. */// 对于 32 位实例来说,默认将最大可用内存限制在 3 GBif (server.arch_bits == 32 && server.maxmemory == 0) {redisLog(REDIS_WARNING,"Warning: 32 bit instance detected but no memory limit set. Setting 3 GB maxmemory limit with 'noeviction' policy now.");server.maxmemory = 3072LL*(1024*1024); /* 3 GB */server.maxmemory_policy = REDIS_MAXMEMORY_NO_EVICTION;}// 如果服务器以 cluster 模式打开,那么初始化 clusterif (server.cluster_enabled) clusterInit();// 初始化复制功能有关的脚本缓存replicationScriptCacheInit();// 初始化脚本系统scriptingInit();// 初始化慢查询功能slowlogInit();// 初始化 BIO 系统bioInit();
}
5、执行事件驱动框架(Reactor模型)
// 运行事件处理器,一直到服务器关闭为止aeSetBeforeSleepProc(server.el,beforeSleep);//事件处理的入口函数aeMain(server.el);// 服务器关闭,停止事件循环aeDeleteEventLoop(server.el);相关文章:
Redis server启动源码
入口main函数 src/redis.c文件main函数 int main(int argc, char **argv) {struct timeval tv;/* We need to initialize our libraries, and the server configuration. */// 初始化库 #ifdef INIT_SETPROCTITLE_REPLACEMENTspt_init(argc, argv); #endif//设置本地时间setl…...
C++基础 强制转换
目录 static_cast:static_cast(expression) const_cast dynamic_cast reinterpret_cast C 提供以下几类转换 static_cast:static_cast<type-id>(expression) tatic_cast 主要用于以下几种情况: 用于显式地将一个表达式转换为另一…...
【python、opencv】opencv仿射变换原理及代码实现
opencv仿射变换原理 仿射变换是opencv的基本知识点,主要目的是将原始图片经过仿射变换矩阵,平移、缩放、旋转成目标图像。用数学公式表示就是坐标转换。 其中x,y是原始图像坐标,u,v是变换后的图像坐标。将公式转换为…...
mac本地部署stable-diffusion
下载Homebrew /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" ①输入“1”选择中科大版本,然后输入Y(YES),直接输入开机密码(不显示)然后回车确认,开始下载 ②…...
dockers安装rabbitmq
RabbitMQ: easy to use, flexible messaging and streaming — RabbitMQhttps://www.rabbitmq.com/ Downloading and Installing RabbitMQ — RabbitMQ docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.12-management 之后参照:dock…...
07、pytest指定要运行哪些用例
官方用例 # 目录结构 | |----test_mod.py | |----testing||----test_dir.py# content of test_mod.py import pytestdef func(x):return x 1def test_mod():print("test_mod function was invoked")assert func(3) 5def test_func():print("test_func was in…...
springboot集成cxf
<?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://ma…...
快速认识什么是:Kubernetes
每次谈到容器的时候,除了Docker之外,都会说起 Kubernetes,那么什么是 Kubernetes呢?今天就来一起学快速入门一下 Kubernetes 吧!希望本文对您有所帮助。 Kubernetes,一种用于管理和自动化云中容器化工作负…...
YOLOv6 学习笔记
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、YOLOv6贡献和改进二、YOLOv6核心概念三、YOLOv6架构改进四、YOLOv6重参思想五、YOLOv6的损失函数总结 前言 在计算机视觉领域,目标检测技术一直…...
paypal贝宝怎么绑卡支付
一、PayPal是什么 PayPal是一个很多国家地区通用的支付渠道,我们可以把它理解为一项在线服务,相当于美国版的支付宝。你可以通过PayPal进行汇款和收款,相比传统的电汇和西联那类的汇款方式,PayPal更加简单和容易,被很…...
活动回顾|德州仪器嵌入式技术创新发展研讨会(上海站)成功举办,信驰达科技携手TI推动技术创新
2023年11月28日,德州仪器(TI)嵌入式技术创新发展研讨会在上海顺利举办。作为TI中国第三方IDH,深圳市信驰达科技有限公司受邀参加,并设置展位,展出CC2340系列低功耗蓝牙模块及TPMS、蓝牙数字钥匙解决方案,与众多业内伙伴…...
Vue 循环走马灯
1、使用 transform: translateX(),循环将滚动内容在容器内偏移,超出容器部分隐藏; 2、避免滚动到末尾时出现空白,需要预留多几个。 3、一次循环偏移的距离scrollLoopWidth 可能受样式影响需要做些微调,比如单个item的…...
<Linux>(极简关键、省时省力)《Linux操作系统原理分析之Linux文件管理(3)》(27)
《Linux操作系统原理分析之Linux文件管理(3)》(27) 8 Linux文件管理8.6 文件管理和操作8.6.1 系统对文件的管理8.6.2 进程对文件的管理 8 Linux文件管理 8.6 文件管理和操作 8.6.1 系统对文件的管理 Linux 系统把所有打开的活动…...
【华为数据之道学习笔记】3-2 基础数据治理
基础数据用于对其他数据进行分类,在业界也称作参考数据。基础数据通常是静态的(如国家、币种),一般在业务事件发生之前就已经预先定义。它的可选值数量有限,可以用作业务或IT的开关和判断条件。当基础数据的取值发生变…...
GO设计模式——7、适配器模式(结构型)
目录 适配器模式(Adapter Pattern) 优缺点 使用场景 注意事项 代码实现 适配器模式(Adapter Pattern) 适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。将一个类的接口转化为客户希望的…...
Java实现TCP一对一通信,实现UDP群聊通信
TCP一对一通信: 实现服务端对话框: 其中可自由更改对话框的样式 import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; public class QqMain extends JFrame implements ActionListener{public static void …...
Vue + Element 实现按钮指定间隔时间点击
1、业务需求 需要加一个按钮,调用第三方API,按钮十分钟之内只能点击一次,刷新页面也只能点击一次 2、思路 加一个本地缓存的时间戳,通过时间戳计算指定时间内不能点击按钮 3、实现 1)vue页面 <template>&l…...
UE Websocket笔记
参考链接 [UE4 C入门到进阶]12.Websocket网络通信 - 哔哩哔哩 包含怎么用Nodejs 写测试服务器 UE4_使用WebSocket和Json(上) - 知乎 包含Python写测试服务器 UE4_使用WebSocket和Json(下) - 知乎 示例代码 xxx.Build.cs"W…...
STM32h7 接收各种can id情况下滤波器的配置
1、接收所有数据 /* 此处id2都为0,不进行id校验,接收所有数据*/ static void CAN_Filter_Config(void){FDCAN_FilterTypeDef sFilterConfig1;/* Configure Rx filter */sFilterConfig1.IdType FDCAN_STANDARD_ID;sFilterConfig1.FilterIndex 0;sFilte…...
《深入理解计算机系统》学习笔记 - 第三课 - 浮点数
Floating Point 浮点数 文章目录 Floating Point 浮点数分数二进制示例能代表的数浮点数的表示方式浮点数编码规格化值规格化值编码示例 非规格化的值特殊值 示例IEEE 编码的一些特殊属性四舍五入,相加,相乘四舍五入四舍五入的模式二进制数的四舍五入 浮…...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...
3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
怎么让Comfyui导出的图像不包含工作流信息,
为了数据安全,让Comfyui导出的图像不包含工作流信息,导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo(推荐) 在 save_images 方法中,删除或注释掉所有与 metadata …...
加密通信 + 行为分析:运营商行业安全防御体系重构
在数字经济蓬勃发展的时代,运营商作为信息通信网络的核心枢纽,承载着海量用户数据与关键业务传输,其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级,传统安全防护体系逐渐暴露出局限性&a…...
