概述
为了应对高并发的场景,javaweb项目一般都是部署成集群形式,同时通过Spring Cloud
的 Eureka
技术,实现web模块,负载均衡的访问服务模块。本文介绍了,搭建简单的高可用的Eureka
集成方案。
整体框架
搭建注册中心
①依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.baizhi</groupId>
<artifactId>Eureka</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<!--引入Cloud-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!--引入Eureka依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
②编写入口类
@EnableEurekaServer 开启服务注解
需要在入口类上加上此注解,注册中心才能生效。
package com.baizhi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer //开启服务注解
public class SpringBootEurekaService {
public static void main(String[] args) {
SpringApplication.run(SpringBootEurekaService.class,args);
}
}
③配置核心文件
# 自定义端口号
server.port=1111
# 指定当前注册中心的服务名称
spring.application.name=eureka-registry
## 启用注册中心主动失效,并且每次主动失效检测间隔为5s 默认值60s
eureka.server.eviction-interval-timer-in-ms= 5000
## 设置eureka注册中心的响应更新时间
eureka.server.responseCacheUpdateIntervalMs=3000
eureka.server.responseCacheAutoExpirationInSeconds=60
# 客户端每60s 发送一个心跳 (期待值)
eureka.server.expected-client-renewal-interval-seconds=60
# 接收的心跳达到预期的百分比85%
eureka.server.renewal-percent-threshold=0.85
# 当没有新数据产生是间隔多少时间同步一次数据
eureka.server.wait-time-in-ms-when-sync-empty=10000
## 配置注册中心的主机名
eureka.instance.instance-id = eureka-1
eureka.instance.hostname = pro1
## 服务刷新时间配置,每隔这个时间会主动心跳一次
eureka.instance.lease-renewal-interval-in-seconds= 5
## 服务提供者被认定为丢失心跳超时,失效多久后被删除
eureka.instance.lease-expiration-duration-in-seconds=15
## 配置定时获取|抓取注册中心的数据时间
eureka.client.registry-fetch-interval-seconds= 5
eureka.client.instance-info-replication-interval-seconds= 5
## 配置集群中其他eureka实例,用于本eureka实例的注册方。
eureka.client.region=beijing
## 这里配置其他的中心的标识名 例: zone-2,zone-3
eureka.client.availability-zones.beijing=zone-2
## 这里配置实际的中心地址
## 多个时,重开一行复写
eureka.client.service-url.zone-2=http://pro2:1111/eureka/
高可用时,其他的配置文件,对比
因为是跑在不同的服务器上的,所以,端口号不会冲突
server.port=1111
# 指定当前注册中心的服务名称
spring.application.name=eureka-registry
## 启用注册中心主动失效,并且每次主动失效检测间隔为5s 默认值60s
eureka.server.eviction-interval-timer-in-ms= 5000
## 设置eureka注册中心的响应更新时间
eureka.server.responseCacheUpdateIntervalMs=3000
eureka.server.responseCacheAutoExpirationInSeconds=60
eureka.server.expected-client-renewal-interval-seconds=60
eureka.server.renewal-percent-threshold=0.85
eureka.server.wait-time-in-ms-when-sync-empty=10000
## 配置注册中心的主机名
eureka.instance.instance-id = eureka-2
eureka.instance.hostname = pro2
## 服务刷新时间配置,每隔这个时间会主动心跳一次
eureka.instance.lease-renewal-interval-in-seconds= 5
## 服务提供者被认定为丢失心跳超时,失效多久后被删除
eureka.instance.lease-expiration-duration-in-seconds=15
## 配置定时获取|抓取注册中心的数据时间
eureka.client.registry-fetch-interval-seconds= 5
eureka.client.instance-info-replication-interval-seconds= 5
## 配置集群中其他eureka实例,用于本eureka实例的注册方。
eureka.client.region=beijing
eureka.client.availability-zones.beijing=zone-1
eureka.client.service-url.zone-1=http://pro1:1111/eureka/
☆、以下是单机版的注册中心配置 ↓↓↓
# 服务基本配置
server.port=8750
# 配置Eureka服务注册中心
# 服务实例的主机名
eureka.instance.hostname=localhost
# 注册中心服务相关配置 ,如果收到的心跳低于期望心跳85%,则注册中心会进入`自我保护机制` - 不删除注册中心认定失效的节点
# 客户端每60s 发送一个心跳
eureka.server.expected-client-renewal-interval-seconds=60
# 每1s检查一次,是否有down机的实例
eureka.server.eviction-interval-timer-in-ms=1000
# 接收的心跳达到预期的百分比85%
eureka.server.renewal-percent-threshold=0.85
# 当没有新数据产生是间隔多少时间同步一次数据
eureka.server.wait-time-in-ms-when-sync-empty=10000
# 关闭Eureka自我注册和抓取功能
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
☆、作为参考的配置选项,与本次项目无关 ↑↑↑
④配置日志
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender" >
<encoder>
<pattern>%p %c#%M %d{yyyy-MM-dd HH:mm:ss} %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 控制台输出日志级别 -->
<root level="ERROR">
<appender-ref ref="STDOUT" />
</root>
<logger name="org.springframework" level="INFO" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
</configuration>
⑤启动注册中心
打包后发布到集群的服务器上
启动命令:
后方的参数根据,配置文件中的参数,在不同机器上,启动不同的服务。
例:以本次项目参考--spring.profiles.active=eureka-1
和--spring.profiles.active=eureka-2
java -jar Eureka-1.0-SNAPSHOT.jar --spring.profiles.active=eureka-1
⑥访问
以配置文件中的端口为准,出现下图 蓝色框内表示,成功启动。
配置服务实例
服务实例,即指 Eureka代理后实际访问的控制模块
①引入依赖
<!--引入cloud-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--集成Eureka-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
②配置属性
注:如果是单机模式,直接在最后的默认地址中配置为单个即可
#Spring设置
spring:
#配置以此名进行Euraka注册
application:
name: USER-SERVICE
# Eureka的配置
eureka:
instance:
instance-id: 001 # 服务实例的id
prefer-ip-address: true # 服务实例是否优先在注册中心显示ip
lease-renewal-interval-in-seconds: 10 # 主动发送心跳频率10s
lease-expiration-duration-in-seconds: 30 # 认定实例失效时间30s
client:
register-with-eureka: true # 在Eureka注册中心注册
fetch-registry: false # 不需要在Eureka抓取数据
service-url:
defaultZone: http://pro1:1111/eureka/,http://pro2:1111/eureka/ # Eureka的地址
③发布运行
打包发布到集群服务器
扫描二维码关注公众号,回复:
10352079 查看本文章
启动命令:
java -jar UserModel-1.0-SNAPSHOT.jar
当有多个服务器共同运行时,其他节点在启动时要给定不一样的实例参数id,命令如下:
java -jar UserModel-1.0-SNAPSHOT.jar --eureka.instance.instance-id=002
配置使用端
即配置web模块的使用
①引入依赖
<!--引入cloud-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--集成Eureka-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
②开启访问请求的负载均衡
在入口类,注册restTemple实例到工厂的时候,需要添加 @LoadBalanced 注解
package com.baizhi;
import com.baizhi.loadbalance.UserDefinedClientHttpRequestIntterceptor;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RoundRobinRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class UserWebmodelApplication {
public static void main(String[] args) {
SpringApplication.run(UserWebmodelApplication.class, args);
}
//注册一个Rest组件 SpringMVC中自带的一个Rest客户端工具,可以无缝和SpringBoot集成
@Bean
@LoadBalanced //开启负载均衡
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate();
/* //添加拦截器
restTemplate.getInterceptors().add(new UserDefinedClientHttpRequestIntterceptor());
*/
return restTemplate;
}
@Bean
public IRule rule(){
return new RoundRobinRule();//设置轮训策略
}
}
③配置属性
注:单机模式下,直接在最后的参数中修改为唯一ip即可
# 向Eureka注册中心获取服务
eureka:
client:
fetch-registry: true # 需要获取服务
register-with-eureka: false # 不需要注册
service-url:
defaultZone: http://pro1:1111/eureka/,http://pro2:1111/eureka/ # Eureka地址
④发布
因为是SpringMVC
发布为war
包即可。
<groupId>com.baizhi</groupId>
<artifactId>user_web_model</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
在服务器启动:
java -jar user_web_model-0.0.1-SNAPSHOT.war
Eureka 原理
配置restTemple上的拦截器
拦截器的存在,使得每次发送请求,都要先走拦截器,通过实现,决定去找哪个服务模块。
①注册restTemple时添加拦截器
package com.baizhi;
import com.baizhi.loadbalance.UserDefinedClientHttpRequestIntterceptor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class UserWebmodelApplication {
public static void main(String[] args) {
SpringApplication.run(UserWebmodelApplication.class, args);
}
//注册一个Rest组件 SpringMVC中自带的一个Rest客户端工具,可以无缝和SpringBoot集成
@Bean
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate();
//添加拦截器
restTemplate.getInterceptors().add(new UserDefinedClientHttpRequestIntterceptor());
return restTemplate;
}
}
②实现拦截器
package com.baizhi.loadbalance;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import java.io.IOException;
//创建拦截器
public class UserDefinedClientHttpRequestIntterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
return clientHttpRequestExecution.execute(new UserDefinedHttpRequest(httpRequest),bytes);
}
}
③在拦截器中,执行逻辑
package com.baizhi.loadbalance;
import org.aspectj.weaver.ast.Var;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
public class UserDefinedHttpRequest implements HttpRequest {
//提供一个存储真实请求地址的map
private Map<String, List<HostAndPort>> sourceMap = null;
//属性 目标请求
private HttpRequest httpRequest;
//构造
public UserDefinedHttpRequest(HttpRequest httpRequest){
this.httpRequest=httpRequest;
//向地址属性中,放入真实的请求地址
sourceMap = new HashMap<>();
sourceMap.put("USER-SERVICE", Arrays.asList(
new HostAndPort("pro1",9090),
new HostAndPort("pro2",9090)
));
}
@Override
public String getMethodValue() {
return httpRequest.getMethodValue();
}
//获取真实的请求地址
@Override
public URI getURI() {
//获取请求中的地址
URI originURI = httpRequest.getURI();
//创建最终返回值的引用
URI outURI = null;
try {
//如果请求中有符合已注册的真实地址,则进行选择返回
if (sourceMap.containsKey(originURI.getHost())) {
//负载均衡,随机返回
List<HostAndPort> ips = sourceMap.get(originURI.getHost());
//随机返回ip地址
HostAndPort finalIp = ips.get(new Random().nextInt(ips.size()));
//进行最终的请求释放
outURI = new URI(originURI.getScheme(), originURI.getUserInfo(), finalIp.getHost(), finalIp.getPort(), originURI.getPath(), originURI.getQuery(), originURI.getFragment());
} else {
outURI = originURI;
}
}catch (Exception e){
e.printStackTrace();
}
System.out.println("目前选择的访问地址是"+outURI.getHost());
return outURI;
}
@Override
public HttpHeaders getHeaders() {
return httpRequest.getHeaders();
}
}
④需要一个实体类,配置实际的ip和端口
package com.baizhi.loadbalance;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@NoArgsConstructor
@AllArgsConstructor
@Data
@Accessors(chain = true)
public class HostAndPort {
private String host;
private Integer port;
}
⑤使用
//定义一个访问地址抬头
private String prefix="http://USER-SERVICE:9090/user/restUserManager";