1. 筹备阶段:基础数据管理(MySQL基础)

你开了一家社区超市,刚开始只有一台收银机(单机数据库),商品信息、库存、销售记录都存在一张大表里。

1.1 商品表设计:字段类型选择

商品表需要存名称、价格、库存、描述。你发现描述用TEXT,但查询时很少用到,就单独拆成一张附表。这里涉及到行溢出:InnoDB每行最多只能存8KB左右,TEXT字段会存储在溢出页,只保留20字节指针。如果频繁查询商品列表时不查描述,就能避免读取溢出页的IO。

1.2 索引的底层原理

顾客开始搜索商品,比如“找所有保质期大于30天的牛奶”。你给expire_days加了索引。InnoDB的索引是B+树,叶子节点存主键和expire_days。范围查询时,B+树通过双向链表顺序读取,非常快。
深入细节:B+树的阶数(页面大小16KB,每个索引项约16字节,则一个页面可存约1000个键,三层B+树就能存10亿条数据,IO次数极少)。
自适应哈希索引:InnoDB监控到某些索引值被频繁等值查询,会自动在内存中建哈希索引加速,但只适用于等值查询且无法持久化。

1.3 SQL优化与执行计划

某天查询“销量前十的商品”变慢,用EXPLAIN发现filesort。原来ORDER BY sale_count DESC没有索引,MySQL只能先查全表,再排序。你加了联合索引(sale_count, id),让排序直接在索引上完成(索引有序)。
索引下推:MySQL5.6后,如果有WHERE sale_count > 100 AND expire_days > 30,存储引擎会先根据索引过滤sale_count > 100,对通过的每条记录判断expire_days > 30,减少回表次数。

1.4 事务与锁

两人同时结账,同时扣同一商品库存,可能超卖。你用了事务,在扣库存时加行锁。
行锁的实现:InnoDB在聚簇索引的每条记录上加锁,锁信息存在记录头中的heap_no。如果没走索引,会锁全表(其实锁所有聚簇索引记录)。
间隙锁:当用范围条件更新时,比如UPDATE stock SET count = count - 1 WHERE id > 100,InnoDB会给(100, 最大值)这个区间加间隙锁,防止幻读。
死锁:两个事务互相持有对方需要的锁,InnoDB会立即检测死锁(等待图算法),回滚其中一个事务。我们调整了业务逻辑,让所有更新都按相同顺序访问资源,避免死锁。

1.5 事务隔离级别与MVCC

你希望结账时看到的库存是实时的,但不想被未提交的事务影响。默认隔离级别是可重复读,通过MVCC实现:每行记录有两个隐藏列——trx_id(最后修改的事务ID)、roll_pointer(指向undo log)。ReadView判断可见性:如果当前事务ID小于活跃事务列表最小值,则可见;如果大于最大值,不可见;如果在列表中,不可见。
可重复读解决了什么? 同一事务内两次查询结果一致,但可能产生幻读。InnoDB通过next-key lock(行锁+间隙锁)防止幻读。


2. 超市火了:高并发下引入Redis缓存

超市生意火爆,收银台前排长队,数据库CPU飙升。你需要把热点数据放到缓存里。

2.1 商品详情缓存

你把商品信息存到Redis,用SET命令,过期时间随机。
Redis的String底层是SDS,比C字符串安全,有len字段,二进制安全,预分配空间减少内存分配次数。
过期删除策略:Redis每秒10次随机抽取20个带过期时间的key,删除过期key;同时惰性删除:每次访问key时检查是否过期。两种结合,既避免定时扫描阻塞,又避免内存堆积。

2.2 热点商品击穿

秒杀时某商品缓存失效,大量请求同时打到DB。你用互斥锁:当缓存不存在时,尝试SETNX获取锁,成功则查库并回写,失败则sleep后重试。
锁的细节SETNX要结合EXPIRE原子操作(Redis 2.6后可用SET key value NX EX seconds)。但业务执行时间可能超过锁时间,我们用Redisson的看门狗:客户端拿到锁后启动一个定时线程,每10秒检查一次,如果锁还在,就续期30秒(默认)。
Redlock算法:在Redis集群中,为了避免主从切换导致锁丢失,客户端向所有master加锁,超过半数成功才算成功。但实际使用时,由于时钟漂移,Redlock并不完美,我们只在极端重要场景用。

