spring微服务(顺序由简入难易于理解)
一.为微服务应用增加健康监控
1.在 build.gradle 文件 dependencies 属性中增加
compile('org.springframework.boot:spring-boot-starter-actuator')
2.启动程序后在地址栏输入127.0.0.1:8801/actuator/health即可查看当前服务的状态
status有UP(正常运行)、DOWN、OUT_OF_SERVICE、UN_KNOWN等
3.在application中我们可以公开一些信息供外部访问,例如
开放信息
info:
app:
name: 提供用户查询服务
version: 1.0
4.注:在springboot2.x版本中默认只暴露/info和/health,可以使用如下配置增加入口:
management:
endpoints:
web:
exposure:
include: bus-refresh,refresh
二.微服务间直接调用api
1.不推荐直接调用,一般使用@RestTemplate调用其他服务的api,在启动类实例化对象,使用@Bean作用是实例化一个Bean并使用该方法的名称命名
示例代码:
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
2.在controller中调用
示例代码:
@RestController
public class UserController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getUser")
public String getUser(){
return restTemplate.getForObject("http://127.0.0.1:8801/user/get", String.class);
}
}
三.微服务的注册与发现
0.通过注册与发现可以实时的获取服务的配置,而不需要去手动修改
1.新建项目添加依赖
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-server')
2.在application.yml文件中添加配置
#Eureka 服务端信息
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://localhost:8808/eureka
各配置的作用详解:
register-with-eureka 表示是否把自己注册到Eureka Server,默认true;
fetch-registry表示是否从其他Eureka Server获取注册信息,默认true;
service-url.defaultZone设置与Enreka Server交互的地址,多个地址用逗号隔开(英文逗号)
3.在启动类加注解@EnableEurekaServer标识是Eureka Server
4.在需要被注册的微服务工程上添加依赖
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
5.application.yml信息
application.name用于指定注册到Eureka Server上的应用名称;
instance.prefer-ip-adress表示将自己的ip注册到EurekaServer,若不配置该属性,或者设置为false,则表示注册所在操作系统的hostname到Eureka Server。
6.在需要被注册的启动类上添加注解@EnableDiscoveryClient注解,声明这是一个Eureka Client。
7.调用其他服务可以通过DiscoveryClient寻找其他服务
示例代码:
@GetMapping("/getUser")
public String getUser() {
List<ServiceInstance> list = discoveryClient.getInstances("service-user");
if (list.size() > 0) {
String url = list.get(0).getUri().toString();
return restTemplate.getForObject(url + "/user/get", String.class);
} else {
return null;
}
}
}
四.使用Ribbon实现负载均衡##
0.注册一个微服务后启动多个实例就相当于用同一个服务名称启动了多个服务节点,达到了集群的目的,这时消费端与服务端之间的的调用就应该采用Ribbon
1.添加依赖
compile('org.springframework.cloud:spring-cloud-starter-netflix-ribbon')
2.启动类添加@LoadBanlanced开启负载均衡能力
代码示例:
@SpringBootApplication
public class LearnConsumerUserApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(LearnConsumerUserApplication.class, args);
}
}
3.修改调用服务端的代码
示例代码:
@RestController
public class UserController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getUser")
public String getUser() {
return restTemplate.getForObject("http://service-user/user/get", String.class);
}
}
五.使用Feign实现声明式REST调用
1.在消费端添加依赖
compile('org.springframework.cloud:spring-cloud-starter-openfeign')
官网M2和M6资源有差异,阿里云的M2和官网的M2也有差异,有点奇怪,保险起见增加如下2个依赖:
compile('org.springframework.cloud:spring-cloud-openfeign-core')
compile('io.github.openfeign:feign-java8')
2.修改消费端启动类,增加注解@ EnableFeignClients,去掉之前的@restTemplate和负载均衡,Feign做了整合
3.创建接口Feign,增加@Feign(name=“service-user”)注解适应Eureka和Ribbon
@FeignClient("service-user")
public interface UserFeignClient {
@GetMapping("/user/get")
public String getUser();
@GetMapping("/user/type1/{id}")
public String getUserById1(@PathVariable(value="id") String id);
@GetMapping("/user/type2")
public String getUserById2(@RequestParam("id") String id);
@PostMapping("/user/type3")
public String getUserById3(@RequestParam Map<String, Object> reqMap);
@PostMapping("/user/type4")
public String getUserById4(@RequestBody String jsonObj);
}
4.修改消费端的controller,使用上面的接口
@RestController
public class UserController {
@Autowired
private UserFeignClient userFeignClient;
@GetMapping("/getUser")
public String getUser() {
return userFeignClient.getUser();
}
@GetMapping("/getUser1/{id}")
public String getUserById1(@PathVariable String id) {
return userFeignClient.getUserById1(id);
}
@GetMapping("/getUser2")
public String getUserById2(@RequestParam("id") String id) {
return userFeignClient.getUserById2(id);
}
@PostMapping("/getUser3")
public String getUserById3(@RequestParam Map<String, Object> reqMap) {
return userFeignClient.getUserById3(reqMap);
}
@PostMapping("/getUser4")
public String getUserById4(@RequestBody String jsonObj) {
return userFeignClient.getUserById4(jsonObj);
}
}
六.使用Hystrix实现微服务的容错处理
- Hystrix 能使你的系统在出现依赖服务失效的时候,通过隔离系统所依赖的服务,防止服务级联失败,同时提供失败回退机制,更优雅地应对失效,并使你的系统能更快地从异常中恢复。
都能做什么?
1、防止单个依赖耗尽容器(例如 Tomcat)内所有用户线程
2、降低系统负载,对无法及时处理的请求快速失败(fail fast)而不是排队
3、提供失败回退,以在必要时让失效对用户透明化
4、使用隔离机制(例如『舱壁』/『泳道』模式,熔断器模式等)降低依赖服务对整个系统的影响
5、针对系统服务的度量、监控和报警,提供优化以满足近实时性的要求
6、在 Hystrix 绝大部分需要动态调整配置并快速部署到所有应用方面,提供优化以满足快速恢复的要求
7、能保护应用不受依赖服务的整个执行过程中失败的影响,而不仅仅是网络请求
1.消费端增加依赖
compile('org.springframework.cloud:spring-cloud-starter-netflix-hystrix')
2. 增加一个 UserFeignClientFallback 实现 FallbackFactory
@Component
public class UserFeignClientFallback implements FallbackFactory<UserFeignClient> {
@Override
public UserFeignClient create(Throwable cause) {
return new UserFeignClient() {
@Override
public String getUser() {
//可把日志记录到其他地方
System.out.println("失败原因:" + cause.getMessage());
return "调取用户信息异常";
}
@Override
public String getUserById1(String id) {
return "调取用户信息异常";
}
@Override
public String getUserById2(String id) {
return "调取用户信息异常";
}
@Override
public String getUserById3(Map<String, Object> reqMap) {
return "调取用户信息异常";
}
@Override
public String getUserById4(String jsonObj) {
return "调取用户信息异常";
}
};
}
}
- UserFeignClient 增加 fallbackFactory配置
@FeignClient(name = "service-user", fallbackFactory = UserFeignClientFallback.class)
public interface UserFeignClient {
@GetMapping("/user/get")
public String getUser();
@GetMapping("/user/type1/{id}")
public String getUserById1(@PathVariable(value = "id") String id);
@GetMapping("/user/type2")
public String getUserById2(@RequestParam("id") String id);
@PostMapping("/user/type3")
public String getUserById3(@RequestParam Map<String, Object> reqMap);
@PostMapping("/user/type4")
public String getUserById4(@RequestBody String jsonObj);
}
4.application.yml文件中开启hystrix
feign: hystrix: enabled: true
七.使用Zuul构建为服务网关
- 为什么要使用微服务网关?
1、客户端会多次请求不同的微服务,增加了客户端的复杂性。
2、存在跨域请求,在一定场景下处理相对复杂。
3、认证复杂,每个服务都需要独立认证。
4、难以重构,随着项目迭代可能需要重新划分微服务,如果客户端直接与微服务通信,那么重构也会很难实施。
5、某些微服务可能使用了防火墙,直接访问会有一定的困难。
以上问题借助微服务网关既可以解决。
Zuul简介
Zuul是Netflix开源的微服务网关,它可以和Eureka、Ribbon、Hystrix等组件配合使用。Zuul的核心是一系列的过滤器,这些过滤器可以完成一下功能:
1、验证与安全保障: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求。
2、审查与监控: 在边缘位置追踪有意义数据及统计结果,从而为我们带来准确的生产状态结论。
3、动态路由: 以动态方式根据需要将请求路由至不同后端集群处。
4、压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平。
5、负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求。
6、静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群。
7、多区域弹性: 跨越AWS区域进行请求路由,旨在实现ELB使用多样化并保证边缘位置与使用者尽可能接近。
1.开始构建一个微服务网关,新建工程,引入依赖
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
compile('org.springframework.cloud:spring-cloud-starter-netflix-zuul')
2.修改启动类,增加@EnableZuulProxy注解,代表这是一个网关代理服务,该代理使用Ribbon来定位注册在Eureka上的服务,同时整合了Hystrix
3.填写application.yml配置类
#服务端口
server:
port: 8888
#Eureka配置信息
spring:
application:
name: gateway-zuul
eureka:
client:
service-url:
defaultZone: http://localhost:8808/eureka
instance:
prefer-ip-address: true
4.注:一些实用的配置
#只路由指定服务,忽略其他服务,并忽略路径中含有admin的请求, 映射路径(含已注册的服务和外部服务)
zuul:
ignored-services: '*'
ignored-patterns: /**/admin/**
routes:
service-user: /userSer/**
consumer-user: /userCon/**
demo-rooter:
url: http://10.2.6.33:8100/
path: /demo/**
a、ignored-services: '*' 代表忽略所有微服务, 只路由routes中的配置
b、ignored-patterns: //admin/ 代表忽略含有admin的路径
c、service-user: /userSer/** 把原来的用户服务端映射到 /userSer 上, 类似取了一个别名
这样原来访问 http://127.0.0.1:8888/service-user/user/get 就变成了 http://127.0.0.1:8888/suserSer/user/get
d、demo-rooter: 这个自定义的路由,名字可以随便取, 配置指定的路径和目标地址
例子中 http://127.0.0.1:8888/demo/getSome 相当于访问了 http://10.2.6.33:8100/getSome
八.使用spring Cloud Config统一配置微服务配置##
- 在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config为服务端和客户端提供了分布式系统的外部化配置支持。配置服务中心采用Git的方式存储配置文件,因此我们很容易部署修改,有助于对环境配置进行版本管理。
1.创建server工程,增加依赖
compile('org.springframework.cloud:spring-cloud-config-server')
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
2.启动类增加注解@EnableConfigServer注解
3.application.yml配置
服务端口
server:
port: 8099
配置信息所在git仓库信息
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/seley6/test
search-paths: config
username:
password:
eureka:
client:
service-url:
defaultZone: http://localhost:8808/eureka
instance:
prefer-ip-address: true
3.创建一个配置的客户端,添加依赖
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.cloud:spring-cloud-starter-config')
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
注:增加actuator是为了实现客户端通过 /actuator/refresh 实现配置动态更新。
4.创建bootstrap.yml配置文件
配置配置服务端
spring:
application:
name: config-client
cloud:
config:
discovery:
enabled: true
service-id: config-server
profile: dev
label: master
eureka:
client:
service-url:
defaultZone: http://localhost:8808/eureka
instance:
prefer-ip-address: true
开放刷新入口
management:
endpoints:
web:
exposure:
include: refresh
5.向远程git仓库提交文件 config-client-dev.yml
服务端口
server:
port: 9901
info:
app:
version: 1.0
配置服务的路劲规则:
/{label}/{application}-{profile}.yml
label相当于git分支,application是应用名,profile是属性名, 所以对应步骤2 我们应该增加一个config-client-dev.yml文件并提交到master。
6.创建一个api获得version信息
@RestController
public class TestController {
@Value("${info.app.version}")
private String version;
@GetMapping("/getVersion")
public String getUser() {
return this.version;
}
}
7.依次启动服务
①、启动 learn-discover-eureka (8808)
②、启动 learn-config-server (8099)
③、启动 learn-config-client (端口我们在远程git中配置的是9901)
④、访问如下链接即可查到对应远程git中的配置信息
http://127.0.0.1:8099/config-client/dev
⑤、访问如下链接我们 可以查到版本结果
http://127.0.0.1:9901/getVersion
⑥、修改 config-client-dev.yml 中version信息,再次提交到git
⑦、访问步骤④中的地址,发现配置已经修改, 但访问步骤⑤还是返回之前的配置信息, 这时我们要POST方式请求 http://127.0.0.1:9901/actuator/refresh, 之后再次访问步骤④,发现配置信息已经更新。
注意:刷新并不是重启服务,所以端口信息还有其他需要启动时才生效的配置是无法通过此方式动态更新的, 需要手动重启服务。
8.自动刷新配置
前文我们讨论了使用/refresh 端点手动刷新配置,但是如果所有微服务节点的配置都需要手动去刷新的话,那必然是一个繁琐的工作,并且随着系统的不断扩张,会变得越来越难以维护。因此,实现配置的自动刷新是很有必要的,本节我们讨论使用Spring Cloud Bus实现配置的自动刷新。
Spring Cloud Bus提供了批量刷新配置的机制,它使用轻量级的消息代理(例如RabbitMQ、Kafka等)连接分布式系统的节点,这样就可以通过Spring Cloud Bus广播配置的变化或者其他的管理指令。
下面我们以RabbitMQ为例,为大家讲解如何使用Spring Cloud Bus实现配置的自动刷新。
①、 安装RabbitMQ。 去另一篇文章中去看 Windows下安装RabbitMQ
②、服务端 learn-config-server 增加依赖
compile('org.springframework.cloud:spring-cloud-starter-bus-amqp')
③、application.yml 中增加 rabbitmq配置
(事实上我们可以不配置服务端信息,我们通过任一客户端都可以刷新其他微服务配置,但为了均衡客户端职责,同时服务端也是高可靠的,所以我们用服务端 /bus/refresh 来更新其他客户端)
#服务端口
server:
port: 8099
#配置信息所在git仓库信息
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/seley6/test
search-paths: config
username:
password:
rabbitmq:
host: 10.2.6.33
port: 5672
username: gaojinliang
password: [密码]
eureka:
client:
service-url:
defaultZone: http://localhost:8808/eureka
instance:
prefer-ip-address: true
#开放全局配置刷新入口
management:
endpoints:
web:
exposure:
include: bus-refresh
④、客户端 learn-config-client 增加依赖
compile('org.springframework.cloud:spring-cloud-starter-bus-amqp')
⑤、bootstrap.yml 中增加对rabbitmq的配置
#配置配置服务端
spring:
application:
name: config-client
cloud:
config:
discovery:
enabled: true
service-id: config-server
profile: dev
label: master
rabbitmq:
host: 127.0.0.1
port: 5672
username: gaojinliang
password: []
eureka:
client:
service-url:
defaultZone: http://localhost:8808/eureka
instance:
prefer-ip-address: true
#开放刷新入口
management:
endpoints:
web:
exposure:
include: refresh
⑥、测试类中增加 @RefreshScope 注解代表需要动态刷新配置
@RestController
@RefreshScope
public class TestController {
@Value("${info.app.version}")
private String version;
@GetMapping("/getVersion")
public String getUser() {
return this.version;
}
}
依次服务启动:
a、启动 learn-discover-eureka (8808)
b、启动 learn-config-server (8099)
c、启动 learn-config-client
(可以启动多个,以了解更新过程。 可以去掉远程仓库中的端口配置,本地区分端口启动, 也可以远程多建几个配置文件,本地使用 spring.cloud.config.profile 区分)
d、访问如下链接我们 可以查到版本结果
http://127.0.0.1:9901/getVersion
e、修改 config-client-dev.yml 中version信息,再次提交到git
f、 这时我们要POST方式请求 http://127.0.0.1:8099/actuator/bus-refresh, 之后再次访问步骤4,发现配置信息已经更新, 如果启动了多个客户端实例,则会发现它们都已经更新。
一般Git服务都提供 WebHook 功能,可以在提交变更后触发指定地址,这样就更自动化了, 由于我们的Git仓库在外网上,无法触发内网地址,所以就不尝试了,有兴趣的同学可以去试试,另外也可以在公司内网搭建一个Gitblit尝试。
局部刷新
某些场景下(例如灰度发布),我们可能只想刷新部分微服务的配置,此时可通过/bus/refresh端点的destination参数来定位要刷新的应用程序。
例如: /actuator/bus-refresh?destination=config-client:9901 ,这样消息总线上的微服务实例就会根据destination参数的值来判断是否需要要刷新。其中 config-client:9901 指的是各个微服务的ApplicationContext ID。
destination参数也可以用来定位特定的微服务。例如:/actuator/bus-refresh?destination=config-client:** ,这样就可以触发config-client微服务所有实例的配置刷新。
九.使用HystrixDashboard + Turbine实现可视化监控数据##
- 了解了Hystrix实现微服务的容错处理,除此之外Hystrix还实现了近乎实时的监控。
接下来我们来给多个客户端做监控,并使用 Turbine 做聚合,并在 HystrixDashboard 中显示。
1.建多个客户端工程,分别增加依赖
compile('org.springframework.cloud:spring-cloud-starter-netflix-hystrix')
2.启动类增加注解@EnableCircuitBreaker
3.application.yml文件开放hystrix.stream入口
#开启监控入口
management:
endpoints:
web:
exposure:
include: hystrix.stream
注:上面的三步即在各自的服务中开启监控,当服务启动后我们可以从地址 http://ip:port/hystrix.stream 看到各自的监控信息,但显示的只是文本信息。
4.聚合监控信息并显示
①增加工程,添加依赖
compile('org.springframework.cloud:spring-cloud-starter-netflix-turbine')
compile('org.springframework.cloud:spring-cloud-starter-netflix-hystrix-dashboard')
compile('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
② 启动类增加 @EnableTurbine 和 @EnableHystrixDashboard
③application.yml配置
#服务端口
server:
port: 8765
#Eureka配置信息
spring:
application:
name: service-trubine
eureka:
client:
service-url:
defaultZone: http://localhost:8808/eureka
instance:
prefer-ip-address: true
#聚合服务监控数据
turbine:
app-config: consumer-user,consumer-user2
cluster-name-expression: "'default'"
注:注册到eureka服务,并通过 turbine.app-config 配置哪些服务需要被聚合监控
5.依次启动服务
①、启动 learn-discover-eureka (8808)
②、启动 learn-service-user (8801)
③、启动 learn-consumer-user (8802)
④、启动 learn-consumer-user2 (8812)
⑤、启动 learn-service-turbine (8765)
⑥、访问 http://127.0.0.1:8765/hystrix 可看到如下界面
(1)URL地址, 可以录入单个服务的监控地址 http://ip:port/actuator/hystrix.stream,我们这里主要是看聚合数据所以录入 http://127.0.0.1:8765/turbine.stream
(2)Delay:延迟可以不填
(3)Title: 标题随便写一个
(4)点击 Monitor Stream 既可看到如下监控界面 (需要多次刷新2个工程中的接口)