服务注册中心是服务实现注册化和管理化的核心组件,类似于目录服务的作用,主要用来存储服务信息,例如服务提供者url串、路由信息等。服务注册中心是SOA架构中最基础的设施之一。
1.服务的注册
2 服务的发现
1 Dubo的注册中心Zookeeper
2 SpringCloud的Eureka
1.服务管理
2.服务的依赖关系管理
Eureka是Netflix开发的服务发现组件,本身是一个基于Rest的服务,SpringCloud将其集成在其子项目中,以实现SpringCloud的服务注册与发现,同时还提供了负载均衡和注册
Eureka Server
通过Register、Get、Renew等接口提供服务的注册与发现。
Application Service(Service Provider)
服务提供方
把自身的服务实例注册到Eureka Server中
Application Client(Service Consumer)
服务调用方
通过Eureka Server获取服务列表,消费服务
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.13.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.luyi</groupId> <artifactId>springcloud-eureka-server</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springcloud-eureka-server</name> <description>Demo project for Spring Boot</description> <properties> <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> 复制代码
@EnableEurekaServer @SpringBootApplication public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class, args); } } 复制代码
spring.application.name=eureka-server #修改服务器端口 server.port=8761 #是否将自己注册到eureka-server中,默认为true eureka.client.registerWithEureka=false #是否从Eureka-server中获取服务信息,默认为true eureka.client.fetchRegistry=false 复制代码
springcloud-eureka-server-ha
在搭建Eureka集群时,需要添加多个配置文件,并且使用SpringBoot的多环境配置方式,集群中需要多少节点就添加多少个配置文件
Eureka1
spring.application.name=eureka-server #修改服务器端口 server.port=8761 #设置Eureka实例名称,以配置文件的变量为主 eureka.instance.hostname=eureka1 #设置服务注册中心地址,指向另一个注册中心 eureka.client.serviceUrl.defaultZone=http://eureka2:8761/eureka/ 复制代码
Eureka2
spring.application.name=eureka-server #修改服务器端口 server.port=8761 #设置Eureka实例名称,以配置文件的变量为主 eureka.instance.hostname=eureka2 #设置服务注册中心地址,指向另一个注册中心 eureka.client.serviceUrl.defaultZone=http://eureka1:8761/eureka/ 复制代码
<?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> 复制代码
部署环境:jdk1.8
将项目打包
将jar包上传到/usr/local/eureka文件夹下
#!/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
vi /etc/hosts
192.168.234.130 eureka1 192.168.234.131 eureka2 复制代码
./server.sh start #启动 ./server.sh stop #停止 复制代码
springcloud-eureka-provider
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.13.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.luyi</groupId> <artifactId>springcloud-eureka-provider</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springcloud-eureka-provider</name> <description>Demo project for Spring Boot</description> <properties> <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</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> 复制代码
//表示Eureka的客户端 @EnableEurekaClient @SpringBootApplication public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class, args); } } 复制代码
spring.application.name=eureka-provider server.port=9090 #设置服务注册中心地址,向所有注册中心做注册 eureka.client.serviceUrl.defaultZone=http://eureka1:8761/eureka/,http://eureka2:8761/eureka/ 复制代码
C:/Windows/System32/drivers/etc
192.168.234.130 eureka1 192.168.234.131 eureka2
@RestController public class UserController { @RequestMapping("/user") public List<User> getUsers(){ List<User> users = new ArrayList<>(); users.add(new User(1, "zhangsan", 20)); users.add(new User(2, "lisi", 22)); users.add(new User(3, "wangwu", 30)); return users; } } 复制代码
/** * Author: LuYi * Date: 2019/11/6 12:30 * Description: 描述 */ public class User { private Integer userid; private String username; private Integer userage; public User() { } public User(Integer userid, String username, Integer userage) { this.userid = userid; this.username = username; this.userage = userage; } public Integer getUserid() { return userid; } public void setUserid(Integer userid) { this.userid = userid; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public Integer getUserage() { return userage; } public void setUserage(Integer userage) { this.userage = userage; } } 复制代码
服务的消费者和生产者都需要在Eureka注册中心注册
spring.application.name=eureka-consumer server.port=9091 #设置服务注册中心地址,向所有注册中心做注册 eureka.client.serviceUrl.defaultZone=http://eureka1:8761/eureka/,http://eureka2:8761/eureka/ 复制代码
@Service public class UserService { @Autowired private LoadBalancerClient loadBalancerClient; //ribbon:负载均衡器 public List<User> getUsers(){ //选择调用的服务的名称 //ServiceInstance:封装了服务的基本信息,如:ip、端口号 ServiceInstance si = loadBalancerClient.choose("eureka-provider"); //拼接访问服务的url StringBuffer sb = new StringBuffer(); //http://localhost:9090/user sb.append("http://").append(si.getHost()).append(":").append(si.getPort()).append("/user"); //SpringMVC RestTemplate RestTemplate restTemplate = new RestTemplate(); ParameterizedTypeReference<List<User>> type = new ParameterizedTypeReference<List<User>>() { }; //ResponseEntity:封装了返回值信息 ResponseEntity<List<User>> entity = restTemplate.exchange(sb.toString(), HttpMethod.GET, null, type); return entity.getBody(); } } 复制代码
@RestController public class UserController { @Autowired private UserService userService; @RequestMapping("/consumer") public List<User> getUsers(){ return userService.getUsers(); } } 复制代码
CAP原则又称为CAP定理,指的是在分布式系统中,Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得,只能三选二。
CAP由Eric Brewer在2000年PODC会议上提出。该猜想在两年后被证明成立,称为我们熟悉的CAP定理
一般情况下,微服务在Eureka注册后,每隔30s会发送一次心跳包,同时会定期删除90s没有发送心跳包的服务。
微服务自身的原因
微服务和Eureka之间网络的原因
如果是因为微服务故障,不会导致大批量出现无法收不到心跳包的情况,只会引发部分故障,而网络故障则会导致大规模收不到心跳包。
考虑到这个区别,Eureka设置了一个阈值,如果在短时间内大规模的收不到心跳包,就会判断为是网络故障,这样Eureka就不会删除心跳过期的服务
阈值是多少呢
15分钟内判断是否低于85%
Eureka Server在运行期间,会判断心跳失败比例在15分钟内是否达到85%
这种算法叫做Eureka Server的自我保护模式。
修改Eureka Server配置文件
#关闭自我保护:true为开启,false为关闭 eureka.server.enable-self-preservation=false #清理间隔(单位:毫秒,默认是60*1000) eureka.server.eviction-interval-timer-in-ms=60000 复制代码
#启动shutdown endpoints.shutdown.enabled=true #禁用密码验证 endpoints.shutdown.sensitive=false 复制代码
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); } } 复制代码
<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/ 复制代码
spring.application.name=eureka-provider server.port=9090 #设置服务注册中心地址,向所有注册中心做注册 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 复制代码