springcloud
- 创建maven项目,添加依赖
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.9.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>springcloudtest01</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<delimiters>
<delimit>$</delimit>
</delimiters>
</configuration>
</plugin>
</plugins>
</build>
- 把src文件删除,创建module子工程,例如,cloud-api
在此项目中添加配置例如 lombok,也可不加
- 将api项目打包成jar,步骤如下:
- 创建项目cloud-provider-dept-8001,同上一样,module
父工程查看,多了一个子工程:
8001中的pom文件添加:
<dependencies>
<!-- 引入自己定义的api通用包,可以使用Dept部门Entity -->
<dependency>
<groupId>com.dw</groupId>
<artifactId>cloud-api</artifactId>
<version>${project.version}</version>
</dependency>
<!-- actuator监控信息完善 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 将微服务provider侧注册进eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<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>
</dependency>
<!-- 修改后立即生效,热部署 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
8001项目中编写yml文件:
server:
port: 8001
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml
type-aliases-package: com.dw.entity
mapper-locations:
- classpath:mybatis/mapper/*.xml
spring:
application:
name: cloud-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/clouddb01?useUnicode=true&characterEncoding=utf-8&userSSL=false&serverTimezone=GMT%2B8
username: root
password: root
dbcp2:
min-idle: 5
initial-size: 5
max-total: 5
max-wait-millis: 200
resources包下创建mybatis包,该包下创建mybatis.cfg.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>
创建数据库:
drop database if exists clouddb01;
create DATABASE clouddb01 CHARACTER SET utf8;
use clouddb01;
create TABLE dept(
deptno BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
dname VARCHAR(60),
db_source VARCHAR(60)
);
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());
src/main/java下创建包com.dw.dao,接口DeptDao,service,impl,controller层 写好以及mybaits文件下创建mapper DeptDao.xml映射文件:
- 创建主启动类,注意位置
- 运行项目,输入 localhost:8001/dept/list
目前为止,微服务的生产者已经创建成功,接下来需要创建微服务中的消费者
-
创建module,cloud-consumer-dept-80,pom添加配置信息
<dependencies> <dependency><!-- 自己定义的api --> <groupId>com.dw</groupId> <artifactId>cloud-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- Ribbon相关 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 修改后立即生效,热部署 --> <dependency> <groupId>org.springframework</groupId> <artifactId>springloaded</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> </dependencies>
端口80 设置:
在src/main/java下创建包com.dw.cfgbeans,并创建ConfigBean
创建controller,并创建主启动:
启动8001 项目和80项目,输入 http://localhost/consumer/dept/get/1
运行成功,说明生成者和消费者都可以正常运行。
-
Eureaka 构建步骤
什么是Eureaka?
- Eureka是Netflix的一个子模块,也是核心模块之一。Eureka是一个基于rest的服务,用于定位服务,以实现云端中间层服务发现的故障转移。服务注册与发现对于微服务架构来说是非常重要的,有了服务发现与注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件了。功能类似于dubbo的注册中心,比如zookeeper.
Eureka包含两个组件:Eureka Server 和Eureka Client
Eureka Server提供服务注册服务
各个节点启动后,会在Eureka Server中进行注册,这样的EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可用在界面中直观的看到。
EurekaClient是一个java客户端,用于简化Eureka Server的交互,客户段同时也具备一个内置的、使用沦陷负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)。
- Eureka Server 提供服务注册和发现
- Service Provider 服务提供方将自身服务注册到Eureka,从而使服务消费方能够找到
- Service Consumer 服务消费方,从Eureka获取注册的服务列表,从而能够消费服务
创建项目module,cloud-eureka-7001,配置pom:
<dependencies>
<!--eureka-server服务端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<!-- 修改后立即生效,热部署 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
配置当前项目application.yml:
server:
port: 7001
eureka:
instance:
hostname: localhost #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址(单机)。
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
创建主启动类,并加上@EnableEurekaServer注解,表示当前项目是服务端:
测试,输入 localhost:7001/
出现此页面,7001项目正常运行。
目前,eureka已经创建完成
-
接下来需要把服务的提供者,注册到Eureka
修改8001 项目,pom文件中添加,看是否有
<!-- 将微服务provider侧注册进eureka --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
修改8001 中的yml文件,添加:
eureka:
client: #客户端注册进eureka服务列表内
service-url:
defaultZone: http://localhost:7001/eureka
启动类加入EnableEurekaClient注解:
测试,先启动7001,然后启动8001,输入http://localhost:7001/
application中已经有一个应用了,说明8001项目已经注册到了eureka中心。
服务名称修改:
8001项目中的application.yml加上配置:
instance:
instance-id: microservicecloud-dept
prefer-ip-address: true #访问路径可用显示ip
那么在eureka注册中心可见如下信息:
这样相当于给8001服务起了个别名
那么 加上 ip显示提示 true时,鼠标放在服务名字上,左下角显示当前服务所在ip地址:
继续修改8001项目中的application.yml文件,添加如下:
info:
app.name: dw-microservicecloud
company.name: www.dw.com
build.artifactId: $project.artifactId$
build.version: $project.version$
那么,点击eureka中的服务链接,显示内容如下:
目前位置,服务已经注册到了eureka中。
8001 yml配置如下:
server:
port: 8001
mybatis:
config-location: classpath:mybatis/mybatis.cfg.xml
type-aliases-package: com.dw.entity
mapper-locations:
- classpath:mybatis/mapper/*.xml
spring:
application:
name: cloud-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/clouddb01?useUnicode=true&characterEncoding=utf-8&userSSL=false&serverTimezone=GMT%2B8
username: root
password: root
dbcp2:
min-idle: 5
initial-size: 5
max-total: 5
max-wait-millis: 200
eureka:
client: #客户端注册进eureka服务列表内
service-url:
defaultZone: http://localhost:7001/eureka
instance:
instance-id: microservicecloud-dept
prefer-ip-address: true #访问路径可用显示ip
info:
app.name: dw-microservicecloud
company.name: www.dw.com
build.artifactId: $project.artifactId$
build.version: $project.version$
- 服务已经注册到了Eureka中,那么80项目应该从Eureka中发现服务
在8001项目中添加服务发现接口,在controller包的DeptController中添加代码:
这里注意导包:import org.springframework.cloud.client.discovery.DiscoveryClient;
@Resource
DiscoveryClient client;
/*
* 测试 用来查看eureka中所有可见的服务 开发中不需要写这个方法
*
* getInstances("") 其中引用 application的名字 例如 我这里时CLOUD-DEPT
*/
@RequestMapping(value = "/dept/discovery",method = RequestMethod.GET)
public Object discovery() {
List<String> list =client.getServices();
System.out.println("====="+list);
List<ServiceInstance> serList = client.getInstances("CLOUD-DEPT");
for(ServiceInstance element:serList) {
System.out.println(element.getServiceId()+"\t"+element.getHost());
}
return this.client;
}
在 8001 主启动类 中添加注解 @EnableDiscoveryCilent 允许被发现
启动7001 项目 和 8001项目,输入http://localhost:8001/dept/discovery
这里显示的时已经注册到eureka中的服务,同时,控制台也有打印信息:
这里时输出已经注册到eureka中的服务
以上结束8001已经注册服务,那么80项目怎么才可用发现8001服务,接着继续
-
修改80 工程
找到controller中DeptController_Consumer
添加:
@RequestMapping(value = "/consumer/dept/discovery") public Object discovery() { return restTemplate.getForObject(REST_URL_PREFIX+"/dept/discovery", Object.class); }
启动 80 项目,此时 7001 8001 80 都启动,输入http://localhost/consumer/dept/discovery
实现:
浏览器----->80项目----->restTemplate远程调用----->8001----->从eureka中获取所有已注册的项目信息
-
Eureka集群搭建
-
新建工程7002 和7003
-
把7001 项目中内容 copy到 7002 和7003
-
修改7002 和 7003 主启动类
-
修改映射配置信息
-
- 修改7001 7002 7003 yml
启动 7001 7002 7003 8001 输入http://eureka7001.com:7001/
输入http://eureka7002.com:7002/
输入http://eureka7003.com:7003/
现在,集群搭建成功。
-
Ribbon 负载均衡
Ribbon是什么?
spring cloud Ribbon 是基于Netflix Ribbon实现的一套客户端 负载均衡工具。
简单的说,Ribbon是NetFlix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将NetFlix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善额配置项 如连接超时,重试等。就是在配置文件中列出Load Balancer(简称LB)后面的所有机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。重点是客户端的 负载均衡
以上,只有80项目是客户端(消费者),所以要修改80项目,加上负载均衡
修改80的pom文件,添加Ribbon相关的依赖
修改80的yml文件,没有经过eureka,现在要经过eureka访问8001:
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
这样客户端就知道注册中心的位置了。
修改80项目的ConfigBean,添加注解即可:@LoadBalanced
这样 客户端就可以通过负载均衡来访问服务了,但是还没有配置完。
80 项目的主启动类加上注解:@EnableEurekaClient
修改80 项目的controller:要与注册中心的服务名相同
这样,客户端就通过注册中心找到服务端进行功能的调用。
启动7001 7002 7003,启动8001 ,启动客户端80:
输入http://localhost/consumer/dept/get/1
80项目(客户端)通过eureka访问 8001(服务端)提供的功能
浏览器输入url----->80客户端-----> 根据 eureka的 CLOUD-DEPT 找到服务,8001是根据此名 注册到了eureka标志
80在eureka中根据它找到8001,调用8001服务。
- Ribbon负载均衡
Ribbon在工作时分成两步:
第一步先选择EurekaServer,它优先选择在同一个区域内负责较少的server
第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址
上图提供了三个服务实例,目前我们只有一个服务实例 8001,还不算真正意义上的客户端的负载均衡。
例如 在卖煎饼果子,现在只有张三一个服务人员在卖,客户没有办法选择去哪排队。
所以,现在要建服务端的集群,即8002 8003,加上8001,一个三个实例。
步骤:
-
新建8002和8003
-
8001pom yml 包 各种全部 copy到 8002 8003,并修改其启动类名字,yml各自对应各自的端口号
-
以及创建数据库,修改各自连接数据库的 表(这里是为了到时候区分,负载均衡访问效果做了此操作,往下看)
创建数据库:2 3
DROP database if EXISTS clouddb02;
CREATE DATABASE clouddb02 character set utf8;
USE clouddb02;
CREATE TABLE dept
(
deptno BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
dname VARCHAR(60),
db_source VARCHAR(60)
);
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());
DROP database if EXISTS clouddb03;
CREATE DATABASE clouddb03 character set utf8;
USE clouddb03;
CREATE TABLE dept
(
deptno BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
dname VARCHAR(60),
db_source VARCHAR(60)
);
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());
测试:
先启动eureka 集群 3个,7001 7002 7003
然后启动 8001 8002 8003,可先输入http://localhost:8001/dept/list,http://localhost:8002/dept/list ,http://localhost:8003/dept/list ,查看能否查出数据,说明三个服务端正常访问
启动80,输入http://localhost/consumer/dept/list,显示结果:
第一次访问:
第二次刷新:
第三次刷新:
那么,这样就达到了效果,默认是轮询规则。
- Ribbon算法:
默认是轮询,改变算法,例如使用随机算法:
打开80项目中的配置类,ConfigBean,添加如下:
// RandomRule 随机
@Bean
public IRule myRule() {
return new RandomRule();
}
达到随机访问。
Feign 是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单,它的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器和解码器。Spring cloud 对Feign进行了封装,使其支持了Spring mvc标准注解 和HttpMessageConverters.Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
步骤如下:这里文字描述,不截图了
此时可在80 项目中改,也可新建一个 做区分,掌握其中关键点即可
-
例如,80项目所有都copy到新的fegin项目目录下
-
此fegin 的pom中添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency>
-
修改api,api工程新建DeptClientService接口,并新增注解 @FeignClient
例如:@FeignClient(value = " CLOUD-DEPT")
-
然后写上接口方法,同8001
-
然后api 依照之前方式打包成jar
-
回到 feign工程,修改controller,注入DeptClientService ,该方法调用即可,之前是restTemplate
// 调用api项目中的service @Autowired DeptClientService deptClientService; @RequestMapping(value = "/consumer/dept/list") public List<Dept> list() { return this.deptClientService.list(); }
-
在 fegin项目启动类加上注解:
@EnableFeignClients(basePackages = {“com.dw”})
@ComponentScan(“com.dw”)
-
测试 :
运行7001-7003,8001-8003,最后启动regin,输入http://localhost/consumer/dept/list
能正常访问,因为已封装了Ribbon,认采用轮询。
- Hystrix断路器
服务雪崩
多个微服务之间相互调用的时候,假设微服务A调用微服务B,微服务B调用微服务C.C又调用其他的服务,这就是“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的服务器资源,进而引起系统崩溃,这就是“雪崩效应”。
熔断机制是应对服务雪崩的一种微服务链路保护机制。当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回“错误”的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路。在springCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的情况。当失败的调用到一定阈值,默认是5秒内20此调用失败会启动熔断机制。
步骤如下:参考主要
之前的8001举例,
-
添加pom依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency>
-
修改controller,这里为了模拟出错 然后返回信息效果写了如下代码:
@RequestMapping(value = "/dept/get/{deptno}",method = RequestMethod.GET) @HystrixCommand(fallbackMethod = "processHystrix_Get") public Dept getById(@PathVariable("deptno")Long deptno) { Dept dept = deptService.findById(deptno); // 故意出错 if(null == dept) { throw new RuntimeException("该id没有对应的信息"); } return dept; } public Dept processHystrix_Get(@PathVariable("deptno")Long deptno) { Dept dept = new Dept(); dept.setDeptno(deptno); dept.setDname("没有对应的信息"); dept.setDb_source("no this db"); return dept; }
-
然后再hystrix主启动类添加 注解**@EnableCircuitBreaker**
-
测试,启动三个eureka,启动该hystrix项目,再启动80
输入http://localhost/consumer/dept/get/112,一个不存在的id
显示:
说明熔断机制起了作用。