1. 什么是注册中心?
2. 分布式应用的CAP理论知识
3. eureka 服务注册与发现实战
Spring Cloud Eureka是Spring Cloud Netflix 子项目的核心组件之一
1.1 理解注册中心:
在微服务架构中往往会有一个注册中心,每个微服务都会向注册中心去注册自己的地址及端口信息,注册中心维护着服务名称与服务实例的对应关系。每个微服务都会定时从注册中心获取服务列表,同时汇报自己的运行情况,这样当有的服务需要调用其他服务时,就可以从自己获取到的服务列表中获取实例地址进行调用,Eureka实现了这套服务注册与发现机制。
服务提供者provider:
项目启动的时候向注册中心上报自己的地址和端口信息
服务消费者consumer:
项目启动的时候向注册中心上报自己的地址和端口信息,调用时向注册中心获取服务提供者的接口。
1.2 为什么要用注册中心:
当微服务应用的机器越来越多时,调用方需要知道提供者的接口,而单独靠配置文件去控制地址和端口是很难维护的,一个接口地址的修改就会引发其他配置文件的修改。
CAP定理:
指的是在一个分布式系统中,Consistency(一致性)、availability(可用性)、Partition(容错性),三者不可同时获得。
为什么不能三者同时兼容呢?
注册中心选择:
zookeeper:
CP设计,保证了一致性,集群搭建的时候,某个节点失效,则会进行选举的leader,或者半数以上的节点不可用,则无法提供服务,因此可用性无法满足。
eureka:
AP原则:无主从节点,一个节点挂了,自动切换到其他节点可以使用,去中心化。
环境:idea、Java 8、SpringBoot 2.2.5、SpringCloud Hoxton.SR3
注意:SpringBoot的版本需要与SpringCloud版本适配。
3.1 创建注册中心服务 eureka-server
打开 idea,File --> New -->Project --> Spring initializr
填好包名和项目名
选好SpringBoot的版本,选择服务注册依赖,Next之后就 finish。
编写 application.yml 配置文件
server:
port: 8761
eureka:
instance:
hostname: localhost #服务名
client:
register-with-eureka: false # 是服务端,不向注册中心注册自己的信息
fetch-registry: false # 是服务端,不向注册中心拉取自己的信息
service-url: # 注册中心地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
在主程序入口 EurekaServerApplication,使用@EnableEurekaServer开启 eureka 服务端
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
打开浏览器访问:http://localhost:8761/
Application:No instances available
指的是没有可用服务注册进来
3.2 创建商品服务 product_service
3.2.1 FIle --> New --> Project --> Spring initializr
填写包名和项目名
3.2.2 添加 Spring Web、eureka client 客户端依赖
3.2.3 创建 controller、service、serviceImpl、domain 这些包
3.2.4 在 domain 下编写 Product 实体类
/**
* 商品实体类
* @author 药岩
* @date 2020/3/21
*/
public class Product implements Serializable {
public Product() {
}
public Product(int id, String name, int price, int store) {
this.id = id;
this.name = name;
this.price = price;
this.store = store;
}
private int id;
/**
* 商品名称
*/
private String name;
/**
* 商品价格
*/
private int price;
/**
* 商品库存
*/
private int store;
此处 get、set 方法...
}
3.2.5 编写 ProductService 接口
public interface ProductService {
List<Product> listProduct();
Product findById(int id);
}
3.2.6 编写 ProductServiceImpl 实现类,基于内存操作,暂时不连接 数据库。
/**
* ProductServiceImpl 实现类
* @author 药岩
* @date 2020/3/21
*/
@Service
public class ProductServiceImpl implements ProductService {
private static Map<Integer, Product> mapDao = new HashMap<>();
static {
Product product1 = new Product(1, "iphone11", 5999, 10);
Product product2 = new Product(2, "ipad Pro", 6999, 12);
Product product3 = new Product(3, "MacBook Pro", 17999, 14);
Product product4 = new Product(4, "洗衣机", 8999, 15);
Product product5 = new Product(5, "小爱同学", 299, 10);
Product product6 = new Product(6, "冰箱", 3999, 10);
Product product7 = new Product(7, "电视", 3999, 10);
mapDao.put(1, product1);
mapDao.put(2, product2);
mapDao.put(3, product3);
mapDao.put(4, product4);
mapDao.put(5, product5);
mapDao.put(6, product6);
mapDao.put(7, product7);
}
/**
* 获取全部商品列表
* @author 药岩
* @date 2020/3/21
*/
@Override
public List<Product> listProduct() {
Collection<Product> collection = mapDao.values();
List<Product> list = new ArrayList<>(collection);
return list;
}
/**
* 根据id获取商品
* @author 药岩
* @date 2020/3/21
*/
@Override
public Product findById(int id) {
return mapDao.get(id);
}
}
3.2.7 在controller 包下,编写 ProductController 控制层
@RestController
@RequestMapping(value = "/api/v1/product")
public class ProductController {
@Autowired
private ProductService productService;
/**
* 获取全部商品列表
* @author 药岩
* @date 2020/3/21
*/
@RequestMapping(value = "list")
public List<Product> listProduct(){
return productService.listProduct();
}
/**
* ProductServiceImpl 实现类
* @author 药岩
* @date 2020/3/21
*/
@RequestMapping(value = "find")
public Product findById(@RequestParam("id") int id){
return productService.findById(id);
}
}
启动 product_service 项目,确保项目没有问题,浏览器访问:http://localhost:8080/api/v1/product/list
3.2.8 编写 product_service 项目的 application.yml
server:
port: 8771
spring:
application:
name: product-service # 服务名称
eureka:
client:
service-url: # 指定注册中心地址,往注册中心注册
defaultZone: http://localhost:8761/eureka/
3.2.9 先启动eureka_server项目 再启动 product_service 项目
访问 eureka_server 打开浏览器:http://localhost:8761/
可以看到,product-service 服务已经注册到 注册中心去了。
再添加一个 product-service 服务,端口为 8772
这里的 single instance only 指只能有单个服务实例,把它默认勾选去掉,添加一个端口为8772的product-service 服务
直接点击运行来启动项目
再访问 注册中心可以发现有8771、8772 两个端口服务,达到集群模式。
注意:
上图红色英文是eureka的自我保护模式,当这个模式开启时,如果 product-service 服务由于网络原因无法上报自己的地址和端口时,eureka 并不会将它移除,而把这个自我保护模式关闭后,如果由于网络抖动无法上报,则会把该服务移除。(不建议关闭)
如需要关闭,则配置文件配置以下内容:
# 关闭自我保护模式
eureka:
server:
enable-self-preservation: false
问题:
为什么 product-service 在配置文件指定注册中心地址就能注册,而不需要在主程序入口添加 @EnableEurekaClient 注解呢?
@SpringBootApplication
@EnableEurekaClient //可以不需要加这个注解
public class ProductServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProductServiceApplication.class, args);
}
}
官方文档解释:如果你这个依赖在这个类路径下的会自动去注册,所以不需要添加 @EnableEurekaClient 注解。