MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Redis单机数据库服务器数据库的管理优化

2024-12-095.2k 阅读

Redis 单机数据库服务器数据库的管理优化

Redis 内存管理优化

  1. 内存分配策略
    • Redis 使用自己的内存分配器(如 jemalloc ,在 Linux 下默认使用)来管理内存。Jemalloc 在减少内存碎片方面表现出色。例如,当 Redis 频繁地进行键值对的插入和删除操作时,如果使用系统默认的内存分配器(如 glibc 的 malloc ),可能会导致大量的内存碎片,使得内存利用率降低。而 jemalloc 通过将内存划分为不同大小的 chunk ,并采用特定的分配和回收策略,能有效减少这种情况。
    • 我们可以通过调整 Redis 配置文件中的 malloc - allocator 参数来选择不同的内存分配器。例如,如果想使用 tcmalloc (Google 开发的内存分配器),可以在 Redis 配置文件中添加:
    malloc - allocator tcmalloc
    
    • 不过需要注意的是,不同的内存分配器在不同的负载场景下表现不同,需要根据实际业务场景进行测试和选择。比如,在小对象频繁创建和销毁的场景下,jemalloc 可能更具优势;而在大对象分配为主的场景下,tcmalloc 可能有更好的表现。
  2. 内存使用监控
    • Redis 提供了 INFO 命令来获取服务器的各种信息,其中包括内存使用情况。通过执行 INFO memory ,可以获取详细的内存使用指标。例如:
    $ redis - cli INFO memory
    # Memory
    used_memory:939928
    used_memory_human:917.90K
    used_memory_rss:1185792
    used_memory_rss_human:1.13M
    used_memory_peak:939928
    used_memory_peak_human:917.90K
    used_memory_peak_perc:100.00%
    used_memory_overhead:892656
    used_memory_startup:802296
    used_memory_dataset:47272
    used_memory_dataset_perc:50.26%
    
    • used_memory 表示 Redis 分配器分配的内存总量,以字节为单位。used_memory_human 则以更易读的方式显示这个值。used_memory_rss 是从操作系统角度看到的 Redis 进程占用的物理内存大小。used_memory_peak 记录了 Redis 使用内存的峰值。
    • 可以通过编写脚本定期获取这些指标,来监控 Redis 的内存使用趋势。比如使用 Python 和 redis - py 库:
    import redis
    import time
    
    r = redis.Redis(host='localhost', port = 6379, db = 0)
    
    while True:
        info = r.info('memory')
        print(f"Used Memory: {info['used_memory_human']}, RSS: {info['used_memory_rss_human']}")
        time.sleep(60)
    
  3. 内存优化配置
    • maxmemory 参数:通过设置 maxmemory ,可以限制 Redis 使用的最大内存量。当 Redis 内存使用达到这个限制时,会根据 maxmemory - policy 策略进行数据淘汰。例如,如果设置 maxmemory 100mb ,表示 Redis 最多使用 100MB 的内存。
    • maxmemory - policy 策略:
      • noeviction :默认策略,当内存达到限制时,不进行任何数据淘汰,新的写入操作会返回错误,适合不允许数据丢失的场景。
      • volatile - lru :从设置了过期时间的键中,使用最近最少使用(LRU)算法淘汰数据。例如,假设我们有一些缓存数据,这些数据设置了过期时间,使用这个策略可以优先淘汰长时间未被访问的缓存数据。
      • volatile - ttl :从设置了过期时间的键中,优先淘汰剩余生存时间(TTL)短的数据。如果有一些数据的时效性很强,比如一些限时促销的缓存数据,使用这个策略能保证先淘汰即将过期的数据。
      • volatile - random :从设置了过期时间的键中,随机淘汰数据。
      • allkeys - lru :从所有键中,使用 LRU 算法淘汰数据,适合缓存场景,不区分数据是否设置了过期时间。
      • allkeys - random :从所有键中,随机淘汰数据。
    • 例如,如果我们的 Redis 主要用于缓存,希望在内存不足时优先淘汰长时间未被访问的缓存数据,可以在配置文件中设置:
    maxmemory 100mb
    maxmemory - policy allkeys - lru
    

