首先比较一下Zookeeper和Eureka的区别?
1、CAP:C:强一致性,A:高可用性,P:分区容错性(分布式中必须有)
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:
CA-单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。(RDMBS,关系型数据库:mysql,sqlserver,oracle等)
CP-满足一致性,分区容错性的系统,通常性能不是特别高。(MongoDB,Redis,HBase等)
AP-满足可用性,分区容错性的系统,通常可能对一致性要求低一些。(CouchDB,DynamoDB等)
Zookeeper遵守CP原则
为什么说Zookeeper不保证高可用性? 原因在于当master节点(是某一台Zookeeper服务器)因为网络故障与其他节点(应该是注册进入Zookeeper节点)失去联系时,剩余节点会重新进行leader选
举。问题在于,选举leader的时间太长,30~120s,且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生
的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。(总结:Zookeeper在某一时刻,只有一台节点提供服务注册发现(master节点),如果宕机了,此时其他的节点还
没有选举好,就会造成服务器瘫痪)
Eureka遵守AP原则
Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某
个Eureka注册时候如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。(总结:就是
各个节点的Eureka虽然都提供服务,但是数据不保证一致性,猜想:某一时刻还是只有一台服务器提供注册,只是当这台服务器出现问题,此时其他的Eureka直接顶替他,但是此时的数据可能就没有同步
就会造成数据的不一致性)
除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
1.Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
2.Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
3.当网络稳定时,当前实例新的注册信息会被同步到其它节点中
mysql数据库环境搭建
创建三个数据库,为了实现每一个微服务都有一个独立的数据库
CREATE DATABASE javas1 CHARSET=utf8;
CREATE DATABASE javas2 CHARSET=utf8;
CREATE DATABASE javas3 CHARSET=utf8;
CREATE TABLE USER(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(32) NOT NULL,
dbSource VARCHAR(32) NOT NULL
)
INSERT INTO USER(NAME,dbSource) VALUES("1",DATABASE());
INSERT INTO USER(NAME,dbSource) VALUES("2",DATABASE());
INSERT INTO USER(NAME,dbSource) VALUES("3",DATABASE());
INSERT INTO USER(NAME,dbSource) VALUES("4",DATABASE());
INSERT INTO USER(NAME,dbSource) VALUES("5",DATABASE());
1、创建Eureka server(服务注册中心)
1.1 application.properties
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
//不把自己注册到Euraka中
register-with-eureka: false
//不从eureka上获取注册信息
fetch-registry: false
serviceUrl:
//单台服务器填写本机的地址(多台Eureka server 服务器的集群看下面)
defaultZone: http://localhost:8761/eureka
1.2 @EnableEurekaServer 启动
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
1.3 访问:http://localhost:8081/
2、创建 eureka client 服务提供者
数据库等其他的配置省略
1、application.properties
server.port=8082
spring.application.name=provider
//可显示ip(鼠标停留在注册的实例上)
eureka.instance.prefer-ip-address=true
eureka.client.service-url.defaultZone=http://localhost:8761/eureka //填写Eureka server服务器地址,如果有多台Eureka server服务器,就写多个(用,分开)
2、创建controller
@RestController
public class TestController {
@GetMapping("hellow")
public String hellow(){
return "hellow";
}
}
3、启动(可以改变端口,启动多个provider),感觉最好加上@EnableEurekaClient注解,测试的时候没加也没问题
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
4、Application就是我们配置的名字
如果需要修改Status中的值
eureka:
instance:
instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}} //可以修改
3、创建 eureka client 服务消费者
3.1 application.properties
server.port=80
spring.application.name=consumer
//注册服务的时候使用ip进行注册
eureka.instance.prefer-ip-address=true
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
消费者如果不希望添加到Eureka中列表中,可以设置,但是如果此消费者被其他的消费者调用,就需要注册到列表中(注意不添加到列表不代表不能从注册列表获取提供者的服务),注册到列表中的目的,是我们可以通过列表中的application名字来访问服务,而不是通过具体的IP了。
server.port=80
spring.application.name=consumer
//不把自己添加到Eureka列表中
eureka.client.register-with-eureka=false
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
3.2 controller
@RestController
public class ConsumerController {
@Autowired
public RestTemplate restTemplate;
@GetMapping("hellow")
public String consumerhellow(){
//通过http来访问provider(Eureka提供的application名字,也是我们在提供者的配置的填写的application.name)(url可以是生产者直接对外暴露的url,但是这样就没有负载均衡了)
String forObject = restTemplate.getForObject("http://PROVIDER/hellow", String.class);
return "consumer"+forObject;
}
}
补充:RestTemplate(同步)使用:https://docs.spring.io/spring-framework/docs/5.1.9.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html,5.0之后RestTemplate不在添加新的东西了
后续可能使用WebClient(异步):https://docs.spring.io/spring/docs/5.1.9.RELEASE/spring-framework-reference/web-reactive.html#webflux-client
3.3 @EnableDiscoveryClient 和 RestTemplate ,启动
@EnableDiscoveryClient //开启服务发现功能
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@LoadBalanced //使用负载均衡机制
@Bean //注册RestTemplate
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate();
return restTemplate;
}
}
3.3使用actuator来显示应用信息
如果测试不行,记得清除target目录,重新编译
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${spring-boot.version}</version>
</dependency>
2、可以在父工程的pom中导入
<build>
<finalName>springcloud-depedences</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<delimiters>
<delimiter>$</delimiter> //用$做分隔符
</delimiters>
</configuration>
</plugin>
</plugins>
</build>
3、applicaiton
info:
app.name: zy.provider:8081
company.name: www.xx.xx
#取pom.xml中配置
build.artifictId: $project.artifactId$ //project.artifactId会被替换成pom中<artifactId>xx</artifactId>
build.version: $project.version$
补充:如果使用${xx.xx},我们可以直接在配置文件中配置数据,就不用使用插件了
测试结果
4、搭建Eureka server 端集群
1、为了测试方便,在hosts文件夹中添加,添加多个域名(如果使用 localhost可能会出现问题,没有测试)
127.0.0.1 eurekaserver8090.com
127.0.0.1 eurekaserver8091.com
2、第一台 eureka server 服务器配置(主要就是serviceUrl 改变了)
server:
port: 8090
eureka:
instance:
hostname: eurekaserver8090.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
#指定其他的eureka server服务器,多个服务用,分割
defaultZone: http://eurekaserver8091.com:8091/eureka/
3、第二台 eureka server 服务器
server:
port: 8091
eureka:
instance:
hostname: eurekaserver8091.com
client:
register-with-eureka: false
fetch-registry: false
serviceUrl:
#指定其他的eureka server服务器ip地址,多个服务用,分割
defaultZone: http://eurekaserver8090.com:8090/eureka/
4、eureka client 配置:将所有的eureka客户端(消费者和提供者)注册到所有的eureka服务端中
eureka:
instance:
prefer-ip-address: true
instance-id: 192.168.1.100:8081 //名字可以任意
client:
service-url:
defaultZone: http://eurekaserver8090.com:8090/eureka/,http://eurekaserver8091.com:8091/eureka/ //添加部分:改为所有的Eureka集群ip
5、Ribbon负载均衡
RIbbon和Nginx区别: https://www.cnblogs.com/toov5/p/9953971.html
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套 客户端(消费者,对于提供者来说是客户端) 负载均衡的工具。
概述:
负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA。
常见的负载均衡有软件Nginx,LVS,硬件F5等。
相应的在中间件,例如:dubbo和SpringCloud中均给我们提供了负载均衡,SpringCloud的负载均衡算法可以自定义。
集中式LB:即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5(贵),也可以是软件,如nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方;
进程式LB:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
1、给eureka client 消费者添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>${ribbon.version}</version>
</dependency>
2、只需要使用@LoadBalanced注解即可(如果单单指使用轮询算法的话,即使不使用ribbon插件,可以使实现负载均衡)
@Configuration
public class RestTemplateConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
3、使用内置其他的算法
补充RetryRule:就是开始使用轮询算法,如果某一台服务器宕机了,当轮询到调用宕机的服务器的时候,就会方法服务器错误,执行多次错误之后,ribbon就会选择跳过这个服务,不在访问宕机的服务器了。
使用 IRule,来切换其他内置算法
@Configuration
public class RestTemplateConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean
public IRule iRule(){
return new RandomRule();
}
}
4、使用自定义算法
4.1 使用 @RibbonClient
注意 CustomConfiguration.class 不能被它扫描到@ComponentScan,就是说让不能放到和主启动类一个目录下(否则所有的服务共享这个负载均衡算法)
@RibbonClient(name = "PROVIDER", configuration = CustomConfiguration.class) //表示该消费者指对PROVIDER服务使用某一种算法
@EnableDiscoveryClient
@SpringBootApplication
public class SpringcloudConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudConsumerApplication.class, args);
}
}
添加CustomConfiguration
package com.zy.myRule;
//注意包名
@Configuration
public class CustomConfiguration {
@Bean
public IRule iRule(){
return new MyRule(); //可以自定义算法
}
}
需求:比如说每一个微服务访问5次(模仿着RandomRule算法改的)
package com.zy.myRule;
public class MyRule extends AbstractLoadBalancerRule {
private static int a=0;//第x个服务器被访问
private static int b=0; //第a次被访问
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
if(b<5){
b++;
}else {
a++;
b=1;
if (a==upList.size()){
a=0;
}
}
server = upList.get(a);
if (server == null) {
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// TODO Auto-generated method stub
}
}
6、Feign负载均衡
概述:
客户端(消费者,对于提供者来说是客户端) 负载均衡的工具。
由于Ribbon使用微服务名字获得调用接口
而Feign使用的是接口+注解,获得调用服务,体现在了面向接口编程,并且Feign继承了Ribbon
一般Ribbon和Feign可以互相搭配使用
6.1、添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
6.2、定义接口
package com.zy.springcloudconsumer81.server;
@FeignClient("PROVIDER")
public interface FeiginServer {
@GetMapping("/user/{id}")
public User get(@PathVariable(value = "id") Integer id);
}
6.3、使用
package com.zy.springcloudconsumer81.controller;
@RestController
public class UserController {
@Autowired
private FeiginServer feiginServer;
@GetMapping("/user/get/{id}")
public User get(@PathVariable(value = "id") Integer id){
User user = deptClientServer.get(id);
return user;
}
}
注意:我们可以将接口放到一个公共的API模块,利用 @ComponentScan(),来扫描这个包,但是实际测试的时候发现如果添加了这个注解,会造成自己的controller不能被访问(具体原因还不清楚)
7、Hystrix断路器
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至服务雪崩。
功能:
服务降级
服务熔断
服务限流
接近实时的监控
测试服务熔断
1、修改提供者(@HystrixCommand)
package com.zy.springcloudprovider8081.controller;
@RestController
public class UserController {
@Autowired
private UserService userService;
@HystrixCommand(fallbackMethod = "defaultStores")
@GetMapping("/user/{id}")
public User get(@PathVariable(value = "id",required = true) Integer id){
User user = userService.get(id);
int a = 1/0; //伪造错误
return user;
}
public User defaultStores(Integer id){ //参数需要相同
User user = new User();
user.setName("错误了");
return user;
}
}
2、使用@EnableCircuitBreaker
package com.zy.springcloudprovider8081;
//开启注解
@EnableCircuitBreaker
@EnableEurekaClient
@SpringBootApplication
public class SpringcloudProvider8081Application {
public static void main(String[] args) {
SpringApplication.run(SpringcloudProvider8081Application.class, args);
}
}
测试服务降级
服务降级是在消费者端(客户端)完成的
1、在客户端定义一个接口实现FallbackFactory
package com.zy.springcloudconsumer81.server;
@Component
public class UserclientServiceFa1lbackFactory implements FallbackFactory<FeiginServer> {
@Override
public FeiginServer create(Throwable throwable) {
return new FeiginServer(){
@Override
public User get(Integer id) {
User user = new User();
user.setId(id);
user.setName("该服务已经停止.....请稍后访问");
return user;
}
};
}
}
2、修改 @FeignClient 注解
package com.zy.springcloudconsumer81.server;
//如果任何一个访问出了问题,去执行UserclientServiceFa1lbackFactory相对应的方法;
@FeignClient(value = "PROVIDER",fallbackFactory = UserclientServiceFa1lbackFactory.class)
public interface FeiginServer {
@GetMapping("/user/{id}")
public User get(@PathVariable(value = "id") Integer id);
}
3、application(一定得添加),@EnableHystrix并不能替代下面的配置
feign:
hystrix:
enabled: true
4、启动
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class SpringcloudConsumer81Application {
public static void main(String[] args) {
SpringApplication.run(SpringcloudConsumer81Application.class, args);
}
}
测试:
1、如果服务端配置了 fallbackMethod,会去调用服务端的fallback,自己的fallbackFactory方法不会被调用
2、fallbackFactory调用的前提1、服务端没有配置 fallbackMethod,并且服务端发生了错误 2、或者。服务端直接停止服务(客户端连接不上服务端)。
8、HystrixDashboard
1、新建一个module,或者用任何一个微服务做为HystrixDashboard
2、添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
3、所有需要被监控的的微服务都需要监控依赖配置actuator
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
4、在被监控的微服务中配置application,可以直接include: "*",但是注意如果只写include: hystrix.stream,那么info和health就访问不了
management:
endpoints:
web:
exposure:
include:
- info
- health
- hystrix.stream
4.1测试
4.1.1 访问http://192.168.1.100:8081/actuator/hystrix.stream,如果测试出现下面的结果
4.1.2 访问被监控的微服务的任何controller(有可能需要访问的是加了@HystrixCommand注解的方法)
4.1.3 访问http://192.168.1.100:8081/actuator/hystrix.stream,表示成功(如果这一步没有数据,会导致后面使用 HystrixDashboard 的时候出现Unable to connect to Command Metric Stream)
3、配置监控其他微服务的模块,applicaiton
server:
port: 9001
4、@EnableHystrixDashboard
@EnableHystrixDashboard
@SpringBootApplication
public class SpringcloudHystrixdashboardApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudHystrixdashboardApplication.class, args);
}
}
5、使用仪表盘,下面配置网站,就是我们之前测试的网址。
6、详细说明
8、zuul 路由网关
Zuul包含了对请求的路由和过滤两个最主要的功能:
其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础.Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。
注意:Zuul服务最终还是会注册进Eureka
1、创建工程
2、主要额外添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
3、配置application
server:
port: 9100
#注册到eureka服务堡列表中
eureka:
client:
service-url:
defaultZone: http://eurekaserver8090.com:8090/eureka/,http://eurekaserver8091.com:8091/eureka/
instance:
instance-id: zuul-9100
prefer-ip-address: true
spring:
application:
name: zuul-getway
zuul:
#(可以选择不加)加上统一的公共前缀
prefix: /zy/
routes:
#真实服务隐藏,使http://192.168.1.101:9100/provider/xx不生效(provider小写),可以使用"*",忽略掉所有
ignored-services: provider
api-a:
#HTTP调用将/user转发到PROVIDER服务
path: /user/**
#大小写都可以
service-id: PROVIDER
api-b:
path: /xx/**
service-id: XXX
4、启动
@EnableZuulProxy
@SpringBootApplication
public class SpringcloudZuulApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudZuulApplication.class, args);
}
}
访问: http://192.168.1.101:9100/zy/user/user/1
9、Config分布式配置中心
微服务的配置有gitHub统一管理,但是这样配置一旦修改,服务就得重启,我们之后可以使用动态配置。
9.1、创建Config 服务端
9.1.1 在gitHub上克隆一个仓库
9.1.2 克隆仓库,创建application.yml,添加配置基本配置,上传到gitHub上,注意:application.yml保存格式一定是utf-8,这些配置不是用来加载Config 服务端的,而是用来测试Config服务端时候可以读取到gitHub仓库配置
spring:
profiles:
active: dev
---
spring:
profiles: dev
application:
name: springcloud-dev
---
spring:
profiles: test
application:
name: springcloud-dev
9.1.3 创建一个model
9.1.4 主要添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
9.1.5 application.yml
spring:
application:
name: springcloud-config
cloud:
config:
server:
git:
uri: https://github.com/zhengyanzy/springcloud-applicationConfig.git
username: xx
password: xx
server:
port: 9200
9.1.6 启动,添加EnableConfigServer
@EnableConfigServer
@SpringBootApplication
public class SpringcloudConfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudConfigApplication.class, args);
}
}
9.1.7 测试访问:http://192.168.1.101:9200/application-test.yml 、http://192.168.1.101:9200/application-dev.yml,测试是否可以连接gitHub并且访问配置文件
9.2 、创建Config 客户端(可以使用之前我们创建的model)
9.2.1 主要添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
9.2.2 将某一个model配置的application.yml,推送到gitHub上,取名provider-8081.yml,可以将该model中application.yml中的配置全部注释掉,用来测试下面的配置是否成功
9.2.3 新建bootstrap.yml (bootstrap.yml中的配置会覆盖application.yml配置)
spring:
cloud:
config:
label: master
#gitHub中的配置文件,没有后缀名
name: provider-8081
#如果 provider-8081.yml包含了多个配置,可以通过下面的配置
#profile: dev
#Config 服务端
uri: http://localhost:9200
9.2.4 正常启动,启动成功表示成功访问到gitHub中的配置文件了
补充:Config 服务端应该是不需要注册到eureka上的,毕竟eureka的配置也需要通过config server side来从gitHub获取,所以config server side是最先启动的。