redis单机服务器的实现

redis服务器参数

struct redisServer {

    /* General */

    pid_t pid;                  /* Main process pid. */

    char *configfile;           /* Absolute config file path, or NULL */

    char *executable;           /* Absolute executable file path. */

    char **exec_argv;           /* Executable argv vector (copy). */

    int dynamic_hz;             /* Change hz value depending on # of clients. */

    int config_hz;              /* Configured HZ value. May be different than

                                   the actual 'hz' field value if dynamic-hz

                                   is enabled. */

    int hz;                     /* serverCron() calls frequency in hertz */

    redisDb *db;

    dict *commands;             /* Command table */

    dict *orig_commands;        /* Command table before command renaming. */

    aeEventLoop *el;

    unsigned int lruclock;      /* Clock for LRU eviction */

    int shutdown_asap;          /* SHUTDOWN needed ASAP */

    int activerehashing;        /* Incremental rehash in serverCron() */

    int active_defrag_running;  /* Active defragmentation running (holds current scan aggressiveness) */

    char *requirepass;          /* Pass for AUTH command, or NULL */

    char *pidfile;              /* PID file path */

    int arch_bits;              /* 32 or 64 depending on sizeof(long) */

    int cronloops;              /* Number of times the cron function run */

    char runid[CONFIG_RUN_ID_SIZE+1];  /* ID always different at every exec. */

    int sentinel_mode;          /* True if this instance is a Sentinel. */

    size_t initial_memory_usage; /* Bytes used after initialization. */

    int always_show_logo;       /* Show logo even for non-stdout logging. */

    /* Modules */

    dict *moduleapi;            /* Exported core APIs dictionary for modules. */

    dict *sharedapi;            /* Like moduleapi but containing the APIs that

                                   modules share with each other. */

    list *loadmodule_queue;     /* List of modules to load at startup. */

    int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a

                                   client blocked on a module command needs

                                   to be processed. */

    /* Networking */

    int port;                   /* TCP listening port */

    int tcp_backlog;            /* TCP listen() backlog */

    char *bindaddr[CONFIG_BINDADDR_MAX]; /* Addresses we should bind to */

    int bindaddr_count;         /* Number of addresses in server.bindaddr[] */

    char *unixsocket;           /* UNIX socket path */

    mode_t unixsocketperm;      /* UNIX socket permission */

    int ipfd[CONFIG_BINDADDR_MAX]; /* TCP socket file descriptors */

    int ipfd_count;             /* Used slots in ipfd[] */

    int sofd;                   /* Unix socket file descriptor */

    int cfd[CONFIG_BINDADDR_MAX];/* Cluster bus listening socket */

    int cfd_count;              /* Used slots in cfd[] */

    list *clients;              /* List of active clients */

    list *clients_to_close;     /* Clients to close asynchronously */

    list *clients_pending_write; /* There is to write or install handler. */

    list *slaves, *monitors;    /* List of slaves and MONITORs */

    client *current_client; /* Current client, only used on crash report */

    rax *clients_index;         /* Active clients dictionary by client ID. */

    int clients_paused;         /* True if clients are currently paused */

    mstime_t clients_pause_end_time; /* Time when we undo clients_paused */

    char neterr[ANET_ERR_LEN];   /* Error buffer for anet.c */

    dict *migrate_cached_sockets;/* MIGRATE cached sockets */

    uint64_t next_client_id;    /* Next client unique ID. Incremental. */

    int protected_mode;         /* Don't accept external connections. */

    /* RDB / AOF loading information */

    int loading;                /* We are loading data from disk if true */

    off_t loading_total_bytes;

    off_t loading_loaded_bytes;

    time_t loading_start_time;

    off_t loading_process_events_interval_bytes;

    /* Fast pointers to often looked up command */

    struct redisCommand *delCommand, *multiCommand, *lpushCommand,

                        *lpopCommand, *rpopCommand, *zpopminCommand,

                        *zpopmaxCommand, *sremCommand, *execCommand,

                        *expireCommand, *pexpireCommand, *xclaimCommand,

                        *xgroupCommand;

    /* Fields used only for stats */

    time_t stat_starttime;          /* Server start time */

    long long stat_numcommands;     /* Number of processed commands */

