今天我带领大家模拟一个微服务项目,将平常的ALL IN ONE项目拆分成多个子服务,实现消息提供者发送消息,消息接收者消费消息,最后会以Eureka进行服务注册与发现,让多个服务通过注册中心服务器来实现消费消息。
1、创建springcloud主服务(普通Maven项目)
pom文件的配置:
springcloud依赖、springboot依赖、数据库mysql依赖、连接池Druid依赖、mybatis整合springboot依赖、junit单元测试依赖、lombak依赖、log4j日志依赖等
<?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>com.baoji</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>springcloud-api</module>
<module>springcloud-provider-8001</module>
<module>springcloud-consumer-80</module>
<module>springcloud-eureka-7001</module>
</modules>
<!-- 打包方式 -->
<packaging>pom</packaging>
<!--版本-->
<properties>
<junit.version>4.12</junit.version>
<lombok.version>1.16.16</lombok.version>
</properties>
<dependencyManagement>
<dependencies>
<!--springcloud依赖-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-dependencies -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--SpringBoot依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--数据库mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<!--数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.14</version>
</dependency>
<!--springboot mybatis启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<!--Junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--日志门面-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2、使用IDEA工具连接mysql,创建dept表
建表语句:
insert into dept(deptname,dept_source) values ('开发部',DATABASE());
insert into dept(deptname,dept_source) values ('产品部',DATABASE());
insert into dept(deptname,dept_source) values ('营销部',DATABASE());
insert into dept(deptname,dept_source) values ('宣传部',DATABASE());
insert into dept(deptname,dept_source) values ('运维部',DATABASE());
select * from dept;

3、创建springcloude-api子服务(普通maven项目)
springcloud-api子服务:此自服务只负责创建pojo对象,供其他服务使用。
- 1.1、pom中配置属于自己的依赖
<?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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.baoji</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-api</artifactId>
<!--为自己的model配置依赖,如果父类中已经配置了该依赖,这里就不用写了-->
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
- 2、创建pojo类(使用lombak快速创建)

package com.baoji.springcloud.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 网络传输必须将实体类进行序列化
*/
@Data
@NoArgsConstructor //无参构造
@Accessors(chain = true) //链式写法
public class Dept implements Serializable { //Dept实体类 orm实体关系映射
private Long deptno;
private String deptname;
//数据存放在哪个数据库 微服务,一个服务对应一个数据库,同一个信息可能存在不同的数据库
private String db_source;
public Dept(String deptname) {
this.deptname = deptname;
}
}
4、创建springcloud-provider-8001子服务(设置消息提供者端口:8001)

