【温故知新】Redis04性能瓶颈与优化

Redis性能瓶颈与优化

一、核心瓶颈
主要限制:内存容量(单节点内存上限)、网络带宽(高并发下网络I/O瓶颈)、单核CPU性能(单线程主线程瓶颈)。

二、优化方案
1、软件层优化
版本选择:使用Redis 6.0+版本,开启网络I/O多线程,根据服务器配置合理设置线程数,突破网络瓶颈。
Key优化:避免大Key(拆分大Hash、大List),定期扫描大对象,减少单命令耗时;合理设计键名与数据结构,适配动态编码优化。
命令优化:使用Pipeline批量操作、Lua脚本、事务,减少网络往返次数;避免频繁执行耗时命令(如keys、hgetall),替换为高效替代命令。
持久化优化:合理配置持久化策略,缓存场景可关闭AOF或采用everysec刷盘;主从复制场景开启无磁盘复制,减少IO开销;定期清理过期键,优化内存使用。

2、系统层优化
内存优化:禁用透明大页(THP),避免内存碎片增加;选用jemalloc/tcmalloc内存分配器,提升内存分配效率。
CPU优化:CPU绑核,将Redis进程绑定到固定CPU核心,减少缓存失效,提升CPU利用率。
网络优化:优化TCP配置,调整连接超时时间、缓冲区大小,提升网络传输效率;避免网络带宽过载,合理规划集群节点网络。

三、性能监控
慢查询日志:配置合理的慢查询阈值,记录耗时命令,分析命令执行效率,优化耗时操作。
延迟监控:利用Redis内置延迟钩子,统计99分位延迟数据,实时掌握服务响应延迟情况,及时发现性能瓶颈。
内存分析:通过INFO命令获取多维内存统计数据,扫描大对象,分析内存占用情况,优化内存使用效率,避免内存溢出。

【温故知新】Redis03稳定性

Redis稳定性

一、单机稳定性
1、持久化机制(数据安全保障)
混合持久化(默认开启):结合RDB与AOF优势,AOF文件头部存储RDB全量数据,尾部存储增量命令,兼顾RDB恢复速度快与AOF数据全的特点,规避单一持久化短板。
RDB快照持久化:通过fork子进程+写时复制(Copy-on-Write)机制,对内存数据做全量快照,写入.rdb二进制文件,恢复速度快;支持定期备份与灾备,配置灵活,开启stop-writes-on-bgsave-error保护,避免备份失败导致数据丢失;配合完善的数据恢复机制,保障数据安全。
AOF日志持久化:采用命令追加模式,将所有写命令追加到.aof文件,恢复时通过重放命令还原数据,实现实时日志备份;支持三种可配置刷盘策略(always每个命令fsync最安全、everysec每秒后台fsync默认、no由OS决定最性能),兼顾数据安全与性能;AOF重写机制可合并冗余命令、压缩历史日志,fork子进程执行,不阻塞主线程,实现持久化异步化。

2、容错与安全机制(减少异常影响)
内存安全:maxmemory限制内存容量,配合过期删除与内存淘汰策略,避免内存溢出;大Key检测规避单命令阻塞;写时复制减少内存占用,保障内存合理利用。
并发安全:单线程命令原子执行,避免并发不一致;Lua脚本、事务机制进一步强化原子性,支持复杂业务场景的并发控制。
权限与监控:密码认证、ACL权限控制,防止非法操作;慢查询日志(阈值可配置)记录耗时命令,便于排查性能隐患;内置监控机制,支持实时掌握服务运行状态。
架构优势:简单架构降低故障率,减少并发Bug,便于调试与维护;核心组件设计简洁,依赖少,进一步提升服务稳定性。

二、高可用架构
1、主从复制
核心机制:采用主写从读架构,从节点异步复制主节点数据,实现数据冗余与读写分离,减少主节点负载压力。
同步方式:支持全量同步(基于RDB快照)与增量同步(基于AOF日志),通过部分重同步PSYNC2机制(依托复制积压缓冲区与主从RunID匹配),减少同步数据量,提升同步效率。
优化设计:支持无磁盘复制(diskless),避免持久化文件写入磁盘带来的IO开销;主节点宕机时,可实现故障自动转移,保障服务不中断。

2、哨兵模式
核心作用:通过独立的哨兵进程,实现主从节点状态监控、自动故障检测、主节点选举与配置自动更新,无需人工干预,实现故障自愈。
故障检测:采用主观下线(SDOWN,单哨兵检测节点异常)与客观下线(ODOWN,多哨兵共识确认异常)相结合的方式,避免误判,确保故障检测准确性。
高可用保障:多哨兵部署,避免哨兵单点故障;主节点宕机后,通过Raft算法选举最优从节点升为主节点,自动更新集群配置,快速恢复服务。

