目录
一:Eureka简介
二:demo搭建
利用maven搭建父子工程:
2:新建一个子module:eurekaserver作为Eureka的注册中心:
3:新建服务提供者的模块 eureka-service.producer
4:新建服务调用方模块 eureka-service.consumer
三:在Eureka中调用远程服务
代码地址:代码地址
一:Eureka简介
Eureka 是 Netflix 开发的,一个基于 REST 服务的,服务注册与发现的组件。它在为服务中注意要用于服务管理:
1:自动注册和发现。2:实现状态监管 3:实现动态路由 4:实现负载均衡
它主要包括两个组件:Eureka Server 和 Eureka Client
- Eureka Client:一个Java客户端,用于简化与 Eureka Server 的交互(通常就是微服务中的客户端和服务端)
- Eureka Server:提供服务注册和发现的能力(通常就是微服务中的注册中心)
各个微服务启动时,会通过 Eureka Client 向 Eureka Server 注册自己,Eureka Server 会存储该服务的信息
也就是说,每个微服务的客户端和服务端,都会注册到 Eureka Server,这就衍生出了微服务相互识别的话题
- 同步:每个 Eureka Server 同时也是 Eureka Client(逻辑上的)
多个 Eureka Server 之间通过复制的方式完成服务注册表的同步,形成 Eureka 的高可用 - 识别:Eureka Client 会缓存 Eureka Server 中的信息
即使所有 Eureka Server 节点都宕掉,服务消费者仍可使用缓存中的信息找到服务提供者 - 续约:微服务会周期性(默认30s)地向 Eureka Server 发送心跳以Renew(续约)信息(类似于heartbeat)
- 续期:Eureka Server 会定期(默认60s)执行一次失效服务检测功能
它会检查超过一定时间(默认90s)没有Renew的微服务,发现则会注销该微服务节点
描述注册中心,服务提供方,客户端消费者的示意图如下:
Eureka: 就是服务注册中心(可以是一个集群),对外暴露及自己的地址。
提供者: 启动后向Eureka注册自己的信息。
消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并定时更新。
心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态,Eureka会更新注册的服务是否可用。
二:demo搭建
注意!!!搭建微服务项目要注意springclound和springboot的版本匹配问题,如果版本不匹配会出现很多奇怪的问题,本人在使用的时候
被折腾了好久,我整理一下他们的版本匹配,下面pom中的版本使用匹配都是实践可行的。
spring-boot-starter-parent | spring-cloud-dependencies | ||||
版本号 | 发布日期 | | 版本号 | 发布日期 | |
1.5.2.RELEASE | 2017年3月 | 稳定版 | Dalston.RC1 | 2017年未知月 | |
1.5.9.RELEASE | 2017年11月 | 稳定版 | Edgware.RELEASE | 2017年11月 | 稳定版 |
1.5.16.RELEASE | | | Edgware.SR5 | | |
1.5.20.RELEASE | | | Edgware.SR5 | | |
2.0.2.RELEASE | 2018年5月 | | Finchley.BUILD-SNAPSHOT | 2018年未知月 | |
2.0.3.RELEASE | | | Finchley.RELEASE | | |
2.0.6.RELEASE | | | Finchley.SR2 | | |
2.1.4.RELEASE | | | Greenwich.SR1 | | |
利用maven搭建父子工程:
1:父工程就是个普通maven工程,没有使用任何模板,在pom.xml里使用了如下配置,因为后面要用它做其它项目,所以多了一点:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>Eureka-Demo</groupId>
<artifactId>demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>eurekaserver</module>
<module>serviceproducer</module>
</modules>
<properties>
<java.version>1.8</java.version>
<spring-clound.version>Finchley.SR1</spring-clound.version>
<mapper.starter.version>2.0.3</mapper.starter.version>
<mysql.version>5.1.32</mysql.version>
<springboot.version>2.0.3.RELEASE</springboot.version>
<pageHelper.starter.version>1.2.5</pageHelper.starter.version>
</properties>
<dependencyManagement>
<dependencies>
<!--SpringClound-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-clound.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--通用mapper启动器-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${mapper.starter.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2:新建一个子module:eurekaserver作为Eureka的注册中心:
注册中心的pom.xml中引入:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
3:新建springboot启动类,因为没有使用springIO模板,springboot启动类没有自动生成。
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main (String [] args){
SpringApplication.run(EurekaApplication.class);
return;
}
}
注意,启动类上的注解和平常的Springboot项目启动类多了一个用于指明是个Eureka服务的注解:@EnableEurekaServer
为了避免默认端口号冲突在application.yml中修改端口:
server:
port: 8010
启动报错如下:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'gsonBuilder' defined in class path resource [org/springframework/boot/autoconfigure/gson/GsonAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.google.gson.GsonBuilder]: Factory method 'gsonBuilder' threw exception; nested exception is java.lang.BootstrapMethodError: java.lang.NoSuchMethodError: com.google.gson.GsonBuilder.setLenient()Lcom/google/gson/GsonBuilder;
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:590) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1247) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1096) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:759) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:869) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:398) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:330) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1258) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]
at com.eureka.EurekaApplication.main(EurekaApplication.java:14) [classes/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.google.gson.GsonBuilder]: Factory method 'gsonBuilder' threw exception; nested exception is java.lang.BootstrapMethodError: java.lang.NoSuchMethodError: com.google.gson.GsonBuilder.setLenient()Lcom/google/gson/GsonBuilder;
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:582) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]
... 18 common frames omitted
检查引入的依赖包,发现下面有如下的jar包,是这个jar包版本的问题,可以把这个jar包改为直接依赖版本号提升一下,但是对于我们这个项目来说不需要它,所以可以直接排除它:
在注册中心的pom.xml中排除它:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<exclusions>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
再次启动发现还是报错,而且一直在报,但是报错信息变了,不是上面gson的问题了:发现是Eureka客户端的问题。
但是我们发现项目已经启动了,我们先试着访问一下:竟然成功了:
那报错的原因是为啥呢?
原因在于:Eureka作为注册中心,一般不允许挂掉,所以一般都会做成集群,做成集群之后,注册中心之间就需要相互监听,也就需要相互注册,所以注册中心也是逻辑上的Eureka客户端。我们看到我们注册中心模块只引入了eureka-server的依赖,但是引入的jar中却包含了客户端的内容
后台一直在重复报错,连接拒绝,一直在重试一直timeout,我们debug可以看到它连接的地址是:
为了不报错我们需要把它这个默认的地址改掉 ,改为我们自己的地址,我们没有做集群,只能自己注册自己。
eureka中有个属性可以修改这个地址:注意这个属性值是map类型的,从下面的源码中可以看出。
server:
port: 8010
eureka:
client:
service-url:
defaultZone: http://localhost:8010/eureka/
我们可以看到客户端配置bean中这个属性的默认值就是尝试连接本地的8761端口地址。
通过上面的设置,我们再来重启一下。发现还是会报一次错误,而不是一直报错,这就正常了,因为我们自己注册了自己,当启动的时候尝试请求我们的注册中心,但是我们注册中心在启动,所以会报一次错,当注册中心启动完成之后,就不会再报错了。
页面内容如下:
但是我们发现当前注册到Eureka的实例应用名确实unknown的,这不是我们所想看到的,我们可以通过如下设置给我们的服务起名称:
spring:
application:
name: eureka-server
添加后的结果如下:
3:新建服务提供者的模块 eureka-service.producer
1)引入Eureka客户端的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<exclusions>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
2)添加启动类入口
/**
* 注意这里的注解也可以是@EnableEurekaClient
* 但是推荐使用 @EnableDiscoveryClient ,因为@EnableEurekaClient只能把服务注册到
* Eureka注册中心,但是注册中心不止Eureka一个,springboot也可以使用其它
* 注册中心,比如:Zookeeper等。但是用@EnableDiscoveryClient就可以同时向其它
* 注册中心注册
* */
@EnableDiscoveryClient
@SpringBootApplication
public class ServiceApplication {
public static void main(String[] arg){
SpringApplication.run(ServiceApplication.class);
}
}
3)添加配置:同注册中心的配置,配置注册的地址,定义服务的名字,修改启动端口号。
spring:
datasource:
hikari:
jdbc-url: jdbc:mysql://127.0.0.1:3306/db_1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
application:
name: eureka-service.producer
eureka:
client:
service-url:
defaultZone: http://localhost:8010/eureka/
server:
port: 8011
启动服务提供者模块后访问注册中心可以看到注册成功了。
但是我们发现Status下面的链接地址却是如上箭头所示的那样,这是因为我们注册的服务都是本地地址,如果注册的服务发布在不同的服务器上,上面就不能这样显示了。
如果服务提供者和注册中心服务在同一台机器上,大家都是localhost的域名,那就没问题,能发现服务;
如果服务提供者和注册中心服务不在同一台机器上,会报一个没找到服务的异常,原因是根据localhost和端口没有找到相应的服务。
此时,一种解决方法是让服务提供者上报自己的 ip地址,并显示在Eureka的注册列表中。
我们在注册中心模块和服务提供者模块的配置文件中都配置如下属性,来上报自己的ip地址,因为我们都是在本地启动的,所以配置的还是localhost.
#注册中心的配置
server:
port: 8010
eureka:
instance:
prefer-ip-address: true
instance-id: 127.0.0.1:8010
#服务提供者的配置
eureka:
instance:
prefer-ip-address: true
instance-id: 127.0.0.1:8011
重启访问页面如下:
4:新建服务调用方模块 eureka-service.consumer
配置内容同服务提供方的内容
1:新建一个模块导入依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<exclusions>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
2:启动类配置
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {
public static void main (String [] arg){
SpringApplication.run(ConsumerApplication.class);
}
}
3:配置文件application.yml配置
server:
port: 8012
eureka:
client:
service-url:
defaultZone: http://localhost:8010/eureka/
instance:
prefer-ip-address: true
instance-id: 127.0.0.1:8012
spring:
application:
name: eureka-service.consumer
启动之后,刷新页面。看到服务注册已经成功了。
三:在Eureka中调用远程服务
这里使用http的方式调用远程,当然也可以使用其它技术比如RPC等。
1:我在EUREKA-SERVICE.PRODUCER模块中写了一个如下接口: 用于返回所有的产品信息
访问地址:http://localhost:8011/getProducts
@RestController
public class ApplicationRestController {
@Autowired
private ProductService productService;
@GetMapping("/getProducts")
public String getAllProduct(){
return productService.getAllProduct().toString();
}
}
我想在EUREKA-SERVICE.CONSUMER中调用,如果按以往 做法,在这里面写个controller然后直接调用http://localhost:8011/getProducts就可以得到结果,但是我们现在要从Eureka注册中心获取服务的提供者进行请求。
我们在EUREKA-SERVICE.CONSUMER使用如下方式调用:
@RestController
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
@Autowired
DiscoveryClient discoveryClient;
@GetMapping("/getAllProduct")
public String getAllProduct(){
/**第一步获取服务实例。
* 参数是实例id(服务id),也就是我们定义的spring.application.name参数
* 返回结果是个集合,因为我们的服务会做集群,不止一个,但是服务id都为同一个,
* 当一个服务比如:serviceproducer这个服务,发布到多个服务器上时,在注册中心的页面上的
* Availability Zones就会有多个。
* 我们根据服务id也就时页面上的 Application得到其中一个实例。
* */
List<ServiceInstance> instances = discoveryClient.getInstances("eureka-service.producer");
//第二步: 从实例中获取服务的具体地址,我们这里只有一个服务,所以只用第一个
ServiceInstance serviceInstance = instances.get(0);
//第三步: 从服务实例中获取ip+port
String host = serviceInstance.getHost();
int port = serviceInstance.getPort();
String uri="http://"+host+":"+port+"/getProducts";
String vos = restTemplate.getForObject(uri, String.class);
// List<ProductVo> productVos = Arrays.asList(forObject);
return vos;
}
}