Redis 持久化优化

  1. RDB 持久化优化
    • RDB 原理:RDB(Redis Database)是 Redis 的一种持久化方式,它将 Redis 在某一时刻的数据以快照的形式保存到磁盘上。在进行 RDB 持久化时,Redis 会 fork 一个子进程,由子进程负责将内存数据写入到临时 RDB 文件中,完成后再将临时文件替换正式的 RDB 文件。
    • 优化配置
      • save 配置:在 Redis 配置文件中,save 参数用于设置触发 RDB 持久化的条件。例如,save 900 1 表示在 900 秒内如果至少有 1 个键被修改,则触发 RDB 持久化。不合理的 save 配置可能导致频繁的持久化操作,影响性能。如果业务对数据丢失不太敏感,可以适当延长触发时间间隔,如 save 3600 10 ,表示 1 小时内至少有 10 个键被修改才触发持久化。
      • rdbcompression :这个参数用于控制是否对 RDB 文件进行压缩。默认是开启的(rdbcompression yes),虽然压缩会占用一定的 CPU 资源,但能显著减少 RDB 文件的大小,节省磁盘空间。如果服务器 CPU 资源紧张,可以考虑关闭压缩(rdbcompression no)。
      • rdbchecksum :用于在加载 RDB 文件时进行数据校验,默认是开启的(rdbchecksum yes)。开启校验会增加加载 RDB 文件的时间,如果对数据准确性要求极高,建议保持开启;如果追求快速加载,且对数据准确性有其他保障机制,可以考虑关闭(rdbchecksum no)。
    • 代码示例:我们可以通过 Redis 命令手动触发 RDB 持久化。在 Redis 客户端中执行 SAVE 命令,会阻塞当前 Redis 进程,直到 RDB 持久化完成;执行 BGSAVE 命令,则会在后台异步进行 RDB 持久化,不会阻塞主线程。
    $ redis - cli BGSAVE
    Background saving started
    
  2. AOF 持久化优化
    • AOF 原理:AOF(Append - Only - File)持久化方式是将 Redis 执行的写命令以追加的方式保存到 AOF 文件中。当 Redis 重启时,通过重新执行 AOF 文件中的命令来恢复数据。AOF 持久化可以提供更高的数据安全性,因为它可以配置为每执行一条写命令就同步到磁盘(appendfsync always)。
    • 优化配置
      • appendfsync :该参数有三个可选值:alwayseverysecnoalways 表示每执行一条写命令就同步到磁盘,数据安全性最高,但性能影响最大;everysec 表示每秒同步一次,这是一个性能和数据安全性的平衡选择;no 表示由操作系统决定何时同步,性能最高,但数据安全性最低。一般情况下,everysec 是比较常用的配置。
      • no - appendfsync - on - rewrite :当开启 AOF 重写时,默认情况下新的写命令会同时追加到 AOF 文件和重写缓冲区。这个参数设置为 yes 时,在 AOF 重写期间,新的写命令只会追加到重写缓冲区,而不会追加到 AOF 文件,这样可以提高重写效率,但如果在重写过程中服务器崩溃,可能会丢失部分数据。一般建议保持默认值 no
      • auto - aof - rewrite - min - sizeauto - aof - rewrite - percentageauto - aof - rewrite - min - size 设置 AOF 文件触发重写的最小大小,auto - aof - rewrite - percentage 设置 AOF 文件当前大小相对于上次重写后大小的增长百分比,当 AOF 文件大小同时满足这两个条件时,会触发 AOF 重写。例如,auto - aof - rewrite - min - size 64mbauto - aof - rewrite - percentage 100 表示当 AOF 文件大小超过 64MB 且比上次重写后大小增长了 100% 时,触发 AOF 重写。合理设置这两个参数可以避免不必要的重写操作,减少对性能的影响。
    • AOF 重写优化:AOF 重写是为了压缩 AOF 文件大小,减少磁盘空间占用和恢复时间。在重写过程中,Redis 会 fork 一个子进程,子进程根据当前内存数据生成一个新的 AOF 文件,而主线程继续处理客户端请求。为了优化重写过程,可以尽量减少在重写期间的写操作。比如,可以在业务低峰期手动触发重写,通过执行 BGREWRITEAOF 命令。
    $ redis - cli BGREWRITEAOF
    Background append only file rewriting started
    