3、Cluster集群(横向扩展)
架构设计:去中心化架构,通过16384个哈希槽实现数据分片存储,支持多主多从部署,节点间通过Gossip协议进行状态同步,通信规范高效。
扩展能力:支持动态增删节点,解决单节点内存上限问题,实现横向扩展,适配业务增长带来的数据量提升需求。
容错机制:主节点宕机后,其从节点自动补位,哈希槽自动迁移;支持故障检测与迁移,多节点宕机时,只要哈希槽全覆盖,就不影响整体服务可用性。

【温故知新】Redis02核心数据结构与底层算法

Redis数据结构及算法

一、基础对象结构
核心对象:所有数据类型均基于redisObject统一封装,包含五大核心属性:type(数据类型)、encoding(底层编码)、ptr(数据指针)、refcount(引用计数)、lru(淘汰标识)。
设计优势:支持动态编码,可根据数据量、数据类型按需切换底层实现,兼顾性能与内存利用率;通过引用计数实现内存自动回收,支持对象共享,进一步提升内存利用率。

二、动态编码优化
核心原则:同一数据类型根据数据量、数据特性,自动切换底层编码,支持编码升级与降级,针对不同场景优化,减少内存碎片,提升算法效率。
1、String
底层采用SDS动态字符串(主力)与int整数编码(小整数场景),适配短字符串常用场景。
2、List
小数据量用压缩列表(ziplist),大数据量转快速链表(quicklist),同时支持listpack紧凑列表,解决ziplist级联更新问题。
3、Hash
小对象用ziplist,大数据量转Dict字典,平衡内存与查询性能。
4、Set
整数小集合用intset,字符串/大集合用hashtable,实现高效去重。
5、ZSet
小数据量用ziplist,大数据量用skiplist跳表+hashtable组合,兼顾排序与查询效率。

三、核心数据结构
1、SDS动态字符串
核心特性:二进制安全,支持预分配冗余空间减少扩容开销,采用惰性释放策略降低内存重分配损耗,可实现O(1)时间复杂度获取字符串长度。
优势:规避C字符串短板,适配Redis中字符串的各种使用场景(如键名、字符串值),兼顾性能与灵活性。

2、链表
底层实现:双端无环链表,带有表头指针与长度计数器,便于快速定位链表首尾节点与获取链表长度。
作用:作为部分数据结构的底层支撑,提升插入、删除操作的灵活性,适配需要频繁修改的场景。

3、Dict字典
核心机制:采用链地址法解决哈希冲突,通过渐进式Rehash实现无阻塞扩容,哈希算法选用MurmurHash2,哈希分布均匀,减少冲突概率。
优化设计:双哈希表结构实现平滑扩容,通过负载因子控制扩容时机;渐进式Rehash分批次迁移数据,结合定时任务与读写操作触发迁移,避免阻塞主线程。
性能:单键增删改查操作时间复杂度为O(1),ziplist编码场景下,因紧凑存储(CPU缓存友好,无指针开销)进一步提升效率。

4、压缩列表(ziplist)
核心设计:小数据紧凑存储,采用连续内存块组织数据,元素间无指针开销,大幅节省内存空间,支持快速定位元素。
优化:通过级联更新控制,减少级联更新带来的性能损耗,适配小数据量、高频访问的场景,后续被listpack进一步优化替代。

5、快速链表(quicklist)
核心结构:双向链表与Ziplist节点的结合,通过分段存储平衡内存占用与操作性能,避免纯链表的内存碎片问题。
性能优势:链表两端插入、删除操作时间复杂度为O(1),Ziplist节点紧凑存储提升内存利用率,兼顾灵活性与内存效率。

6、紧凑列表(listpack)
设计目的:替代ziplist,解决ziplist级联更新的性能问题,优化内存占用与遍历效率。
优势:支持倒序遍历优化,紧凑存储节省内存,无级联更新隐患,适配小数据量紧凑存储场景。

7、整数集合(intset)
核心特性:有序存储、无重复元素,支持自动升级策略(int16→int32→int64),根据元素大小动态调整存储类型,最大限度节省内存。
性能:采用二分查找算法,查询效率高,适配纯整数小集合场景,内存利用率远超hashtable。

8、跳表(skiplist)
核心结构:多层索引结构,层级随机晋升概率为1/4,实现简单(无需旋转操作),替代平衡树降低实现复杂度。
性能:平均查询时间复杂度为O(logN),插入、删除操作效率高,支持高效范围查询,结合hashtable实现O(1)时间复杂度获取元素分数,双结构兼顾排序与查询需求。