- 1、配置pom文件
生产者服务调用pojo服务创建实例是通过pom文件引入dependency来使用
<?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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.baoji</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-provider-8001</artifactId>
<dependencies>
<!--我们要拿到实体类,所以要配置api model-->
<dependency>
<groupId>com.baoji</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--jetty 充当服务器,相当于tomcat-->
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jetty -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<!--热部署工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
- 2、配置mybatis-config.xml文件(mybatis配置二级缓存)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
- 3、配置application.yml文件(服务端口号、mybatis配置、mapper配置、spring配置、连接数据库)
server:
port: 8080
# mybatis 配置
mybatis:
type-aliases-package: com.baoji.springcloud.pojo
config-location: classpath:mybatis/mybatis-config.xml #config-location 为mybatis配置文件
mapper-locations: classpath:mybatis/mapper/*.xml #mapper-locations 为mapper配置
#spring配置
spring:
application:
name: springcloud-provider
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db01?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
username: root
password: root
- 4、编写Dao层(按编号查询员工、添加员工、查询所有员工)
package com.baoji.springcloud.dao;
import com.baoji.springcloud.pojo.Dept;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface DeptDao {
//添加员工
public boolean addDept(Dept dept);
//按编号查询员工
public Dept getDeptById(long id);
//查询所有员工
public List<Dept> findAll();
}
- 4、编写DeptMapper.xml文件(实体类和数据库映射)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.baoji.springcloud.dao.DeptDao">
<insert id="addDept" parameterType="Dept">
insert into dept(deptname, dept_source) values (#{deptname},DATABASE());
</insert>
<select id="getDeptById" resultType="Dept" parameterType="Long">
select * from dept where deptno = #{deptno};
</select>
<select id="findAll" resultType="Dept">
select * from dept;
</select>
</mapper>
- 5、编写业务接口层
package com.baoji.springcloud.service;
import com.baoji.springcloud.pojo.Dept;
import java.util.List;
public interface DeptService {
//添加员工
public boolean addDept(Dept dept);
//按编号查询员工
public Dept getDeptById(long id);
//查询所有员工
public List<Dept> findAll();
}
- 6、编写业务实现层(属性注入dao层获取数据)
package com.baoji.springcloud.service;
import com.baoji.springcloud.dao.DeptDao;
import com.baoji.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptDao deptDao;
public boolean addDept(Dept dept) {
return deptDao.addDept(dept);
}
public Dept getDeptById(long id) {
return deptDao.getDeptById(id);
}
public List<Dept> findAll() {
return deptDao.findAll();
}
}
- 7、编写控制层
package com.baoji.springcloud.controller;
import com.baoji.springcloud.pojo.Dept;
import com.baoji.springcloud.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
//提供ResultFul服务,以resuFul方式返回
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
@PostMapping("/dept/add")
public boolean addDept(Dept dept){
return deptService.addDept(dept);
}
@GetMapping("/dept/get/{id}")
public Dept getDeptById(@PathVariable("id") long id){
return deptService.getDeptById(id);
}
@GetMapping("/dept/list")
public List<Dept> findAll(){
return deptService.findAll();
}
}
- 8、编写springboot核心启动类
package com.baoji.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DeptProvidre_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvidre_8001.class,args);
}
}
- 9、浏览器测试消息提供者是否获取数据(生产者测试成功!)

5、创建springcloud-consumer-80 消费者服务(此处80端口为消费者端口,服务器默认端口)

- 1、配置pom文件(给消费者pom中引入springcloud-api实体类服务和spring-boot-starter-web)
<?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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.baoji</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-consumer-80</artifactId>
<!--消费者-->
<!--实体类+web-->
<dependencies>
<dependency>
<groupId>com.baoji</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
- 2、编写配置文件(将RestTemplate手动注册到spring中,使用@bean注解)
消费者没有service层、一般分布式项目中,消费者如果想要直接访问生产者,必须使用RestTemplate, 供我们直接调用生产者控制层提供的接口就可以 ,我们将RestTemplate直接手动注册到spring中
package com.baoji.springcloud.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ConfigBean { //Configuration -- spring applicationContext.xml
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
- 3、编写控制层
基本原理核心代码:
我们直接属性注入RestTemplate对象,先将生产者前面的绝对路径定义成静态常量,根据生产者控制层提供接口的类型来调用该对象里面的返回方法。
post类型的参数,返回的是restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
get类型的参数,返回的是restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
返回对象的方法参数为(生产者请求地址,实参,返回参数类型的字节码)
package com.baoji.springcloud.controller;
import com.baoji.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
public class DeptConsumerController {
//消费者:不应该有service层
//RestTemplate 供我们直接调用就可以 直接手动注册到spring中
//参数(url,请求路径:Map,Class<T> responseType)
@Autowired
private RestTemplate restTemplate; //提供多种快捷访问Http服务的方法,简单的restFul服务模板
//请求路径前固定的url
private static final String REST_URL_PREFIX = "http://localhost:8080";
//消费者请求的地址
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
//服务器返回对应的对象
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
}
@RequestMapping("/consumer/dept/add")
public boolean add(Dept dept) {
//返回post类型的对象,三个参数(请求的地址,拼接的参数,返回响应的字节码)
return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> list(){
//返回get类型的对象,两个参数(url,返回响应的字节码)
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",List.class);
}
}
- 4、编写消费者服务启动类
package com.baoji.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DeptConsume_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsume_80.class,args);
}
}
- 5、浏览器测试消费者远程服务调用生产者获取数据
消费者生产者模式和普通ALLIN ONE之间的区别:完全解耦,服务与服务之间,没有依赖。
浏览器输入消费者接口的路径,注意看两次测试截图路径的变化,第一次为生产者通过url获取数据,第二次是消费者直接输入自己的url访问的路径获取数据,一般ALL IN ONE项目中,前端通过获取生产者的接口url来进行数据提取,现在多服务项目中,消费者可以不依赖生产者的url,就可以获取数据,达到了消息提供者与消息接收者之间的完全解耦。

5、Eureka介绍
我们都知道Dubbo框架的服务注册和发现是基于Zookeeper客户端实现的,每次消息提供者和消息接收者进行注册时必须要先开启Zookeeper客户端,才可以对服务进行注册。消息提供者和消息接收者通过RPC远程过程异步调用
Dubbo架构图:


Eureka是Springcloud框架的服务注册组件,服务提供者发送消息服务到注册中心,服务消费者获得注册中心的地址,微服务项目有了服务注册与发现,只需要使用服务的标识符,就可以访问到服务,而不需要经常修改调用的配置文件,功能类似于Dubbo的注册中心Zookeeper
Eureka架构原理:
- 1、SpringCloud封装了NetFlix公司的Eureka模块来实现注册和发现。
- 2、Eureka采用C-S架构,EurekaServer作为服务注册功能的服务器,他是服务注册中心
- 3、其他微服务使用Eureka的客户端连接到EurekaServer并维持心跳连接,系统维护人员就可以通过EureakaServer来监听系统中各个微服务是否正常运行,SpringCloude的其他模块(例如:Zuul)就可以通过EurekaServer来发现系统中的其他微服务。


6、使用Eureka实现服务发现与注册(创建子服务)
- 创建springcloud-eureka-7001子服务(端口号为7001)
eureka操作第一步:
- 1、导入pom依赖(spring-cloud-starter-netflix-eureka-server)
<?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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.baoji</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-eureka-7001</artifactId>
<!--导入eureka-->
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-netflix-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
</dependencies>
</project>
eureka操作第二步:
- 2、Eureka配置文件编写(配置eureka服务端主机名、表示自己是注册中心、和eureka服务注册中心交互的地址)
server:
port: 7001
#Eureka配置
eureka:
instance:
hostname: localhost # Eureka服务端的实例名称
client:
register-with-eureka: false #表示是否向Eureka中心注册自己,自己就是Eureka服务器,不需要注册自己
fetch-registry: false # fetch-registry为false 表示自己是注册中心
service-url: # 监控页面,和eureka服务注册中心交互的地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
- 3、编写springboot启动类
eureka操作第三步:
3、设置@EnableEurekaServer注解: 注册中心服务器端的启动类,可以接收别人注册进来
package com.baoji.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
//启动之后 访问http://localhost:7001/
@SpringBootApplication
@EnableEurekaServer // EnableEurekaServer 注册中心服务器端的启动类,可以接收别人注册进来
public class EurekaServer_7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServer_7001.class,args);
}
}
- 4、浏览器测试(通过eureka地址测试是否成功(localhost:7001))
红线处表示服务注册后的服务实例存放处


7、将服务提供者注册进服务注册中心
eureka第一步:服务提供者中导入依赖
- 1、在服务提供者pom文件dependencys下添加spring-cloud-starter-eureka依赖
<!--配置spring-cloud-starter-eureka 为了将服务提供者注册进注册中心-->
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.3.RELEASE</version>
</dependency>
eureka第二步:在服务提供者配置文件中添加服务发送之后注册到服务器的url路径
- 2、在服务提供者配置文件中加入eureka的配置
#Eureka的配置 服务注册到哪里 和注册中心服务的路径一样
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
eureka第三步:在服务提供者启动类中添加自动注册注解
- 3、启动类中添加@EnableEurekaClient注解,将服务自动注册到服务端
@EnableEurekaClient //把客户端服务启动之后,将服务自动注册到服务端
- 4、浏览器测试eureka(发现Application中已经注册进服务了,服务名为我们在生产者spring配置中的名字)
仔细看Status中描述信息为localhost地址,我们可以将此描述信息更改默认描述

- 5、更改eureka的默认信息
在生产者配置文件中的eureka配置中加入
instance:
instance-id: springcloud-provider-dept8001 # 修改eureka上的默认描述信息

此时我们可以发现配置信息已经更改了

- 6、接下来我们配置eureka的监控信息(不重要)
1、在消息接收方pom中加入完善监控的依赖
<!--actuator完善监控的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、在消息接收方配置文件中加入配置信息
#info信息
info:
app.name: linchi-springcloud
company.name: com.baoji

- 7、测试监控输出信息(点击start下的描述信息url,会出现刚才配置的监控个人信息)

8、Eureka自我保护机制
自我保护机制:当某时刻,消息提供方服务因断电、断网、自己关闭服务等各种原因导致某一个微服务不可用了,eureka不会立刻清理,依然会对该微服务的信息进行保存。
自我保护模式是一种应对网络异常的安全保护措施,宁可保护所有的微服务,也不盲目删除注销任何健康的微服务,使用自我保护模式,让Eureka集群更加的健壮和稳定。
springcloud中,可以使用eureka.server.enable-self-preservation = false禁用自我保护模式,但是不推荐使用。

9、服务发现
在团队开发中,面对的是好多个微服务,开发者要想通过微服务获取到对应服务的信息,就必须这么来做:
- 1、在服务发送者的controller类中,属性注入DiscoveryClient类,得到具体微服务的信息,通过这个类
client.getServices()方法就可以获得所有微服务的清单,通过client.getInstances("SPRINGCLOUD-PROVIDER");就可以根据微服务id(微服务id为spring配置文件中配置的名称)获得每个微服务实例,遍历就可得到微服务的具体端口号、服务主机等信息。
//获取一些配置的信息,得到具体的微服务
@Autowired
private DiscoveryClient client;
//注册进来的微服务 获取一些消息
@GetMapping("/dept/discovery")
public Object discovery() {
//获得所有微服务列表的清单
List<String> services = client.getServices();
System.out.println("discovery=>services" + services);
//得到具体的微服务信息 通过微服务的id
List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER");
for (ServiceInstance instance : instances) {
System.out.println(
instance.getHost() + "\t" + instance.getUri() + "\t" + instance.getPort() + "\t"
);
}
return this.client;
}
- 2、在服务发送者的启动类中加入服务发现注解
@EnableDiscoveryClient,该注解可以获取微服务的一些信息
@EnableDiscoveryClient //服务发现,获取微服务的一些信息
- 3、浏览器测试获取到微服务的数据(输入http://localhost:8080/dept/discovery)
获取数据成功!

10、Eureka集群配置(本项目集群中设置三个注册中心)
集群中设置多个注册中心,防止一个注册中心出现错误,导致系统瘫痪
- 1、和上述创建springcloud-eureka-7001子服务(端口号为7001)方式一样再创建两个子服务,也是分为三步:
1.1、pom文件引入eureka
1.2、Eureka配置文件编写(配置eureka服务端主机名、表示自己是注册中心、和eureka服务注册中心交互的地址)
1.3、设置@EnableEurekaServer注解: 注册中心服务器端的启动类,可以接收别人注册进来

- 2、在本机c盘下->windows->System32->etc->hosts文件下修改文件,将域名和localhost关联

- 3、让每个注册中心去挂载别的注册中心,注册中心相互关联,每个eureka配置相互关联另外两个注册中心的默认url地址。


- 4、将服务提供者发送到多个注册中心,就是发布到集群中

- 5、现在我们同时启动三个注册中心(7001、7002、7003),浏览器输入一个注册中心的地址,可以看到另外关联的两个注册中心也出现了

- 6、现在我们开启服务提供者(8001),将服务注册到集群中

- 7、在浏览器中输入一个注册中心的地址访问,可以发现里面已经注册到服务实例了。

如果注册中心(7001端口)的注册中心挂了,但是不影响服务注册到其他注册中心,服务还是可以继续使用,只是端口改变了而已。这样做的好处是不会影响线上服务的注册。
11、CAP原理
- CAP概念:
CAP理论指的是一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项

我们平常使用的关系型数据库RDBMS(Mysql、Oracle、SqlServer)等都是采用的ACID原则(A:原子性、C:一致性、I:隔离性、D:持久性),但是非关系型数据库NoSql(redis、mongdb)等都是采用了CAP原则(c:强一致性、A:可用性、P:分区容错性)

12 数据一致性
当聊到集群的时候,就无法不提到数据一致性的问题,那么,我们就以Eureka的Server集群为例,谈一谈集群中的数据一致性问题,并试图剖析Eureka集群的底层原理。
分布式下,有一个著名的CAP原则。C指数据一致性,A指服务可用性,P则是分区容忍性。CAP三者,最多可得其二。如在单点服务中,由于不存在分区,则可达到CA。而在分布式服务里,基于分布式本身的特性,服务存在于多个服务中,依靠网络连接。在这种情况下,由于网络延迟等原因,分区是必然存在的。也就是说,在分布式下,P必然存在,任何集群都需要在C和A中做选择。
在这样的背景下,根据选择的不同,集群服务可能是AP的,也可能是CP的。如果是AP的,则集群允许在段时间里,各节点的数据出现不一致的情况,但是集群整体服务保持可用。如果是CP的,则集群在各节点数据不一致的段时间里,整体服务是不可用的,需要等待可节点数据同步到一致,再恢复可用状态。
需要理解的是,无论是AP还是CP,对于客户端来说,可能感知并不是很强烈。在AP情况下,客户端的感知可能是服务存在一定延迟,响应较慢,而不是服务不可用。在CP情况下,客户端一般来说对数据的更新是不敏感的。即客户端并不需要过分及时的取得最新的数据,也可以正常完成服务。
13、保持在线 --心跳机制(Eureka判断客户端在向注册中心注册服务期间是否存活)
在上面的部分,我们聊了Eureka的基本架构,理解了Eureka选择AP的理由。但是,有一个问题没有提到,即:server如何监测client的即时状态?
eureka通过心跳来监测client的状态。心跳,也是集群中常用的一种状态监测手段。那么,心跳是什么呢?
想象A、B两个人正在打电话,当B一段时间不说话时,A可能就会怀疑B是否还在电话旁,就会问:“你还在吗?”。这种情况,可以理解为是A主动发起了一个请求,去查询B的状态。事实上,B可以每隔一段时间,主动给A的个回应:“嗯……对……没错”。这样,A就会知道,B一直在,就不会猜也不会问了。
心跳正如打电话时的第二种状态:
client每隔一定时间,向server发送一个心跳(请求)。 server每隔一段时间,检查所有client的心跳信息,当发现某个client长时间没有心跳,则认为该client下线。 client再次上线后,会再次向server发送心跳。 eureka里,client发送心跳的间隔时间默认为30秒,某client如果90秒内都没有心跳,则会被认为宕机。server默认每隔1分钟检查一次所有client的心跳信息。
配置项如下:

14、Eureka的AP原理
在AP和CP之间,Eureka选择的AP。即在数据不致的情况下,也允许客户端服务。我们尝试分析下,Eureka这种做法的理由是什么?
在分布式微服务架构中,基本角色有三种,分别是生产者,消费者,注册中心。生产者对外提供服务,消费者使用生产者的服务,而注册中心则承担一个中间人角色,维护生产者服务列表数据,并在消费者需要的时候提供给消费者。
在Eureka架构中,EurekaServer就是一个注册中心的角色。在EurekaServer中,存放的主要数据就是生产者服务列表,在需要的时候,将其提供给消费者。
所以,Eureka选择AP的理由是:
Eureka的功能专注于服务注册和发现,不会出现数据的竞态。
数据竞态指的是:在同一时间点,有多个线程竞争同一数据。
我们来了解一下Eureka的基本架构,分析为何不会出现数据竞态。架构图如下:
Eureka的基本架构

- 1、Eureka Server各节点之间通过复制来同步数据。
- 2、生产者向Eureka Server中的任一节点注册数据。
- 3、消费者向Eureka Server中的任一节点获取数据。(事实上,Eureka架构中,并没有严格区分生产者、消费者,二者通常是一体的)
- 4、消费者根据取得的生产者列表直接向任一生产者发起服务调用,如失败,可选择向其他生产者再次调用。
基于以上:
- 1、每个client只会向一个server注册。
- 2、每个server节点总是从其他服务中获取数据,用新的数据覆盖掉旧的数据。
- 3、消费者从server节点中取得client列表,并缓存一份在本地,下次直接从本地获取。
- 4、即使某个client宕机,导致某些server中的生产者列表不准确,此时,消费者可以根据需要进行重试。
- 5、当server中的生产者列表发生变化时,会将变化向订阅的服务消费者发布,消费者就会更新本地缓存。
综上,Eureka选择AP是可行的。事实上,由于注册中心仅承担服务注册与发现的功能,并且,消费者有失败重试,本地缓存,服务订阅等相关处理机制,这使得注册中心集群可以选择CP,也可以选择AP。Zookeeper选择的就是AP。
15、Eureka比Zookeeper好在哪里???
CAP理论指出:一个分布式系统不可能同时满足C(一致性)、A(可用性)、P(容错性)、但是分区容错性P在分布式系统中是必须要保证的,所以我们只能在A和C之间进行权衡。
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉,总的来说,服务注册功能对可用性的要求比一致性高。
- 那么问题来了,Zookeeper和Eureka同样都是服务注册中心,那么Eureka比Zookeeper好在哪里呢?
首先:Zookeeper和Eureka用到的原则都是CAP原则
1、Zookeeper保证的是CP,即一致性和容错性
Eureka保证的是AP,即可用性和容错性
2、Zookeeper原理:Zookeeper集群中存在多个节点时会选取主节点来管理所有的节点,但是当msater主节点因为网络故障与其他节点失去联系时,剩余其他节点会重新进行leeder选举,那么问题来了?选举主节点时间太长,30-120s,在整个选取期间整个zk集群是不可用的,这就导致在选取主节点期间注册服务瘫痪问题,在云部署环境下,因为网络问题让zk集群失去主节点是常会发生的事件,虽然服务最终都能够恢复,但是长时间的选举主节点导致注册长期不可用是不能容忍的。给用户的用户体验不够好。
3、Eureka原理:Eureka弥补了zookeeper的这个缺点,在设计时优先保证可用性,Eureka各个节点都是平等的其他节点挂掉不会影响正常节点的注册,剩余的节点依然可以提供注册和查询服务,而Eureka的客户端向某个Eureka服务注册时,如果发现连接失败,则会自动切换到其他节点,因为节点之间是同等优先级的,只要有一台Eureka服务注册中心存在,就能保证服务的可用性,只不过查到的信息可能不是最新的,除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,当出现网络故障时,Eureka会这么做:
- 1、Eureka不再会从注册列表中移除因为长时间没收到心跳而应过期的服务
- 2、Eureka仍能够接收新服务的注册和查询请求,但是不会同步到其他节点上(保证当前注册中心节点依然可用)
- 3、当网络稳定时,当前实例新的注册信息会被同步到以前因为网络原因暂时不可用的其他节点中
总结:Eureka可以更好的应对因为网络故障导致部分节点失去联系的情况,而不会像Zookeeper那样使整个注册服务瘫痪


