微服务架构的四个核心问题:
1、服务很多,客户端该怎么访问?
2、这么多服务,服务与服务之间如何通信?
3、这么多服务,如何治理?
4、服务挂了怎么办?
spring cloud 生态:解决以上的问题
针对以上的问题,有三套解决方案:
1、Spring cloud NetFlix:一站式解决方案
使用api网关:zuul组件
Feign --HttpClient — http:通信方式,阻塞
服务注册与发现:Eureka
熔断机制:Hystrix
2、Apache Dubbo Zookeeper:半自动,需要整合别人的包
API:没有,找第三方组件或自己实现
服务通信:Dubbo
服务注册与发现:Zookeeper
熔断机制:没有,借助Hystrix
Dubbo这个方案并不完善
3、Spring cloud Alibaba:最新的一站式解决方案,更简单
新概念:服务网格:server Mesh
istio
万变不离其宗:主要核心如下
1、API(解决路由问题)
2、HTTP、RPC(解决通信问题)
3、注册和发现(解决高可用问题)
4、熔断机制(服务降级问题,防止服务雪崩)
以上问题造成的源头:网络不可靠
常见面试问题:
1.什么是微服务?
2.微服务之间是如何独立通讯的?
3.SpringCloud和Dubbo的区别?
4.SpringBoot和SpringCloud,请你谈谈对他们的理解?
- springboot专注于快速方便的开发单个个体服务 -jar
- springCloud是关注全局的微服务协调治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,给各个服务之间提供:配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等集成服务
- SpringBoot可以离开SpringCloud独立使用,开发项目,但是SpringCloud离不开springboot,属于依赖关系
- SpringBoot专注于快速,方便的开发单个个体微服务,SpringCloud关注全局的服务治理框架
5.什么是服务熔断,什么是服务降级?
6.微服务的优缺点分别是什么?请你说说在项目开发中遇到的坑?
7.你所知道的微服务技术栈有哪些?请举例一二
8.Eureka和zookeeper都可以提供服务注册与发现的功能,请你说说区别?
传统互联网架构:
SpringCloud与Dobbo区别:
最大区别:SpringCloud抛弃了Dubbo的RPC通信,采用基于HTTP的Rest方式
参考:
- https://springcloud.cc/spring-cloud-netflix.html
- 中文API文档:https://springcloud.cc/spring-cloud-dalston.html
- SpringCloud中国社区:http://springcloud.cn/
- SpringCloud中文网:https://www.springcloud.cc/
SpringCloud版本选择:
关联git:
进入E:\workspace\IDEA\Study,把之前的java中的.git拷贝到当前文件夹,并把之前push的文件也放到这里:如果不放到这里,这里之前的push的代码都会从当前的远程仓库中删除。
git remote add origin https://github.com/msj-1995/Java.git
git push -u origin springcloud
一、项目搭建
1.1、提供者服务
1、创建普通Maven项目
这是一个父项目,可以把src给删了
2、导入包
打包方式:pom
<!-- 打包方式 -->
<packaging>pom</packaging>
管理版本:
<properties></properties>
依赖管理:与只是用dependencies是一样的,只用使用了一个总的dependencyManagement来管理。
<dependencyManagement>
<dependencies>
<dependency></dependency>
</dependencies>
</dependencyManagement>
pom.xml
<?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.msj</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<!--junit版本管理-->
<junit.version>4.12</junit.version>
<lombok.version>1.16.10</lombok.version>
</properties>
<!-- 打包方式 -->
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<!--springcloud的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<!--可以删除,也可以改为import(导入)-->
<scope>import</scope>
</dependency>
<!-- 使用springCloud,需要依赖springBoot(注意版本对应):也可以使用父工程的方式导入,即parent-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 连接数据库 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!-- springboot启动器:如果使用mybatis,需要使用mybatis-springboot的启动器 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<!-- 引用版本管理里的版本,以后一个方便的在properties中修改版本,而不需要来这里修改 -->
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!--log4j日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--日志相关-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
如果之后Maven有问题的(资源不能导出时,还需要配置资源过滤问题)
2、新建模块
api模块:新建普通Maven模块
这时我们看到springcloud-api中依赖并没有添加进去,因为我们父项目只是管理了那些依赖,如果我们需要使用的话,在模块中还需要导入,这时导入的依赖就会指向父项目。
导入:
springcloud-api中的pom.xml
<?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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.msj</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-api</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
指向父项目:
这时子项目中也已经导入依赖了,这就是依赖管理的好处(用到了再导入)
<!-- 当前自己Module需要的依赖,如果父依赖中已经配置了版本,这里就不用写了 -->
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
3、连接数据库
创建数据库
连接数据库
创建数据库表:
建表语句:
create table dept
(
deptno bigint auto_increment,
dname varchar(60) null,
db_source varchar(60) null,
constraint dept_pk
primary key (deptno)
)
comment '部门表';
db_source:因为是分布式,这个字段的作用是知道以后数据是从那个表中读出的。
插入数据:
insert into dept(dname, db_source) values ('开发部', DATABASE());
insert into dept(dname, db_source) values ('人事部', DATABASE());
insert into dept(dname, db_source) values ('运维部', DATABASE());
insert into dept(dname, db_source) values ('财务部', DATABASE());
insert into dept(dname, db_source) values ('市场部', DATABASE());
DATABASE()函数用于获取数据库名
效果:修改为正确的库
4、实体类
在springcloud-api中新建com.msj.springcloud.api.pojo包
分布式:所有的实体类都有序列化
Dept.java
package com.msj.springcloud.api.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@NoArgsConstructor
// 支持链式写法注解
@Accessors(chain = true)
public class Dept implements Serializable {
// Dept实体类
private Long deptno; // 主键
private String dname;
// 这个字段存放使用那个数据库字段的字段, 微服务->一个服务对应一个数据库,同一个信息可能存在不同的数据库
private String db_source;
public Dept(String dname) {
this.dname = dname;
}
/**
* 链式写法与非链式写法
* 非链式写法:
* Dept dept = new Dept();
* dept.setDeptno();
* dept.setDname()
*
* 使用链式写法:
* dept.setDeptno().setDname().setDb_source()
*/
}
以前该实体类写完要去写实现等,但是现在这个模块只写实体类,现在这个模块就是一个微服务,只写实体类。这就是服务提供者。
5、服务提供者模块
新建springcloud-provider-dept-8001的普通Maven模块,由于拆分的微服务比较多,所以我们把端口号加上,方便管理,可以把test删了
导入微服务需要的依赖:
<?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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.msj</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-provider-dept-8001</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--我们需要实体类:所以我们需要springcloud-api模块-->
<dependency>
<groupId>com.msj</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!-- 日志门面 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- spring test:父工程中到如果springboot dependencies,所以这里不用写版本,与父工程的版本一致:2.1.4.RELEASE -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<!-- spring web 启动器(也是在springboot dependencies中) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 可以使用jetty作为我们的服务器:虽然跟tomcat没什么区别 (springboot dependencies中也有) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<!-- 热部署工具(springboot dependencies中) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
配置:在resources下新建application.yml
目录解构
数据库-命名与实体类驼峰命名的转换:
mybatis:
configuration:
map-underscore-to-camel-case: true
由于我们这里没有转换,就不用配置了
数据源我们也可以使用org.gjt.mm.mysql.Driver
该Driver继承自com.mysql.jdbc.Driver,拥有该驱动的全部功能
application.xml
server:
port: 8001
mybatis:
type-aliases-package: com.msj.springcloud.api.pojo
config-location: classpath:mybatis/mybatis-config.xml # mybatis的核心配置文件
mapper-locations: classpath:mybatis/mapper/*.xml # mybatis的mapper文件位置
# spring的配置
spring:
application:
name: springcloud-provider-dept # 为该模块(微服务)起一个名字
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://localhost:3307/springclouddb01?useUnicode=true&characterEncoding=utf-8
username: root
password: 1234
mybatis核心配置文件:
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 开启缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
编写接口:
包解构:com.msj.springcloud.mapper
DeptMapper接口
package com.msj.springcloud.mapper;
// 注意:这里的Dept是通过pom导入的,因为它属于另一个模块
import com.msj.springcloud.api.pojo.Dept;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
// 表示这个一个Mapper接口
@Mapper
// 让spring托管
@Repository
public interface DeptMapper {
public boolean addDept(Dept dept);
public Dept queryById(Long id);
public List<Dept> queryAll();
}
编写Mapper文件:
在resources/mybatis/mapper下新建:
DeptMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.msj.springcloud.mapper.DeptMapper">
<insert id="addDept" parameterType="dept">
insert into dept(dname, db_source) values(#{dname},DATABASE());
</insert>
<select id="queryById" resultType="dept" parameterType="Long">
select * from dept where deptno = #{deptno};
</select>
<select id="queryAll" resultType="dept">
select * from dept;
</select>
</mapper>
Service层:
在com.msj.springcloud.service中写:
DeptService接口
package com.msj.springcloud.service;
import com.msj.springcloud.api.pojo.Dept;
import java.util.List;
public interface DeptService {
public boolean addDept(Dept dept);
public Dept queryById(Long id);
public List<Dept> queryAll();
}
DeptServiceImpl实现类
package com.msj.springcloud.service;
import com.msj.springcloud.api.pojo.Dept;
import com.msj.springcloud.mapper.DeptMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Override
public boolean addDept(Dept dept) {
return deptMapper.addDept(dept);
}
@Override
public Dept queryById(Long id) {
return deptMapper.queryById(id);
}
@Override
public List<Dept> queryAll() {
return deptMapper.queryAll();
}
}
controller层:提供Restful服务
包结构:com.msj.springcloud.controller
DeptController.java
package com.msj.springcloud.controller;
import com.msj.springcloud.api.pojo.Dept;
import com.msj.springcloud.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
// 提供Restful服务:数据直接使用json格式传送
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
@PostMapping("/dept/add")
public boolean addDept(Dept dept) {
return deptService.addDept(dept);
}
@GetMapping("/dept/get/{id}")
public Dept queryById(@PathVariable("id") Long id) {
return deptService.queryById(id);
}
@GetMapping("/dept/list")
public List<Dept> queryAll() {
return deptService.queryAll();
}
}
在com.msj.springcloud下写一个主启动类:
DeptProvider_8001.java
package com.msj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// 启动类
@SpringBootApplication
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class, args);
}
}
启动:查询全部
通过id查询:
添加:不能添加,因为通过浏览器?传参属于get提交,而我们后台是post的方式的提交,所以不能提交上,这也比较安全。
待会我们的服务消费者只要能拿到这个生产者的接口就可以操作生产者了。
1.2、消费者服务
新建模块:springcloud-consumer-dept-80
1、导包:只要一些实体类和web相关的包
pom.xml
<?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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.msj</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-consumer-dept-80</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- 实体类 -->
<dependency>
<groupId>com.msj</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
2、配置文件
application.yml:只用配置一个端口号即可
server:
port: 80
3、消费者接口
包结构:com.msj.springcloud.controller
类:DeptConsumerController.java
理解:消费者不应该有service层
springboot支持Restful风格的请求:springboot中封装了RestTemplate模板,里面的方法供我们直接调用
RestTemplate并没有@Bean,我们需要手动注册到spring中。
可供使用的方法
手动注册bean,在包com.msj.springcloud.config中新建ConfigBean类:
package com.msj.springcloud.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ConfigBean {
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
DeptConsumerController.java
package com.msj.springcloud.controller;
import com.msj.springcloud.api.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
public class DeptConsumerController {
// 注入RestTemplate 参数: (url, 实体:Map, Class<T> responseType),responseType即请求后返回的数据类型,注意是class对象
@Autowired
private RestTemplate restTemplate; // 提供多种便捷访问远程http服务的方法,简单的Restful服务模板
// 请求地址的前缀
private static final String REST_URL_PREFIX = "http://localhost:8001";
@RequestMapping("/consumer/dept/add")
public boolean add(Dept dept) {
return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
}
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
// 可以使用getForObject,也可以使用getForEntity
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> list() {
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
}
}
主启动类:
package com.msj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class);
}
}
先启动provider(服务提供者)服务,在启动消费者测试:
添加:localhost/consumer/dept/add?dname=“小蕾"
数据进来了,但是插入错误,可能是某处配置错误导致的:
二、Eureka
Eureka:服务注册与发现
Eureka是NetFlix的一个子模块。
2.1、原理讲解
- Eureka的基本架构
- springcloud封装了NetFlix公司开发的Eureka模块来实现服务注册和 发现(对比Zookeeper)
- Eureka采用了C-S的架构设计,EurekaServer作为服务注册功能的服务器,他是服务注册中心
- 而系统中的其他微服务,使用Eureka的客户端来年街道EurekaServer并维持心跳连接,这样系统的维护人员就可以通过EurekaServer来监控系统中各个微服务是否正常运行,springcloud的一些其他模块(比如Zuul)就可以铜鼓偶EurekaServer来发现系统中的其他微服务,并执行相关逻辑。
- Eureka包含两个组件:EurekaServer和EurekaCLient
- 三大角色
- Eureka Server:提供服务的注册与发现。zookeeper
- Service Provider:将自身的服务注册到Eureka中,从而使消费方能够找到。
- Service Consumer:服务消费方从Eureka中获取注册服务列表,从而找到消费服务。
之前我们是通过下载zookeeper服务,然后通过bin中的exe启动服务,现在Eureka是通过springcloud的微服务方式(自己写,通过注解即可启动):
新建模块:springcloud-eureka-7001
1、导包:
只需要导入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">
<parent>
<artifactId>springcloud</artifactId>
<groupId>com.msj</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-eureka-7001</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
2、配置文件:application.yml
使用eureka.client.service-url.defaultZone:覆盖默认的端口
application.yml
server:
port: 7001
# Eureka的配置
eureka:
instance:
hostname: localhost # Eureka服务端的实列名称
client:
register-with-eureka: false # 表示是否向eureka注册中心注册自己(服务器不用注册自己)
fetch-registry: false # 如果fetch-registry为false,表示自己为注册中心
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka # 这就是监控页面地址:hostname和port(端口)都是从配置文件中动态获取的
主启动类:包解构:com.msj.springcloud
在该包下新建主启动类:EurukaServer_7001.java
这样Eureka服务配置成功了(注册中心)
package com.msj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
// 开启Eureka的服务:服务端的启动类,它可以接收别人注册进来
@EnableEurekaServer
public class EurekaServer_7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServer_7001.class, args);
}
}
locahost:7001就进入我们刚才配置的服务监控页面了
2.2、服务注册
1、在springcloud-provider-dept-8001(服务提供者)中加入Eureka的依赖(因为我们要把该服务注册到Eureka中)
<!-- Eureka依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
2、Eureka的配置
server:
port: 8001
mybatis:
type-aliases-package: com.msj.springcloud.api.pojo
config-location: classpath:mybatis/mybatis-config.xml # mybatis的核心配置文件
mapper-locations: classpath:mybatis/mapper/*.xml # mybatis的mapper文件位置
# spring的配置
spring:
application:
name: springcloud-provider-dept # 为该模块(微服务)起一个名字
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://localhost:3307/springclouddb01?useUnicode=true&characterEncoding=utf-8
username: root
password: 1234
# Eureka的配置:服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
3、去主启动类中加开启EurekaClient的注解
package com.msj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
// 启动类
@SpringBootApplication
@EnableEurekaClient
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class, args);
}
}
只要我们加了@EnableEurekaClient注解,该服务就会被注册到Eureka的注册中心。
先启动7001,再启动8001测试:
status中的连接指向了一个连接:这个链接待会可以给他去掉
通过配置修改Satus:
# Eureka的配置:服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
instance:
instance-id: springcloud-provider-dept8001
已修改:
当把8001强制停止(7001仍然开着)过了30s(或者是重新启动8001),可以发现监控页面上出现错误提示:
这就是Eureka的自我保护机制:即某个服务突然崩了,但可能还有其他服务在连接着,这时Eureka是不会让这个服务停止的。
自我保护机制:好死不如赖活
某时刻某个微服务不可以用了,eureka不会立刻清理,一九会对该微服务的信息进行保存。
- 默认情况下,Eureka在一定时间内没有接收到某个服务实例的心跳,Eureka将会注销该实例(默认90秒),但当网络分区发生故障时,微服务与Eureka之间无法进行正常通信,这时注销行为可能变得非常危险,因为微服务本身是健康的,知识网络故障。此时不应该注销这个服务,Eureka通过自我保护机制来解决这个问题-当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式,一旦进入该模式,EurekaServer就会保护服务注册表中的信息,不在删除服务注册表中的信息(也就是不注销任何微服务),当网络故障恢复时,该EurekaServer节点会自动退出自我保护模式。
- 在springCloud中,可以使用eureka.server.enable-self-preservation = false禁用自我保护机制(不推荐)
配置完善监控信息:
默认指向点击的网址是无法访问的
1、springcloud-provider-dept-8001服务中导入actuator依赖
actuator:执行机构
从org.springframework.boot中导入,不需要写版本号
<!-- actuator:完善监控信息 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、配置
server:
port: 8001
mybatis:
type-aliases-package: com.msj.springcloud.api.pojo
config-location: classpath:mybatis/mybatis-config.xml # mybatis的核心配置文件
mapper-locations: classpath:mybatis/mapper/*.xml # mybatis的mapper文件位置
# spring的配置
spring:
application:
name: springcloud-provider-dept # 为该模块(微服务)起一个名字
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://localhost:3307/springclouddb01?useUnicode=true&characterEncoding=utf-8
username: root
password: 1234
# Eureka的配置:服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
instance:
instance-id: springcloud-provider-dept8001 # 修改Eureka上的默认描述信息
# 完善监控信息配置
info:
app.name: msj-springcloud # 项目名字(当然没什么作用,不配也可以)
company.name: msj # 公司名字
3、该微服务中增加一个controller
package com.msj.springcloud.controller;
import com.msj.springcloud.api.pojo.Dept;
import com.msj.springcloud.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
// 提供Restful服务:数据直接使用json格式传送
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
// 获得一些配置信息,得到具体的微服务
@Autowired
private DiscoveryClient discoveryClient;
@PostMapping("/dept/add")
public boolean addDept(Dept dept) {
return deptService.addDept(dept);
}
@GetMapping("/dept/get/{id}")
public Dept queryById(@PathVariable("id") Long id) {
return deptService.queryById(id);
}
@GetMapping("/dept/list")
public List<Dept> queryAll() {
return deptService.queryAll();
}
// 注册进来的微服务,我们可以获取一些消息,可以获取我们配置文件中配置的info
@GetMapping("/dept/discovery")
public Object discovery() {
// 获得微服务列表的清单
List<String> service = discoveryClient.getServices();
System.out.println("discover=>service:" + service);
// 得到一个具体的微服务信息:通过具体的微服务id,也就是applicationName
List<ServiceInstance> instances = discoveryClient.getInstances("springcloud-provider-dept");
for(ServiceInstance instance : instances) {
System.out.println(instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri() + "\t" + instance.getServiceId());
}
return this.discoveryClient;
}
}
主启动类:开启服务发现
package com.msj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
// 启动类
@SpringBootApplication
@EnableEurekaClient
// 服务发现
@EnableDiscoveryClient
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class, args);
}
}
热更新:
测试:
info信息:localhost:8001/actuator/info
只是Eureka监控界面上的给出的地址不是localhost,而是主机名,以后可以自己修改。
2.3、Eureka集群配置
1、新建springcloud-eureka-7002微服务
springcloud-eureka-7003
2、依赖跟7001是一样的
3、配置文件只是修改一下端口
当前集群:就是要让7001中绑定7002和7003,这样当其中某一个出现问题时才可以通知其他。
修改C:\Windows\System32\drivers\etc下的host文件,使eureka700x.com都映射到127.0.0.1上
集群配置修改:集群需要关联其他注册中心
7001:application.yml
server:
port: 7001
# Eureka的配置
eureka:
instance:
hostname: eureka7001.com # Eureka服务端的实列名称:eureka7001本质上还是localhost(127.0.0.1)
client:
register-with-eureka: false # 表示是否向eureka注册中心注册自己(服务器不用注册自己)
fetch-registry: false # 如果fetch-registry为false,表示自己为注册中心
service-url:
# 集群:关联其他的注册中心
defaultZone: http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka # 关联7002和7003注册中心
7002:application.yml
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka # 关联7001和7003注册中心
7003类似:
访问7001:挂在了7002和7003
访问eureka.com:7002
启动服务提供者:8001
这时每个注册中心都会注册上服务,如果某个注册中心突然关了,其他的注册中心任然可以正常运行。
消费者倒是一样的。
2.4、CAP原则
CAP?
- C(Consistency)强一致性
- A(Availability)可用性
- P(Partition tolerance) 分区容错性
CAP的三进二:CA,AP,CP
CAP:一个分布式系统不可能同时很好的满足CAP原则,一般只要满足其中两个。
根据CAP原理,将NoSql数据库分成了满足CA原则,CP原则和AP原则三大类:
- CA:单点集群,满足一致性、可用性的系统,通常可扩展性差
- CP原则:满足一致性、分区容错性的系统,通常性能不是特比高
- AP:满足可用性、分区容错性的系统,通常可能对一致性要求较低。
由于分区容错性P在分布式系统中是必须保证的,一次我们只能在A和C之间进行权衡。
- Zookeeper:保证的CP
- Eureka:保证的是AP
三、Ribbon
spring Cloud Ribbon是基于NetflixRibbon实现的一套”客户端负载工具“
Ribbon的客户端提供一系列完整的配置,如:连接超时,重试等等,简单的说,就是在配置文件中列出LoadBalancer(简称:LB),后边所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接服务器,我们也容易使用Ribbon实现自定义的负载均衡算法。
Ribbon是在客户端配置,我们刚才的所有微服务中只有80端口的服务属于客户端,其他都属于服务端。
Ribbon能干什么
- LB:负载均衡(Load Balance),在微服务或分布式集群中经常用的一种应用。
- LB简单的说就是将用户的请求平摊到多个服务上,从而达到系统的HA(高可用),常见的负载均衡软件有Nginx,Lvs等
- dubbon,SpringCloud中均给我们提供了负载均衡,SpringCloud的负载均衡可以自定义。
- 负载均衡的简单分类:
- 集中式LB:即在服务的消费方和提供方之间使用独立的LB设施,如Nginx,由于设施负责把访问的请求通过某种策略转发至服务器提供方。
- 进程式LB:
- 将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地中选出一个合适的服务器。
- Ribbon就属于进程内LB,他只是一个类库,集成与消费方进程,消费方通过他来获取服务方提供的地址。
3.1、集成Ribbon
1、导入依赖
在消费服务方(springcloud-consumer-dept-80)导入依赖:我们要选择springcloud的ribbon,由于Ribbon需要从注册中心去拿到有哪些服务地址,所以消费者服务中还要导入Eureka
<!-- Ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- Eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
2、编写配置
配置Eureka
server:
port: 80
# eureka配置
eureka:
client:
register-with-eureka: false # 不向eureka注册自己,因为消费者不提供服务
service-url:
defaultZone: http://eureka7002.com:7002/eureka,http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka #客户端可以从三个服务种拿服务
主启动类开启Eureka:
package com.msj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class, args);
}
}
3、配置Ribbon
在config下的ConfigBena种配置,只需要加入一个注解@LoadBalanced即可
ConfigBean.java
package com.msj.springcloud.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ConfigBean {
// 配置负载均衡实现RestTemplate,我们的消费端是restTemplate发起请求的,所以我们的负载均衡从这里开始
@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
修改controller下的DeptConsumerController.java
通过服务名来访问:我们这里只有一个服务提供者,但这个服务提供者有三个Eureka服务都注册类,因此通过服务名,Ribbon就会去找有哪几个注册中心有该服务,然后就可以使用不同的注册中心来访问该服务。
服务名:可以大写,也可以小写(Eureka监控页面的都是大写)
package com.msj.springcloud.controller;
import com.msj.springcloud.api.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
public class DeptConsumerController {
// 注入RestTemplate 参数: (url, 实体:Map, Class<T> responseType),responseType即请求后返回的数据类型,注意是class对象
@Autowired
private RestTemplate restTemplate; // 提供多种便捷访问远程http服务的方法,简单的Restful服务模板
// 请求地址的前缀
// private static final String REST_URL_PREFIX = "http://localhost:8001";
// 使用Ribbon后我们这里的地址应该是一个变量,通过服务名来访问
private static final String REST_URL_PREFIX = "http://springcloud-provider-dept";
@RequestMapping("/consumer/dept/add")
public boolean add(Dept dept) {
return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
}
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
// 可以使用getForObject,也可以使用getForEntity
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> list() {
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
}
}
我们这里测试:7001,7002,8001(先启动),7003就不启动了(防止卡),在启动80
7001和7002都有服务注册了:
启动80消费者:现在还不能体会集群的好处,因为服务方只有一个,所有的服都是到一个数据库种去取东西
访问:localhost/consumer/dept/list
可以查到数据:
Ribbon和Eureka整合以后,客户端可以直接钓友接口,通过服务名调用服务,不用关心IP地址和端口。
3.2、Ribbon实现负载均衡
多写几个提供者服务,让集群可以看到不同请求走不同的服务。即服务提供者变多。
1、再建两个数据库:springclouddb02和springclouddb03
sql语句:
DROP TABLE IF EXISTS `dept`;
CREATE TABLE `dept` (
`deptno` bigint(20) NOT NULL AUTO_INCREMENT,
`dname` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`db_source` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`deptno`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic;
INSERT INTO `dept` VALUES (1, '开发部', DATABASE());
INSERT INTO `dept` VALUES (2, '人事部', DATABASE());
INSERT INTO `dept` VALUES (3, '运维部', DATABASE());
INSERT INTO `dept` VALUES (4, '财务部', DATABASE());
INSERT INTO `dept` VALUES (5, '市场部', DATABASE());
现在三个表的核心是data_source不一样
2、再创建两个服务提供者:8002和8003
导入依赖:依赖与8001一样
修改配置:8002配置,需要修改端口号,instance-id,连接的数据库
注意:他们的服务名都是一样的,三个服务名称一样是前提,但是他们请求的数据库地址不一样了。
server:
port: 8001
mybatis:
type-aliases-package: com.msj.springcloud.api.pojo
config-location: classpath:mybatis/mybatis-config.xml # mybatis的核心配置文件
mapper-locations: classpath:mybatis/mapper/*.xml # mybatis的mapper文件位置
# spring的配置
spring:
application:
name: springcloud-provider-dept # 为该模块(微服务)起一个名字
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://localhost:3307/springclouddb01?useUnicode=true&characterEncoding=utf-8
username: root
password: 1234
# Eureka的配置:服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
instance:
instance-id: springcloud-provider-dept8001 # 修改Eureka上的默认描述信息
# 完善监控信息配置
info:
app.name: msj-springcloud # 项目名字(当然没什么作用,不配也可以)
company.name: msj # 公司名字
8003配置:
server:
port: 8003
mybatis:
type-aliases-package: com.msj.springcloud.api.pojo
config-location: classpath:mybatis/mybatis-config.xml # mybatis的核心配置文件
mapper-locations: classpath:mybatis/mapper/*.xml # mybatis的mapper文件位置
# spring的配置
spring:
application:
name: springcloud-provider-dept # 为该模块(微服务)起一个名字
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://localhost:3307/springclouddb03?useUnicode=true&characterEncoding=utf-8
username: root
password: 1234
# Eureka的配置:服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
instance:
instance-id: springcloud-provider-dept8003 # 修改Eureka上的默认描述信息
# 完善监控信息配置
info:
app.name: msj-springcloud # 项目名字(当然没什么作用,不配也可以)
company.name: msj # 公司名字
把mybatis配置都拷贝过来,不用修改。
同时把java下的东西也拷贝过来。修改启动类的名称,其他都不用改
8002
package com.msj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
// 启动类
@SpringBootApplication
@EnableEurekaClient
// 服务发现
@EnableDiscoveryClient
public class DeptProvider_8002 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8002.class, args);
}
}
8003
package com.msj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
// 启动类
@SpringBootApplication
@EnableEurekaClient
// 服务发现
@EnableDiscoveryClient
public class DeptProvider_8003 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8003.class, args);
}
}
测试:可以只启动一个注册中心(两个,三个也可以)
启动服务提供者:8001,8002,8003
UP(3):三个服务都在线(即都注册到注册中心了)
启动消费者:80
访问list测试:
第一次从db03查到的
第二次是从db02查的
多次测试发现:db01->db02->db03-db01…,循环使用不同的服务(默认:轮询)
3.3、自定义负载均衡算法
我们可以通过IRule接口实现Ribbon的负载均衡算法
IRule接口:
package com.netflix.loadbalancer;
public interface IRule {
Server choose(Object var1);
void setLoadBalancer(ILoadBalancer var1);
ILoadBalancer getLoadBalancer();
}
实现类如下,默认是轮询:
- AbstractLoadBalancerRule,抽象实现类,一般使用它
- AvailabilityFilteringRule:会先过滤掉崩溃的服务(跳闸的服务),对剩下的活着的服务使用轮询。
- BestAvailableRule
- ClientConfigEnableRoundRobinRule
- RoundRobinRule:轮询策略(默认的)
- RandomRule:随机策略
- WeightedResponseTimeRule:根据配置的规则权重选服务
- RetryRule:重试策略,会先按照轮询获取服务,如果获取失败,则会在指定的时间内进行重试
以上常用的几种策略都继承了抽象类:AbstractLoadBalancerRule
我们自定义负载均衡算法时也是集成该抽象类就可以了。
把轮询策略改为随机策略:只需要在ConfigBean中注册随机策略的Bena即可:
随机策略:
package com.msj.springcloud.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ConfigBean {
// 配置负载均衡实现RestTemplate,我们的消费端是restTemplate发起请求的,所以我们的负载均衡从这里开始
@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@Bean
public IRule myRule() {
return new RandomRule();
}
}
启动测试:这里只启动了一个注册中心,3个服务提供者,和一个消费者:
这里的服务是随机的。
自定义负载均衡规则:
我们自己写的负载均衡规则不能跟主启动类同级或是写在主启动类的下面(这样会被自动扫描,我们就体验不到Ribbon注解的作用),我们应该写在主启动类的上一级:这样的话我们的组件和服务代码实现了分离
官网说明:
我们在com.msj下建一个包:myrule
我们在包com.msj.myrule下新建自己的规则:
官方源码随机策略源码:
RandomRule.java
package com.msj.myrule;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class MyRandomRule extends AbstractLoadBalancerRule {
public MyRandomRule() {
}
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
} else {
Server server = null;
while(server == null) {
if (Thread.interrupted()) {
return null;
}
// 获得活着的服务
List<Server> upList = lb.getReachableServers();
// 获得全部服务
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
// 生成区间随机数
int index = this.chooseRandomInt(serverCount);
// 从活着的服务中,随机获取一个服务
server = (Server)upList.get(index);
// 如果没有服务,线程礼让后继续
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
自己想实现的功能:每个服务访问5次,之后再换下一个服务
MyRandomRule.java
解构:
MyRandomRule.java
package com.msj.myrule;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class MyRandomRule extends AbstractLoadBalancerRule {
// 每个服务访问5次,换下一个任务(我们这里有三个任务)
// total=0,默认0,如果为5,指向下一个服务节点
// index=0,默认0,如果total=5,index++,如果index>=0,index置零
private int total = 0; // 被调用的次数
private int currentIndex = 0; // 当前谁在提供服务
public MyRandomRule() {
}
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
} else {
Server server = null;
while(server == null) {
if (Thread.interrupted()) {
return null;
}
// 获得活着的服务
List<Server> upList = lb.getReachableServers();
// 获得全部服务
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
// 生成区间随机数
// int index = this.chooseRandomInt(serverCount);
// 从活着的服务中,随机获取一个服务
// server = (Server)upList.get(index);
// 如果没有服务,线程礼让后继续
// -==========自定义代码===============
if(total < 5) {
server = upList.get(currentIndex);
total++;
}else {
total = 0;
currentIndex++;
if(currentIndex >= upList.size()) {
currentIndex = 0;
}
// 从活着的服务中获取指定的服务来操作
server = upList.get(currentIndex);
}
// -============算法结束===============
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
在config下的ConfigBena中使用自己定义的负载均衡策略:
package com.msj.springcloud.config;
import com.msj.myrule.MyRandomRule;
import com.netflix.loadbalancer.IRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ConfigBean {
// 配置负载均衡实现RestTemplate,我们的消费端是restTemplate发起请求的,所以我们的负载均衡从这里开始
@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@Bean
public IRule myRule() {
return new MyRandomRule();
}
}
测试也预想的一样。
3.3、使用接口方式调用服务
Feign负载均衡
简介:feign是声明式的web service客户端,他让微服务之间的调用变得更简单了,类似controller调用service。spring cloud继承了Ribbon和Eureka,可在使用Feign时提供负载均衡的http客户端。
使用:只需要创建一个接口,然后添加注解即可:
feign:调用微服务访问的两种方法:
1、微服务名字:【ribbon】
2、接口和注解:【feign】
我们把springcloud-consumer-dept-80代码拷贝一份,还是按80的方式创建消费者(目的是保留原来的代码):该消费之的名字为:springcloud-consumer-dept-feign,端口也可以不变
依赖需要在原来的基础上添加Feign的依赖:
<!-- Feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
为了区别我们把主启动类修改一线,并且把Ribbon的注解去掉,因为我们不使用Ribbon实现了,要使用Feign:
FeignDeptConsumer_80.java
package com.msj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class FeignDeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(FeignDeptConsumer_80.class, args);
}
}
现在已经没有Ribbon的服务了,最多是有一个Ribbon的轮询策略:
ConfigBena.java
package com.msj.springcloud.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ConfigBean {
// 配置负载均衡实现RestTemplate,我们的消费端是restTemplate发起请求的,所以我们的负载均衡从这里开始
@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
现在去spring-cloud-api中再建一个包:com.msj.springcloud.api.service,在该包下写我们提供的服务:这是一个接口,不需要实现
把Feig的依赖也导入:
<!-- Feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
写服务接口:DeptClientService.java
package com.msj.springcloud.api.service;
import com.msj.springcloud.api.pojo.Dept;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.List;
@Component
@FeignClient(value = "springcloud-provider-dept")
public interface DeptClientService {
@GetMapping("/dept/get/{id}")
public Dept queryById(@PathVariable("id") Long id);
@GetMapping("/dept/list")
public List<Dept> queryAll();
@PostMapping("/dept/add")
public boolean addDept(Dept dept);
}
这里Feign做的工作跟Ribbon是一样的,其底层还是调用Ribbon实现负载均衡,只是把面向接口编程做到了很好。
修改DeptConsumerController.java
package com.msj.springcloud.controller;
import com.msj.springcloud.api.pojo.Dept;
import com.msj.springcloud.api.service.DeptClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class DeptConsumerController {
@Qualifier("com.msj.springcloud.api.service.DeptClientService")
@Autowired
private DeptClientService service;
@RequestMapping("/consumer/dept/add")
public boolean add(Dept dept) {
return this.service.addDept(dept);
}
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
return this.service.queryById(id);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> list() {
return this.service.queryAll();
}
}
这里使用注入的方式调用
主启动类开启Feign支持:
package com.msj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@EnableEurekaClient
// 开启Feign:它会使指定的包下的@FeignClient生效,这里我们从pox.xml中导入了api,所以api服务下的service中的类的FeignClient也会生效。
@EnableFeignClients(basePackages = {"com.msj.springcloud"})
@ComponentScan("com.msj.springcloud")
public class FeignDeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(FeignDeptConsumer_80.class, args);
}
}
四、Hystrix
4.1、服务降级
Hystrix:服务熔断
复杂分布式体系结构中应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免的失败。
服务雪崩
微服务链路中,如果其中某个服务坏了,则整个服务就崩了。
解决办法:弃车保帅
Hystrix:是一个用于处理分布式系统延迟和容错的开源库,许多依赖不可避免的会调用失败,比如超速,异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
断路器:本身是一种开关装置,当某个服务单元发生故障的时候,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个服务预期的,可以处理备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法处理的异常,这样就可以保证了服务调用方的线程不会被长时间、不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
Hystrix能干嘛:
- 服务降级
- 服务熔断
- 服务限流
- 接近实时监控
- …
我们也可以自己实现这个组件:
通过异常抛出和异步服务,当服务出现问题的时候去调用其他的服务。
服务熔断:
熔断机制对应雪崩效应的一种微服务链路保护机制。
当扇出链路出现服务不可用或者响应时间太长,会进行服务降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路,在SpringCloud框架里熔断机制通过Hystrix实现,Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败就会启动熔断机制,熔断机制的注解是@HystrixCommand
我们针对8001服务重新写一个Hystrix版本的服务提供者:
直接复制一份即可:微服务名字:springcloud-provider-dept-hystrix-8001
修改一下主启动类:
package com.msj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
// 启动类
@SpringBootApplication
@EnableEurekaClient
// 服务发现
@EnableDiscoveryClient
public class DeptProviderHystrix_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProviderHystrix_8001.class, args);
}
}
复制完成后,要使用Hystrix:
1、导入依赖:
<!-- Hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
2、增加hystrix的配置:修改instance-id一下就可以了
server:
port: 8001
mybatis:
type-aliases-package: com.msj.springcloud.api.pojo
config-location: classpath:mybatis/mybatis-config.xml # mybatis的核心配置文件
mapper-locations: classpath:mybatis/mapper/*.xml # mybatis的mapper文件位置
# spring的配置
spring:
application:
name: springcloud-provider-dept # 为该模块(微服务)起一个名字
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://localhost:3307/springclouddb01?useUnicode=true&characterEncoding=utf-8
username: root
password: 1234
# Eureka的配置:服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
instance:
instance-id: springcloud-provider-hystrix-dept8001 # 修改Eureka上的默认描述信息
# 完善监控信息配置
info:
app.name: msj-springcloud # 项目名字(当然没什么作用,不配也可以)
company.name: msj # 公司名字
3、修改DeptController
方案一:
package com.msj.springcloud.controller;
import com.msj.springcloud.api.pojo.Dept;
import com.msj.springcloud.api.service.DeptClientService;
import com.msj.springcloud.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
// 提供Restful服务:数据直接使用json格式传送
@RestController
public class DeptController {
@Autowired
private DeptClientService deptClientService;
@GetMapping("/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
Dept dept = deptClientService.queryById(id);
// 用户不存在,则抛出一个异常
if(dept == null) {
throw new RuntimeException("id=>" + id + ",不存在该用户,或者信息无法找到......");
}
return dept;
}
}
如果出现问题,则抛出异常,程序在运行时捕获该异常后继续去执行相关的代码。(如果异常不捕获,代码就直接崩了)
方案二:服务出现异常时调用备用方法
Hystrix熔断
package com.msj.springcloud.controller;
import com.msj.springcloud.api.pojo.Dept;
import com.msj.springcloud.service.DeptService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
// 提供Restful服务:数据直接使用json格式传送
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
@GetMapping("/dept/get/{id}")
@HystrixCommand(fallbackMethod = "hystrixGet")
public Dept get(@PathVariable("id") Long id) {
Dept dept = deptService.queryById(id);
// 用户不存在,则抛出一个异常
if(dept == null) {
throw new RuntimeException("id=>" + id + ",不存在该用户,或者信息无法找到......");
}
return dept;
}
// 如果出现异常:则调用备选方案
public Dept hystrixGet(@PathVariable("id") Long id) {
return new Dept()
.setDeptno(id)
.setDname("id=>" + id + "没有对应的信息,null--@Hystrrix")
.setDb_source("没有这个数据库");
}
}
使用Hystrix后,遇到异常就会去执行HystrixCommand指向的方法。该方法会返回一个Dept,该Dept最终以RestController的方式显示到页面
主启动类开启Hystrix:
package com.msj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
// 启动类
@SpringBootApplication
@EnableEurekaClient
// 服务发现
@EnableDiscoveryClient
// 开启Hystrix服务
@EnableCircuitBreaker
public class DeptProviderHystrix_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProviderHystrix_8001.class, args);
}
}
启动注册中心,启动hystrix_8001,启动80,测试访问不存在的id:
这样的话不报错,直接返回Hystrix的写入数据库的信息(并不会写入数据库)
这样的话:如果有100个controller,就会要写100个备选的方法,比较麻烦。
如果不使用Hystrix,使用抛出异常的方式,修改springcloud-provider-8001中的DeptController:
package com.msj.springcloud.controller;
import com.msj.springcloud.api.pojo.Dept;
import com.msj.springcloud.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
// 提供Restful服务:数据直接使用json格式传送
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
// 获得一些配置信息,得到具体的微服务
@Autowired
private DiscoveryClient discoveryClient;
@PostMapping("/dept/add")
public boolean addDept(Dept dept) {
return deptService.addDept(dept);
}
@GetMapping("/dept/get/{id}")
public Dept queryById(@PathVariable("id") Long id) {
Dept dept = deptService.queryById(id);
if(dept == null) {
throw new RuntimeException("id=>" + id + ",不存在该用户,或者信息无法找到......");
}
return dept;
}
@GetMapping("/dept/list")
public List<Dept> queryAll() {
return deptService.queryAll();
}
// 注册进来的微服务,我们可以获取一些消息,可以获取我们配置文件中配置的info
@GetMapping("/dept/discovery")
public Object discovery() {
// 获得微服务列表的清单
List<String> service = discoveryClient.getServices();
System.out.println("discover=>service:" + service);
// 得到一个具体的微服务信息:通过具体的微服务id,也就是applicationName
List<ServiceInstance> instances = discoveryClient.getInstances("springcloud-provider-dept");
for(ServiceInstance instance : instances) {
System.out.println(instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri() + "\t" + instance.getServiceId());
}
return this.discoveryClient;
}
}
启动测试:访问不存在的id,直接报错
后台也崩了:
服务指向的信息的ip地址的配置:prefer-ip-address: false
# Eureka的配置:服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://localhost:7002/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
instance:
instance-id: springcloud-provider-hystrix-dept8001 # 修改Eureka上的默认描述信息
prefer-ip-address: true
显示的ip地址:
点击:
正确跳转到:
我们点进prefer-ip-address: true的源码可以看到我们可以走两个地址:
192.168.43.62:8001/actuator/info
和192.168.43.62:8001/actuator/health
走后面这个连接会下载一个东西,如果服务正常的时候Status为UP,即服务或者的时候Status为UP
4.2、关于启动的一些问题
上面的代码,所有的服务(8001、8002,8003)都是朝7001注册中心注册服务的,如果我们某个时候没有启动7001注册中心进行测试,则发现所有的服务都不能启动,原因是注册中心连接失败,因为这里的服务都是先注册到7001注册中心,然后再通过集群复制到其他注册中心的,所以这个时候只启动了7002注册中心,我们应该把服务的注册中心地址修改为7002的,其他同理:
# Eureka的配置:服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://localhost:7002/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
instance:
instance-id: springcloud-provider-hystrix-dept8001 # 修改Eureka上的默认描述信息
4.3、服务降级
图中:A、B、C是三个服务,矩形中的小圆圈表示该服务中的一些组合的微服务。
服务降级:整体的服务资源不够用了,先关闭一些服务,保证一些服务的可用性。
如很多请求访问A,访问B、C请求很少,这时我们可以关闭B、C中的一些服务,让服务器A可以占用更多的资源,从而提高A的高并发,当高并发的时期过了,再把B、C的服务开启。
这也是跟客户端有关,当服务端某个服务关闭后,客户端再去访问的时候,服务降级会告诉该客户的该服务不可用。
使用:
1、我们首先在springcloud-api服务中添加一些跟失败有关的回调函数:
在service包下新建:DeptClientServiceFallbackFactory.java,该类要实现Feign下的FallbackFactory类。
前面Hystrix处理的一个方法,这里处理的是一个类,使用使用工厂方法。
package com.msj.springcloud.api.service;
import com.msj.springcloud.api.pojo.Dept;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
// 服务降级
@Component
public class DeptClientServiceFallbackFactory implements FallbackFactory {
@Override
// 这里是处理一个类,要处理哪个类的回调,就返回哪个类,由于这里的DeptClientService是一个接口,不能new,就不能返回该类的对象,所以我们要先实现DeptClientService接口,或者是重写该接口中的方法,如下
public DeptClientService create(Throwable throwable) {
return new DeptClientService() {
@Override
public Dept queryById(Long id) {
// 这里的操作跟Hystrix中的一样
return new Dept()
.setDeptno(id)
.setDname("id=>" + id + ",没有对应的信息,客户端提供了降级的信息,该服务现在已经被关闭,不可用......")
.setDb_source("no Database");
}
@Override
public List<Dept> queryAll() {
List<Dept> list = new ArrayList<>();
list.add(new Dept()
.setDeptno(1L)
.setDname("queryAll=>没有对应的信息,客户端提供了降级的信息,该服务现在已经被关闭,不可用......")
.setDb_source("no Database"));
return list;
}
@Override
public boolean addDept(Dept dept) {
return false;
}
};
}
}
2、在DeptClientService接口中使用FeignClient注解中的fallbackFactory属性指定请求失败时的回调函数。
package com.msj.springcloud.api.service;
import com.msj.springcloud.api.pojo.Dept;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.List;
@Component
@FeignClient(value = "springcloud-provider-dept", fallbackFactory = DeptClientServiceFallbackFactory.class)
public interface DeptClientService {
@GetMapping("/dept/get/{id}")
public Dept queryById(@PathVariable("id") Long id);
@GetMapping("/dept/list")
public List<Dept> queryAll();
@PostMapping("/dept/add")
public boolean addDept(Dept dept);
}
3、我们这里使用feign的80端口的客户端中配置开启降级服务
修改springcloud-consumer-dept-fein微服务中的配置文件:增加开启服务降级的配置
server:
port: 80
# eureka配置
eureka:
client:
register-with-eureka: false # 不向eureka注册自己,因为消费者不提供服务
service-url:
defaultZone: http://eureka7002.com:7002/eureka,http://eureka7001.com:7001/eureka,http://eureka7003.com:7003/eureka #客户端可以从三个服务种拿服务
# 开启降级服务
feign:
hystrix:
enabled: true
4、启动测试:
启动7001注册中心,带Feign的8001服务和80消费者:
现在服务一切正常,现在把8001服务关了:显示服务关闭信息
处理服务异常的两种方式:服务熔断和服务降级,两种方式都是可以的。
服务熔断与服务降级对比:
服务熔断:服务端,某个服务超时或者异常,引起熔断,。
服务降级:客户端,从整体的网站请求负载考虑,当某个服务熔断或者关闭之后,服务不在被调用(直接不走服务器,通过客户端返回),我们要在客户端准备一个fallbackFactory,返回一个默认的值(缺省值),这时整体的服务水平下降了(但是至少能用,比挂掉强)
4.4、DashBoard流量监控
也消费者相关,客户端。他是一个监控服务
1、新建一个微服务:springcloud-consumer-hystrix-dashboard
把80端口的依赖拿过来,并增加如下依赖:Hystrix和Hystrix的Dash board监控相关的依赖。
<!-- Hystrix依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- dashboard依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
2、配置:以上两个依赖导入就可以了,什么也不用配置就可以使用了
修改一下端口号
server:
port: 9001
3、主启动类:com.msj.springcloud包下
package com.msj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication
// 开启监控页面
@EnableHystrixDashboard
public class DeptConsumerDashBoard_9001 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumerDashBoard_9001.class, args);
}
}
注意:要使客户端能够监控到,服务端8001,8002,8003需要导入晚上监控信息的依赖:actuator
启动:访问Hystrix Dashboard
配置要监控的信息:
1、启动的服务提供者需要增加一个DashBoard的bean:
首先需要在服务提供者中导入对应的依赖:如在hystrix_8001中导入Hystrix的包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
然后在主启动类中通过Servlet注册一个Bean:
Metrics:度量,指标
package com.msj.springcloud;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
// 启动类
@SpringBootApplication
@EnableEurekaClient
// 服务发现
@EnableDiscoveryClient
// 开启Hystrix服务
@EnableCircuitBreaker
public class DeptProviderHystrix_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProviderHystrix_8001.class, args);
}
// 增加一个servlet
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet() {
ServletRegistrationBean registrationBean = new ServletRegistrationBean<>(new HystrixMetricsStreamServlet());
// 添加监控的路径:/actuator/hystrix.stream,我们只要填这个路径就行了
registrationBean.addUrlMappings("/actuator/hystrix.stream");
return registrationBean;
}
}
addUrlMappings中url地址的来源:
注意:1、DashBoard需要Hystrix服务开启才可以用
2、使用hystrix_8001不能访问/consumer/dept/list和add,因为在该微服务中还没有实现对应的方法。
启动测试:启动7001注册中心,hystrix_8001,80端口
访问:localhost/consumer/dept/get/1
访问了上面的地址后,再访问:localhost:8001/actuator/hystrix.stream
我们就可以看到他监控到的一些流:
去到localhost:9001/hystrix,我们输入刚才的地址:localhost:8001/actuator/hystrix.stream
Delay和Title可以随便填,之后就可以监控了:
点击监控流:Monitor Stream:
我们多次刷新访问:localhost/consumer/dept/get/1
会发现监控六种的圆圈再逐渐变大:
如果不刷新,则该圆圈又会变小:
- 一圆
实心圆:有两种含义,它通过颜色的变化代表了实例的健康程度
健康程度从绿色<黄色<红色 递减
该实心圆除了颜色的变化外,它的大小也会根据实例的请求流量变化,流量越大,该实心圆就越大。所以通过实心圆的表示,可以在大量的实例中快速发现故障实例和高压实例。
- 一线
曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。
- 整图说明
五、路由网关
网关是终端访问服务的第一个关口(即最接近前端的服务)
5.1、Zuul路由网关
概述:Zuul包含了对请求的路由和过滤两个最主要的功能:
其中路由功能负责外部请求转发到具体微服务实例上,是实现外部访问统一入口的基础,而过滤器功能则是负责对请求的处理过程进行干预,是实现请求校验,服务器聚合功能的基础。Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转获得的。
注意:Zuul服务最终还是会注册到Eureka
提供:代理+路由+过滤三大功能
Zuul能干嘛?
- 路由
- 过滤
官方文档:https://github.com/Netfilx/zuul
以前通过controller跳转,现在可以通过Zuul跳转。
比如之前有8001、8002、8003三个服务提供者,我们要访问8001的服务,只能通过输入localhost:8001访问,8002只能通过输入localhost:8002访问,8003同理,这时通过路由网关,我们可以屏蔽服务器的真实地址,也可以统一访问方式。
5.2、项目搭建
1、新建springcloud-zuul-9527
2、导入依赖:把dashboard中的依赖放进去,并添加zuul的依赖,如下
<dependencies>
<!-- zuul -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- Hystrix依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- dashboard依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- Ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- Eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- 实体类 -->
<dependency>
<groupId>com.msj</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
2、配置文件application.yml(这里也可以写bootstrap.yml)
server:
port: 9527
spring:
application:
name: springcloud-zuul # 微服务的名字
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
instance-id: zuul9527.com
prefer-ip-address: true # 详细信息显示ip
info:
app.name: msj-springcloud
company.name: msj.com
3、主启动类
在com.msj.springcloud下写:
package com.msj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
// 开启Zuul服务代理
@EnableZuulProxy
public class ZuulApplication_9527 {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication_9527.class, args);
}
}
启动7001,8001(可以是带熔断的hystrix_8001,也可以是普通的8001),启动80,启动9527
访问:localhost/consumer/dept/get/2
由于刚才我们配置hosts文件,erueka7001.com, erueka7002.com, erueka7003.com都是指向127.0.0.1(即localhost),所以我们也可以通过下面的方式访问: eureka7002.com/consumer/dept/get/2
以上两种方式的默认端口都是80端口,由于我们配置了Zuul路由网关,所以我们通过localhost:9527,erueka7001.com:9527, erueka7002.com:9527, erueka7003.com:9527,在加上微服务的名字也可以访问服务:
localhost:9527/springcloud-provider-dept/dept/get/1
注意:现在是通过微服务去访问服务器的,跳过了80消费端,所以我们访问的路径中少了/consumer,我们通过Zuul直接拿到了服务端hytrix_8001或者是8001,8002,8003中的服务。
注意:只是启动的是hystrix_8001的话:localhost:9527/springcloud-provider-dept/dept/list 和 add方法仍不能使用,因为没有实现。
但是这样的话有一个坏处:就是微服务的名字暴露了,我们可以通过配置屏蔽微服务的名字;
修改9527微服务中的配置:
server:
port: 9527
spring:
application:
name: springcloud-zuul # 微服务的名字
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
instance-id: zuul9527.com
prefer-ip-address: true # 详细信息显示ip
info:
app.name: msj-springcloud
company.name: msj.com
# zuul的配置
zuul:
routes: # routes是Map(键值对),我们可以随意配置
mydept.serviceId: springcloud-provider-dept
mydept.path: /mydept/**
代码解析
# zuul的配置
zuul:
routes: # routes是Map(键值对),我们可以随意配置
mydept.serviceId: springcloud-provider-dept
mydept.path: /mydept/**
# 等价于:
zuul:
routes:
mydept:
serviceId: springcloud-provider-dept
path: /mydept/**
routes是Map<String, ZuulProperties.ZuulRoute>类型的,而ZuulRoute的定义如下:
public static class ZuulRoute {
private String id;
private String path;
private String serviceId;
private String url;
private boolean stripPrefix = true;
private Boolean retryable;
private Set<String> sensitiveHeaders = new LinkedHashSet();
private boolean customSensitiveHeaders = false;
}
以上的配置的意思是:设置mydept的serviceId为springcloud-provider-dept,并把mydept.path设置为/mydept/**
重新启动,发现localhost:9527/mydept/dept/get/3 可以访问服务,并且这时也不会暴露微服务名,但是这时通过微服务名仍然可以访问
配置不能使用微服务名访问:
server:
port: 9527
spring:
application:
name: springcloud-zuul # 微服务的名字
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
instance-id: zuul9527.com
prefer-ip-address: true # 详细信息显示ip
info:
app.name: msj-springcloud
company.name: msj.com
# zuul的配置
zuul:
routes: # routes是Map(键值对),我们可以随意配置
mydept:
serviceId: springcloud-provider-dept
path: /mydept/**
ignored-services: springcloud-provider-dept # 不能再使用这个路径访问了
隐藏全部的真实项目:使用通配符:*
zuul:
routes: # routes是Map(键值对),我们可以随意配置
mydept:
serviceId: springcloud-provider-dept
path: /mydept/**
ignored-services: "*"
使用同一的前缀访问:
# zuul的配置
zuul:
routes: # routes是Map(键值对),我们可以随意配置
mydept:
serviceId: springcloud-provider-dept
path: /mydept/**
ignored-services: springcloud-provider-dept # 不能再使用这个路径访问了
prefix: /msj
现在所有的请求都要加上:/msj/path+/服务提供者的controller访问
localhost:9527/msj/mydept/dept/get/3
六、git配置
文档地址:https://springcloud.cc/spring-cloud-dalston.html
Spring Cloud config:分布式配置
这部分跟Netflix已经没有关系了。
分布式系统面临的问题,配置文件的问题
什么是SpringCloud config分布式配置中心
SpringCloud config可以帮助我们把配置文件统一的放到云端去管理
spring cloud config分为服务端和客户端两部分:
- 服务端也成为分布式配置中心,他是一个独立的微服务应用,用来连接配置服务器并未客户端提供获取配置信息,加密,解密信息等访问接口。
- 客户端:则是通过指定配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。
Spring Cloud config分布式配置中心能干嘛?
- 集中管理配置文件
- 不同环境,不同配置,动态化的配置更新,分环境部署,比如:/dev /test /prod /beta /release 等待
- 运行期间动态调整配置,不需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
- 当配置发生变动时,服务不需重启,即可感知到配置的变化,并应用新的配置(需要热部署插件的支持)
- 将配置信息以Rest接口的形式暴露
Spring Cloud config分布式配置中心与Github整合:
可以托管git代码的平台:github,码云,CODING
1、我们这里使用码云
2、登录,新建仓库
开源,语言选java, .gitignore模板选java,开源许可证选GPL3.0,添加Readme和选择单分支模型,点击创建
3、创建后,通过git克隆到本地
里面的东西要删的话都可以删了
我们在该文件夹下新建一个application.yml
spring:
profiles:
active:
# ---:多文档配置分割线
---
spring:
profiles: dev
application:
name: springcloud-config-dev
---
spring:
profiles: test
application:
name: springcloud-config-test
把该文件添加到远程仓库:
git push origin master:origin代表当前的用户,master代表master分支。
6.1、springcloud-config搭建
spring cloud config 是C/S架构,所以我们要建两个微服务:一个是server,一个是client
1、新建服务端:springcloud-config-3344(端口是随便写的)
2、导入依赖
<dependencies>
<!-- config -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
</dependencies>
3、编写配置:可以去官网上查看,可以配置的东西很多
server:
port: 3344
spring:
application:
name: springcloud-config-server
# 连接远程仓库
cloud:
config:
server:
git:
uri: https://gitee.com/mshunj/springcloud-config.git # 地址使用码云仓库中的克隆下的https网址
4、主启动类
package com.msj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
// 开启Config服务
@EnableConfigServer
public class ConfigServer_3344 {
public static void main(String[] args) {
SpringApplication.run(ConfigServer_3344.class, args);
}
}
启动:测试是否能够拿到远程的配置文件
启动报错:因为我们到了Eureka的包,它找不到Eureka就会报错,我们把Eureka的依赖删了。同时再加一个完善信息的包:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- config -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
启动:
HTTP服务具有以下格式的资源:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
通过:localhost:3344/application-dev.yml 读取application中的配置文件
通过config-server:我们可以连接
另一种读法:/{application}/{profile}[/{label}],profile是指多文档配置文件中的某一个分支,如dev,test等,label是指分支,我们这里只有master分支
localhost:3344/application/dev/master
/{label}/{application}-{profile}.yml:label放在前面的读法:
localhost:3344/master/application-dev.yml
如果我们多一个不存在的配置文件,则配置都显示空的:http://localhost:3344/master/application-qq.yml
6.2 、配置客户端
1、在我们clone的仓库中在新建一个配置文件(有关客户端的),名字可以随便起:config-client.yml
由于客户端要用到eureka,所以eureka的配置要有:
spring:
profiles:
active: dev
---
server:
port: 8201
spring:
profiles: dev
application:
name: springcloud-provider-dept
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
---
server:
port: 8202
spring:
profiles: test
application:
name:springcloud-provider-dept
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
2、新建客户端微服务:springcloud-config-client-3355
3、导入依赖
<dependencies>
<!-- spring cloud config -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
一般服务端使用的是server,客户端使用的是starter,如config就是:
服务端:config-server,客户端:starter-config
4、配置
这里可以使用bootstrap.yml(也可以使用application.yml),spring中有bootstrap和application的类加载器,这两个文件都会被识别为spring的配置文件,其他的yml就不可以。
区别:
bootstrap.yml:系统级别的配置
application.yml:用户级别的配置
一般使用用户级别的配置文件就可以了,但是这里可能会跟远程的配置文件有冲突,所以使用系统级别的配置
配置顺序:服务端连接远程仓库,客户端连接服务端即可。刚才配置的服务端已经连接远程仓库了,所以我们这里的客户端只用连接服务端就可以了。
bootstrap.yml
# 系统级别的配置:spring
spring:
cloud:
config:
name: config-client # 需要从git上读取的资源名称,不需要后缀,这里读取git上的config-client.yml
profile: dev # 需要那那个配置环境
label: master # 读取文件的分支
uri: http://localhost:3344 # 连接服务端
以上配置最终拼接的路径为:http://localhost:3344/config-client/dev/master
appliction.yml
# 用户级别的配置
spring:
application:
name: springcloud-config-client-3355
5、为了能保证拿到3344的东西,我们先写一个controller
在msj.com.springcloud.controller下新建ConfigClientController.java
package com.msj.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ConfigClientController {
// 通过远程配置文件注入值,看看能不能拿到
// 注入application.name的值
@Value("${spring.application.name}")
private String applicationName;
// 我们远程配置了eureka,而这里并没有配置,看看能不能拿到
@Value("${eureka.client.service-url.defaultZone")
private String eurekaServer;
@Value("${server.port}")
private String port;
@RequestMapping("/config")
public String getConfig() {
return "application: " + applicationName +
"eurekaServer: " + eurekaServer +
"port: " + port;
}
}
6、主启动类
package com.msj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConfigClient_3355 {
public static void main(String[] args) {
SpringApplication.run(ConfigClient_3355.class, args);
}
}
启动3344,我们可以访问到config-client.yml的内容:现在说明server跟git连通了 localhost:3344/master/config-client-dev.yml
启动3355:测试客户端跟服务端是否连通
注意:我们这里没有配置3355的启动端口,但是我们通过bootstrap.yml系统级配置设置3355客户端启动的时候去远程读取client-config的dev配置,而该配置的server-port是8201,所以3355启动的时候端口号为8201:
并且3355启动时也指明了配置是从3344服务端获取的。
所以我们要访问我们客户端的controller时要通过8201访问:
如果我们把bootstrap中的profile修改为test时,我们启动的端口就变为8202了。
6.3、远程配置实战测试
现在的Eureka集群的配置(7001,7002,7003)每个都有,要修改的话也是每一个都要修改,十分的麻烦,我们现在把它放到远程,统一的修改配置。
1、首先编写远程配置文件:即在我们clone的远程仓库中新建文件
新建:config-eureka.yml
我们通过yml的多环境配置配置了两套环境
spring:
profiles:
active: dev
---
server:
port: 7001
spring:
profiles: dev
application:
name: springcloud-config-eureka
# eureka的配置
eureka:
instance:
hostname: eureka7001.com
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
---
server:
port: 7001
spring:
profiles: test
application:
name: springcloud-config-eureka
# Eureka的配置
eureka:
instance:
hostname: eureka7001.com # Eureka服务端的实列名称:eureka7001本质上还是localhost(127.0.0.1)
client:
register-with-eureka: false # 表示是否向eureka注册中心注册自己(服务器不用注册自己)
fetch-registry: false # 如果fetch-registry为false,表示自己为注册中心
service-url:
# 集群:关联其他的注册中心
defaultZone: http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka # 关联7002和7003注册中心
2、服务提供者我们也不想在本地配置,我们也push到远程管理
在clone到本地的仓库中新建配置文件:config-dept.yml
首先把8001服务中的配置文件中的内容拿过去,然后再增加一些配置
spring:
profiles:
active: dev
---
server:
port: 8001
mybatis:
type-aliases-package: com.msj.springcloud.api.pojo
config-location: classpath:mybatis/mybatis-config.xml # mybatis的核心配置文件
mapper-locations: classpath:mybatis/mapper/*.xml # mybatis的mapper文件位置
# spring的配置
spring:
profiles: dev
application:
name: springcloud-config -dept # 为该模块(微服务)起一个名字
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://localhost:3307/springclouddb01?useUnicode=true&characterEncoding=utf-8
username: root
password: 1234
# Eureka的配置:服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
instance:
instance-id: springcloud-provider-dept8001 # 修改Eureka上的默认描述信息
# 完善监控信息配置
info:
app.name: msj-springcloud # 项目名字(当然没什么作用,不配也可以)
company.name: msj # 公司名字
# 第二套配置环境我们把数据库改为了springclouddb02
---
server:
port: 8001
mybatis:
type-aliases-package: com.msj.springcloud.api.pojo
config-location: classpath:mybatis/mybatis-config.xml # mybatis的核心配置文件
mapper-locations: classpath:mybatis/mapper/*.xml # mybatis的mapper文件位置
# spring的配置
spring:
profiles: test
application:
name: springcloud-config -dept # 为该模块(微服务)起一个名字
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://localhost:3307/springclouddb02?useUnicode=true&characterEncoding=utf-8
username: root
password: 1234
# Eureka的配置:服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/ # 把服务注册到我们刚才配置的注册中心:localhost:7001/eureka
instance:
instance-id: springcloud-provider-dept8001 # 修改Eureka上的默认描述信息
# 完善监控信息配置
info:
app.name: msj-springcloud # 项目名字(当然没什么作用,不配也可以)
company.name: msj # 公司名字
3、为了与之前的项目有区别,我们新建一个微服务
springcloud-config-eureka-7001
a、导入依赖:把原来7001的依赖原封不动的拿过来并添加config的包
<dependencies>
<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>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
b、同时把7001中src复制一份
c、把application.yml中的内容清空,同时新建一个bootstrap.yml
bootstrap.yml
spring:
cloud:
config:
name: config-eureka
label: master
profile: dev
uri: http://localhost:3344
application.yml
spring:
application:
name: springcloud-config-eureka-7001
主启动类:也不需要修改,修改一下名字就可以了
package com.msj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
// 开启Eureka的服务:服务端的启动类,它可以接收别人注册进来
@EnableEurekaServer
public class EurekaConfigServer_7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaConfigServer_7001.class, args);
}
}
启动:3344,保证3344能够访问到拿到远程的配置文件
localhost:3344/master/config-eureka-dev.yml
启动:EurekaConfigServer_7001
给微服务成功在7001端口启动
可以访问7001端口的Eureka页面,并且可以看到7002和7003集群,但是这两个集群并不能访问,因为我们并没有启动它
4、新建一个服务端的:springcloud-config-dept-8001(为了区别)
a、导入依赖:与之前的8001的依赖一样并添加config的包
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Eureka依赖:完善监控信息 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--我们需要实体类:所以我们需要springcloud-api模块-->
<dependency>
<groupId>com.msj</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!-- 日志门面 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- spring test:父工程中到如果springboot dependencies,所以这里不用写版本,与父工程的版本一致:2.1.4.RELEASE -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<!-- spring web 启动器(也是在springboot dependencies中) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 可以使用jetty作为我们的服务器:虽然跟tomcat没什么区别 (springboot dependencies中也有) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<!-- 热部署工具(springboot dependencies中) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
b、src中的东西拷贝一份
c、清除application中的东西,新建bootstrap
bootstrap.yml
spring:
cloud:
config:
name: config-dept
label: master
profile: dev
uri: http://localhost:3344
application.yml
spring:
application:
name: springcloud-config-dept-8001
主启动类也不用改,启动:3344
启动:config-8001:
启动成功
注册信息:远程的config-dept.yml配置
注册中的信息:
我们发现config-8001中也配置application.name,但注册到注册中心后使用的还是远程的application.name,即远程的配置优先级更高。
测试连接数据库:localhost:8001/dept/get/2
数据也是从db01中取的,如果该为test,则会从db02中取
我们修改了远程配置文件,我们只需要在本地build(热部署)一下项目,就可以了。
七、回顾
自己关注学习官网:
搜索spring cloud alibaba
注解
# 支持链式写法注解:Lombok中的注解
@Accessors(chain = true)
# 类似于spring中的applicationContext.xml文件
@Configuration
# 只要我们加了@EnableEurekaClient注解,该服务就会被注册到Eureka的注册中心
@EnableEurekaClient
# 开启Eureka的服务:服务端的启动类,它可以接收别人注册进来
@EnableEurekaServer
# 开启服务发现:让别人可以看到我们注册了那些服务,以及服务的信息
@EnableDiscoveryClient
# 负载均衡:Ribbon
@LoadBalanced
# 服务启动时去加载我们自定义配置的Ribbon负载均衡策略
# @RibbonClient(name=服务名,cofiguration=自定义的负载均衡策略名)
@RibbonClient(name = "springcloud-provider-dept", configuration = MsjRule.class)
# 使用了@FeignClient这个注解,我们就可以直接调用这里的服务
# @FeignClient(vlaue=服务名)
@FeignClient
# 扫描指定的包
@ComponentScan("com.msj.springcloud")
# @HystrixCommand服务熔断,fallbackMethod指定服务出现异常时调用的方法
@HystrixCommand(fallbackMethod = "hystrixGet")
# 开启Hystrix服务:CircuitBreaker熔断器
@EnableCircuitBreaker
# 开启监控页面
@EnableHystrixDashboard
# 开启Zuul服务代理
@EnableZuulProxy
# 开启Config服务
@EnableConfigServer
思路
springCloud项目思路:
1、导入依赖
2、编写配置文件
3、开启这个功能:@EnabledXXXX
4、配置类
springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-eureka
1.4.6.RELEASE
com.msj
springcloud-api
1.0-SNAPSHOT
junit
junit
test
mysql
mysql-connector-java
com.alibaba
druid
ch.qos.logback
logback-core
org.mybatis.spring.boot
mybatis-spring-boot-starter
org.springframework.boot
spring-boot-test
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-jetty
<!-- 热部署工具(springboot dependencies中) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
```
b、src中的东西拷贝一份
c、清除application中的东西,新建bootstrap
bootstrap.yml
spring:
cloud:
config:
name: config-dept
label: master
profile: dev
uri: http://localhost:3344
application.yml
spring:
application:
name: springcloud-config-dept-8001
主启动类也不用改,启动:3344
启动:config-8001:
启动成功
[外链图片转存中…(img-w5lh4Dtm-1628496629415)]
注册信息:远程的config-dept.yml配置
[外链图片转存中…(img-PMrcAYE0-1628496629415)]
注册中的信息:
[外链图片转存中…(img-0eBewJxz-1628496629415)]
我们发现config-8001中也配置application.name,但注册到注册中心后使用的还是远程的application.name,即远程的配置优先级更高。
测试连接数据库:localhost:8001/dept/get/2
数据也是从db01中取的,如果该为test,则会从db02中取
[外链图片转存中…(img-cVwocgVk-1628496629416)]
我们修改了远程配置文件,我们只需要在本地build(热部署)一下项目,就可以了。
七、回顾
[外链图片转存中…(img-jypPiv7m-1628496629416)]
自己关注学习官网:
搜索spring cloud alibaba
注解
# 支持链式写法注解:Lombok中的注解
@Accessors(chain = true)
# 类似于spring中的applicationContext.xml文件
@Configuration
# 只要我们加了@EnableEurekaClient注解,该服务就会被注册到Eureka的注册中心
@EnableEurekaClient
# 开启Eureka的服务:服务端的启动类,它可以接收别人注册进来
@EnableEurekaServer
# 开启服务发现:让别人可以看到我们注册了那些服务,以及服务的信息
@EnableDiscoveryClient
# 负载均衡:Ribbon
@LoadBalanced
# 服务启动时去加载我们自定义配置的Ribbon负载均衡策略
# @RibbonClient(name=服务名,cofiguration=自定义的负载均衡策略名)
@RibbonClient(name = "springcloud-provider-dept", configuration = MsjRule.class)
# 使用了@FeignClient这个注解,我们就可以直接调用这里的服务
# @FeignClient(vlaue=服务名)
@FeignClient
# 扫描指定的包
@ComponentScan("com.msj.springcloud")
# @HystrixCommand服务熔断,fallbackMethod指定服务出现异常时调用的方法
@HystrixCommand(fallbackMethod = "hystrixGet")
# 开启Hystrix服务:CircuitBreaker熔断器
@EnableCircuitBreaker
# 开启监控页面
@EnableHystrixDashboard
# 开启Zuul服务代理
@EnableZuulProxy
# 开启Config服务
@EnableConfigServer
思路
springCloud项目思路:
1、导入依赖
2、编写配置文件
3、开启这个功能:@EnabledXXXX
4、配置类
视频地址:https://www.bilibili.com/video/BV1jJ411S7xr?p=1