Eureka集群的构建
- 原理说明
- Eureka的原理说明
- 微服务rpc远程服务调用中最重要的是什么?
- 高可用, 试想假如只有一个注册中心, 一旦出故障, 则所有服务全部失效.
- 解决的方法就是搭建Eureka注册中心集群, 实现负载均衡+故障容错.
- 集群对外暴露注册中心, 向内各个机器之间互相注册, 相互守望.
- 集群对外暴露注册中心, 向内各个机器之间互相注册, 相互守望.
- 集群环境构建步骤
- 新建cloud-eureka-server7002工程, 内容和cloud-eureka-server7001一致.
- 修改映射配置
- 找到C:WindowsSystem32driversetc路径下的hosts文件
- 增加如下内容
- 写yml
- cloud-eureka-server7001
server: port: 7001 eureka: instance: hostname: eureka7001.com client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://eureka7002.com:7002/eureka/
- cloud-eureka-server7002
server: port: 7002 eureka: instance: hostname: eureka7002.com client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://eureka7001.com:7001/eureka/
- cloud-eureka-server7001
- 将订单/支付这两个微服务注册进eureka集群
- 只需修改一个地方, 即eureka.client.service-url.defaultZone
<!-- 注册到集群的所有机器中 --> defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
- 只需修改一个地方, 即eureka.client.service-url.defaultZone
- 支付微服务8001的集群配置
- 结构图示
- 参考cloud-provider-payment8001创建cloud-provider-payment8002, 注意修改端口号.
- 修改PaymentController.java
@RestController @Slf4j public class PaymentController { @Resource private PaymentService paymentService; @Value("${server.port}") private String serverPort; @PostMapping(value = "/payment/create") public CommonResult create(@RequestBody Payment payment) { int result = paymentService.create(payment); log.info("插入结果: " + result + "aaa"); if (result > 0) { return new CommonResult(200, "插入数据库成功, serverPort:"+serverPort, result); } return new CommonResult(444, "插入数据库失败"); } @GetMapping(value = "/payment/get/{id}") public CommonResult getPaymentById(@PathVariable("id") Long id) { Payment payment = paymentService.getPaymentById(id); log.info("插入结果: " + payment + "haha"); if (payment != null) { return new CommonResult(200, "查询成功, serverPort:"+serverPort, payment); } return new CommonResult(444, "没有对应记录, 查询ID:" + id); } }
- 负载均衡
- 完成以上步骤后, 我们使用发现始终调用8001, 这是因为订单服务访问地址写死了.
- 在cloud-consumer-order80中, 把原先的地址+端口号改为服务名
@RestController @Slf4j public class OrderController { //private static final String PAYMENT_URL = "http://localhost:8001"; private static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE"; @Resource private RestTemplate restTemplate; //浏览器只能发get请求, 但下面调用的实质是post请求 @GetMapping("/consumer/payment/create") public CommonResult<Payment> create(Payment payment) { log.info(payment.toString()); String url = PAYMENT_URL + "/payment/create"; return restTemplate.postForObject(url, payment, CommonResult.class); } @GetMapping("/consumer/payment/get/{id}") public CommonResult<Payment> getPayment(@PathVariable("id") Long id) { String url = PAYMENT_URL + "/payment/get/" + id; return restTemplate.getForObject(url, CommonResult.class); } }
- 服务名在这里看
- 在cloud-consumer-order80中, 把原先的地址+端口号改为服务名
- 但此时我们访问: http://localhost/consumer/payment/get/1 报错了.
- 因为服务名下可能有多个地址, 系统不知道改访问哪个.
- 所以我们需要加入负载均衡功能告诉系统选哪个.
- 使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
- 默认采取轮循机制
@Configuration public class ApplicationContextConfig { //使用@LoadBalanced注解赋予了RestTemplate负载均衡的能力 @LoadBalanced @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } }
- 实际上这是Ribbon的负载均衡功能, 同时, Ribbon和Eureka整合后Consumer可以直接调用服务而不用关心地址和端口号.
- 默认采取轮循机制
actuator微服务信息完善
- 必须导入如下依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
- 主机名称: 服务名称修改
- 修改yml文件
eureka.instance.instance-id: payment8001 --- eureka.instance.instance-id: payment8002
- 效果
- 修改yml文件
- 访问信息有ip信息提示
- 修改yml文件
eureka.instance.prefer-ip-address: true
- 鼠标放到上图的服务名时会有其ip提示.
- 修改yml文件
服务发现Discovery
- 对于注册到Eureka的微服务, 可以通过服务发现来获得该服务的信息
- 以cloud-provider-server8001为例
- 修改其controller
@Resource private DiscoveryClient discoveryClient; @GetMapping(value = "/payment/discovery") public CommonResult discover() { List<String> services = discoveryClient.getServices(); for(String element : services) { log.info(element); } List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE"); for(ServiceInstance instance : instances) { log.info(instance.getServiceId()+" "+instance.getHost()+" "+instance.getPort()+" "+instance.getUri()); } return new CommonResult(200, "DiscoveryClient结果", discoveryClient); }
- 在主启动类加注解@EnableDiscoveryClient
@EnableDiscoveryClient //表示这是一个Eureka客户端 @EnableEurekaClient @SpringBootApplication public class PaymentMain8001 { public static void main(String[] args) { SpringApplication.run(PaymentMain8001.class, args); } }
- 修改其controller
- 测试: http://localhost:8001/payment/discovery
Eureka自我保护机制
- 概述
- 保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护.
- 一旦进入保护模式, Eureka Server将尝试保护其服务注册表中的信息, 不再删除服务注册表中的数据, 即不会注销任何微服务.
- 如果在Eureka Server的首页看到如下提示, 则说明Eureka进入了保护模式
- 一句话理解, 就是某时刻某一个微服务不可用了, Eureka不会立即清理, 依旧会对该微服务的信息进行保存.
- 为什么会产生Eureka自我保护机制?
- 防止EurekaClient可以正常运行, 但是与Eureka Server网络不通情况下, Eureka Server不会立刻剔除EurekaClient服务.
- 这种模式是一种应对网络异常的安全保护措施, 它的架构哲学是宁可同时保留所有微服务, 也不盲目注销任何健康的微服务.
- 使用自我保护模式, 可以让Eureka集群更健壮, 稳定.
- 这属于CAP理论中的AP分支
- 禁止自我保护(一般生产环境不会禁止自我保护)
- 以cloud-eureka-server7001和cloud-provider-server8001为例
- 注册中心Eureka Server端7001
- 默认自我保护机制是开启的
- 关闭自我保护机制(application.yml中配置)
eureka.server.enable-self-preservation=false
eureka.server.eviction-interval-timer-in-ms: 2000 #修改心跳时间为2000ms
- 生产者客户端EurekaClient8001
- 默认
eureka.instance.lease-renewal-interval-in-seconds=30 #Eureka客户端向服务端发送心跳的时间间隔, 单位为秒(默认30秒) eureka.instance.lease-expiration-duration-in-seconds=90 #Eureka服务端在收到最后一次心跳后等待时间上限, 单位为秒(默认90秒), 超时将剔除服务
- 默认