2.3 布隆过滤器防穿透

恶意请求不存在的商品ID,缓存穿透。你用布隆过滤器初始化所有存在的商品ID。
原理:多个哈希函数映射到一个bit数组,判断不存在则一定不存在,存在可能有误判。Redis的Bitmaps操作:SETBITGETBIT。我们使用RedissonRBloomFilter,底层是bitmap,当误判率超过阈值,自动扩容。

2.4 排行榜用Sorted Set

超市要做“热销商品排行榜”,用Redis的Sorted Set,每次销售后ZINCRBY,后台ZREVRANGE取前N名。
底层是跳跃表+哈希表:跳跃表支持范围查询(ZRANGEBYSCORE),哈希表用于快速查权重(ZSCORE)。插入、删除、更新复杂度O(logN)。
内存优化:如果成员数少于128且成员长度小于64,用ziplist编码,紧凑存储节省内存。

2.5 分布式Session

用户登录超市会员系统,以前用Tomcat Session,但集群部署后Session不同步。你用Redis存储Session,Spring Session整合,利用Redis的Hash结构,EXPIRE设置失效时间。
序列化问题:默认JDK序列化性能差,改用Jackson或Kryo。并且要确保对象变更时能反序列化(兼容性设计)。

2.6 计数器和限流

超市门口需要限制进店人数,你用Redis的INCR,并设置过期时间。
原子性INCR是原子操作,底层是单线程处理命令,不会有并发问题。
限流算法:令牌桶用Redis实现:lua脚本批量获取令牌,保证原子性。
Lua脚本优势:Redis执行lua脚本是原子的,中间不会被其他命令插入,适合复杂逻辑。但要注意脚本超时、死循环可能导致阻塞。

2.7 分布式ID生成

订单号需要全局唯一,用Redis的INCR生成,但Redis单机存在上限。我们用雪花算法:64位,1位符号+41位时间戳+10位机器ID+12位序列号。每毫秒可生成4096个ID。
时钟回拨问题:如果机器时间回拨,ID可能重复。我们记录上次生成时间,如果当前时间小于上次时间,等待时间追平或抛出异常。

2.8 持久化

Redis重启后数据不能丢,你开启了RDBAOF
RDBSAVEBGSAVE生成快照,fork子进程,利用写时复制,不阻塞主进程。但可能丢失最后一次快照后的数据。
AOF:记录每一条写命令,用appendfsync策略(always/everysec/no)。always最安全但性能差,everysec最多丢1秒数据。
混合持久化:Redis 4.0后,aof-use-rdb-preamble,AOF重写时先写RDB格式,再追加增量命令,加载更快。

2.9 集群与分片

超市开分店了,单机Redis内存不足,你搭建了Redis Cluster
数据分片:16384个哈希槽,每个节点负责一部分槽。key通过CRC16(key) & 16383计算槽位。客户端直连任意节点,如果槽不在该节点,返回MOVED重定向,客户端缓存槽位信息。
高可用:主从复制,主节点挂了,从节点自动提升。集群中每个主节点可以有多个从节点。
网络抖动:cluster-node-timeout时间内节点无法连通,则标记为疑似下线,如果超过半数主节点认为该节点下线,则真正标记下线,并开始故障转移。
智能客户端:JedisCluster、Lettuce都维护了槽位映射表,减少重定向。


3. 超市连锁:微服务化与中间件

超市开成连锁店,业务拆分:商品中心、订单中心、库存中心、会员中心。各系统独立部署,引入Java中间件。

3.1 服务发现与配置中心(Nacos)

每个服务实例地址动态变化,你引入Nacos
服务注册:服务启动时向Nacos注册IP、端口、健康检查URL。Nacos定期主动探测或接收心跳,如果实例失联,临时实例直接剔除,持久实例标记为不健康但不删除。
服务发现:客户端拉取服务列表,并监听变更。Nacos通过UDP或长轮询推送变更。
CP还是AP? Nacos支持切换:临时实例用AP(Distro协议),非持久实例用CP(Raft协议)。我们通常用AP,保证可用性。
配置中心:Nacos也做配置管理,客户端长轮询检查配置变更,变更后实时推送。配置存储使用Derby或MySQL,支持版本回滚、灰度发布。