    long long stat_numconnections;  /* Number of connections received */

    long long stat_expiredkeys;     /* Number of expired keys */

    double stat_expired_stale_perc; /* Percentage of keys probably expired */

    long long stat_expired_time_cap_reached_count; /* Early expire cylce stops.*/

    long long stat_evictedkeys;     /* Number of evicted keys (maxmemory) */

    long long stat_keyspace_hits;   /* Number of successful lookups of keys */

    long long stat_keyspace_misses; /* Number of failed lookups of keys */

    long long stat_active_defrag_hits;      /* number of allocations moved */

    long long stat_active_defrag_misses;    /* number of allocations scanned but not moved */

    long long stat_active_defrag_key_hits;  /* number of keys with moved allocations */

    long long stat_active_defrag_key_misses;/* number of keys scanned and not moved */

    long long stat_active_defrag_scanned;   /* number of dictEntries scanned */

    size_t stat_peak_memory;        /* Max used memory record */

    long long stat_fork_time;       /* Time needed to perform latest fork() */

    double stat_fork_rate;          /* Fork rate in GB/sec. */

    long long stat_rejected_conn;   /* Clients rejected because of maxclients */

    long long stat_sync_full;       /* Number of full resyncs with slaves. */

    long long stat_sync_partial_ok; /* Number of accepted PSYNC requests. */

    long long stat_sync_partial_err;/* Number of unaccepted PSYNC requests. */

    list *slowlog;                  /* SLOWLOG list of commands */

    long long slowlog_entry_id;     /* SLOWLOG current entry ID */

    long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */

    unsigned long slowlog_max_len;     /* SLOWLOG max number of items logged */

    struct malloc_stats cron_malloc_stats; /* sampled in serverCron(). */

    long long stat_net_input_bytes; /* Bytes read from network. */

    long long stat_net_output_bytes; /* Bytes written to network. */

    size_t stat_rdb_cow_bytes;      /* Copy on write bytes during RDB saving. */

    size_t stat_aof_cow_bytes;      /* Copy on write bytes during AOF rewrite. */

    /* The following two are used to track instantaneous metrics, like

     * number of operations per second, network traffic. */

    struct {

        long long last_sample_time; /* Timestamp of last sample in ms */

        long long last_sample_count;/* Count in last sample */

        long long samples[STATS_METRIC_SAMPLES];

        int idx;

    } inst_metric[STATS_METRIC_COUNT];

    /* Configuration */

    int verbosity;                  /* Loglevel in redis.conf */

    int maxidletime;                /* Client timeout in seconds */

    int tcpkeepalive;               /* Set SO_KEEPALIVE if non-zero. */

    int active_expire_enabled;      /* Can be disabled for testing purposes. */

    int active_defrag_enabled;

    size_t active_defrag_ignore_bytes; /* minimum amount of fragmentation waste to start active defrag */

    int active_defrag_threshold_lower; /* minimum percentage of fragmentation to start active defrag */

    int active_defrag_threshold_upper; /* maximum percentage of fragmentation at which we use maximum effort */

    int active_defrag_cycle_min;       /* minimal effort for defrag in CPU percentage */

    int active_defrag_cycle_max;       /* maximal effort for defrag in CPU percentage */

    unsigned long active_defrag_max_scan_fields; /* maximum number of fields of set/hash/zset/list to process from within the main dict scan */

    size_t client_max_querybuf_len; /* Limit for client query buffer length */

    int dbnum;                      /* Total number of configured DBs */

    int supervised;                 /* 1 if supervised, 0 otherwise. */

    int supervised_mode;            /* See SUPERVISED_* */

    int daemonize;                  /* True if running as a daemon */

    clientBufferLimitsConfig client_obuf_limits[CLIENT_TYPE_OBUF_COUNT];

    /* AOF persistence */

    int aof_state;                  /* AOF_(ON|OFF|WAIT_REWRITE) */

    int aof_fsync;                  /* Kind of fsync() policy */

    char *aof_filename;             /* Name of the AOF file */

    int aof_no_fsync_on_rewrite;    /* Don't fsync if a rewrite is in prog. */

    int aof_rewrite_perc;           /* Rewrite AOF if % growth is > M and... */

    off_t aof_rewrite_min_size;     /* the AOF file is at least N bytes. */

