1、微服务的注册中心
注册中心可以说是微服务架构中的“通讯录”,他记录了服务与服务地址之间的映射关系。在分布式架构中,所有的服务都会注册到这里,当一个服务需要调用其他服务时,在注册中心找到相应的服务地址即可调用。
1.1、注册中心的主要作用
服务注册中心是微服务架构中非常重要的一个组件,在微服务架构中主要起到了协调者的一个作用。注册中心一般包含如下几个功能:
1、服务发现:
- 服务注册/反注册:保存服务提供者和服务调用者的信息;
- 服务订阅/取消订阅:服务调用者订阅服务提供者的信息,最好有实时推送的功能;
- 服务路由(可选):具有筛选整合服务提供者的能力。
2、服务配置:
- 配置订阅:服务提供者和服务调用者订阅微服务相关的配置;
- 配置下发:主动将配置推送给服务提供者和五福调用者。
3、服务健康检测:
1.2、常见的注册中心
- Zookeeper
zookeeper是一个分布式服务框架,是Apache Hadoop的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题。如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。 简单来说:zookeeper = 文件系统 + 监听通知机制。
- Eureka
Eureka是在Java语言上编写的,基于Restful Api开发的服务注册与发现组件,SpringCloud Netflix中的重要组件。
- Consul
Consul是由HashiCorp基于Go语言开发的支持多数据中心分布式高可用的服务发布和注册的服务组件,采用Raft算法保证服务的一致性,且支持健康检查。
- Nacos
Nacos是一个更易于构建云原生应用的动态服务发现,配置管理和服务管理平台。 简单来说:Nacos就是注册中心 + 配置中心的组合。 提供简单易用的特性集,帮助我们解决微服务开发必会设计到的服务注册与发现、服务配置、服务管理等问题。 Nacos还是Spring Cloud Alibaba组件之一,负责服务注册与发现。
1.3、常见注册中心的异同点
我们通过一张表格大致了解Eureka、Consul、Zookeeper之间的异同点,选择什么类型的服务注册与发现组件可以根据自身项目要求决定。
组件名 | 语言 | CAP | 一致性算法 | 服务健康检查 | 对外暴露接口 |
---|
Eureka | Java | AP | 无 | 可配支持 | HTTP | Consul | Go | CP | Raft | 支持 | HTTP/DNS | Zookeeper | Java | CP | Paxos | 支持 | 客户端 | Nacos | Java | AP | Raft | 支持 | HTTP |
2、Eureka概述
2.1、Eureka的基础知识
Eureka是Netflix开发的服务发现框架,SpringCloud将它集成在自己的子项目spring-cloud-netflix 中,实现SpringCloud的服务发现功能。 上述简要描述了Eureka的基础架构,由三个角色组成:
- Eureka
提供服务注册和发现
- Service Provider
服务提供方; 将自身服务注册到Eureka注册中心,从而使服务消费方能够找到。
- Service Consumer
服务消费方; 从Eureka注册中心获取注册服务列表,从而能够消费服务。
2.2、Eureka的交互流程与原理
上图来自Eureka官方的架构图,大致描述了Eureka集群的工作过程。图中包含的组件非常多,可能比较难以理解,下面各组件的介绍:
- us-east-1c和us-east-1d和us-east-1e
属于zone(分区),他们都属于us-east-1这个region(地区)
- Apllication Service
相当于服务提供者(属于客户端Eureka Client)
- Application Cient
相当于服务消费者(属于客户端Eureka Client)
- Make Remote Call
可以简单理解为调用Restful API
由上图可知,Eureka包含两个组件:Eureka Server 和 Eureka Client,它们的作用如下:
- Eureka Client是一个Java客户端(包括服务提供者和消费者),用于简化与Eureka Server的交互;
- Eureka Server是提供服务发现的能力,各个微服务启动时,会通过Eureka Client向Eureka Server进行注册自己的信息(例如网络信息),Eureka Server会存储该服务的信息;
- 微服务启动后,会周期性的向Eureka Server发送心跳(默认周期为30秒),以续约自己的在线信息。如果Eureka Server在一定时间内没有接收到某个微服务节点的心跳,Eureka Server将会注销该微服务的节点(默认检测时长为90秒);
- 每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的方式完成服务注册表的同步;
- Eureka Client会缓存Eureka Server中的信息,即使所有的Eureka Server节点都宕机,服务消费者依然可以使用缓存中的信息找到服务提供者。
综上所述,Eureka通过心跳检测 、健康检查 和客户端缓存等机制 ,提高了系统的灵活性、可伸缩性和可用性。
3、搭建Eureka注册中心案例
3.1、搭建Eureka服务中心
(1)、创建ebuy-eureka模块(为了后续方便,可直接建成SpringBoot项目)
(2)在maven中引入eureka-server依赖坐标
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
此处依赖的版本号尽量与所继承的parent项目的spring-boot-starter-parent版本号保持相同, 如果不同的话,要保证版本尽量相差不大,因为目前更高版本的SpringBoot对于netflix-eureka-server的兼容性还不是很完善。 为了后续一步到位,此处还有一个坑,如果你使用的是JDK8,目前来说是没有这方面的问题;如果是JDK9及以上版本就有问题了,就是JDK9以后的版本中没有关于JAXB-API默认的类路径的配置,因为JAXB-API是java ee的一部分,从jdk9开始java引入了模块的概念, 可以使用模块命令–add-modles java.xml.bind引入jaxb-api,当然还有一种简单粗暴的方法,就是在pom文件中引入以下依赖,以激活jaxb-api应用接口。
启动ebuy-eureka会出现以下错误:
java.lang.TypeNotPresentException: Type javax.xml.bind.JAXBContext not present
解决上述JAXBContext 不存在的异常:
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
(3) 配置application.yml
server:
port: 9000
eureka:
instance:
hostname: 127.0.0.1
client:
register-with-eureka: false
fetch-registry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
(4) 配置启动类
package com.ebuy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EbuyEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EbuyEurekaApplication.class, args);
}
}
@EnableEurekaServer注解: 激活Eureka Server端配置
3.2、服务注册中心管理后台
启动ebuy-eureka工程启动类,在地址栏输入地址localhost:9000 ,直接回车即可进入EurekaServer内置的管理控制台,显示效果如下,其实下图界面与dubbo-amdin的可视化界面虽然画面不同,但是功能基本是一样的,同样是做注册中心的监管工作:
4、客户端服务注册到Eureka注册中心
4.1、商品模块服务注册
4.1.1、创建ebuy-product模块(为了后续方便,可直接建成SpringBoot项目)
4.1.2、在maven中引入eureka-client 客户端相关依赖坐标
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
4.1.3、在com.ebuy.product包下创建pojo、mapper、service、controller层
-
pojo层创建实体类EasybuyProduct package com.ebuy.product.pojo;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
@ToString
public class EasybuyProduct implements Serializable {
private Long epId;
private String epName;
private String epDescription;
private BigDecimal epPrice;
private Long epStock;
private Long epcId;
private Long epcChildId;
private String epFileName;
private static final long serialVersionUID = 1L;
}
-
上述EasyBuyProduct实体类中使用了两个注解@Data和@ToString,分别代表自动生成getter、setter方法和toString()方法,这种方式使用的是lombok插件,可以极大的提升实体类的开发效率和节省编码空间。 -
第一步:首先在settings的plugins中下载插件:lombok ,安装后注意要重启IDEA才能生效: -
第二步:在ebuy-parent父级模块中导入lombok依赖坐标 ,后续只要继承ebuy-parent的模块,实体类都可以直接使用注解:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
-
这样上述实体类上的注解就可以使用了! -
在mapper层创建EasyBuyProductMapper接口: package com.ebuy.product.mapper;
import com.ebuy.product.pojo.EasybuyProduct;
public interface EasybuyProductMapper {
EasybuyProduct selectByPrimaryKey(Long epId);
}
-
在resources目录下新建com/ebuy/product/mapper目录,并在其下新建EasyBuyProductMapper.xml映射文件: <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.ebuy.product.mapper.EasybuyProductMapper" >
<resultMap id="BaseResultMap" type="com.ebuy.product.pojo.EasybuyProduct" >
<id column="ep_id" property="epId" jdbcType="DECIMAL" />
<result column="ep_name" property="epName" jdbcType="VARCHAR" />
<result column="ep_description" property="epDescription" jdbcType="VARCHAR" />
<result column="ep_price" property="epPrice" jdbcType="DECIMAL" />
<result column="ep_stock" property="epStock" jdbcType="DECIMAL" />
<result column="epc_id" property="epcId" jdbcType="DECIMAL" />
<result column="epc_child_id" property="epcChildId" jdbcType="DECIMAL" />
<result column="ep_file_name" property="epFileName" jdbcType="VARCHAR" />
</resultMap>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long" >
select ep_id, ep_name, ep_description, ep_price, ep_stock, epc_id, epc_child_id, ep_file_name from EASYBUY_PRODUCT
where ep_id = #{epId,jdbcType=DECIMAL}
</select>
</mapper>
-
在service层创建EasybuyProductService业务逻辑层接口(和mapper层保持一致即可): package com.ebuy.product.service;
import com.ebuy.product.pojo.EasybuyProduct;
public interface EasybuyProductService {
EasybuyProduct selectByPrimaryKey(Long epId);
}
-
在service.impl层创建EasyBuyProductServiceImpl实现类: package com.ebuy.product.service.impl;
import com.ebuy.product.pojo.EasybuyProduct;
@Service
@SuppressWarnings("all")
public class EasybuyProductServiceImpl implements EasybuyProductService {
@Autowired
EasybuyProductMapper easybuyProductMapper;
@Override
public EasybuyProduct selectByPrimaryKey(Long epId) {
return easybuyProductMapper.selectByPrimaryKey(epId);
}
}
-
在controller层新建ProductContoller: package com.ebuy.product.controller;
import com.ebuy.product.pojo.EasybuyProduct;
import com.ebuy.product.service.EasybuyProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private EasybuyProductService productService;
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
public EasybuyProduct findById(@PathVariable Long id) {
EasybuyProduct product = productService.selectByPrimaryKey(id);
return product;
}
}
4.1.4、配置application.yml文件
server:
port: 9011
spring:
application:
name: ebuy-product
datasource:
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/ebuy?useUnicode=true&characterEncoding=utf8
mybatis:
type-aliases-package: com.ebuy.product.pojo
mapper-locations: com/ebuy/product/mapper/*.xml
logging:
level:
com.ebuy: DEBUG
eureka:
client:
serviceUrl:
defaultZone: http://127.0.0.1:9000/eureka/
instance:
prefer-ip-address: true
4.1.5、修改启动类添加服务注册注解
package com.ebuy;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@MapperScan("com.ebuy.product.mapper")
public class EbuyProductApplication {
public static void main(String[] args) {
SpringApplication.run(EbuyProductApplication.class, args);
}
}
从Spring Cloud Edgware 版本开始, @EnableDiscoveryClient 或 @EnableEurekaClient 可省略。只需加上相关依赖,并进行相应配置,即可将客户端微服务注册到服务发现组件上,但是上述的EurekaServer服务的启动类上必须要加上注解@EnableEurekaServer ,表明当前服务是Eureka服务注册中心。
4.1.6、启动EbuyProductApplication 启动类向Eureka注册商品服务
4.1.7、在地址栏访问controller层Restful接口地址http://localhost:9011/product/816753 ,火狐浏览器可以解析JSON数据,正合咱意:
4.2、订单模块服务注册(步骤和上述商品微服务基本一致)
4.2.1、创建ebuy-order模块(为了后续方便,可直接建成SpringBoot项目)
4.2.2、引入eureka-client客户端依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
4.2.3、同样新建pojo、mapper、service、controller层
- 在mapper层新建EasyBuyOrderMapper接口
- 在service层新建EasyBuyOrderService接口(和Mapper层保持一致即可)
- 在service.impl包下新建EasyBuyOrderServiceImpl实体类
- 在controller层新建OrderController类
4.2.4、配置application.yml文件
server:
port: 9012
spring:
application:
name: ebuy-order
logging:
level:
cn.ebuy: DEBUG
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:9000/eureka/
instance:
prefer-ip-address: true
4.2.5、修改启动类添加服务注册注解
package com.ebuy.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class EbuyOrderApplication {
public static void main(String[] args) {
SpringApplication.run(EbuyOrderApplication.class, args);
}
}
4.2.5、启动EbuyOrderApplication 启动类向Eureka注册服务
5、跨服务调用
上述编写了基础的微服务,均已注册到了Eureka注册中心中。
现在有个需求,用户在下订单时需要调用商品微服务获取商品数据,通过上述案例我们应该知道这是跨服务模块之间的调用,细心的伙子肯定看到了上述我们在商品的controller层提供了供他人调用的HTTP接口,并采用的是Restful风格,所以可以在下订单的时候使用http请求的相关工具类完成,如常见的HttpClient 、OkHttp 等。
其实spring还为我们提供了一个工具类RestTemplate 。
5.1、RestTemplate介绍
Spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了Restful的标准,我们只需要传入url及返回值类型即可。相较于之前常用的HttpClient,RestTemplate是一种更优雅的调用Restful服务的方式。
在Spring应用程序中访问第三方Rest服务与使用Spring RestTemplate类有关。RestTemplate类的设计原则与其他Spring模板类(eg:JdbcTemplate、JmsTemplate)相同,为执行复杂任务提供了一种具有默认行为的简化方法。
RestTemplate默认依赖JDK提供http连接的能力(HttpURLConnection),如果有需要的话也可以通过setRequestFactory方法替换为,(eg:Apache HttpComponents、Netty或者OKHttp)等其他HTTP library。
考虑到RestTemplate类是为了调用REST服务而设计的,因此它的主要方法与REST的基础紧密相连就不足为奇了,后者是HTTP协议的方法:HEAD、POST、PUT、DELETE和OPTIONS。
RestTemplate类具有以下方法:
- headForHeaders()
- getForObject()
- postForObject()
- put()
- delete()
5.2、RestTemplate方法介绍
该模板类的主要切入点为以下几个方法(并对应这HTTP的六个主要方法)
5.3、通过RestTemplate调用微服务
现有个需求,用户在下订单时需要调用商品微服务获取商品数据,这个时候就需要在订单模块中调用商品模块。
(1)首选在pojo层也新建一个EasyBuyProduct实体类,用于跨服务调用接收结果:
package com.ebuy.order.pojo;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
@ToString
public class EasybuyProduct implements Serializable {
private Long epId;
private String epName;
private String epDescription;
private BigDecimal epPrice;
private Long epStock;
private Long epcId;
private Long epcChildId;
private String epFileName;
private static final long serialVersionUID = 1L;
}
(2)在ebuy-order的主启动类EbuyOrderApplication处配置RestTemplate,在服务启动的时候即将RestTemplate交给spring容器管理:
package com.ebuy.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class EbuyOrderApplication {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(EbuyOrderApplication.class, args);
}
}
(3)在OrderController类中先注入RestTemplate类,然后编写调用product模块的方法:
package com.ebuy.order.controller;
import com.ebuy.order.pojo.EasybuyProduct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
RestTemplate restTemplate;
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
public EasybuyProduct findById(@PathVariable Long id) {
EasybuyProduct easybuyProduct=new EasybuyProduct();
easybuyProduct=restTemplate.getForObject("http://127.0.0.1:9011/product/"+id,EasybuyProduct.class);
return easybuyProduct;
}
}
(4)启动ebuy-prodcut和ebuy-order两个服务,在地址栏访问http://localhost:9012/order/816753 上述结果显示说明ebuy-order模块成功调用ebuy-product的rest服务,并响应了结果!
5.4、RestTemplate调用Rest微服务的弊端
上述使用RestTemplate类的方法调用到了商品微服务的RESTFul API接口,但是我们可以很明显的看出,提供者的网络地址(ip,port)等被硬编码到了代码中,这种做法存在许多问题:
5.5、解决RestTemplate硬编码存在的问题
需要通过注册中心动态的对服务注册和服务发现。 EurekaClient的注册,心跳及服务器上的注册信息获取在com.netflix.discovery.DiscoveryClient .initScheduledTasks()方法中实现。
DiscoveryClient默认获取服务器注册信息 间隔30秒,心跳时间30秒,Client启动以后40往服务器发送注册信息。
在OrderController类中使用DiscoveryClient重新编写服务调用:
package com.ebuy.order.controller;
import com.ebuy.order.pojo.EasybuyProduct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping(value = "/dc/{id}",method = RequestMethod.GET)
public EasybuyProduct findDcById(@PathVariable Long id) {
List<ServiceInstance> list=discoveryClient.getInstances("ebuy-product");
ServiceInstance serviceInstance=list.get(0);
EasybuyProduct easybuyProduct=restTemplate.getForObject("http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/product/"+id,EasybuyProduct.class);
return easybuyProduct;
}
}
我们在上述的两个微服务中已经配置了eureka-client相关配置,此时我们需要先启动EurekaServer服务注册中心,然后再启动eureka-client(product、order)两个客户端服务。
启动成功后,在浏览器地址栏访问地址http://localhost:9000/ 说明客户端服务(product提供者和order消费者)已经成功注册到eureka注册中心了。 然后在地址栏访问http://localhost:9012/order/dc/816753 动态调用成功,妥!!!
5.6、Eureka的自我保护机制
微服务第一次注册成功之后,每30秒会发送一次心跳将服务的实例信息注册到中心。通过EurekaServer该实例仍然存在。如果超过90秒没有发送更新,则服务器将从注册信息中将此服务剔除。
Eureka Server在运行期间,会统计心跳失败的比例在15分钟之内是否低于80%,如果出现低于80%的情况(在单机调试的时候很容易满足,实际在生产环境中是由于网咯不稳定导致的),Eureka Server会将当前的实例注册信息保护起来,同时提示一串红字警告。保护模式主要用于一组Eureka Client客户端和Eureka Server服务端之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其注册表中的信息,不再删除服务注册信息中的数据(也就是不会注销任何微服务)。
验证完自我保护机制开启后,并不会马上呈现在web上,而是你人需等待5分钟(可以通过在yml文件中配置如下)
eureka:
server:
wait-time-in-ms-when-sync-empty: 5
即5分钟后再次刷新注册中心可视化监管界限会出现以下提示信息: 也可以关闭自我保护机制:
server:
enable-self-preservation: false
好了,今先到这吧!!!
|