乐闻世界logo
搜索文章和话题

面试题手册

Redis 的 RDB 和 AOF 持久化有什么区别?如何选择?

Redis 提供了两种持久化方式:RDB(Redis Database)和 AOF(Append Only File),它们各有优缺点,也可以同时使用。RDB 持久化工作原理:RDB 是在指定的时间间隔内生成数据集的时间点快照。Redis 会 fork 一个子进程,将内存中的数据写入到一个临时文件中,然后用这个临时文件替换旧的 RDB 文件。优点:文件紧凑:RDB 文件是压缩的二进制文件,体积小,适合备份和灾难恢复恢复速度快:RDB 文件的恢复速度比 AOF 快,因为不需要重新执行命令对性能影响小:RDB 是由子进程执行的,对主进程性能影响较小适合冷备份:RDB 文件可以方便地传输到远程服务器进行备份缺点:数据丢失风险:如果 Redis 突然宕机,可能会丢失最后一次快照之后的所有数据fork 操作耗时:当数据量很大时,fork 子进程可能会阻塞主进程实时性差:RDB 是基于时间间隔的,无法做到实时持久化配置参数:save <seconds> <changes>:设置保存条件,如 save 900 1 表示 900 秒内至少有 1 个 key 变化就保存rdbcompression yes:是否压缩 RDB 文件rdbchecksum yes:是否对 RDB 文件进行校验AOF 持久化工作原理:AOF 记录服务器接收到的每一个写操作命令,将这些命令追加到 AOF 文件的末尾。Redis 重启时,会重新执行 AOF 文件中的命令来恢复数据。优点:数据安全性高:AOF 可以配置为每秒同步或每次写操作同步,数据丢失风险低可读性强:AOF 文件是文本格式,可以手动查看和修改自动重写:AOF 文件过大时会自动重写,压缩文件体积缺点:文件体积大:AOF 文件通常比 RDB 文件大恢复速度慢:需要重新执行所有命令,恢复速度比 RDB 慢性能影响大:每次写操作都需要同步到磁盘,对性能影响较大配置参数:appendonly yes:开启 AOF 持久化appendfsync always/everysec/no:同步策略always:每次写操作都同步,最安全但性能最差everysec:每秒同步一次,推荐配置no:由操作系统决定何时同步,性能最好但安全性最差auto-aof-rewrite-percentage 100:AOF 文件重写的增长百分比auto-aof-rewrite-min-size 64mb:AOF 文件重写的最小大小RDB + AOF 混合持久化Redis 4.0 之后支持混合持久化,开启后,AOF 重写时会将 RDB 的内容写入 AOF 文件开头,后续的增量命令继续以 AOF 格式追加。这样既保证了数据安全性,又提高了恢复速度。配置:aof-use-rdb-preamble yes:开启混合持久化选择建议如果对数据安全性要求不高:只使用 RDB,性能更好如果对数据安全性要求很高:只使用 AOF,配置为 appendfsync everysec如果既要性能又要安全性:使用 RDB + AOF 混合持久化如果数据量很大:建议使用 RDB,因为 AOF 的恢复速度太慢在实际生产环境中,通常会同时开启 RDB 和 AOF,或者使用混合持久化,以兼顾性能和数据安全性。
阅读 0·2月19日 19:37

Redis 支持哪些数据结构?它们的使用场景和底层实现是什么?

Redis 支持多种数据结构,每种数据结构都有其特定的使用场景和底层实现:1. String(字符串)底层实现:使用 SDS(Simple Dynamic String)实现,类似于 C 语言的字符串,但增加了长度信息和空间预分配。使用场景:缓存用户信息、配置信息计数器(INCR、DECR 命令)分布式锁Session 存储常用命令:SET、GET、INCR、DECR、MGET、MSET、SETEX 等2. Hash(哈希)底层实现:当元素较少时使用 ziplist(压缩列表),当元素较多时使用 hashtable(哈希表)。使用场景:存储对象(如用户信息)购物车文章点赞数统计常用命令:HSET、HGET、HMGET、HGETALL、HINCRBY、HDEL 等3. List(列表)底层实现:当元素较少时使用 ziplist,当元素较多时使用 linkedlist(双向链表)或 quicklist。使用场景:消息队列最新列表(如最新文章)时间轴栈和队列操作常用命令:LPUSH、RPUSH、LPOP、RPOP、LRANGE、LLEN、LTRIM 等4. Set(集合)底层实现:使用 intset(整数集合)或 hashtable 实现。使用场景:标签系统共同关注/共同好友抽奖系统去重常用命令:SADD、SREM、SMEMBERS、SISMEMBER、SINTER、SUNION 等5. ZSet(有序集合)底层实现:使用 skiplist(跳跃表)和 hashtable 的组合,hashtable 用于快速查找,skiplist 用于排序。使用场景:排行榜延时队列优先级队列范围查询常用命令:ZADD、ZREM、ZRANGE、ZREVRANGE、ZRANK、ZSCORE 等6. Bitmap(位图)底层实现:基于 String 类型实现,每个 bit 位表示一个状态。使用场景:用户签到统计在线用户统计布隆过滤器常用命令:SETBIT、GETBIT、BITCOUNT、BITOP 等7. HyperLogLog(基数统计)底层实现:使用概率算法,占用 12KB 内存。使用场景:网站独立访客统计(UV)大数据去重统计常用命令:PFADD、PFCOUNT、PFMERGE 等8. Geo(地理位置)底层实现:基于 ZSet 实现,使用 Geohash 算法。使用场景:附近的人距离计算位置服务常用命令:GEOADD、GEODIST、GEOPOS、GEORADIUS 等每种数据结构都有其特定的转换阈值,例如 Hash 和 List 在元素数量和大小超过一定阈值时会从 ziplist 转换为更复杂的数据结构,以平衡内存使用和性能。
阅读 0·2月19日 19:37

Redis 是什么?它有哪些主要特点?

Redis 是一个基于内存的键值存储数据库,它的主要特点包括:高性能:Redis 将所有数据存储在内存中,读写速度极快,单机可以达到每秒 10 万次以上的操作。丰富的数据结构:Redis 支持多种数据类型,包括 String(字符串)、Hash(哈希)、List(列表)、Set(集合)、ZSet(有序集合)、Bitmap(位图)、HyperLogLog(基数统计)、Geo(地理位置)等。持久化支持:Redis 提供了两种持久化方式:RDB(Redis Database):在指定的时间间隔内生成数据集的时间点快照AOF(Append Only File):记录服务器接收到的每一个写操作命令原子性操作:Redis 的所有操作都是原子性的,这意味着要么成功执行,要么完全不执行。支持事务:Redis 通过 MULTI、EXEC、DISCARD、WATCH 等命令支持事务功能。主从复制:Redis 支持主从复制,可以实现数据的读写分离和高可用性。集群支持:Redis Cluster 提供了数据分片和自动故障转移功能,支持水平扩展。发布/订阅:Redis 内置了发布/订阅消息系统,可以实现消息的广播。Lua 脚本支持:Redis 支持 Lua 脚本,可以在服务器端执行复杂的操作。内存优化:Redis 使用了多种内存优化技术,如共享对象、压缩列表等,可以有效减少内存使用。Redis 通常被用作缓存、会话存储、消息队列、排行榜、计数器、实时分析系统等场景。由于其高性能和丰富的数据结构,Redis 已经成为现代应用架构中不可或缺的组件之一。
阅读 0·2月19日 19:37