    off_t aof_rewrite_base_size;    /* AOF size on latest startup or rewrite. */

    off_t aof_current_size;         /* AOF current size. */

    off_t aof_fsync_offset;         /* AOF offset which is already synced to disk. */

    int aof_rewrite_scheduled;      /* Rewrite once BGSAVE terminates. */

    pid_t aof_child_pid;            /* PID if rewriting process */

    list *aof_rewrite_buf_blocks;   /* Hold changes during an AOF rewrite. */

    sds aof_buf;      /* AOF buffer, written before entering the event loop */

    int aof_fd;       /* File descriptor of currently selected AOF file */

    int aof_selected_db; /* Currently selected DB in AOF */

    time_t aof_flush_postponed_start; /* UNIX time of postponed AOF flush */

    time_t aof_last_fsync;            /* UNIX time of last fsync() */

    time_t aof_rewrite_time_last;   /* Time used by last AOF rewrite run. */

    time_t aof_rewrite_time_start;  /* Current AOF rewrite start time. */

    int aof_lastbgrewrite_status;   /* C_OK or C_ERR */

    unsigned long aof_delayed_fsync;  /* delayed AOF fsync() counter */

    int aof_rewrite_incremental_fsync;/* fsync incrementally while aof rewriting? */

    int rdb_save_incremental_fsync;   /* fsync incrementally while rdb saving? */

    int aof_last_write_status;      /* C_OK or C_ERR */

    int aof_last_write_errno;       /* Valid if aof_last_write_status is ERR */

    int aof_load_truncated;         /* Don't stop on unexpected AOF EOF. */

    int aof_use_rdb_preamble;       /* Use RDB preamble on AOF rewrites. */

    /* AOF pipes used to communicate between parent and child during rewrite. */

    int aof_pipe_write_data_to_child;

    int aof_pipe_read_data_from_parent;

    int aof_pipe_write_ack_to_parent;

    int aof_pipe_read_ack_from_child;

    int aof_pipe_write_ack_to_child;

    int aof_pipe_read_ack_from_parent;

    int aof_stop_sending_diff;     /* If true stop sending accumulated diffs

                                      to child process. */

    sds aof_child_diff;             /* AOF diff accumulator child side. */

    /* RDB persistence */

    long long dirty;                /* Changes to DB from the last save */

    long long dirty_before_bgsave;  /* Used to restore dirty on failed BGSAVE */

    pid_t rdb_child_pid;            /* PID of RDB saving child */

    struct saveparam *saveparams;   /* Save points array for RDB */

    int saveparamslen;              /* Number of saving points */

    char *rdb_filename;             /* Name of RDB file */

    int rdb_compression;            /* Use compression in RDB? */

    int rdb_checksum;               /* Use RDB checksum? */

    time_t lastsave;                /* Unix time of last successful save */

    time_t lastbgsave_try;          /* Unix time of last attempted bgsave */

    time_t rdb_save_time_last;      /* Time used by last RDB save run. */

    time_t rdb_save_time_start;     /* Current RDB save start time. */

    int rdb_bgsave_scheduled;       /* BGSAVE when possible if true. */

    int rdb_child_type;             /* Type of save by active child. */

    int lastbgsave_status;          /* C_OK or C_ERR */

    int stop_writes_on_bgsave_err;  /* Don't allow writes if can't BGSAVE */

    int rdb_pipe_write_result_to_parent; /* RDB pipes used to return the state */

    int rdb_pipe_read_result_from_child; /* of each slave in diskless SYNC. */

    /* Pipe and data structures for child -> parent info sharing. */

    int child_info_pipe[2];         /* Pipe used to write the child_info_data. */

    struct {

        int process_type;           /* AOF or RDB child? */

        size_t cow_size;            /* Copy on write size. */

        unsigned long long magic;   /* Magic value to make sure data is valid. */

    } child_info_data;

    /* Propagation of commands in AOF / replication */

    redisOpArray also_propagate;    /* Additional command to propagate. */

    /* Logging */

    char *logfile;                  /* Path of log file */

    int syslog_enabled;             /* Is syslog enabled? */

    char *syslog_ident;             /* Syslog ident */

    int syslog_facility;            /* Syslog facility */

    /* Replication (master) */

