01cookie、session、token的区别,以及使用场景    (为什么要用cookie、session、token,什么时候用)

一、基本概念

  • 定义:由服务器发送给客户端(浏览器)的一小段数据,浏览器会自动保存并在后续请求中自动携带。
  • 存储位置:客户端(浏览器)。
  • 生命周期:可设置过期时间(持久 Cookie)或仅在当前会话有效(会话 Cookie)。
  • 安全性:可通过 HttpOnlySecureSameSite 等属性增强安全。

2. Session

  • 定义:服务器端保存的用户会话数据,通常通过一个唯一 ID(Session ID)与客户端关联。
  • 存储位置:服务器(如内存、Redis、数据库等)。
  • 关联方式:Session ID 一般通过 Cookie 传递(也可通过 URL 参数)。
  • 生命周期:由服务器控制,可设置超时时间。

3. Token(通常指 JWT)

  • 定义:一种自包含的令牌(如 JSON Web Token),包含用户信息和签名,无需服务器存储状态。
  • 存储位置:客户端(可存在 Cookie、localStorage、sessionStorage 等)。
  • 结构:Header + Payload + Signature,Payload 可包含用户 ID、角色、过期时间等。
  • 无状态:服务器无需保存 Token,只需验证签名即可。

二、核心区别对比

特性 Cookie Session Token (JWT)
存储位置 客户端 服务器 客户端
是否需要服务器存储 否(无状态)
自动携带 是(同域下自动随请求发送) 依赖 Cookie 或 URL 否(需手动添加到请求头)
安全性 中(易受 CSRF 攻击) 高(数据在服务端) 高(但需防 XSS)
跨域支持 困难(受同源策略限制)
困难 容易(适合前后端分离/跨域)
扩展性 差(依赖服务器状态) 差(需共享 Session) 好(天然支持分布式)
生命周期管理 可设过期时间 服务端控制 Token 内置过期时间

同源:
协议、域名、端口完全相同,即非跨域

三、为什么需要它们?

Web 协议 HTTP 是无状态的,每次请求都是独立的。为了实现“记住用户登录状态”、“个性化内容”等功能,就需要某种机制来维持状态或识别用户身份

  • Cookie:解决客户端如何携带标识的问题。
  • Session:解决服务器如何记住用户状态的问题。
  • Token:解决在无状态架构(如 RESTful API、微服务)中如何安全认证的问题。

四、使用场景建议

  • 传统 Web 应用(如 PHP、Java Web、Django 等服务端渲染应用)
  • 用户登录后跳转页面,保持登录状态
  • 对安全性要求高,且不涉及跨域
  • 例如:电商后台、企业管理系统

优点:简单、成熟、自动管理;
缺点:难以扩展到分布式系统,跨域麻烦。

使用 Token(如 JWT)的场景:

  • 前后端分离架构(React/Vue + RESTful API)
  • 移动端 App / 小程序 与后端通信
  • 微服务架构,需要无状态认证
  • 跨域或第三方集成(如 OAuth2)
    jti(JWT ID)是 JWT(JSON Web Token)中的一个可选标准声明字段,用于唯一标识一个具体的 Token 实例。它的核心作用是:让每个签发的Token 都有一个“身份证号”,便于后续追踪、吊销或审计。可以把jti储存在Redis中,作为黑名单

优点:无状态、易扩展、跨平台;
缺点:Token 一旦签发无法主动失效(除非引入黑名单),Payload 不宜过大。

⚠️ 注意事项

  • CSRF(跨站请求伪造):Cookie + Session 易受攻击,需用 SameSite=Strict/Lax 或 CSRF Token 防护。
  • XSS(跨站脚本):若 Token 存在 localStorage,可能被 JS 窃取;建议存入 HttpOnly Cookie 并配合 SameSite
  • 敏感信息:不要在 Cookie 或 Token Payload 中存放密码等敏感数据。

02.Redis能干点啥

Redis 的核心作用(按使用场景分类)