四、核心底层算法
1、渐进式Rehash
Dict字典核心优化,分批次迁移哈希表数据,结合定时任务与读写操作触发迁移,避免扩容过程中阻塞主线程,保障服务流畅。

2、MurmurHash2
Dict字典核心哈希算法,哈希分布均匀,冲突概率低,计算高效,适配Redis的哈希存储场景。

3、LRU(最近最少使用)
近似实现,通过随机采样key提升效率,作为内存淘汰策略之一,平衡性能与淘汰准确性,配合随机采样池优化淘汰效果。

4、LFU(最不经常使用)
基于访问频率实现,通过24位计数器记录元素访问频率,设置衰减因子防止冷数据永存,适配高频访问场景的内存淘汰需求。

5、HyperLogLog
基数估计算法,用于统计独立元素个数,内存占用极低,标准误差仅0.81%,无需存储全部元素,适配大数据量基数统计场景。

6、GeoHash
地理位置编码算法,将二维经纬度坐标映射到一维字符串,支持距离计算与范围查询,适配地理位置相关业务场景。

五、过期与淘汰策略
1、过期键删除
采用惰性删除与定期删除相结合的方式,平衡CPU与内存开销。惰性删除在访问键时判断是否过期,避免无用删除操作;定期删除通过随机采样删除过期键,控制删除频率,不阻塞主线程;过期键独立存储于哈希表,提升清理效率。

2、内存淘汰策略
共8种,核心分为4类,配合maxmemory配置使用,防止内存溢出:

2.1、LRU
近似算法,通过随机采样key实现,结合随机采样池优化淘汰准确性。

2.2、LFU
基于访问频率,通过24位计数器记录访问频率,设置衰减因子防止冷数据永存。

2.3、volatile系列
仅淘汰设置过期时间的键,适用于需要保留未过期数据的场景。

2.4、allkeys系列
淘汰所有键,适用于缓存场景,优先保留访问频率高的键;同时支持按TTL过期时间淘汰,适配时间敏感场景。

【温故知新】Redis01核心架构

Redis核心架构

一、Redis 核心定位
本质:纯内存KV数据库 + 分布式缓存,采用C语言实现,代码库简洁(约3万行),由核心开发者维护,Bug率极低,支撑服务稳定性。
核心优势:亚毫秒级低延迟、高并发、高可用、多场景适配,依托纳秒级内存读写基础,可实现微秒级响应时间。
设计理念:极致优化内存操作、简化交互、保障数据安全与服务可用,简单架构降低故障率,便于调试和维护。

二、Redis 为什么这么快
架构层优势:单线程事件循环(无锁、无上下文切换开销)+ IO多路复用(Reactor模式,非阻塞、高并发)+ 全内存存储(纳秒级读写,微秒级响应),三者协同,彻底规避磁盘I/O瓶颈与多线程并发损耗,奠定高性能基础。
算法层优势:以O(1)时间复杂度操作为主,结合动态编码(按需切换底层实现)、高效底层算法(跳表、渐进式Rehash、LRU、LFU等),优化操作效率与内存占用,适配不同业务场景。
实现层优势:jemalloc/tcmalloc内存分配器减少内存碎片、提升复用率;RESP精简协议减少编解码与网络传输开销;异步后台线程避免主线程阻塞;Redis 6.0+网络I/O多线程优化,进一步突破网络瓶颈。
细节优化:Pipeline批量操作、零拷贝技术减少网络往返与数据复制开销;高效数据结构设计节省内存与解析开销;单线程无锁设计避免竞态问题;Lua脚本、事务实现命令原子执行,提升并发效率;RDB、AOF持久化优化,不阻塞主线程,兼顾数据安全与性能。

三、单机架构
内存数据库:数据全量存储于内存,实现纳秒级读写,规避磁盘I/O寻道延迟,可高速访问且无需等待磁盘I/O,支持RDB与AOF两种持久化机制。
事件驱动:基于Reactor模式,通过I/O多路复用监听客户端请求,提升并发处理效率,贴合单线程模型的事件驱动核心设计。
持久化双引擎:采用RDB快照(全量备份)+AOF日志(增量备份)的组合,支持混合持久化,兼顾数据安全与恢复效率,后续将详细展开具体实现。