    char replid[CONFIG_RUN_ID_SIZE+1];  /* My current replication ID. */

    char replid2[CONFIG_RUN_ID_SIZE+1]; /* replid inherited from master*/

    long long master_repl_offset;   /* My current replication offset */

    long long second_replid_offset; /* Accept offsets up to this for replid2. */

    int slaveseldb;                 /* Last SELECTed DB in replication output */

    int repl_ping_slave_period;     /* Master pings the slave every N seconds */

    char *repl_backlog;             /* Replication backlog for partial syncs */

    long long repl_backlog_size;    /* Backlog circular buffer size */

    long long repl_backlog_histlen; /* Backlog actual data length */

    long long repl_backlog_idx;     /* Backlog circular buffer current offset,

                                       that is the next byte will'll write to.*/

    long long repl_backlog_off;     /* Replication "master offset" of first

                                       byte in the replication backlog buffer.*/

    time_t repl_backlog_time_limit; /* Time without slaves after the backlog

                                       gets released. */

    time_t repl_no_slaves_since;    /* We have no slaves since that time.

                                       Only valid if server.slaves len is 0. */

    int repl_min_slaves_to_write;   /* Min number of slaves to write. */

    int repl_min_slaves_max_lag;    /* Max lag of <count> slaves to write. */

    int repl_good_slaves_count;     /* Number of slaves with lag <= max_lag. */

    int repl_diskless_sync;         /* Send RDB to slaves sockets directly. */

    int repl_diskless_sync_delay;   /* Delay to start a diskless repl BGSAVE. */

    /* Replication (slave) */

    char *masterauth;               /* AUTH with this password with master */

    char *masterhost;               /* Hostname of master */

    int masterport;                 /* Port of master */

    int repl_timeout;               /* Timeout after N seconds of master idle */

    client *master;     /* Client that is master for this slave */

    client *cached_master; /* Cached master to be reused for PSYNC. */

    int repl_syncio_timeout; /* Timeout for synchronous I/O calls */

    int repl_state;          /* Replication status if the instance is a slave */

    off_t repl_transfer_size; /* Size of RDB to read from master during sync. */

    off_t repl_transfer_read; /* Amount of RDB read from master during sync. */

    off_t repl_transfer_last_fsync_off; /* Offset when we fsync-ed last time. */

    int repl_transfer_s;     /* Slave -> Master SYNC socket */

    int repl_transfer_fd;    /* Slave -> Master SYNC temp file descriptor */

    char *repl_transfer_tmpfile; /* Slave-> master SYNC temp file name */

    time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */

    int repl_serve_stale_data; /* Serve stale data when link is down? */

    int repl_slave_ro;          /* Slave is read only? */

    int repl_slave_ignore_maxmemory;    /* If true slaves do not evict. */

    time_t repl_down_since; /* Unix time at which link with master went down */

    int repl_disable_tcp_nodelay;   /* Disable TCP_NODELAY after SYNC? */

    int slave_priority;             /* Reported in INFO and used by Sentinel. */

    int slave_announce_port;        /* Give the master this listening port. */

    char *slave_announce_ip;        /* Give the master this ip address. */

    /* The following two fields is where we store master PSYNC replid/offset

     * while the PSYNC is in progress. At the end we'll copy the fields into

     * the server->master client structure. */

    char master_replid[CONFIG_RUN_ID_SIZE+1];  /* Master PSYNC runid. */

    long long master_initial_offset;           /* Master PSYNC offset. */

    int repl_slave_lazy_flush;          /* Lazy FLUSHALL before loading DB? */

    /* Replication script cache. */

    dict *repl_scriptcache_dict;        /* SHA1 all slaves are aware of. */

    list *repl_scriptcache_fifo;        /* First in, first out LRU eviction. */

    unsigned int repl_scriptcache_size; /* Max number of elements. */

    /* Synchronous replication. */

    list *clients_waiting_acks;         /* Clients waiting in WAIT command. */

    int get_ack_from_slaves;            /* If true we send REPLCONF GETACK. */

    /* Limits */

    unsigned int maxclients;            /* Max number of simultaneous clients */

    unsigned long long maxmemory;   /* Max number of memory bytes to use */

    int maxmemory_policy;           /* Policy for key eviction */