1. 缓存(Cache)—— 最常见用途

  • 目的:减轻数据库压力,提升系统响应速度。
  • 原理:将热点数据(如用户信息、商品详情、文章内容)存入 Redis,后续请求直接读缓存,避免查数据库。
  • 典型场景
    • Web 页面缓存
    • API 接口结果缓存
    • 数据库查询结果缓存(如 MyBatis + Redis)
  • 优势:读写性能极高(10万+ QPS),延迟微秒级。

    💡 示例:用户访问商品页 → 先查 Redis → 有则返回,无则查 DB 并写入 Redis。

2. 分布式会话存储(Session Store)

  • 问题:传统 Session 存在单机内存中,无法用于负载均衡下的多台服务器。
  • 解决方案:将 Session 数据(如用户登录状态)统一存到 Redis。
  • 效果:任意后端节点都能读取用户会话,实现无状态服务 + 水平扩展

    ✅ 常用于:Java(Spring Session + Redis)、Node.js、PHP 等 Web 应用。

3. 分布式锁(Distributed Lock)

  • 目的:在分布式系统中,保证多个进程/服务对共享资源的互斥访问。
  • 实现方式:利用 SET key value NX EX 命令(原子操作)。
  • 典型场景
    • 秒杀库存扣减
    • 防止重复提交订单
    • 定时任务只在一个节点执行

      ⚠️ 注意:需处理锁过期、续期、误删等问题(可结合 Redlock 或 Lua 脚本)。

4. 消息队列(Message Queue)—— 轻量级

  • 数据结构:使用 List(LPUSH + BRPOP)或 Stream(Redis 5.0+)。
  • 适用场景
    • 异步任务处理(如发邮件、日志收集)
    • 解耦生产者和消费者
  • 优点:简单、快速、无需引入 Kafka/RabbitMQ
  • 缺点:功能不如专业 MQ(无 ACK、持久化弱、无路由)

    ✅ 适合中小型项目或临时任务队列。

5. 计数器 & 限流(Rate Limiting)

  • 数据结构String(INCR)或 ZSet(滑动窗口)
  • 典型应用
    • 接口调用次数统计
    • 用户登录失败次数限制
    • 短信发送频率控制
    • 分布式 ID 生成(INCR)

      💡 示例:INCR user:123:login_attempts + EXPIRE 实现 5 分钟内最多试错 5 次。

6. 排行榜 & 实时统计(Leaderboard)

  • 数据结构Sorted Set(ZSet)
  • 原理:成员(member)+ 分数(score),自动排序。
  • 场景
    • 游戏积分榜
    • 热门文章 Top 10
    • 实时销售额排名

      ✅ 支持高效插入、更新、范围查询(如 ZREVRANGE)。

7. 发布/订阅(Pub/Sub)—— 实时通信

  • 模式:消息广播机制
  • 命令PUBLISH channel msg / SUBSCRIBE channel
  • 应用场景
    • 聊天室消息推送
    • 系统通知广播
    • 微服务间简单事件通知

      ⚠️ 缺点:消息不持久(订阅者离线会丢消息),不适合可靠消息场景。

