紧跟上一篇博客,这里采用的也是上一篇博客的代码作为分析:Spring Cloud服务管理框架Eureka简单示例(三)。我们在搭建起了简单的单机模式Eureka项目之后,如果Eureka服务器和客户端不能满足高并发访问,项目需要集群部署,也可以利用Eureka做到这一点。我们这里创建两个Eureka服务器端,两个Eureka客户端(作为服务提供者),一个Eureka客户端(作为服务调用者),如图:
通过运行多个实例并让它们彼此注册,Eureka可以变得更有弹性和可用性。实际上,这是默认行为,所以需要做的就是将一个有效的serviceUrl添加到对等点。我们可以将多个对等点添加到一个系统中,只要它们之间至少有一条边连接,它们就会同步这些注册。如果对等体在物理上是分开的(在数据中心或多个数据中心之间),那么系统就可以在原则上生存下来,以避免“大脑分裂”。由于资源限制,这里让服务运行在同一个主机上,通过修改hosts文件配置,模仿两个不同的主机。
搜素Windows的“记事本”应用,以管理员身份运行,文件-->打开,找到C:\Windows\System32\drivers\etc目录,打开里面的“hosts”文件,在文件末尾为自己的主机添加两个虚拟的域名:
127.0.0.1 peer1 peer2
创建两个Eureka服务器端
找到我们上一篇博客编写的eureka-server项目,修改application.yml文件,配置如下:
---
server:
port: 8761
spring:
profiles: peerA
application:
name: eureka-server-A
eureka:
instance:
hostname: peer1
client:
serviceUrl:
defaultZone: http://peer2:8762/eureka/
---
server:
port: 8762
spring:
profiles: peerB
application:
name: eureka-server-B
eureka:
instance:
hostname: peer2
client:
serviceUrl:
defaultZone: http://peer1:8761/eureka/
由于需要启动的是两个项目,所以这里配置一下两个项目的名称。再配置启动环境,方便我们作为项目的启动条件,打开ServerApp的class类,修改main方法,让系统读取我们输入在控制台的第一行,得到启动环境再启动:
package com.init.springCloud;
import java.util.Scanner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class ServerApp {
public static void main(String[] args) {
@SuppressWarnings("resource")
Scanner scan = new Scanner(System.in);
String profiles = scan.nextLine();
new SpringApplicationBuilder(ServerApp.class).profiles(profiles).run(args);
}
}
之后运行ServerApp的main()方法,启动项目,在控制台输入“peerA”,回车,项目启动过程中会报错,但是不影响我们启动项目,报错的原因是eureka-server-A会将自己的服务注册到eureka-server-B,但是eureka-server-B还未启动,我们可以在浏览器访问http://localhost:8761,能够进入Eureka的后台管理界面,说明项目已经启动成功。再运行一次ServerApp的main()方法,在控制台输入“peerB”,回车,eureka-server-B项目是不会报错的,它能成功将自己注册到eureka-server-A。最后我们两个管理后台都能看见注册的服务信息:
创建两个Eureka客户端(提供者)
找到我们上一篇博客编写的eureka-provider,修改application.yml文件,为服务提供者再提供一个服务器端的注册地址,让我们的服务提供者可以将自己的服务注册到两个服务器端,配置如下:
spring:
application:
name: eureka-provider
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka
同样,为了模拟启用两个服务提供者,我们参照上面提供的方法,修改ProviderApp类的main()方法,让项目在启动的时候,读取输入的第一行,获取我们输入的端口号,根据输入端口的不同,启动两个不同的项目:
package com.init.springCloud;
import java.util.Scanner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class ProviderApp {
public static void main(String[] args) {
@SuppressWarnings("resource")
Scanner scan = new Scanner(System.in);
String port = scan.nextLine();
new SpringApplicationBuilder(ProviderApp.class).properties("server.port=" + port).run(args);
}
}
为了能够让我们待会儿要创建的Eureka客户端(服务调用者)可以区分到底是从哪一个Eureka客户端(服务提供者)获取到返回值的,我们改造一下eureka-provider的Person类,为Person增加一个message属性,用以展示对应请求的路径(包含端口号),用端口号来鉴别调用的是哪一个服务:
package com.init.springCloud;
import lombok.Data;
@Data
public class Person {
private Integer id; //主键ID
private String name; //姓名
private String message; //信息
}
之后,为ProviderController类的searchPerson方法添加一个参数,传入请求信息,并把请求信息的URL设置到Person类的message属性里:
package com.init.springCloud;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProviderController {
@RequestMapping(value = "/search/{id}", method = RequestMethod.GET,produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Person searchPerson(@PathVariable Integer id,HttpServletRequest request){
Person person = new Person();
person.setId(id);
person.setName("Spirit");
person.setMessage(request.getRequestURL().toString());
return person;
}
}
运行ProviderApp类的main()方法,在控制台输入8080,启动第一个项目;然后再次运行ProviderApp类的main()方法,在控制台输入8081,启动第二个项目。项目成功启动之后,我们在浏览器分别访问http://localhost:8080/search/1和http://localhost:8081/search/1,能够得到对应的响应结果,如下:
然后访问我们的Eureka服务器端,http://localhost:8761或者http://localhost:8762,也能够看到服务被注册到了服务器端。
创建一个Eureka客户端(服务调用者)
找到我们上一篇博客编写的eureka-consumer,修改application.yml文件,为服务调用者再提供一个服务器端的注册地址,让我们的服务调用者可以将自己的服务注册到两个服务器端,再将端口修改了,避免冲突导致的不能启动项目,配置如下:
server:
port: 9090
spring:
application:
name: eureka-consumer
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka
服务调用者需要的配置就完成了,然后我们运行ConsumerApp类的main()方法,成功启动之后,访问http://localhost:9090/router,反复刷新几次,可以看到,我们的服务调用者是依次去调用了8080和8081的服务,恰好每个服务轮询一次。
补充
1.Eureka客户端(服务调用者)的循环方式,用的是Ribbon的load balancers中Common rules的RoundRobinRule,这条规则只是简单地选择循环服务器,它通常被用作更高级规则的默认规则或回退。相关知识会在后续的博客中更新,读者也可以自己参照NetFlix的Ribbon相关资料进行学习。
2.我开篇给出的架构图中有提到,Eureka服务器端会将自己的服务注册列表复制给自己的集群Eureka服务器,那么意味着Eureka客户端(服务调用者)只需要将自己的服务注册到Eureka服务器A,Eureka服务器A就会把自己的服务列表同步到Eureka服务器B,Eureka服务器B也会得到Eureka客户端(服务调用者)的注册信息。问题来了,是否就等同于我上面的示例,只需要为Eureka客户端(服务调用者)配置一个服务注册地址就行了呢?其实不然,如果我配置的这个单一服务注册地址对应的机器没有宕掉,那还好说,万一我只注册到服务器A,服务器A又宕机了,在服务器B就不可能存在我的服务列表。所以,配置注册地址,有多少个服务器,就配置多少个注册地址。
最后,大家有什么不懂的或者其他需要交流的内容,也可以进入我的QQ讨论群一起讨论:654331206
Spring Cloud系列:
Spring Cloud服务管理框架Eureka简单示例(三)