简介
SpringCloud和SpringBoot之间有严格的版本对应关系,因此要小心选择版本,应该根据SpringCloud版本选择SpringBoot版本。本次课程选择SpringCloud的Hoxton.SR1版本,SpringBoot采用2.2.2.RELEASE版本,SpringCloud alibaba采用2.1.0.RELEASE版本。
零基础部分
父工程搭建
Maven 使用dependencyManagement元素来提供了一种管理依赖版本号的方式。通常会在一个组织或者项目的最顶层的父POM 中看到dependencyManagement 元素。使用pom.xml 中的dependencyManagement 元素能让所有在子项目中引用一个依赖而不用显式的列出版本号。Maven 会沿着父子层次向上走,直到找到一个拥有dependencyManagement 元素的项目并以此版本号为准。
dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显示的声明需要用的依赖。在dependencyManagement时,由于它没有真正引入依赖,有时会导致文件爆红,我们可以先注释,导入依赖后再恢复。
spring-boot-maven-plugin插件导入爆红通常是缺少版本号的问题。
支付模块
为了方便与前端的交换,我们将数据封装起来,增加错误码code等一系列信息。
public class CommonResult<T> {
private Integer code;//错误码
private String message;//错误信息
private T data;
//当缺少Data时的构造方法
public CommonResult(Integer code,String message){
this(code,message,null);
}
}
浏览器一般不支持post请求,我们可以使用Postman工具进行模拟。
当微服务达到一定数量后,IDEA会从run
变成run Dashboard
,在新版本IDEA中被更名为Services,开启步骤如下:services->ADD Service(+号)-> Run Configruation Type->Spring Boot
。
模块流程:建Module、改POM、写YML、主启动。
业务类流程:建表SQL、Entities实体类、Dao层、Service层、Controller层。
消费模块
RestTemplate提供了多种便捷访问远程Http服务的方法,是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集。在使用之前需要将其注入到SpringBoot容器中。
支付模块的Controller需要增加@RequestBody
注解,因为是消费端发送请求给支付模块,否则会导致缺少对应的数据。
在新版本中开启热部署,compiler.automake.allow.when.app.running
的开启方式发生了改变,需要在设置中的高级设置进行修改。关于IDEA2022开启热部署没有compiler.automake.allow.when.app.running的解决方案
个人感觉还是不要开启热部署,电脑性能有点顶不住。
对于公共的Entities实体类,可以放在一个特定的模块中,将该模块clean后install,然后在POM文件中引入该依赖。
初级内容
注册中心
EurekaServer服务
Eureka的MAVEN依赖分为server
和client
依赖,同时注解也分为@EnableEurekaServer
和@EnableEurekaClient
注解。
application.yml
文件的设置spring.application.name
就是Eureka中服务名称。
消费者只需指明需要的服务,注册中心就会为其找到对应的服务,例如:"http://CLOUD-PAYMENT-SERVICE"
。
当同一个服务有多个提供者是,根据名称无法分辨时,需要为RestTemplate
添加@LoadBallanced
注解进行负载均衡。
服务发现:注册进Eureka的消费者,可以通过服务发现获得注册的服务。需要在服务的Controller层编写DiscoveryClient
存储信息和在主启动类中增加注解@EnableDiscoveryClient
。
保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式, Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。
注册进Eureka的地址:defaultZone: http://localhost:7001/eureka
。
Zookeeper
Zookeeper是临时存储服务的,当服务长时间不发生心跳包,Zookeeper将会删除该服务。
解决Zookeeper版本jar包冲突问题
<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper3.5.3-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.4.9版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
Consul
Consul的Web管理页面端口号为8500。
Consul使用开发模式启动:consul agent -dev
。
三个注册中心之间的异同
组件名 | 语言 | CAP | 对外暴露接口 |
---|---|---|---|
Eureka | Java | AP | HTTP |
Consul | Go | CP | HTTP/DNS |
Zookeeper | Java | CP | 客户端 |
CAP:C:Consistency(强一致性)、A:Availability(可用性)、P:Partition tolerance(分区容错性)。CAP理论关注粒度是数据,而不是整体系统设计的策略。
服务调用
Ribbon
Ribbon是客户端负载均衡的工具,与之相比,Nginx是服务端的负载均衡。
Ribbon在工作时分成两步:第一步先选择EurekaServer,它优先选择在同一个区域内负载较少的server。第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。
自定义负载均衡算法
自定义负载均衡的接口是IRule
接口,有多种实现类。
这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的。
新建MySelfRule规则类后需要在主启动类添加@RibbonClient
注解。
@Configuration
public class MySelfRule
{
@Bean
public IRule myRule()
{
return new RandomRule();//定义为随机
}
}
OpenFeign
OpenFeign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可。
主启动类上需要添加@EnableFeignClients
注解。
openfeign底层是ribbon,客户端一般默认等待1秒钟,需要修改的话再yml文件处修改即可。
服务调用
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}")
CommonResult<Payment> getPaymentById(@Param("id") Long id);
}
服务降级
Hystrix断路器
复杂分布式体系结构中应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等, Hystrixi能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FaIBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
常见概念
服务降级:服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示。通常在程序运行异常、超时、服务熔断触发服务降级、线程池/信号量满。
服务熔断:达到最大访问后,直接拒绝访问,然后调用服务降级的方法并返回友好提示。
服务限流:限制并发访问数,有序提供服务。
服务降级
服务降级配置注解:业务类:@HystrixCommand
;主启动类:@EnableCircuitBreaker
。
服务降级一般放在客户端。
@DefaultProperties
注解用于设置全局的服务降级方法。
服务端
//指定出现问题后处理方法和触发服务降级的条件
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
})
public String paymentInfo_TimeOut(Integer id) {
int timeNumber = 5;
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池: " + Thread.currentThread().getName() + "paymentInfo_TimeOut,id: " + id + "\t" + "耗时" + timeNumber + "秒钟";
}
//负责处理服务降级的方法
public String paymentInfo_TimeOutHandler(Integer id) {
return "线程池: " + Thread.currentThread().getName() + "paymentInfo_TimeOutHandler,id: " + id + "\t" + "o(╥﹏╥)o";
}
客户端
实现远程调用接口的类中编写服务降级操作,然后在原接口中指明该类作为服务降级的处理者。
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentHystrixService.class)
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
@Component
public class PaymentFallbackService implements PaymentFallbackService{
@Override
public String paymentInfo_OK(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_OK";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_TimeOut";
}
}
服务熔断
服务熔断当检测到该节点微服务调用响应正常后,将恢复调用链路。主要机制是半开状态,会试探性的开启服务,如果能正常调用的话则重新恢复调用链路。
熔断机制的注解是@HystrixCommand
。
服务端
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),//是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),//请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),//时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),//失败率达到多少后熔断
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id)
{
if(id < 0)
{
throw new RuntimeException("******id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id)
{
return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " +id;
}
服务网关
GateWay
SpringCloud Gateway使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架。
GateWay基于Spring WebFlux ,Spring WebFlux 是 Spring 5.0 引入的新的响应式框架,区别于 Spring MVC,它不需要依赖Servlet API,它是完全异步非阻塞的,并且基于 Reactor 来实现响应式流规范。
GateWay的核心逻辑就是路由转发+执行过滤器链。
三大概念
路由:路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由。
断言:开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由。
过滤:Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
yml文件配置网关
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
yml文件配置动态路由
默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能。
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
# uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址,lb的意思是进行负载均衡
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
自定义过滤器
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("***********come in MyLogGateWayFilter:"+new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if(uname == null){
log.info("******用户名为null,非法用户");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
服务配置
Config
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。
SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
客户端配置文件使用的是bootstrap.yml
,application.yml
是用户级的资源配置项,bootstrap.yml
是系统级的,优先级更高。
bootstrap配置文件不生效的话,可以通过引入依赖的方式解决。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>3.1.0</version>
</dependency>
bootstrap设置了端口为3355,但是一直被切换成80端口,并且没有注册进Eureka,猜测是bootstrap.yml
文件配置无效,但是又能够从3344端口获取到信息,问题尚未解决。
动态刷新问题最后还需要向客户端发送POST请求才能触发刷新:curl -X POST "http://localhost/actuator/refresh
。
配置yml连接github
在连接github的时候注意将SSH连接方法改为HTTPS的地址,在使用HTTPS方式时最好加上自己的账号密码。而且新版本不再是master,而是main。
spring:
application:
name: cloud-config-center #注册进Eureka服务器的微服务名
cloud:
config:
server:
git:
uri: https://github.com/xiqin-huang/springcloud-config.git #GitHub上面的git仓库名字
####搜索目录
search-paths:
- springcloud-config
####读取分支
label: main
服务总线
Bus
Bus支持两种消息代理:RabbitMQ和Kafka。
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。
服务总线有两种通知方式,一种是通知服务端,使其集中通知;一种是通知服务端,使其扩散通知,一般采用通知服务端的方式。
消息驱动
Stream
消息驱动的目的是屏蔽底层信息中间件的差异,降低切换成本,统一消息的编程模型。
应用程序通过 inputs 或者 outputs 来与 Spring Cloud Stream中binder对象交互。通过我们配置来binding(绑定) ,而 Spring Cloud Stream 的 binder对象负责与消息中间件交互。
如果没有设置group,重启微服务后之前死机的信息不能被收到,反之如果设置了group,重启后会读取之前未被读取的信息。
yaml文件
注意修改rabbitmq处的配置。
server:
port: 8801
spring:
application:
name: cloud-stream-provider
rabbitmq:
host: IP地址
port: 5672
username: name
password: password
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
bindings: # 服务的整合处理
output: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: send-8801.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址
生产者
实现自定义的接口,然后使用@EnableBinding()
@EnableBinding(Source.class)
public class MessageProviderImpl implements IMessageProvider {
@Resource
private MessageChannel output;//消息发送管道
@Override
public String send() {
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("***serial"+serial);
return null;
}
}
消费者
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {
@Value("${server.port}")
private String serverPort;
@StreamListener(Sink.INPUT)
public void input(Message<String> message){
System.out.println("消费者1号,接收到的消息是"+message.getPayload()+"\t port: "+serverPort);
}
}
分布式请求链路追踪
Sleuth
在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。SpringCloud Sleuth提供了一套完整的服务追踪的解决方案,在分布式系统中提供追踪解决方案并且兼容支持了zipkin。
启动zipkin:下载后jar包后,使用java -jar zipkin-server-2.12.9-exec.jar
启动即可。Web页面为:http://localhost:9411/zipkin/
。
Cloud Alibaba
Nacos
Nacos:一个构建云原生应用的动态服务发现、配置管理和服务管理平台,注册中心和配置中心的组合,Nacos=Eureka+Config+Bus。
Nacos通过下载压缩包,解压后在bin目录下使用startup.cmd
启动即可,命令运行成功后直接访问http:/localhost:8848/nacos
,账号密码均为nacos。
IDEA通过复制微服务的方式以不同的端口启动,减少复制粘贴,但是可能会出现其他问题。
Nacos支持AP和CP两种模式切换。
注意在配置管理中的文件后缀是.yaml
,而不是.yml
,而且需要选择yaml格式填写数据。
Nacos用命名空间+分组+Data ID来管理多环境多项目。
bootstrap.yml
文件中填写的命名空间是其生成的字符串。
默认Nacos使用嵌入式数据库实现数据的存储。所以,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储。
使用mysql8的朋友,可以把nacos换为从1.3.1开始的版本,同时在conf下的application.properties中加入老师的配置再加上&serverTimezone=UTC
,最后在bin目录下cmd,然后输入startup.cmd -m standalone
。
从嵌入式数据库切换到MySQL数据库:首先运行conf目录下的nacos-mysql.sql
,值得注意的是需要自己建对应名称的数据库,脚本没有创建对应的数据库。然后修改application.properties
文件。
Sentinel
Sentinel是分布式系统的流量防卫兵,作用与之前所学的Hystrix相似。
Sentinel在命令行java -jar sentinel-dashboard-1.7.1.jar
即可启动。如果使用的是较高版本的JDK,需要使用命令java --add-opens java.base/java.lang=ALL-UNNAMED -jar sentinel-dashboard-1.7.1.jar
。参考连接
Sentinel的端口号为8080,账号密码均为sentinel。
Sentinel采用懒加载模式,只有当微服务被访问一次之后才显示。
fallback管运行异常,blockHandler管配置违规。
将限流配置规侧特久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel_上的流控规则持续有效
流控规则
针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
流控模式
- 直接:api达到限流条件时,直接限流。
- 关联:当关联的资源达到阈值时,就限流自己。
- 链路:只记录指定涟路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源】
流控效果
- 快速失败:直接失败,抛异常。
- Warm Up:根据codeFactor冷加载因子,默认3的值,从阈值经过预热时长,才达到设置的QPS阈值。
- 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效。
降级规则
时间窗口结束后,关闭降级。
在旧版本的Sentinel中的断路器没有半开状态,新版本中新增了半开状态。
降级策略
- RT(平均响应时间,秒级):平均响应时间超出阈值且在时间窗口内通过的请求>=5,两个条件同时满足后触发降级。
- 异常比例(秒级):QPS>=5且异常比例(秒级统计)超过阈值时,触发降级。
- 异常数(分钟级):超过阈值时,触发降级。
热点规则
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
热点规则还可以指定特定的参数值拥有独特的阈值。
@SentinelResource
使用@SentinelResource
注解指定自定义的处理方法。
在本类自定义方法
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2){
return "-------testHotKey"
}
//处理方法
public String deal_testHotKey (String p1, String p2, BlockException exception){
return "-------deal_HotKey";
}
调用其他类的自定义方法
@GetMapping("/rateLimit/customerBlockerHandler")
@SentinelResource(value = "customerBlockerHandler",blockHandlerClass = CustomerBlockHandler.class,blockHandler = "handlerException2")
public CommonResult customerBlockerHandler()
{
return new CommonResult(200,"按客户自定义",new Payment(2020L,"serial003"));
}
//CustomerBlockerClass类
public class CustomerBlockHandler {
public static CommonResult handlerException(BlockException exception){
return new CommonResult(444,"按客户自定义,global handlerException-----1");
}
public static CommonResult handlerException2(BlockException exception){
return new CommonResult(444,"按客户自定义,global handlerException-----2");
}
}
Seata
一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题。
Seata0.9.0版本默认使用MySQL5和JDK8版本,太高版本会出现问题。需要将JDK版本降低,修改版本无效可能是系统变量path的优先级低,将其上移即可。MySQL需要在lib文件夹中删除MySQL5的jar包,放入MySQL8的jar包,然后配置文件的URL应为url = "jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=utf-8&useSSL=false&nullCatalogMeansCurrent=true&serverTimezone=UTC"
。
Failed to fetch schema of order
的解决办法,这也是MySQL8所带来的问题,mysql5 useInformationSchema 默认false mysql8 默认true。
使用@GlobalTransactional
注解开启全局事务,标注该注解的方法即为TM(事务管理者)。
术语
-
TC(事务协调者):维护全局和分支事务的状态,驱动全局事务提交或回滚。
-
TM(事务管理器):定义全局事务的范围:开始全局事务、提交或回滚全局事务。
-
RM(资源管理器):管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
-
XID(全局ID):全局事务的唯一ID。
处理过程
- TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID。
- XID在微服务调用链路的上下文中传播。
- RM向TC注册分支事务,将其纳入XID对应全局事务的管辖。
- TM向TC发起针对ID的全局提交或回滚决议。
- TC调度ⅪD下管辖的全部分支事务完成提交或回滚请求。