Redis 如何进行安全配置?有哪些安全最佳实践?

Redis 的安全配置是保护 Redis 服务器免受攻击的重要措施,需要从多个维度进行安全加固。1. 网络安全绑定监听地址问题描述:Redis 默认绑定所有网络接口,容易被攻击者扫描和攻击。解决方案:# 配置文件 redis.confbind 127.0.0.1 10.0.0.1# 只监听本地和内网接口# 避免绑定 0.0.0.0使用防火墙问题描述:Redis 端口对外开放,容易被攻击者访问。解决方案:# 使用 iptables 限制访问iptables -A INPUT -p tcp --dport 6379 -s 10.0.0.0/24 -j ACCEPTiptables -A INPUT -p tcp --dport 6379 -j DROP# 使用 firewalld 限制访问firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/24" port protocol="tcp" port="6379" accept'firewall-cmd --reload使用 SSL/TLS问题描述:Redis 数据传输未加密,容易被中间人攻击。解决方案:# 生成证书openssl genrsa -out redis.key 2048openssl req -new -key redis.key -out redis.csropenssl x509 -req -days 365 -in redis.csr -signkey redis.key -out redis.crt# 配置 Redis 使用 TLStls-port 6380port 0tls-cert-file /path/to/redis.crttls-key-file /path/to/redis.keytls-ca-cert-file /path/to/ca.crt2. 认证安全设置密码问题描述:Redis 默认无密码,任何人都可以访问。解决方案:# 配置文件 redis.confrequirepass your_strong_password# 或使用命令行CONFIG SET requirepass your_strong_password# 连接时使用密码redis-cli -a your_strong_password使用 ACL(Access Control List)问题描述:Redis 6.0 之前只能设置一个密码,无法精细控制权限。解决方案:# 创建用户ACL SETUSER user1 on >password1 ~user:* +@read# 创建管理员用户ACL SETUSER admin on >admin_password ~* +@all# 查看用户列表ACL LIST# 删除用户ACL DELUSER user1禁用危险命令问题描述:Redis 有一些危险命令,如 FLUSHALL、FLUSHDB、CONFIG 等,容易被滥用。解决方案:# 配置文件 redis.confrename-command FLUSHALL ""rename-command FLUSHDB ""rename-command CONFIG ""rename-command SHUTDOWN ""rename-command DEBUG ""# 或重命名命令rename-command FLUSHALL "REALLY_FLUSH_ALL"3. 数据安全启用持久化问题描述:Redis 默认不启用持久化,数据丢失风险高。解决方案:# 启用 RDB 持久化save 900 1save 300 10save 60 10000# 启用 AOF 持久化appendonly yesappendfsync everysec加密持久化文件问题描述:持久化文件未加密,容易被窃取。解决方案:# 使用文件系统加密# Linux 使用 eCryptfs# macOS 使用 FileVault# Windows 使用 BitLocker# 或使用第三方加密工具# 如 cryptsetup、GPG 等定期备份问题描述:没有定期备份,数据丢失后无法恢复。解决方案:# 定期备份 RDB 文件0 2 * * * cp /var/lib/redis/dump.rdb /backup/dump_$(date +\%Y\%m\%d).rdb# 定期备份 AOF 文件0 3 * * * cp /var/lib/redis/appendonly.aof /backup/appendonly_$(date +\%Y\%m\%d).aof# 备份到远程服务器rsync -avz /backup/ user@remote-server:/backup/4. 运行安全使用非特权用户运行问题描述:Redis 使用 root 用户运行,存在安全风险。解决方案:# 创建 Redis 用户useradd -r -s /bin/false redis# 修改 Redis 配置文件所有者chown redis:redis /etc/redis/redis.confchown redis:redis /var/lib/redis# 使用 Redis 用户运行sudo -u redis redis-server /etc/redis/redis.conf限制文件权限问题描述:Redis 配置文件和数据文件权限过大,容易被篡改。解决方案:# 限制配置文件权限chmod 600 /etc/redis/redis.conf# 限制数据文件权限chmod 700 /var/lib/redis# 限制日志文件权限chmod 600 /var/log/redis/redis.log使用 chroot问题描述:Redis 可以访问整个文件系统,存在安全风险。解决方案:# 配置文件 redis.confchroot /var/lib/redisdir /# 或使用 systemd 的 chroot 功能[Service]User=redisGroup=redisExecStart=/usr/bin/redis-server /etc/redis/redis.confProtectSystem=fullReadWritePaths=/var/lib/redis5. 监控安全启用慢查询日志问题描述:慢查询日志未启用,无法发现异常操作。解决方案:# 配置文件 redis.confslowlog-log-slower-than 10000slowlog-max-len 128# 查看慢查询SLOWLOG GET 10监控异常操作问题描述:无法及时发现异常操作,如大量删除、大量查询等。解决方案:# 使用 Redis Exporter 监控# 配置 Prometheus 抓取数据scrape_configs: - job_name: 'redis' static_configs: - targets: ['localhost:9121']# 配置告警规则groups: - name: redis_alerts rules: - alert: RedisSlowQueries expr: rate(redis_slowlog_length[5m]) > 10 for: 5m labels: severity: warning annotations: summary: "Redis slow queries rate is high"审计日志问题描述:Redis 默认不记录审计日志,无法追踪操作。解决方案:# 配置文件 redis.conflogfile /var/log/redis/redis.logloglevel notice# 使用第三方审计工具# 如 Redis-Audit、Redis-Log 等6. 集群安全主从复制认证问题描述:主从复制未设置认证,容易被恶意从节点连接。解决方案:# 主节点配置masterauth your_master_password# 从节点配置requirepass your_slave_passwordmasterauth your_master_password哨兵模式认证问题描述:哨兵模式未设置认证,容易被恶意哨兵节点连接。解决方案:# 哨兵配置文件 sentinel.confsentinel auth-pass mymaster your_master_passwordsentinel down-after-milliseconds mymaster 30000sentinel parallel-syncs mymaster 1sentinel failover-timeout mymaster 180000集群模式认证问题描述:集群模式未设置认证,容易被恶意节点加入集群。解决方案:# 集群配置文件 redis.confcluster-enabled yescluster-config-file nodes.confcluster-node-timeout 5000cluster-require-full-coverage yescluster-auth-file /path/to/cluster_auth_file7. 安全最佳实践定期更新 Redis 版本问题描述:使用旧版本 Redis,存在已知漏洞。解决方案:# 定期检查 Redis 版本redis-server --version# 更新 Redisapt-get updateapt-get install redis-server# 或从源码编译wget https://download.redis.io/redis-stable.tar.gztar -xzf redis-stable.tar.gzcd redis-stablemakemake install定期检查安全配置问题描述:安全配置未定期检查,可能存在安全漏洞。解决方案:# 使用 Redis 安全检查工具# 如 redis-audit、redis-safety 等# 定期检查配置redis-cli CONFIG GET "*"# 定期检查用户权限redis-cli ACL LIST使用安全扫描工具问题描述:未使用安全扫描工具,无法发现安全漏洞。解决方案:# 使用 Nmap 扫描 Redis 端口nmap -p 6379 <redis-server-ip># 使用 Redis 安全扫描工具# 如 redis-rogue-server、redis-attack 等总结Redis 的安全配置需要从网络安全、认证安全、数据安全、运行安全、监控安全、集群安全等多个维度进行加固。在实际应用中,需要根据具体的业务场景和安全要求,选择合适的安全配置。同时,需要定期检查和更新安全配置,确保 Redis 的安全性。
阅读 0·2月19日 19:37

