一、简介
1.1、服务注册中心
1)概念:服务实现服务化管理的核心组件,是SOA架构中最基础的设施,用来存储服务信息(如服务提供者的url、路由信息等);
2)作用:服务的注册与发现;
3)案例:SpringCloud中的Eureka、Dubbo中使用的Zookeeper
1.2、Eureka介绍
1.2.1、概念
Netflix 开发的服务发现组件,本身是一个基于 REST 的服务。Spring Cloud 将它 集成在其子项目 spring-cloud-netflix 中,以实现 Spring Cloud 的服务注册于发现,同时还提供 了负载均衡、故障转移等能力;
1.2.2、三种角色
1)Eureka Server:通过 Register、Get、Renew 等接口提供服务的注册和发现;
2)Application Service (Service Provider):服务提供方,把自身的服务实例注册到 Eureka Server 中;
3)Application Client (Service Consumer):服务调用方,通过 Eureka Server 获取服务列表,消费服务;
1.3、架构图
Register:服务注册,把自己的 IP 和端口注册给 Eureka;
Renew:服务续约,发送心跳包,每 30 秒发送一次,告诉 Eureka 自己还在线;
Cancel:服务下线,当 provider 关闭时会向 Eureka 发送消息,把自己从服务列表中删除,防止consumer 调用到不存在的服务;
Get Registry:获取(其它)服务注册列表;
Replicate:集群中数据同步,eureka集群中的数据复制与同步;
Make Remote Call:完成服务的远程调用;
1.4、CAP 原则
Consistency:一致性
Availability:可用性
Partition tolerance:分区容错性
指在一个分布式系统中,只可同时满足二点,没法三者兼顾;
特征 | 说明 |
---|---|
Consistency | 数据一致性,也叫做数据原子性,系统在执行某项操作后仍然处于一致的状态。在分布式系统 中,更新操作执行成功后所有的用户都应该读到最新的值, 这样的系统被认为是具有强一致性的。等同于所有节点访问 同一份最新的数据副本 |
Availablity | 每一个操作总是能够在一定的时间内返回结果,这里需 要注意的是"一定时间内"和"返回结果"。一定时间内指的是, 在可以容忍的范围内返回结果,结果可以是成功或者是失败 |
Partition-torlerance | 在网络分区的情况下,被分隔的节点仍能正常对外提供 服务(分布式集群,数据被分布存储在不同的服务器上,无论 什么情况,服务器都能正常被访问) |
1.5、ZK与Eureka对比
对比项 | Zookeeper | Eureka | |
---|---|---|---|
CAP | CP | AP | |
Dubbo 集成 | 支持 | - | |
Spring Cloud 集成 | 支持 | 支持 | |
kv 服务 | 支持 | - | ZK 支持数 据存储,eureka 不支持 |
使用接口(多语言能力) | 提供客户端 | http 多语言 | ZK 的跨语 言支持比较弱 |
watch | 支持 | 支持 | 什么是 Watch 支持? 就是客户单 监听服务端的 变化情况。 zk 通过订阅监 听来实现 eureka 通过轮 询的方式来实现 |
集群监控 | - | 支持 | metrics, 运维者可以收 集并报警这些 度量信息达到监控目的 |
二、单机服务
2.1、引入pom依赖
<?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.bjsxt</groupId>
<artifactId>springcloud-eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springcloud-eureka-server</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.13.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.2、配置修改
spring.application.name=eureka-server
server.port=8761
#是否将自己注册到 Eureka-Server中,默认的为true
eureka.client.registerWithEureka=false
#是否从Eureka-Server中获取服务注册信息,默认为true
eureka.client.fetchRegistry=false
2.3、开启注册
启动类中新增@EnableEurekaServer
注解
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
2.4、效果查看
访问地址http://localhost:8761/
三、集群服务
pom依赖同单机服务
3.1、多配置文件
application-eureka1.properties
spring.application.name=eureka-server
server.port=8761
#设置 eureka 实例名称,与配置文件的变量为主
eureka.instance.hostname=eureka1
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://eureka2:8761/eureka/
application-eureka2.properties
spring.application.name=eureka-server
server.port=8761
#设置 eureka 实例名称,与配置文件的变量为主
eureka.instance.hostname=eureka2
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://eureka1:8761/eureka/
3.2、集成日志
logback.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="${catalina.base}/logs/" />
<!-- 控制台输出 -->
<appender name="Stdout" class="ch.qos.logback.core.ConsoleAppender">
<!-- 日志输出编码 -->
<layout class="ch.qos.logback.classic.PatternLayout">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{
yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{
50} - %msg%n
</pattern>
</layout>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="RollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${
LOG_HOME}/server.%d{
yyyy-MM-dd}.log</FileNamePattern>
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{
yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{
50} - %msg%n
</pattern>
</layout>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 日志输出级别 -->
<root level="DEBUG">
<appender-ref ref="Stdout" />
<appender-ref ref="RollingFile" />
</root>
<!--日志异步到数据库 -->
<!-- <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
日志异步到数据库
<connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
连接池
<dataSource class="com.mchange.v2.c3p0.ComboPooledDataSource">
<driverClass>com.mysql.jdbc.Driver</driverClass>
<url>jdbc:mysql://127.0.0.1:3306/databaseName</url>
<user>root</user>
<password>root</password>
</dataSource>
</connectionSource>
</appender> -->
</configuration>
3.3、打包上传至Linux环境
1)install
打包
2)rz
命令,将springcloud-eureka-server-ha-0.0.1-SNAPSHOT.jar
上传至 /usr/local/eureka
目录下(两台机192.168.48.128、192.168.48.129)
[root@localhost eureka]# pwd
/usr/local/eureka
[root@localhost eureka]# ll
总用量 39116
-rw-r--r--. 1 root root 40054481 8月 14 23:24 springcloud-eureka-server-ha-0.0.1-SNAPSHOT.jar
3.4、修改host文件
128与129两台机器都需要修改
[root@localhost eureka]# vim /etc/hosts
[root@localhost eureka]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.48.128 eureka1
192.168.48.129 eureka2
3.5、编写启动脚本
上传server.sh
到部署包同级目录
#!/bin/bash
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
脚本授权
chmod -R 755 server.sh
将脚本中的\r
替换成空白(在Windows下每一行结尾是\n\r
,而Linux下则是\n
,直接启动会提示多出\r
)
sed -i 's/\r$//' server.sh
3.6、启动注册中心
./server.sh start
[root@localhost eureka]# ./server.sh start
Starting the springcloud-eureka-server-ha-0.0.1-SNAPSHOT.jar...
springcloud-eureka-server-ha-0.0.1-SNAPSHOT.jar Started and the PID is 4010.
You can check the log file in /usr/local/eureka/logs/springcloud-eureka-server-ha-0.0.1-SNAPSHOT.jar for details.
3.7、开防火墙
[root@localhost eureka]# firewall-cmd --zone=public --add-port=8761/tcp --permanent
success
[root@localhost eureka]# firewall-cmd --reload
success
3.8、查看效果
分别访问 http://192.168.48.128:8761/
和http://192.168.48.129:8761/
,可见服务eureka1
、eureka2
相互注册成功
四、provider与consumer服务搭建
4.1、provider
4.1.1、pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
4.1.2、配置文件
spring.application.name=eureka-provider
server.port=9090
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://192.168.48.128:8761/eureka/,http://192.168.48.129:8761/eureka/
4.1.3、Controller
@RestController
public class UserController {
@RequestMapping("/provider")
public List<User> getUsers(){
List<User> list = new ArrayList<>();
list.add(new User(1,"zhangsan",20));
list.add(new User(2,"lisi",22));
list.add(new User(3,"wangwu",20));
return list;
}
}
4.1.4、pojo
public class User {
private int userid;
private String username;
private int userage;
public User(int userid, String username, int userage) {
super();
this.userid = userid;
this.username = username;
this.userage = userage;
}
public User() {
super();
}
//getter setter...
}
4.1.5、启动类注解
@EnableEurekaClient
4.2、consumer
4.1.1、配置文件
spring.application.name=eureka-consumer
server.port=9091
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://192.168.48.128:8761/eureka/,http://192.168.48.129:8761/eureka/
4.1.2、Controller
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/consumer")
public List<User> getUsers(){
return this.userService.getUsers();
}
}
4.1.3、pojo
public class User {
private int userid;
private String username;
private int userage;
public User(int userid, String username, int userage) {
super();
this.userid = userid;
this.username = username;
this.userage = userage;
}
public User() {
super();
}
// getter setter
}
4.1.4、UserService
使用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(":").append(si.getPort()).append("/provider");
//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;
}
}
4.1.5、启动类注解
@EnableEurekaClient
4.3、效果查看
1)启动两个服务,并注册到Eureka中心
2)通过访问consumer,调用provider中的服务
五、安全认证
5.1、eureka服务端引入pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
5.2、eureka服务端增加用户配置信息
#开启 http basic 的安全认证
security.basic.enabled=true
security.user.name=admin
security.user.password=123456
5.3、eureka服务端修改注册中心url配置
加上用户名和密码
#设置服务注册中心地址,指向另一个注册中心
eureka.client.serviceUrl.defaultZone=http://admin:123456@eureka1:8761/eureka/
5.4、重新打包后访问eureka服务端
需要输入用户名和密码,说明安全认证已经生效了
5.5、provider与consumer配置修改
eureka.client.serviceUrl.defaultZone=http://admin:123456@192.168.48.128:8761/eureka/,http://admin:123456@192.168.48.129:8761/eureka/
六、自我保护
6.1、自我保护介绍
6.1.1、触发条件
一般情况下,微服务在 Eureka 上注册后,会每 30 秒发送心跳包,Eureka 通过心跳来 判断服务时候健康,同时会定期删除超过 90 秒没有发送心跳服务;
6.1.2、收不到微服务的心跳情况
1)是微服务自身的原因
2)微服务与 Eureka 之间的网络故障
6.1.3、触发保护阈值
15 分钟之内是否低于 85%;
如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制;
6.1.4、为什么需要自我保护
1)因为同时保留"好数据"与"坏数据"总比丢掉任何数据要更好,当网络故障恢复后, 这个 Eureka 节点会退出"自我保护模式";
2)Eureka 还有客户端缓存功能(也就是微服务的缓存功能)。即便 Eureka 集群中所有节点 都宕机失效,微服务的 Provider 和 Consumer都能正常通信;
3)微服务的负载均衡策略会自动剔除死亡的微服务节点;
6.2、关闭自我保护
#关闭自我保护:true 为开启自我保护,false 为关闭自我保护
eureka.server.enableSelfPreservation=false
#清理间隔(单位:毫秒,默认是 60*1000)
eureka.server.eviction.interval-timer-in-ms=60000