Spring Cloud学习day98:Eureka的原理和Ri

2019-12-04  本文已影响0人  开源oo柒

一、Eureka注册中心架构原理:

1.Eureka架构图:

示例
操作 名称 作用
Register 服务注册 把自己的 IP 和端口注册给 Eureka
Renew 服务续约 发送心跳包,每 30 秒发送一次。告诉 Eureka 自己还活着
Cancel 服务下线 当 provider 关闭时会向 Eureka 发送消息,把自己从服务列表中删除。防止 consumer 调用到不存在的服务。
Get Registry 获取服务注册列表 获取其他服务列表
Replicate 集群中数据同步 eureka 集群中的数据复制与同步
Make Remote Call 远程调用 完成服务的远程调用

2.基于分布式 CAP 定理,Eureka 与 Zookeeper 的区别:

CAP 原则又称 CAP 定理,指的是在一个分布式系统中,Consistency (一致性)、Availability (可用性)、Partition tolerance (分区容错性),三者不可兼得。

分布式系统 P CAP 定理
C:数据一致性( Consistency) 也叫做数据原子性系统在执行某项操作后仍然处于一致的状态。在分布式系统中,更新操作执行成功后所有的用户都应该读到最新的值,这样的系统被认为是具有强一致性的。等同于所有节点访问同一份最新的数据副本。
A:服务可用性( Availablity) 每一个操作总是能够在一定的时间内返回结果,这里需要注意的是"一定时间内"和"返回结果"。一定时间内指的是,在可以容忍的范围内返回结果,结果可以是成功或者是失败。
P :分区容错性( Partition-torlerance) 在网络分区的情况下,被分隔的节点仍能正常对外提供服务(分布式集群,数据被分布存储在不同的服务器上,无论什么情况,服务器都能正常被访问)

任何分布式系统只可同时满足二点,没法三者兼顾。

定律
CA,放弃P 如果想避免分区容错性问题的发生,一种做法是将所有的数据(与事务相关的)都放在一台机器上。虽然无法 100%保证系统不会出错,单不会碰到由分区带来的负面效果。当然这个选择会严重的影响系统的扩展性。
CP:放弃 A 相对于放弃"分区容错性"来说,其反面就是放弃可用性。一旦遇到分区容错故障,那么受到影响的服务需要等待一定时间,因此在等待时间内系统无法对外提供服务。
AP:放弃 C 这里所说的放弃一致性,并不是完全放弃数据一致性,而是放弃数据的强一致性,而保留数据的最终一致性。以网络购物为例,对只剩下一件库存的商品,如果同时接受了两个订单,那么较晚的订单将被告知商品告罄。
对比项 Zookeeper Eureka
CAP CP AP
Dubbo 集成 支持 ~
Spring Cloud 集成 支持 支持
kv服务 支持 ~
使用接口 提供客户端 http多语言
watch支持 支持 支持
集群监控 ~ metrics

3.Eureka的自我保护:

一般情况下,微服务在 Eureka 上注册后,会每 30 秒发送心跳包,Eureka 通过心跳来判断服务时候健康,同时会定期删除超过 90 秒没有发送心跳服务。

通常(微服务的自身的故障关闭)只会导致个别服务出现故障,一般不会出现大面积故障,而(网络故障)通常会导致 Eureka Server 在短时间内无法收到大批心跳。考虑到这个区别,Eureka 设置了一个阀值,当判断挂掉的服务的数量超过阀值时,Eureka Server 认为很大程度上出现了网络故障,将不再删除心跳过期的服务。

15 分钟之内是否高于 85%;
Eureka Server 在运行期间,会统计心跳失败的比例在 15 分钟内是否高于 85%
这种算法叫做 Eureka Server 的自我保护模式。

