SpringCloud学习之Zuul统一异常处理及回退
一、Filter中统一异常处理
其实在SpringCloud的Edgware SR2版本中对于ZuulFilter中的错误有统一的处理,但是在实际开发当中对于错误的响应方式,我想每个团队都有自己的处理规范。那么如何做到自定义的异常处理呢?
我们可以先参考一下SpringCloud提供的SendErrorFilter:
/*
* Copyright 2013-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package org.springframework.cloud.netflix.zuul.filters.post; import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.netflix.zuul.util.ZuulRuntimeException;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils; import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.ERROR_TYPE;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SEND_ERROR_FILTER_ORDER; /**
* Error {@link ZuulFilter} that forwards to /error (by default) if {@link RequestContext#getThrowable()} is not null.
*
* @author Spencer Gibb
*/
//TODO: move to error package in Edgware
public class SendErrorFilter extends ZuulFilter { private static final Log log = LogFactory.getLog(SendErrorFilter.class);
protected static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran"; @Value("${error.path:/error}")
private String errorPath; @Override
public String filterType() {
return ERROR_TYPE;
} @Override
public int filterOrder() {
return SEND_ERROR_FILTER_ORDER;
} @Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
// only forward to errorPath if it hasn't been forwarded to already
return ctx.getThrowable() != null
&& !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
} @Override
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
ZuulException exception = findZuulException(ctx.getThrowable());
HttpServletRequest request = ctx.getRequest(); request.setAttribute("javax.servlet.error.status_code", exception.nStatusCode); log.warn("Error during filtering", exception);
request.setAttribute("javax.servlet.error.exception", exception); if (StringUtils.hasText(exception.errorCause)) {
request.setAttribute("javax.servlet.error.message", exception.errorCause);
} RequestDispatcher dispatcher = request.getRequestDispatcher(
this.errorPath);
if (dispatcher != null) {
ctx.set(SEND_ERROR_FILTER_RAN, true);
if (!ctx.getResponse().isCommitted()) {
ctx.setResponseStatusCode(exception.nStatusCode);
dispatcher.forward(request, ctx.getResponse());
}
}
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
} ZuulException findZuulException(Throwable throwable) {
if (throwable.getCause() instanceof ZuulRuntimeException) {
// this was a failure initiated by one of the local filters
return (ZuulException) throwable.getCause().getCause();
} if (throwable.getCause() instanceof ZuulException) {
// wrapped zuul exception
return (ZuulException) throwable.getCause();
} if (throwable instanceof ZuulException) {
// exception thrown by zuul lifecycle
return (ZuulException) throwable;
} // fallback, should never get here
return new ZuulException(throwable, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null);
} public void setErrorPath(String errorPath) {
this.errorPath = errorPath;
} }
在这里我们可以找到几个关键点:
1)在上述代码中,我们可以发现filter已经将相关的错误信息放到request当中了:
request.setAttribute("javax.servlet.error.status_code", exception.nStatusCode);
request.setAttribute("javax.servlet.error.exception", exception);
request.setAttribute("javax.servlet.error.message", exception.errorCause);
2)错误处理完毕后,会转发到 xxx/error的地址来处理
那么我们可以来做个试验,我们在gateway-service项目模块里,创建一个会抛出异常的filter:
package com.hzgj.lyrk.springcloud.gateway.server.filter; import com.netflix.zuul.ZuulFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; @Component
@Slf4j
public class MyZuulFilter extends ZuulFilter {
@Override
public String filterType() {
return "post";
} @Override
public int filterOrder() {
return 9;
} @Override
public boolean shouldFilter() {
return true;
} @Override
public Object run() {
log.info("run error test ...");
throw new RuntimeException();
// return null;
}
}
紧接着我们定义一个控制器,来做错误处理:
package com.hzgj.lyrk.springcloud.gateway.server.filter; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @RestController
public class ErrorHandler { @GetMapping(value = "/error")
public ResponseEntity<ErrorBean> error(HttpServletRequest request) {
String message = request.getAttribute("javax.servlet.error.message").toString();
ErrorBean errorBean = new ErrorBean();
errorBean.setMessage(message);
errorBean.setReason("程序出错");
return new ResponseEntity<>(errorBean, HttpStatus.BAD_GATEWAY);
} private static class ErrorBean {
private String message; private String reason; public String getMessage() {
return message;
} public void setMessage(String message) {
this.message = message;
} public String getReason() {
return reason;
} public void setReason(String reason) {
this.reason = reason;
}
}
}
启动项目后,我们通过网关访问一下试试:
二、关于zuul回退的问题
1、关于zuul的超时问题:
这个问题网上有很多解决方案,但是我还要贴一下源代码,请关注这个类 AbstractRibbonCommand,在这个类里集成了hystrix与ribbon。
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/ package org.springframework.cloud.netflix.zuul.filters.route.support; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonHttpResponse;
import org.springframework.cloud.netflix.ribbon.support.AbstractLoadBalancingClient;
import org.springframework.cloud.netflix.ribbon.support.ContextAwareRequest;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommand;
import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandContext;
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.client.ClientHttpResponse;
import com.netflix.client.AbstractLoadBalancerAwareClient;
import com.netflix.client.ClientRequest;
import com.netflix.client.config.DefaultClientConfigImpl;
import com.netflix.client.config.IClientConfig;
import com.netflix.client.config.IClientConfigKey;
import com.netflix.client.http.HttpResponse;
import com.netflix.config.DynamicIntProperty;
import com.netflix.config.DynamicPropertyFactory;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixCommandProperties.ExecutionIsolationStrategy;
import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.zuul.constants.ZuulConstants;
import com.netflix.zuul.context.RequestContext; /**
* @author Spencer Gibb
*/
public abstract class AbstractRibbonCommand<LBC extends AbstractLoadBalancerAwareClient<RQ, RS>, RQ extends ClientRequest, RS extends HttpResponse>
extends HystrixCommand<ClientHttpResponse> implements RibbonCommand { private static final Log LOGGER = LogFactory.getLog(AbstractRibbonCommand.class);
protected final LBC client;
protected RibbonCommandContext context;
protected ZuulFallbackProvider zuulFallbackProvider;
protected IClientConfig config; public AbstractRibbonCommand(LBC client, RibbonCommandContext context,
ZuulProperties zuulProperties) {
this("default", client, context, zuulProperties);
} public AbstractRibbonCommand(String commandKey, LBC client,
RibbonCommandContext context, ZuulProperties zuulProperties) {
this(commandKey, client, context, zuulProperties, null);
} public AbstractRibbonCommand(String commandKey, LBC client,
RibbonCommandContext context, ZuulProperties zuulProperties,
ZuulFallbackProvider fallbackProvider) {
this(commandKey, client, context, zuulProperties, fallbackProvider, null);
} public AbstractRibbonCommand(String commandKey, LBC client,
RibbonCommandContext context, ZuulProperties zuulProperties,
ZuulFallbackProvider fallbackProvider, IClientConfig config) {
this(getSetter(commandKey, zuulProperties, config), client, context, fallbackProvider, config);
} protected AbstractRibbonCommand(Setter setter, LBC client,
RibbonCommandContext context,
ZuulFallbackProvider fallbackProvider, IClientConfig config) {
super(setter);
this.client = client;
this.context = context;
this.zuulFallbackProvider = fallbackProvider;
this.config = config;
} protected static HystrixCommandProperties.Setter createSetter(IClientConfig config, String commandKey, ZuulProperties zuulProperties) {
int hystrixTimeout = getHystrixTimeout(config, commandKey);
return HystrixCommandProperties.Setter().withExecutionIsolationStrategy(
zuulProperties.getRibbonIsolationStrategy()).withExecutionTimeoutInMilliseconds(hystrixTimeout);
} protected static int getHystrixTimeout(IClientConfig config, String commandKey) {
int ribbonTimeout = getRibbonTimeout(config, commandKey);
DynamicPropertyFactory dynamicPropertyFactory = DynamicPropertyFactory.getInstance();
int defaultHystrixTimeout = dynamicPropertyFactory.getIntProperty("hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds",
0).get();
int commandHystrixTimeout = dynamicPropertyFactory.getIntProperty("hystrix.command." + commandKey + ".execution.isolation.thread.timeoutInMilliseconds",
0).get();
int hystrixTimeout;
if(commandHystrixTimeout > 0) {
hystrixTimeout = commandHystrixTimeout;
}
else if(defaultHystrixTimeout > 0) {
hystrixTimeout = defaultHystrixTimeout;
} else {
hystrixTimeout = ribbonTimeout;
}
if(hystrixTimeout < ribbonTimeout) {
LOGGER.warn("The Hystrix timeout of " + hystrixTimeout + "ms for the command " + commandKey +
" is set lower than the combination of the Ribbon read and connect timeout, " + ribbonTimeout + "ms.");
}
return hystrixTimeout;
} protected static int getRibbonTimeout(IClientConfig config, String commandKey) {
int ribbonTimeout;
if (config == null) {
ribbonTimeout = RibbonClientConfiguration.DEFAULT_READ_TIMEOUT + RibbonClientConfiguration.DEFAULT_CONNECT_TIMEOUT;
} else {
int ribbonReadTimeout = getTimeout(config, commandKey, "ReadTimeout",
IClientConfigKey.Keys.ReadTimeout, RibbonClientConfiguration.DEFAULT_READ_TIMEOUT);
int ribbonConnectTimeout = getTimeout(config, commandKey, "ConnectTimeout",
IClientConfigKey.Keys.ConnectTimeout, RibbonClientConfiguration.DEFAULT_CONNECT_TIMEOUT);
int maxAutoRetries = getTimeout(config, commandKey, "MaxAutoRetries",
IClientConfigKey.Keys.MaxAutoRetries, DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES);
int maxAutoRetriesNextServer = getTimeout(config, commandKey, "MaxAutoRetriesNextServer",
IClientConfigKey.Keys.MaxAutoRetriesNextServer, DefaultClientConfigImpl.DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER);
ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1);
}
return ribbonTimeout;
} private static int getTimeout(IClientConfig config, String commandKey, String property, IClientConfigKey<Integer> configKey, int defaultValue) {
DynamicPropertyFactory dynamicPropertyFactory = DynamicPropertyFactory.getInstance();
return dynamicPropertyFactory.getIntProperty(commandKey + "." + config.getNameSpace() + "." + property, config.get(configKey, defaultValue)).get();
} @Deprecated
//TODO remove in 2.0.x
protected static Setter getSetter(final String commandKey, ZuulProperties zuulProperties) {
return getSetter(commandKey, zuulProperties, null);
} protected static Setter getSetter(final String commandKey,
ZuulProperties zuulProperties, IClientConfig config) { // @formatter:off
Setter commandSetter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("RibbonCommand"))
.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey));
final HystrixCommandProperties.Setter setter = createSetter(config, commandKey, zuulProperties);
if (zuulProperties.getRibbonIsolationStrategy() == ExecutionIsolationStrategy.SEMAPHORE){
final String name = ZuulConstants.ZUUL_EUREKA + commandKey + ".semaphore.maxSemaphores";
// we want to default to semaphore-isolation since this wraps
// 2 others commands that are already thread isolated
final DynamicIntProperty value = DynamicPropertyFactory.getInstance()
.getIntProperty(name, zuulProperties.getSemaphore().getMaxSemaphores());
setter.withExecutionIsolationSemaphoreMaxConcurrentRequests(value.get());
} else if (zuulProperties.getThreadPool().isUseSeparateThreadPools()) {
final String threadPoolKey = zuulProperties.getThreadPool().getThreadPoolKeyPrefix() + commandKey;
commandSetter.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(threadPoolKey));
} return commandSetter.andCommandPropertiesDefaults(setter);
// @formatter:on
} @Override
protected ClientHttpResponse run() throws Exception {
final RequestContext context = RequestContext.getCurrentContext(); RQ request = createRequest();
RS response; boolean retryableClient = this.client instanceof AbstractLoadBalancingClient
&& ((AbstractLoadBalancingClient)this.client).isClientRetryable((ContextAwareRequest)request); if (retryableClient) {
response = this.client.execute(request, config);
} else {
response = this.client.executeWithLoadBalancer(request, config);
}
context.set("ribbonResponse", response); // Explicitly close the HttpResponse if the Hystrix command timed out to
// release the underlying HTTP connection held by the response.
//
if (this.isResponseTimedOut()) {
if (response != null) {
response.close();
}
} return new RibbonHttpResponse(response);
} @Override
protected ClientHttpResponse getFallback() {
if(zuulFallbackProvider != null) {
return getFallbackResponse();
}
return super.getFallback();
} protected ClientHttpResponse getFallbackResponse() {
if (zuulFallbackProvider instanceof FallbackProvider) {
Throwable cause = getFailedExecutionException();
cause = cause == null ? getExecutionException() : cause;
if (cause == null) {
zuulFallbackProvider.fallbackResponse();
} else {
return ((FallbackProvider) zuulFallbackProvider).fallbackResponse(cause);
}
}
return zuulFallbackProvider.fallbackResponse();
} public LBC getClient() {
return client;
} public RibbonCommandContext getContext() {
return context;
} protected abstract RQ createRequest() throws Exception;
}
请注意:getRibbonTimeout方法与getHystrixTimeout方法,其中这两个方法 commandKey的值为路由的名称,比如说我们访问:http://localhost:8088/order-server/xxx来访问order-server服务, 那么commandKey 就为order-server
根据源代码,我们先设置gateway-server的超时参数:
#全局的ribbon设置
ribbon:
ConnectTimeout: 3000
ReadTimeout: 3000
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
zuul:
host:
connectTimeoutMillis: 10000
当然也可以单独为order-server设置ribbon的超时参数:order-server.ribbon.xxxx=xxx , 为了演示zuul中的回退效果,我在这里把Hystrix超时时间设置短一点。当然最好不要将Hystrix默认的超时时间设置的比Ribbon的超时时间短,源码里遇到此情况已经给与我们警告了。
那么我们在order-server下添加如下方法:
@GetMapping("/sleep/{sleepTime}")
public String sleep(@PathVariable Long sleepTime) throws InterruptedException {
TimeUnit.SECONDS.sleep(sleepTime);
return "SUCCESS";
}
2、zuul的回退方法
我们可以实现ZuulFallbackProvider接口,实现代码:
package com.hzgj.lyrk.springcloud.gateway.server.filter; import com.google.common.collect.ImmutableMap;
import com.google.gson.GsonBuilder;
import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component; import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.LocalTime; @Component
public class FallBackHandler implements ZuulFallbackProvider { @Override
public String getRoute() {
//代表所有的路由都适配该设置
return "*";
} @Override
public ClientHttpResponse fallbackResponse() {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
} @Override
public int getRawStatusCode() throws IOException {
return 200;
} @Override
public String getStatusText() throws IOException {
return "OK";
} @Override
public void close() { } @Override
public InputStream getBody() throws IOException {
String result = new GsonBuilder().create().toJson(ImmutableMap.of("errorCode", 500, "content", "请求失败", "time", LocalDateTime.now()));
return new ByteArrayInputStream(result.getBytes());
} @Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
此时我们访问:http://localhost:8088/order-server/sleep/6 得到如下结果:
当我们访问:http://localhost:8088/order-server/sleep/1 就得到如下结果:
SpringCloud学习之Zuul统一异常处理及回退的更多相关文章
- SpringCloud学习之zuul
一.为什么要有网关 我们先看一个图,如果按照consumer and server(最初的调用方式),如下所示 这样我们要面临如下问题: 1. 用户面临着一对N的问题既用户必须知道每个服务.随着服务的 ...
- 服务网关zuul之三:zuul统一异常处理
我们详细介绍了Spring Cloud Zuul中自己实现的一些核心过滤器,以及这些过滤器在请求生命周期中的不同作用.我们会发现在这些核心过滤器中并没有实现error阶段的过滤器.那么这些过滤器可以用 ...
- SpringCloud 学习(5) --- Zuul(一)基本概念、配置
[TOC] Spring Cloud eureka:注册中心 服务端:提供注册 客户端:进行注册 ribbon:负载均衡(集群) Hystrix:熔断器,执行备选方案 Feign:远程调用 Zuul: ...
- SpringCloud学习笔记-zuul网关
公司目前使用的是dubbo方式实现微服务,想试水改造接口层服务为Spring Cloud, 以下是网络拓补图. 第一层负载均衡可以用nginx或者zuul(即有2层zuul), 本图画的是nginx. ...
- SpringCloud学习之Zuul路由转发、拦截和熔断处理(七)
Spring Cloud Zuul 服务网关是微服务架构中一个不可或缺的部分.通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由.均衡负载功能之外,它还具备了权限控制等功能. Sp ...
- SpringCloud学习系列之七 ----- Zuul路由网关的过滤器和异常处理
前言 在上篇中介绍了SpringCloud Zuul路由网关的基本使用版本,本篇则介绍基于SpringCloud(基于SpringBoot2.x,.SpringCloud Finchley版)中的路由 ...
- springCloud学习05之api网关服务zuul过滤器filter
前面学习了zuul的反向代理.负载均衡.fallback回退.这张学习写过滤器filter,做java web开发的对filter都不陌生,那就是客户端(如浏览器)发起请求的时候,都先经过过滤器fil ...
- SpringCloud学习笔记(6):使用Zuul构建服务网关
简介 Zuul是Netflix提供的一个开源的API网关服务器,SpringCloud对Zuul进行了整合和增强.服务网关Zuul聚合了所有微服务接口,并统一对外暴露,外部客户端只需与服务网关交互即可 ...
- Redis总结(五)缓存雪崩和缓存穿透等问题 Web API系列(三)统一异常处理 C#总结(一)AutoResetEvent的使用介绍(用AutoResetEvent实现同步) C#总结(二)事件Event 介绍总结 C#总结(三)DataGridView增加全选列 Web API系列(二)接口安全和参数校验 RabbitMQ学习系列(六): RabbitMQ 高可用集群
Redis总结(五)缓存雪崩和缓存穿透等问题 前面讲过一些redis 缓存的使用和数据持久化.感兴趣的朋友可以看看之前的文章,http://www.cnblogs.com/zhangweizhon ...
随机推荐
- 14-TypeScript简单工厂模式
在TypeScript中,要调用功能,通常在调用方通过实例化被调用方对象来调用相关方法,但这种实现在调用方和被调用方形成了强耦合的关系. 另外如果被调用方有种实现,在调用方需要根据场景去实例化不同的类 ...
- Python之旅_第一章Python入门
一.编程语言分类 1.机器语言:即计算机能听懂的二进制语言,0000 0001,直接操控硬件: 2.汇编语言:简写的英文标识符代替二进制语言,本质同样是直接操控硬件: 3.高级语言:用更贴近人类的语言 ...
- $.ajax 中的contentType
$.ajax 中的contentType 在 cnodejs.org 论坛中有一个问题,让我也很奇怪,说是 $.ajax 设置数据类型 applicaiton/json之后,服务器端(express) ...
- HTTP协议扫盲(五)HTTP请求防篡改
相关链接: http://www.cnblogs.com/ziyi--caolu/p/4742577.html 请求防重放:http://www.2cto.com/kf/201612/573045.h ...
- webservice面试题
webservice是什么? 1.基于WEB的服务,服务端整出一些资源让客户端应用访问(提供数据) 2.webservice是一个跨语言跨平台的规范(抽象) 3.是多个跨语言跨平台的应用间通信整合的方 ...
- 真正理解拉格朗日乘子法和 KKT 条件
这篇博文中直观上讲解了拉格朗日乘子法和 KKT 条件,对偶问题等内容. 首先从无约束的优化问题讲起,一般就是要使一个表达式取到最小值: \[min \quad f(x)\] 如 ...
- POJ-2965 The Pilots Brothers' refrigerator---思维题
题目链接: https://vjudge.net/problem/POJ-2965 题目大意: 一个冰箱上有4*4共16个开关,改变任意一个开关的状态(即开变成关,关变成开)时,此开关的同一行.同一列 ...
- Google Cardboard的九轴融合算法——基于李群的扩展卡尔曼滤波
Google Cardboard的九轴融合算法 --基于李群的扩展卡尔曼滤波 极品巧克力 前言 九轴融合算法是指通过融合IMU中的加速度计(三轴).陀螺仪(三轴).磁场计(三轴),来获取物体姿态的方法 ...
- Oracle表空间的管理方式
解释说明:表空间是一个逻辑概念:=> oracle 逻辑概念段区块管理方式: number one => tablespace number two=> segments Oracl ...
- MySQL 表空间传输
聊到MySQL数据迁移的话题,表空间传输时一个很实用的方法. 在MySQL 5.6 Oracle引入了一个可移动表空间的特征(复制的表空间到另一个服务器)和Percona Server采用部分备份,这 ...