阶段六:中间件与运维(2-3周)
作为资深前端开发者,你已经具备了工程化部署和性能优化的经验。本阶段将学习Java后端生态中的核心中间件(Redis、Elasticsearch)和运维工具(Docker、K8s、CI/CD、监控),帮助你构建高可用、高性能的后端系统。
6.1 缓存(第1-5天)
6.1.1 Redis概述
Redis(Remote Dictionary Server)是一个基于内存的高性能键值存储数据库,广泛用于缓存、消息队列、分布式锁等场景。
与前端概念对比:
| 前端概念 | Redis |
|---|---|
| localStorage / sessionStorage | Redis缓存 |
| IndexedDB | Redis持久化存储 |
| Vuex / Pinia 状态管理 | Redis集中式缓存 |
| Service Worker缓存 | Redis CDN缓存 |
| WebSocket实时通信 | Redis Pub/Sub消息 |
6.1.2 Redis安装与基本使用
Windows安装
- 下载:Redis Windows版
- 解压到指定目录,如
D:\redis - 启动服务:
redis-server.exe redis.confDocker安装(推荐)
docker run -d --name redis -p 6379:6379 redis:7.0GUI工具
- RedisInsight(官方免费,推荐)
- Another Redis Desktop Manager(免费开源)
- Redis Manager(IDEA插件)
Redis命令行基本操作
# 连接Redis
redis-cli
# 字符串操作
SET name "张三"
GET name
SET counter 100
INCR counter # 自增,counter变为101
DECR counter # 自减,counter变为100
INCRBY counter 10 # 增加10
# 设置过期时间
SET token "abc123" EX 3600 # 3600秒后过期
SET code "1234" PX 60000 # 60000毫秒后过期
EXPISE name 300 # 给已有key设置过期时间
TTL name # 查看剩余生存时间(秒)
# Hash操作(类似JS对象)
HSET user:1 name "张三"
HSET user:1 age 25
HSET user:1 email "zhangsan@example.com"
HGET user:1 name # 获取单个字段
HGETALL user:1 # 获取所有字段
HMSET user:1 name "张三" age 25 email "zhangsan@example.com" # 批量设置
# List操作(类似JS数组)
LPUSH mylist "A" "B" "C" # 从左边插入
RPUSH mylist "D" "E" # 从右边插入
LPOP mylist # 从左边弹出
RPOP mylist # 从右边弹出
LRANGE mylist 0 -1 # 获取所有元素
LLEN mylist # 获取长度
# Set操作(类似JS Set)
SADD myset "A" "B" "C"
SADD myset "C" "D" # C重复,不会添加
SMEMBERS myset # 获取所有成员
SISMEMBER myset "A" # 是否包含
SCARD myset # 获取元素个数
# Sorted Set操作(带分数的Set)
ZADD leaderboard 100 "张三"
ZADD leaderboard 200 "李四"
ZADD leaderboard 150 "王五"
ZRANGE leaderboard 0 -1 WITHSCORES # 按分数升序
ZREVRANGE leaderboard 0 -1 WITHSCORES # 按分数降序
ZINCRBY leaderboard 50 "张三" # 增加分数
# 通用命令
KEYS * # 查看所有key(生产慎用)
SCAN 0 MATCH user:* COUNT 100 # 分批获取key
DEL name # 删除key
EXISTS name # 判断key是否存在
TYPE name # 查看key的类型
FLUSHDB # 清空当前数据库6.1.3 Redis数据结构与使用场景
五种基本数据类型
| 数据类型 | 底层结构 | 典型场景 | 前端类比 |
|---|---|---|---|
| String | SDS(简单动态字符串) | 缓存、计数器、分布式锁 | localStorage |
| Hash | 字典(HashTable) | 对象存储、购物车 | JS Object |
| List | 双向链表 + 压缩列表 | 消息队列、最新列表 | Array |
| Set | 整数集合 + 字典 | 标签、共同关注、去重 | JS Set |
| ZSet | 跳表 + 字典 | 排行榜、延迟队列 | 无直接对应 |
特殊数据类型
- HyperLogLog:基数统计(UV统计)
- Bitmap:位图(签到、在线状态)
- Stream:消息队列(类似Kafka)
- GEO:地理位置(附近的人)
6.1.4 Spring Boot集成Redis
Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>application.yml配置
spring:
redis:
host: localhost
port: 6379
password: # 密码,没有则留空
database: 0 # 数据库索引(0-15)
timeout: 5000ms # 连接超时时间
lettuce:
pool:
max-active: 20 # 最大连接数
max-idle: 10 # 最大空闲连接
min-idle: 5 # 最小空闲连接
max-wait: 3000ms # 最大等待时间RedisTemplate配置类
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 使用Jackson2JsonRedisSerializer作为值的序列化器
Jackson2JsonRedisSerializer<Object> jsonSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(
mapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL
);
jsonSerializer.setObjectMapper(mapper);
// 使用StringRedisSerializer作为key的序列化器
StringRedisSerializer stringSerializer = new StringRedisSerializer();
// key使用String序列化
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
// value使用JSON序列化
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
template.afterPropertiesSet();
return template;
}
}RedisTemplate操作示例
@Service
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// ==================== String操作 ====================
public void setString(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
public void setStringWithExpire(String key, Object value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
public Object getString(String key) {
return redisTemplate.opsForValue().get(key);
}
public Boolean delete(String key) {
return redisTemplate.delete(key);
}
public Long increment(String key) {
return redisTemplate.opsForValue().increment(key);
}
public Long incrementBy(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
// ==================== Hash操作 ====================
public void setHash(String key, String hashKey, Object value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}
public void setHashAll(String key, Map<String, Object> map) {
redisTemplate.opsForHash().putAll(key, map);
}
public Object getHash(String key, String hashKey) {
return redisTemplate.opsForHash().get(key, hashKey);
}
public Map<Object, Object> getHashAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
public Long deleteHash(String key, String... hashKeys) {
return redisTemplate.opsForHash().delete(key, (Object[]) hashKeys);
}
// ==================== List操作 ====================
public void pushLeft(String key, Object value) {
redisTemplate.opsForList().leftPush(key, value);
}
public void pushRight(String key, Object value) {
redisTemplate.opsForList().rightPush(key, value);
}
public Object popLeft(String key) {
return redisTemplate.opsForList().leftPop(key);
}
public Object popRight(String key) {
return redisTemplate.opsForList().rightPop(key);
}
public List<Object> range(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
public Long listSize(String key) {
return redisTemplate.opsForList().size(key);
}
// ==================== Set操作 ====================
public void addToSet(String key, Object... values) {
redisTemplate.opsForSet().add(key, values);
}
public Set<Object> getSetMembers(String key) {
return redisTemplate.opsForSet().members(key);
}
public Boolean isSetMember(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
public Long setSize(String key) {
return redisTemplate.opsForSet().size(key);
}
// ==================== ZSet操作 ====================
public void addToZSet(String key, Object value, double score) {
redisTemplate.opsForZSet().add(key, value, score);
}
public Set<Object> zSetRange(String key, long start, long end) {
return redisTemplate.opsForZSet().range(key, start, end);
}
public Set<Object> zSetReverseRange(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRange(key, start, end);
}
public Double zSetScore(String key, Object value) {
return redisTemplate.opsForZSet().score(key, value);
}
public Long zSetRank(String key, Object value) {
return redisTemplate.opsForZSet().rank(key, value);
}
// ==================== 通用操作 ====================
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
public Boolean expire(String key, long timeout, TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
public Long getExpire(String key) {
return redisTemplate.getExpire(key);
}
public Set<String> keys(String pattern) {
return redisTemplate.keys(pattern);
}
}实际业务示例:用户缓存
@Service
public class UserCacheService {
@Autowired
private UserMapper userMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String USER_KEY_PREFIX = "user:";
private static final long USER_CACHE_TTL = 30; // 分钟
/**
* 查询用户(先查缓存,缓存没有再查数据库)
*/
public User getUserById(Long id) {
String key = USER_KEY_PREFIX + id;
// 1. 先查缓存
User user = (User) redisTemplate.opsForValue().get(key);
if (user != null) {
return user;
}
// 2. 缓存没有,查数据库
user = userMapper.selectById(id);
if (user != null) {
// 3. 写入缓存,设置过期时间
redisTemplate.opsForValue().set(key, user, USER_CACHE_TTL, TimeUnit.MINUTES);
}
return user;
}
/**
* 更新用户(同时更新缓存)
*/
public void updateUser(User user) {
String key = USER_KEY_PREFIX + user.getId();
userMapper.updateById(user);
// 更新缓存
redisTemplate.opsForValue().set(key, user, USER_CACHE_TTL, TimeUnit.MINUTES);
}
/**
* 删除用户(同时删除缓存)
*/
public void deleteUser(Long id) {
String key = USER_KEY_PREFIX + id;
userMapper.deleteById(id);
redisTemplate.delete(key);
}
}使用@Cacheable注解简化缓存
// 开启缓存支持
@SpringBootApplication
@EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
// 查询时自动缓存
@Cacheable(value = "product", key = "#id")
public Product getProductById(Long id) {
return productMapper.selectById(id);
}
// 更新时自动清除缓存
@CacheEvict(value = "product", key = "#product.id")
public void updateProduct(Product product) {
productMapper.updateById(product);
}
// 更新时自动更新缓存
@CachePut(value = "product", key = "#product.id")
public Product updateProductAndReturn(Product product) {
productMapper.updateById(product);
return product;
}
// 清除所有缓存
@CacheEvict(value = "product", allEntries = true)
public void clearAllProductCache() {
// 清除所有product缓存
}
}6.1.5 缓存穿透、击穿、雪崩
缓存穿透(Cache Penetration)
问题描述: 查询一个不存在的数据,缓存中没有,每次请求都会打到数据库。就像子弹穿透了缓存一样。
前端类比: 就像前端请求一个不存在的API,缓存和CDN都没有命中,每次都打到后端。
解决方案:
@Service
public class CachePenetrationSolution {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private UserMapper userMapper;
/**
* 方案1:缓存空值(简单但不够优雅)
*/
public User getUserById_CacheNull(Long id) {
String key = "user:" + id;
Object cached = redisTemplate.opsForValue().get(key);
// 如果缓存的是空值标记,直接返回null
if ("NULL".equals(cached)) {
return null;
}
if (cached != null) {
return (User) cached;
}
User user = userMapper.selectById(id);
if (user == null) {
// 缓存空值,设置较短的过期时间
redisTemplate.opsForValue().set(key, "NULL", 5, TimeUnit.MINUTES);
return null;
}
redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES);
return user;
}
/**
* 方案2:布隆过滤器(推荐,高效)
* 在缓存之前加一层布隆过滤器,快速判断数据是否存在
*/
@Component
public class BloomFilterManager {
// 使用Redis的Bitmap实现布隆过滤器
// 或者使用Google Guava的BloomFilter
private BloomFilter<Long> bloomFilter;
@PostConstruct
public void init() {
// 创建布隆过滤器
// 预期元素数量10000,误判率0.01
bloomFilter = BloomFilter.create(
Funnels.longFunnel(), 10000, 0.01);
// 加载所有用户ID到布隆过滤器
loadUserIds();
}
private void loadUserIds() {
// 从数据库加载所有用户ID
// bloomFilter.put(userId);
}
public boolean mightContain(Long id) {
return bloomFilter.mightContain(id);
}
}
public User getUserById_BloomFilter(Long id) {
// 1. 先查布隆过滤器
if (!bloomFilterManager.mightContain(id)) {
return null; // 一定不存在
}
// 2. 布隆过滤器说可能存在,再查缓存
String key = "user:" + id;
User user = (User) redisTemplate.opsForValue().get(key);
if (user != null) {
return user;
}
// 3. 查数据库
user = userMapper.selectById(id);
if (user != null) {
redisTemplate.opsForValue().set(key, user, 30, TimeUnit.MINUTES);
}
return user;
}
}缓存击穿(Cache Breakdown)
问题描述: 一个热点key在过期的瞬间,大量并发请求同时打到数据库。
前端类比: 就像前端某个热门数据缓存失效,瞬间大量请求同时发起,导致后端压力骤增。
解决方案:
@Service
public class CacheBreakdownSolution {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ProductMapper productMapper;
/**
* 方案1:互斥锁(分布式锁)
* 只允许一个线程查询数据库,其他线程等待
*/
public Product getProductById_Mutex(Long id) {
String key = "product:" + id;
Product product = (Product) redisTemplate.opsForValue().get(key);
if (product != null) {
return product;
}
// 尝试获取分布式锁
String lockKey = "lock:product:" + id;
String lockValue = UUID.randomUUID().toString();
try {
// 加锁,设置过期时间防止死锁
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(locked)) {
// 获取锁成功,查询数据库
product = productMapper.selectById(id);
if (product != null) {
redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
}
} else {
// 获取锁失败,休眠后重试
Thread.sleep(50);
return getProductById_Mutex(id); // 递归重试
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 释放锁(只释放自己的锁)
Object currentLockValue = redisTemplate.opsForValue().get(lockKey);
if (lockValue.equals(currentLockValue)) {
redisTemplate.delete(lockKey);
}
}
return product;
}
/**
* 方案2:逻辑过期(推荐)
* 不设置Redis过期时间,在value中存储逻辑过期时间
*/
public Product getProductById_LogicalExpire(Long id) {
String key = "product:" + id;
Map<String, Object> cached = (Map<String, Object>) redisTemplate.opsForValue().get(key);
if (cached == null) {
// 缓存不存在,直接查数据库并重建缓存
return rebuildCache(id);
}
Product product = (Product) cached.get("product");
Long expireTime = (Long) cached.get("expireTime");
if (System.currentTimeMillis() < expireTime) {
// 未过期,直接返回
return product;
}
// 已过期,开启异步线程重建缓存
String lockKey = "lock:product:" + id;
String lockValue = UUID.randomUUID().toString();
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(locked)) {
// 获取锁成功,异步重建缓存
CompletableFuture.runAsync(() -> {
try {
rebuildCache(id);
} finally {
redisTemplate.delete(lockKey);
}
});
}
// 返回旧数据(保证可用性)
return product;
}
private Product rebuildCache(Long id) {
Product product = productMapper.selectById(id);
if (product != null) {
String key = "product:" + id;
Map<String, Object> cacheMap = new HashMap<>();
cacheMap.put("product", product);
cacheMap.put("expireTime", System.currentTimeMillis() + 30 * 60 * 1000L);
redisTemplate.opsForValue().set(key, cacheMap);
}
return product;
}
}缓存雪崩(Cache Avalanche)
问题描述: 大量缓存key在同一时间过期,导致大量请求同时打到数据库。
前端类比: 就像前端多个页面的缓存同时失效,瞬间所有请求都打到后端。
解决方案:
@Service
public class CacheAvalancheSolution {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 方案1:过期时间加随机值
* 避免大量key同时过期
*/
public void setWithRandomExpire(String key, Object value) {
// 基础过期时间30分钟 + 随机0-5分钟
long baseTime = 30;
long randomTime = ThreadLocalRandom.current().nextLong(0, 5);
redisTemplate.opsForValue().set(key, value, baseTime + randomTime, TimeUnit.MINUTES);
}
/**
* 方案2:多级缓存
* 本地缓存 + Redis缓存 + 数据库
*/
@Component
public class MultiLevelCache {
// 本地缓存(Caffeine)
private final Cache<String, Object> localCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public Object get(String key) {
// 1. 查本地缓存
Object value = localCache.getIfPresent(key);
if (value != null) {
return value;
}
// 2. 查Redis缓存
value = redisTemplate.opsForValue().get(key);
if (value != null) {
localCache.put(key, value); // 回填本地缓存
return value;
}
// 3. 查数据库(由调用方处理)
return null;
}
public void put(String key, Object value) {
localCache.put(key, value);
redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
}
}
/**
* 方案3:服务降级与限流
*/
@Component
public class CacheFallbackHandler {
// 当缓存和数据库都不可用时,返回默认值
public <T> T getOrDefault(String key, T defaultValue, Supplier<T> dbQuery) {
try {
T result = dbQuery.get();
return result != null ? result : defaultValue;
} catch (Exception e) {
// 数据库查询失败,返回默认值
return defaultValue;
}
}
}
}6.1.6 Redis集群与高可用
Redis主从复制
Master(主节点)
├── Slave1(从节点1,只读)
├── Slave2(从节点2,只读)
└── Slave3(从节点3,只读)配置主从复制:
# 从节点配置(redis-slave.conf)
replicaof 192.168.1.100 6379 # 主节点IP和端口
masterauth your_master_password # 主节点密码Redis哨兵模式(Sentinel)
Sentinel集群(3个哨兵)
├── Sentinel-1(监控Master和Slave)
├── Sentinel-2
└── Sentinel-3
│
├── Master(主节点)
├── Slave1(从节点1)
└── Slave2(从节点2)哨兵配置(sentinel.conf):
# 监控主节点,名称mymaster,最少2个哨兵同意
sentinel monitor mymaster 192.168.1.100 6379 2
# 主节点下线判定时间(毫秒)
sentinel down-after-milliseconds mymaster 5000
# 故障转移超时时间
sentinel failover-timeout mymaster 60000
# 同步时从节点数量
sentinel parallel-syncs mymaster 1Spring Boot配置哨兵:
spring:
redis:
sentinel:
master: mymaster
nodes: 192.168.1.100:26379,192.168.1.101:26379,192.168.1.102:26379
password: your_passwordRedis Cluster集群
Redis Cluster(6节点,3主3从)
├── Master-1(端口7001)── Slave-1(端口7004)
├── Master-2(端口7002)── Slave-2(端口7005)
└── Master-3(端口7003)── Slave-3(端口7006)
数据分片:16384个slot
Master-1: 0-5460
Master-2: 5461-10922
Master-3: 10923-16383Cluster配置(redis-cluster.conf):
port 7001
cluster-enabled yes
cluster-config-file nodes-7001.conf
cluster-node-timeout 5000
appendonly yesSpring Boot配置Cluster:
spring:
redis:
cluster:
nodes: 192.168.1.100:7001,192.168.1.100:7002,192.168.1.100:7003,
192.168.1.100:7004,192.168.1.100:7005,192.168.1.100:7006
max-redirects: 3
password: your_password6.1.7 分布式锁(Redisson)
为什么需要分布式锁
前端类比: 就像前端使用乐观锁(版本号)来处理并发编辑冲突,后端在分布式环境下也需要分布式锁来保证数据一致性。
Redisson简介
Redisson是一个基于Redis的Java驻内存数据网格(In-Memory Data Grid),提供了分布式锁、分布式集合、分布式对象等功能。
Maven依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.3</version>
</dependency>配置
spring:
redis:
redisson:
config: |
singleServerConfig:
address: "redis://127.0.0.1:6379"
password: null
database: 0
connectionPoolSize: 64
connectionMinimumIdleSize: 10分布式锁使用示例
@Service
public class DistributedLockService {
@Autowired
private RedissonClient redissonClient;
/**
* 基本分布式锁
*/
public void basicLock() {
RLock lock = redissonClient.getLock("myLock");
try {
// 加锁(阻塞等待)
lock.lock();
// 执行业务逻辑
doBusiness();
} finally {
// 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 带超时的分布式锁
*/
public void lockWithTimeout() {
RLock lock = redissonClient.getLock("myLock");
try {
// 尝试加锁,等待3秒,锁定后10秒自动释放
boolean acquired = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (acquired) {
doBusiness();
} else {
// 获取锁失败
System.out.println("获取锁失败");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
/**
* 公平锁(按请求顺序获取锁)
*/
public void fairLock() {
RLock fairLock = redissonClient.getFairLock("myFairLock");
try {
fairLock.lock();
doBusiness();
} finally {
if (fairLock.isHeldByCurrentThread()) {
fairLock.unlock();
}
}
}
/**
* 读写锁(读读不互斥,读写互斥)
*/
public void readWriteLock() {
RReadWriteLock rwLock = redissonClient.getReadWriteLock("myRWLock");
// 读锁
RLock readLock = rwLock.readLock();
readLock.lock();
try {
// 读操作
readData();
} finally {
readLock.unlock();
}
// 写锁
RLock writeLock = rwLock.writeLock();
writeLock.lock();
try {
// 写操作
writeData();
} finally {
writeLock.unlock();
}
}
/**
* 联锁(同时锁定多个锁)
*/
public void multiLock() {
RLock lock1 = redissonClient.getLock("lock1");
RLock lock2 = redissonClient.getLock("lock2");
RLock lock3 = redissonClient.getLock("lock3");
RLock multiLock = redissonClient.getMultiLock(lock1, lock2, lock3);
try {
multiLock.lock();
doBusiness();
} finally {
multiLock.unlock();
}
}
/**
* 红锁(RedLock,跨多个Redis实例)
*/
public void redLock() {
RLock lock1 = redissonClient.getLock("lock1");
RLock lock2 = redissonClient.getLock("lock2");
RLock lock3 = redissonClient.getLock("lock3");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {
// 只要有一个实例加锁成功即可
redLock.lock();
doBusiness();
} finally {
redLock.unlock();
}
}
/**
* 实际业务场景:库存扣减
*/
public boolean deductStock(Long productId, int quantity) {
String lockKey = "stock:lock:" + productId;
RLock lock = redissonClient.getLock(lockKey);
try {
boolean acquired = lock.tryLock(3, TimeUnit.SECONDS);
if (!acquired) {
return false;
}
// 查询库存
String stockKey = "stock:" + productId;
int stock = (int) redissonClient.getBucket(stockKey).get();
if (stock >= quantity) {
// 扣减库存
redissonClient.getBucket(stockKey).set(stock - quantity);
return true;
}
return false;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
private void doBusiness() {
// 业务逻辑
}
private void readData() {
// 读操作
}
private void writeData() {
// 写操作
}
}使用注解实现分布式锁
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
String key();
long waitTime() default 3;
long leaseTime() default 10;
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
// AOP切面
@Aspect
@Component
public class DistributedLockAspect {
@Autowired
private RedissonClient redissonClient;
@Around("@annotation(distributedLock)")
public Object around(ProceedingJoinPoint joinPoint,
DistributedLock distributedLock) throws Throwable {
String lockKey = "lock:" + distributedLock.key();
RLock lock = redissonClient.getLock(lockKey);
try {
boolean acquired = lock.tryLock(
distributedLock.waitTime(),
distributedLock.leaseTime(),
distributedLock.timeUnit()
);
if (!acquired) {
throw new RuntimeException("获取分布式锁失败: " + lockKey);
}
return joinPoint.proceed();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
// 使用注解
@Service
public class OrderService {
@DistributedLock(key = "'order:' + #orderId", waitTime = 5)
public void processOrder(Long orderId) {
// 处理订单
}
}6.2 搜索引擎(第6-9天)
6.2.1 Elasticsearch概述
Elasticsearch(ES)是一个基于Lucene的分布式全文搜索引擎,提供近实时的搜索和分析功能。
与前端概念对比:
| 前端概念 | Elasticsearch |
|---|---|
| 浏览器Ctrl+F搜索 | ES全文检索 |
| 前端本地搜索(如表格筛选) | ES服务端搜索 |
| 前端分页 | ES分页查询 |
| 前端排序 | ES排序与聚合 |
| 前端模糊匹配 | ES模糊查询 |
Elasticsearch vs MySQL:
| 对比项 | MySQL | Elasticsearch |
|---|---|---|
| 数据存储 | 行存储 | 倒排索引 |
| 查询方式 | SQL精确查询 | 全文检索 + 精确查询 |
| 事务支持 | 支持ACID | 不支持事务 |
| 实时性 | 实时写入 | 近实时(1秒延迟) |
| 适用场景 | 核心业务数据 | 搜索、日志分析 |
| 数据一致性 | 强一致 | 最终一致 |
6.2.2 倒排索引原理
正排索引(Forward Index)
文档ID → 文档内容
doc1 → "Java编程入门"
doc2 → "Python数据分析"
doc3 → "Java与Python对比"倒排索引(Inverted Index)
词条 → 包含该词条的文档ID列表
Java → [doc1, doc3]
Python → [doc2, doc3]
编程 → [doc1]
入门 → [doc1]
数据分析 → [doc2]
对比 → [doc3]倒排索引结构:
Term(词条)
├── Term Dictionary(词条字典,有序)
├── Term Index(词条索引,FST结构,加速查找)
└── Posting List(倒排列表,文档ID + 词频 + 位置)6.2.3 Elasticsearch安装与基本操作
Docker安装
# 单节点安装
docker run -d --name elasticsearch \
-p 9200:9200 \
-p 9300:9300 \
-e "discovery.type=single-node" \
-e "xpack.security.enabled=false" \
elasticsearch:8.9.0
# 安装Kibana(可视化管理)
docker run -d --name kibana \
-p 5601:5601 \
-e "ELASTICSEARCH_HOSTS=http://elasticsearch:9200" \
--link elasticsearch \
kibana:8.9.0基本REST API
# 查看集群健康状态
GET /_cluster/health
# 查看集群节点
GET /_cat/nodes?v
# 查看索引列表
GET /_cat/indices?v
# 创建索引
PUT /products
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"properties": {
"id": { "type": "long" },
"name": { "type": "text", "analyzer": "ik_max_word" },
"description": { "type": "text", "analyzer": "ik_smart" },
"price": { "type": "double" },
"category": { "type": "keyword" },
"brand": { "type": "keyword" },
"status": { "type": "integer" },
"createdAt": { "type": "date" }
}
}
}
# 添加文档
POST /products/_doc/1
{
"name": "iPhone 15 Pro Max",
"description": "苹果公司最新旗舰手机,搭载A17 Pro芯片",
"price": 9999.00,
"category": "手机",
"brand": "Apple",
"status": 1,
"createdAt": "2024-01-01T00:00:00"
}
# 批量添加文档
POST /products/_bulk
{"index": {"_id": "2"}}
{"name": "华为Mate 60 Pro", "description": "华为最新旗舰,搭载麒麟9000S芯片", "price": 6999.00, "category": "手机", "brand": "华为", "status": 1, "createdAt": "2024-01-02T00:00:00"}
{"index": {"_id": "3"}}
{"name": "小米14 Ultra", "description": "小米旗舰手机,徕卡影像系统", "price": 5999.00, "category": "手机", "brand": "小米", "status": 1, "createdAt": "2024-01-03T00:00:00"}
# 查询文档
GET /products/_doc/1
# 搜索文档
GET /products/_search
{
"query": {
"match": {
"name": "手机"
}
}
}
# 更新文档
PUT /products/_doc/1
{
"name": "iPhone 15 Pro Max 256GB",
"price": 10999.00
}
# 局部更新
POST /products/_update/1
{
"doc": {
"price": 9499.00
}
}
# 删除文档
DELETE /products/_doc/1
# 删除索引
DELETE /products6.2.4 查询DSL详解
全文检索查询
# match查询(分词后匹配)
GET /products/_search
{
"query": {
"match": {
"name": "苹果 手机"
}
}
}
# match_phrase查询(短语匹配,要求顺序和位置)
GET /products/_search
{
"query": {
"match_phrase": {
"description": "旗舰手机"
}
}
}
# multi_match查询(多字段搜索)
GET /products/_search
{
"query": {
"multi_match": {
"query": "华为",
"fields": ["name^3", "description^2", "brand"]
}
}
}精确查询
# term查询(不分词,精确匹配)
GET /products/_search
{
"query": {
"term": {
"category": "手机"
}
}
}
# terms查询(多值匹配)
GET /products/_search
{
"query": {
"terms": {
"brand": ["Apple", "华为", "小米"]
}
}
}
# range查询(范围查询)
GET /products/_search
{
"query": {
"range": {
"price": {
"gte": 5000,
"lte": 10000
}
}
}
}
# prefix查询(前缀查询)
GET /products/_search
{
"query": {
"prefix": {
"name": "华为"
}
}
}
# wildcard查询(通配符查询)
GET /products/_search
{
"query": {
"wildcard": {
"name": "华*"
}
}
}复合查询
# bool查询(组合查询)
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "手机" } }
],
"filter": [
{ "term": { "status": 1 } },
{ "range": { "price": { "gte": 5000, "lte": 10000 } } }
],
"should": [
{ "match": { "brand": "华为" } }
],
"must_not": [
{ "term": { "category": "配件" } }
]
}
}
}
# function_score查询(自定义评分)
GET /products/_search
{
"query": {
"function_score": {
"query": { "match": { "name": "手机" } },
"functions": [
{
"field_value_factor": {
"field": "price",
"modifier": "log1p",
"factor": 0.001
}
},
{
"gauss": {
"createdAt": {
"origin": "now",
"scale": "30d",
"offset": "7d"
}
}
}
],
"boost_mode": "multiply"
}
}
}高亮显示
GET /products/_search
{
"query": {
"match": { "name": "手机" }
},
"highlight": {
"fields": {
"name": {},
"description": {}
},
"pre_tags": ["<em>"],
"post_tags": ["</em>"]
}
}6.2.5 聚合分析
# 聚合查询(类似SQL的GROUP BY)
GET /products/_search
{
"size": 0,
"aggs": {
"category_agg": {
"terms": {
"field": "category",
"size": 10
},
"aggs": {
"avg_price": {
"avg": { "field": "price" }
},
"max_price": {
"max": { "field": "price" }
},
"min_price": {
"min": { "field": "price" }
}
}
}
}
}
# 日期直方图聚合
GET /products/_search
{
"size": 0,
"aggs": {
"date_histogram": {
"date_histogram": {
"field": "createdAt",
"calendar_interval": "month"
}
}
}
}
# 范围聚合
GET /products/_search
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "key": "0-3000", "to": 3000 },
{ "key": "3000-6000", "from": 3000, "to": 6000 },
{ "key": "6000-10000", "from": 6000, "to": 10000 },
{ "key": "10000+", "from": 10000 }
]
}
}
}
}6.2.6 Spring Data Elasticsearch集成
Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>application.yml配置
spring:
elasticsearch:
uris: http://localhost:9200
# username: elastic # 如果开启了安全认证
# password: your_password
connection-timeout: 5s
socket-timeout: 30s实体类定义
@Data
@Document(indexName = "products")
@Setting(shards = 3, replicas = 1)
public class ProductDocument {
@Id
private Long id;
@MultiField(
mainField = @Field(type = FieldType.Text, analyzer = "ik_max_word"),
subFields = @Field(type = FieldType.Keyword)
)
private String name;
@Field(type = FieldType.Text, analyzer = "ik_smart")
private String description;
@Field(type = FieldType.Double)
private Double price;
@Field(type = FieldType.Keyword)
private String category;
@Field(type = FieldType.Keyword)
private String brand;
@Field(type = FieldType.Integer)
private Integer status;
@Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second_millis)
private LocalDateTime createdAt;
}Repository接口
public interface ProductRepository extends ReactiveElasticsearchRepository<ProductDocument, Long> {
// 方法命名查询
Flux<ProductDocument> findByNameContaining(String name);
Flux<ProductDocument> findByCategory(String category);
Flux<ProductDocument> findByPriceBetween(Double min, Double max);
Flux<ProductDocument> findByBrandAndStatus(String brand, Integer status);
// 自定义查询
@Query("{\"match\": {\"name\": {\"query\": \"?0\"}}}")
Flux<ProductDocument> searchByName(String keyword);
}ElasticsearchRestTemplate操作
@Service
public class ProductService {
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
/**
* 全文检索
*/
public List<ProductDocument> search(String keyword, int page, int size) {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 搜索条件
queryBuilder.withQuery(QueryBuilders.multiMatchQuery(keyword, "name", "description", "brand"));
// 分页
queryBuilder.withPageable(PageRequest.of(page, size));
// 高亮
queryBuilder.withHighlightBuilder(
new HighlightBuilder()
.field("name")
.field("description")
.preTags("<em>")
.postTags("</em>")
);
// 排序
queryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
SearchHits<ProductDocument> searchHits = elasticsearchRestTemplate.search(
queryBuilder.build(), ProductDocument.class);
return searchHits.getSearchHits().stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}
/**
* 复合查询
*/
public List<ProductDocument> complexSearch(String keyword, String category,
Double minPrice, Double maxPrice) {
BoolQuery.Builder boolQueryBuilder = QueryBuilders.boolQuery();
// 关键词搜索
if (StringUtils.hasText(keyword)) {
boolQueryBuilder.must(QueryBuilders.multiMatchQuery(keyword, "name", "description"));
}
// 分类过滤
if (StringUtils.hasText(category)) {
boolQueryBuilder.filter(QueryBuilders.termQuery("category", category));
}
// 价格范围
if (minPrice != null || maxPrice != null) {
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
if (minPrice != null) rangeQuery.gte(minPrice);
if (maxPrice != null) rangeQuery.lte(maxPrice);
boolQueryBuilder.filter(rangeQuery);
}
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder()
.withQuery(boolQueryBuilder.build())
.withPageable(PageRequest.of(0, 20));
SearchHits<ProductDocument> searchHits = elasticsearchRestTemplate.search(
queryBuilder.build(), ProductDocument.class);
return searchHits.getSearchHits().stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}
/**
* 聚合查询
*/
public Map<String, Long> aggregateByCategory() {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder()
.withAggregations(
AggregationBuilders.terms("category_agg")
.field("category")
.size(10)
);
SearchHits<ProductDocument> searchHits = elasticsearchRestTemplate.search(
queryBuilder.build(), ProductDocument.class);
// 解析聚合结果
// ...
return Collections.emptyMap();
}
}数据同步(MySQL → Elasticsearch)
@Service
public class DataSyncService {
@Autowired
private ProductMapper productMapper;
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
/**
* 全量同步
*/
public void fullSync() {
int page = 0;
int size = 500;
while (true) {
Page<Product> productPage = productMapper.selectPage(
new Page<>(page, size), null);
List<ProductDocument> documents = productPage.getRecords().stream()
.map(this::convertToDocument)
.collect(Collectors.toList());
if (documents.isEmpty()) break;
elasticsearchRestTemplate.save(documents);
page++;
if (!productPage.hasNext()) break;
}
}
/**
* 增量同步(基于更新时间)
*/
public void incrementalSync(LocalDateTime lastSyncTime) {
List<Product> products = productMapper.selectList(
new QueryWrapper<Product>()
.ge("updated_at", lastSyncTime)
);
List<ProductDocument> documents = products.stream()
.map(this::convertToDocument)
.collect(Collectors.toList());
elasticsearchRestTemplate.save(documents);
}
private ProductDocument convertToDocument(Product product) {
ProductDocument doc = new ProductDocument();
doc.setId(product.getId());
doc.setName(product.getName());
doc.setDescription(product.getDescription());
doc.setPrice(product.getPrice());
doc.setCategory(product.getCategory());
doc.setBrand(product.getBrand());
doc.setStatus(product.getStatus());
doc.setCreatedAt(product.getCreatedAt());
return doc;
}
}6.3 DevOps基础(第10-16天)
6.3.1 Docker容器化
Docker与前端概念对比
| 前端概念 | Docker |
|---|---|
| npm init | docker init |
| package.json | Dockerfile |
| npm run build | docker build |
| node_modules | Docker Image |
| 运行项目 | docker run |
| docker-compose(前端也用) | docker-compose(编排多个服务) |
| Vercel / Netlify部署 | Docker部署 |
Docker安装
- 下载:Docker Desktop
- 安装后验证:
docker --version
docker-compose --versionDocker基本命令
# 镜像操作
docker pull nginx:latest # 拉取镜像
docker images # 查看本地镜像
docker rmi nginx:latest # 删除镜像
docker image prune # 清理无用镜像
# 容器操作
docker ps # 查看运行中的容器
docker ps -a # 查看所有容器
docker run -d --name mynginx -p 8080:80 nginx # 运行容器
docker stop mynginx # 停止容器
docker start mynginx # 启动容器
docker restart mynginx # 重启容器
docker rm mynginx # 删除容器
docker logs mynginx # 查看日志
docker exec -it mynginx /bin/bash # 进入容器
# 容器与宿主机文件交互
docker cp mynginx:/usr/share/nginx/html/index.html ./index.html # 容器→宿主机
docker cp ./index.html mynginx:/usr/share/nginx/html/index.html # 宿主机→容器6.3.2 Dockerfile编写
Java应用Dockerfile
# 多阶段构建(推荐)
# 第一阶段:构建
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests -B
# 第二阶段:运行
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# 设置时区
RUN apk add --no-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
# 创建非root用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# 从构建阶段复制jar包
COPY --from=builder /app/target/*.jar app.jar
# 暴露端口
EXPOSE 8080
# JVM参数优化
ENV JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD wget -q --spider http://localhost:8080/actuator/health || exit 1
# 启动命令
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]前端应用Dockerfile(对比)
# 前端构建
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --registry=https://registry.npmmirror.com
COPY . .
RUN npm run build
# Nginx运行
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]完整Spring Boot项目Dockerfile
# 基础镜像
FROM eclipse-temurin:17-jdk-alpine AS builder
# 设置工作目录
WORKDIR /app
# 先复制Maven配置,利用Docker缓存
COPY pom.xml .
COPY .mvn .mvn
COPY mvnw .
# 下载依赖(利用Docker缓存层)
RUN ./mvnw dependency:go-offline -B
# 复制源代码
COPY src ./src
# 构建
RUN ./mvnw clean package -DskipTests -B
# 运行阶段
FROM eclipse-temurin:17-jre-alpine
# 安装必要工具
RUN apk add --no-cache curl tzdata
# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
WORKDIR /app
# 从构建阶段复制
COPY --from=builder /app/target/*.jar app.jar
# 创建日志目录
RUN mkdir -p /app/logs
# 暴露端口
EXPOSE 8080
# 环境变量
ENV SPRING_PROFILES_ACTIVE=prod
ENV JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC"
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# 启动
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -Dspring.profiles.active=$SPRING_PROFILES_ACTIVE -jar app.jar"]构建与运行
# 构建镜像
docker build -t myapp:1.0 .
# 运行容器
docker run -d \
--name myapp \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=prod \
-e JAVA_OPTS="-Xms512m -Xmx1024m" \
-v /app/logs:/app/logs \
myapp:1.06.3.3 Docker Compose编排
完整项目docker-compose.yml
version: '3.8'
services:
# MySQL数据库
mysql:
image: mysql:8.0
container_name: mysql
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: myapp
MYSQL_USER: appuser
MYSQL_PASSWORD: app123
TZ: Asia/Shanghai
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
- ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
networks:
- app-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
# Redis缓存
redis:
image: redis:7.0-alpine
container_name: redis
ports:
- "6379:6379"
volumes:
- redis-data:/data
command: redis-server --appendonly yes
networks:
- app-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
# Elasticsearch搜索引擎
elasticsearch:
image: elasticsearch:8.9.0
container_name: elasticsearch
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- TZ=Asia/Shanghai
ports:
- "9200:9200"
- "9300:9300"
volumes:
- es-data:/usr/share/elasticsearch/data
networks:
- app-network
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
interval: 30s
timeout: 10s
retries: 5
# Kibana可视化
kibana:
image: kibana:8.9.0
container_name: kibana
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
- TZ=Asia/Shanghai
ports:
- "5601:5601"
depends_on:
elasticsearch:
condition: service_healthy
networks:
- app-network
# Spring Boot应用
app:
build:
context: .
dockerfile: Dockerfile
container_name: myapp
environment:
- SPRING_PROFILES_ACTIVE=docker
- JAVA_OPTS=-Xms512m -Xmx1024m
ports:
- "8080:8080"
volumes:
- app-logs:/app/logs
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
elasticsearch:
condition: service_healthy
networks:
- app-network
restart: unless-stopped
# Nginx反向代理
nginx:
image: nginx:alpine
container_name: nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/conf.d:/etc/nginx/conf.d
- ./nginx/ssl:/etc/nginx/ssl
- ./nginx/html:/usr/share/nginx/html
depends_on:
- app
networks:
- app-network
restart: unless-stopped
# 数据卷
volumes:
mysql-data:
redis-data:
es-data:
app-logs:
# 网络
networks:
app-network:
driver: bridgeDocker Compose常用命令
# 启动所有服务
docker-compose up -d
# 查看运行状态
docker-compose ps
# 查看日志
docker-compose logs -f
# 查看特定服务日志
docker-compose logs -f app
# 停止所有服务
docker-compose stop
# 停止并删除容器、网络
docker-compose down
# 停止并删除容器、网络、数据卷(慎用!)
docker-compose down -v
# 重新构建并启动
docker-compose up -d --build
# 进入容器
docker-compose exec app /bin/sh
# 扩容服务
docker-compose up -d --scale app=36.3.4 Kubernetes(K8s)基础
K8s核心概念
| 概念 | 说明 | 前端类比 |
|---|---|---|
| Pod | 最小部署单元,包含一个或多个容器 | 单个页面/组件 |
| Deployment | 管理Pod的副本数和滚动更新 | CI/CD部署流程 |
| Service | 暴露Pod的网络访问 | Nginx反向代理 |
| Ingress | 外部访问入口 | 前端路由 + Nginx |
| ConfigMap | 配置文件 | .env文件 |
| Secret | 敏感配置 | .env.local |
| Namespace | 资源隔离 | 项目目录 |
| Node | 运行Pod的物理/虚拟机器 | 服务器 |
| Cluster | 一组Node的集合 | 服务器集群 |
Pod配置
# pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:1.0
ports:
- containerPort: 8080
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 5Deployment配置
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
labels:
app: myapp
spec:
replicas: 3 # 副本数
selector:
matchLabels:
app: myapp
strategy:
type: RollingUpdate # 滚动更新策略
rollingUpdate:
maxSurge: 1 # 最多多出1个Pod
maxUnavailable: 0 # 更新时不允许有Pod不可用
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:1.0
imagePullPolicy: Always
ports:
- containerPort: 8080
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
env:
- name: SPRING_PROFILES_ACTIVE
valueFrom:
configMapKeyRef:
name: myapp-config
key: spring-profiles-active
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: myapp-secret
key: db-password
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30
periodSeconds: 5Service配置
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
type: ClusterIP # 集群内部访问
selector:
app: myapp
ports:
- port: 80 # Service端口
targetPort: 8080 # 容器端口
protocol: TCP
---
# NodePort类型(外部可访问)
apiVersion: v1
kind: Service
metadata:
name: myapp-service-nodeport
spec:
type: NodePort
selector:
app: myapp
ports:
- port: 80
targetPort: 8080
nodePort: 30080 # 节点端口(30000-32767)
---
# LoadBalancer类型(云环境)
apiVersion: v1
kind: Service
metadata:
name: myapp-service-lb
spec:
type: LoadBalancer
selector:
app: myapp
ports:
- port: 80
targetPort: 8080Ingress配置
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls
rules:
- host: myapp.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80ConfigMap和Secret
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
data:
spring-profiles-active: "prod"
application.yml: |
spring:
datasource:
url: jdbc:mysql://mysql-service:3306/myapp
redis:
host: redis-service
port: 6379
---
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: myapp-secret
type: Opaque
data:
# base64编码
db-password: cm9vdDEyMw==
redis-password: cmVkaXMxMjM=K8s常用命令
# 部署
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml
# 查看状态
kubectl get pods
kubectl get deployments
kubectl get services
kubectl get ingress
kubectl get all
# 查看详细信息
kubectl describe pod myapp-pod
kubectl describe deployment myapp-deployment
# 查看日志
kubectl logs -f myapp-pod
kubectl logs -f deployment/myapp-deployment
# 进入容器
kubectl exec -it myapp-pod -- /bin/sh
# 扩缩容
kubectl scale deployment myapp-deployment --replicas=5
# 滚动更新
kubectl set image deployment/myapp-deployment myapp=myapp:2.0
kubectl rollout status deployment/myapp-deployment
kubectl rollout history deployment/myapp-deployment
kubectl rollout undo deployment/myapp-deployment
# 删除
kubectl delete -f deployment.yaml
kubectl delete pod myapp-pod
kubectl delete deployment myapp-deployment6.3.5 CI/CD
GitHub Actions
# .github/workflows/ci-cd.yml
name: Java CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
JAVA_VERSION: '17'
DOCKER_IMAGE: myapp
DOCKER_REGISTRY: registry.cn-hangzhou.aliyuncs.com
jobs:
# 代码质量检查
lint:
name: Code Quality
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: 'temurin'
cache: 'maven'
- name: Run Checkstyle
run: mvn checkstyle:check
- name: Run SpotBugs
run: mvn spotbugs:check
# 单元测试
test:
name: Unit Tests
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: 'temurin'
cache: 'maven'
- name: Run Tests
run: mvn test -B
- name: Generate Coverage Report
run: mvn jacoco:report
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
file: target/site/jacoco/jacoco.xml
# 构建与推送Docker镜像
build:
name: Build & Push Docker Image
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push'
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Registry
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}
tags: |
type=sha,prefix=
type=ref,event=branch
type=semver,pattern={{version}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# 部署到测试环境
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/develop'
environment: staging
steps:
- uses: actions/checkout@v4
- name: Deploy to K8s
run: |
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig
export KUBECONFIG=kubeconfig
kubectl set image deployment/myapp-deployment \
myapp=${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:${{ github.sha }} \
-n staging
kubectl rollout status deployment/myapp-deployment -n staging --timeout=300s
# 部署到生产环境
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
environment: production
steps:
- uses: actions/checkout@v4
- name: Deploy to K8s
run: |
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig
export KUBECONFIG=kubeconfig
kubectl set image deployment/myapp-deployment \
myapp=${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:${{ github.sha }} \
-n production
kubectl rollout status deployment/myapp-deployment -n production --timeout=300sGitLab CI/CD
# .gitlab-ci.yml
stages:
- lint
- test
- build
- deploy-staging
- deploy-production
variables:
MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
cache:
paths:
- .m2/repository
# 代码检查
lint:
stage: lint
image: maven:3.9-eclipse-temurin-17
script:
- mvn checkstyle:check spotbugs:check
only:
- merge_requests
- main
- develop
# 单元测试
test:
stage: test
image: maven:3.9-eclipse-temurin-17
script:
- mvn test -B
- mvn jacoco:report
artifacts:
reports:
junit: target/surefire-reports/TEST-*.xml
coverage_report:
coverage_format: cobertura
path: target/site/jacoco/jacoco.xml
# 构建Docker镜像
build:
stage: build
image: docker:24-dind
services:
- docker:24-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $DOCKER_IMAGE .
- docker push $DOCKER_IMAGE
only:
- main
- develop
# 部署到测试环境
deploy-staging:
stage: deploy-staging
image: bitnami/kubectl:latest
script:
- kubectl set image deployment/myapp-deployment myapp=$DOCKER_IMAGE -n staging
- kubectl rollout status deployment/myapp-deployment -n staging
environment:
name: staging
url: https://staging.myapp.com
only:
- develop
# 部署到生产环境
deploy-production:
stage: deploy-production
image: bitnami/kubectl:latest
script:
- kubectl set image deployment/myapp-deployment myapp=$DOCKER_IMAGE -n production
- kubectl rollout status deployment/myapp-deployment -n production
environment:
name: production
url: https://myapp.com
when: manual # 手动触发
only:
- mainJenkins Pipeline
// Jenkinsfile
pipeline {
agent any
environment {
MAVEN_HOME = tool 'Maven-3.9'
JAVA_HOME = tool 'JDK-17'
DOCKER_IMAGE = 'myapp'
DOCKER_REGISTRY = 'registry.cn-hangzhou.aliyuncs.com'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Code Quality') {
steps {
sh '${MAVEN_HOME}/bin/mvn checkstyle:check'
}
}
stage('Unit Test') {
steps {
sh '${MAVEN_HOME}/bin/mvn test -B'
}
post {
always {
junit 'target/surefire-reports/TEST-*.xml'
jacoco execPattern: 'target/**/*.exec'
}
}
}
stage('Build Docker Image') {
steps {
script {
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-credentials') {
def image = docker.build("${DOCKER_IMAGE}:${env.BUILD_NUMBER}")
image.push()
image.push('latest')
}
}
}
}
stage('Deploy to Staging') {
when {
branch 'develop'
}
steps {
sh '''
kubectl set image deployment/myapp-deployment \
myapp=${DOCKER_REGISTRY}/${DOCKER_IMAGE}:${BUILD_NUMBER} \
-n staging
kubectl rollout status deployment/myapp-deployment -n staging
'''
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
input {
message '确认部署到生产环境?'
ok '确认部署'
}
steps {
sh '''
kubectl set image deployment/myapp-deployment \
myapp=${DOCKER_REGISTRY}/${DOCKER_IMAGE}:${BUILD_NUMBER} \
-n production
kubectl rollout status deployment/myapp-deployment -n production
'''
}
}
}
post {
success {
echo 'Pipeline succeeded!'
}
failure {
echo 'Pipeline failed!'
}
always {
cleanWs()
}
}
}6.3.6 Nginx反向代理与负载均衡
Nginx基本配置
# nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll; # Linux推荐
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time';
access_log /var/log/nginx/access.log main;
# 性能优化
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 100m;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml
application/rss+xml image/svg+xml;
# 引入子配置
include /etc/nginx/conf.d/*.conf;
}反向代理配置
# conf.d/myapp.conf
# 上游服务(负载均衡)
upstream backend {
# 轮询(默认)
# server 192.168.1.100:8080;
# server 192.168.1.101:8080;
# 权重轮询
server 192.168.1.100:8080 weight=3;
server 192.168.1.101:8080 weight=2;
server 192.168.1.102:8080 weight=1;
# IP哈希(会话保持)
# ip_hash;
# 最少连接
# least_conn;
# 健康检查
# server 192.168.1.100:8080 max_fails=3 fail_timeout=30s;
keepalive 32;
}
# HTTP重定向到HTTPS
server {
listen 80;
server_name myapp.com www.myapp.com;
return 301 https://$server_name$request_uri;
}
# HTTPS配置
server {
listen 443 ssl http2;
server_name myapp.com www.myapp.com;
# SSL证书
ssl_certificate /etc/nginx/ssl/myapp.crt;
ssl_certificate_key /etc/nginx/ssl/myapp.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 安全头
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 前端静态文件
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html; # SPA路由支持
# 缓存策略
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
location = /index.html {
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
}
# API反向代理
location /api/ {
proxy_pass http://backend/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 缓冲区
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
# WebSocket支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# 健康检查端点
location /nginx-health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# 错误页面
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}前后端分离部署架构
┌─────────────┐
│ Nginx │
│ (443/80) │
└──────┬──────┘
│
┌────────────┼────────────┐
│ │ │
┌─────▼─────┐ ┌───▼───┐ ┌─────▼─────┐
│ 前端静态 │ │ API │ │ WebSocket │
│ 文件服务 │ │ 代理 │ │ 代理 │
└───────────┘ └───┬───┘ └─────┬─────┘
│ │
┌─────▼───────────▼─────┐
│ 后端服务集群 │
│ ┌─────┐ ┌─────┐ │
│ │App-1│ │App-2│ ... │
│ └─────┘ └─────┘ │
└───────────┬───────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
┌─────▼─────┐ ┌──────▼──────┐ ┌─────▼─────┐
│ MySQL │ │ Redis │ │ ES │
│ (主从) │ │ (集群) │ │ (集群) │
└───────────┘ └─────────────┘ └───────────┘6.4 监控与链路追踪(第17-21天)
6.4.1 Spring Boot Actuator
Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>application.yml配置
management:
endpoints:
web:
exposure:
include: "*" # 暴露所有端点
base-path: /actuator
endpoint:
health:
show-details: always # 显示健康详情
show-components: always
info:
enabled: true
metrics:
enabled: true
prometheus:
enabled: true
info:
env:
enabled: true
java:
enabled: true
os:
enabled: true
metrics:
tags:
application: ${spring.application.name}
export:
prometheus:
enabled: true
step: 15s # 采集间隔
health:
redis:
enabled: true
db:
enabled: true
elasticsearch:
enabled: true常用端点
# 健康检查
GET /actuator/health
{
"status": "UP",
"components": {
"db": {
"status": "UP",
"details": {
"database": "MySQL",
"validationQuery": "isValid()"
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 500107862016,
"free": 200043548672,
"threshold": 10485760
}
},
"redis": {
"status": "UP",
"details": {
"version": "7.0.0"
}
}
}
}
# 应用信息
GET /actuator/info
{
"app": {
"name": "myapp",
"version": "1.0.0"
},
"java": {
"version": "17.0.8",
"vendor": "Eclipse Adoptium"
}
}
# 所有指标
GET /actuator/metrics
# JVM内存使用
GET /actuator/metrics/jvm.memory.used
# HTTP请求统计
GET /actuator/metrics/http.server.requests
# 自定义指标
GET /actuator/metrics/custom.metric.name
# 环境变量
GET /actuator/env
# 配置属性
GET /actuator/configprops
# 线程dump
GET /actuator/threaddump
# heap dump
GET /actuator/heapdump自定义指标
@Service
public class MetricsService {
private final MeterRegistry meterRegistry;
private final Counter orderCounter;
private final Timer orderTimer;
private final AtomicInteger activeUsers;
public MetricsService(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
// 计数器
this.orderCounter = Counter.builder("order.created")
.description("Total number of orders created")
.tag("type", "normal")
.register(meterRegistry);
// 计时器
this.orderTimer = Timer.builder("order.process.time")
.description("Order processing time")
.register(meterRegistry);
// 仪表盘
this.activeUsers = meterRegistry.gauge("user.active",
new AtomicInteger(0));
}
public void createOrder(Order order) {
orderCounter.increment();
orderTimer.record(() -> {
// 处理订单
processOrder(order);
});
}
public void updateActiveUsers(int count) {
activeUsers.set(count);
}
}6.4.2 Prometheus + Grafana
Docker部署
# docker-compose-monitoring.yml
version: '3.8'
services:
# Prometheus
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
networks:
- monitoring
# Grafana
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin123
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
- grafana-data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
depends_on:
- prometheus
networks:
- monitoring
# Node Exporter(主机监控)
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.rootfs=/rootfs'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
networks:
- monitoring
# cAdvisor(容器监控)
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: cadvisor
ports:
- "8081:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
networks:
- monitoring
volumes:
prometheus-data:
grafana-data:
networks:
monitoring:
driver: bridgePrometheus配置
# prometheus/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
# Prometheus自身
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# Spring Boot应用
- job_name: 'spring-boot-app'
metrics_path: '/actuator/prometheus'
scrape_interval: 10s
static_configs:
- targets: ['app:8080']
labels:
application: 'myapp'
environment: 'production'
# Node Exporter
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
# cAdvisor
- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']
# MySQL Exporter
- job_name: 'mysql'
static_configs:
- targets: ['mysql-exporter:9104']
# Redis Exporter
- job_name: 'redis'
static_configs:
- targets: ['redis-exporter:9121']常用PromQL查询
# JVM内存使用
jvm_memory_used_bytes{application="myapp"}
# HTTP请求速率
rate(http_server_requests_seconds_count[5m])
# HTTP请求平均耗时
rate(http_server_requests_seconds_sum[5m]) / rate(http_server_requests_seconds_count[5m])
# CPU使用率
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# 内存使用率
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
# 磁盘使用率
(1 - node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100
# 容器CPU使用率
rate(container_cpu_usage_seconds_total{name="myapp"}[5m])
# 容器内存使用
container_memory_usage_bytes{name="myapp"}
# 错误率
rate(http_server_requests_seconds_count{status=~"5.."}[5m]) / rate(http_server_requests_seconds_count[5m]) * 1006.4.3 ELK日志收集
ELK架构
应用日志 → Logstash/Filebeat → Elasticsearch → Kibana
↓
存储与检索
↓
可视化展示Docker部署ELK
# docker-compose-elk.yml
version: '3.8'
services:
elasticsearch:
image: elasticsearch:8.9.0
container_name: elasticsearch
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
ports:
- "9200:9200"
volumes:
- es-data:/usr/share/elasticsearch/data
networks:
- elk
logstash:
image: logstash:8.9.0
container_name: logstash
ports:
- "5044:5044"
- "5000:5000/tcp"
- "5000:5000/udp"
- "9600:9600"
volumes:
- ./logstash/pipeline:/usr/share/logstash/pipeline
- ./logstash/config:/usr/share/logstash/config
depends_on:
- elasticsearch
networks:
- elk
kibana:
image: kibana:8.9.0
container_name: kibana
ports:
- "5601:5601"
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
depends_on:
- elasticsearch
networks:
- elk
filebeat:
image: elastic/filebeat:8.9.0
container_name: filebeat
volumes:
- ./filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml
- /var/log:/var/log:ro
- app-logs:/app-logs:ro
depends_on:
- logstash
networks:
- elk
volumes:
es-data:
app-logs:
networks:
elk:
driver: bridgeLogstash配置
# logstash/pipeline/logstash.conf
input {
# 接收Beats(Filebeat)
beats {
port => 5044
}
# TCP输入
tcp {
port => 5000
codec => json
}
# 从文件读取
# file {
# path => "/app-logs/*.log"
# start_position => "beginning"
# sincedb_path => "/dev/null"
# }
}
filter {
# 解析JSON日志
if [message] =~ /^\{/ {
json {
source => "message"
}
}
# 解析日期
date {
match => ["timestamp", "ISO8601", "yyyy-MM-dd HH:mm:ss.SSS"]
target => "@timestamp"
}
# 添加标签
if [level] == "ERROR" {
mutate {
add_tag => ["error"]
}
}
# 地理位置解析
if [client_ip] {
geoip {
source => "client_ip"
}
}
# 删除不需要的字段
mutate {
remove_field => ["@version", "host", "agent"]
}
}
output {
# 输出到Elasticsearch
elasticsearch {
hosts => ["http://elasticsearch:9200"]
index => "app-logs-%{+YYYY.MM.dd}"
# 如果需要认证
# user => "elastic"
# password => "your_password"
}
# 调试输出(开发环境)
# stdout {
# codec => rubydebug
# }
}Filebeat配置
# filebeat/filebeat.yml
filebeat.inputs:
# 应用日志
- type: log
enabled: true
paths:
- /app-logs/*.log
fields:
app: myapp
env: production
fields_under_root: true
multiline:
pattern: '^\d{4}-\d{2}-\d{2}'
negate: true
match: after
# Nginx访问日志
- type: log
enabled: true
paths:
- /var/log/nginx/access.log
fields:
app: nginx
type: access
fields_under_root: true
# Nginx错误日志
- type: log
enabled: true
paths:
- /var/log/nginx/error.log
fields:
app: nginx
type: error
fields_under_root: true
output.logstash:
hosts: ["logstash:5044"]
processors:
- add_host_metadata:
when.not.contains.tags: forwarded
- add_cloud_metadata: ~Spring Boot日志配置
<!-- logback-spring.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<springProperty scope="context" name="appName" source="spring.application.name"/>
<springProperty scope="context" name="env" source="spring.profiles.active"/>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- JSON格式文件输出(便于Logstash解析) -->
<appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/${appName}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/${appName}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"app":"${appName}","env":"${env}"}</customFields>
</encoder>
</appender>
<!-- TCP直接发送到Logstash -->
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>localhost:5000</destination>
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="JSON_FILE"/>
<appender-ref ref="LOGSTASH"/>
</root>
</configuration>Logstash TCP依赖
<!-- pom.xml -->
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>6.4.4 SkyWalking链路追踪
SkyWalking概述
SkyWalking是一个开源的应用性能监控(APM)系统,提供分布式追踪、服务拓扑分析、性能指标分析等功能。
与前端概念对比:
| 前端概念 | SkyWalking |
|---|---|
| Chrome DevTools Performance | SkyWalking性能分析 |
| Lighthouse性能评分 | SkyWalking服务评分 |
| Sentry错误追踪 | SkyWalking链路追踪 |
| 前端埋点监控 | SkyWalking指标采集 |
Docker部署SkyWalking
# docker-compose-skywalking.yml
version: '3.8'
services:
# SkyWalking OAP Server
oap:
image: apache/skywalking-oap-server:9.5.0
container_name: skywalking-oap
ports:
- "11800:11800" # gRPC
- "12800:12800" # REST
environment:
SW_STORAGE: elasticsearch
SW_STORAGE_ES_CLUSTER_NODES: elasticsearch:9200
SW_CORE_REST_HOST: 0.0.0.0
SW_CORE_REST_PORT: 12800
SW_CORE_GRPC_HOST: 0.0.0.0
SW_CORE_GRPC_PORT: 11800
JAVA_OPTS: "-Xms512m -Xmx512m"
depends_on:
- elasticsearch
networks:
- skywalking
# SkyWalking UI
ui:
image: apache/skywalking-ui:9.5.0
container_name: skywalking-ui
ports:
- "8088:8080"
environment:
SW_OAP_ADDRESS: http://oap:12800
depends_on:
- oap
networks:
- skywalking
elasticsearch:
image: elasticsearch:8.9.0
container_name: skywalking-es
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
- "9200:9200"
volumes:
- skywalking-es-data:/usr/share/elasticsearch/data
networks:
- skywalking
volumes:
skywalking-es-data:
networks:
skywalking:
driver: bridgeJava Agent接入
# 下载SkyWalking Java Agent
# https://skywalking.apache.org/downloads/
# 启动时添加Agent
java -javaagent:/path/to/skywalking-agent.jar \
-Dskywalking.agent.service_name=myapp \
-Dskywalking.collector.backend_service=localhost:11800 \
-jar app.jarDocker集成Agent
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# 下载SkyWalking Agent
ADD https://archive.apache.org/dist/skywalking/java-agent/9.0.0/apache-skywalking-java-agent-9.0.0.tgz /tmp/
RUN tar -xzf /tmp/apache-skywalking-java-agent-9.0.0.tgz -C /opt/
COPY target/*.jar app.jar
ENV SW_AGENT_NAME=myapp
ENV SW_BACKEND_SERVICE=localhost:11800
ENTRYPOINT ["sh", "-c", "java -javaagent:/opt/skywalking-agent/skywalking-agent.jar -Dskywalking.agent.service_name=${SW_AGENT_NAME} -Dskywalking.collector.backend_service=${SW_BACKEND_SERVICE} -jar app.jar"]自定义追踪
@Service
public class TracedService {
/**
* 使用注解追踪方法
*/
@Trace
public void processOrder(Order order) {
// 方法会被自动追踪
}
/**
* 自定义标签
*/
@Tag(key = "orderId", value = "arg[0].id")
@Tag(key = "amount", value = "arg[0].amount")
public void createOrder(Order order) {
// 自定义标签会附加到Span上
}
/**
* 自定义Span
*/
public void complexBusiness() {
try (Scope scope = ContextManager.createLocalSpan("complexBusiness")) {
// 业务逻辑
step1();
step2();
step3();
}
}
/**
* 自定义上下文传播
*/
public void crossServiceCall() {
ContextCarrier carrier = new ContextCarrier();
AbstractSpan span = ContextManager.createExitSpan("remoteCall", carrier, "remote-service:8080");
try {
// 传递上下文到远程服务
// carrier.serialize() 可以放入HTTP Header
remoteCall();
} finally {
ContextManager.stopSpan();
}
}
}SkyWalking告警配置
# alarm-settings.yml
rules:
# 服务响应时间告警
service_resp_time_rule:
metrics-name: service_resp_time
op: ">"
threshold: 1000 # 响应时间超过1000ms
period: 10 # 10分钟窗口
count: 3 # 连续3次
silence-period: 5 # 静默5分钟
message: "服务 {name} 响应时间超过1秒"
# 服务成功率告警
service_sla_rule:
metrics-name: service_sla
op: "<"
threshold: 8000 # 成功率低于80%
period: 10
count: 2
silence-period: 5
message: "服务 {name} 成功率低于80%"
# 端点响应时间告警
endpoint_resp_time_rule:
metrics-name: endpoint_resp_time
op: ">"
threshold: 2000
period: 10
count: 3
silence-period: 5
message: "端点 {name} 响应时间超过2秒"
# Webhook通知
webhooks:
- http://localhost:8080/webhook/receive6.5 综合练习任务
练习1:电商缓存系统
- 实现商品详情页缓存(Redis)
- 处理缓存穿透、击穿、雪崩问题
- 使用Redisson实现分布式锁
- 实现库存扣减的并发控制
练习2:全文搜索系统
- 使用Elasticsearch实现商品搜索
- 实现搜索建议(Suggest)
- 实现搜索结果高亮
- 实现聚合分析(分类统计、价格区间)
- 实现MySQL与Elasticsearch数据同步
练习3:Docker化部署
- 编写Spring Boot应用的Dockerfile
- 编写docker-compose.yml编排所有服务
- 配置Nginx反向代理
- 实现前后端分离部署
练习4:监控体系搭建
- 集成Spring Boot Actuator
- 部署Prometheus + Grafana
- 配置ELK日志收集
- 接入SkyWalking链路追踪
- 配置告警规则
练习5:完整CI/CD流水线
- 编写GitHub Actions / GitLab CI配置
- 实现代码质量检查 → 测试 → 构建 → 部署全流程
- 实现滚动更新和回滚
6.6 学习检查清单
Redis缓存
- [ ] 掌握Redis五种基本数据类型及使用场景
- [ ] 能使用Spring Boot集成Redis
- [ ] 理解缓存穿透、击穿、雪崩的原因和解决方案
- [ ] 掌握Redis主从、哨兵、集群架构
- [ ] 能使用Redisson实现分布式锁
Elasticsearch搜索
- [ ] 理解倒排索引原理
- [ ] 掌握Elasticsearch基本操作
- [ ] 能使用Spring Data Elasticsearch集成
- [ ] 掌握全文检索、复合查询、聚合分析
- [ ] 理解MySQL与Elasticsearch数据同步方案
Docker与K8s
- [ ] 能编写Java应用的Dockerfile
- [ ] 掌握Docker Compose编排
- [ ] 理解K8s核心概念(Pod、Deployment、Service、Ingress)
- [ ] 能编写K8s部署配置文件
- [ ] 掌握基本的K8s运维命令
CI/CD
- [ ] 能编写GitHub Actions / GitLab CI配置
- [ ] 理解CI/CD流水线的各个环节
- [ ] 掌握自动化测试、构建、部署流程
监控与追踪
- [ ] 掌握Spring Boot Actuator的使用
- [ ] 能部署Prometheus + Grafana监控体系
- [ ] 掌握ELK日志收集架构
- [ ] 理解SkyWalking链路追踪原理
- [ ] 能配置告警规则
完成本阶段后,你应该能够构建高可用、高性能的Java后端系统,掌握中间件的使用和运维技能,具备全栈开发能力。
评论
正在加载评论…