Redis 如何进行监控和运维?有哪些关键指标和工具?

Redis 的监控和运维是保证 Redis 稳定运行的重要环节,需要从多个维度进行监控和管理。1. Redis 监控指标基础指标内存使用情况:# 查看内存使用情况INFO memory# 关键指标used_memory:1024000 # 已使用内存used_memory_human:1.00M # 已使用内存(人类可读)used_memory_rss:2048000 # 操作系统分配的内存used_memory_rss_human:2.00M # 操作系统分配的内存(人类可读)used_memory_peak:2048000 # 历史内存使用峰值used_memory_peak_human:2.00M # 历史内存使用峰值(人类可读)maxmemory:1073741824 # 最大内存限制maxmemory_human:1.00G # 最大内存限制(人类可读)mem_fragmentation_ratio:2.00 # 内存碎片率连接情况:# 查看连接情况INFO clients# 关键指标connected_clients:10 # 已连接客户端数blocked_clients:0 # 被阻塞客户端数client_longest_output_list:0 # 最长输出列表client_biggest_input_buf:0 # 最大输入缓冲区命令执行情况:# 查看命令执行情况INFO commandstats# 关键指标cmdstat_get:calls=1000,usec=5000,usec_per_call=5.00cmdstat_set:calls=500,usec=2500,usec_per_call=5.00持久化情况:# 查看持久化情况INFO persistence# 关键指标rdb_last_save_time:1234567890 # 最后一次 RDB 保存时间rdb_changes_since_last_save:100 # 自上次保存以来的变更次数aof_enabled:1 # AOF 是否启用aof_rewrite_in_progress:0 # AOF 重写是否进行中性能指标QPS(每秒查询数):# 计算 QPSINFO stats# 关键指标instantaneous_ops_per_sec:1000 # 当前每秒操作数total_commands_processed:1000000 # 总处理命令数total_connections_received:10000 # 总连接数延迟:# 查看延迟INFO stats# 关键指标instantaneous_input_kbps:100 # 当前输入带宽instantaneous_output_kbps:200 # 当前输出带宽命中率:# 计算命中率keyspace_hits:10000 # 命中次数keyspace_misses:1000 # 未命中次数hit_rate = keyspace_hits / (keyspace_hits + keyspace_misses)2. Redis 监控工具Redis 自带命令INFO 命令:# 查看所有信息INFO# 查看特定信息INFO memoryINFO statsINFO replicationINFO persistenceSLOWLOG 命令:# 查看慢查询SLOWLOG GET 10# 查看慢查询数量SLOWLOG LEN# 清空慢查询SLOWLOG RESETMONITOR 命令:# 实时监控 Redis 命令MONITOR第三方监控工具Redis Exporter:# 安装 Redis Exporterdocker run -d --name redis-exporter \ -e REDIS_ADDR=redis://localhost:6379 \ prom/redis-exporter# 配置 Prometheus 抓取数据scrape_configs: - job_name: 'redis' static_configs: - targets: ['localhost:9121']Grafana + Prometheus:# 使用 Grafana 展示 Redis 监控数据# 导入 Redis Dashboardhttps://grafana.com/grafana/dashboards/11835-redis-dashboard/Redis Insight:# Redis 官方可视化工具# 下载地址https://redis.com/redis-enterprise/redis-insight/3. Redis 运维操作备份与恢复RDB 备份:# 手动触发 RDB 备份SAVE# 或BGSAVE# 备份文件位置/var/lib/redis/dump.rdb# 恢复 RDB 备份# 停止 Redisredis-cli shutdown# 复制备份文件cp dump.rdb /var/lib/redis/dump.rdb# 启动 Redisredis-server /etc/redis/redis.confAOF 备份:# 备份 AOF 文件cp /var/lib/redis/appendonly.aof /backup/appendonly.aof# 恢复 AOF 备份# 停止 Redisredis-cli shutdown# 复制备份文件cp /backup/appendonly.aof /var/lib/redis/appendonly.aof# 启动 Redisredis-server /etc/redis/redis.conf数据迁移主从迁移:# 在从节点配置主节点redis-cli slaveof <master-ip> <master-port># 等待同步完成redis-cli info replication# 取消主从关系redis-cli slaveof no one使用 MIGRATE 命令:# 迁移单个 keyMIGRATE <target-host> <target-port> <key> <target-database> <timeout># 迁移多个 keyMIGRATE <target-host> <target-port> "" <target-database> <timeout> KEYS key1 key2 key3使用 Redis-Shake:# 安装 Redis-Shakewget https://github.com/alibaba/RedisShake/releases/download/v2.0.3/redis-shake-v2.0.3.tar.gztar -xzf redis-shake-v2.0.3.tar.gz# 配置文件cat > shake.conf << EOFsource.type: standalonesource.address: source.redis.com:6379source.password: source_passwordtarget.type: standalonetarget.address: target.redis.com:6379target.password: target_passwordEOF# 启动迁移./redis-shake.linux -type=sync -conf=shake.conf集群扩容添加节点:# 添加新节点redis-cli --cluster add-node <new-node-ip>:<new-node-port> <existing-node-ip>:<existing-node-port># 分配哈希槽redis-cli --cluster reshard <existing-node-ip>:<existing-node-port> \ --cluster-from <node-id> \ --cluster-to <new-node-id> \ --cluster-slots 1000删除节点:# 迁移哈希槽redis-cli --cluster reshard <existing-node-ip>:<existing-node-port> \ --cluster-from <node-id> \ --cluster-to <other-node-id> \ --cluster-slots 1000# 删除节点redis-cli --cluster del-node <existing-node-ip>:<existing-node-port> <node-id>4. Redis 故障排查内存不足问题现象:Redis 报错:OOM command not allowed when used memory > 'maxmemory'Redis 性能下降排查步骤:# 查看内存使用情况INFO memory# 查看大 Keyredis-cli --bigkeys# 查看内存碎片率INFO memory | grep mem_fragmentation_ratio解决方案:# 调整最大内存限制CONFIG SET maxmemory 2gb# 开启内存淘汰策略CONFIG SET maxmemory-policy allkeys-lru# 清理内存碎片MEMORY PURGE连接数过多问题现象:Redis 报错:max number of clients reached客户端连接失败排查步骤:# 查看连接数INFO clients# 查看最大连接数CONFIG GET maxclients解决方案:# 调整最大连接数CONFIG SET maxclients 10000# 关闭空闲连接CONFIG SET timeout 300慢查询问题现象:Redis 响应慢慢查询日志增多排查步骤:# 查看慢查询SLOWLOG GET 10# 查看慢查询配置CONFIG GET slowlog-log-slower-thanCONFIG GET slowlog-max-len解决方案:# 调整慢查询阈值CONFIG SET slowlog-log-slower-than 10000# 优化慢查询命令# 避免 KEYS 命令,使用 SCAN# 避免 O(n) 复杂度的命令# 使用 Pipeline 减少网络往返主从同步延迟问题现象:从节点数据滞后读取到旧数据排查步骤:# 查看主从同步状态INFO replication# 查看同步延迟master_repl_offset:1000000slave_repl_offset:999000解决方案:# 调整复制缓冲区大小CONFIG SET repl-backlog-size 10mb# 调整复制超时时间CONFIG SET repl-timeout 60# 优化网络延迟# 使用更快的网络# 减少主从节点之间的距离5. Redis 性能优化配置优化内存优化:# 设置最大内存maxmemory 2gb# 设置内存淘汰策略maxmemory-policy allkeys-lru# 关闭 THPecho never > /sys/kernel/mm/transparent_hugepage/enabled持久化优化:# RDB 配置save 900 1save 300 10save 60 10000# AOF 配置appendonly yesappendfsync everysecauto-aof-rewrite-percentage 100auto-aof-rewrite-min-size 64mb网络优化:# 设置 TCP backlogtcp-backlog 511# 设置 TCP keepalivetcp-keepalive 300# 设置超时时间timeout 300运维优化定期备份:# 定期备份 RDB 文件0 2 * * * cp /var/lib/redis/dump.rdb /backup/dump_$(date +\%Y\%m\%d).rdb# 定期备份 AOF 文件0 3 * * * cp /var/lib/redis/appendonly.aof /backup/appendonly_$(date +\%Y\%m\%d).aof监控告警:# 配置监控告警# 内存使用率超过 80% 告警# QPS 下降超过 50% 告警# 延迟超过 100ms 告警# 连接数超过 80% 告警总结Redis 的监控和运维是保证 Redis 稳定运行的重要环节。需要从基础指标、性能指标、监控工具、运维操作、故障排查、性能优化等多个维度进行监控和管理。在实际应用中,需要建立完善的监控体系,及时发现和解决问题,确保 Redis 的稳定性和性能。
阅读 0·2月19日 19:37

