1 Hystix

1.1 简介

Hystix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。

我感觉难以解释清楚,鉴于接下来的demo项目基本不会用这个模块,就过一遍代码算了。

1.2 配置并测试

模拟一下熔断:一旦请求的接口超过1s没有响应就不再继续请求,开始执行实现定义好的回滚函数,返回一个提示信息。

注意:Ribbon的重试机制和Hystrix的熔断机制有一定关系。

1.2.1 引入依赖

往user-consume即服务调用者的pom文件里面添加hystrix依赖。

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

1.2.2 开启熔断

在UserConsumeApplication中添加@EnableCircuitBreaker

可以看到注解有点多了,可以用@SpringCloudApplication代替上面三个注解

//@EnableDiscoveryClient // 开启EurekaClient功能
//@EnableCircuitBreaker //开启熔断
//@SpringBootApplication
@SpringCloudApplication //三合一注解

1.2.3 改造consume

改造consume即消费者的调用方法,记录调用接口耗费的时间,编写调用超时的回滚方法。

@HystrixCommand(fallbackMethod="queryUserByIdFallback"):声明一个失败回滚处理函数queryUserByIdFallback,当queryUserById执行超时(默认是1000毫秒),就会执行fallback函数,返回错误提示。

@Component
public class UserDao {
@Autowired
private RestTemplate restTemplate; private static final Logger logger = LoggerFactory.getLogger(UserDao.class); @HystrixCommand(fallbackMethod = "queryUserByIdFallback")
public User queryUserById(Long id){
long begin = System.currentTimeMillis();
String url = "http://user-service/user/" + id;
User user = this.restTemplate.getForObject(url, User.class);
long end = System.currentTimeMillis();
// 记录访问用时:
logger.info("访问用时:{}", end - begin);
return user;
} public User queryUserByIdFallback(Long id){
User user = new User();
user.setId(id);
user.setUsername("用户信息查询出现异常!");
return user;
}
}
@Service
public class UserService {
@Autowired
private UserDao userDao; public List<User> queryUserByIds(List<Long> ids) {
List<User> users = new ArrayList<>();
ids.forEach(id -> {
// 我们测试多次查询,
users.add(this.userDao.queryUserById(id));
});
return users;
}
}

1.2.4 改造service

即改造服务提供者,将方法随机延迟一段时间,模拟超时。

@Service
public class UserService {
@Autowired
private UserMapper userMapper; public User queryById(Long id) throws InterruptedException {
// 为了演示超时现象,我们在这里然线程休眠,时间随机 0~2000毫秒
Thread.sleep(new Random().nextInt(2000));
return this.userMapper.selectByPrimaryKey(id);
}
}

1.2.5 结果

很明显,超过1000ms的请求被回滚方法处理掉了。

1.2.6 设置Hystrix超时时间

之前我们设置的Robbin的ReadTimeout是1000ms,即请求一个接口超过1000ms未响应就请求另一个有相同功能的接口而Hystrix的超时时间默认也是1000ms,观察现象可知,先触发了熔断。但这样是不合理的,明明可以请求另一个接口得到结果,结果触发了熔断,返回的是找不到结果。

所以注意一点:Hystrix的超时时间一定要大于Robbin的重试时间。

hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMillisecond: 6000 # 设置hystrix的超时时间为6000ms

2. Feign

2.1 简介

Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。

2.2 使用Feign

Feign的引入和配置全部都在consume中进行

2.2.1 引入依赖

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2.2.2 UserConsumeApplication添加注解

@EnableFeignClients

2.2.3 编写UserClient接口

在项目中添加client包,下面新建UserClient接口

@FeignClient("user-service")
public interface UserClient {
@GetMapping("/user/{id}")
User queryUserById(@PathVariable("id") Long id);
}
  • 首先这是一个接口,Feign会通过动态代理,帮我们生成实现类。这点跟mybatis的mapper很像
  • @FeignClient,声明这是一个Feign客户端,类似@Mapper注解。同时通过value属性指定服务名称
  • 接口中的定义方法,完全采用SpringMVC的注解,Feign会根据注解帮我们生成URL,并访问获取结果