    int maxmemory_samples;          /* Pricision of random sampling */

    int lfu_log_factor;             /* LFU logarithmic counter factor. */

    int lfu_decay_time;             /* LFU counter decay factor. */

    long long proto_max_bulk_len;   /* Protocol bulk length maximum size. */

    /* Blocked clients */

    unsigned int blocked_clients;   /* # of clients executing a blocking cmd.*/

    unsigned int blocked_clients_by_type[BLOCKED_NUM];

    list *unblocked_clients; /* list of clients to unblock before next loop */

    list *ready_keys;        /* List of readyList structures for BLPOP & co */

    /* Sort parameters - qsort_r() is only available under BSD so we

     * have to take this state global, in order to pass it to sortCompare() */

    int sort_desc;

    int sort_alpha;

    int sort_bypattern;

    int sort_store;

    /* Zip structure config, see redis.conf for more information  */

    size_t hash_max_ziplist_entries;

    size_t hash_max_ziplist_value;

    size_t set_max_intset_entries;

    size_t zset_max_ziplist_entries;

    size_t zset_max_ziplist_value;

    size_t hll_sparse_max_bytes;

    size_t stream_node_max_bytes;

    int64_t stream_node_max_entries;

    /* List parameters */

    int list_max_ziplist_size;

    int list_compress_depth;

    /* time cache */

    time_t unixtime;    /* Unix time sampled every cron cycle. */

    time_t timezone;    /* Cached timezone. As set by tzset(). */

    int daylight_active;    /* Currently in daylight saving time. */

    long long mstime;   /* Like 'unixtime' but with milliseconds resolution. */

    /* Pubsub */

    dict *pubsub_channels;  /* Map channels to list of subscribed clients */

    list *pubsub_patterns;  /* A list of pubsub_patterns */

    int notify_keyspace_events; /* Events to propagate via Pub/Sub. This is an

                                   xor of NOTIFY_... flags. */

    /* Cluster */

    int cluster_enabled;      /* Is cluster enabled? */

    mstime_t cluster_node_timeout; /* Cluster node timeout. */

    char *cluster_configfile; /* Cluster auto-generated config file name. */

    struct clusterState *cluster;  /* State of the cluster */

    int cluster_migration_barrier; /* Cluster replicas migration barrier. */

    int cluster_slave_validity_factor; /* Slave max data age for failover. */

    int cluster_require_full_coverage; /* If true, put the cluster down if

                                          there is at least an uncovered slot.*/

    int cluster_slave_no_failover;  /* Prevent slave from starting a failover

                                       if the master is in failure state. */

    char *cluster_announce_ip;  /* IP address to announce on cluster bus. */

    int cluster_announce_port;     /* base port to announce on cluster bus. */

    int cluster_announce_bus_port; /* bus port to announce on cluster bus. */

    int cluster_module_flags;      /* Set of flags that Redis modules are able

                                      to set in order to suppress certain

                                      native Redis Cluster features. Check the

                                      REDISMODULE_CLUSTER_FLAG_*. */

    /* Scripting */

    lua_State *lua; /* The Lua interpreter. We use just one for all clients */

    client *lua_client;   /* The "fake client" to query Redis from Lua */

    client *lua_caller;   /* The client running EVAL right now, or NULL */

    dict *lua_scripts;         /* A dictionary of SHA1 -> Lua scripts */

    unsigned long long lua_scripts_mem;  /* Cached scripts' memory + oh */

    mstime_t lua_time_limit;  /* Script timeout in milliseconds */

    mstime_t lua_time_start;  /* Start time of script, milliseconds time */

    int lua_write_dirty;  /* True if a write command was called during the

                             execution of the current script. */

    int lua_random_dirty; /* True if a random command was called during the

                             execution of the current script. */

    int lua_replicate_commands; /* True if we are doing single commands repl. */

    int lua_multi_emitted;/* True if we already proagated MULTI. */

    int lua_repl;         /* Script replication flags for redis.set_repl(). */

    int lua_timedout;     /* True if we reached the time limit for script

                             execution. */

    int lua_kill;         /* Kill the script if true. */

    int lua_always_replicate_commands; /* Default replication type. */

    /* Lazy free */

    int lazyfree_lazy_eviction;

