引言:这一篇博客是将上一篇spring-cloud-eureka-server的单机模式改为集群模式,体现eureka的高可用特性。生产环境无论是Eureka注册中心还是Client客户端大多是部署在多台机器上,也就是集群模式,只有采用集群模式才能体现eureka的高可用。这篇博客本人采用集群模式实现了spring-cloud-eureka服务端和客户端的高可用,同时使用RestTemplate和Fengin实现了微服务的调用。下面呈上干货代码:
spring-boot-samples
com.hsf
0.0.1-SNAPSHOT
4.0.0
cloud-eureka-server1
0.0.1-SNAPSHOT
jar
spring-cloud-netflix-eureka-server cluster project demo
com.oracle
ojdbc6
11.2.0.3
org.springframework.cloud
spring-cloud-netflix-eureka-server
2.0.2.RELEASE
com.netflix.eureka
eureka-core
1.9.3
javax.servlet
servlet-api
org.springframework.boot
spring-boot-starter-freemarker
2.0.2.RELEASE
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
2.0.1.RELEASE
${project.artifactId}
org.springframework.boot
spring-boot-maven-plugin
spring-boot-samples
com.hsf
0.0.1-SNAPSHOT
4.0.0
cloud-eureka-client2
com.hsf
0.0.1-SNAPSHOT
spring-cloud-eureka client2 demo
jar
1.8
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
2.0.2.RELEASE
com.sun.jersey
jersey-client
com.sun.jersey
jersey-core
com.sun.jersey.contribs
jersey-apache-client4
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-web
2.0.2.RELEASE
com.fasterxml.jackson.core
jackson-databind
com.google.code.gson
gson
2.8.4
${project.artifactId}
org.springframework.boot
spring-boot-maven-plugin
cloud-eureka-server1与cloud-eureka-server2互相注册对方的地址
1) cloud-eureka-server1项目的application-yaml文件
eureka:
server:
enable-self-preservation: false #关闭自我保护机制
eviction-interval-timer-in-ms: 5000 #默认 60*1000 单位:毫秒
instance:
hostname: peer1
client:
fetchRegistry: false
serviceUrl:
defaultZone: http://peer2:8762/eureka/ #注册到peer2节点的地址上
registerWithEureka: false #禁用自己注册
spring:
profiles:
active: dev
application:
name: eurekaServer1
main:
allow-bean-definition-overriding: true
#必须配置freemark模板引擎参数,否则启动报错
freemarker:
template-loader-path: classpath:/templates/
prefer-file-system-access: false
2) cloud-eureka-server2项目的application.yaml文件
spring:
profiles:
active: dev
application:
name: eurekaServer
main:
allow-bean-definition-overriding: true
freemarker:
template-loader-path: classpath:/templates/
prefer-file-system-access: false
eureka:
server:
enable-self-preservation: false #关闭自我保护机制
eviction-interval-timer-in-ms: 5000 #默认 60*1000 单位:毫秒
instance:
hostname: peer2
client:
fetchRegistry: false
serviceUrl:
defaultZone: http://peer1:8761/eureka/ #注册到pper1服务节点的地址上
registerWithEureka: false #禁用自己注册
注意:peer1和peer2域名都需要在 C:\Windows\System32\drivers\etc 目录下的hosts文件中配置对应的域名解析
# localhost name resolution is handled within DNS itself.
127.0.0.1 localhost
::1 localhost
127.0.0.1 peer1
127.0.0.1 peer2
3) cloud-eureka-server2项目的application-dev.yaml文件
server:
port: 8762
address: 127.0.0.1
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:oracle:thin:@localhost:1521:ORCL
driverClassName: oracle.jdbc.driver.OracleDriver
username: SYSTEM
password: password
1) cloud-eureka-client1项目的application.yaml文件
server:
port: 9090
connection-timeout: 30000s
servlet:
context-path: /eurekaClient
spring:
profiles:
active: dev
application:
name: eurekaClient
datasource:
type: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useSSL=false
username: root
password: password #mysql数据库root用户连接密码
jpa:
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL57Dialect
logging:
level:
org:
hibernate:
SQL: debug
type:
descriptor:
sql: trace
cloud-eureka-client2项目的application.yaml文件只需要修改上述的server.port=9091即可,其他与cloud-eureka-client1项目的
application.yaml内容保持相同。
server:
port: 9091
1)cloud-eureka-server1 与cloud-eureka-server2 spring-boot项目的启动类代码:
package com.hsf.cloudeurekaserver1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer1Application {
public static void main(String[] args){
SpringApplication.run(EurekaServer1Application.class,args);
}
}
package com.hsf.cloudeurekaserver2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaServer2Application {
public static void main(String[] args){
SpringApplication.run(EurekaServer2Application.class);
}
}
注意:spring-cloud-eureka服务端项目均要加上@EnableEurekaServer注解
2) cloud-eureka-client1与 cloud-eureka-client2 spring-boot项目的启动类代码:
package com.hsf.cloudeurekaclient1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class CloudEurekaClient1Application {
public static void main(String[] args) {
SpringApplication.run(CloudEurekaClient1Application.class, args);
}
}
package com.hsf.cloudeurekaclient2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class EurekaClient2Application {
public static void main(String[] args){
SpringApplication.run(EurekaClient2Application.class,args);
}
}
在IDEA中依次启动cloud-eureka-server1, cloud-eureka-server1, cloud-eureka-client1, cloud-eureka-client2四个项目,项目启动成功后,在浏览器地址栏中输入http://peer1:8761后回车,出现如下画面表示高可用服务注册中心和服务客户端项目启动成功
图 1 Eureka注册中心服务注册与服务发现效果图
上图中可以看到peer1的备用注册中心地址:http://peer2:8762/eureka/ 以及一个服务实例EUREKACLIENT ,但是有两个不同的实例ID,分别是eurekaClient1和eurekaClient2;若出现peer1节点宕机,所有的服务都将注册到pee2节点上来。开发者可以手动关停cloud-eureka-server1应用,然后再浏览器地址栏中输入http://peer2:8762回车查看效果可以看到EUREKACLIENT服务注册到了peer2节点上来了,之前是没有的。注意:开发环境上用的是伪集群,用的是相同IP地址+不同端口号,而正式环境一定是不同机器IP地址+相同端口号的。
cloud-eureka-client1项目的controller包中新建DemoController.java控制器类,代码如下
package com.hsf.cloudeurekaclient1.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
private static final Logger logger = LoggerFactory.getLogger(DemoController.class);
@Value("${eureka.instance.hostname}")
private String hostname;
@Value("${server.port}")
private String port;
@RequestMapping(path="/remoteIndex",method= RequestMethod.GET)
public String Index(){
return hostname+":"+port;
}
}
cloud-eureka-client2项目的DemoController同上,可直接从cloud-eureka-client1项目中复制过来
spring-boot-samples
com.hsf
0.0.1-SNAPSHOT
4.0.0
cloud-web
0.0.1-SNAPSHOT
jar
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
2.0.2.RELEASE
com.sun.jersey
jersey-client
com.sun.jersey
jersey-core
com.sun.jersey.contribs
jersey-apache-client4
org.springframework.cloud
spring-cloud-starter-openfeign
2.0.2.RELEASE
com.oracle
ojdbc6
11.2.0.3
commons-codec
commons-codec
1.10
${project.artifactId}
org.apache.maven.plugins
maven-compiler-plugin
3.1
${java.version}
UTF-8
org.springframework.boot
spring-boot-maven-plugin
com.hsf.samplesimpl.MySpringApplication
org.apache.maven.plugins
maven-surefire-plugin
2.22.1
1) application-dev.yaml
server:
address: 127.0.0.1
port: 8080
rpcServices:
serviceName: eurekaClient
contextPath: /eurekaClient
indexUrl: /remoteIndex
userInfoUrl: /userInfo
spring:
datasource: #配置数据源,必配,否则项目启动报错
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:oracle:thin:@localhost:1521:ORCL
driverClassName: oracle.jdbc.driver.OracleDriver
username: SYSTEM
password: password #oracle数据库SYSTEM用户登陆密码
eureka:
client:
serviceUrl:
defaultZone: http://peer1:8761/eureka/,http://peer2:8762/eureka/ #服务中心注册地址
instance:
hostname: localhost
instance-id: cloud-web #实例注册到eureka服务器上的唯一实例ID
prefer-ip-address: true #显示IP地址
lease-renewal-interval-in-seconds: 30 #过多长时间发送心跳给eureka服务器,表明它仍然活着,默认为30s
注意:服务消费方方也要注册到注册中心去,不然调用其他微服务时会报No Instances Available for eurekaClient 异常,导致调用微服务失败,http接口报500错误
2) application.yaml
server:
servlet:
context-path: /myApp
session:
cookie:
name: customCookie
max: 30m
connection-timeout: 30000ms
spring:
profiles:
active: dev
application:
name: myApplication
jpa:
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.Oracle10gDialect
logging:
level:
org:
hibernate:
SQL: debug
type:
descriptor:
sql: trace
分别提供一个证明负载均衡和从数据库查询数据的接口,mysql表用的之前建的userinfo表,持久层框架用的spring-data-jpa框架,代码如下
package com.hsf.cloudeurekaclient1.controller;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.hsf.cloudeurekaclient2.model.UserInfo;
import com.hsf.cloudeurekaclient2.service.UserInfoService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
private static final Logger logger = LoggerFactory.getLogger(DemoController.class);
@Value("${eureka.instance.hostname}")
private String hostname;
@Value("${server.port}")
private String port;
@Autowired
private UserInfoService userInfoService;
private static Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
@RequestMapping(path="/remoteIndex",method= RequestMethod.GET)
public String Index(){
return hostname+":"+port;
}
@RequestMapping(path="/userInfo",method = RequestMethod.GET)
public ResponseEntity queryUserInfo(@RequestParam("userAccount") String userAccount){
logger.info("/userInfo request begin:----------");
UserInfo userInfo = userInfoService.findByUserAccount(userAccount);
String jsonInfo = gson.toJson(userInfo);
logger.info("userInfo:"+jsonInfo);
ResponseEntity entity = new ResponseEntity(userInfo, HttpStatus.OK);
return entity;
}
}
1) 接口类
package com.hsf.cloudeurekaclient1.service;
import com.hsf.cloudeurekaclient1.model.UserInfo;
public interface UserInfoService {
UserInfo findByUserAccount(String userAccount);
}
2) 实现类
package com.hsf.cloudeurekaclient1.service.impl;
import com.hsf.cloudeurekaclient1.dao.UserInfoRepository;
import com.hsf.cloudeurekaclient1.model.UserInfo;
import com.hsf.cloudeurekaclient1.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserInfoServiceImpl implements UserInfoService {
@Autowired
private UserInfoRepository userDao;
@Override
public UserInfo findByUserAccount(String userAccount) {
return userDao.findByUserAccount(userAccount);
}
}
package com.hsf.cloudeurekaclient1.dao;
import com.hsf.cloudeurekaclient1.model.UserInfo;
import org.springframework.data.repository.CrudRepository;
public interface UserInfoRepository extends CrudRepository {
UserInfo findByUserAccount(String userAccount);
}
cloud-eureka-client2项目的Restful接口开发代码同上,可知直接复制过来
代码结构如下图所示
package com.hsf.cloudeurekaclient1.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;
@Entity
@Table(name="userinfo")
public class UserInfo implements Serializable {
@Id
private Integer Id;
@Column(name="user_account")
private String userAccount;
private String password;
@Column(name="nick_name")
private String nickName;
@Column(name="dept_no")
private Integer deptNo;
@Column(name="email_address")
private String emailAddress;
@Column(name="birth_day")
private Date birthDay;
//省略set,get方法
}
package com.hsf.samplesimpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class MySpringApplication {
private static final Logger logger = LoggerFactory.getLogger(MySpringApplication.class);
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MySpringApplication.class);
application.run(args);
}
@Bean
@LoadBalanced //客户端添加支持负载均衡注解
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
5.1 采用RestTemplate调用微服务
1) Controller层代码
IUserService接口
package com.hsf.samplesimpl.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
@RestController
public class UserController extends BaseController {
@Autowired
private RestTemplate restTemplate; //注入RestTemplate bean实例
@Value("${rpcServices.serviceName}") //取自配置文件的远程微服务名
private String serviceName;
@Value("${rpcServices.contextPath}") //微服务上下文路径
private String contextPath;
@Value("${rpcServices.indexUrl}") // 验证负载均衡URL
private String indexUrl;
//验证客户端调用微服务实现了负载均衡接口
@RequestMapping(path="/localIndex",method = RequestMethod.GET)
public String index(){
logger.info("/localIndex request start");
StringBuilder builder = new StringBuilder("http://");
builder.append(serviceName).append(contextPath).append(indexUrl);
String uri = builder.toString();
logger.info("uri:"+uri);
ResponseEntity entity = restTemplate.getForEntity(uri,String.class);
return entity.getBody();
}
// 验证从微服务提供方数据库获取数据接口
@RequestMapping(path="/remoteUserInfo",method = RequestMethod.GET)
public ResponseEntity
queryMysqlUserInfo(@RequestParam("userAccount") String userAccount) {
logger.info("/remoteUserInfo request start");
logger.info("userAccount:"+userAccount);
StringBuilder builder = new StringBuilder("http://");
builder.append(serviceName).append(contextPath).append(userInfoUrl).append("?
userAccount={userAccount}");
String uri = builder.toString();
logger.info("uri:"+uri);
Map paramMap = new HashMap<>();
paramMap.put("userAccount",userAccount);
ResponseEntity entity =
restTemplate.getForEntity(uri,MySqlUserInfo.class,paramMap);
return entity;
}
}
MysqlUserInfo实体类
package com.hsf.samplesimpl.model;
import java.io.Serializable;
import java.util.Date;
public class MySqlUserInfo implements Serializable {
private Integer Id;
private String userAccount;
private String password;
private String nickName;
private Integer deptNo;
private String emailAddress;
private Date birthDay;
//此处省略set和get方法
}
2) 在IDEA中启动两个Eureka服务端项目和Eureka客户端项目后,再启动服务消费方cloud-web项目,均为Debug模式启动,Eureka服务端和客户端以及服务消费方项目启动成功后如下图所示:
3) 在来看一下服务注册中心的服务实例信息
我们发现cloud-web项目的实例也注册到了服务注册中心上来了
4) postman测试RestTemplate调用eurekaClient服务的/remoteIndex接口验证客户端负载均衡
第一次调用效果图
第二次调用效果图
连续调用循环出现9090和9091端口,验证了客户端负载均衡
5) postman测试RestTemplate调用eurekaClient服务的获取mysql数据库用户信息接口
1) Fengin服务接口层代码
package com.hsf.samplesimpl.service;
import com.hsf.samplesimpl.model.MySqlUserInfo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name="eurekaClient") //name对应服务提供方的服务名,也就是yaml配置文件中spring.application.name对应的value值
public interface FeignService {
//RequestMapping 注解中的value值对用url映射,method对应请求方法,根据官方文档提示这里不能用GetMapping注解
@RequestMapping(value = "/eurekaClient/remoteIndex",method = RequestMethod.GET)
String getUrl();
@RequestMapping(value="/eurekaClient/userInfo",method = RequestMethod.GET)
MySqlUserInfo getUserInfo(@RequestParam("userAccount") String userAccount);
}
2) Controller层代码
UserController类中注入FenginService实例并新增两个Fengin调用的接口
@Resource
private FeignService feignService;
@GetMapping(value="/remoteUrl")
public String getRemoteUri(){
return feignService.getUrl();
}
@GetMapping(value="/feignUserInfo")
public MySqlUserInfo getRemoteUserInfo(String userAccount){
return feignService.getUserInfo(userAccount);
}
3) postman测试Fengin调用eurekaClient微服务的验证负载均衡接口
4) postman测试Fengin调用eurekaClient微服务获取Mysql数据库用户信息接口