专题一:分布式Id
分布式 ID 是什么?
分布式 ID 是一种在分布式系统中生成唯一标识符(ID)的方案,用于标识系统中各个实体(如用户、订单、商品等)。在分布式系统中,生成的 ID 需要具备全局唯一性和高效性。分布式 ID 通常用于数据库主键、消息队列标识、文件系统等场景中。
为什么使用分布式 ID?
分布式系统中,多个节点可能需要生成 ID,保证 ID 唯一性是一个关键问题。如果不采用合理的分布式 ID 生成机制,可能会导致冲突或重复的 ID,从而引发数据不一致、操作冲突等问题。分布式 ID 生成方案解决了以下几个问题:
- 全局唯一性:ID 在不同的机器、服务之间生成时必须确保全局唯一,避免重复。
- 高并发性能:分布式环境下,ID 生成需要支持高并发,避免成为系统的瓶颈。
- 去中心化:不依赖单一的中心节点生成 ID,避免单点故障和性能瓶颈。
- 时间有序:在某些场景中,ID 的生成顺序可能会对业务有一定要求。
分布式 ID 生成方案
目前有多种方案可以用于生成分布式 ID,下面是几种常见的实现方式:
方案一:UUID (通用唯一标识符)
UUID 是一种常见的 ID 生成方案,生成的 ID 长度通常为 128 位,具有全局唯一性。UUID 的特点是:
- 全局唯一性:UUID 使用算法确保在分布式环境下的唯一性。
- 不依赖于时间戳或者机器标识,生成方式完全独立。
- 存储和传输时需要较大的空间(32 字符的字符串,带有 4 个连字符)。
String id = UUID.randomUUID().toString();
// 5d1634ae-da40-43ec-9aad-f0e808a42886
方案二:数据库自增 ID
通过数据库的自增列生成 ID,适用于小规模的分布式系统,单台数据库服务器生成自增 ID,但是该方案的缺点是:
- 单点问题:如果数据库服务器出现故障,可能会导致 ID 生成不可用。
- 性能瓶颈:当并发量较高时,数据库可能成为性能瓶颈。
- 无法支持多节点:如果系统需要在多个节点上生成 ID,单一的数据库无法支持。
方案三:号段模式
号段模式的核心思想是,每个服务或每个节点从中心服务器申请一个 ID 号段(即一段连续的 ID),然后在本地生成 ID。当本地用完该号段时,再向中心服务器申请新的号段。这样,每个节点只需维护一个本地的计数器,并且可以通过持续请求号段来确保 ID 的唯一性。号段模式的特点:
- 高效性:生成 ID 的速度非常快,因为本地计数器递增,不涉及复杂的网络请求。
- 唯一性:每个服务都从中心服务申请独立的号段,因此 ID 在全局范围内是唯一的。
- 分布式支持:多个节点可以并行生成 ID,避免了中心化服务的瓶颈。
- 内存占用:每个节点都需要存储自己的号段和计数器,如果号段过大,可能会占用较多的内存。
- 号段耗尽问题:如果某个节点的号段用尽且无法及时从中心服务器获取新的号段,可能会出现 ID 重复或 ID 不可用的情况。
方案四:Snowflake (雪花算法)
Snowflake 是 Twitter 提出的分布式 ID 生成算法,生成的 ID 是一个 64 位的整数,具有全局唯一性和时间有序性。Snowflake 的 ID 结构如下:
专题二:分布式Session
分布式 Session 是什么?
分布式 Session 是指在分布式系统或集群环境中,将用户的会话数据(Session 数据)存储在一个共享、可访问的地方,而不是存储在单一的应用服务器或单个进程中。这样,无论用户请求被哪个应用服务器处理,都能访问到相同的会话数据,从而确保在分布式环境下,用户体验一致且不丢失会话信息。
为什么使用分布式 Session?
负载均衡:在分布式系统中,负载均衡器会将请求分发到不同的服务器节点上。如果每个服务器节点维护本地 Session,用户的请求可能会被路由到不同的节点,导致无法共享同一个 Session 数据。而使用分布式 Session,可以确保无论用户请求被哪个节点处理,都能访问到一致的会话数据。
- 可扩展性:随着应用的扩展,可能需要添加更多的服务器节点。如果每个节点都维护自己的 Session 数据,扩展会变得困难。而使用分布式 Session,可以通过共享存储来处理扩展,不需要对每个节点的 Session 进行迁移或同步。
- 高可用性:分布式 Session 通过集中存储,可以确保会话数据的高可用性。即使某个节点宕机,其他节点仍然可以从共享存储中读取会话数据,避免用户体验中断。
- 容错性:通过将 Session 数据存储在分布式存储系统(如 Redis)中,可以提供数据的冗余备份,从而增强系统的容错性。如果某个节点故障,Session 数据不会丢失,系统可以继续正常工作。
分布式 Session 的常见实现方案
方案一:Spring-Session
- 优点:数据持久化、易于管理、支持事务。
- 缺点:性能较低,相比于内存数据库,响应速度较慢,数据库可能成为瓶颈。
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
application.yml
spring:
redis:
host: 127.0.0.1
port: 6379
session:
store-type: redis
timeout: 3600
redis:
namespace: login_user
demo
@GetMapping("/login")
public String login(@RequestParam String username, @RequestParam String password, HttpSession session){
//账号密码正确
session.setAttribute("login_user", username);
return "登录成功";
}
@GetMapping("/info")
public String info(HttpSession session) {
return "当前登录的是:" + session.getAttribute("login_user");
}
方案二:Token + Redis
Redis 是一种高效的内存数据存储,它广泛用于存储 Session 数据,尤其适用于分布式应用。Redis 可以通过其高效的读写性能和数据共享功能,支持跨多个应用节点共享会话数据。
- 优点:高性能、低延迟、易于扩展,支持数据的自动过期。
- 缺点:数据可能会丢失(如果没有持久化),对内存要求较高。
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
application.yml
spring:
redis:
host: 127.0.0.1
port: 6379
demo
@GetMapping("/loginWithToken")
public String loginWithToken(@RequestParam String username, @RequestParam String password) {
//账号密码正确
String key = "token_" + UUID.randomUUID().toString();
stringRedisTemplate.opsForValue().set(key, username, 3600, TimeUnit.SECONDS);
return key;
}
@GetMapping("/infoWithToken")
public String infoWithToken(@RequestHeader String token) {
return "当前登录的是:" + stringRedisTemplate.opsForValue().get(token);
}
方案三:JWT WEb Token(加密算法)
通过JWT(JSON Web Token)生成 Token 来标识会话。客户端存储 Session ID,服务器只需验证 Token 即可。
- 优点:无状态服务器,不需要额外的存储,简化了服务器端的会话管理。
- 缺点:适用于无状态认证,且 Token 易被篡改,需额外的安全机制。
pom.xml
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
demo
@GetMapping("/loginWithJwt")
public String loginWithJwt(@RequestParam String username, @RequestParam String password) {
Algorithm algorithm = Algorithm.HMAC256(JWT_KEY);
String token = JWT.create()
.withClaim("login_user", username)
.withClaim(UID, 1)
.withExpiresAt(new Date(System.currentTimeMillis()</