Redis 的过期策略和内存淘汰机制是什么?如何选择合适的策略?

Redis 的过期策略和内存淘汰机制是 Redis 内存管理的核心内容,对于保证 Redis 的稳定性和性能至关重要。1. 过期策略Redis 有三种过期策略:定时删除、惰性删除和定期删除。定时删除(Timed Expiration)工作原理:在设置 key 的过期时间时,创建一个定时器,当过期时间到达时,立即删除 key。优点:内存友好:过期 key 会被及时删除,不会占用内存保证过期 key 不会占用内存缺点:CPU 不友好:如果有过期 key,需要创建大量的定时器,消耗 CPU 资源影响性能:定时器的创建和删除会消耗系统资源Redis 实现:Redis 没有使用定时删除策略,因为这种方式对 CPU 消耗太大。惰性删除(Lazy Expiration)工作原理:key 过期时不立即删除,而是在访问 key 时检查是否过期,如果过期则删除。优点:CPU 友好:只有在访问 key 时才检查是否过期,不会消耗额外的 CPU 资源性能好:不会因为过期 key 的删除而影响系统性能缺点:内存不友好:如果过期 key 永远不被访问,就会一直占用内存可能导致内存泄漏:大量过期 key 不被访问,会占用大量内存Redis 实现:Redis 使用了惰性删除策略,在执行命令时检查 key 是否过期。// Redis 源码中的惰性删除实现int expireIfNeeded(redisDb *db, robj *key) { // 获取 key 的过期时间 mstime_t when = getExpire(db, key); // 如果 key 没有设置过期时间,直接返回 if (when < 0) return 0; // 如果 key 还没有过期,直接返回 if (mstime() < when) return 0; // key 已过期,删除 key server.stat_expiredkeys++; propagateExpire(db, key, server.lazyfree_lazy_expire); notifyKeyspaceEvent(NOTIFY_EXPIRED, "expired", key, db->id); return dbDelete(db, key);}定期删除(Periodic Expiration)工作原理:每隔一段时间,随机抽取一些 key 检查是否过期,如果过期则删除。优点:平衡 CPU 和内存:通过限制删除操作的频率,平衡 CPU 和内存的使用避免内存泄漏:定期删除过期 key,避免内存泄漏缺点:实现复杂:需要合理设置删除频率和删除数量可能遗漏:随机抽取可能会遗漏一些过期 keyRedis 实现:Redis 使用了定期删除策略,在 serverCron 函数中定期执行。// Redis 源码中的定期删除实现void activeExpireCycle(int type) { // 获取当前时间 long long start = ustime(); // 遍历所有数据库 for (int j = 0; j < server.dbnum; j++) { // 随机抽取一些 key 检查是否过期 for (int i = 0; i < num; i++) { // 随机选择一个 key dictEntry *de = dictGetRandomKey(db->dict); // 检查 key 是否过期 if (expireIfNeeded(db, de->key)) { // key 已过期,删除 expired++; } } }}Redis 的过期策略Redis 使用了惰性删除 + 定期删除的组合策略:惰性删除:在访问 key 时检查是否过期,如果过期则删除定期删除:每隔一段时间,随机抽取一些 key 检查是否过期,如果过期则删除这种组合策略平衡了 CPU 和内存的使用,既保证了过期 key 会被及时删除,又不会因为大量的删除操作而影响系统性能。2. 内存淘汰机制当 Redis 内存使用达到最大内存限制时,Redis 会触发内存淘汰机制,删除一些 key 以释放内存。内存淘汰策略Redis 提供了 8 种内存淘汰策略:1. noeviction(不淘汰)描述:当内存使用达到最大内存限制时,不淘汰任何 key,新写入操作会返回错误。适用场景:对数据完整性要求高的场景,不允许数据丢失。配置:maxmemory-policy noeviction2. allkeys-lru(所有 key 使用 LRU)描述:从所有 key 中淘汰最近最少使用的 key。适用场景:需要淘汰所有 key,使用 LRU 算法。配置:maxmemory-policy allkeys-lru3. allkeys-lfu(所有 key 使用 LFU)描述:从所有 key 中淘汰最不经常使用的 key。适用场景:需要淘汰所有 key,使用 LFU 算法。配置:maxmemory-policy allkeys-lfu4. allkeys-random(所有 key 随机淘汰)描述:从所有 key 中随机淘汰 key。适用场景:不需要考虑访问频率,随机淘汰 key。配置:maxmemory-policy allkeys-random5. volatile-lru(设置了过期时间的 key 使用 LRU)描述:从设置了过期时间的 key 中淘汰最近最少使用的 key。适用场景:只淘汰设置了过期时间的 key,使用 LRU 算法。配置:maxmemory-policy volatile-lru6. volatile-lfu(设置了过期时间的 key 使用 LFU)描述:从设置了过期时间的 key 中淘汰最不经常使用的 key。适用场景:只淘汰设置了过期时间的 key,使用 LFU 算法。配置:maxmemory-policy volatile-lfu7. volatile-random(设置了过期时间的 key 随机淘汰)描述:从设置了过期时间的 key 中随机淘汰 key。适用场景:只淘汰设置了过期时间的 key,随机淘汰。配置:maxmemory-policy volatile-random8. volatile-ttl(淘汰即将过期的 key)描述:从设置了过期时间的 key 中淘汰即将过期的 key。适用场景:优先淘汰即将过期的 key。配置:maxmemory-policy volatile-ttlLRU 算法实现Redis 使用了近似的 LRU 算法,而不是精确的 LRU 算法。为什么使用近似 LRU?精确的 LRU 算法需要维护一个链表,每次访问 key 都需要更新链表,消耗大量 CPU 资源近似 LRU 算法只需要记录 key 的访问时间,不需要维护链表,性能更好Redis 的近似 LRU 实现:Redis 为每个 key 记录最后一次访问的时间,当需要淘汰 key 时,随机抽取一些 key,淘汰其中访问时间最早的 key。// Redis 源码中的近似 LRU 实现unsigned int LRU_GetLRU(redisObject *obj) { unsigned int lru = obj->lru; if (lru >= LRU_CLOCK()) { lru = (lru & 0x7FFFFFFF); } else { lru = LRU_CLOCK() & 0x7FFFFFFF; } return lru;}LFU 算法实现Redis 4.0 之后引入了 LFU 算法,用于淘汰最不经常使用的 key。LFU 算法实现:Redis 为每个 key 记录访问频率,当需要淘汰 key 时,淘汰访问频率最低的 key。// Redis 源码中的 LFU 实现void LFU_DecrAndReturn(robj *obj) { unsigned long ldt = obj->lru >> 8; unsigned long counter = LFUGetCounterIncrAndReturn(obj->lru); if (counter) { counter--; obj->lru = (ldt << 8) | counter; }}内存淘汰策略选择选择建议:如果所有 key 都设置了过期时间:使用 volatile-lru 或 volatile-lfu如果只有部分 key 设置了过期时间:使用 allkeys-lru 或 allkeys-lfu如果对数据完整性要求高:使用 noeviction如果不需要考虑访问频率:使用 allkeys-random 或 volatile-random3. 监控内存使用可以使用 INFO memory 命令查看 Redis 的内存使用情况:# 查看内存使用情况INFO memory# 查看内存使用详情used_memory:1024000used_memory_human:1.00Mused_memory_rss:2048000used_memory_rss_human:2.00Mused_memory_peak:2048000used_memory_peak_human:2.00Mmaxmemory:1073741824maxmemory_human:1.00G总结Redis 的过期策略和内存淘汰机制是 Redis 内存管理的核心内容。过期策略包括定时删除、惰性删除和定期删除,Redis 使用了惰性删除 + 定期删除的组合策略。内存淘汰机制包括 8 种策略,可以根据实际场景选择合适的策略。在实际应用中,需要根据业务需求和性能要求,选择合适的过期策略和内存淘汰策略。
阅读 0·2月19日 19:36