四、运行模型
1、单线程主线程
核心机制:基于Reactor模式的事件驱动,单主线程集中处理所有客户端读写、计算命令,依托事件循环机制实现高效调度。
性能优势:无多线程上下文切换与锁竞争开销(上下文切换耗时可达微秒级),命令原子执行、天然线程安全;无锁设计简化架构,事件处理时间复杂度达到O(1),同时避免竞态条件,简化并发控制。
优化设计:引入异步后台线程,专门处理过期删除、AOF重写等耗时操作,避免阻塞主线程;单线程模型简化整体设计,减少并发相关Bug,降低调试与维护成本。

2、IO多路复用机制
底层实现:优先采用epoll(Linux环境),兼容kqueue/select实现跨平台适配,贴合单线程事件循环设计,支持非阻塞I/O操作。
性能优势:单线程可监听海量客户端连接(多socket),通过非阻塞IO避免空等,实现一核多并发;高效处理大量并发连接的同时,减少系统调用次数,降低性能损耗。
工作逻辑:由内核负责监听多个文件描述符,当有就绪事件(如客户端请求)时,及时通知主线程处理,确保主线程高效响应,不做无用等待。

3、多线程演进(Redis 6.0+)
核心优化:引入网络I/O多线程设计,可根据实际场景配置线程数,仅将网络I/O相关操作(连接建立、数据读写)交由多线程处理。
设计原则:命令执行仍保持单线程,兼顾高并发与命令原子性,既解决网络I/O瓶颈,又避免多线程并发带来的竞态问题,进一步提升网络读写效率。

五、内存架构
1、全内存存储模型
核心设计:所有数据全量存储于内存,读写操作直接作用于内存,数据结构设计紧凑,内存访问速度远超磁盘,无需等待磁盘I/O,实现微秒级响应时间。
性能优势:彻底规避磁盘I/O寻道与读写延迟,多数操作耗时可达O(1),奠定亚毫秒级延迟的基础;内存访问基于纳秒级速度,进一步放大性能优势。
内存优化:采用对象共享与引用计数机制,提升内存利用率;引入内存池管理,减少内存碎片;配合内存预分配与惰性释放策略,优化内存使用效率,降低内存重分配开销。

2、内存分配与管理
分配器选择:默认采用jemalloc内存分配器,也支持tcmalloc,替代C标准malloc,适配Redis的内存使用场景。
优化设计:按内存块大小分块分配,减少内存碎片,提升内存复用率;通过负载因子控制哈希表扩容时机,避免扩容过程中阻塞主线程,优化整体性能。
辅助优化:采用惰性释放策略,避免频繁释放内存带来的性能波动;对部分数据结构进行内存压缩,进一步降低内存占用。

3、内存安全机制
写时复制(Copy-on-Write):持久化过程中,子进程共享主进程内存空间,仅当主进程执行写操作时,才复制对应内存块,大幅减少内存占用,是RDB持久化的核心机制。
大Key检测:实时监控大Key(如单Hash存储百万级字段),避免单命令执行耗时过长阻塞主线程,保障服务流畅运行。
内存限制:通过maxmemory配置最大内存容量,防止内存溢出;配合过期删除与内存淘汰策略,确保内存合理利用,避免服务因内存不足异常。

六、网络架构
通信协议:采用RESP精简序列化协议,协议轻量易解析、支持二进制安全,减少编解码与网络传输开销,提升解析速度。
连接优化:支持非阻塞IO与连接复用,提升客户端连接效率,适配IO多路复用的非阻塞特性;引入零拷贝技术,让客户端缓冲数据直接发送至网络,避免内核态与用户态的数据复制,降低性能损耗。
命令优化:支持Pipeline管道、mset/mget批量命令,减少网络往返次数;支持Lua脚本与Multi/Exec事务,实现命令原子执行,提升并发场景下的执行效率。

Memcached常用操作

*生产环境建议直接用linux

1、命令行直接运行
1.1、可以直接指定参数运行

#最大16M内存,监听11211端口,最大连接数8
memcached.exe -m 16 -p 11211 -c 8

1.2、可以注册为Windows服务,再运行

#注册为服务
memcached.exe -d install
#开启服务
memcached.exe -d start
#关闭服务
memcached.exe -d stop
#卸载服务
memcached.exe -d uninstall

Continue reading Memcached常用操作

Redis分片(Jedis)

Redis的分片技术一般是通过客户端或代理来实现的

1、用jedis实现分片的时候,服务端不需要做任何配置即可

package com.djhu.redis.test;

import java.util.ArrayList;
import java.util.List;

import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;