(1)因为同时保留"好数据"与"坏数据"总比丢掉任何数据要更好,当网络故障恢复后,这个 Eureka 节点会退出"自我保护模式"。
(2)Eureka 还有客户端缓存功能(也就是微服务的缓存功能)。即便 Eureka 集群中所有节点都宕机失效,微服务的 Provider 和 Consumer都能正常通信。
(3)微服务的负载均衡策略会自动剔除死亡的微服务节点。

#自我保护的开关:true 为开启自我保护,false 为关闭自我保护
eureka.server.enableSelfPreservation=false
#清理间隔(单位:毫秒,默认是 60*1000)
eureka.server.eviction.interval-timer-in-ms=60000
示例

4.如何优雅停服?

停止服务的另一种方式,不需要在Eureka Server 中配置关闭自我保护。

修改POM文件中Eureka的依赖。

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
#启用 shutdown
endpoints.shutdown.enabled=true
#禁用密码验证
endpoints.shutdown.sensitive=false
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<String, String> 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<String, String> param) {
        // 创建Httpclient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        String resultString = "";
        try {
            // 创建Http Post请求
            HttpPost httpPost = new HttpPost(url);
            // 创建参数列表
            if (param != null) {
                List<NameValuePair> 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注册中心的安全验证:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
#开启 http basic 的安全认证
security.basic.enabled=true
security.user.name=user
security.user.password=123456
低版本和高版本的区别
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在微服务中的作用:

(1)Ribbon 是一个基于 Http 和 TCP 的客服端负载均衡工具,它是基于 Netflix Ribbon 实 现的。
(2)它不像 spring cloud 服务注册中心、配置中心、API 网关那样独立部署,但是它几乎 存在于每个 spring cloud 微服务中。包括 feign 提供的声明式服务调用也是基于该 Ribbon 实现的。
(3)ribbon 默认提供很多种负载均衡算法,例如 轮询、随机 等等。甚至包含自定义的负 载均衡算法。

解决并提供了微服务的负载均衡的问题。

2.负载均衡的解决方案分类:

即在 consumer 和 provider 之间使用独立的负载均衡设施(可 以是硬件,如 F5, 也可以是软件,如 nginx), 由该设施负责把 访问请求 通过某种策略转发 至 provider。

进程内负载均衡,将负载均衡逻辑集成到 consumer,consumer 从服务注册中 心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的 provider。

Ribbon 就属于后者,它只是一个类库,集成于 consumer 进程,consumer 通过它来获取 到 provider 的地址。

3.Ribbon中常见的负载均衡:

策略名称 对应的类名 实现原理
轮询策略 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 被选中的权重

4.Ribbon默认的轮询策略:

@Service
public class UserService {

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    // ribbon 负 载均衡器
    public List<User> 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<List<User>> type = new ParameterizedTypeReference<List<User>>() {};      
     //ResponseEntity:封装了返回值信息   
     ResponseEntity<List<User>> response = rt.exchange(sb.toString(),HttpMethod.GET, null, type);   
     List<User> list =response.getBody();   
     return list;  
     }

}
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打包上传到两台Linux服务器上。

修改: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

5.指定其他负载均衡策略:

@EnableEurekaClient
@SpringBootApplication
public class  ConsumerApplication  {
    @Bean
    public RandomRule createRule() {
        return new RandomRule();
    }

    public static void main(String[] args) {
        SpringApplication.run( ConsumerApplication .class, args);
    }
}
#设置负载均衡策略 eureka-provider 为调用的服务的名称 
eureka-provider.ribbon.NFLoadBalancerRuleClassName=策略全限定类名

6.Ribbon 的点对点直连 :

在不需要通过Eureka注册中心而是直连某个服务。

spring.application.name=eureka-consumer
server.port=9091 
 
#禁用 eureka 
ribbon.eureka.enabled=false 
#指定具体的服务实例清单 
eureka-provider.ribbon.listOfServers=192.168.226.130:9090
@SpringBootApplication
public class  ConsumerApplication  {
    public static void main(String[] args) {
        SpringApplication.run( ConsumerApplication .class, args);
    }
}
上一篇 下一篇

猜你喜欢

热点阅读