Redis 性能优化

  1. 客户端连接优化
    • 连接池使用:在应用程序中,使用连接池可以减少频繁创建和销毁 Redis 连接的开销。以 Java 为例,使用 Jedis 连接池:
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.JedisPoolConfig;
    
    public class RedisConnectionPoolExample {
        private static JedisPool jedisPool;
    
        static {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(100);
            config.setMaxIdle(20);
            config.setMinIdle(5);
            jedisPool = new JedisPool(config, "localhost", 6379);
        }
    
        public static Jedis getJedis() {
            return jedisPool.getResource();
        }
    
        public static void main(String[] args) {
            Jedis jedis = getJedis();
            try {
                jedis.set("key", "value");
                String value = jedis.get("key");
                System.out.println("Value: " + value);
            } finally {
                jedis.close();
            }
        }
    }
    
    • 合理设置连接参数:在 Redis 配置文件中,tcp - backlog 参数用于设置 TCP 监听队列的长度。默认值为 511 ,如果客户端连接请求非常频繁,可以适当增大这个值,以避免连接请求被拒绝。例如,设置为 tcp - backlog 1024
  2. 命令优化
    • 批量操作:Redis 支持批量执行命令,以减少网络开销。例如,在 Python 中使用 redis - py 库,可以使用管道(Pipeline)来批量执行命令:
    import redis
    
    r = redis.Redis(host='localhost', port = 6379, db = 0)
    pipe = r.pipeline()
    pipe.set('key1', 'value1')
    pipe.set('key2', 'value2')
    pipe.get('key1')
    pipe.get('key2')
    results = pipe.execute()
    print(results)
    
    • 避免大键值操作:大键值(如非常大的字符串、集合等)会占用大量内存,并且在进行读写操作时会阻塞 Redis 主线程较长时间。尽量将大数据进行拆分存储。例如,如果有一个非常大的 JSON 对象,可以将其拆分成多个小的键值对进行存储。
  3. CPU 资源优化
    • 绑定 CPU 核心:可以通过将 Redis 进程绑定到特定的 CPU 核心上,避免 CPU 上下文切换带来的性能开销。在 Linux 系统中,可以使用 taskset 命令。例如,假设 Redis 进程 ID 为 1234 ,要将其绑定到 CPU 核心 0 和 1 上,可以执行:
    taskset - p 0x3 1234
    
    • 优化配置参数:在 Redis 配置文件中,io - threads 参数可以开启多线程 I/O ,提高 Redis 的网络 I/O 性能。不过需要注意的是,Redis 的命令处理仍然是单线程的,多线程 I/O 主要用于提高网络读写性能。例如,设置 io - threads 4 表示开启 4 个 I/O 线程(除主线程外)。

