分布式微服务系统架构第166集:华为经验汇总

加群联系作者vx:xiaoda0423

仓库地址:https://webvueblog.github.io/JavaPlusDoc/

https://1024bat.cn/

https://github.com/webVueBlog/fastapi_plus

https://webvueblog.github.io/JavaPlusDoc/

点击勘误issues,哪吒感谢大家的阅读

1. 四种事务隔离级别

隔离级别

脏读 (Dirty Read)

不可重复读 (Non-repeatable Read)

幻读 (Phantom Read)

MySQL 默认

READ UNCOMMITTED

(读未提交)

✅ 可能发生

✅ 可能发生

✅ 可能发生

READ COMMITTED

(读已提交)

❌ 阻止

✅ 可能发生

✅ 可能发生

否(Oracle 默认)

REPEATABLE READ

(可重复读)

❌ 阻止

❌ 阻止

✅*(InnoDB 下几乎阻止)

✅(MySQL 默认)

SERIALIZABLE

(可串行化)

❌ 阻止

❌ 阻止

❌ 阻止

✅ 表示可能发生,❌ 表示可以避免

  • InnoDB 默认通过 间隙锁(Gap Lock) 解决了大部分幻读


2. 关键概念

  • 脏读:读取到另一个事务未提交的数据

  • 不可重复读:同一事务中两次读同一行数据,结果不一致(因为被其他事务修改)

  • 幻读:同一事务中两次相同条件查询,结果行数不同(因为其他事务插入/删除了符合条件的行)


3. 配置事务隔离级别

查询当前隔离级别

SELECT @@transaction_isolation;

设置隔离级别(会话级)

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

设置隔离级别(全局)

SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

4. MySQL InnoDB 特点

  • 默认隔离级别:REPEATABLE READ

  • 为了避免幻读,InnoDB 在可重复读下使用 Next-Key Lock(行锁 + 间隙锁)

  • MVCC(多版本并发控制)在 READ COMMITTED 和 REPEATABLE READ 下生效


5. 选择建议

  • READ UNCOMMITTED:几乎不用,数据不安全

  • READ COMMITTED:常用于高并发、能容忍不可重复读(Oracle 默认)

  • REPEATABLE READ:MySQL 默认,平衡性能和一致性

  • SERIALIZABLE:最高安全,但性能差,基本不用在高并发

synchronized不是乐观锁,它是悲观锁
我给你分三部分说明:


1. 悲观锁 vs 乐观锁

特性

悲观锁(synchronized/ReentrantLock

乐观锁(CAS、版本号机制)

加锁方式

操作前直接加锁,假设会发生冲突

操作时不加锁,先尝试更新,失败再重试

开销

阻塞线程,需要上下文切换

自旋重试,无阻塞(但可能CPU消耗高)

实现方式

synchronized

ReentrantLock、数据库 SELECT ... FOR UPDATE

AtomicInteger

、CAS、自增版本号字段

适合场景

并发高、冲突概率大

冲突少、读多写少


2. synchronized 是悲观锁

public synchronized void increment() {
    count++;
}
  • JVM 会在进入 increment() 方法时直接加锁(monitorenter 指令),其他线程必须等锁释放才能执行。

  • 这是悲观策略:假设每次都会冲突,所以先锁住资源。


3. 乐观锁实现(Java CAS)

Java 里常见的乐观锁实现是 CAS(Compare And Swap) :

import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        int oldValue, newValue;
        do {
            oldValue = count.get();
            newValue = oldValue + 1;
        } while (!count.compareAndSet(oldValue, newValue));
    }
}
  • compareAndSet:比较当前值是否和预期值相等,如果相等就更新,否则重试。

  • 无需加锁,适合高并发读多写少场景。

1. Feign 是什么

  • 声明式 HTTP 客户端:只需要定义 Java 接口 + 注解,就能发 HTTP 请求。

  • 集成了 Ribbon(负载均衡)和 Hystrix(容错降级,旧版),新版可配合 Spring Cloud LoadBalancer 和 Sentinel。

  • 在 Spring Cloud 中用于服务间调用,替代 RestTemplate 手写 URL。


2. Feign 的优势

  1. 调用简单:不需要手写 RestTemplate.getForObject(),直接调用接口方法。

  2. 可读性好:调用方看起来像本地方法调用。

  3. 支持负载均衡:配合注册中心自动选择服务实例。

  4. 可扩展:可自定义请求拦截器、编码器、解码器、日志等。


3. 快速上手

3.1 引入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

3.2 启用 Feign

@SpringBootApplication
@EnableFeignClients
public class MyApp {}

3.3 定义 Feign 接口

@FeignClient(name = "order-service") // name = 服务在注册中心的名字
public interface OrderClient {
    @GetMapping("/orders/{id}")
    Order getOrder(@PathVariable("id") Long id);
}

3.4 调用接口

@Service
public class UserService {
    @Autowired
    private OrderClient orderClient;

    public void showOrder(Long id) {
        Order order = orderClient.getOrder(id);
        System.out.println(order);
    }
}

注意name 必须与注册中心(Eureka / Nacos)里的服务名一致,且要启用负载均衡(默认开启)。


4. 常用配置

4.1 开启 Feign 日志

logging:
  level:
    com.example.client.OrderClient: DEBUG
@Bean
Logger.Level feignLoggerLevel() {
    return Logger.Level.FULL; // NONE, BASIC, HEADERS, FULL
}

4.2 配置超时

feign:
  client:
    config:
      default:       # default 表示所有 Feign 客户端
        connectTimeout: 5000
        readTimeout: 10000

4.3 请求拦截器(统一加 Token)

@Component
public class FeignAuthInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        template.header("Authorization", "Bearer xxx");
    }
}

4.4 降级处理(Fallback)

@FeignClient(name = "order-service", fallback = OrderClientFallback.class)
public interface OrderClient { ... }

@Component
public class OrderClientFallback implements OrderClient {
    @Override
    public Order getOrder(Long id) {
        return new Order(-1L, "服务不可用");
    }
}

5. 使用建议

  • 小项目可以直接用 Feign 默认配置,大项目要结合 Sentinel 做限流熔断。

  • 对高并发调用,可以开启 Feign + HTTP/2 + 连接池(默认 OkHttp 支持)。

  • 返回值最好定义统一响应结构,方便做错误处理。

  • 尽量避免在 Feign 接口里写复杂逻辑,保持“纯接口”风格。

1. Spring Cloud 是什么

  • 微服务全家桶:基于 Spring Boot,提供一套分布式系统基础设施

  • 目标:简化微服务架构下的服务注册、配置管理、负载均衡、服务调用、网关、安全、链路追踪、容错限流等复杂功能。

  • 核心理念:关注业务逻辑,公共能力交给 Spring Cloud 组件。


2. 核心功能

功能

作用

常用组件

服务注册与发现

让微服务能自动注册、互相调用

Eureka

、Consul、Nacos

负载均衡

多实例服务间流量分配

Ribbon

(已过时)、Spring Cloud LoadBalancer

服务调用

微服务间 HTTP 调用封装

OpenFeign

配置中心

动态配置管理

Spring Cloud Config

、Nacos

网关

请求路由、鉴权、限流

Spring Cloud Gateway

容错限流

熔断、降级、隔离、限流

Hystrix

(过时)、Sentinel、Resilience4j

消息驱动

事件驱动通信

Spring Cloud Stream

(Kafka/RabbitMQ)

链路追踪

分布式调用跟踪

Sleuth + Zipkin

分布式任务

定时任务调度

Spring Cloud Task

、xxl-job(第三方)


3. Spring Cloud 常用架构图

┌──────────┐
         │  Gateway │ ← 统一入口(鉴权/限流/路由)
         └─────┬────┘
               ↓
   ┌────────────────────────┐
   │ 服务注册中心 (Eureka)   │ ← 服务实例注册/发现
   └─────┬──────────────────┘
         ↓
  ┌───────────────┐    ┌───────────────┐
  │ user-service   │    │ order-service │ ← 微服务业务模块
  └─────┬──────────┘    └─────┬────────┘
        ↓                      ↓
   ┌───────────────┐     ┌───────────────┐
   │ Config Server  │     │ Message Broker│
   │ (动态配置)     │     │ (Kafka/Rabbit)│
   └────────────────┘     └───────────────┘

4. 典型微服务调用流程

  1. 服务启动 → 注册到注册中心(Eureka/Nacos)。

  2. 其他服务通过注册中心获取地址列表。

  3. 调用时由 LoadBalancer 选择实例(轮询/随机/权重等)。

  4. 通过 Feign 发起请求,结果返回。

  5. 配置中心可动态推送配置信息到服务。

  6. 链路追踪记录调用路径和耗时。


5. 常用组件快速上手

5.1 Eureka 服务注册与发现

<!-- 服务端 -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApp {}
server:
  port: 8761
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false

5.2 OpenFeign 服务调用

@FeignClient("order-service")
public interface OrderClient {
    @GetMapping("/orders/{id}")
    Order getOrder(@PathVariable Long id);
}

5.3 Gateway 网关