public class JedisShardTest
{
	public static void main(String[] args)
	{
		List<JedisShardInfo> jedisShardInfoList = new ArrayList<JedisShardInfo>();
		jedisShardInfoList.add(new JedisShardInfo("172.16.172.4", 6379));
		jedisShardInfoList.add(new JedisShardInfo("172.16.172.4", 6380));

		ShardedJedis sharded = new ShardedJedis(jedisShardInfoList);
		sharded.set("key01", "a");
		sharded.set("key02", "b");
		sharded.set("key03", "c");
		sharded.set("key04", "d");
		sharded.set("key05", "e");
		
		System.out.println(sharded.get("key03"));
	}
}

2、用Jedis连接池实现分片

package com.djhu.redis.test;

import java.util.ArrayList;
import java.util.List;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.util.Hashing;
import redis.clients.util.Sharded;

public class JedisSharedFactory
{
	// 最大可用连接数,默认值为8,如果赋值为-1则表示不限制
	private static int MAX_TOTAL = 256;
	// 最大空闲连接数,默认值为8
	private static int MAX_IDLE = 32;
	// 最小空闲连接数
	private static int MIN_IDLE = 4;
	// 最大等待连接毫秒数,默认值为-1表示永不超时
	private static int MAX_WAIT = 3000;
	// 连接redis超时时间
	private static int TIMEOUT = 3000;
	// true表示验证连接
	private static boolean TEST_ON_BORROW = true;

	//连接池
	private static ShardedJedisPool jedisPool = null;
	public static void initJedisPool()
	{
		try
		{
			JedisPoolConfig config = new JedisPoolConfig();
			config.setMaxTotal(MAX_TOTAL);
			config.setMaxIdle(MAX_IDLE);
			config.setMinIdle(MIN_IDLE);
			config.setMaxWaitMillis(MAX_WAIT);
			config.setTestOnBorrow(TEST_ON_BORROW);
			
			List<JedisShardInfo> jedisShardInfoList = new ArrayList<JedisShardInfo>();
			jedisShardInfoList.add(new JedisShardInfo("172.16.172.4", 6379));
			jedisShardInfoList.add(new JedisShardInfo("172.16.172.4", 6380));
			jedisPool = new ShardedJedisPool(config, jedisShardInfoList,Hashing.MURMUR_HASH,Sharded.DEFAULT_KEY_TAG_PATTERN);
		} 
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public synchronized static ShardedJedis getConnection()
	{
		try
		{
			if (jedisPool != null)
			{
				ShardedJedis resource = jedisPool.getResource();
				return resource;
			} else
			{
				return null;
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
			return null;
		}
	}

	public static void returnResource(final ShardedJedis jedis)
	{
		if (jedis != null)
		{
			jedis.close();
		}
	}
	
	public static void main(String[] args)
	{
		initJedisPool();
		ShardedJedis redis = getConnection();
		redis.set("key10", "j");
		redis.set("key11", "k");
		redis.set("key12", "l");
		redis.set("key13", "m");
		redis.set("key14", "n");
		
		System.out.print(redis.get("key12"));
		
		returnResource(redis);
	}
}

Jedis连接Redis3 Cluster

1、源码如下

package com.djhu.redis.test;

import java.util.Set;
import java.util.HashSet;

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;

public class JedisClusterTest
{
	public static void main(String[] args)
	{
		Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();  
        jedisClusterNodes.add(new HostAndPort("172.16.172.4", 6379));  
        jedisClusterNodes.add(new HostAndPort("172.16.172.4", 6380));  
        jedisClusterNodes.add(new HostAndPort("172.16.172.4", 6381));  
        jedisClusterNodes.add(new HostAndPort("172.16.172.4", 6382));  
        jedisClusterNodes.add(new HostAndPort("172.16.172.4", 6383));  
        jedisClusterNodes.add(new HostAndPort("172.16.172.4", 7384));  
        
		//JedisCluster cluster = new JedisCluster(jedisClusterNodes,3000,1000);
        JedisCluster cluster = new JedisCluster(jedisClusterNodes);
		cluster.set("key10", "j");
		cluster.set("key11", "k");
		cluster.set("key12", "l");
		cluster.set("key13", "m");
		cluster.set("key14", "n");
		
		System.out.println(cluster.get("key12"));
		
	}
}

2、如果遇到下面错误,主要是因为建立cluster时,ip用了127.0.0.1。用其他ip重建一下cluster,就可以解决了。

Exception in thread "main" redis.clients.jedis.exceptions.JedisClusterMaxRedirectionsException: Too many Cluster redirections?
	at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:34)
	at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:68)
	at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:85)
	at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:68)
	at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:85)
	at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:68)
	at redis.clients.jedis.JedisClusterCommand.run(JedisClusterCommand.java:29)
	at redis.clients.jedis.JedisCluster.set(JedisCluster.java:75)