SpringCloud进阶
1. 服务之间的通讯
使用支付服务调用用户服务
-
服务与服务之间远程调用,可以用json的格式返回数据,也可以返回一个对象!
支付服务与用户服务都使用User来封装对象。
抽取User作为一个公共模块,支付服务与用户服务依赖于它
①:创建一个模块
package com.hannfengyi; /** * User: Han * Date: 2020/2/14 * Time: 9:54 * Description: */ public class User { private Long id; private String username; private String intro; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getIntro() { return intro; } public void setIntro(String intro) { this.intro = intro; } public User(Long id, String username, String intro) { this.id = id; this.username = username; this.intro = intro; } public User() { } }
②:支付模块与用户模块引用它,封装数据
com.hanfengyi springcloud-user-common 1.0-SNAPSHOT ③:浏览器访问支付微服务,支付微服务调用用户微服务,用户微服务把数据返回给支付微服务,支付微服务把数据响应浏览器!
需要在java代码中发送http请求,使用Spring集成的RestTemplate工具类,它是基于Restful风格的http工具!
在支付微服务中配置类中注册RestTemplate,交给Spring管理
@SpringBootApplication public class PayServerApplication { public static void main( String[] args ) { SpringApplication.run(PayServerApplication.class); } @LoadBalanced @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
@LoadBalanced表示作为负载均衡器
在支付微服务控制器注入RestTemplate使用http发送请求!
@RestController public class PayController { @Autowired private RestTemplate restTemplate; @GetMapping("/pay/user/{id}") public User pay(@PathVariable("id") Long id){ String url = "http://localhost:1000/user/"+id; return restTemplate.getForObject(url,User.class); } }
浏览器访问支付微服务http://localhost:1000/user/1测试,可以看到响应!
2. 负载均衡器-Ribbon
上面用户服务与支付服务之间通信,仅仅是两台服务之间的通信,本身是没有注册到eureka(注册中心)的
对用户模块做集群,使用Ribbon客户端负载均衡器分发请求到不同的用户服务!
Ribbon与ngnix的区别
两者都是负载均衡器,做请求分发,ngnix是服务端负载均衡器,是一个独立的服务,Ribbon是客户端负载均衡器,集成在客户端中!
Ribbon与Feign的工作原理
支付服务调用用户服务,要指定用户服务服务名“user-server”,用户服务做了集群之后,"user-server"对应两个服务,分别有不同的ip和端口,支付服务使用Ribbon通过服务名“user-server”寻找到两个服务,然后按照负载均衡算法(轮询、随机...)对用户服务发起请求!
①:对用户服务做集群,创建一个新的用户服务,与之前的用户服务代码一样!
修改端口号为1001
eureka:
client:
serviceUrl:
defaultZone: http://localhost:3000/eureka/,http://localhost:3001/eureka/ #注册中心服务端的注册地址
instance:
prefer-ip-address: true
instance-id: user-server:1001 #使用ip进行注册
server:
port: 1001
spring:
application:
name: user-server
两个服务的spring.application.name必须一致,表示在一个集群
②:支付服务集成Ribbon
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
③:Ribbon已经集成了RestTemplate,但是要使用@LoadBalanced注解赋予它具备负载均衡的能力,在支付微服务配置类中配置!
@SpringBootApplication
public class PayServerApplication
{
public static void main( String[] args )
{
SpringApplication.run(PayServerApplication.class);
}
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
④:修改url,在user-server这个集群中寻找服务
@RestController
public class PayController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/pay/user/{id}")
public User pay(@PathVariable("id") Long id){
String url = "http://user-server/user/"+id;
return restTemplate.getForObject(url,User.class);
}
}
3. 负载均衡器-Feign
Feign:掉用本地接口的方式调用远程服务,与Ribbon的不同的是,解决了参数过多拼接url带来的麻烦!
-
新建一个Order服务,使用Feign负载均衡器,同样的访问用户服务!
①:导入Feign的依赖
org.springframework.cloud spring-cloud-starter-openfeign ②:配置端口号
eureka: client: serviceUrl: defaultZone: http://localhost:3000/eureka/,http://localhost:3001/eureka/ #注册中心服务端的注册地址 instance: prefer-ip-address: true instance-id: pay-server:4000 #使用ip进行注册 server: port: 4000 spring: application: name: order-server
③:创建一个接口
@FeignClient(value = "user-server") public interface UserFeignClient { @GetMapping(value = "/user/{id}") User user(@PathVariable("id") Long id); }
@FeignClient(value = "user-server"),参数需要目标服务名,通过服务名去注册中心找到目标服务,再根据@GetMapping(value = "/user/{id}")资源路径找到目标服务的Controller!
④:Controller中注入接口
@RestController public class OrderController { @Autowired private UserFeignClient userFeignClient; @GetMapping("/order/user/{id}") public User order(@PathVariable("id") Long id){ return userFeignClient.user(id); } }
在浏览器中访问http://localhost:4000/order/user/1 该路径时,会创建UserFeignClient这个接口的代理对象,调用user方法,在这个方法中,Feign负载均衡器会根据指定的服务名和资源路径去寻找到多台服务,此时采用轮询或者随机等算法,对其中某台服务发起调用!
⑤:修改Feign的算法为随机算法,只需要在配置类中配置一个Bean即可
@Bean public IRule randomRule(){ return new RandomRule(); }
4. Hystrix断路器(熔断器)
4.1:概述
Hystrix:用来对微服务做隔离和监控,防止一个微服务故障,导致整个微服务群被拖垮引发的雪崩效应!
Hystrix通过如下机制来解决雪崩效应问题 :
①:资源隔离(限制请求数量):包括线程池隔离和信号量隔离,限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。
线程池隔离:通过线程池中线程数量来限制请求并发量!
信号量隔离:使用计数器计数的方式,当请求数量达到被设置的阀值的时候,限制请求并发量!
②:熔断:当请求失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),
正常情况下,断路器处于关闭状态(Closed),
如果调用持续出错或者超时,电路被打开进入熔断状态(Open),后续一段时间内的所有调用都会被拒绝(Fail Fast),
一段时间以后,保护器会尝试进入半熔断状态(Half-Open),允许少量请求进来尝试,
如果调用仍然失败,则回到熔断状态
如果调用成功,则回到电路闭合状态;
③:降级机制:超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。
④:缓存:提供了请求缓存、请求合并实现。
4.2:Ribbon实现
1.在消费者服务集成Hystrix,不在提供者集成,如果用户服务故障,那么Hystrix也会故障,不能起到熔断的作用!
在支付服务中导入Hystrix依赖
org.springframework.cloud spring-cloud-starter-netflix-hystrix 2.在配置类中开启Hystrix功能
@SpringBootApplication @EnableCircuitBreaker public class PayServerApplication { public static void main( String[] args ) { SpringApplication.run(PayServerApplication.class); } @LoadBalanced @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
@EnableCircuitBreaker注解的作用开启Hystrix功能
3.在需要发生熔断的服务方法中使用注解 @HystrixCommand(fallbackMethod = "ErrorMethod"),如果发生错误,会执行指定的托底方法,触发熔断机制,返回托底数据!
@RestController public class PayController { @Autowired private RestTemplate restTemplate; @GetMapping("/pay/user/{id}") @HystrixCommand(fallbackMethod = "ErrorMethod") public User pay(@PathVariable("id") Long id){ // String url = "http://localhost:1000/user/"+id; String url = "http://user-server/user/"+id; return restTemplate.getForObject(url,User.class); } public User ErrorMethod(@PathVariable("id") Long id){ return new User(-1L, "无此用户", "用户服务错误!"); } }
上面这个例子表示在访问user-server服务集群的时候,如果发生异常,或者该服务不可用,则会执行ErrorMethod该托底方法中的代码!
4.3:Feign的实现
因为Feign已经集成了Hystrix,不需要导入依赖
①:在application.yml中开启Hystrix
feign: hystrix: enabled: true
②:在Feign用于访问服务的接口中@FeignClient注解中添加属性fallback用来指定托底方法
@FeignClient(value = "user-server",fallback = UserFeignClientFallBack.class) public interface UserFeignClient { @GetMapping(value = "/user/{id}") User user(@PathVariable("id") Long id); }
③:因为fallback的属性要求是一个字节码对象,需要创建一个类,必须实现该接口,重写方法,重写的方法就是托底方法,该类必须交给Spring管理!
@Component public class UserFeignClientFallBack implements UserFeignClient { @Override public User user(Long id) { return new User(-1l, "用户不存在", "用户服务异常!"); } }
5. Zuul网关
Zuul网关:作为整个微服务群的请求入口,可以保护微服务群的安全,可以通过Zuul来实现权限校验、限流、日志、监控、负载均衡等。
Zuul作为一个独立的应用,默认集成了Ribbon,其本质是一个servlet
1.集成Zuul
①:新建一个模块,创建Zuul服务
②:导入依赖
org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-netflix-zuul ③:在配置类中使用@EnableZuulProxy开启Zuul
@SpringBootApplication @EnableDiscoveryClient @EnableEurekaClient @EnableZuulProxy public class ZuulServer5000 { public static void main( String[] args ) { SpringApplication.run(ZuulServer5000.class); } }
④:application.yml的配置
eureka: client: serviceUrl: defaultZone: http://localhost:3000/eureka/,http://localhost:3001/eureka/ #注册中心服务端的注册地址 instance: prefer-ip-address: true instance-id: zuul-server:5000 #使用ip进行注册 server: port: 5000 spring: application: name: zuul-server
Zuul是需要注册到注册中心的,此时通过浏览器访问用户服务http://localhost:5000/user-server/user/1 ,请求到达Zuul,Zuul会通过服务名user-server去注册中心寻找到目标服务,通过Ribbon负载均衡器发起远程调用!
2.Zuul访问地址的优化
通过上面的例子可以看出,如果我们需要访问某个服务,需要在地址栏中暴露出服务名字,显然这是不好的,可以在application.yml中配置一下属性,通过指定的别名来访问!
zuul: ignoredServices: '*' routes: pay-server: /pay/** user-server: /user/** order-server: /order/**
ignoredServices:表示忽略使用服务名的方式访问
routes:可以指定访问某个服务名使用的资源路径!
现在如果要访问用户服务可以通过http://localhost:5000/user/user/1来进行访问!这里的user就已经代表了user-server!
3.Hystrix超时处理
使用Zuul网关发起请求,发生异常,触发熔断机制,执行托底方法超时在application.yml中配置
zuul: host: connect-timeout-millis: 150000 #HTTP连接超时要比Hystrix的大 socket-timeout-millis: 150000 #socket超时 ribbon: #ribbon超时 ReadTimeout: 50000 ConnectTimeout: 50000 hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 100000
6. filter实现登录拦截
- 自定义类继承ZuulFilter重写核心方法!把该类交给spring容器管理!
@Component public class LoginZuulFilter extends ZuulFilter { @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return 1; } @Override public boolean shouldFilter() { //1.获得当前请求上下文 RequestContext currentContext = RequestContext.getCurrentContext(); //2.根据当前请求上下文获得请求对象 HttpServletRequest request = currentContext.getRequest(); //3.拿到请求头的uri路径 String requestURI = request.getRequestURI(); //4.如果访问路径是"/login",则不做校验 if(StringUtils.isEmpty(requestURI) && requestURI.endsWith("login")){ return false; } return true; } @Override public Object run() throws ZuulException { //1.获得当前请求上下文 RequestContext currentContext = RequestContext.getCurrentContext(); //2.根据当前请求上下文获得请求对象,和响应对象 HttpServletRequest request = currentContext.getRequest(); HttpServletResponse response = currentContext.getResponse(); //3.拿到请求头的token令牌 String token = request.getHeader("token"); Map
hashmap = new HashMap<>(); //4.如果没有token信息,返回提示信息 if(StringUtils.isEmpty(token)){ hashmap.put("success", false); hashmap.put("message","当前用户还未登录"); try { response.getWriter().print(JSONObject.toJSONString(hashmap)); //5.阻止请求继续往下执行 currentContext.setSendZuulResponse(false); } catch (IOException e) { e.printStackTrace(); } } return null; } } 使用阿里巴巴的fastjson可以将对象转换为json格式
com.alibaba fastjson 1.2.62
shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
run:过滤器的具体业务逻辑。
-
filterType:返回字符串,代表过滤器的类型。包含以下4种:
pre:请求在被路由之前执行
routing:在路由请求时调用
post:在routing和errror过滤器之后调用
error:处理请求时发生错误调用
filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。
- 过滤器执行周期
正常流程:
- 请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
- 异常流程:
- 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
- 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
- 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。
7. SpringCloud Config分布式配置中心
7.1 概述
在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。
7.2 作用
①:集中管理配置文件
②:动态化的配置更新,不同环境不同部署
③:运行时动态调整配置,不需要在部署服务的时候编写配置文件,服务会向配置中心统一拉取自己的配置信息
④:配置发生变动时,服务不需要重启就可以感知配置变化,自动应用最新的配置
⑤:配置信息以restful接口的方式暴露
7.3 与git/svn集成
①:在github/码云上创建仓库,创建所需服务的配置文件
②:新建一个config-server独立的应用,maven中导入依赖
org.springframework.cloud
spring-cloud-config-server
③:在配置类中使用注解@EnableConfigServer开启配置中心支持
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication
{
public static void main( String[] args )
{
SpringApplication.run(ConfigServerApplication.class);
}
}
④:在配置文件application.yml中配置git的配置,拉取配置文件
eureka:
client:
serviceUrl:
defaultZone: http://localhost:3000/eureka/,http://localhost:3001/eureka/ #注册中心服务端的注册地址
instance:
prefer-ip-address: true
instance-id: config-server:6000 #使用ip进行注册
server:
port: 6000
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/immerseshe/spring-cloud-config.git
username: ***
password: ***
这里的url要指向仓库地址
⑤:测试,访问http://localhost:6000/文件名.yml,会返回配置数据!
如果遇到 此网址使用了一个通常用于网络浏览以外目的的端口。出于安全原因,Firefox 取消了该请求。错误
解决方式:
在Firefox地址栏输入 about:config
,
右键新建一个字符串键:
首选项名称 填写 network.security.ports.banned.override 。
值 的填写有三种方式:
a、如果只有单个端口号时,只接输入的端口号即可,如 6666 ;
b、如果要填写多个端口号时,端口号之间用逗号隔开,如:6666,7777,8888 ;
c、一个一个添加端口号特别麻烦,在能保证安全的前提下,还简化直接输入 0-65535 。
添加端口号允许访问!
7.4 客户端配置
在服务中配置不再使用自身的配置文件,而是去配置中心拉取配置信息!
①:导入config客户端依赖
org.springframework.cloud
spring-cloud-starter-config
②:删除掉原来的application.yml,创建bootstrap.yml,该配置文件相比较application.yml的优先级更高。在配置文件中指向配置中心的服务地址,配置文件名字和环境
spring:
cloud:
config:
name: order-server
profile: dev
label: master
uri: http://localhost:6000
③:测试,先在git上修改配置,修改端口号,启动服务,查看端口是否变化!