spring:
  cloud:
    gateway:
      routes:
        - id: user_route
          uri: lb://user-service
          predicates:
            - Path=/users/**

6. 版本体系

  • Spring Cloud 依赖 Spring Boot,版本需要匹配:

    • Hoxton → Spring Boot 2.2/2.3

    • 2021.x → Spring Boot 2.6+

    • 2022.x → Spring Boot 3.x(Jakarta EE 迁移)

  • 在 pom.xml 用 Spring Cloud BOM 管理版本:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>2022.0.3</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

1. TTL 是什么

  • 定义:Key 在 Redis 中还能存活的时间(秒或毫秒)。

  • 到期后:

    • Key 会被自动删除(实际是惰性+定期删除结合)。

    • 再访问会返回 nil(不存在)。

  • 用于缓存、会话、限时任务等场景。


2. 相关命令

命令

作用

返回值

EXPIRE key seconds

设置过期时间(秒)

1=成功,0=key不存在或不能设置

PEXPIRE key ms

设置过期时间(毫秒)

同上

TTL key

查看剩余过期时间(秒)

-1=永不过期,-2=已过期或不存在

PTTL key

查看剩余过期时间(毫秒)

同上

EXPIREAT key timestamp

指定时间戳(秒)过期

同上

PEXPIREAT key ms_timestamp

毫秒时间戳

同上

SET key value EX 60

创建 key 并设置 TTL(秒)

OK

SET key value PX 60000

创建 key 并设置 TTL(毫秒)

OK

PERSIST key

移除 TTL(变为永久)

1=成功,0=失败


3. 使用示例

# 设置过期时间
SET session:123 "Tom" EX 300       # 300秒后过期
EXPIRE product:1001 60             # 单独设置TTL

# 查看TTL
TTL session:123

# 毫秒精度
PEXPIRE tempkey 1500               # 1.5秒后过期
PTTL tempkey

# 移除TTL
PERSIST session:123

4. Redis 过期删除机制

Redis 不会精确到期时立刻删除 key,而是结合三种策略:

  1. 定时删除(主动扫描部分 key,减少内存占用)。

  2. 惰性删除(访问时才检查 TTL,过期就删除)。

  3. 淘汰策略(内存满时按策略淘汰)。


5. 常见应用

  • 缓存数据:防止数据长期占内存。

  • 会话管理:用户 token 自动过期。

  • 分布式锁SET key value NX EX 30

  • 限时业务:秒杀、优惠券有效期。

  • 防缓存雪崩:给 TTL 加随机值。


6. 注意事项

  • 更新 key 会保留 TTL(如 SET 覆盖会重置 TTL,HSET 修改 hash 字段不会影响 TTL)。

  • TTL 不保证精确到毫秒删除,删除时间取决于惰性检查/定期扫描。

  • 批量 key 统一过期容易造成缓存雪崩 → 加随机 TTL。

  • 持久化时 TTL 会一并保存,重启恢复后继续生效。

1. 为什么用 Redis 做缓存

  • 速度快:内存存储,单线程事件驱动,延迟通常 < 1ms。

  • 支持丰富数据结构:不仅能存字符串,还能存对象、集合、排序集等。

  • 支持过期策略:TTL、LRU/LFU 淘汰,防止内存溢出。

  • 分布式可用:可配合主从、哨兵、集群,支持高并发。


2. 缓存使用基本思路(Cache Aside Pattern)

读流程:
1. 先从 Redis 缓存查
2. 如果命中,直接返回
3. 如果未命中,从数据库查 → 回填 Redis(加过期时间)

写流程:
1. 更新数据库
2. 删除或更新 Redis 缓存

✅ 优点:保证数据库为最终权威数据
⚠️ 缺点:并发高时可能出现缓存击穿、雪崩、穿透问题(后面会讲解决方案)


3. Spring Boot 使用 Redis 缓存(示例)

3.1 添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

3.2 配置 application.yml

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    timeout: 2000
    lettuce:
      pool:
        max-active: 8
        max-idle: 4
        min-idle: 1

3.3 代码示例

@Service
public class UserService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private UserMapper userMapper;

    public User getUserById(Long id) {
        String key = "user:" + id;
        String json = redisTemplate.opsForValue().get(key);
        if (json != null) {
            return JSON.parseObject(json, User.class);
        }
        User user = userMapper.selectById(id);
        if (user != null) {
            redisTemplate.opsForValue().set(key, JSON.toJSONString(user), 10, TimeUnit.MINUTES);
        }
        return user;
    }

    public void updateUser(User user) {
        userMapper.updateById(user);
        redisTemplate.delete("user:" + user.getId());
    }
}

4. 缓存常见问题与解决方案

问题

场景

解决方案

缓存穿透

查询不存在的 key

缓存空值(短 TTL)+ 布隆过滤器

缓存击穿

热点 key 过期瞬间大量请求涌入

设置热点 key 永不过期 + 后台异步更新;或加互斥锁

缓存雪崩

大量 key 同时过期

给 TTL 加随机值,错开过期时间;多层缓存

数据不一致

更新数据库后缓存没同步

写操作优先更新数据库,然后删除缓存(避免脏数据)


5. 进阶优化

  1. 序列化优化

  • 默认 JDK 序列化占用多,可用 JSON(Fastjson / Jackson)或 Kryo。

  • 批量操作

    • 用 pipeline 一次发送多条命令减少 RTT。

  • 热点数据预热

    • 系统启动时提前加载常用数据进缓存。

  • 分布式锁

    • 用 SET key value NX EX 或 Redisson 实现互斥更新。

  • 监控

    • INFO 查看命中率、内存占用,结合 Prometheus+Grafana 可视化。


    6. 简易缓存流程图

    ┌───────────┐
       │  请求数据  │
       └─────┬─────┘
             ↓
     ┌─────────────┐
     │ 查 Redis 缓存 │───命中──▶ 返回数据
     └─────┬───────┘
           ↓未命中
     ┌─────────────┐
     │ 查数据库     │
     └─────┬───────┘
           ↓
      回填 Redis 缓存(TTL)
           ↓
         返回数据

    1. Redis 是什么

    • Remote Dictionary Server:基于内存的高性能 Key-Value 数据库。

    • 主要特性:

      • 内存存储(可持久化到磁盘)

      • 数据结构丰富

      • 单线程事件驱动(命令原子执行)

      • 支持主从、哨兵、高可用、集群

      • 常用于缓存、分布式锁、消息队列、排行榜等


    2. 常用数据类型

    类型

    命令示例

    使用场景

    String

    (字符串/二进制)

    SET key val

     / GET key

    缓存对象、计数器

    List

    (双端链表)

    LPUSH key val

     / RPOP key

    消息队列、时间线

    Set

    (无序去重集合)

    SADD key val

     / SISMEMBER key val

    标签、去重

    Hash

    (哈希表)

    HSET user:1 name Tom

     / HGET user:1 name

    对象存储

    ZSet

    (有序集合)

    ZADD rank 100 Tom

     / ZRANGE rank 0 10

    排行榜、延时任务

    BitmapSETBIT key offset 1

     / GETBIT key offset

    签到、活跃用户

    HyperLogLogPFADD key val

     / PFCOUNT key

    去重计数(近似)

    GeoGEOADD city 116.4 39.9 Beijing

    地理位置搜索


    3. 持久化机制

    RDB(快照)

    • 定期将内存数据写入磁盘(.rdb 文件)。

    • 优点:体积小,恢复快。

    • 缺点:可能丢失最近数据。

    • 配置:

      save 900 1   # 900秒内有1次写操作就触发快照
      save 300 10

    AOF(追加文件)

    • 每次写操作追加到 .aof 文件。

    • 优点:数据丢失更少(可配置每秒/每次写入刷盘)。

    • 缺点:文件体积大,恢复慢。

    • 常用策略:RDB + AOF 混合持久化(Redis 4.0+)。


    4. 过期与淘汰策略

    • 过期策略

      • 定时删除(定期扫描)

      • 惰性删除(访问时检查)

    • 淘汰策略maxmemory-policy):

      • volatile-lru:从设置过期时间的 key 中挑 LRU

      • allkeys-lru:所有 key 中挑 LRU

      • volatile-ttl:按 TTL 近过期时间

      • noeviction:内存满直接报错(默认)


    5. 主从复制与高可用

    • 主从复制(Master-Slave)

      • 从节点实时复制主节点数据

      • 读写分离

    • 哨兵(Sentinel)

      • 监控主从节点

      • 主节点故障自动切换

    • 集群(Cluster)

      • 分片存储(哈希槽 0~16383)

      • 自动故障转移


    6. 常用实战场景

    • 缓存(页面缓存、热点数据缓存)

    • 分布式锁(SET key val NX EX

    • 限流(INCR + 过期时间)

    • 延时队列(ZSet + 时间戳)

    • 排行榜(ZSet)

    • 会话管理(String/Hash + TTL)


    7. 性能与调优

    • 减少大 key(避免一次性取超大数据)

    • 批量命令MGET / MSET / pipeline)

    • 合理 TTL(防止内存爆炸)

    • 使用连接池(减少频繁建立 TCP 连接开销)

    • 监控INFOslowlogredis-cli monitor


    8. 常用命令速查

    SET key value EX 60 NX   # 设置过期时间 & 仅在不存在时设置
    GET key
    DEL key
    EXPIRE key 60
    INCR key
    HSET user:1 name Tom age 20
    LPUSH queue a b c
    RPOP queue
    ZADD rank 100 Tom
    ZRANGE rank 0 -1 WITHSCORES

    1. 老年代是什么

    • 属于 Java 堆(Heap) 的一部分,线程共享

    • 存放 生命周期较长 或 体积较大 的对象。

    • 与 新生代(Young Generation) 配合使用,形成 JVM 分代内存结构

    • 在 Minor GC 时通常不参与回收,但在 Major GC / Full GC 时会被回收。


    2. 对象进入老年代的条件

    常见有 4 种情况:

    1. 对象年龄达到阈值

    • 对象在 Survivor 区经过多次 Minor GC 仍存活,年龄(Age)增加。

    • 达到 -XX:MaxTenuringThreshold(默认 15)就会晋升到老年代。

  • 大对象直接进入老年代

    • 如果对象过大(如大数组、长字符串),可能跳过新生代直接放入老年代。

    • 控制参数:-XX:PretenureSizeThreshold(仅对 Serial/ParNew 有效)。

  • 动态年龄判断

    • Survivor 区对象年龄总大小超过 Survivor 区的一半时,比这个年龄大的对象直接进入老年代。

  • 新生代空间不足

    • Minor GC 后仍放不下存活对象,多余部分直接晋升老年代。


    3. 老年代的 GC 特点

    • 对象存活率高,回收频率低。

    • 采用 标记-整理(Mark-Compact) 或 标记-清除(Mark-Sweep) 算法:

      • 标记-整理:避免碎片化,但会有对象移动成本。

      • 标记-清除:快,但可能导致内存碎片。

    • 触发回收的情况:

    1. 老年代满(分配对象或晋升时触发)。

    2. 调用 System.gc()(可能触发 Full GC)。

    3. 元空间/永久代不足(间接触发 Full GC)。


    4. 常见问题

    1. 频繁 Full GC

    • 老年代空间太小。

    • 短命的大对象直接进入老年代。

    • 新生代晋升太快,挤占老年代。

  • 内存碎片

    • CMS 收集器使用标记-清除,可能产生碎片,导致大对象分配失败 → 提前 Full GC。

  • GC 停顿长

    • 老年代对象多,回收时要扫描 & 移动大量对象。


    5. 调优建议

    • 增大老年代
      -Xms / -Xmx 配合 -XX:NewRatio 调整新生代与老年代比例。

    • 合理设置晋升阈值
      -XX:MaxTenuringThreshold,延缓晋升,减轻老年代压力。

    • 控制大对象创建
      避免一次性分配大数组/长字符串,必要时分片处理。

    • 减少对象在 Survivor 区的溢出
      调整 Survivor 区大小(-XX:SurvivorRatio)。

    • 收集器选择

      • 低延迟:G1、ZGC、Shenandoah。

      • 吞吐量:Parallel GC。


    6. GC 日志中的老年代

    示例(G1 GC 日志):

    [GC pause (G1 Evacuation Pause) (mixed), 0.0456789 secs]
       [Eden: 512M(512M)->0B(512M) Survivors: 32M->32M Heap: 2G(4G)->1.6G(4G)]
    • Heap 中的减少量主要来自老年代 & 新生代混合回收。


    ✅ 总结口诀

    新生代放不下 → 晋升老年代
    老年代满 → Full GC
    晋升条件:年龄阈值 / 大对象 / 动态年龄 / Survivor 溢出

    1. 垃圾回收的目标

    • 释放内存:自动回收不再被引用的对象占用的堆空间。

    • 保证可达对象可用:只清理不可达的对象。

    • 减少停顿:平衡吞吐量与延迟。


    2. GC 主要管理的区域

    • 堆(Heap) :GC 核心关注区

      • 存活时间长的大对象、晋升对象

      • Eden 区(对象初始分配)

      • Survivor 0 / Survivor 1 区(对象晋升前的中转)

      • 新生代(Young Generation)

      • 老年代(Old Generation)

    • 方法区(Method Area / 元空间 Metaspace) :类元数据、常量等(部分 GC 会回收)。


    3. 判断对象是否可回收

    3.1 引用计数法(Reference Counting)

    • 每个对象维护引用计数,计数为 0 时可回收。

    • 缺点:循环引用无法回收 → Java 不采用。

    3.2 可达性分析(Reachability Analysis)✅

    • 从 GC Roots(栈、本地方法栈引用、静态变量等)出发,沿引用链可达的对象为存活对象,其余为垃圾。


    4. 常用 GC 算法

    算法

    适用区域

    原理

    优点

    缺点

    标记-清除

    老年代

    标记存活对象 → 清除未标记对象

    实现简单

    碎片多

    复制算法

    新生代

    复制存活对象到另一块区域 → 清空原区

    无碎片

    需要额外空间

    标记-整理

    老年代

    标记存活对象 → 向一端移动

    无碎片

    移动对象成本高

    分代收集

    全堆

    新生代复制算法 + 老年代标记整理

    各取所长

    需分代管理


    5. GC 类型

    • Minor GC

      • 回收新生代(Eden + Survivor)

      • 速度快、停顿短

    • Major GC / Full GC

      • 回收老年代 + 新生代(Full GC 还可能回收方法区)

      • 停顿时间长

      • 触发原因:老年代满、System.gc()、元空间不足等


    6. 常见垃圾收集器

    6.1 串行收集器(Serial)

    • 单线程 GC,适合单核/小内存

    • 参数:-XX:+UseSerialGC

    6.2 并行收集器(Parallel / 吞吐量优先)

    • 多线程回收,适合批处理

    • 参数:-XX:+UseParallelGC

    6.3 CMS(Concurrent Mark Sweep)

    • 并发标记-清除,减少停顿时间

    • 缺点:内存碎片、浮动垃圾

    • 参数:-XX:+UseConcMarkSweepGC(JDK9 之后被标记废弃)

    6.4 G1(Garbage First)✅

    • 面向服务端,分区回收,低延迟

    • 参数:-XX:+UseG1GC

    6.5 ZGC / Shenandoah(JDK11+ / JDK12+)

    • 超低延迟(毫秒级停顿)

    • 大堆支持(TB 级)


    7. 常用调优参数

    -Xms1g -Xmx1g           # 堆初始/最大
    -XX:NewRatio=2          # 新生代:老年代=1:2
    -XX:SurvivorRatio=8     # Eden:Survivor=8:1:1
    -XX:+UseG1GC            # 使用G1收集器
    -XX:MaxGCPauseMillis=200 # 目标最大停顿
    -XX:+PrintGCDetails     # 输出GC详情
    -XX:+PrintGCDateStamps  # 输出时间戳

    8. GC 调优思路

    1. 确认目标:低延迟还是高吞吐?

    2. 收集数据jstat / GC logs / jmap / jvisualvm

    3. 分析瓶颈:频繁 Full GC?老年代占满?

    4. 调整参数:堆大小、分代比例、GC 类型

    5. 压测验证:确认 GC 次数与停顿符合预期


    9. 总结口诀

    新生代复制,老年代整理;
    GC Roots 做基准,可达即存活;
    选对收集器,目标先明确。

    1. JVM 内存结构总览

    Java 虚拟机在运行时会把内存分成几个区域,主要有:

    ┌───────────────────────────────┐
    │ 线程私有(Thread-private)     │
    │   - 程序计数器(PC Register)   │
    │   - Java 虚拟机栈(JVM Stack) │
    │   - 本地方法栈(Native Stack) │
    ├───────────────────────────────┤
    │ 线程共享(Thread-shared)      │
    │   - 堆(Heap)                 │
    │   - 方法区(Method Area,JDK8+ 元空间 Metaspace)│
    └───────────────────────────────┘

    2. 各区域详细说明

    2.1 程序计数器(Program Counter Register)

    • 线程私有

    • 保存当前线程执行的字节码行号指示器(下一条要执行的指令地址)。

    • 如果执行的是本地方法(Native),计数器值为 undefined

    • 不会发生 OutOfMemoryError


    2.2 Java 虚拟机栈(JVM Stack)

    • 线程私有

    • 每个方法执行时都会创建一个 栈帧(Stack Frame) :

      • 局部变量表

      • 操作数栈

      • 动态链接

      • 方法出口

    • 深度递归或方法嵌套过多会 StackOverflowError

    • 如果 JVM 栈可动态扩展,扩展失败会 OutOfMemoryError


    2.3 本地方法栈(Native Method Stack)

    • 与 JVM 栈类似,但为 Native 方法服务(JNI 调用 C/C++)。

    • 可能抛 StackOverflowError 或 OutOfMemoryError


    2.4 堆(Heap)

    • 线程共享

    • 存放所有对象实例和数组,是 垃圾收集(GC) 管理的主要区域。

    • 逻辑上分为:

      新生代(Young Generation)
        - Eden
        - Survivor 0 / Survivor 1
      老年代(Old Generation)
    • 特点:

      • 新生代对象存活时间短,频繁 Minor GC。

      • 老年代对象存活时间长,Major/Full GC。

    • 堆不够会抛 OutOfMemoryError: Java heap space


    2.5 方法区(Method Area)

    • 线程共享

    • 存储已加载的类信息、常量、静态变量、即时编译后的代码等。

    • JDK8 之前:在堆的永久代(PermGen)

    • JDK8 之后:用 元空间(Metaspace) 存储在本地内存中。

    • 常见 OOM:

      • JDK7 及以前:OutOfMemoryError: PermGen space

      • JDK8+:OutOfMemoryError: Metaspace


    3. 常见内存错误类型

    错误

    原因

    StackOverflowError

    递归过深 / 方法调用层级过多(栈空间耗尽)

    OutOfMemoryError: Java heap space

    堆空间不足

    OutOfMemoryError: GC overhead limit exceeded

    GC 占用时间过长但回收效果差

    OutOfMemoryError: Metaspace

    类元信息占用过多(JDK8+)

    OutOfMemoryError: Direct buffer memory

    NIO 直接内存不足

    OutOfMemoryError: unable to create new native thread

    系统线程数达到上限


    4. JVM 调优常用参数

    # 堆设置
    -Xms512m      # 初始堆
    -Xmx1024m     # 最大堆
    
    # 新生代
    -Xmn256m      # 新生代大小
    
    # 元空间(JDK8+)
    -XX:MetaspaceSize=128m
    -XX:MaxMetaspaceSize=256m
    
    # 栈大小
    -Xss1m        # 每个线程的栈大小
    
    # GC 日志(JDK9-)
    -XX:+PrintGCDetails
    -XX:+PrintGCTimeStamps

    5. 总结口诀

    三私有:PC 寄存器、JVM 栈、本地方法栈
    两共享:堆、方法区(元空间)
    GC 主要盯堆,类信息在方法区,递归爆栈找 JVM 栈

    一、运行期注解 + AOP(Spring 场景最常用)

    1) 定义注解

    import java.lang.annotation.*;
    
    @Documented
    @Target({ElementType.METHOD})          // 作用在方法(也可 CLASS / FIELD / PARAMETER 等)
    @Retention(RetentionPolicy.RUNTIME)    // 运行期可见:AOP/反射才能拿到
    public @interface AuditLog {
        String value() default "";
        boolean saveArgs() default true;
    }

    2) 在业务方法上使用

    @Service
    public class OrderService {
        @AuditLog(value = "创建订单", saveArgs = true)
        public String createOrder(String userId, int amount) {
            // ... 业务逻辑
            return "OK";
        }
    }

    3) 用 AOP 拦截并处理

    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.stereotype.Component;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    @Slf4j
    @Aspect
    @Component
    public class AuditLogAspect {
    
        // 按“注解”为切点:标了 @AuditLog 的方法都会被拦截
        @Around("@annotation(AuditLog)")
        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            long start = System.currentTimeMillis();
    
            Method method = ((MethodSignature) pjp.getSignature()).getMethod();
            AuditLog anno = method.getAnnotation(AuditLog.class);
    
            Object ret;
            Throwable err = null;
            try {
                ret = pjp.proceed(); // 执行目标方法
                return ret;
            } catch (Throwable e) {
                err = e;
                throw e;
            } finally {
                long cost = System.currentTimeMillis() - start;
                String argsStr = anno.saveArgs() ? Arrays.toString(pjp.getArgs()) : "<hidden>";
                log.info("AUDIT | {} | method={} | args={} | cost={}ms | ok={}",
                        anno.value(), method.getName(), argsStr, cost, err == null);
                // 这里也可以:落库、发MQ、埋点、告警……
            }
        }
    }

    4) 开启 AOP(Spring Boot 通常已开启)

    @SpringBootApplication
    @EnableAspectJAutoProxy(exposeProxy = true) // 可选
    public class App {}

    到这里,你已经完成了自定义注解 + AOP 织入:谁打了 @AuditLog,谁就触发你的横切逻辑(日志、鉴权、限流、事务补偿等都可这样做)。


    二、无 Spring 时的运行期反射处理(轻量示例)

    public class Runner {
        public static void main(String[] args) throws Exception {
            Method m = OrderService.class.getMethod("createOrder", String.class, int.class);
            AuditLog anno = m.getAnnotation(AuditLog.class);
            if (anno != null) {
                System.out.println("Will audit: " + anno.value());
            }
            // 直接反射调用
            OrderService s = new OrderService();
            m.invoke(s, "u1", 100);
        }
    }

    适合简单场景;若要“全局拦截”,仍建议用 AOP 或代理。


    三、进阶:常见注解型需求模板

    1. 权限校验 @RequireRole("ADMIN")
      在 AOP 里先拿到当前用户角色,不满足则抛 AccessDeniedException

    2. 分布式锁 @DistributedLock(key="#orderId")
      AOP 中解析 SpEL → Redis/Tair 加锁 → proceed() → finally 解锁。

    3. 限流 @RateLimit(qps=100)
      AOP 里用令牌桶/滑动窗口,超限直接拒绝或降级。

    4. 缓存 @Cacheable(key="#id", ttl=300)
      AOP:先查缓存 → 命中直接返回;未命中执行方法并写缓存。

    以上都只是把“横切关注点”抽象成注解 + AOP 逻辑。


    四、编译期注解处理(JSR 269,代码生成/校验)

    如果你需要在编译期处理(如生成代码、做静态校验),用注解处理器:

    @SupportedAnnotationTypes("com.example.AutoDto")
    @SupportedSourceVersion(SourceVersion.RELEASE_17)
    public class AutoDtoProcessor extends AbstractProcessor {
        @Override
        public boolean process(Set<? extends TypeElement> annos, RoundEnvironment env) {
            for (Element e : env.getElementsAnnotatedWith(AutoDto.class)) {
                // 读取元素信息,生成源文件/校验
                // processingEnv.getFiler().createSourceFile("...");
            }
            return true;
        }
    }

    注册到 META-INF/services/javax.annotation.processing.Processor,编译时自动执行(类似 Lombok、MapStruct 的机制)。


    五、易踩坑 & 建议

    • @Retention 必须是 RUNTIME(运行期要读到注解)。

    • 代理限制:Spring AOP 基于代理,final 类/方法、private 方法不会被代理;需要可以用 CGLIB(默认对无接口类用 CGLIB),或换 AspectJ LTW

    • 自调用失效:同类内部方法互调不会过代理 → 用 AopContext.currentProxy() 或拆分到另一个 Bean。

    • 作用范围@Target 选对(方法、类、字段、参数)。

    • 性能:高频 AOP 里少用重度反射,缓存 Method/Annotation 元数据。

    • 顺序:多个切面需要可控顺序 → @Order

    在 Spring Bean 生命周期 里,AOP 不是单独的一个阶段,而是通过 BeanPostProcessor(特别是 AbstractAutoProxyCreator 及其子类) 在 初始化阶段之后 把代理逻辑织入的。

    我给你按顺序标一下 AOP 发生的位置:


    1. Bean 生命周期回顾(关键位置标记)

    1. 实例化 Bean
    2. 属性注入
    3. Aware 接口回调
    4. BeanPostProcessor.postProcessBeforeInitialization()
    5. 初始化方法(afterPropertiesSet / init-method)
    6. BeanPostProcessor.postProcessAfterInitialization()  ← ★ AOP 在这里织入
    7. Bean 就绪可用(可能是代理对象)
    8. 销毁阶段

    2. 为什么 AOP 在 AfterInitialization

    • Spring AOP 本质是 JDK 动态代理 / CGLIB 代理

    • 代理对象必须在 Bean 完成初始化后 才能替换原对象(确保依赖注入和初始化逻辑已经执行)。

    • postProcessAfterInitialization() 返回的不是原始 Bean,而是一个 代理对象,里面包了目标 Bean 和切面逻辑。


    3. 工作流程简述

    1. 容器启动时加载所有 Bean 定义。

    2. 创建 Bean → 执行依赖注入。

    3. 调用 BeanPostProcessor

    • 如果是 AbstractAutoProxyCreator 及子类,会检查该 Bean 是否匹配切面(Advisor)。

    • 如果匹配,就用 JDK Proxy(接口)或 CGLIB Proxy(类)生成代理。

  • 代理对象放回容器,后续你拿到的就是这个代理对象。

  • 调用方法时,代理会先执行切面逻辑,再调用原方法。


  • 4. 代码证明

    @Component
    public class DemoService {
        public void hello() {
            System.out.println("Hello method executed");
        }
    }
    
    @Aspect
    @Component
    public class LogAspect {
        @Before("execution(* com.example.DemoService.*(..))")
        public void before() {
            System.out.println("Before advice");
        }
    }

    启动后:

    @Autowired
    DemoService demoService;
    
    @PostConstruct
    public void test() {
        System.out.println(demoService.getClass()); 
        // class com.example.DemoService$$EnhancerBySpringCGLIB$$...
    }

    说明 Spring 返回的是代理类,而不是原始类。


    ✅ 结论
    Spring AOP 是通过 BeanPostProcessor 在初始化后的后置处理阶段 创建代理对象实现的。
    所以 AOP 织入点 在生命周期的 postProcessAfterInitialization() 。

    1. 生命周期总流程(简化版)

    1. 实例化(Instantiation)

    • Spring 通过反射(构造函数/工厂方法)创建 Bean 实例。

  • 属性赋值(Populate Properties)

    • 给 Bean 的属性注入依赖(依赖注入阶段)。

  • BeanNameAware / BeanFactoryAware / ApplicationContextAware 回调

    • 如果 Bean 实现了这些接口,会被注入对应的上下文信息。

  • BeanPostProcessor(前置处理)

    • 所有 BeanPostProcessor.postProcessBeforeInitialization() 调用。

  • 初始化(Initialization)

    • 如果实现了 InitializingBean.afterPropertiesSet(),会调用。

    • 如果配置了 init-method,也会执行。

  • BeanPostProcessor(后置处理)

    • 调用 BeanPostProcessor.postProcessAfterInitialization(),可替换或增强 Bean(如 AOP)。

  • 使用阶段

    • Bean 被应用调用。

  • 销毁(Destruction)

    • 如果实现了 DisposableBean.destroy(),会调用。

    • 如果配置了 destroy-method,也会执行。

    • 容器关闭时:


    2. 生命周期详细阶段(Spring IOC 内部执行顺序)

    [1] Bean实例化(Constructor / Factory)
    [2] 属性注入(Dependency Injection)
    [3] Aware接口回调(BeanNameAware / BeanFactoryAware / ApplicationContextAware)
    [4] BeanPostProcessor 前置处理(postProcessBeforeInitialization)
    [5] 初始化:
        ├── afterPropertiesSet() (InitializingBean接口)
        └── 自定义 init-method
    [6] BeanPostProcessor 后置处理(postProcessAfterInitialization)
    [7] Bean 可用(被应用调用)
    [8] 容器关闭:
        ├── destroy() (DisposableBean接口)
        └── 自定义 destroy-method

    3. 常用扩展点

    扩展点

    时机

    作用

    BeanNameAware

    属性注入后

    获取 Bean 名称

    BeanFactoryAware

    属性注入后

    获取 BeanFactory

    ApplicationContextAware

    属性注入后

    获取 ApplicationContext

    BeanPostProcessor

    初始化前/后

    修改 Bean 或包装代理对象

    InitializingBean

    初始化阶段

    自定义初始化逻辑

    DisposableBean

    销毁阶段

    自定义销毁逻辑


    4. 代码示例

    @Component
    public class MyBean implements BeanNameAware, InitializingBean, DisposableBean {
    
        @Override
        public void setBeanName(String name) {
            System.out.println("BeanNameAware: " + name);
        }
    
        @Override
        public void afterPropertiesSet() {
            System.out.println("InitializingBean: 初始化逻辑");
        }
    
        @Override
        public void destroy() {
            System.out.println("DisposableBean: 销毁逻辑");
        }
    }

    BeanPostProcessor 示例:

    @Component
    public class MyBeanPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) {
            System.out.println("前置处理: " + beanName);
            return bean;
        }
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) {
            System.out.println("后置处理: " + beanName);
            return bean;
        }
    }

    5. 总结口诀

    实例化 → 注入属性 → Aware回调 → 前置处理 → 初始化 → 后置处理 → 使用 → 销毁

    Spring 在 Java 领域里指的是 Spring Framework,它是一个开源的轻量级 Java 企业级开发框架,最初是为了解决 企业级开发中过度复杂的配置与依赖管理 问题,现在已经发展成一个完整的生态体系(Spring Boot、Spring Cloud、Spring Data 等)。

    我帮你分成 核心概念 → 核心模块 → 工作原理 → 常用子项目 → 学习建议 来讲。


    1. 核心概念

    • IoC(控制反转)

      • 把对象的创建与依赖管理交给 Spring 容器,而不是自己 new

      • 常用方式:XML 配置、Java 注解(@Component@Autowired)、Java Config。

    • DI(依赖注入)

      • IoC 的实现方式,通过构造器注入、Setter 注入或字段注入,把依赖自动注入到类中。

    • AOP(面向切面编程)

      • 把通用功能(如日志、事务、安全、缓存)从业务逻辑中分离,通过切面统一织入。

    • 声明式事务

      • 通过 @Transactional 注解实现事务管理,不必手写 begin/commit/rollback

    • 模块化

      • 核心容器、数据访问、Web、AOP、消息、测试等模块可以按需引入。


    2. 核心模块

    模块

    功能

    spring-core

    核心容器与 IoC/DI 实现

    spring-beans

    Bean 定义与管理

    spring-context

    IoC 容器高级特性(事件发布、国际化等)

    spring-aop

    面向切面编程实现

    spring-tx

    事务管理

    spring-jdbc

    简化 JDBC 数据访问

    spring-web

    支持 Web 应用开发(Servlet API 集成)

    spring-webmvc

    MVC 框架(Spring MVC)


    3. 工作原理(简化流程)

    1. 容器启动

    • 加载配置(XML/注解/JavaConfig)。

    • 扫描包路径下的组件类(@Component@Service@Controller)。

  • Bean 创建

    • 按定义实例化 Bean(构造函数、工厂方法)。

    • 执行依赖注入(DI)。

  • AOP 织入

    • 匹配切面定义,把代理对象注入到容器。

  • 应用运行

    • 控制器接收请求(Spring MVC)。

    • 调用业务层与数据访问层。

  • 销毁阶段

    • 容器关闭,调用 Bean 的销毁方法。


    4. 常用子项目

    • Spring Boot
      简化 Spring 应用开发,内置 Tomcat/Jetty,无需繁琐 XML 配置。

    • Spring Cloud
      微服务框架,提供注册中心、配置中心、网关、负载均衡等。

    • Spring Data
      统一数据访问方式,支持 JPA、MongoDB、Elasticsearch 等。

    • Spring Security
      权限与认证框架。

    • Spring Batch
      批处理框架,适合处理大批量数据任务。


    5. 学习建议

    1. 先学 Spring Core + Spring MVC,掌握 IoC、DI、AOP、MVC 流程。

    2. 过渡到 Spring Boot,理解自动配置与约定优于配置。

    3. 了解 Spring Data / Spring Security 等常用模块。

    4. 最后学习 Spring Cloud,理解分布式与微服务。

    1. 原型(prototype)是什么

    在 JavaScript 里:

    • 每个函数(除了箭头函数)在创建时,都会自动获得一个 prototype 属性(是一个对象)。

    • 通过 new 调用函数创建的实例,会有一个 隐藏属性[[Prototype]](大多数浏览器可用 __proto__ 访问),它指向这个构造函数的 prototype 对象。

    • 这个 prototype 对象可以挂载方法或属性,供所有实例共享。

    示例:

    function Person(name) {
      this.name = name;
    }
    Person.prototype.sayHi = function() {
      console.log(`Hi, I'm ${this.name}`);
    };
    
    const p1 = new Person('Alice');
    const p2 = new Person('Bob');
    
    p1.sayHi(); // Hi, I'm Alice
    p2.sayHi(); // Hi, I'm Bob
    console.log(p1.sayHi === p2.sayHi); // true(共享方法)

    2. 原型链(prototype chain)是什么

    • 当你访问一个对象的属性时,JS 会先在对象自身查找,如果找不到,就沿着 [[Prototype]] 继续查找。

    • 这种对象通过原型连接起来形成的链式结构,就是 原型链

    • 查找顺序:

      对象自身 → 对象的原型 → 原型的原型 → … → Object.prototype → null

    示例:

    console.log(p1.__proto__ === Person.prototype); // true
    console.log(Person.prototype.__proto__ === Object.prototype); // true
    console.log(Object.prototype.__proto__); // null

    3. 原型链查找过程

    p1.toString();

    查找过程:

    1. 在 p1 自身找 toString → 找不到

    2. 在 Person.prototype 找 → 找不到

    3. 在 Object.prototype 找到 → 调用这个方法

    4. 找不到时返回 undefined


    4. __proto__ vs prototype

    属性

    属于谁

    作用

    prototype构造函数

    定义实例共享的属性/方法

    __proto__实例对象

    指向创建它的构造函数的 prototype


    5. 使用场景

    1. 方法共享:节省内存,避免每个实例都生成一份相同方法。

    2. 继承:通过修改 prototype 或 __proto__ 实现继承链。

    3. 多层继承:子类原型指向父类实例或父类原型,形成多层原型链。

    继承示例:

    function Animal(name) {
      this.name = name;
    }
    Animal.prototype.run = function() {
      console.log(`${this.name} is running`);
    };
    
    function Dog(name, breed) {
      Animal.call(this, name);
      this.breed = breed;
    }
    Dog.prototype = Object.create(Animal.prototype); // 继承
    Dog.prototype.constructor = Dog;
    
    Dog.prototype.bark = function() {
      console.log(`${this.name} barks`);
    };
    
    const d = new Dog('Tommy', 'Bulldog');
    d.run();  // 来自 Animal
    d.bark(); // 自己的

    6. 总结口诀

    • 原型:对象的公共属性/方法存储地(构造函数的 prototype)。

    • 原型链:对象属性查找的路径(沿 __proto__ 向上找)。

    • constructor:原型对象上默认有个 constructor 指回构造函数。

    1. 核心区别对比

    特性

    varletconst
    作用域

    函数作用域(function scope)

    块作用域(block scope)

    块作用域(block scope)

    变量提升

    会提升到作用域顶部,值为 undefined

    会提升,但处于 暂时性死区(TDZ) ,未初始化前不能访问

    同 let

    重复声明

    允许在同一作用域重复声明

    不允许

    不允许

    重新赋值

    允许

    允许

    不允许

    (引用类型可修改内部值)

    初始化

    可选

    可选

    必须初始化

    2. 示例

    2.1 作用域

    if (true) {
      var a = 1;
      let b = 2;
      const c = 3;
    }
    console.log(a); // ✅ 1(var 是函数作用域)
    console.log(b); // ❌ ReferenceError
    console.log(c); // ❌ ReferenceError

    2.2 变量提升

    console.log(x); // undefined(声明提升)
    var x = 5;
    
    console.log(y); // ❌ ReferenceError(暂时性死区)
    let y = 6;

    2.3 const 引用类型

    const arr = [1, 2, 3];
    arr.push(4); // ✅ 可以修改内容
    // arr = [5, 6]; // ❌ 不能重新赋值引用

    3. 使用建议

    1. **优先用 const**:默认不变的值保持不可变,防止误改。

    2. **需要重新赋值时用 let**:如循环变量、累加器等。

    3. **避免使用 var**:容易因变量提升和作用域问题导致 bug。

    4. 代码风格(ESLint 推荐):

    • 默认用 const

    • 仅在需要重新赋值时改用 let

    • 不使用 var


    4. 总结口诀

    const 定常量,let 定变量,var 尽量别用
    const / let 都是块级作用域,var 是函数作用域
    var 会提升,let / const 有“暂时性死区”保护

    1. px(像素,绝对单位)

    • 定义:屏幕上的一个物理像素点(或逻辑像素)。

    • 特点

      • 固定大小,不会随父元素或页面字体大小变化。

      • 在不同分辨率下可能显示效果差异(高分屏下 1px 实际会更细)。

    • 示例

      div {
        font-size: 16px; /* 始终是 16 像素 */
      }
    • 适用场景:需要绝对精确尺寸的元素(比如边框、图标)。


    2. em(相对单位,参考父元素字体大小)

    • 定义:相对父元素 font-size 的倍数。

    • 计算方式

      1em = 父元素 font-size 的大小
    • 特点

      • 会继承并叠加(嵌套时容易出现意外放大)。

      • 对排版和可读性有更好的适配性。

    • 示例

      .parent {
        font-size: 16px;
      }
      .child {
        font-size: 1.5em; /* 1.5 × 16px = 24px */
      }
    • 适用场景:希望跟随父级字体比例变化的情况(比如按钮内边距)。


    3. rem(相对单位,参考根元素字体大小)

    • 定义:相对 HTML 根元素 <html> 的 font-size

    • 计算方式

      1rem = html 元素的 font-size 大小
    • 特点

      • 不会受父元素影响(比 em 更稳定)。

      • 常用在响应式布局,配合媒体查询动态调整根字体大小。

    • 示例

      html {
        font-size: 16px;
      }
      div {
        font-size: 2rem; /* 2 × 16px = 32px */
      }
    • 适用场景:响应式字体、全局统一尺寸控制。


    4. 对比总结

    单位

    参考对象

    是否继承叠加

    常用场景

    px

    屏幕像素

    精确尺寸(边框、图片)

    em

    父元素 font-size

    随父元素缩放(按钮、段落)

    rem

    根元素 font-size

    响应式全局尺寸


    5. 实战技巧

    • 响应式布局推荐用 rem:只改 <html> 字体大小即可全局缩放。

    • 需要跟随父元素变化,用 em

    • 绝对固定的像素,直接用 px

    • 结合 JS 动态设置根字体大小,可以实现页面随屏幕宽度缩放:

      document.documentElement.style.fontSize = window.innerWidth / 10 + 'px';

    方法 1:Flex 布局(推荐)

    <div class="row">
      <div class="item">A</div>
      <div class="item">B</div>
      <div class="item">C</div>
    </div>
    .row {
      display: flex;         /* 开启 Flex 布局 */
    }
    .item {
      padding: 10px;
      background: lightblue;
      margin: 5px;
    }

    ✅ 优点:简单、响应式好、支持等分、对齐控制方便。
    ❌ 缺点:IE9 以下不支持。


    方法 2:display: inline-block

    <div class="item">A</div>
    <div class="item">B</div>
    <div class="item">C</div>
    .item {
      display: inline-block;  /* 改为行内块元素 */
      padding: 10px;
      background: lightgreen;
      margin: 5px;
    }

    ✅ 优点:兼容性好(IE8+)。
    ❌ 缺点:HTML 代码的空格会造成间距,可用 font-size: 0 消除。


    方法 3:浮动(float)

    <div class="item">A</div>
    <div class="item">B</div>
    <div class="item">C</div>
    <div style="clear: both;"></div> <!-- 清除浮动 -->
    .item {
      float: left;
      padding: 10px;
      background: pink;
      margin: 5px;
    }

    ✅ 优点:老浏览器通吃。
    ❌ 缺点:需要清除浮动,布局灵活性差。


    方法 4:CSS Grid

    <div class="row">
      <div class="item">A</div>
      <div class="item">B</div>
      <div class="item">C</div>
    </div>
    .row {
      display: grid;
      grid-auto-flow: column; /* 按列排列 */
    }
    .item {
      padding: 10px;
      background: lightcoral;
    }

    ✅ 优点:适合复杂布局。
    ❌ 缺点:老旧浏览器不支持。


    如果只是简单让 div 一行显示,现在主流是 Flex 或 inline-block
    我建议你用 Flex,以后加对齐、等分、换行都方便。

    1. 父容器属性(决定子元素排列方式)

    属性

    常用值

    作用

    displayflex

     / inline-flex

    开启 Flex 布局(inline-flex 不会独占一行)

    flex-directionrow

     / row-reverse / column / column-reverse

    主轴方向(水平/垂直/反向)

    flex-wrapnowrap

     / wrap / wrap-reverse

    是否换行及方向

    flex-flow<flex-direction> <flex-wrap>

    上面两个属性的简写

    justify-contentflex-start

     / flex-end / center / space-between / space-around / space-evenly

    沿主轴对齐方式

    align-itemsstretch

     / flex-start / flex-end / center / baseline

    交叉轴对齐方式(子项在单行中如何对齐)

    align-content

    同上

    多行时交叉轴的行与行之间的对齐(对多行布局才生效)


    2. 子元素属性(决定自身在 Flex 容器里的表现)

    属性

    作用

    order

    控制子项在容器中的排列顺序(数字越小越靠前)

    flex-grow

    剩余空间的放大比例(默认 0,不放大)

    flex-shrink

    空间不足时的缩小比例(默认 1,可缩小)

    flex-basis

    子项在分配空间前占据的主轴尺寸(默认 auto

    flexflex-grow flex-shrink flex-basis

     的简写

    align-self

    覆盖父容器 align-items 对当前子项的设置


    3. 常见组合示例

    3.1 水平垂直居中

    .parent {
      display: flex;
      justify-content: center; /* 主轴居中 */
      align-items: center;     /* 交叉轴居中 */
    }

    3.2 等宽布局

    .item {
      flex: 1; /* 等分剩余空间 */
    }

    3.3 固定 + 自适应

    .left {
      flex: 0 0 200px; /* 不放大、不缩小、固定 200px */
    }
    .right {
      flex: 1; /* 占剩余空间 */
    }

    4. 记忆小技巧

    • 主轴方向 → flex-direction

    • 主轴对齐 → justify-content

    • 交叉轴对齐 → align-items / align-content

    • 子项伸缩 → flex-grow / flex-shrink

    • 初始大小 → flex-basis

    • 覆盖父设置 → align-self


    如果你愿意,我可以帮你画一张 Flex 布局属性速查图,把主轴、交叉轴和这些属性的作用范围都标出来,一眼就能找到该用哪个属性。
    要我帮你画吗?这样记忆会更直观。

    display: block 是 CSS 布局的基础属性之一,用来让一个元素以 块级元素(block-level element) 的方式渲染。


    1. 作用

    当一个元素是 display: block 时,它会:

    1. 独占一行(不与其他元素并排显示,默认会换行)。

    2. 宽度默认占满父容器(可以通过 width 改变)。

    3. 可以设置宽高width / height 有效)。

    4. 上下可有 margin,而 inline 元素的上下 margin 通常不生效。


    2. 常见的 block 元素

    即使不写 display: block,以下标签默认就是块级:

    <div>, <p>, <h1>~<h6>, <section>, <article>, <header>, <footer>, <ul>, <ol>, <li>, <nav>, <form>, <table>

    3. 示例

    <style>
    .box {
      display: block;
      width: 200px;
      height: 80px;
      background: lightblue;
    }
    </style>
    
    <span class="box">我是块级元素</span>

    即使原本 <span> 是行内元素(inline),加了 display: block 后,也会变成块级,能设置宽高、独占一行。


    4. 常见用途

    • 把 行内元素(如 <a><span><img>)转换为块级,用来:

      • 让它支持宽高

      • 让它独占一行

      • 方便做盒子模型布局

    • 在响应式布局中切换显示方式(例如按钮在大屏并排,小屏用 display: block 堆叠)


    5. 对比

    特点

    block

    独占一行,可设置宽高

    inline

    不换行,宽高由内容决定,width/height 不生效

    inline-block

    不换行,但可设置宽高

    flex

     / grid

    容器布局模式


    响应式布局(Responsive Layout)就是让页面在不同设备、不同屏幕宽度下都能良好显示和交互,核心是布局会自动适配,而不是固定死的像素值。

    我给你分成 核心思路 → 常用技术 → 示例代码 → 实战建议 来讲。


    1. 核心思路

    1. 流式布局:用百分比、automin/max-width 等代替固定像素。

    2. 媒体查询(Media Queries) :针对不同屏幕宽度、分辨率,切换样式。

    3. 弹性/网格布局flex 和 grid 可以在不同空间下自动调整元素位置和大小。

    4. 响应式图片:不同分辨率加载不同尺寸的图片。

    5. 断点(Breakpoints)设计:设定几个关键屏宽(如 576px、768px、1024px、1200px)来调整布局。


    2. 常用技术

    技术

    作用

    百分比宽度

    容器和元素随屏幕大小变化

    max-width / min-width

    限制元素在不同设备下的最大/最小尺寸

    vh / vw

    按视口宽高比例设置尺寸

    媒体查询(@media)

    针对不同屏宽、设备应用不同样式

    Flexbox

    让子元素在空间变化时自动伸缩、换行

    CSS Grid

    更复杂的二维布局,自适应调整行列

    响应式单位(rem / em)

    字体和间距随根字体大小缩放


    3. 示例代码

    3.1 基于媒体查询的响应式布局

    .container {
      display: flex;
      flex-wrap: wrap;
    }
    
    .item {
      flex: 1 1 100%; /* 默认占满一行 */
      padding: 10px;
      background: lightblue;
    }
    
    /* 平板以上屏幕 */
    @media (min-width: 768px) {
      .item {
        flex: 1 1 50%; /* 两列 */
      }
    }
    
    /* 桌面端 */
    @media (min-width: 1200px) {
      .item {
        flex: 1 1 25%; /* 四列 */
      }
    }

    3.2 百分比 + max-width

    img {
      max-width: 100%;
      height: auto;
    }

    3.3 使用 vw / vh

    .banner {
      height: 50vh; /* 高度占视口的一半 */
    }

    4. 实战建议

    1. 移动优先(Mobile First)

    • 默认写小屏样式 → 用 @media (min-width) 逐渐增强适配大屏。

  • 内容优先(Content First)

    • 断点设计以内容布局变化为依据,不要只看设备型号。

  • 组件化

    • 每个组件自己处理响应式,减少全局样式冲突。

  • 配合框架

    • Bootstrap、Tailwind CSS、Ant Design 都有现成的响应式工具类。

  • 测试

    • Chrome DevTools → Device Toolbar → 模拟不同屏幕查看效果。

    那就是局部容器用 display: flex,它的子元素才会受 flex 规则影响,其它区域还是普通布局。


    1. 局部 Flex 布局的核心

    .container {
      display: flex; /* 只让这个容器内的子元素参与 flex 布局 */
    }
    • Flex 的作用域:只影响设置了 display: flex 的元素的 直接子元素

    • 外部布局可以是普通的 blockinline-blockgrid 等。

    • 内部可以自由用 flex-directionjustify-contentalign-items 等属性控制排列。


    2. 示例:局部区域水平垂直居中

    <div class="header">普通头部</div>
    
    <div class="flex-box">
      <div class="item">A</div>
      <div class="item">B</div>
      <div class="item">C</div>
    </div>
    
    <div class="footer">普通底部</div>
    .flex-box {
      display: flex;                /* 开启局部 flex */
      justify-content: center;      /* 水平居中 */
      align-items: center;          /* 垂直居中 */
      height: 150px;
      background: #eee;
    }
    
    .item {
      padding: 10px 20px;
      background: lightblue;
      margin: 5px;
    }

    ✅ 只有 .flex-box 内的 A、B、C 受 flex 布局控制,header 和 footer 完全不受影响。


    3. 常见局部 Flex 用法

    (1) 局部水平排列

    .row {
      display: flex;
      gap: 10px;
    }

    (2) 局部垂直排列

    .column {
      display: flex;
      flex-direction: column;
      gap: 10px;
    }

    (3) 局部等宽布局

    .equal {
      display: flex;
    }
    .equal > div {
      flex: 1; /* 平分宽度 */
    }

    4. 小技巧

    • 嵌套布局:Flex 容器里还可以再嵌套 flex 容器,实现更复杂的布局。

    • 响应式:结合 flex-wrap: wrap 可以让子元素在小屏自动换行。

    • 局部与全局混搭:上层可以是 Grid,某些区域再用 flex 局部控制排列。

    1. XXL-JOB 是什么

    • 一个 分布式任务调度平台(类似轻量级定时任务中心)。

    • 支持任务可视化管理、动态修改 Cron、失败重试、分片广播等功能。

    • 核心由两部分组成:

    1. 调度中心(xxl-job-admin) :管理任务、分配执行器。

    2. 执行器(xxl-job-executor) :你的业务服务,接收调度中心的调度并执行任务。


    2. 部署调度中心(xxl-job-admin)

    1. 下载源码

      git clone https://github.com/xuxueli/xxl-job.git
    2. 导入 SQL

    • 找到 xxl-job/doc/db/tables_xxl_job.sql

    • 在 MySQL 中执行,初始化调度中心数据库表。

  • 修改配置

    • 编辑 xxl-job-admin/src/main/resources/application.properties

      spring.datasource.url=jdbc:mysql://127.0.0.1:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
      spring.datasource.username=root
      spring.datasource.password=123456
      
      xxl.job.login.username=admin
      xxl.job.login.password=123456
  • 启动调度中心

    cd xxl-job-admin
    mvn spring-boot:run

    访问 http://localhost:8080/xxl-job-admin(默认 admin / 123456 登录)。


  • 3. 集成执行器(在你的 Spring Boot 项目中)

    1. 引入依赖

      <dependency>
          <groupId>com.xuxueli</groupId>
          <artifactId>xxl-job-core</artifactId>
          <version>2.4.0</version>
      </dependency>
    2. 配置 application.yml

      xxl:
        job:
          admin:
            addresses: http://127.0.0.1:8080/xxl-job-admin  # 调度中心地址
          executor:
            appname: my-job-executor
            address:
            ip:
            port: 9999
            logpath: /data/applogs/xxl-job/jobhandler
            logretentiondays: 30
          accessToken:
    3. 注册执行器配置类

      @Configuration
      public class XxlJobConfig {
          @Bean
          public XxlJobSpringExecutor xxlJobExecutor() {
              XxlJobSpringExecutor executor = new XxlJobSpringExecutor();
              executor.setAdminAddresses("http://127.0.0.1:8080/xxl-job-admin");
              executor.setAppname("my-job-executor");
              executor.setPort(9999);
              executor.setLogPath("/data/applogs/xxl-job/jobhandler");
              executor.setLogRetentionDays(30);
              return executor;
          }
      }
    4. 编写任务 Handler

      import com.xxl.job.core.handler.annotation.XxlJob;
      import org.springframework.stereotype.Component;
      
      @Component
      public class DemoJobHandler {
      
          @XxlJob("demoJobHandler")
          public void execute() throws Exception {
              System.out.println("XXL-JOB 任务执行:" + System.currentTimeMillis());
          }
      }

    4. 在调度中心创建任务

    1. 登录调度中心 → 任务管理 → 新增任务

    2. 选择执行器 my-job-executor

    3. 填写 JobHandlerdemoJobHandler

    4. 配置 Cron 表达式(如 0/10 * * * * ? 表示每 10 秒执行一次)

    5. 保存 → 启动任务


    5. 常用功能

    • 分片广播:适合分布式批量处理任务(执行器数 = 分片数)。

    • 失败重试:任务失败后按配置次数重试。

    • GLUE 模式:支持在线编写 Java 代码任务(无需部署)。

    • 路由策略:如 FIRST、RANDOM、ROUND、CONSISTENT_HASH 等。

    • 日志查看:执行日志可在页面直接查看。


    6. XXL-JOB 优势

    • Web 界面管理任务,支持动态修改。

    • 集群调度,支持多执行器部署。

    • 支持分布式任务分片与路由策略。

    • 任务失败报警(邮件/钉钉/企业微信)。

    一、核心思路(最简流程)

    1. 采集文档并分配 docID

    2. 规范化文本(小写、Unicode 归一、去标点/HTML、同义与词干可选)

    3. 分词(英文可按词边界;中文需用分词器)

    4. 遍历每个 (term, docID, position),写入字典:term -> postings

    5. 对每个 term 的 Posting List 按 docID 升序,合并重复,计算 tf,可选保存 positions

    6. 持久化:写出 词典(term→文件偏移、DF 等)与 倒排表文件(docID 列表、tf、positions),并进行压缩(gap+可变长编码)

    7. 额外表:docID -> {length, stored fields} 用于排名与展示

    二、极简可跑的 Python 示例(含位置与 BM25 所需统计)

    from collections import defaultdict
    import re
    import math
    
    def tokenize(text):
        # 英文示例:小写+简单切词;中文请接入 jieba/HanLP 等
        text = re.sub(r"<[^>]+>", " ", text)       # 去HTML
        text = re.sub(r"[^a-zA-Z0-9]+", " ", text) # 非字母数字
        return [w for w in text.lower().split() if w]
    
    def build_inverted_index(docs):
        """
        docs: dict[int, str]  e.g. {1: "Text...", 2: "More..."}
        return:
          index: dict[term] -> list of {"doc":id, "tf":n, "pos":[...]}
          df: dict[term] -> doc_freq
          doclen: dict[docID] -> length_in_tokens
        """
        postings = defaultdict(lambda: defaultdict(list))  # term -> docID -> positions
        doclen = {}
        for doc_id, text in docs.items():
            tokens = tokenize(text)
            doclen[doc_id] = len(tokens)
            for i, tok in enumerate(tokens):
                postings[tok][doc_id].append(i)
    
        index, df = {}, {}
        for term, doc_pos in postings.items():
            plist = []
            for d, pos_list in sorted(doc_pos.items()):  # 按 docID 排序
                plist.append({"doc": d, "tf": len(pos_list), "pos": pos_list})
            index[term] = plist
            df[term] = len(plist)
        return index, df, doclen
    
    # 示例与查询(布尔与BM25打分)
    docs = {
        1: "Elasticsearch builds on Lucene. Lucene uses inverted index.",
        2: "Inverted index enables fast full-text search.",
        3: "Lucene segments are immutable; merging reduces small files."
    }
    index, df, doclen = build_inverted_index(docs)
    N = len(docs); avgdl = sum(doclen.values())/N
    
    def bm25_score(query_terms, k1=1.2, b=0.75):
        # 简化版 BM25:不做查询词频统计
        scores = defaultdict(float)
        for term in query_terms:
            if term not in index: 
                continue
            ni = df[term]
            idf = math.log((N - ni + 0.5) / (ni + 0.5) + 1)
            for item in index[term]:
                d = item["doc"]; tf = item["tf"]; dl = doclen[d]
                denom = tf + k1*(1 - b + b*dl/avgdl)
                scores[d] += idf * (tf*(k1+1) / denom)
        return sorted(scores.items(), key=lambda x: x[1], reverse=True)
    
    print(bm25_score(tokenize("lucene inverted index")))

    要点:

    • index[term] 的 posting 保存 docID/tf/positions,满足布尔/短语/临近搜索与排序。

    • 线上应将 positions 可选化(节省空间)。

    三、工程化与性能优化清单

    1) 词典与倒排存储

    • 词典(term→{df, cf, offset, length}})放内存或 mmap;term 查找可用 FST/前缀树。

    • 倒排:[docID_gap编码][tf][pos_gap...];docID/pos 用 gap(差分)+ 可变长(VarInt/VarByte/VByte)或 PForDelta、QMX 等压缩。

    • 按块(block)切分并加 跳跃表/skip pointers,加快并集/交集与跳跃。

    2) 写入路径

    • 采用 段(segment)不可变 策略:内存缓冲 → 定期 flush 生成小段 → 后台 merge 合段(减少小文件)。

    • 更新/删除:写新段与 删除标记(tombstone) ,在合并时清理;避免就地改写。

    3) 分词与规范化

    • 英文:lowercase、去停用词、词干化(Porter/Lemmatize,可按需)。

    • 中文:接入 jieba / HanLP / ICTCLAS / THULAC 等;保留位置以支持短语匹配。

    • 多语言:Unicode NFKC 归一、同义词扩展(离线字典或查询时扩展)。

    4) 查询执行

    • 先用倒排做候选集过滤,再用正排/DocValues 做精确筛选(数值/范围/日期)。

    • 排序:BM25/PL2 + 学习排序(LTR);需要 doclen/avgdl 等统计。

    • 并行:多核并行处理多个 term 或多个段;大规模用分片(shards)并行,协调节点合并 TopK。

    5) 大规模离线构建(MapReduce/Spark 思路)

    • Map:对每个文档输出 (term, (docID, pos))

    • Shuffle:按 term 分组并排序

    • Reduce:合并为有序 posting list,写出段文件,并记录词典偏移

    • 后续再做段级合并与压缩/索引块化
      (这正是搜索引擎和 ES/Lucene 的离线/在线混合套路)

    6) 评测与质量

    • 功能:AND/OR、短语、临近(slop)、高亮

    • 质量:MRR、NDCG@K、Recall/Precision

    • 性能:QPS、P99 延迟;索引大小、构建时长;合并对查询抖动的影响

    四、常见坑

    • 中文不分词 → 命中率极差;必须接入分词。

    • 未按 docID 排序与 gap 压缩 → 空间/速度都很差。

    • positions 全保留 → 指数级膨胀;仅在需要短语/临近检索时开启。

    • 频繁原地更新 → 复杂且慢;改用段不可变 + 合并。

    • 长文本字段 → 建议只索引必要字段;展示内容走存储字段或外部存储。

    五、如果你要“像 ES/Lucene 那样”

    • 段式不可变结构 + 后台合并(tiered merge)

    • 词典用 FST;posting 分块 + skip list

    • mmap 文件;doc values 做排序/聚合

    • 刷新(refresh)让新段可见,flush 写磁盘,translog 保持崩溃恢复

    1. ES 底层架构

    Elasticsearch 是基于 Lucene 的分布式搜索引擎,底层核心结构:

    Client
       ↓
    Coordinator Node(协调节点)
       ↓
    Primary Shard(主分片)
       ↓
    Replica Shard(副本分片)
       ↓
    Lucene Segment(倒排索引存储单元)
       ↓
    文件系统(底层存储)
    • Node:ES 集群中的单个实例。

    • Index:类似数据库的库。

    • Shard:索引分成的多个分片(Primary 主分片 + Replica 副本分片)。

    • Segment:Lucene 的最小搜索单元,底层不可变文件。

    • Cluster State:集群元数据(节点信息、索引信息等)。


    2. 存储原理(基于 Lucene)

    Elasticsearch 并不是直接存数据到数据库,而是用 倒排索引 来加速全文检索。

    倒排索引结构:

    term(关键词) → [doc1, doc3, doc7 ...]
    • 正排索引:doc → field 值(用于精确获取)。

    • 倒排索引:field 值(term) → doc 列表(用于搜索匹配)。

    写入过程(Indexing Flow):

    1. 文档 JSON 通过 Analyzer(分词器) 分解成 Token。

    2. Token 按字段构建倒排表,写入 Segment(不可变)。

    3. Segment 写入文件系统(默认基于 Lucene + mmap)。

    4. 写入前先进入 内存缓冲区(Index Buffer)→ translog(事务日志)保证宕机恢复。

    5. 定期 flush:生成新的 Segment 并清空缓冲区。

    6. 定期 merge:将多个小 Segment 合并成大 Segment(减少文件数,提高搜索效率)。


    3. 搜索原理

    搜索是 分布式 + 并行 的过程。

    查询流程(Search Flow):

    1. 协调节点(Coordinator Node)接收查询请求。

    2. 将请求转发给所有分片(Primary 或 Replica 都可)。

    3. 每个分片在自己的 倒排索引(Segment) 中执行查询。

    4. 分片返回 Top N 结果(docID + score)。

    5. 协调节点合并排序,返回最终结果。


    4. 底层优化机制

    4.1 存储优化

    • 不可变 Segment:写入快、搜索快,但会产生大量小文件 → 需要 merge

    • 内存映射(mmap) :Lucene 使用操作系统内存映射文件,提高 IO 性能。

    • 压缩存储:倒排表 + doc 值采用压缩(减少磁盘 & 提高缓存命中率)。

    4.2 查询优化

    • 跳表 / FST(Finite State Transducer) :优化 term 查找。

    • Bitset / Roaring Bitmap:快速标记匹配的 doc。

    • 倒排 + 正排结合:先倒排快速筛选,再正排精确过滤。

    4.3 分布式一致性

    • 写入:先写主分片,再写副本分片,保证最终一致性。

    • 刷新(refresh) :默认 1 秒刷新一次,使新文档可被搜索到(near real-time)。

    • 副本分片:既能提高可用性,又能分担读取压力。


    5. 关键点总结

    • ES 底层是 Lucene,核心是 倒排索引

    • Segment 不可变 → 写快、并发安全,但需要合并。

    • 搜索过程是 分布式并行,依赖 协调节点合并结果

    • 高性能来自:

      • 内存映射文件

      • 压缩索引

      • 跳表 / FST

      • 多分片并行


    WebSocket 是一种 全双工、长连接 的网络通信协议,常用于实时通信场景(比如聊天、推送、股票行情、游戏等)。


    1. 特点

    特性

    说明

    双向通信

    客户端和服务器都能主动发消息。

    长连接

    一次握手,持续保持连接,不必频繁建立 HTTP 请求。

    低延迟

    适合需要实时推送的场景。

    基于 TCP

    在 TCP 基础上定义了自己的帧格式,通常运行在 80/443 端口。


    2. 通信过程

    1. 客户端发起 HTTP 请求(带有 Upgrade: websocket 头),请求升级为 WebSocket。

    2. 服务器响应 101 状态码,表示协议切换成功。

    3. 握手完成后,双方可以通过 TCP 直接交换数据帧。

    4. 保持长连接,直到一方主动关闭。


    3. 浏览器端示例

    // 创建连接
    const socket = new WebSocket("ws://localhost:8080");
    
    // 连接成功
    socket.onopen = () => {
        console.log("WebSocket connected");
        socket.send("Hello Server");
    };
    
    // 接收消息
    socket.onmessage = (event) => {
        console.log("Message from server:", event.data);
    };
    
    // 连接关闭
    socket.onclose = () => {
        console.log("WebSocket closed");
    };
    
    // 错误处理
    socket.onerror = (error) => {
        console.error("WebSocket error:", error);
    };

    4. Node.js 服务器端示例

    const WebSocket = require('ws');
    
    const wss = new WebSocket.Server({ port: 8080 });
    
    wss.on('connection', (ws) => {
        console.log('Client connected');
    
        ws.on('message', (message) => {
            console.log('Received:', message);
            ws.send(`Echo: ${message}`);
        });
    
        ws.on('close', () => {
            console.log('Client disconnected');
        });
    });

    5. 常见应用场景

    • 即时聊天(WhatsApp、微信网页版)

    • 实时推送(股票、体育比分)

    • 多人在线游戏

    • 协同编辑(Google Docs 实时同步)

    Docker 是一个容器化平台,可以让你把应用和它的依赖打包在一个轻量级、可移植的容器里运行,避免“在我电脑上可以跑”的问题。


    1. 核心概念

    概念

    说明

    镜像(Image)

    应用运行所需的只读模板,比如一个包含 Ubuntu 系统 + Python 环境的打包文件。

    容器(Container)

    镜像的运行实例,可以启动、停止、删除。容器里的环境互相隔离。

    Docker Hub

    官方镜像仓库,你可以直接拉取别人做好的镜像,或者上传自己的镜像。

    Dockerfile

    用来定义镜像构建步骤的文件。


    2. 常用命令

    # 查看 Docker 版本
    docker --version
    
    # 拉取镜像
    docker pull nginx:latest
    
    # 运行容器(映射端口,后台运行)
    docker run -d -p 8080:80 nginx:latest
    
    # 查看正在运行的容器
    docker ps
    
    # 查看所有容器(包括已停止的)
    docker ps -a
    
    # 停止容器
    docker stop <容器ID或名字>
    
    # 删除容器
    docker rm <容器ID或名字>
    
    # 删除镜像
    docker rmi <镜像ID>
    
    # 构建镜像(在 Dockerfile 所在目录执行)
    docker build -t myapp:1.0 .
    
    # 进入容器终端
    docker exec -it <容器ID> /bin/bash

    3. Dockerfile 示例

    # 基础镜像
    FROM python:3.11
    
    # 设置工作目录
    WORKDIR /app
    
    # 复制项目文件到容器
    COPY . .
    
    # 安装依赖
    RUN pip install -r requirements.txt
    
    # 启动应用
    CMD ["python", "app.py"]

    构建并运行:

    docker build -t myapp .
    docker run -d -p 5000:5000 myapp

    4. Docker 使用优势

    • 跨平台一致性:一次构建,到处运行。

    • 轻量快速:比虚拟机启动快,资源占用少。

    • 易于分发:直接推送到镜像仓库即可分享。

    • 隔离性好:每个容器互不影响。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值