Feign
feign是基于Ribbon 的服务调用方式,SpringcloudNetFix之Ribbon中写了ribbn的调用方式,是注册restTemplate调用getForObject方法发送请求,需要在请求路径中拼接参数,在参数较少的情况下,这个方法还是可行的。但参数一旦增大就不可用了,所以这里了解一下Feign。
Feign它基于Ribbon进行了封装,把一些负责的url和参数处理细节屏蔽起来,我们只需要简单编写Fiegn的客户端接口就可以像调用本地service去调用远程微服务。
Feign实战
在上篇文章SpringcloudNetFix之Ribbon的基础上再添加一个子项目,取名为pay
通用步骤
1.导入依赖
<dependencies>
<!--spring-cloud-starter-netflix-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--导入Feign的包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- user类的项目依赖 -->
<dependency>
<groupId>com.huawei</groupId>
<artifactId>springboot-netfix-pojo-user</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
2.yml文件
eureka:
client:
serviceUrl:
defaultZone: http://localhost:10010/eureka/
instance:
instance-id: server-pay
spring:
application:
name: server-pay
server:
port: 10040
3.启动类
@SpringBootApplication
@EnableFeignClients
public class PayApplication {
public static void main(String[] args) {
SpringApplication.run(PayApplication.class, args);
}
}
注意:这里是@EnableFeignClients
FeignClient配置
创一个Client类,UserFeignClient
@FeignClient("server-user")
public interface UserFeignClient {
@GetMapping("/user/{id}")
User getById(@PathVariable("id")Long id);
}
注意这是一个interface
@FeignClient的参数是,userServer子项目yml文件中写的服务的名字
下面的方法和userServer中的接口方法要一样(请求方式、请求地址和参数,返回值类型),但要写成接口。
payController
@Autowired
private UserFeignClient userFeignClient;
@GetMapping("/pay/{id}")
public User getById(@PathVariable("id")Long id){
return userFeignClient.getById(id);
}
}
Ribbon轮询底层
轮询:就是挨个分配,他的底层就是获取全部的server的个数返回一个数组,如果长度是0,则返回null。再定义一个增长int 的 index去模运算 数组长度的结果就是得到的server的下标,再非空判断再返回。
可以自定义负载均衡的算法,在启动类子定义Bean
//负载均衡算法
@Bean
public RandomRule randomRule(){
return new RandomRule();
}
这个是随机算法
Hystrix熔断器
说Hystrix就需要先了解几个概念
1.雪崩
一个服务故障产生的连锁反应,导致这个微服务崩溃 。举例来说,就像在路上有一辆很大的车抛锚了,其他的车都只能堵在其后边,导致整个道路堵死。
有雪崩就有针对方法
2.服务熔断
服务多次访问失败,就会被标记为‘熔断状态’,如果某个请求去访问一个熔断状态的服务,会快速失败,触发服务降级:目的-防止请求阻塞
3.降级机制
当一个业务(请求)失败/超时,就会走预先设置好的第二种方案(也就是降级逻辑)。
4.缓存
提供了请求缓存、请求合并实现 , 在高并发的场景之下,Hystrix请求缓存可以方便地开启和使用请求缓存来优化系统,达到减轻高并发时请求线程的消耗、降低请求响应时间的效果。
Hystrix应用
1.导包
<!-- Hystrix相关包 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
我们用上面的pay项目,因为到了openFeign的依赖就不用再导入上面的依赖了
2.修改UserFeignClient
@FeignClient(value = "server-user",fallbackFactory = UserFeignClientFallbackFactory.class)
public interface UserFeignClient {
@GetMapping("/user/{id}")
User getById(@PathVariable("id")Long id);
}
fallbackFactory的参数是一个类,需要自己写
3.UserFeignClientFallbackFactory类
@Component
public class UserFeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {
@Override
public UserFeignClient create(Throwable throwable) {
return new UserFeignClient() {
@Override
public User getById(Long id) {
throwable.printStackTrace();
return new User(-1L,"服务器异常,请稍后重试",0,"");
}
};
}
}
实现FallbackFactory<UserFeignClient>接口,泛型是之前的UserFeignClient接口,重写create方法,再重写UserFeignClient的getById方法,这个方法就是降级机制方法。
4.测试
写完后可以关闭userServer,结果就会输出降级机制方法的值
5.其他方法
还有一个方法,不用fallbackFactory ,直接用fallback,代码如下
@FeignClient(value = "user-server",fallback = UserFeignClientFallback.class)
public interface UserFeignClient {
@GetMapping(value = "/user/{id}" )
User getById(@PathVariable("id")Long id);
}
//让Spring扫描到该托底类
@Component
public class UserFeignClientFallback implements UserFeignClient {
//日志打印器
private Logger log = LoggerFactory.getLogger(UserFeignClientFallback.class);
@Override
public User getById(Long id) {
log.info("用户服务不可用");
//托底数据
return new User(-1l,"无此用户","用户服务不可用");
}
}
服务网关-spring cloud zuul
什么是zuul
Zuul是Netflix的开源项目,Spring Cloud将其收纳成为自己的一个子组件。zuul用的是多线程阻塞模型,它本质上就是一个同步 Servlet,这样的模型比较简单,他的问题是多线程之间上下文切换是有开销的,线程越多开销就越大。线程池数量固定意味着能力接受的请求数固定,当后台请求变慢,面对大量的请求,线程池中的线程容易被耗尽,后续的请求会被拒绝。
在Zuul 2.0中它采用了 Netty 实现异步非阻塞编程模型,异步非阻塞模式对线程的消耗比较少,对线程上线文切换的消耗也比较小,并且可以接受更多的请求。它的问题就是线程模型比较复杂,要求深究底层原理需要花一些功夫。
Zuul 在云平台上提供动态路由(请求分发),监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门,也要注册入Eureka,
zuul搭建
1.导依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.启动类
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class,args);
}
}
3.yml文件
eureka:
client:
serviceUrl:
defaultZone: http://localhost:10010/eureka/
instance:
instance-id: server-zuul
spring:
application:
name: server-zuul
server:
port: 10050
zuul:
prefix: "/servers" #统一访问前缀
ignoredServices: "*" #禁用掉使用浏览器通过服务名的方式访问服务
routes:
server-pay: "/pay/**" #指定pay-server这个服务使用 /pay路径来访问 - 别名
server-order: "/order/**" #指定order-server这个服务使用 /order路径来访问
server-user: "/user/**" #指定order-server这个服务使用 /order路径来访问
配置完yml,网页的访问路径就改为:localhost:10050/servers/order/order/1
servers为前缀,第一个order为orderServer服务名的别名,不想给访问的人看,就可以配置别名,第二个order为orderController的mapping路径
zuul自定义Filter
zuul的工作原理
zuul的底层是通过各种Filter来实现的,zuul中的filter按照执行顺序分为了“pre”前置(”custom”自定义一般是前置),“routing”路由,“post”后置,以及“error”异常Filter组成,当各种Filter出现了异常,请求会跳转到“error filter”,然后再经过“post filter” 最后返回结果,下面是Filter的执行流程图:
提示:
-
正常流程:
-
请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
-
-
异常流程:
-
整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
-
如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
-
如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。
-
ZuulFilter
自定义一个filter类去继承ZuulFilter重写四个方法,我这里以登录拦截请求为例
// 需要交给spring容器管理
@Component
public class LoginCheckFilter extends ZuulFilter {
@Override
public String filterType() {
// 返回filter的类型
return "pre";
}
@Override
public int filterOrder() {
// 在有很多的prefilter时,想当于filter的优先级,越小的越先执行
return 0;
}
@Override
public boolean shouldFilter() {
// RequestContext是他提供的工具
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
// 是否包含"/login" 如果包含返回 false,就不执行下面的run方法,true执行
return !request.getRequestURI().contains("/login");
}
@Override
public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
HttpServletResponse response = currentContext.getResponse();
// 设置字符集,否者会乱码
response.setContentType("application/json;charset=utf-8");
if(request.getHeader("token") == null){
// 是否走下个filter是看currentContext.setSendZuulResponse的值;false就不走,默认是true
// 和下面的return null 无关
currentContext.setSendZuulResponse(false);
try {
// 提示信息
response.getWriter().println("快去请如来佛祖");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return null;
}
}
结果