Redis 缓存穿透、缓存击穿、缓存雪崩有什么区别?如何解决?

Redis 缓存策略是使用 Redis 作为缓存时的核心问题,需要解决缓存穿透、缓存击穿、缓存雪崩等问题,同时需要设计合理的缓存更新策略。1. 缓存穿透问题描述:缓存穿透是指查询一个不存在的数据,由于缓存中没有这个数据,请求会直接打到数据库。如果大量这样的请求,会对数据库造成巨大压力。解决方案:方案一:缓存空对象public User getUserById(Long id) { User user = redis.get("user:" + id); if (user != null) { return user.equals("NULL") ? null : user; } user = db.queryUserById(id); if (user == null) { redis.set("user:" + id, "NULL", 300); // 缓存空对象,5分钟过期 } else { redis.set("user:" + id, user, 3600); } return user;}方案二:布隆过滤器// 使用布隆过滤器判断 key 是否存在if (!bloomFilter.mightContain("user:" + id)) { return null; // 直接返回,不查询数据库}User user = redis.get("user:" + id);if (user != null) { return user;}user = db.queryUserById(id);if (user != null) { redis.set("user:" + id, user, 3600);}return user;2. 缓存击穿问题描述:缓存击穿是指某个热点 key 在缓存过期的一瞬间,大量请求同时查询这个 key,导致所有请求都打到数据库。解决方案:方案一:互斥锁public User getUserById(Long id) { User user = redis.get("user:" + id); if (user != null) { return user; } // 获取分布式锁 String lockKey = "lock:user:" + id; try { if (redis.setnx(lockKey, "1", 10)) { // 10秒过期 user = db.queryUserById(id); redis.set("user:" + id, user, 3600); } else { // 等待并重试 Thread.sleep(100); return getUserById(id); } } finally { redis.del(lockKey); } return user;}方案二:逻辑过期public User getUserById(Long id) { String value = redis.get("user:" + id); if (value != null) { JSONObject json = JSON.parseObject(value); if (json.getBoolean("expired")) { // 异步更新缓存 asyncUpdateCache(id); } return json.getObject("data", User.class); } // 缓存不存在,直接查询数据库 User user = db.queryUserById(id); JSONObject json = new JSONObject(); json.put("data", user); json.put("expired", false); redis.set("user:" + id, json.toJSONString(), 3600); return user;}3. 缓存雪崩问题描述:缓存雪崩是指大量的 key 在同一时间过期,或者 Redis 宕机,导致大量请求直接打到数据库。解决方案:方案一:设置随机过期时间// 设置过期时间时加上随机值int expire = 3600 + new Random().nextInt(600); // 3600-4200秒redis.set("user:" + id, user, expire);方案二:缓存预热// 系统启动时预热缓存@PostConstructpublic void init() { List<User> users = db.queryAllUsers(); for (User user : users) { redis.set("user:" + user.getId(), user, 3600); }}方案三:使用 Redis 高可用使用 Redis Sentinel 或 Redis Cluster避免单点故障4. 缓存更新策略策略一:Cache Aside Pattern(旁路缓存模式)// 读操作public User getUserById(Long id) { User user = redis.get("user:" + id); if (user != null) { return user; } user = db.queryUserById(id); redis.set("user:" + id, user, 3600); return user;}// 写操作public void updateUser(User user) { db.updateUser(user); redis.del("user:" + user.getId()); // 删除缓存,而不是更新缓存}策略二:Write Through Pattern(写穿透模式)public void updateUser(User user) { db.updateUser(user); redis.set("user:" + user.getId(), user, 3600); // 同时更新缓存和数据库}策略三:Write Behind Pattern(写回模式)public void updateUser(User user) { redis.set("user:" + user.getId(), user, 3600); // 先更新缓存 // 异步写入数据库 asyncWriteToDB(user);}5. 缓存一致性问题描述:缓存和数据库的数据不一致,导致读取到脏数据。解决方案:方案一:延时双删public void updateUser(User user) { db.updateUser(user); redis.del("user:" + user.getId()); // 第一次删除 try { Thread.sleep(500); // 延时 } catch (InterruptedException e) { e.printStackTrace(); } redis.del("user:" + user.getId()); // 第二次删除}方案二:订阅 Binlog// 订阅数据库的 Binlog,当数据库变更时,自动更新缓存@CanalEventListenerpublic class CacheUpdateListener { @ListenPoint(destination = "example", schema = "test", table = "user") public void onEvent(CanalEntry.Entry entry) { // 解析 Binlog,更新缓存 User user = parseUserFromBinlog(entry); redis.set("user:" + user.getId(), user, 3600); }}6. 缓存预热问题描述:系统启动时缓存为空,大量请求直接打到数据库。解决方案:方案一:定时任务预热@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点预热public void warmUpCache() { List<User> users = db.queryHotUsers(); for (User user : users) { redis.set("user:" + user.getId(), user, 3600); }}方案二:异步加载public User getUserById(Long id) { User user = redis.get("user:" + id); if (user != null) { return user; } // 异步加载缓存 asyncLoadCache(id); // 返回默认值或从数据库查询 return db.queryUserById(id);}7. 缓存降级问题描述:当 Redis 故障时,系统如何保证可用性。解决方案:方案一:直接返回默认值public User getUserById(Long id) { try { User user = redis.get("user:" + id); if (user != null) { return user; } } catch (Exception e) { log.error("Redis error", e); } // Redis 故障,直接查询数据库 return db.queryUserById(id);}方案二:使用本地缓存public User getUserById(Long id) { try { User user = redis.get("user:" + id); if (user != null) { return user; } } catch (Exception e) { log.error("Redis error", e); // 使用本地缓存 return localCache.get("user:" + id); } User user = db.queryUserById(id); redis.set("user:" + id, user, 3600); return user;}总结Redis 缓存策略需要综合考虑缓存穿透、缓存击穿、缓存雪崩等问题,同时需要设计合理的缓存更新策略和缓存一致性方案。在实际应用中,需要根据具体的业务场景,选择合适的缓存策略。同时,需要持续监控缓存的命中率和性能,及时调整缓存策略。
阅读 0·2月19日 19:36

Redis 有哪些常见的应用场景?如何实现这些场景?

Redis 凭借其高性能和丰富的数据结构,在实际项目中有着广泛的应用场景。以下是 Redis 的主要应用场景及实现方式。1. 缓存应用场景热点数据缓存:缓存频繁访问的数据,减轻数据库压力查询结果缓存:缓存复杂查询的结果,提高查询性能页面缓存:缓存页面渲染结果,减少服务器计算实现方式// 读取缓存public User getUserById(Long id) { String key = "user:" + id; User user = redis.get(key); if (user != null) { return user; } // 缓存未命中,查询数据库 user = db.queryUserById(id); // 写入缓存 redis.set(key, user, 3600); // 缓存 1 小时 return user;}// 更新缓存public void updateUser(User user) { db.updateUser(user); redis.del("user:" + user.getId()); // 删除缓存}注意事项设置合理的过期时间,避免缓存雪崩使用缓存穿透、缓存击穿、缓存雪崩的解决方案监控缓存命中率,及时调整缓存策略2. 会话存储应用场景用户登录状态:存储用户的登录状态和会话信息分布式会话:在分布式系统中共享会话信息临时数据存储:存储临时数据,如验证码、临时令牌等实现方式// 用户登录public String login(String username, String password) { User user = db.authenticate(username, password); if (user != null) { String sessionId = generateSessionId(); String key = "session:" + sessionId; // 存储会话信息 redis.set(key, user, 1800); // 30 分钟过期 return sessionId; } return null;}// 验证会话public User validateSession(String sessionId) { String key = "session:" + sessionId; User user = redis.get(key); if (user != null) { // 刷新过期时间 redis.expire(key, 1800); return user; } return null;}3. 计数器应用场景文章阅读量:统计文章的阅读次数视频播放量:统计视频的播放次数点赞数:统计点赞数量访问量统计:统计网站访问量实现方式// 增加计数public long incrementViewCount(Long articleId) { String key = "article:view:" + articleId; return redis.incr(key);}// 获取计数public long getViewCount(Long articleId) { String key = "article:view:" + articleId; return redis.get(key);}// 批量获取计数public Map<Long, Long> getViewCounts(List<Long> articleIds) { String[] keys = articleIds.stream() .map(id -> "article:view:" + id) .toArray(String[]::new); return redis.mget(keys);}4. 排行榜应用场景游戏排行榜:实时显示玩家排名文章排行榜:显示热门文章排行商品销量排行:显示商品销量排行用户积分排行:显示用户积分排行实现方式// 增加分数public void addScore(Long userId, double score) { String key = "leaderboard:user"; redis.zadd(key, score, userId.toString());}// 获取排行榜public List<User> getLeaderboard(int start, int end) { String key = "leaderboard:user"; // 获取排名和分数 Set<Tuple> tuples = redis.zrevrangeWithScores(key, start, end); // 转换为用户列表 return tuples.stream() .map(tuple -> { Long userId = Long.parseLong(tuple.getElement()); User user = db.getUserById(userId); user.setScore(tuple.getScore()); user.setRank(redis.zrevrank(key, userId.toString()) + 1); return user; }) .collect(Collectors.toList());}// 获取用户排名public long getUserRank(Long userId) { String key = "leaderboard:user"; Long rank = redis.zrevrank(key, userId.toString()); return rank != null ? rank + 1 : 0;}5. 消息队列应用场景异步任务:将耗时任务放入队列,异步处理任务调度:实现定时任务和任务调度事件通知:实现发布订阅模式日志收集:收集和分发日志实现方式// 生产者:发送消息public void sendMessage(String queue, String message) { redis.lpush(queue, message);}// 消费者:消费消息public String consumeMessage(String queue) { return redis.brpop(queue, 0); // 阻塞式消费}// 批量消费public List<String> consumeMessages(String queue, int count) { List<String> messages = new ArrayList<>(); for (int i = 0; i < count; i++) { String message = redis.rpop(queue); if (message == null) { break; } messages.add(message); } return messages;}6. 分布式锁应用场景库存扣减:防止超卖订单创建:防止重复创建订单资源竞争:解决并发竞争问题限流控制:实现接口限流实现方式// 获取锁public boolean tryLock(String key, String value, int expireTime) { String result = redis.set(key, value, "NX", "EX", expireTime); return "OK".equals(result);}// 释放锁public void unlock(String key, String value) { String script = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end"; redis.eval(script, Collections.singletonList(key), Collections.singletonList(value));}// 使用锁public void deductStock(Long productId, int quantity) { String lockKey = "lock:product:" + productId; String lockValue = UUID.randomUUID().toString(); try { // 获取锁 if (tryLock(lockKey, lockValue, 10)) { // 扣减库存 db.deductStock(productId, quantity); } } finally { // 释放锁 unlock(lockKey, lockValue); }}7. 限流器应用场景API 限流:限制 API 调用频率防刷接口:防止恶意刷接口用户限流:限制用户操作频率系统保护:保护系统不被过载实现方式// 固定窗口限流public boolean allowRequest(String key, int limit, int expireTime) { String count = redis.get(key); if (count == null) { redis.set(key, "1", expireTime); return true; } int currentCount = Integer.parseInt(count); if (currentCount < limit) { redis.incr(key); return true; } return false;}// 滑动窗口限流public boolean allowRequestSliding(String key, int limit, int windowSize) { long currentTime = System.currentTimeMillis(); long windowStart = currentTime - windowSize; // 移除窗口外的数据 redis.zremrangeByScore(key, 0, windowStart); // 添加当前请求 redis.zadd(key, currentTime, UUID.randomUUID().toString()); // 统计窗口内的请求数 long count = redis.zcard(key); return count <= limit;}8. 标签系统应用场景文章标签:为文章添加标签用户标签:为用户添加标签标签查询:根据标签查询内容标签推荐:基于标签推荐内容实现方式// 添加标签public void addTags(Long articleId, Set<String> tags) { String key = "article:tags:" + articleId; redis.sadd(key, tags.toArray(new String[0]));}// 获取标签public Set<String> getTags(Long articleId) { String key = "article:tags:" + articleId; return redis.smembers(key);}// 根据标签查询文章public Set<Long> getArticlesByTag(String tag) { String key = "tag:articles:" + tag; return redis.smembers(key);}// 获取共同标签public Set<String> getCommonTags(Long articleId1, Long articleId2) { String key1 = "article:tags:" + articleId1; String key2 = "article:tags:" + articleId2; return redis.sinter(key1, key2);}9. 地理位置应用场景附近的人:查找附近的人附近的位置:查找附近的位置距离计算:计算两个位置的距离位置服务:提供位置相关服务实现方式// 添加位置public void addLocation(String key, double longitude, double latitude) { redis.geoadd(key, longitude, latitude, key);}// 查找附近的位置public List<Location> getNearbyLocations(String key, double longitude, double latitude, double radius) { return redis.georadius(key, longitude, latitude, radius, GeoUnit.KM);}// 计算距离public double getDistance(String key, String member1, String member2) { return redis.geodist(key, member1, member2, GeoUnit.KM);}10. 实时统计应用场景在线人数:统计在线用户数UV 统计:统计独立访客数PV 统计:统计页面浏览量实时数据:实时统计业务数据实现方式// UV 统计(使用 HyperLogLog)public long getUV(String key) { return redis.pfcount(key);}public void addUV(String key, String userId) { redis.pfadd(key, userId);}// PV 统计(使用计数器)public long getPV(String key) { String count = redis.get(key); return count != null ? Long.parseLong(count) : 0;}public void addPV(String key) { redis.incr(key);}总结Redis 在实际项目中有广泛的应用场景,包括缓存、会话存储、计数器、排行榜、消息队列、分布式锁、限流器、标签系统、地理位置、实时统计等。每个应用场景都有其特定的实现方式和注意事项。在实际开发中,需要根据具体的业务需求,选择合适的应用场景和实现方式,充分发挥 Redis 的优势。
阅读 0·2月19日 19:35

Redis 的底层实现原理是什么?包括哪些核心数据结构和机制?

Redis 的底层实现原理是理解 Redis 高性能的关键,主要包括数据结构、网络模型、内存管理等核心内容。1. SDS(Simple Dynamic String)基本概念:SDS 是 Redis 中字符串的底层实现,是对 C 语言字符串的封装。SDS 结构:struct sdshdr { int len; // 字符串长度 int free; // 剩余可用空间 char buf[]; // 字节数组};SDS 优势:O(1) 时间复杂度获取字符串长度:C 语言字符串需要遍历整个字符串才能获取长度,时间复杂度为 O(n)避免缓冲区溢出:SDS 会检查剩余空间,空间不足时会自动扩容减少内存重分配次数:SDS 使用空间预分配和惰性释放策略,减少内存重分配次数二进制安全:SDS 可以存储任意二进制数据,包括 '\0' 字符兼容 C 语言字符串函数:SDS 遵循 C 语言字符串以 '\0' 结尾的惯例空间预分配策略:当 SDS 长度小于 1MB 时,扩容时会分配 2 倍的长度当 SDS 长度大于等于 1MB 时,扩容时会分配 1MB 的额外空间2. 链表基本概念:Redis 的链表是双向链表,用于实现 List、发布订阅、慢查询等功能。链表结构:typedef struct listNode { struct listNode *prev; // 前置节点 struct listNode *next; // 后置节点 void *value; // 节点值} listNode;typedef struct list { listNode *head; // 表头节点 listNode *tail; // 表尾节点 unsigned long len; // 链表长度 void *(*dup)(void *ptr); // 节点值复制函数 void (*free)(void *ptr); // 节点值释放函数 int (*match)(void *ptr, void *key); // 节点值比较函数} list;链表特点:双向链表:可以方便地进行前后遍历无环链表:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL带表头表尾指针:可以快速获取表头和表尾节点带链表长度计数器:可以快速获取链表长度多态:链表节点可以存储不同类型的值3. 字典(Dict)基本概念:字典是 Redis 中 Hash 的底层实现,使用哈希表实现。字典结构:typedef struct dictht { dictEntry **table; // 哈希表数组 unsigned long size; // 哈希表大小 unsigned long sizemask; // 哈希表大小掩码,用于计算索引 unsigned long used; // 已有节点数量} dictht;typedef struct dictEntry { void *key; // 键 union { void *val; uint64_t u64; int64_t s64; } v; // 值 struct dictEntry *next; // 指向下一个哈希表节点,形成链表} dictEntry;typedef struct dict { dictType *type; // 类型特定函数 void *privdata; // 私有数据 dictht ht[2]; // 两个哈希表,用于 rehash int rehashidx; // rehash 索引,-1 表示没有进行 rehash} dict;哈希算法:Redis 使用 MurmurHash2 算法计算哈希值,然后使用 hash & sizemask 计算索引。哈希冲突解决:Redis 使用链地址法解决哈希冲突,即每个哈希表节点都有一个 next 指针,指向下一个哈希表节点。Rehash 过程:为 ht[1] 分配空间,大小为第一个大于等于 ht[0].used * 2 的 2^n将 ht[0] 中的所有键值对 rehash 到 ht[1]释放 ht[0],将 ht[1] 设置为 ht[0],ht[1] 创建一个空白哈希表渐进式 Rehash:为了避免 rehash 对性能的影响,Redis 使用渐进式 rehash,即分多次将 ht[0] 中的键值对 rehash 到 ht[1]。4. 跳跃表(Skip List)基本概念:跳跃表是 Redis 中 ZSet 的底层实现,是一种有序数据结构。跳跃表结构:typedef struct zskiplistNode { sds ele; // 成员对象 double score; // 分值 struct zskiplistNode *backward; // 后退指针 struct zskiplistLevel { struct zskiplistNode *forward; // 前进指针 unsigned long span; // 跨度 } level[]; // 层} zskiplistNode;typedef struct zskiplist { struct zskiplistNode *header, *tail; // 表头和表尾节点 unsigned long length; // 跳跃表长度 int level; // 跳跃表最大层数} zskiplist;跳跃表特点:多层结构:跳跃表有多层,最底层包含所有元素,上层是下层的子集有序性:跳跃表中的元素按照 score 从小到大排序查找效率高:跳跃表的查找时间复杂度为 O(log n)空间换时间:跳跃表通过多层结构提高查找效率,但会占用更多空间5. 整数集合(IntSet)基本概念:整数集合是 Redis 中 Set 的底层实现之一,用于存储整数。整数集合结构:typedef struct intset { uint32_t encoding; // 编码方式 uint32_t length; // 集合包含的元素数量 int8_t contents[]; // 保存元素的数组} intset;整数集合特点:有序性:整数集合中的元素按照从小到大排序唯一性:整数集合中的元素都是唯一的升级机制:当新元素的类型比当前编码类型大时,会自动升级编码类型升级过程:根据新元素的类型,扩展整数集合的底层空间将原有元素转换为新类型,并放到正确的位置将新元素添加到整数集合中6. 压缩列表(ZipList)基本概念:压缩列表是 Redis 中 List、Hash、ZSet 的底层实现之一,用于存储少量元素。压缩列表结构:<zlbytes><zltail><zllen><entry><entry>...<zlend>压缩列表特点:紧凑存储:压缩列表使用连续内存空间,存储紧凑节省内存:压缩列表没有指针和额外开销,节省内存查找效率低:压缩列表的查找时间复杂度为 O(n)更新效率低:压缩列表的更新可能需要内存重分配压缩列表转换:当压缩列表的元素数量或大小超过阈值时,会转换为其他数据结构:List 转换为 linkedlist 或 quicklistHash 转换为 hashtableZSet 转换为 skiplist7. I/O 多路复用基本概念:Redis 使用 I/O 多路复用模型,可以同时处理多个客户端连接。I/O 多路复用模型:Redis 使用 epoll(Linux)、kqueue(BSD)、select(Windows)等 I/O 多路复用函数。工作流程:Redis 服务器创建 socket,绑定端口,监听连接使用 epoll_ctl 将 socket 添加到 epoll 实例中使用 epoll_wait 等待事件发生当有事件发生时,epoll_wait 返回,处理事件优势:高并发:可以同时处理多个客户端连接非阻塞:不会因为某个客户端的阻塞而影响其他客户端高效:使用事件驱动模型,效率高8. 事件循环基本概念:Redis 使用事件循环模型,处理文件事件和时间事件。文件事件:文件事件是 Redis 服务器对 socket 操作的抽象,包括可读事件和可写事件。时间事件:时间事件是 Redis 服务器对定时操作的抽象,包括定时任务和周期性任务。事件循环流程:void aeMain(aeEventLoop *eventLoop) { eventLoop->stop = 0; while (!eventLoop->stop) { // 处理文件事件 aeProcessEvents(eventLoop, AE_ALL_EVENTS); // 处理时间事件 processTimeEvents(eventLoop); }}9. 内存管理内存分配器:Redis 使用 jemalloc 作为内存分配器,jemalloc 是一个高性能的内存分配器。内存碎片:Redis 使用 jemalloc 的内存碎片整理功能,减少内存碎片。内存统计:Redis 使用 INFO memory 命令查看内存使用情况,包括 usedmemory、usedmemoryrss、usedmemory_peak 等指标。总结Redis 的底层实现原理包括 SDS、链表、字典、跳跃表、整数集合、压缩列表等数据结构,以及 I/O 多路复用、事件循环、内存管理等机制。这些底层实现保证了 Redis 的高性能和高可靠性。理解这些底层实现原理,有助于更好地使用 Redis 和优化 Redis 的性能。
阅读 0·2月19日 19:35

SameSite Cookie 属性如何防止 CSRF 攻击?

SameSite Cookie 属性是防御 CSRF 攻击的重要机制,它控制 Cookie 在跨站请求中的发送行为。SameSite 属性概述SameSite 是 Cookie 的一个属性,用于指示浏览器是否应该在跨站请求中发送该 Cookie。它有三个可选值:Strict、Lax 和 None。属性值详解1. SameSite=Strict行为:只在同站请求中发送 Cookie跨站请求(包括导航)都不会发送 Cookie适用场景:银行、支付等高安全性应用敏感操作(如转账、修改密码)不需要跨站功能的应用示例:// 设置 SameSite=Strictdocument.cookie = 'sessionid=abc123; SameSite=Strict; Secure; HttpOnly';优点:提供最强的 CSRF 防护完全阻止跨站请求携带 Cookie缺点:用户从外部链接进入时需要重新登录可能影响用户体验2. SameSite=Lax(推荐)行为:允许某些跨站请求发送 Cookie阻止大多数 CSRF 攻击允许的跨站请求:顶级导航(GET 请求)链接跳转(<a> 标签)表单 GET 请求阻止的跨站请求:POST 请求(表单提交)AJAX 请求<iframe>、<img>、<script> 等资源加载示例:// 设置 SameSite=Laxdocument.cookie = 'sessionid=abc123; SameSite=Lax; Secure; HttpOnly';适用场景:大多数 Web 应用需要外部链接跳转的应用平衡安全性和用户体验优点:提供良好的 CSRF 防护用户体验较好现代浏览器默认值缺点:某些跨站 POST 请求可能受影响需要确保应用兼容性3. SameSite=None行为:允许所有跨站请求发送 Cookie必须配合 Secure 属性使用示例:// 设置 SameSite=Nonedocument.cookie = 'sessionid=abc123; SameSite=None; Secure; HttpOnly';适用场景:需要跨站功能的应用第三方登录(如 OAuth)嵌入式内容优点:不影响现有跨站功能兼容旧应用缺点:无法防御 CSRF 攻击需要其他防护措施浏览器支持情况主流浏览器支持Chrome:51+ 版本支持,80+ 版本默认 LaxFirefox:60+ 版本支持Safari:12+ 版本支持Edge:79+ 版本支持Opera:39+ 版本支持兼容性处理// 检测浏览器是否支持 SameSitefunction setCookie(name, value, days) { let expires = ''; if (days) { const date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); expires = '; expires=' + date.toUTCString(); } // 现代浏览器 let sameSite = '; SameSite=Lax'; // 旧版浏览器不支持 SameSite const cookieString = name + '=' + value + expires + sameSite + '; path=/; Secure; HttpOnly'; document.cookie = cookieString;}同站与跨站的定义同站(Same-Site)相同的顶级域名(eTLD+1)例如:https://example.com 和 https://www.example.com 是同站https://app.example.com 和 https://api.example.com 是同站跨站(Cross-Site)不同的顶级域名例如:https://example.com 和 https://evil.com 是跨站https://example.com 和 https://example.net 是跨站实际应用示例1. 银行应用(Strict)// 高安全性要求document.cookie = 'sessionid=abc123; SameSite=Strict; Secure; HttpOnly; Max-Age=3600';2. 电商网站(Lax)// 平衡安全性和用户体验document.cookie = 'sessionid=abc123; SameSite=Lax; Secure; HttpOnly; Max-Age=86400';3. 第三方登录(None)// 需要跨站功能document.cookie = 'oauth_token=xyz789; SameSite=None; Secure; HttpOnly; Max-Age=3600';框架集成Express.jsapp.use(session({ secret: 'your-secret', cookie: { secure: true, httpOnly: true, sameSite: 'lax' }}));Spring Boot@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .and() .csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); }}Django# settings.pySESSION_COOKIE_SECURE = TrueSESSION_COOKIE_HTTPONLY = TrueSESSION_COOKIE_SAMESITE = 'Lax'CSRF_COOKIE_SECURE = TrueCSRF_COOKIE_HTTPONLY = TrueCSRF_COOKIE_SAMESITE = 'Lax'常见问题1. SameSite=None 不生效原因:缺少 Secure 属性解决:必须同时设置 Secure 属性// 错误document.cookie = 'sessionid=abc123; SameSite=None';// 正确document.cookie = 'sessionid=abc123; SameSite=None; Secure';2. 跨站 POST 请求失败原因:SameSite=Lax 阻止了跨站 POST 请求解决:使用 SameSite=None(需要其他 CSRF 防护)改用同站请求使用 CSRF Token3. 第三方登录失败原因:SameSite 属性阻止了跨站 Cookie解决:为特定 Cookie 设置 SameSite=None使用 OAuth 2.0 的授权码模式使用 PostMessage 通信最佳实践1. 默认使用 SameSite=Lax// 大多数应用的最佳选择document.cookie = 'sessionid=abc123; SameSite=Lax; Secure; HttpOnly';2. 敏感操作使用 SameSite=Strict// 高安全性要求document.cookie = 'admin_session=xyz789; SameSite=Strict; Secure; HttpOnly';3. 避免 SameSite=None// 尽量避免,除非必要// 如果必须使用,配合其他防护措施document.cookie = 'third_party_token=abc123; SameSite=None; Secure; HttpOnly';4. 配合其他防护措施CSRF TokenOrigin/Referer 验证自定义请求头5. 测试兼容性在不同浏览器中测试测试跨站场景测试第三方集成总结SameSite Cookie 属性是防御 CSRF 攻击的有效手段。推荐使用 SameSite=Lax 作为默认配置,它在提供良好 CSRF 防护的同时保持良好的用户体验。对于高安全性要求的应用,可以使用 SameSite=Strict。尽量避免使用 SameSite=None,除非确实需要跨站功能,并配合其他防护措施。
阅读 0·2月19日 19:35