熔断即断路保护。微服务架构中,如果下游服务因访问压⼒过⼤⽽响应变慢或失 败,上游服务为了保护系统整体可⽤性,可以暂时切断对下游服务的调⽤。这种牺 牲局部,保全整体的措施就叫做熔断。
gateway 过滤器
Filter 在"pre"类型过滤器中可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post"类型的过滤器中可以做响应内容、响应头的修改、日志的输出、流量监控等。
随堂测试
1-1、以下属于微服务架构优势的是()
A可以自由使用不同的技术
口B远程调用而导致延迟增加
C并行开发和部署多个服务
D故障隔离
口E模块边界定义较难
1-2、下面哪些是微服务架构和SOA架构的区別()
A拆分粒度更细
B组件化程度更高
口C微服务是真正的服务化而SOA不是
D通信往往更加轻量级
1-3、关于做服务中一些概念描述正确的是()
口A服务发现对实时性并无要求
B负载均衡有客户端负载均衡和服务器端负载均衡之说
口C熔断是退而求其次的措施,返回预设值
D网关如同統一入口出口,可以对请求进行精细化控制
2-1、以下关于 Spring Cloud描述正确的是()
A Spring Cloud充分利用了 Springboot?开发带来的便利性
B Spring Cloud可以说是一种规范,其下有不同的实现
C Spring Cloud帮我们解決微服务架构过程中的一系列题
D Spring Cloud采用组件化机制,不同组件解決不同问题,这些组件共同构成 Spring Cloud技术栈
2-2、下面描述错误的是()
口 A Eureka服务注册中心
B Hystrix实现负载均衡,从一个服务的多台机器中选择一台
C Ribbon提供熔断降级功能
口 D Feign远程调用
1、 Eureka满足CAP原则中的()分值7分
口A一致性
B可用性
C分区容错性
口D高性能
2、 Eureka Client从 Eureka Serveri端获取服务列表信息目前主要采用哪种模式()分值7分
A PUSI
B POLL
C 查询数据库
D 查询 Redis
3、下列关于 Eureka描述正确的是()分值6分
A集群模式下每一个 Eureka Server.相对于其他 Server来说都是客户端
B Eureka心跳机制是为了探测 Eureka Server是否存活
C心跳续约间隔默认30秒
D Eureka Client获取 Serverj端服务实例之后不会在本地缓存
4、关于 Eureka自我保护机制描述正确的是()分值7分
A不会剔除任何服务实例:
B可以配置关闭:
C是CAP中A的体现
D Eureka Server仍然能够接受新服务的注册和查询请求
5、关于 Ribbon描述正确的是()分值6分
A Ribbon属于客户端负载均衡
口 B Ribbon默认随机策略
口 C Ribbon原理上是基于 Filter过滤器
D Rule是 Ribbon对负载均衡策略的抽象和规范接
6、下面哪些属于雪崩效应解决或预防手段()分值7分
A服务熔断
B网关限流
C服务降级
D上游 Nginx限流
7、关于 dystrix描述正确的是()分值7分
O A Hystrix可以进行熔断但无法完成降级
D B Hystrix可以进行降级但无法完成熔断
C Hystrix可以完成熔断和降级
OD以上都不对
8、关于 Hystrix工作机制描述正确的是()分值6分
A在某一时间窗内错误请求数和最小请求数达到一定阈值, Hystrix将跳闸
口B跳闸之后将无法恢复,除非重启服务
口C跳闻之后可以恢复,但需要手动进行
D跳闸之后 Hystrix会进行自动修复尝试
9、关于 dystrix舱壁模式描述正确的是()分值7分
A不同的@ Hystrixcommand方法应该使用同一个线程池
B不同的@ Hystrixcommand方法可以各自使用一个线程池,避免影响
C舱壁模式在这里指的其实就是线程池隔离策略
口D以上都不对
10、关于 Feign/ Openfeign的描述正确的是()分值6分
A Feign日志可以帮助我们调试问题
口B使用 Openfeignl时仅仅只需要@ Enablefeign Clients开启功能即可,不需要做其他任何注解配置
C Openfeign使用起来类似于 Dubbo的方式,体现了面向接口编程
D Openfeign是支持一些 Springmvc注解的
11、通过追踪 Openfeign源码发现()分值7分
A Controller层注入的 Feign Client是一个代理对象
B底层是采用动态代理机制进行的功能增强
C Openfeign使用时的负载均衡实现交给了 Ribbon执行
D最终请求使用了 Httpurlconnection
12、关于 Gate Way网关描述正确的是()分值6分
口 A Gateway仅仅完成类似于 Nginx的路由转发
B Spring Cloud Gateway基于BIO模型
C Spring Cloud Gateway基于 Webflux实现
D 可以完成黑白名单、日志监控、限流等精细化控制
13、关于 Gateway过滤器描述正确的是
分值7分
A 从类型上分为 Gateway Filter?和 Globalfilter两种
B Gatewayfilter7i和 Globalfilter都会对所有路由生效
C Globalfilter?全局生效, Gate Wayfilter可以指定对具体的路由生效
D过滤器涉及到pre和post两个生命周期时机点
14、关于 Spring Cloud Config描述正确的是()分值7分
A 使用时需要暴露服务的 actuator相关端点
口B客户端获取到最新的配置数据后一点也不需要考虑做进一步处理
C客户端获取到最新的配置数据后根据情況看是否需要进一步处理,比如数据库连接池大小的配
口D以上都不对
15、关于 Spring Cloud Strean描述正确的是()分值7分
A帮我们屏蔽底层具体MQ之间的差异,提供上层抽象
B具体是由 Binders绑定器对象来对接具体的消息中间件
C Stream中 Binder.不能变更
D inputi通道对应生产者, output通道对应消费者
作业
一、编程题
请同学们根据下⾯的业务描述和要求,使⽤第⼀代Spring Cloud核⼼组件完成项⽬构建、编码及测试。
作业具体要求参考以下链接文档:
作业资料说明:
1、提供资料:代码工程、验证及讲解视频。
2、讲解内容包含:题目分析、实现思路、代码讲解。
3、效果视频验证:
- 注册新账号
- 一分钟内只允许获取一次验证码
- 发邮件功能
- 校验验证码
- 验证码超时展示
- 保存令牌数据库
- 令牌保存cookie中
- 跳转到欢迎页面
- 登录
- 生成Token保存到令牌表和Cookies中最后转到欢迎页面
- 未登录状态网关拦截
- 回IP防暴刷过滤器
- 在1分钟内注册超过100次时返回错误信息
按照改图进行搭建即可.
nginx 做到动静分离
server { listen 80; server_name localhost; location /static/ { root /Users/ale/Desktop/abc/stage-3-module-4/code/static/; rewrite '^/static(.*)$' $1 break; } location /api/ { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # 客户端的真实IP proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:9002/; # 网关地址 } }
配置主机名, 也为了以后避免跨域问题 和 注册登录页面使用.
127.0.0.1 edu.lagou.com
使用前的准备
- 创建数据库, 导入表
create database lagou_3_4; -- 验证码存储表 DROP TABLE IF EXISTS `lagou_auth_code`; CREATE TABLE `lagou_auth_code` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '⾃增主键', `email` varchar(64) DEFAULT NOT NULLCOMMENT '邮箱地址', `code` varchar(6) DEFAULT NOT NULLCOMMENT '验证码', `createtime` datetime DEFAULT NOT NULLCOMMENT '创建时间', `expiretime` datetime DEFAULT NOT NULLCOMMENT '过期时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; SET FOREIGN_KEY_CHECKS = 1; -- 令牌存储表 DROP TABLE IF EXISTS `lagou_token`; CREATE TABLE `lagou_token` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '⾃增主键', `email` varchar(64) NOT NULL COMMENT '邮箱地址', `token` varchar(255) NOT NULL COMMENT '令牌', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- lagou_token 表中为 邮箱 字段添加 UNIQUE 索引 ALTER TABLE `lagou_3_4`.`lagou_token` ADD UNIQUE INDEX(`email`); -- lagou_token 表中添加 password 字段 ALTER TABLE `lagou_3_4`.`lagou_token` ADD COLUMN `password` varchar(40) NOT NULL COMMENT '用户密码' AFTER `token`; SET FOREIGN_KEY_CHECKS = 1; INSERT INTO `lagou_3_4`.`lagou_auth_code`(`email`, `code`, `createtime`, `expiretime`) VALUES ('zhangsan@qq.com', '543363', '2020-12-19 18:36:39', '2020-12-19 18:36:42');
- lagou-common 模块的开发, 引入必须的依赖信息. 然后就是常规操作进行Java Web 项目的分层开发.
email 邮件服务
- 引入 spring-boot-starter-mail 依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
- 配置 application.yml
spring: application: name: lagou-service-email mail: # 发送邮件服务器 host: smtp.qq.com username: acc8226@vip.qq.com # 可拥有多个授权码,所以无需记住该授权码,也不要告诉其他人 password: YOUR-PASSWORD
- EmailServiceImpl 类注入 JavaMailSender 进行邮件的发送
public void sendSimpleMail(String toEmailAddress, String code) { final SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); simpleMailMessage.setTo(toEmailAddress); simpleMailMessage.setSubject("收到来自【lagou】发送的验证码"); simpleMailMessage.setText(String.format(pattern, code)); simpleMailMessage.setFrom(this.fromEmailAddress); this.javaMailSender.send(simpleMailMessage); log.info("邮件发送成功 address = {}, code = {}", toEmailAddress, code); }
- 类 EmailController
暴露 "{email}/{code}" 接口, 用于提供向指定邮箱发送验证码的服务
场景:注册页面点击"获取验证码"按钮,被 user 微服务"create/{email}" 行为触发. 该接口本身并没有直接被用户进行调用.
测试用例:
用于校验此发送邮件的服务是否可用
http://localhost:8082/email/acc8226@qq.com/654473 预计返回 true
code 发送验证码服务
- "create/{email}" 用于⽣成验证码 并 发送到对应邮箱 (暴露出的接口)
场景:注册页面点击"获取验证码"按钮触发.
/** * ⽣成验证码 并 发送到对应邮箱 * 场景:注册页面点击"获取验证码"按钮触发 * * @param email * @return 0成功 1失败 2该用户已注册,不能再次注册 */ @GetMapping("create/{email}") public int create(@PathVariable("email") final String email) { int resultCode; try { // 1. 判断该用户是否存在 final boolean isEmailRegistered = this.userServiceFeignClient.isRegistered(email); if (isEmailRegistered) { resultCode = 2; } else { final String randomCode = RandomUtils.getSixRandomCode(); // 2. 若不存在 则 调用 email 微服务,发送携带了验证码的邮件 final boolean register = this.mailServiceFeignClient.send(email, randomCode); // 3. 顺便入库 this.authCodeService.createAuthCode(email, randomCode); log.info("create # send result = {}, email = {}, randomCode = {}", register, email, randomCode); resultCode = 0; } } catch (Exception e) { resultCode = 1; log.error("create ⽣成验证码 失败", e); } return resultCode; }
- "validate/{email}/{code}" 用于校验验证码是否正确(合法)
场景:被 user 微服务的注册功能调用. 该接口本身并没有直接被用户进行调用.
/** * 校验验证码是否正确(合法),0合法, 1验证码前后校验不一致, 2超时, 3异常情况 * 场景:被 user 微服务的注册功能调用 * * @param email 传入的电子邮箱 * @param code 传入的验证码,前端已做了非空校验,此处不再做校验 * @return */ @GetMapping("validate/{email}/{code}") public int validate(@PathVariable("email") String email, @PathVariable("code") String code)
测试用例:
验证-发送验证码是否总是返回 true
http://localhost:8081/code/create/zhangsan@qq.com
http://localhost:8081/code/create/test@qq1.com
http://localhost:8081/code/create/acc8226@qq.com
http://localhost:8081/code/create/acc8226@vip.qq.com
验证-校验验证码是否正确(合法)
http://localhost:8081/code/validate/zhangsan@qq.com/888888 预计返回1不匹配
http://localhost:8081/code/validate/zhangsan@qq.com/707636 预计返回 0,表示验证
码匹配
注意:
code 服务调用发送邮件微服务, 使用 feign 解决了使用 restTemple存在这不便之处。
但是我配置后发现 feign 默认 Read timed 超时了,然后会重试一次。导致时常验证码会重复发送一次.
2020-12-21 00:44:25.836 DEBUG 36852 --- [nio-8081-exec-1] c.l.edu.service.MailServiceFeignClient : [MailServiceFeignClient#register] <--- ERROR SocketTimeoutException: Read timed out (2381ms) 2020-12-21 00:44:25.837 DEBUG 36852 --- [nio-8081-exec-1] c.l.edu.service.MailServiceFeignClient : [MailServiceFeignClient#register] java.net.SocketTimeoutException: Read timed out
这是因为 code 验证码微服务通过feign调用email总能看到 ReadTimeout, 因为默认的超时时间是 1秒, 这里在 yml 配置文件中调长了超时时间,一定程度上解决了超时的问题. 这里使用了 ribbon, 这个客户端的负载均衡器.
ribbon: # 适当调大 请求处理超时时间,避免之后 feign 进行重试 ReadTimeout: 20000
user 用户微服务
提供注册和登录的功能.
1. 注册接口(暴露出的接口)
http://localhost:8080/user/register/zhangsan@qq.com/123456/442252 第一次预计 true 表示注册成功,第二次为 false 表示注册失败。
/** * 注册接⼝ * * @param email E-mail * @param password 密码 * @param code 验证码 * @return 0合法, 1验证码前后校验不一致, 2超时, 3异常情况,4该用户已注册过了 */ @GetMapping("register/{email}/{password}/{code}") public int register(HttpServletResponse response, @PathVariable("email") String email, @PathVariable("password") String password, @PathVariable("code") String code) { int validateResult; // 1. 如果没有注册才允许注册 if (this.tokenService.isRegistered(email)) { validateResult = 4; } else { try { // 2. 调用 code 微服务-校验验证码的合法性: 0合法, 1密码前后校验不一致, 2超时, 3异常情况 validateResult = this.authCodeServiceFeignClient.validate(email, code); log.info("验证码校验结果 = {}", validateResult); if (validateResult == 0) { // 3. 生成 token 并入库 【1. 入库】 Token token = this.tokenService.register(email, password); // 4. token 写入 cookie 中 【2. 写入 cookie 并返回前端】 saveTokenToResponse(response, token.getToken()); } } catch (Exception e) { log.error("register method throw error:", e); validateResult = 3; } } // 5. 前端根据此不出错的标记去跳转到 welcome 页面 return validateResult; }
- 是否已注册接口
"isRegistered/{email}"
- 根据邮箱判断,true 代表已经注册过,false 代表尚未注册
- 场景:被 code微服务-获取验证码的前置条件用到该方法
http://localhost:8080/user/isRegistered/zhangsan@qq.com 预计 true
http://localhost:8080/user/isRegistered/nobody@qq.com 预计 false
3. 登录接口(暴露出的接口)
"login/{email}/{password}"
http://localhost:8080/user/login/zhangsan@qq.com/123456 预计 返回邮箱
http://localhost:8080/user/login/zhangsan@qq.com/1234564532 预计返回ERROR,表示密码不匹配
http://localhost:8080/user/login/nobody@qq.com/123456 预计返回EMPTY,表示用户不存在
/** * 登录接⼝ * * @param email 邮箱地址 * @return 登录成功返回邮箱地址, 否则 原始密码已损坏 和 密码不一致为 ERROR 或 用户不存在为 EMPTY */ @GetMapping("login/{email}/{password}") public String login(HttpServletResponse response, @PathVariable String email, @PathVariable String password) { log.info("login: email = {}, password = {}", email, password); final String returnMsg; final Optional<Token> findOne = this.tokenService.findOneItemByEmail(email); if (!findOne.isPresent()) { // 1. 用户不存在的情况:应提示用户去注册 returnMsg = Response.EMPTY_MESSAGE; } else { // 2. 根据 email 知道对应的 用户信息 Token token = findOne.get(); String originPassword = token.getPassword(); // 3. 用户的密码继续比对, 密码不一致的情况(包含了 原始密码已损坏 的情况) if (Strings.isBlank(originPassword) || !originPassword.equals(password)) { returnMsg = Response.ERROR_MESSAGE; } else { // 4. 刷新 token 并入库 【1. 入库】 this.tokenService.login(token); // 5. 将 token 信息返回前端 【2. 写入 cookie 并返回前端】 saveTokenToResponse(response, token.getToken()); // 6. 前端根据此不出错的标记去跳转到 welcome 页面 returnMsg = token.getEmail(); } } log.info("login: returnMsg = {}", returnMsg); return returnMsg; }
- token 反查 email 接口 (暴露出的接口)
"info/{token}"
场景: welcome页面会用到该支接口
http://localhost:8080/user/info/03837c5e-52a3-42ce-9e8a-7c2da840d68e 预计返回 email 信息
http://localhost:8080/user/info/12222229-2222-7777-55555-ac8333333335 预计返回EMPTY,表示 token 不存在或已失效. 前端据此重定向到 login 登录页面.
搭建 eureka 服务
使用以下两个端口
鉴于个人计算机很难模拟多主机的情况,这里 host 文件配置多主机实例.
127.0.0.1 LagouCloudEurekaServerA 127.0.0.1 LagouCloudEurekaServerB
8761 配置文件如下:
#eureka server服务端口 server: port: 8761 spring: application: # 应用名称,应用名称会在Eureka中作为服务名称 name: lagou-cloud-eureka-server # eureka 客户端配置(和Server交互),Eureka Server 其实也是一个Client eureka: client: service-url: # 配置客户端所交互的Eureka Server的地址(Eureka Server集群中每一个Server其实相对于其它Server来说都是Client) # 集群模式下,defaultZone应该指向其它 Eureka Server,如果有更多其它Server实例,逗号拼接即可 defaultZone: http://LagouCloudEurekaServerB:8762/eureka # 集群模式下可以改成 true register-with-eureka: true # 集群模式下可以改成 true fetch-registry: true dashboard: # 默认已经为 true了 enabled: true instance: # 当前eureka实例的主机名 hostname: LagouCloudEurekaServerA
可选择是否开启 访问健康检查接口, 可用了解微服务的运行状态
网关服务
- 关键配置
spring: application: name: lagou-cloud-gateway cloud: # gateway 网关从服务注册中心获取实例信息然后负载后路由 gateway: routes: # 路由可以有多个 # 我们自定义的路由 ID,保持唯一 - id: service-code-router # 目标服务地址 自动投递微服务(部署多实例)动态路由:uri配置的应该是一个服务名称,而不应该是一个具体的服务实例的地址. http为写死地址,lb为从注册中心取地址 uri: lb://lagou-service-code # 断言:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默 认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。 predicates: - Path=/code/** - id: service-email-router uri: lb://lagou-service-email predicates: - Path=/email/** - id: service-user-router uri: lb://lagou-service-user predicates: - Path=/user/**
- TokenFilter 用于对邮件微服务的 token认证, 验证客户端请求cookie中携带的token是否合法,合法则放⾏,此处不考虑token更新问题)
这里我的实现方式原本想的是调用了 code 服务的验证 token接口的, 但是我使用的lagou_token表被我改造成一个邮箱只对应一条数据, 所以重新登录后原有 token被刷掉. 所以这里我只对 token 做了非空校验和长度的限制校验.
if (null != path) { // 排除 user 和 code 服务 if (!path.startsWith("/user") && !path.startsWith("/code")) { Optional<String> tokenOptional = getTokenValue(request); // token is null if (!tokenOptional.isPresent()) { return write401Mono(response); } String token = tokenOptional.get(); // token is blank if (Strings.isBlank(token)) { return write401Mono(response); } // 校验 token 是否合法, 这里只做基本校验。认为只要长度大于 30 就是后端仍可的合法 token if (token.length() < 30) { return write401Mono(response); } } } // 合法请求,放行,执行后续的过滤器 return chain.filter(exchange);
- IP注册接⼝的防暴刷控制的 RushFilter 过滤器. 如果被校验住会有状态吗为303 和 "您频繁进⾏注册,请求已被拒绝"的提示
// 注册服务的路径 if (null != path && path.startsWith("/user/register/")) { // 由于使用了 nginx 转发,所以从特定请求头中拿出 实际 IP 地址 String realIP = request.getHeaders().getFirst("X-Real-IP"); if (!Strings.isBlank(realIP)) { IPRecordQueue ipRecordQueue = concurrentHashMap.get(realIP); if (ipRecordQueue == null) { ipRecordQueue = new IPRecordQueue(min, per); concurrentHashMap.put(realIP, ipRecordQueue); } boolean isOffer = ipRecordQueue.offer(LocalDateTime.now()); if (!isOffer) { // 前端据此 UNAUTHORIZED 跳转到 login 页面 response.setStatusCode(HttpStatus.SEE_OTHER); DataBuffer wrap = response.bufferFactory().wrap("您频繁进⾏注册,请求已被拒绝".getBytes()); return response.writeWith(Mono.just(wrap)); } } } // 合法请求,放行,执行后续的过滤器 return chain.filter(exchange);
测试注册接口是否可以做到1分钟请求2次后便阻断
http://edu.lagou.com/api/user/register/acc8226@qq.com/123456/333322
测试直连网关的连通性
http://localhost:9002/email/acc8226@vip.qq.com/666666 预计返回 true
测试加入nginx 之后, 访问网关的连通性
http://edu.lagou.com/api/email/acc8226@vip.qq.com/888888 预计返回 true
config 配置服务 和 bus刷新服务的支持
直接访问服务端 配置
这两种写法都是可以的
- /{application}-{profile}.yml
- /{label}/{application}-{profile}.yml
我这里配置了 2 套环境.
- 开发环境
- 生产环境
示例配置 yml 文件如下
spring: # 数据库配置 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://YOUR-IP:3306/lagou_3_4?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: YOUR-PASSWORD # 邮箱配置 mail: ## 发送邮件服务器 host: smtp.qq.com ## 发送邮件的邮箱地址 username: acc8226@qq.com ## 客户端授权码,不是邮箱密码,这个在qq邮箱设置里面自动生成的 password: YOUR-PASSWORD # 防暴刷配置:限制单个客户端ip最新 X 分钟的请求注册接口不能超过 Y 次 myconfig: x: 1 y: 1
手动验证发邮件服务
http://localhost:8082/actuator/refresh
配置了bus后发消息给所有需要刷新的服务
http://localhost:9002/actuator/bus-refresh
安装 rabbit mq, 使用 brew install rabbitmq
命令.
或者手动安装
sbin/rabbitmq-server
sbin/rabbitmqctl shutdown
在浏览器输入http://localhost:15672 即可进入rabbitmq控制终端登录页面
默认用户名和密码为 guest/guest.
三个主要页面
注册页面
获取验证码
http://edu.lagou.com/api/code/create/acc8226@qq.com
进行注册
http://edu.lagou.com/api/user/register/acc8226@qq.com/123456/333322
登录页面
进行登录
http://edu.lagou.com/api/user/login/acc8226@qq.com/123456
欢迎页面
会调用/api/user/info/
, token查询邮箱地址的结果.
关于测试
- 注册测试
- 登录测试
- 未登录状态下,清空cookie,直接访问后台的邮件服
务,http://www.test.com/api/email/{email}/{code}
,验证⽆token情况下是否被⽹关拦截.
http://edu.lagou.com/api/email/acc8226@qq.com/123322
参考
Session 与 Cookie 基础
由于http协议是无状态的协议,为了能够记住请求的状态,于是引入了Session和Cookie的机制。我们应该有一个很明确的概念,那就是Session是存在于服务器端的,在单体式应用中,他是由tomcat管理的,存在于tomcat的内存中,当我们为了解决分布式场景中的session共享问题时,引入了redis,其共享内存,以及支持key自动过期的特性,非常契合session的特性,我们在企业开发中最常用的也就是这种模式。但是只要你愿意,也可以选择存储在JDBC,Mongo中,这些,spring都提供了默认的实现,在大多数情况下,我们只需要引入配置即可。
而Cookie则是存在于客户端,更方便理解的说法,可以说存在于浏览器。Cookie并不常用,至少在我不长的web开发生涯中,并没有什么场景需要我过多的关注Cookie。http协议允许从服务器返回Response时携带一些Cookie,并且同一个域下对Cookie的数量有所限制,之前说过Session的持久化依赖于服务端的策略,而Cookie的持久化则是依赖于本地文件。虽然说Cookie并不常用,但是有一类特殊的Cookie却是我们需要额外关注的,那便是与Session相关的sessionId,他是真正维系客户端和服务端的桥梁。
从零开始的Spring Session(一) | 程序猿DD
http://blog.didispace.com/spring-session-xjf-1/