    int lazyfree_lazy_expire;

    int lazyfree_lazy_server_del;

    /* Latency monitor */

    long long latency_monitor_threshold;

    dict *latency_events;

    /* Assert & bug reporting */

    const char *assert_failed;

    const char *assert_file;

    int assert_line;

    int bug_report_start; /* True if bug report header was already logged. */

    int watchdog_period;  /* Software watchdog period in ms. 0 = off */

    /* System hardware info */

    size_t system_memory_size;  /* Total memory in system as reported by OS */

 

    /* Mutexes used to protect atomic variables when atomic builtins are

     * not available. */

    pthread_mutex_t lruclock_mutex;

    pthread_mutex_t next_client_id_mutex;

    pthread_mutex_t unixtime_mutex;

};

redisDb结构

/* Redis database representation. There are multiple databases identified

 * by integers from 0 (the default database) up to the max configured

 * database. The database number is the 'id' field in the structure. */

typedef struct redisDb {

    dict *dict;                 /* The keyspace for this DB */

    dict *expires;              /* Timeout of keys with a timeout set */

    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)*/

    dict *ready_keys;           /* Blocked keys that received a PUSH */

    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */

    int id;                     /* Database ID */

    long long avg_ttl;          /* Average TTL, just for stats */

    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */

} redisDb;

一个redis服务器通常会构建16个数据库,可以通过命令select index选择要使用的数据库,例如:select 6;

可以看见,一个数据库里有一个dict的结构,这个结构是未来我们对数据存取删改的结构;

还有expires结构,这个结构就是未来我们设置每个键的生存时间的结构;

若将某个键用了watch命令,那么dirty会增加

 expire设置生存时间

expire key s

expireat key time(s或毫秒的精度)

expireat key 1377527300

pexpire

移除过期时间,persist

TTL是显示过期时间

PTTL是显示还剩下多少过期时间

过期删除策略

定时删除:

新建一个定时器,到时间删除,好处是减少内存压力,不好的地方是增大cpu的占用。若有大量访问但只是将时间耗费删除过期键上那么得不偿失。

惰性删除:

对cpu比较友好,只有在取出键的时候才会进行过期检查。

定期删除:

难点是确定删除的时长和频次

 

AOF RDB

RDB,使用save或bgsave会产生rdb文件,当服务器下次开始时候,会加载,主服务器会加载没有过期的键值,从服务器全部加载。

AOF,当服务器以AOF的模式运行时候,无论键过期,只要没有被删除或定期删除,那么aof文件不会产生影响,对删除的数据,需要在aof文件中追加一个DEL命令,来标记已经被删除。对于aof文件的重写,不会加载过期键。

涉及到主从服务器的时候,只有主服务器发生DEL命令时候,从服务器才会删除这个键值,当客户端请求时候,哪怕这个键值已经过期也会返回结果。

数据库通知:订阅和发布模式使用subscribe __keyspace@0__:message就可以看到操作message的所有操作

RDB的持久化

AOF文件会优先于rdb文件加载,只有在aof没有或者只开启了rdb模式才会加载;

原型在rdbsave()函数;

可以用save 800 1000 这个命令间歇存储 800 1000分别代表的意思是800s和修改1000次,这两个参数在如下的结构体里定义

struct saveparam {

    time_t seconds;

    int changes;

};

dirty计数器和lastsave属性

dirty是保存距离上一次存储所进行的删除修改写入操作的次数

lastsave则是代表距离上一次运行save的时刻

这两个参数在redisserver结构体里

那么如何刷新这些属性呢,或者说如何看见redis是否要进行保存呢?那秘密就是在每100ms执行一次的serverCron()里,它会检查是否满足保存条件,满足的话就会进行存储;

接下来介绍rdb文件的结构:

REDIS为五个字符

db_version是四个字节的

databases是n个数据库

EOF是结束符

check_sum是一个8字节的无符号的整数

AOF持久化

可以理解为,保存命令

文件保存和同步,现代操作系统调用write后会将数据临时存在一个缓冲区里,若此时断电,会丢失数据,所以有fsysnc fdatasysnc两个同步函数

appendfsysnc保证文件的安全性 

事件

时间事件:统一事件源+settimer

文件事件:io复用

 


 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值