8. 位图(Bitmap)与布隆过滤器(Bloom Filter)

  • Bitmap:用 bit 位存储状态,节省空间
    → 如:用户签到(SETBIT user:123:sign 20251110 1
  • Bloom Filter(需 RedisBloom 模块):
    → 高效判断“某元素可能存在”(如防止缓存穿透)

9. 地理位置(GEO)

  • 命令GEOADDGEORADIUS
  • 场景:附近的人、门店搜索
  • 底层:基于 Sorted Set + GeoHash

10. 作为 JWT 黑名单/白名单存储(认证控制)

  • 存储被吊销的 Token ID(jti
  • 控制用户登出、权限变更等实时生效

03.hashmap,hashtable,concurrenthashmap的区别

特性 HashMap Hashtable ConcurrentHashMap
线程安全 ❌ 不安全 ✅ 安全(全表锁) ✅ 安全(分段锁 / CAS + synchronized)
null 键/值 ✅ 允许一个 null key,多个 null value ❌ 不允许 null key 或 value ❌ 不允许 null key 或 value
底层结构 数组 + 链表 + 红黑树(JDK 8+) 数组 + 链表 数组 + 链表 + 红黑树(JDK 8+)
锁粒度 无锁 方法级 synchronized(整表锁) 分段锁(JDK 7)→ CAS + synchronized(JDK 8+,锁单个桶)
性能(并发) 最高(无锁) 最低(高并发下严重阻塞) 高(并发读不加锁,写只锁部分)
迭代器 fail-fast fail-fast 弱一致性(不会抛 ConcurrentModificationException)
继承关系 AbstractMap Dictionary(已废弃) AbstractMap

04rabbitmq消息有序性

保证同一业务实体(如订单ID、用户ID)的消息处理顺序,不同实体之间可并行处理。

🔑 核心思想:分区(Partitioning) + 单线程消费

原则 说明
1. 同一业务 ID 的消息必须进入同一个队列 通过 hash(业务ID) % N 路由到固定队列
2. 每个队列只由一个消费者(单线程)处理 避免并发打乱顺序
3. RabbitMQ 队列本身是 FIFO 入队顺序 = 出队顺序,天然保序

🛠️ 实现步骤

  1. 预创建多个队列(如 order.queue.0 ~ order.queue.3

  2. 生产者按业务 ID 路由

    1
    2
    int index = Math.abs(orderId.hashCode()) % queueCount;
    channel.basicPublish("", "order.queue." + index, ..., message);
  3. 每个队列绑定一个单线程消费者(禁止多线程并发消费同一队列)

  4. 消费者关闭 prefetch,手动 ACK

    1
    2
    channel.basicQos(1); // 每次只取1条
    // 处理成功后再 basicAck

⚠️ 关键前提(缺一不可)

环节 要求
生产者 同一业务 ID 的消息必须按序发送(避免并发发同一流消息)
Broker 队列 FIFO 特性保证入队顺序
消费者 单线程处理 + 不 requeue 到队首(失败应进死信队列)

05.什么时候用rabbitmq

1. 应用解耦(Decoupling)

  • 问题:模块 A 直接调用模块 B,B 挂了 → A 也失败。
  • RabbitMQ 方案:A 发消息到队列,B 异步消费。
  • 例子
    • 用户注册后,发邮件、发短信、写日志 → 各自独立消费
    • 订单创建后,通知库存、物流、积分系统

🎯 价值:系统间松耦合,故障隔离。

2. 异步处理(Asynchronous Processing)

  • 问题:用户点击“下单”,要等 2 秒(因为同步调支付、发短信…),体验差。
  • RabbitMQ 方案:下单成功后发消息,后台慢慢处理。
  • 效果:接口响应从 2s → 50ms。

🎯 价值:提升用户体验,降低请求链路延迟。

3. 流量削峰(Traffic Shaping / Peak Shaving)

  • 问题:秒杀活动瞬间 10 万请求,数据库扛不住。
  • RabbitMQ 方案:请求先入队列,消费者按能力匀速处理(比如每秒 1000 单)。
  • 注意:需配合限流 + 排队提示(“正在排队中…”)

🎯 价值:保护后端系统不被冲垮。

4. 最终一致性(Eventual Consistency)

  • 问题:分布式事务太重(如 Seata),但又需要数据一致。
  • RabbitMQ 方案:通过“事件驱动”实现补偿。
    • 例:扣款成功 → 发 PaymentSuccessEvent → 库存服务消费并减库存
    • 失败则重试 or 进死信队列人工干预

🎯 价值:轻量级实现跨服务数据同步。

5. 广播通知(Pub/Sub)

  • 问题:一个事件要通知多个系统(如“用户注销”要清空缓存、踢掉 WebSocket、记录审计日志)。
  • RabbitMQ 方案:用 Exchange + 多个 Queue 绑定,实现一对多广播。

🎯 价值:避免硬编码通知列表。

❌ 二、不适合使用 RbbitMQ 的场景

场景 原因 更佳选择
需要强顺序(全局有序) RabbitMQ 不天然支持 Kafka / RocketMQ
需要消息回溯(重复消费历史消息) 队列消费后即删除 Kafka(持久化日志)
超大消息(> 10MB) 内存压力大,性能差 文件存储 + 消息传 URL
低延迟要求(< 10ms) 网络 + Broker 开销 直接 RPC / gRPC
简单定时任务 杀鸡用牛刀 Quartz / XXL-JOB
高吞吐日志收集(每秒百万条) 性能不如专用系统 Kafka / Fluentd

06.秒杀实现

🎯 核心目标

  1. 高性能:扛住瞬时高并发(万级 QPS)
  2. 一致性:不能超卖(卖出去的 ≤ 实际库存)
  3. 可靠性:即使系统崩溃,数据最终一致

阶段 1️⃣:库存预热 —— 从数据库加载到 Redis

⏰ 时机

  • 秒杀开始前(比如提前 5 分钟、1 小时,或商品上架时)
  • 不能在秒杀请求到来时才查 DB(会压垮数据库)

💡 做什么

将商品的可售库存写入 Redis,作为秒杀的“令牌池”。

✅ 示例

1
2
3
-- 数据库商品表
SELECT id, stock FROM products WHERE id = 1001;
-- 结果:stock = 100
1
2
3
# 写入 Redis(Key 设计很重要!)
SET stock:1001 100 # 初始库存 100
EXPIRE stock:1001 3600 # 可选:设置过期时间

📌 关键点

  • Redis 中的库存 = 本次秒杀可售数量(可能是总库存,也可能是单独配置的秒杀库存)
  • 通常用 String 类型(方便 DECR 原子操作)

阶段 2️⃣:秒杀扣减 —— 在 Redis 中原子操作

🚀 用户点击“抢购”时

后端只做一件事:原子扣减 Redis 库存

1
2
3
4
5
6
7
8
9
10
11
12
// Java + Lettuce / Jedis 示例
String key = "stock:1001";
Long result = redis.decr(key); // 原子减 1

if (result >= 0) {
// ✅ 抢到了!返回“下单成功”
return "success";
} else {
// ❌ 库存不足(可能被别人抢光了)
redis.incr(key); // 可选:回滚(但通常不回滚,因为 result<0 说明已超卖)
return "sold out";
}

⚠️ 为什么安全?

  • DECR 是 Redis 单线程原子操作,天然防并发超卖
  • 即使 10 万人同时请求,Redis 也能保证最多 100 人成功

📌 注意
有些系统会用 GET + SET 或 Lua 脚本做更复杂的校验(如每人限购),但核心仍是原子性


阶段 3️⃣:异步落库 —— 订单创建 & 数据库库存扣减

📦 关键思想:“先抢资格,后建订单”

当 Redis 扣减成功后:

  1. 立即返回用户:“抢购成功,请支付”

  2. 同时发送一条消息到 RabbitMQ/Kafka,内容如:

    1
    2
    3
    4
    5
    {
    "userId": 123,
    "productId": 1001,
    "orderId": "O20251110123456"
    }

🔄 后台消费者处理消息(异步)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 消费者收到消息
void handleOrderMessage(OrderMsg msg) {
try {
// 1. 创建订单(写订单表)
orderDao.insert(new Order(msg.orderId, msg.userId, msg.productId));

// 2. 【可选】扣减数据库库存(如果 Redis 库存 ≠ DB 库存)
// 注意:这里要防重复消费!
if (!isProcessed(msg.orderId)) {
productDao.decreaseStock(msg.productId, 1);
markAsProcessed(msg.orderId);
}

// 3. 发通知、积分、日志等...
} catch (Exception e) {
// 失败则重试 or 进死信队列
requeueToDLQ(msg);
}
}

❓ 你可能会问:数据库库存还要扣吗?

这取决于你的库存模型:

方案 说明 是否需要扣 DB 库存
✅ 纯 Redis 库存 秒杀库存独立于 DB,DB 只存“总库存” ❌ 不需要(但需对账)
🔄 Redis + DB 双写 Redis 是缓存,DB 是权威 ✅ 需要(最终一致)
🧩 分离库存 DB 存“总库存”,Redis 存“秒杀专用库存” ❌ 秒杀库存扣完即可,DB 总库存定期同步

💡 主流做法
秒杀用独立库存(Redis),和普通销售库存分开。
秒杀结束后,再把“已售数量”同步到 DB 做汇总。

🔐 如何保证“不超卖 + 不少卖”?

风险 解决方案
Redis 扣了,但订单创建失败 消息队列重试 + 幂等处理(订单 ID 唯一)
重复消费导致多扣库存 消费者做幂等(如用订单 ID 去重)
Redis 崩溃丢失库存 持久化(AOF)+ 高可用(哨兵/集群)
DB 和 Redis 不一致 定时对账任务(补偿)

🛡️ 终极保障
即使中间环节出错,对账系统会在事后修复数据(比如发现 Redis 卖了 100,DB 只扣了 98 → 补扣 2)。

🌰 完整流程示例(以 100 件商品为例)

  1. 预热SET stock:1001 100
  2. 秒杀开始
    • 用户 A 请求 → DECR → 返回 99 ≥ 0 → 成功
    • 用户 B 请求 → DECR → 返回 98 ≥ 0 → 成功
    • 第 100 个用户 → DECR → 返回 0 ≥ 0 → 成功
    • 第 101 个用户 → DECR → 返回 -1 < 0 → 失败
  3. 异步:100 条消息进入 MQ,订单服务慢慢创建 100 个订单
  4. 结束:DB 中该商品“秒杀已售”字段更新为 100

✅ 总结:秒杀库存流转三原则

阶段 原则
预热 提前加载,绝不现场查 DB
扣减 Redis 原子操作,保证不超卖
落库 异步处理,通过 MQ 解耦 + 幂等保证最终一致

07.spring的事务传递性

方法 A 调用方法 B,A 有事务,B 也有 @Transactional
那 B 是加入 A 的事务?还是开启新事务?还是不支持事务?

答案由 @Transactional(propagation = ...) 中的 传播行为(Propagation) 决定。

✅ Spring 提供的 7 种事务传播行为

传播行为 说明 类比理解
REQUIRED(默认) 如果当前存在事务,则加入;否则新建一个事务 “有群就进,没群就建”
SUPPORTS 如果当前存在事务,则加入;否则以非事务方式执行 “有群就聊,没群就算了”
MANDATORY 必须在已有事务中执行,否则抛异常 “必须在群里说话,否则禁言!”
REQUIRES_NEW 无论当前是否有事务,都挂起当前事务(如果有),创建新事务 “我要单独开个小会,原会议暂停”
NOT_SUPPORTED 以非事务方式执行,如果当前有事务,则挂起它 “我现在不想开会,请暂停群聊”
NEVER 以非事务方式执行,如果当前有事务,则抛异常 “禁止在群里说话!”
NESTED 如果当前存在事务,则在嵌套事务内执行;否则行为同 REQUIRED “在大会议里开个子议题,可单独回滚”

Java 方法参数传递机制

Java 中所有的参数传递都是 值传递(Pass-by-Value),无论是基本类型还是引用类型。

  • 对于 基本类型(如 intboolean),传递的是实际值的副本。方法内部对参数的修改不会影响原始变量。
  • 对于 引用类型(如对象、数组),传递的是 对象引用的副本(即堆中对象地址的拷贝)。这个副本和原引用指向同一个对象(储存在堆中),因此通过它修改对象的属性,会影响原对象;但如果在方法内让引用指向一个新对象(比如 obj = new Xxx()),则不会影响原来的引用。

本质上,Java 没有‘按引用传递’。之所以对象属性能被修改,是因为多个引用共享同一个堆对象,而不是因为传参机制不同。”

在JVM中,Java 方法调用时,实参的“值”被复制到被调用方法的局部变量表中。

  • 基本类型的“值” = 数据本身(如 int=5
  • 引用类型的“值” = 对象在堆中的地址(即 Oop 指针,例如 0x000000076ab23450

JVM 不会传递变量本身,也不会传递对象本身,只传递“值”的副本。


© 2024 竹林听雨 使用 Stellar 创建
总访问 113 次 | 本页访问 26