1 CAP 概念
在分布式系统中,必定会遇到 CAP,如:
- C(Consistency):一致性
- 在分布式系统中,所有的计算机节点的数据在同一时刻都是相同的,数据都是一致的,不能因为分布式导致不同系统拿到的数据不一致。也就是说,用户在某个节点写了数据,在其它节点获得该数据的值是最新的;
- A(Availability):可用性
- 保证系统可用,也就是无论什么时候,系统都应该被用户访问到,用户可以获得正常的响应结果(阻塞也不允许)。比如做好集群、做好主备等等,这就是高可用。
- P(Partition tolerance):分区容错性
- 在整个发布式系统,都是部署到不同的节点上,或是不同的机房甚至不同的地域,某些服务会部署在不同的子网,每个子网就是一个区,也就是网络分区,分区之间的通信也有可能出现通信故障。当某个节点、区域、分区出现问题,整个系统还是能够提供一致性和可用性的服务。
1.1 CAP 无法同时满足
如果从理论上来讲,以上三点都应该满足,但在分布式系统中同时满足这三点不可能,所以对于 CAP 来讲,只能满足其中两者,要么 AP,要么 CP,要么 CA。
举例说明一下为什么CAP不能同时满足:
上图,ABCDE这5个节点都是分别部署在不同地域的机房的节点,假设现在我们的分区容错性 P
做的很好,保证不会出现网络方面的故障,这个时候看一下一致性 C
和可用性 A
,如果此时一个请求把数据写到 A 点,随后又有请求从 B 节点,那么由于他们之间在不同的地域,数据的同步有时间延迟,可能几百毫秒可能几秒。
- 如果此时要满足数据的一致性 C,那么请求就可能会被阻塞,而阻塞就表示当前系统不可用,即此时就是满足了 CP,就无法满足 A
- 如果此时要满足系统的可用性 A,那么请求到的数据就可能达不到一致性 C,即此时就是满足了 AP,就无法满足 C
- 如果不满足 P,即所有分布式系统都在同一内网内,避免了输送数据同步的时间延迟等问题,便可以满足 AC
对于 CP、AP、CA 这三种,平时开发的时候,要如何选择?
首先 P
分区容错是一定满足的,因为我们在部署的时候往往都是多节点集群部署,设置异地互备。
那么接下来就要抉择一致性 C
和可用性 A
?
主流的互联网公司会采取 AP,也就是 数据的弱一致性,因为很多数据没必要强一致性。都是要注意,数据层面的交互,关系型数据库,Redis,mongodb等,它们肯定是强一致性,因为需要提供给你的网站数据服务。
2 会话
会话代表的是客户端与客户端的一次交互过程,这个过程可以是连续也可以是时断时续。
2.1 无状态会话
Http 请求时无状态的,用户向服务端发起多个请求,服务端并不会知道这多次请求都是来自同一用户,这个就是无状态的。cookie 的出现就是为了有状态的记录用户。
常见的,ios与服务端交互、前后端分离、小程序与服务端交互,他们都是通过发起 Http 来调用接口数据。每次交互服务端都不会拿到客户端的状态,但是我们可以通过手段处理,比如每次用户发起请求的时候携带一个 userId
或 user-token
,如此一来,就能让服务端根据 userId
或 user-token
来获取相应的数据。每个用户的下一次请求都能被服务端识别来自同一个用户。
2.2 状态会话
Tomcat 中的会话,就是有状态的,服务器 tomcat 会为用户创建一个 session,同时前端会有一个 jsessionid,每次交互都会携带。如此一来,服务器只要在接到用户请求时,就可以拿到 jsessionid,那么此时就可以操作会话了。
@GetMapping("/setSession")
public Object setSession(HttpServletRequest request) {
HttpSession session = request.getSession();
session.setAttribute("userInfo", "userMsg");
session.setMaxInactiveInterval(3600);
session.getAttribute("userInfo")
return "ok";
}
2.3 集群分布式系统会话
集群或分布式系统本质都是多个系统,假设这个里有两个服务器节点,分别是 A、B 系统,它们可以是集群,也可以是分布式系统,一开始用户和 A 系统交互,那么这个时候的用户状态,我们可以保存到 Redis 中,作为 A 系统的会话信息,随后用户的请求进入到 B 系统,此时 B 系统的会话可以和 Redis 关联,如此一来 A、B 系统的会话就统一了。那么这个其实就是分布式会话,通过 Redis 来保存用户的状态。
2.4 SpringSession
除了每次直接操作 Redis 从而达到集群分布式会话,还可以使用 SpringSession 实现分布式会话。
导入 SpringSession 依赖:
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
配置 yml
文件,设置 session 存储类型:
spring:
session:
store-type: redis
然后在启动类 Application
文件中添加以下注解,开启 HttpSession
使用 Redis,同时去除安全自动装配(否则每次请求都需要输入密码)
/**
* 开启 Redis 作为 SpringSession
*/
@EnableRedisHttpSession
/**
* 去除安全自动装配,负责使用 SpringSession 而导入的SpringBoot安全依赖
* 会导致前端那边每次使用都要输入密码
*/
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
然后使用 SpringSession 的操作与普通使用 Session 的操作一致
@RestController
@RequestMapping("test")
public class TestController {
@GetMapping("setSession")
public ServerResponse setSession(HttpServletRequest request, HttpServletResponse reponse) {
HttpSession session = request.getSession();
session.setAttribute("title","content");
return ServerResponse.createdBySuccess(session.getAttribute("title"));
}
}