Spring|Spring Cloud学习day98(Eureka的原理和Ribbon负载均衡)
一、Eureka注册中心架构原理:
1.Eureka架构图:
文章图片
示例
操作 | 名称 | 作用 |
---|---|---|
Register | 服务注册 | 把自己的 IP 和端口注册给 Eureka |
Renew | 服务续约 | 发送心跳包,每 30 秒发送一次。告诉 Eureka 自己还活着 |
Cancel | 服务下线 | 当 provider 关闭时会向 Eureka 发送消息,把自己从服务列表中删除。防止 consumer 调用到不存在的服务。 |
Get Registry | 获取服务注册列表 | 获取其他服务列表 |
Replicate | 集群中数据同步 | eureka 集群中的数据复制与同步 |
Make Remote Call | 远程调用 | 完成服务的远程调用 |
- 什么是CAP原则?
CAP 原则又称 CAP 定理,指的是在一个分布式系统中,Consistency (一致性)、Availability (可用性)、Partition tolerance (分区容错性),三者不可兼得。
- 分布式系统CAP定理:
分布式系统 P CAP 定理 | |
---|---|
C:数据一致性( Consistency) | 也叫做数据原子性系统在执行某项操作后仍然处于一致的状态。在分布式系统中,更新操作执行成功后所有的用户都应该读到最新的值,这样的系统被认为是具有强一致性的。等同于所有节点访问同一份最新的数据副本。 |
A:服务可用性( Availablity) | 每一个操作总是能够在一定的时间内返回结果,这里需要注意的是"一定时间内"和"返回结果"。一定时间内指的是,在可以容忍的范围内返回结果,结果可以是成功或者是失败。 |
P :分区容错性( Partition-torlerance) | 在网络分区的情况下,被分隔的节点仍能正常对外提供服务(分布式集群,数据被分布存储在不同的服务器上,无论什么情况,服务器都能正常被访问) |
- CAP定律:
任何分布式系统只可同时满足二点,没法三者兼顾。
定律 | ||
---|---|---|
CA,放弃P | 如果想避免分区容错性问题的发生,一种做法是将所有的数据(与事务相关的)都放在一台机器上。虽然无法 100%保证系统不会出错,单不会碰到由分区带来的负面效果。当然这个选择会严重的影响系统的扩展性。 | |
CP:放弃 A | 相对于放弃"分区容错性"来说,其反面就是放弃可用性。一旦遇到分区容错故障,那么受到影响的服务需要等待一定时间,因此在等待时间内系统无法对外提供服务。 | |
AP:放弃 C | 这里所说的放弃一致性,并不是完全放弃数据一致性,而是放弃数据的强一致性,而保留数据的最终一致性。以网络购物为例,对只剩下一件库存的商品,如果同时接受了两个订单,那么较晚的订单将被告知商品告罄。 |
- Zookeeper与Eureka的区别:
对比项 | Zookeeper | Eureka |
---|---|---|
CAP | CP | AP |
Dubbo 集成 | 支持 | ~ |
Spring Cloud 集成 | 支持 | 支持 |
kv服务 | 支持 | ~ |
使用接口 | 提供客户端 | http多语言 |
watch支持 | 支持 | 支持 |
集群监控 | ~ | metrics |
- 什么是自我保护?
一般情况下,微服务在 Eureka 上注册后,会每 30 秒发送心跳包,Eureka 通过心跳来判断服务时候健康,同时会定期删除超过 90 秒没有发送心跳服务。
- 导致Eureka接收不到心跳的原因:
(1)微服务自身的原因。
(2)微服务与 Eureka 之间的网络故障:
通常(微服务的自身的故障关闭)只会导致个别服务出现故障,一般不会出现大面积故障,而(网络故障)通常会导致 Eureka Server 在短时间内无法收到大批心跳。考虑到这个区别,Eureka 设置了一个阀值,当判断挂掉的服务的数量超过阀值时,Eureka Server 认为很大程度上出现了网络故障,将不再删除心跳过期的服务。
- 阀值是多少?
15 分钟之内是否高于 85%;
Eureka Server 在运行期间,会统计心跳失败的比例在 15 分钟内是否高于 85%
这种算法叫做 Eureka Server 的自我保护模式。
- 为什么要启动自我保护?
(1)因为同时保留"好数据"与"坏数据"总比丢掉任何数据要更好,当网络故障恢复后,这个 Eureka 节点会退出"自我保护模式"。
(2)Eureka 还有客户端缓存功能(也就是微服务的缓存功能)。即便 Eureka 集群中所有节点都宕机失效,微服务的 Provider 和 Consumer都能正常通信。
(3)微服务的负载均衡策略会自动剔除死亡的微服务节点。
- Eureka自我保护的开关:
#自我保护的开关:true 为开启自我保护,false 为关闭自我保护
eureka.server.enableSelfPreservation=false
#清理间隔(单位:毫秒,默认是 60*1000)
eureka.server.eviction.interval-timer-in-ms=60000
文章图片
示例 4.如何优雅停服?
停止服务的另一种方式,不需要在Eureka Server 中配置关闭自我保护。
- 在服务中添加Actuator.jar包:
修改POM文件中Eureka的依赖。
org.springframework.cloud
spring-cloud-starter-eureka-server
- 修改配置文件:
#启用 shutdown
endpoints.shutdown.enabled=true
#禁用密码验证
endpoints.shutdown.sensitive=false
- 发送一个关闭服务的URL请求:
package com.bjsxt.springboothelloworld;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
public class HttpClientUtil {public static String doGet(String url, Map param) {// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
String resultString = "";
CloseableHttpResponse response = null;
try {
// 创建uri
URIBuilder builder = new URIBuilder(url);
if (param != null) {
for (String key : param.keySet()) {
builder.addParameter(key, param.get(key));
}
}
URI uri = builder.build();
// 创建http GET请求
HttpGet httpGet = new HttpGet(uri);
// 执行请求
response = httpclient.execute(httpGet);
// 判断返回状态是否为200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}public static String doGet(String url) {
return doGet(url, null);
}public static String doPost(String url, Map param) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建参数列表
if (param != null) {
List paramList = new ArrayList<>();
for (String key : param.keySet()) {
paramList.add(new BasicNameValuePair(key, param.get(key)));
}
// 模拟表单
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList,"utf-8");
httpPost.setEntity(entity);
}
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}return resultString;
}public static String doPost(String url) {
return doPost(url, null);
}public static String doPostJson(String url, String json) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建请求内容
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}return resultString;
}public static void main(String[] args) {
String url ="http://127.0.0.1:9090/shutdown";
//该url必须要使用dopost方式来发送
HttpClientUtil.doPost(url);
}
}
5.加强Eureka注册中心的安全验证:
- 在Server的POM文件中添加Security依赖:
org.springframework.boot
spring-boot-starter-security
- 修改Eureka Server的配置文件:
#开启 http basic 的安全认证
security.basic.enabled=true
security.user.name=user
security.user.password=123456
文章图片
低版本和高版本的区别
- 修改访问集群节点的url:
eureka.client.serviceUrl.defaultZone=http://user:123456@eureka2:8761/eureka/
- 修改微服务的配置文件添加注册中心的用户和密码:
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://user:123456@eureka1:8761/eureka/,http://user:123456@eureka2:8761/eureka/
#启用 shutdown
endpoints.shutdown.enabled=true
#禁用密码验证
endpoints.shutdown.sensitive=false
文章图片
示例 二、Ribbon负载均衡
1.Ribbon在微服务中的作用:
- 什么是Ribbon?
(1)Ribbon 是一个基于 Http 和 TCP 的客服端负载均衡工具,它是基于 Netflix Ribbon 实 现的。
(2)它不像 spring cloud 服务注册中心、配置中心、API 网关那样独立部署,但是它几乎 存在于每个 spring cloud 微服务中。包括 feign 提供的声明式服务调用也是基于该 Ribbon 实现的。
(3)ribbon 默认提供很多种负载均衡算法,例如 轮询、随机 等等。甚至包含自定义的负 载均衡算法。
- Ribbon解决了什么问题:
解决并提供了微服务的负载均衡的问题。2.负载均衡的解决方案分类:
- 集中式负载均衡:
即在 consumer 和 provider 之间使用独立的负载均衡设施(可 以是硬件,如 F5, 也可以是软件,如 nginx), 由该设施负责把 访问请求 通过某种策略转发 至 provider。
- 进程内负载均衡:
进程内负载均衡,将负载均衡逻辑集成到 consumer,consumer 从服务注册中 心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的 provider。
- Ribbon的负载均衡:
Ribbon 就属于后者,它只是一个类库,集成于 consumer 进程,consumer 通过它来获取 到 provider 的地址。
- 两种负载均衡方式结构图:
文章图片
集中式
文章图片
进程内
策略名称 | 对应的类名 | 实现原理 |
---|---|---|
轮询策略 | RoundRobinRule | 轮询策略表示每次都顺序取下一个 provider,比如一共有 5 个provider,第 1 次取第 1 个,第 2次取第 2 个,第 3 次取第 3 个,以此类推 |
权重轮询策略 | WeightedResponseTimeRule | 特点:根据每个provider 的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性越低。 ——————原理:一开始为轮询策略,并开启一个计时器,每 30 秒收集一次每个 provider 的平均响应时间,、当信息足够时,给每个 provider附上一个权重,并按权重随机选择 |
随机策略 | RandomRule | 从 provider 列表中随机选择一个provider |
最少并发数策略 | BestAvailableRule | 选择正在请求中的并发数最小的 provider,除非这个provider 在熔断中。 |
在“选定的负载均 衡策略”基础上进行重 试机制 | RetryRule | “选定的负载均衡策略”这个策略是轮询策略RoundRobinRule 。该重试策略先设定一个阈值时间段,如果在这个阈值时间段内当选择provider 不成功,则一直尝试采用“选定的负载均衡策略:轮询策略”最后选择一个可用的provider |
可用性敏感策略 | AvailabilityFilteringRule | 过滤性能差的 provider,有 2种。第一种:过滤掉在 eureka 中处于一直连接失败 provider。 第二种:过滤掉高并发的 provider |
区域敏感性策略 | ZoneAvoidanceRule | 以一个区域为单位考察可用性,对于不可用的区域整个丢弃,从剩下区域中选可用的provider 。如果这个 ip 区域内有一个或多个实例不可达或响应变慢,都会降低该 ip 区域内其他 ip 被选中的权重 |
- 在Consumer中添加打印语句:
@Service
public class UserService {@Autowired
private LoadBalancerClient loadBalancerClient;
// ribbon 负 载均衡器
public List getUsers() {
// 选择调用的服务的名称
// ServiceInstance 封装了服务的基本信息,如 IP,端口
ServiceInstance si = this.loadBalancerClient.choose("eureka-provider");
// 拼接访问服务的 URL
StringBuffer sb = new StringBuffer();
// http://localhost:9090/user
}sb.append("http://").append(si.getHost()).append(":").appen d(si.getPort()).append("/user");
//添加输出URL语句
System.out.println(sb.toString());
//springMVC RestTemplate
RestTemplate rt = new RestTemplate();
ParameterizedTypeReference> type = new ParameterizedTypeReference>() {};
//ResponseEntity:封装了返回值信息
ResponseEntity> response = rt.exchange(sb.toString(),HttpMethod.GET, null, type);
List list =response.getBody();
return list;
}}
- 修改Consumer的配值文件:
spring.application.name=eureka-consumer
server.port=9091
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://user:123456@eur eka1:8761/eureka/,http://user:123456@eureka2:8761/eureka/
- 部署Provider的集群:
将Provider打包上传到两台Linux服务器上。
- 添加server.sh启动脚本:
修改:JAR_NAME="项目名称";
SPRING_PROFILES_ACTIV="";
设置权限:chmod -R 755 server.sh
cd `dirname $0`
CUR_SHELL_DIR=`pwd`
CUR_SHELL_NAME=`basename ${BASH_SOURCE}`
JAR_NAME="项目名称"
JAR_PATH=$CUR_SHELL_DIR/$JAR_NAME
#JAVA_MEM_OPTS=" -server -Xms1024m -Xmx1024m -XX:PermSize=128m"
JAVA_MEM_OPTS=""
#SPRING_PROFILES_ACTIV="-Dspring.profiles.active=配置文件变量名称"
SPRING_PROFILES_ACTIV=""
LOG_DIR=$CUR_SHELL_DIR/logs
LOG_PATH=$LOG_DIR/${JAR_NAME%..log
echo_help()
{
echo -e "syntax: sh $CUR_SHELL_NAME start|stop"
}
if [ -z $1 ];
then
echo_help
exit 1
fi
if [ ! -d "$LOG_DIR" ];
then
mkdir "$LOG_DIR"
fi
if [ ! -f "$LOG_PATH" ];
then
touch "$LOG_DIR"
fi
if [ "$1" == "start" ];
then
# check server
PIDS=`ps --no-heading -C java -f --width 1000 | grep $JAR_NAME | awk '{print $2}'`
if [ -n "$PIDS" ];
then
echo -e "ERROR: The $JAR_NAME already started and the PID is ${PIDS}."
exit 1
fi
echo "Starting the $JAR_NAME..."
# start
nohup java $JAVA_MEM_OPTS -jar $SPRING_PROFILES_ACTIV $JAR_PATH >> $LOG_PATH 2>&1 &
COUNT=0
while [ $COUNT -lt 1 ];
do
sleep 1
COUNT=`ps--no-heading -C java -f --width 1000 | grep "$JAR_NAME" | awk '{print $2}' | wc -l`
if [ $COUNT -gt 0 ];
then
break
fi
done
PIDS=`ps--no-heading -C java -f --width 1000 | grep "$JAR_NAME" | awk '{print $2}'`
echo "${JAR_NAME} Started and the PID is ${PIDS}."
echo "You can check the log file in ${LOG_PATH} for details."
elif [ "$1" == "stop" ];
then
PIDS=`ps --no-heading -C java -f --width 1000 | grep $JAR_NAME | awk '{print $2}'`
if [ -z "$PIDS" ];
then
echo "ERROR:The $JAR_NAME does not started!"
exit 1
fi
echo -e "Stopping the $JAR_NAME..."
for PID in $PIDS;
do
kill $PID > /dev/null 2>&1
done
COUNT=0
while [ $COUNT -lt 1 ];
do
sleep 1
COUNT=1
for PID in $PIDS ;
do
PID_EXIST=`ps --no-heading -p $PID`
if [ -n "$PID_EXIST" ];
then
COUNT=0
break
fi
done
done
echo -e "${JAR_NAME} Stopped and the PID is ${PIDS}."
else
echo_help
exit 1
fi
- 启动Consumer和Provider集群:
文章图片
示例
- 在启动类中添加负载均衡策略对象的方法:
@EnableEurekaClient
@SpringBootApplication
public classConsumerApplication{
@Bean
public RandomRule createRule() {
return new RandomRule();
}public static void main(String[] args) {
SpringApplication.run( ConsumerApplication .class, args);
}
}
- 修改Consumer配置文件更换负载均衡策略:
#设置负载均衡策略 eureka-provider 为调用的服务的名称
eureka-provider.ribbon.NFLoadBalancerRuleClassName=策略全限定类名
6.Ribbon 的点对点直连 :
【Spring|Spring Cloud学习day98(Eureka的原理和Ribbon负载均衡)】在不需要通过Eureka注册中心而是直连某个服务。
- 修改配置文件与 Eureka 相关的配置,添加新配置项 :
spring.application.name=eureka-consumer
server.port=9091
#禁用 eureka
ribbon.eureka.enabled=false
#指定具体的服务实例清单
eureka-provider.ribbon.listOfServers=192.168.226.130:9090
- 修改启动类去除@EnableEurekaClient注解:
@SpringBootApplication
public classConsumerApplication{
public static void main(String[] args) {
SpringApplication.run( ConsumerApplication .class, args);
}
}
推荐阅读
- 由浅入深理解AOP
- Activiti(一)SpringBoot2集成Activiti6
- 继续努力,自主学习家庭Day135(20181015)
- python学习之|python学习之 实现QQ自动发送消息
- 一起来学习C语言的字符串转换函数
- SpringBoot调用公共模块的自定义注解失效的解决
- 解决SpringBoot引用别的模块无法注入的问题
- 定制一套英文学习方案
- 漫画初学者如何学习漫画背景的透视画法(这篇教程请收藏好了!)
- 《深度倾听》第5天──「RIA学习力」便签输出第16期