3.2 远程调用(Dubbo/OpenFeign)

服务间需要调用,你用Dubbo,基于Netty的RPC框架。
SPI扩展:Dubbo的扩展点加载机制,可以替换协议、序列化、负载均衡。比如默认负载均衡是随机,我们改用一致性哈希,让相同参数的请求落到同一节点,利用缓存。
服务治理:集群容错(Failover、Failfast、Failsafe)、服务降级(mock)、路由(tag路由、条件路由)。
序列化:我们改用Kryo,比Hessian2更快,体积更小。
Netty通信:Dubbo基于Netty异步非阻塞,减少线程开销。连接复用,但需要处理粘包半包(Dubbo自定义协议:header+body)。

3.3 网关(Spring Cloud Gateway)

外部请求统一从网关进入,你选了Spring Cloud Gateway,基于WebFlux。
路由:根据路径、header、host等断言匹配路由。
过滤器:实现鉴权、日志、限流。限流用Redis的令牌桶,集成RequestRateLimiter
跨域:通过CORS配置。
高可用:网关集群部署,前端用Nginx负载均衡。网关层也做熔断,集成Sentinel。

3.4 消息队列(RocketMQ)

订单创建后需要通知库存减扣、积分增加、短信发送,同步调用太慢,你引入RocketMQ

架构:NameServer(无状态,路由注册)、Broker(主从,存储消息)、Producer、Consumer。
消息存储:CommitLog顺序写,每个消息写入后,生成ConsumeQueue(逻辑队列),方便消费者拉取。零拷贝(mmap)加速读写。
刷盘机制:同步刷盘(消息写入物理磁盘后才返回成功) vs 异步刷盘(写入PageCache即返回)。我们为了性能用异步刷盘+主从同步复制。
消息可靠性:主从复制,同步复制保证消息不丢(slave确认后才返回),但性能差;异步复制可能丢少量消息,但可用性高。
顺序消息:将同一订单的所有消息发送到同一队列,消费者单线程消费该队列。RocketMQ通过队列锁保证顺序,但故障转移时可能乱序。
事务消息:预扣库存和发送消息需原子。流程:半消息发送,执行本地事务,提交/回滚。如果半消息长时间未提交,MQ回查业务方确认状态。
消息重试与死信:消费失败会重试16次,间隔递增。超过重试次数进入死信队列,人工干预。
消息幂等:消费者通过业务主键(订单号)去重,用Redis的SETNX或数据库唯一键。
消息过滤:TAG或SQL92表达式,Broker端过滤,减少网络传输。
消息轨迹:开启轨迹记录,追踪消息生命周期。

3.5 分布式事务(Seata)

订单服务调用库存服务、积分服务,需要分布式事务。你用Seata AT模式
AT原理:两阶段提交,第一阶段各个分支事务执行SQL并记录undo_log,返回成功;第二阶段全局提交时异步删除undo_log,全局回滚时根据undo_log回滚。
全局锁:TC(Transaction Coordinator)协调,防止脏写。性能较好,但对SQL有要求(必须支持回滚)。
TCC模式:Try、Confirm、Cancel,业务侵入大,适合性能要求高的场景。
Saga模式:长事务,补偿机制,适合业务流程复杂的场景。

3.6 分布式调度(XXL-JOB)

每天凌晨需要统计销售报表,你引入XXL-JOB
架构:调度中心(集群)、执行器。调度中心触发任务,执行器执行。
任务分片:统计任务可拆分成多个分片,每个分片处理一部分数据,利用Redis分布式锁协调。
故障转移:调度中心发现执行器失效,自动转移到其他执行器。
动态任务:支持API动态创建任务,我们用于临时数据修复。

3.7 限流与熔断(Sentinel)