Redis 数据结构优化

  1. 字符串优化
    • 尽量使用短字符串:长字符串会占用更多的内存,并且在进行读写操作时性能相对较低。例如,如果存储用户 ID ,尽量使用固定长度的短字符串表示,而不是使用过长的 UUID 格式(如果可以通过其他方式生成更短且唯一的 ID )。
    • 字符串拼接优化:在进行字符串拼接操作时,如果使用 Redis 的 APPEND 命令,要注意性能。因为每次 APPEND 操作都会修改字符串对象,可能导致内存重新分配。如果需要大量拼接操作,可以考虑在客户端先进行拼接,然后再使用 SET 命令一次性写入 Redis 。
  2. 哈希优化
    • 合理设置哈希字段数量:哈希数据结构适用于存储对象类型的数据。但是,如果哈希中的字段数量过多,会导致单个哈希对象占用较大内存,并且在进行部分字段操作时性能下降。例如,如果一个哈希对象存储用户信息,对于一些不常用的信息,可以考虑拆分到另一个哈希对象或者使用其他数据结构存储。
    • 哈希编码优化:Redis 对于哈希对象有两种编码方式:ziplisthashtable 。当哈希对象的字段数量较少且字段和值的长度都较短时,Redis 会使用 ziplist 编码,这种编码方式占用内存少且访问速度快。可以通过设置 hash - max - zipped - entrieshash - max - zipped - value 参数来控制何时使用 ziplist 编码。例如,hash - max - zipped - entries 512 表示当哈希对象的字段数量小于等于 512 时,可能使用 ziplist 编码;hash - max - zipped - value 64 表示当哈希对象的字段值长度小于等于 64 字节时,可能使用 ziplist 编码。
  3. 列表优化
    • 双向链表特性利用:Redis 的列表是基于双向链表实现的。在进行插入和删除操作时,在列表头部和尾部操作的时间复杂度为 O(1) ,而在中间位置插入和删除的时间复杂度为 O(N) 。因此,如果业务场景允许,尽量在列表头部或尾部进行操作。例如,实现一个简单的消息队列,可以使用 LPUSHRPOP 命令从列表头部插入消息,从列表尾部取出消息。
    • 列表长度控制:过长的列表会占用大量内存,并且在进行遍历等操作时性能较低。可以根据业务需求,对列表长度进行限制。例如,使用 LTRIM 命令定期修剪列表长度。假设我们有一个存储日志的列表,只需要保留最近 1000 条日志,可以执行:
    $ redis - cli LTRIM log_list 0 999
    
  4. 集合优化
    • 集合操作优化:Redis 集合支持多种操作,如 SADD (添加元素)、SREM (删除元素)、SMEMBERS (获取所有元素)等。在进行集合操作时,要注意操作的时间复杂度。例如,SMEMBERS 操作的时间复杂度为 O(N) ,如果集合元素数量非常大,这个操作会比较耗时。可以考虑使用 SSCAN 命令进行渐进式遍历。
    • 集合编码优化:与哈希类似,Redis 集合也有两种编码方式:intsethashtable 。当集合中的元素都是整数且数量较少时,会使用 intset 编码,这种编码方式占用内存少且操作速度快。可以通过设置 set - max - intset - entries 参数来控制何时使用 intset 编码。例如,set - max - intset - entries 512 表示当集合中的元素数量小于等于 512 且都是整数时,可能使用 intset 编码。
  5. 有序集合优化
    • 分数范围查询优化:有序集合常用于排行榜等场景,其中元素根据分数进行排序。在进行分数范围查询时,如 ZRANGEBYSCORE 操作,要注意分数范围的设置。如果范围过大,可能会返回大量数据,影响性能。可以根据业务需求,合理限制返回的元素数量。例如,只获取排行榜前 10 名,可以执行:
    $ redis - cli ZRANGEBYSCORE leaderboard +inf - inf LIMIT 0 10
    
    • 有序集合编码优化:有序集合有 ziplistskiplist 两种编码方式。当有序集合的元素数量较少且成员和分数的长度都较短时,会使用 ziplist 编码。可以通过设置 zset - max - zipped - entries 参数来控制何时使用 ziplist 编码。例如,zset - max - zipped - entries 128 表示当有序集合的元素数量小于等于 128 时,可能使用 ziplist 编码。

Redis 安全优化

  1. 认证设置
    • 在 Redis 配置文件中,通过设置 requirepass 参数来设置密码。例如,设置密码为 mypassword
    requirepass mypassword
    
    • 客户端连接时,需要使用 AUTH 命令进行认证。以 Redis 客户端为例:
    $ redis - cli
    127.0.0.1:6379> AUTH mypassword
    OK
    
    • 应用程序中也需要在连接 Redis 时提供密码。以 Python 的 redis - py 库为例:
    import redis
    
    r = redis.Redis(host='localhost', port = 6379, db = 0, password='mypassword')
    
  2. 绑定 IP
    • 在 Redis 配置文件中,通过 bind 参数指定 Redis 服务器监听的 IP 地址。例如,只监听本地回环地址 127.0.0.1 ,可以设置:
    bind 127.0.0.1
    
    • 如果需要监听多个 IP 地址,可以用空格分隔,如 bind 127.0.0.1 192.168.1.100 。这样可以限制只有指定 IP 地址的客户端能够连接 Redis ,提高安全性。
  3. 端口隐藏
    • 默认情况下,Redis 监听 6379 端口,这是众所周知的。可以通过修改 port 参数,将 Redis 监听的端口改为其他不常用的端口。例如,设置为 12345 :
    port 12345
    
    • 修改端口后,客户端连接时需要指定新的端口,如 redis - cli - p 12345 。虽然这不能完全防止恶意扫描,但可以增加一定的安全性。
  4. 安全配置检查
    • 可以使用 Redis - Sentinel 提供的 redis - cli --eval 命令来检查 Redis 的安全配置。例如,检查是否设置了密码:
    $ redis - cli --eval /path/to/check_security.lua
    
    • check_security.lua 脚本可以自定义,通过检查 CONFIG GET requirepass 的返回值来判断是否设置了密码等安全配置项。

通过以上对 Redis 单机数据库服务器在内存管理、持久化、性能、数据结构和安全等方面的优化,可以有效提升 Redis 的运行效率和稳定性,满足不同业务场景的需求。