第一节:微服务架构为什么需要注册中心,它解决了什么问题?
1,什么是注册中心?
注册中心可以说是微服务架构中的“通讯录”,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其他服务时,就到这里找到服务的地址,进行调用。
举个栗子吧,如我们手机通讯录的两个使用场景:
(1)当我想给张三打电话时,那我需要在通讯录中按照名字找到张三时,然后就可以找到他的手机号拨打电话。(服务发现)
(2)李四办了手机号,那么他把手机号告诉我,我把李四的号码存进通讯录,后续,我就可以从通讯录找到他。(服务注册)
2,为什么需要注册中心?它解决了什么问题?
解决了:
1,服务管理
比如你有上百个服务部署在上百台服务器上面,你怎么知道每个服务地址多少,端口多少,eureka就是干这事的,服务都在我这注册,如果有人用,我来给你找。
2,服务的依赖关系。
3,什么是注册中心Eureka?
Eureka也是一个基于注册中心的rest风格微服务,包含了服务的定位,负载均衡,故障转移等等,是SpringCloud的子项目,为SpringCloud提供服务的注册与发现的功能。
Eureka包含三种角色:
(1)eureka server注册发现;
(2)service provider服务提供方,自己注册到eureka;
(3)service consumer 服务消费方,从eureka得到注册列表,消费服务。
第二节:一个Eureka注册中心的入门例子
下面的大部分章节都是工程实例,有需要可以下载项目源码。
该基础篇的项目最终结构如下:
好了,下面我们开始敲代码,一步一步实现上面图片的内容。
首先我们新建一个单机版的eureka注册中心例子,这个最简单。
(1)在spring-cloud-in-action下新建一个子模块,命名为eureka,packaging选择pom。在eureka下面新建maven项目eureka-server。
(2)在eureka-server的pom文件中加入如下代码:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
(3)在子模块eureka的pom文件中添加eureka服务端依赖(之所以加在这里,是为了方便所有eureka服务使用):
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
(4)在总模块spring-cloud-in-action的pom文件中添加如下代码:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
(5)三个pom文件配置好之后,添加一个配置文件:application.properties,加上如下代码:
spring.application.name=eureka-server
server.port=8761
#是否将自己注册到eureka-server 默认为true。
#现在置为false,是因为只有自己本身这一个注册中心,没有其他注册中心可以注册自己
eureka.client.registerWithEureka=false
#是否从eureka-server获取注册信息,默认为true
eureka.client.fetchRegistry=false
(6)添加一个启动类。EurekaApplication.java。
package com.twf.eureka.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
(7)运行启动类,在浏览器打开http://localhost:8761/,页面展示如下:
(多说一句:在写这个例子的时候,一开始运行总是报错,报找不到配置类啥的,但是代码和老师的完全一致,实在不知道是怎么回事,请课程老师远程帮我调试,最后觉得可能是jar包下载失败,强制更新也没有用,让我把整个maven仓库中jar全删掉重新下,然后就可以了。)
第三节:建设高可用集群版的注册中心Eureka
第二节的例子是单机版的注册中心,大家都知道单机版的服务稳定性差,风险高,可用性低,一般不使用。所以这节我们来构建集群版的注册中心。
(1)在eureka模块下新建一个maven项目,命名为eureka-server-ha。
(2)在pom文件中加入如下代码:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
(3)添加一个启动类,拷贝第二节中的启动类就好。
(4)添加两个配置文件。
第一个:application-eureka1.properties
spring.application.name=eureka-server
server.port=8761
eureka.instance.hostname=eureka1
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://eureka2:8762/eureka/
第二个:application-eureka2.properties
spring.application.name=eureka-server
server.port=8762
eureka.instance.hostname=eureka2
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://eureka1:8761/eureka/
(5)clean package打包这个项目,然后cmd打开两个控制台,分别都进入jar所在目录,分别输入如下命令中的一条。
java -jar eureka-server-ha-0.0.1-SNAPSHOT.jar --spring.profiles.active=eureka1
java -jar eureka-server-ha-0.0.1-SNAPSHOT.jar --spring.profiles.active=eureka2
(6)浏览器访问localhost:8761和localhost:8762,界面如下。
成功启动。
第四节:在高可用的eureka上,构建provider服务
(1)在eureka模块下新建maven项目,命名为eureka-provider。
(2)在pom文件中添加如下代码:
<!--添加依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--添加maven打包插件-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
(3)添加配置文件application.properties,添加如下代码:
spring.application.name=eureka-provider
server.port=8083
eureka.client.serviceUrl.defaultZone=http://eureka1:8761/eureka/,http://eureka2:8762/eureka/
(4)新建启动类。
package com.twf.eureka.provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
(5)运行启动类,同时启动上一节的两个eureka注册中心。
(6)添加一个接口。
新建一个po类:
package com.twf.eureka.provider.domain;
public class Product {
private int id;
private String name;
public Product(int id, String name) {
super();
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Product [id=" + id + ", name=" + name + "]";
}
}
新建一个controller:
package com.twf.eureka.provider.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.twf.eureka.provider.domain.Product;
@RestController
public class ProductController {
@RequestMapping(value="list",method=RequestMethod.GET)
public List<Product>listProduct() {
List<Product> list = new ArrayList<Product>();
list.add(new Product(1, "登山包"));
list.add(new Product(2, "登山杖"));
list.add(new Product(3, "冲锋衣"));
list.add(new Product(4, "帐篷"));
list.add(new Product(5, "睡袋"));
return list;
}
}
重启服务,浏览器访问http://localhost:8083/list,结果如下:
第五节:在高可用的eureka上,构建consumer服务
这里实现一个消费方通过注册中心找到服务的注册信息,从而调用服务的目的。
(1)在eureka模块下,新建一个maven项目,命名为eureka-consumer。
(2)修改pom文件,同第四节。
(3)添加配置文件application.properties,添加代码如下:
spring.application.name=eureka-consumer
server.port=8084
eureka.client.serviceUrl.defaultZone=http://eureka1:8761/eureka/,http://eureka2:8762/eureka/
(4)新建一个实体类。
package com.twf.eureka.consumer.domain;
public class Product {
private int id;
private String name;
public Product(int id, String name) {
super();
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Product [id=" + id + ", name=" + name + "]";
}
}
(5)新建一个controller类。
package com.twf.eureka.consumer.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.twf.eureka.consumer.domain.Product;
import com.twf.eureka.consumer.service.ProductService;
@RestController
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping(value="list",method=RequestMethod.GET)
public List<Product>listProduct() {
List<Product> list = productService.listProduct();
return list;
}
}
(6)新建一个service类。
package com.twf.eureka.consumer.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.twf.eureka.consumer.domain.Product;
@Service
public class ProductService {
@Autowired
private LoadBalancerClient loadBalancerClient; // ribbon的负载均衡客户端
public List<Product> listProduct() {
ServiceInstance si = loadBalancerClient.choose("eureka-provider");
StringBuffer sb = new StringBuffer("");
sb.append("http://");
sb.append(si.getHost());
sb.append(":");
sb.append(si.getPort());
sb.append("/list");
System.out.println(sb.toString());
RestTemplate restTemplate = new RestTemplate();
ParameterizedTypeReference<List<Product>> typeRef = new ParameterizedTypeReference<List<Product>>(){};
ResponseEntity<List<Product>> resp = restTemplate.exchange(sb.toString(), HttpMethod.GET, null, typeRef);
List<Product> plist = resp.getBody();
return plist;
}
}
(7)新建一个启动类,同第4节。
(8)运行启动类,日志打印http://laptop-gei69kqu:8083/list,将该url复制到浏览器中访问,结果和第四节的结果一致。
第六节:剖析注册中心eureka的架构原理
1,Register(服务注册):把自己的ip和端口给eureka。
2,renew(服务续约):发送心跳,30秒发送一次心跳,告诉eureka自己还活着。
3,eviction(剔除):超过90秒,eureka认为你死了,从注册表剔除。
4,cancel(服务下线):provider停止关闭,调用eureka,把自己从注册表剔除,防止consumer调用不存在的服务。
5,get registery(获取注册表)。
6,replicate(复制):eureka集群自己的数据同步和复制。
第七节:基于分布式CAP定理
第八节:在什么条件下,Eureka会启动自我保护?为什么要启动自我保护?
启动两个eureka注册中心,再启动一个provider和一个consumer,一共4个服务。启动成功之后,再停掉provider和consumer,过一段时间,eureka界面出现如下提示:
然后再启动这两个服务,过一段时间,红色提示消失。
如果要关闭自我保护模式,则需要在eureka的所有注册中心(服务端)的配置文件都加上如下配置:
#关闭自我保护,将该配置设为false
eureka.server.enableSelfPreservation=false
#清理间隔(单位毫秒,默认60*1000)
eureka.server.eviction-interval-timer-in-ms=60000
从新打包启动:
当关闭provider和consumer之后,过一分钟,刷新,界面如下:
发现关闭的服务已经从注册中心移除了。
第九节:如何优雅停止服务?并快速安全的让Eureka注销服务
优雅停服指的是,在停止服务之前,做一些安全操作。
假如我们要优雅停止provider,做法就是在配置文件中加入如下配置:
#启用shutdown
endpoints.shutdown.enabled=true
#禁用密码验证
endpoints.shutdown.sensitive=false
通过http发送一个shutdown请求就可以了。
比如我这里用的是一个http工具类,代码见文末源码。
第十节:如何加强Eureka注册中心的安全认证?
以eureka-server-ha注册中心为例,来看eureka如何进行安全认证。
(1)修改pom文件,添加安全依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
(2)修改配置文件,一是添加安全认证的配置,而是修改注册中心的地址。两个配置文件都要修改。
#设置服务注册中心地址,指向另一个注册中心(此时地址中加入了账号密码user:123456)
eureka.client.serviceUrl.defaultZone=http://user:123456@eureka2:8762/eureka/
#安全认证
#开启基于http basic的安全认证
security.basic.enabled=true
security.user.name=user
security.user.password=123456
(3)打包启动之后,在访问eureka注册中心之前会出现如下的要输入用户密码的界面:
(4)其他的服务如果要注册到这个注册中心,则其配置文件设置的注册中心地址也要加上用户名及密码。如下:
eureka.client.serviceUrl.defaultZone=http://user:123456@eureka1:8761/eureka/,http://user:123456@eureka2:8762/eureka/