2.2.4 使用UserClient请求数据

改造UserService,改为使用UserClient请求数据

@Service
public class UserService {
//@Autowired
//private UserDao userDao; @Autowired
private UserClient userClient; //注入UserClient public List<User> queryUserByIds(List<Long> ids) {
List<User> users = new ArrayList<>();
ids.forEach(id -> {
//使用UserClient请求接口
users.add(userClient.queryUserById(id));
//users.add(this.userDao.queryUserById(id));
});
return users;
}
}

2.3 负载均衡

Feign是对请求的封装,本身集成了Ribbon负载均衡。可以对其进行配置,和单独使用Robbin的写法一样

user-service:
ribbon:
ConnectTimeout: 250 # 连接超时时间(ms)
ReadTimeout: 1000 # 通信超时时间(ms)
OkToRetryOnAllOperations: true # 是否对所有操作重试
MaxAutoRetriesNextServer: 1 # 同一服务不同实例的重试次数
MaxAutoRetries: 1 # 同一实例的重试次数

2.4 Hystrix支持

Feign也集成了Hystrix。

需要配置以开启Hystrix

feign:
hystrix:
enabled: true # 开启Feign的熔断功能

回滚方法不像之前那样写了,要专门定义一个类。

@Component
public class UserFeignClientFallback implements UserClient {
@Override
public User queryUserById(Long id) {
User user = new User();
user.setId(id);
user.setUsername("用户查询出现异常");
return user;
}
}

在UserClient接口上配置回滚类

@FeignClient(value = "user-service", fallback = UserFeignClientFallback.class)
public interface UserClient {
@GetMapping("/user/{id}")
User queryUserById(@PathVariable("id") Long id);
}

2.5.请求压缩

Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数即可开启请求与响应的压缩功能:

feign:
compression:
request:
enabled: true # 开启请求压缩
response:
enabled: true # 开启响应压缩

同时,我们也可以对请求的数据类型,以及触发压缩的大小下限进行设置:

feign:
compression:
request:
enabled: true # 开启请求压缩
mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
min-request-size: 2048 # 设置触发压缩的大小下限

注:上面的数据类型、压缩大小下限均为默认值。

3. Zuul网关

3.1 简介

Zuul是Netflix开源的微服务网关,它可以和Eureka、Ribbon和Hystrix等组件配合使用。核心是一系列的过滤器,完成身份认证与权限管理动态路由、压力测试等功能。

工作流程如下所示

不管是来自于客户端(PC或移动端)的请求,还是服务内部调用。一切对服务的请求都会经过Zuul这个网关,然后再由网关来实现 鉴权、动态路由等等操作。Zuul就是我们服务的统一入口。

3.2 搭建

3.2.1 建一个新的module

其他步骤前面已经说过,选择模块时注意选择Zuul即可。

3.2.2 引入依赖

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

3.2.3 添加注解

@SpringBootApplication
@EnableZuulProxy //开启zuul网关
@EnableDiscoveryClient //开启Eureka客户端发现功能
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}

3.3 路由功能

测试之前先把之前添加的user-service的随机延迟给注释掉

Zuul的路由功能:

  • 配置
