文章目录
SpringCloud
Springboot和springcloud版本对应关系,详细参考 https://start.spring.io/actuator/info
1 搭建父工程
搭建一个普通的springBoot项目,pom文件中删除dependencies,保留 properties 和 plugins,父工程的 packaging 必须是 pom:
删除src目录,父工程不会写代码
2 服务治理springcloud-Eureka
2.1 服务治理
服务治理是微服务架构中最为核心和基础的模块,它主要用来实现各个微服务实例的自动化注册和发现。
Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件中的一部分,它基于 Netflix Eureka做了二次封装。主要负责完成微服务架构中的服务治理功能。
服务注册
在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,包括服务的主机与端口号、服务版本号、通讯协议等一些附加信息。注册中心按照服务名分类组织服务清单,同时还需要以心跳检测的方式去监测清单中的服务是否可用,若不可用需要从服务清单中剔除,以达到排除故障服务的效果。
服务发现
在服务治理框架下,服务间的调用不再通过指定具体的实例地址来实现,而是通过服务名发起请求调用实现。服务调用方通过服务名从服务注册中心的服务清单中获取服务实例的列表清单,通过指定的负载均衡策略取出一个服务实例位置来进行服务调用。
Eureka概念
它既包含了服务端组件,也包含了客户端组件,并且服务端与客户端均采用 java 编写,所以 Eureka 主要适用于通过 java 实现的分布式系统,或是 JVM 兼容语言构建的系统。
Eureka 服务端,即服务注册中心。
Eureka 客户端,主要处理服务的注册和发现。
服务提供者
服务注册:在服务注册时,需要确认eureka. dient.register-with-eureka=true参数是否正确,若为false,将不会启动注册操作。
服务同步:两个服务提供者的服务信息就可以通过这两个服务注册中心中的任意一台获取到。
服务续约:防止 Eureka Server 的 “剔除任务” 将该服务实例从服务列表中排除出去。
服务消费者
获取服务:确保 eureka-client-fetch-registery=true 参数没有被修改成 false,该值默认为 true。
服务调用:在 Ribbon 中会默认采用轮询的方式进行调用,从而实现客户端的负载均衡。
服务下线:在客户端程序中,当服务实例进行正常的关闭操作时,它会触发一个服务下线的 REST 请求给 Eureka Server,告诉服务注册中心:“我要下线了”。服务端在接收到请求之后,将该服务状态设置为下线(DOWN),并把该下线事件传播出去。
2.2 创建eureka子模块
右键父工程,new—Module—Spring Initializr—子模块命名—勾选依赖:Spring Cloud Discovery(Eureka Server)----完成
- 修改pom
首先子模块继承父工程
转移多余的依赖和声明到父工程,只留下eureka-server和plugins
修改父工程pom.xml,添加子工程转移过来的声明和版本信息,这样其他子模块公用这个版本。添加相应模块:
修改启动类:添加注解@EnableEurekaServer,表示这是一个服务注册中心 - 添加配置文件:application.yml
# 指定当前应用的名字
spring:
application:
name: eureka-server
# 端口号
server:
port: 5000
eureka:
instance:
# 主机地址
hostname: localhost
client:
fetch-registry: false
register-with-eureka: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
- 启动,访问:localhost:5000
2.3 创建服务提供者(eureka)/消费者(ribbon)子模块
同理,右键新的模块,选依赖:web(Spring Web)和Spring Cloud Discovery(EurekaDiscoveryClient),父级pom添加子模块;
启动类@EnableDiscoveryClient;
配置文件:
spring:
application:
name: my-service
server:
port: 8080
eureka:
client:
# 服务注册中心的地址
service-url:
defaultZone: http://localhost:5000/eureka/
增加一个接口:
启动,访问:
作用:写Controller提供服务
调远端服务
新建,增加依赖SpringCloudRouting(Ribbon),启动类@EnableDiscoveryClient;配置文件(只需修改端口号)
增加配置类RibbonConfig.java
@Configuration
public class RibbonConfig {
/**
* 调用远端服务的对象
* @return
*/
@Bean
@LoadBalanced // 负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
新增控制类RibbonController.java
@RestController
public class RibbonController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/ribbon/hello")
public String hello() {
String url = "http://MY-SERVICE/service/hello";
String res = restTemplate.getForObject("", String.class);
return res;
}
}
为什么只需要服务的名字(my-service)就可以访问?
从注册中心拉取服务列表
2.4 服务优雅下线
下线的实例依然显示在线,依然会被消费,这个时候就会出问题。Eureka Server 默认是每隔一段时间(默认 30 秒)将当前清单中超过(默认 90 秒)没有续约的服务剔除掉。
在本地调试的时候可以将 eureka 的自我保护机制关掉,在 eureka 注册中心配置:
eureka:
server:
# 关闭注册中心的自我保护机制
enable-self-preservation: false
启用shutdown(推荐)
在服务提供者加入依赖:
<!--监控服务-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置加入启用shutdown:
management:
endpoint:
shutdown:
# 启动 下线 通知
enabled: true
访问监控端点:localhost:8080/actuator
由于为了保护应用不被攻击下线接口,所以默认没有暴露 shutdown 接口的,这里需要我们手动自己开启:
management:
endpoints:
web:
exposure:
# 暴露下线通知接口
include: health,info,shutdown
再次访问
用postman发起post/delete请求:http://localhost:8080/actuator/shutdown
这个时候在注册中心对应的服务就不在了。
由于应用的 shutdown 接口需要保护,不然会被恶意攻击导致应用下线,所以我们一般需要配置相应的配置来保证这个应用的安全,这时候就只能通过配置的前缀来访问shutdown接口:
2.5 服务异常下线
在服务治理环节 Eureka 的 server 端会发出 5 个事件通知,分别是:
EurekaInstanceCanceledEvent 当有服务下线时会执行
EurekaInstanceRegisteredEvent 当有服务注册时会执行
EurekaInstanceRenewedEvent 当有服务续约时会执行
EurekaRegistryAvailableEvent Eureka 注册中心启动执行
EurekaServerStartedEvent Eureka Server 启动时执行
由于 Eureka 拥有自我保护机制,当其注册表里服务因为网络或其他原因出现故障而关停时,Eureka 不会剔除服务注册,而是等待其修复。我们可以采取关闭此功能,让其剔除不健康节点,从而导致执行 EurekaInstanceCanceledEvent 事件。
配置Eureka服务器
配置Eureka客户端
在Eureka Server端创建监听器,EurekaListener.java
@Component
public class EurekaListener {
/**
* 日志记录器
*/
private Logger logger = LoggerFactory.getLogger(EurekaListener.class);
/**
* 监听服务下线
* @param e
*/
@EventListener
public void shutdown(EurekaInstanceCanceledEvent e) {
// 对应下线服务id
String serverId = e.getServerId();
// 对应下线的服务名字
String appName = e.getAppName();
// 可以发短信,邮件...
// 通知运维
logger.info("服务:{},服务ID:{},异常下线",appName,serverId);
}
}
服务下线,如果没有通知到位,整个调用链就会断,为了让我们知道服务出现了什么问题,通知运维。
测试
3 高可用注册中心,集群
微服务架构在分布式环节中要充分考虑发生故障的情况,所以在生产环境中必须对各个组件进行高可用部署。对于服务提供者如此,对服务注册中心也是一样。Eureka server 的高可用实际上就是将自己作为服务向其他服务注册中心注册自己,这样就可以形成一组互相注册的服务注册中心,以实现服务清单的互相同步,达到高可以用的效果。
修改hosts文件
为了能够访问到eureka1和eureka2,必须在系统中进行配置,Windows系统:C:\Windows\System32\drivers\etc\hosts 文件
eureka01和02服务注册中心,相互注册
ribbon和server向两个注册中心注册:
启动两个eureka
注意:第四步固定写法--spring.profiles.active=eureka01
,图中写错了,懒得重新截图了,hhhhh~~
同理,服务提供者service也可以这么做,根据配置文件做集群,很nice~(配置文件只需要改个端口号,配置两个启动service)
分布式:简单来说原来是在一个springboot完成的事情,现在分布在多个springboot里做。
集群:有两个springboot,他们提供的功能一模一样,只是端口号不同,这样就形成集。在集群的服务里面顺序(随机)选一个。提升并发能力。
可承受负载X2,横向扩展;如果其中一个端口挂了,还有另外的端口可用,这就是高可用。
4 客户端负载均衡SpringCloudRibbon
Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具,它基于 Netflix Ribbon 实现。负载均衡在系统架构中是一个非常重要,并且不得不去实施的内容,因为负载均衡是对系统的高可用,网络压力的环节和处理能力扩容的重要手段之一。
通过 spring cloud ribbon 的封装,我们在微服务架构中使用客户端负载均衡调用非常简单,只需要 2 个步骤: 一:服务提供者只需要启动多个服务实例并注册到一个注册中心或是多个相关的服务注册中心;二:服务消费者直接通过调用被@LoadBalanced 注解修饰过的 RestTemplate 来实现面向服务接口的调用。
4.1 RestTemplate详解
get请求:url传参,路径传参 ,getForEntity,getForObject
service–HelloController.java
ribbon–RibbonController.java
post请求:postForObject传对象 ,postForEntity,postForLocation
额外创建应用放pojo:new–Module–Maven
注意:省略无参/有参构造,getter/setter方法,toString方法
在ribbon,service添加依赖:
<!-- pojo-->
<dependency>
<groupId>com</groupId>
<artifactId>pojo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
就可以在控制层使用了:HelloController.java
/**
* 传递对象
* @param user
*/
@PostMapping("/service/user")
public JsonResult helloUser(@RequestBody User user) {
return Response.ok("success",user);
}
消费者RibbonController.java
@PostMapping("/ribbon/user")
public JsonResult helloUser(@RequestBody User user) {
String url = "http://MY-SERVICE/service/user";
return restTemplate.postForObject(url, user, JsonResult.class);
}
put请求
delete请求