双十一大促,为防止系统崩溃,你配置Sentinel
滑动窗口:统计QPS、RT、异常比例。滑动窗口将时间划分为多个格子,每个格子独立计数,比固定窗口更平滑。
流量控制:直接拒绝、Warm Up、排队等待。Warm Up让流量缓慢增加,避免冷启动。
熔断降级:异常比例、慢调用比例超过阈值,熔断器打开,快速失败。熔断器状态机:关闭->打开->半开,半开后尝试放行少量请求。
热点参数限流:针对商品ID限流,比如秒杀商品只允许每秒100次请求。
规则持久化:推送到Nacos,动态生效。

3.8 链路追踪(SkyWalking)

微服务调用链复杂,你部署SkyWalking,基于字节码注入。
数据采集:探针收集Trace、Span信息,包括调用时长、异常栈。
采样:默认每秒最多10条,避免性能损耗。
存储:用Elasticsearch存储追踪数据。
拓扑图:自动生成服务依赖图。
告警:根据指标触发告警。

3.9 容器化与编排(K8s)

为便于部署,你将服务打包成Docker镜像,用K8s编排。
Pod:每个服务运行在Pod,可水平伸缩。
ConfigMap/Secret:配置挂载。
Service:服务发现,集群内部通过DNS访问。
Ingress:外部访问入口,类似网关。
HPA:根据CPU或自定义指标自动扩缩容。
Helm:打包部署。
Service Mesh:后期尝试Istio,实现流量治理、安全、可观测性。


4. 持续优化:深入底层细节

4.1 MySQL深度优化

  • InnoDB缓冲池innodb_buffer_pool_size设为内存的70%。监控命中率,如果低于95%,考虑增加内存。
  • 自适应哈希索引:监控Innodb_adaptive_hash_searches,如果很高,说明效果不错。
  • redo loginnodb_log_file_size设大,减少checkpoint频率。但恢复时间长。
  • binlog格式:用ROW格式,便于主从同步和数据恢复。
  • 主从延迟:并行复制,slave-parallel-workers=4。监控Seconds_Behind_Master
  • 分库分表中间件:ShardingSphere的SQL解析优化,支持分页查询优化(先查分片键再查数据)。

4.2 Redis深度优化

  • 内存碎片info memory查看mem_fragmentation_ratio,大于1.5时重启或CONFIG SET activedefrag yes
  • bigkey:用redis-cli --bigkeys扫描,大key可能导致阻塞。拆分成多个小key或用哈希结构。
  • 热key:本地缓存(Caffeine)+ Redis二级缓存,或者热key备份到多个节点,读写分散。
  • pipeline:批量操作减少RTT,但注意命令过多会阻塞网络。
  • Redis 6.0多线程:IO多线程处理网络读写,但命令执行仍是单线程,避免并发问题。

4.3 JVM调优

  • 堆内存:根据服务特点调整新生代、老年代比例,GC日志分析。
  • CMS/G1:低延迟服务用G1,设置最大停顿时间。
  • 类加载:避免重复加载,使用-XX:+TraceClassLoading排查。
  • OOMHeapDumpOnOutOfMemoryError生成dump,用MAT分析。

4.4 网络与IO

  • Netty:直接内存池,减少GC压力;零拷贝(FileRegion)传输文件。
  • Nginx:调优worker_processes、keepalive、缓冲区。
  • TCP参数:tcp_nodelay、tcp_tw_reuse等。

5. 踩坑与复盘

  • MySQL死锁:一次批量更新导致死锁,因为不同线程更新顺序不一致。强制排序解决。
  • Redis缓存雪崩:大量key同时过期,导致DB被打爆。过期时间加随机偏移。
  • 消息堆积:消费者挂了,消息堆积到几十亿,磁盘快满。紧急扩容消费者,并跳过非关键消息。
  • 分布式事务回滚失败:AT模式中undo_log被删,无法回滚。启用全局锁后,必须保证本地事务幂等。
  • 网关限流误杀:Sentinel配置错误导致正常请求被限。灰度发布,逐步调参。
  • K8s OOMKilled:JVM未感知容器内存限制,加-XX:+UseCGroupMemoryLimitForHeap