server:
port: 10010 #服务端口
spring:
application:
name: api-gateway #指定服务名 eureka:
client:
registry-fetch-interval-seconds: 5 # 获取服务列表的周期:5s
service-url:
defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
instance:
prefer-ip-address: true
ip-address: 127.0.0.1 zuul:
prefix: /api # 添加路由前缀
routes:
user-service: # 路由id,一般与服务名相同
path: /user-service/** # 映射路径
serviceId: user-service
  • 解释:Zuul从Eureka中拉取服务列表,如果有人请求/api/user-service/**,将请求转发给服务列表中的

    user-service服务。

坑爹的一点:之前测试的时候Zuul一直报找不到user-service服务的错误,找了一段时间后来自己好了。

没有再复现出来,不知道为啥。如果报错了,可以尝试着用maven的reimport,再等等试试。

  • 由于路由id一般与服务名相同,因此zuul提供了一种简化配置,与上面实现相同功能
zuul:
prefix: /api # 添加路由前缀
routes:
user-service: /user-service/** # 这里是映射路径

访问http://localhost:10010/api/user-service/user/20即可得到结果。

3.4 过滤功能

3.4.1 ZuulFilter

ZuulFilter是过滤器的顶级父类,我们自己实现的过滤器都要继承自它。我们看看其中重要的四个方法:

public abstract ZuulFilter implements IZuulFilter{
abstract public String filterType();
abstract public int filterOrder();
boolean shouldFilter();
Object run() throws ZuulException;
}
  • shouldFilter:返回布尔值,判断过滤器是否需要执行。true表示执行,false反之。
  • run:表示具体的过滤逻辑。
  • filterType:返回字符串,表示本过滤器的类型
    • pre:请求在被路由之前执行。
    • routing:在路由请求时调用
    • post:在routing和errror过滤器之后调用
    • error:处理请求时发生错误调用
  • filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。

3.4.2 生命周期

  • 正常流程:

    • 请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
  • 异常流程:
    • 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给post过滤器,最后返回给用户。
    • 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
    • 如果是post过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达post过滤器了。

3.4.3 使用场景

  • 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
  • 异常处理:一般会在error类型和post类型过滤器中结合来处理。
  • 服务调用时长统计:pre和post结合使用。

3.4.4 模拟登录校验

添加登录过滤器

@Component
public class LoginFilter extends ZuulFilter {
@Override
public String filterType() {
// 登录校验,肯定是在前置拦截
return "pre";
}
@Override
public int filterOrder() {
// 顺序设置为1
return 1;
}
@Override
public boolean shouldFilter() {
// 返回true,代表过滤器生效。
return true;
}
@Override
public Object run() {
// 登录校验逻辑。
// 1)获取Zuul提供的请求上下文对象
RequestContext ctx = RequestContext.getCurrentContext();
// 2) 从上下文中获取request对象
HttpServletRequest req = ctx.getRequest();
// 3) 从请求中获取token
String token = req.getParameter("access-token");
// 4) 判断
if(token == null || "".equals(token.trim())){
// 没有token,登录校验失败,拦截
ctx.setSendZuulResponse(false);
// 返回401状态码。也可以考虑重定向到登录页。
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
// 校验通过,可以考虑把用户信息放入上下文,继续向后执行
return null;
}
}

3.5 负载均衡和熔断功能

Zuul内部集成了Ribbon负载均衡和Hystrix熔断器,需要配置

zuul:
retryable: true
ribbon:
ConnectTimeout: 250 # 连接超时时间(ms)
ReadTimeout: 2000 # 通信超时时间(ms)
OkToRetryOnAllOperations: true # 是否对所有操作重试
MaxAutoRetriesNextServer: 2 # 同一服务不同实例的重试次数
MaxAutoRetries: 1 # 同一实例的重试次数
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMillisecond: 6000 # 熔断超时时长:6000ms

总结

一直在想着如何配置,如何实现,容易犯一叶障目不见泰山的毛病,下面就概括一下。

Eureka 注册中心

服务提供者在里面注册,服务消费者拉取服务列表,根据服务列表去发请求并获取数据。

这其中涉及到最重要的一个问题:如何保证服务列表里面的服务是有效的

  • 对于提供者,这个问题是:你怎么才能知道我什么时候可用,什么时候不可用?

Eureka和提供者就形成了约定:提供者准备好后在Eureka里面注册,Eureka知道提供者可用了。注册后提供者每隔一段时间向Eureka发个消息说我还活着,Eureka就知道提供者依旧可用。Eureka每隔一段时间就会扫描整个服务列表,把很长时间没报告过的服务剔除出去。这样注册中心的服务列表就能始终更新,始终可用。

  • 对于消费者,这个问题是:我怎么知道你不行了?

消费者每隔一段时间就从注册中心拉取服务列表,注册中心和服务提供者的约定保证了这个列表可以信任,我就根据这个列表发出请求,得到数据。

注意上面的三个时间段,这都是可以配置的,怎么配置就根据实际情况来了。

Robbin 负载均衡

服务的消费者在拉取服务列表后,针对同一业务可能有多个可选的服务提供者。

如果一直使用其中一个,那就是传说中的"一核有难,八核围观",表现差劲,浪费资源,这就需要负载均衡了。

Ribbon提供了诸如随机、轮询等多种策略可选。

它还提供了重试机制:选择了一个服务后,如果这个服务坏了(毕竟每隔一段时间才拉取服务列表,服务列表也是每隔一段时间才更新),或者说反应太慢,到了一定时间就考虑再换一个服务。

Hystrix 熔断机制

为了避免一个模块的错误拖垮整个系统,因此将其隔离起来。

不要让一颗老鼠屎坏了一锅汤?这个机制我不太能讲清楚。

但是注意它和Ribbon的联系,一个服务没有反应,应该先用Ribbon进行重试,而不是先熔断。然后这又引出来一个问题,一个错误如果每次都被重试机制掩盖了,那系统还会被拖垮吗?还是说这个业务的服务都坏了,试来试去找不到一个好用的,才触发熔断?想不清楚,真遇到了再说吧。

Feign 请求封装

封装了RestTemplate,让请求的编码更简单。

请求必然涉及负载均衡和熔断问题,所以Feign里面也可以配置Robbin和Hystrix。

但不光如此,Feign还提供了请求压缩等小功能。

Zuul 网关

微服务提供者的接口是暴露在外的,要是谁都能用那就乱套了。

就像学校的器材室有各种器材,但是谁都能自由取用就乱了。因此我们需要一个管理器材的老师,拿器材的人来了,先判断他有没有老师的批准,再根据老师的批准决定他能拿什么器材,最后把他带到对应的器材室拿相应的器材。

Zuul就像是这个老师,负责鉴权(判断一个人能不能拿器材,能拿什么器材)、路由(领到地方拿器材)。

Zuul虽然不发请求,但是对请求有很强的干预能力,所以它也可以配置负载均衡和熔断。

SPRING CLOUD微服务DEMO-下篇的更多相关文章

  1. 【spring colud】spring cloud微服务项目搭建【spring boot2.0】

    spring cloud微服务项目搭建 =================================== 示例版本: 1.spring boot 2.0版本 2.开发工具 IntellJ IDE ...

  2. spring cloud微服务实践二

    在上一篇,我们已经搭建了spring cloud微服务中的注册中心.但只有一个注册中心还远远不够. 接下来我们就来尝试提供服务. 注:这一个系列的开发环境版本为 java1.8, spring boo ...

  3. Spring Cloud微服务(一):公共模块的搭建

    本demo后台采用spring cloud微服务,前端选用vue,进行前后端分离搭建.具体项目见git:光头才能强 创建文件夹,并分别创建以下jar工程 创建公共模块(后续有需要,还会增加).无论是d ...

  4. Spring Cloud微服务学习笔记

    Spring Cloud微服务学习笔记 SOA->Dubbo 微服务架构->Spring Cloud提供了一个一站式的微服务解决方案 第一部分 微服务架构 1 互联网应用架构发展 那些迫使 ...

  5. Spring Cloud微服务Sentinel+Apollo限流、熔断实战总结

    在Spring Cloud微服务体系中,由于限流熔断组件Hystrix开源版本不在维护,因此国内不少有类似需求的公司已经将眼光转向阿里开源的Sentinel框架.而以下要介绍的正是作者最近两个月的真实 ...

  6. Spring Cloud微服务限流之Sentinel+Apollo生产实践

    Sentinel概述 在基于Spring Cloud构建的微服务体系中,服务之间的调用链路会随着系统的演进变得越来越长,这无疑会增加了整个系统的不可靠因素.在并发流量比较高的情况下,由于网络调用之间存 ...

  7. Spring Cloud微服务系列文,服务调用框架Feign

    之前博文的案例中,我们是通过RestTemplate来调用服务,而Feign框架则在此基础上做了一层封装,比如,可以通过注解等方式来绑定参数,或者以声明的方式来指定请求返回类型是JSON.    这种 ...

  8. Spring Cloud 微服务

    https://mp.weixin.qq.com/s?__biz=MzU0OTE4MzYzMw==&mid=2247486301&idx=2&sn=f6d45860269b61 ...

  9. 一张图了解Spring Cloud微服务架构

    Spring Cloud作为当下主流的微服务框架,可以让我们更简单快捷地实现微服务架构.Spring Cloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟.经得起实际考验的服务框架组合起来 ...

  10. 如何优化Spring Cloud微服务注册中心架构?

    作者: 石杉的架构笔记 1.再回顾:什么是服务注册中心? 先回顾一下什么叫做服务注册中心? 顾名思义,假设你有一个分布式系统,里面包含了多个服务,部署在不同的机器上,然后这些不同机器上的服务之间要互相 ...

随机推荐

  1. php跨域的几种方式

    PHP实现跨域的几种形式 1.JSONP(JSON with padding)原理 利用html里面script标签可以加载其他域下的js这一特性,使用script src的形式来获取其他域下的数据, ...

  2. inclusion_tag 基本使用

    inclusion_tag的用途 inclusion_tag可以实现从后台往前端传递绑定数据的样式,一般用来动态显示模板页面中显示固定格式的数据. inclusion_tag的用法 step1: 编写 ...

  3. Hibernate使用时需要注意的几个小问题

    今天晚上玩了一下JDBC连接数据库,之后又利用Hibernate进行了数据库的访问,感觉利用Hibernate对数据库访问在文件配置好了之后确实更加简单快捷. 但是在操作的过程中也有一些细节需要注意一 ...

  4. ios overflow:scroll不顺畅解决办法

    是要在其样式里面添加这段代码就行  -webkit-overflow-scrolling: touch;

  5. hive拉链表以及退链例子笔记

    拉链表设计: 在企业中,由于有些流水表每日有几千万条记录,数据仓库保存5年数据的话很容易不堪重负,因此可以使用拉链表的算法来节省存储空间.  例子: -- 用户信息表; 采集当日全量数据存储到 (当日 ...

  6. 2.etcd集群的安装(cfssl版)

    etcd的安装注意两点 1.systemd的配置文件   2. 证书 1. 解决 systemd的问题,想安装指定版本的etcd可以通过 yum方式安装 etcd 可以获得 systemc 和 etc ...

  7. 55-python基础-python3-字典-删除键值对-del语句

    字典-键值对的彻底删除 对于字典中不再需要的信息,可使用del 语句将相应的键—值对彻底删除. 使用del 语句时,必须指定字典名和要删除的键. 注意  删除的键—值对永远消失了.

  8. each of which 用法

    each of which 在以下為 同位語,非關代. 1. An urn contains two balls, each of which is known to be either white ...

  9. JavaScript —— 字符串中使用正则表达式

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. react native 打包至iphone设备

    1.新建bundle 在自己项目的ios文件夹下新建一个文件夹取名bundle PS:ios文件夹和node_modules文件夹在同一级目录下,这个bundle文件夹名称随意取,后面要用到,但是记得 ...