具体关于SpringCloud介绍请参考其他文章,下面主要讲解SpringCloud搭建微服务的步骤,其中用到了Eurake、Ribbon、Feign、Hystrix、Zuul、Config技术。
本次案例主要分为以下几个微服务
本次案例用的工具版本如下
eclipse : Mars
JDK : 1.8
SpringCloud : Dalston
SpringBoot : 1.5.9
首先建立一个microservice-parent总工程,总工程中并不做什么业务逻辑,总工程主要定义一个POM文件,将后续各个微服公用的一些jar包在总工程的pom中进行导入。为方便起见,建立一个working set用于归类所有的微服务,命名为springcloudset。然后建立总工程
New -> Maven -> Maven Project
注意,Packaging模式要选择pom模式,不要选择jar模式。
POM文件如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
pom
UTF-8
1.8
1.8
4.12
1.2.17
1.16.18
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR1
pom
import
org.springframework.boot
spring-boot-dependencies
1.5.9.RELEASE
pom
import
mysql
mysql-connector-java
5.1.46
com.alibaba
druid
1.1.0
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.0
ch.qos.logback
logback-core
1.2.3
junit
junit
${junit.version}
test
log4j
log4j
${log4j.version}
然后建立一个microservice-com的公共模块,用于提供后续微服需要的公共的东西。创建好公共模块,其它微服务需要的话,不用再创建,直接引用该模块即可。
1、下面在总工程下,即microservice-parent下创建第一个微服务microservice-com模块。
New -> Maven -> Maven Module
注意,Packaging不要选择pom模式,而应该选择ja模式,后续的微服都是如此。
pom.xml内容为:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-com
org.projectlombok
lombok
2、然后在该模块中建立一个User的bean,以供其它微服务进行调用。
package com.lzj.springcloud.entity;
public class User {
private int id;
private String name;
private int age;
public User() {
super();
}
public User(int id, String name, int age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
3、创建完公共微服后,执行mvn clean install进行装载。
创建完microservice-com模块后,在microservice-parent父工程中的pom.xml文件中增加了
microservice-com
1、与建立microservice-com服务类似,建立一个microservice-provider微服务
2、该服务用于提供直接操作数据库user表。首先在pom中配置依赖的包,操作数据库需要的数据库驱动、数据源等。
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-provider
com.lzj.springcloud
microservice-com
${project.version}
junit
junit
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-starter-jetty
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
在microservice-provider微服找那个需要引用microservice-com微服中User bean,所以在pom文件中引入了microservice-com的依赖。
com.lzj.springcloud
microservice-com
${project.version}
3、在microservice-provider需要操作数据库,在application.yml中配置mybatis和数据源如下:
server:
port: 8002
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
4、在src/main/resources目录下创建mybatis文件夹后新建mybatis.cfg.xml文件,内容如下:
5、创建Dao接口,用于操作user表的接口
package com.lzj.springcloud.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.lzj.springcloud.entity.User;
@Mapper
public interface UserDao {
public boolean addUser(User user);
public User getUser(int id);
public List getUsers();
}
6、在src/main/resources/mybatis目录下创建mapper目录,并在mapper目录下创建UserMapper.xml文件,内容如下:
insert into user(NAME, AGE) values(#{name}, #{age})
7、创建UserService服务接口
package com.lzj.springcloud.service;
import java.util.List;
import com.lzj.springcloud.entity.User;
public interface UserService {
public boolean addUser(User user);
public User getUser(int id);
public List getUsers();
}
8、创建UserServiceImpl接口的实现
package com.lzj.springcloud.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.lzj.springcloud.dao.UserDao;
import com.lzj.springcloud.entity.User;
import com.lzj.springcloud.service.UserService;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public boolean addUser(User user) {
boolean flag;
flag = userDao.addUser(user);
return flag;
}
@Override
public User getUser(int id) {
User user = userDao.getUser(id);
return user;
}
@Override
public List getUsers() {
List users = userDao.getUsers();
return users;
}
}
9、创建Controller层,用于相应REST请求
package com.lzj.springcloud.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.lzj.springcloud.entity.User;
import com.lzj.springcloud.service.UserService;
@RestController
public class UserController {
@Autowired
private UserService service;
@RequestMapping(value="/add", method=RequestMethod.POST)
public boolean addUser(@RequestBody User user){
boolean flag = service.addUser(user);
return flag;
}
@RequestMapping(value="/get/{id}", method=RequestMethod.GET)
public User getUser(@PathVariable("id") int id){
User user = service.getUser(id);
return user;
}
@RequestMapping(value="/getUser/list", method=RequestMethod.GET)
public List getUsers(){
List users = service.getUsers();
return users;
}
}
10、创建microservice-provider的启动类
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
11、测试microservice-provider微服
启动microservice-provider服务,然后在浏览器中发送请求 http://localhost:8002/getUser/list,相应结果如下
创建并配置完microservice-provider后,在microservice-parent的pom的文件中,module标签如下
microservice-com
microservice-provider
microservice-consumer
microservice-consumer服务用于请求microservice-provicer
创建与microservice-com类似,在microservice-parent下创建microservice-consumer微服。
1、pom配置如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-consumer
com.lzj.springcloud
microservice-com
${project.version}
org.springframework.boot
spring-boot-starter-web
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
热部署即为,工程每次修改完后,会重新部署。
2、application.yml配置文件如下:
配置访问端口
server:
port: 8003
3、配置RestTemplate的bean
RestTemplate用于模拟发送REST的客户端请求
package com.lzj.springcloud.configbean;
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();
}
}
3、配置controller层
package com.lzj.springcloud.controller;
import java.util.List;
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 com.lzj.springcloud.entity.User;
@RestController
public class UserConsumerController {
private static String REST_URL_PREFIX = "http://localhost:8002";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value="/consumer/add")
public boolean addUser(User user){
Boolean flag = restTemplate.postForObject(REST_URL_PREFIX + "/add", user, Boolean.class);
return flag;
}
@RequestMapping(value="/consumer/get/{id}")
public User get(@PathVariable("id") int id){
User user = restTemplate.getForObject(REST_URL_PREFIX + "/get" + "/id", User.class);
return user;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@RequestMapping(value="/consumer/list")
public List getList(){
List list = restTemplate.getForObject(REST_URL_PREFIX + "/getUser/list", List.class);
return list;
}
}
4、创建启动类
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
5、测试
在浏览器中发送 http://localhost:8003/consumer/add?name=lzj5&age=35
相应如下:
发送请求后,在数据库user表中插入了一条数据
至此需要的微服务已经全部创建完毕,在microservice-parent的pom文件中可以看到总工程下有三个微服务。
microservice-com
microservice-provider
microservice-consumer
eureka微服务用于注册和发现服务。
首先建立一个依赖于microservice-parent的microservice-eurake1工程,与上面建立方法一样。
1、pom文件配置如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-eurake1
org.springframework.cloud
spring-cloud-netflix-eureka-server
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
如果出现“Failed to read artifact descriptor for org.springframework.cloud:spring-cloud-st”问题,请参考解决“Failed to read artifact descriptor for org.springframework.cloud:spring-cloud-st”。
2、application.yml文件配置如下
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
port: 9001
3、创建启动类
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
注意,一定不要忘记@EnableEurekaServer
注解,该注解用于激活eureka的服务端。
现在可以启动microservice-eureka微服务了
正确创建eureka微服务后,启动页面如上图所示,只是还没有微服务注册进来。
5、把microservice-provider微服务注册进microservice-eureka服务中
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-config
microservice-provider中的pom文件如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-provider
com.lzj.springcloud
microservice-com
${project.version}
junit
junit
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-starter-jetty
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-config
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
eureka:
client:
service-url:
defaultZone: http://localhost:9001/eureka
microservice-provider中application.yml配置文件如下:
server:
port: 8002
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
defaultZone: http://localhost:9001/eureka
@EnableEurekaClient
注解。表示microservice-provider微服务启动时就启动eureka的客户端,该客户端自动的把microservice-provider服务注册进microservice-eureka1中。package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
6、测试microservice-provider服务已经注册进了microservice-eureka1服务。
启动microservice-eureka1微服务,然后再启动microservice-provider微服务,
在浏览器中请求http://localhost:9001/
,反馈网页如下:
可见网页中Application那一栏多了一个MICROSERVICECLOUD-PROVIDER服务,此处显示的服务名字是在microservice-provider服务中的application.yml文件中配置的
spring:
application:
name: microservicecloud-provider #微服务的名字
为追求完美者,下面完善注册服务的信息,如果只是想达到功能,7、8、9可以不用看。
7、修改注册服务的主机名称
我们看到在上述的测试中,注册进eureka中的MICROSERVICE-PROVIDER服务,在状态一栏里面显示的是主机名称:服务名称的形式,主机默认是我本人主机名称,如下图所示。
下面修改microservicecloud-provider中的application.yml文件,关于eureka的部分改为如下:
eureka:
client:
service-url:
defaultZone: http://localhost:9001/eureka
instance:
instance-id: microservicecloud-provider8002
重启服务,状态栏中显示的即为配置的instance-id名称。
8、eureka中显示注册微服务的ip信息
当鼠标放在注册微服务的链接上,左下角显示注册微服务的ip信息,如下所示
左下角显示了microservice-provider微服务的ip信息,要想达到此效果,只需要microservice-provider服务中application.yml中eureka的配置改为如下
eureka:
client:
service-url:
defaultZone: http://localhost:9001/eureka
instance:
instance-id: microservicecloud-provider8002 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
9、点击注册微服务链接,显示微服务的info信息
当点击上图划红线的链接处,显示下面erro信息
下面定制当点击链接时,显示指定信息
首先在microservice-parent的pom中增加
的配置,pom如下所示
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
pom
UTF-8
1.8
1.8
4.12
1.2.17
1.16.18
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR1
pom
import
org.springframework.boot
spring-boot-dependencies
1.5.9.RELEASE
pom
import
mysql
mysql-connector-java
5.1.46
com.alibaba
druid
1.1.0
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.0
ch.qos.logback
logback-core
1.2.3
junit
junit
${junit.version}
test
log4j
log4j
${log4j.version}
microservicecloud
src/main/resources
true
org.apache.maven.plugins
maven-resources-plugin
$
microservice-com
microservice-provider
microservice-consumer
microservice-eurake1
然后再microservice-provider的服务中增加如下依赖
org.springframework.boot
spring-boot-starter-actuator
最后在microservice-provider中application.yml中配置显示的info信息
server:
port: 8002
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
defaultZone: http://localhost:9001/eureka
instance:
instance-id: microservicecloud-provider8002 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservicecloud-provider
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
重启microservice-eurake1和microservicecloud-provider微服务,点击注册的微服务microservicecloud-provider的链接,显示如下页面
10、eureka的自我保护
过段时间,微服务没有用时,会出现下面红色的警告信息,即为eureka的自我保护。
默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,EurekaServer就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。
在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。当它收到的心跳数重新恢复到阈值以上时,该Eureka Server节点就会自动退出自我保护模式。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
在Spring Cloud中,可以使用eureka.server.enable-self-preservation = false 禁用自我保护模式。
第五步只是建立了一个eureka的微服务,如果当这个微服务down掉了,那么其它微服务就不能被注册和发现,整个系统就会down掉,所以下面建立多个eureka微服务,配置eureka集群,需要注册的微服务要注册到所有的eureka的微服务中,即注册到整个集群上,当一个eureka的微服务挂掉了,其它的eureka微服可以继续工作。
首先修改host文件,添加127.0.0.1的多个域名映射,方便后面模拟根据多个地址进行注册服务。
分别复制microservice-eurake1工程为microservice-eurake2和microservice-eurake3,
把microservice-eurake2服务中pom文件中的artifactId改为(因为之前该微服是复制过来的,如果是创建过来的就不用修改)
把microservice-eurake3服务中pom文件中的artifactId改为
修改microservice-eureka1的application.yml文件为
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/
server:
port: 9001
修改microservice-eureka2的application.yml文件为
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9003.com:9003/eureka/
server:
port: 9002
修改microservice-eureka3的application.yml文件为
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9002.com:9002/eureka/
server:
port: 9003
然后修改microservice-provider微服务application.yml中的defaultZone配置,把该微服务同时注册到三个eureka微服务中,即eureka集群中。
server:
port: 8002
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
#defaultZone: http://localhost:9001/eureka
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9003.com:9003/eureka/
instance:
instance-id: microservicecloud-provider8002 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservicecloud-provider
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
测试:运行microservice-eurake1、microservice-eurake2、microservice-eurake3、microservice-provider
从浏览器中访问eureka1的管理端http://localhost:9001/
可以看出,两个划横线的为microservice-eurake1的备份,microservice-provider为注册上来的服务。eureka的3个微服务组成了一个服务注册的集群,只有有一个能工作,能保证业务的执行。
Ribbon的负载均衡是应用于客户端的,即调用一方的,在本案例中就是应用于microservice-consumer微服务的,下面就对microservice-consumer微服务使用负载均衡。
1、microservice-consumer进行Ribbon配置
下面进行修改microservice-consumer微服务的配置
pom文件中增加Ribbon需要的依赖
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-ribbon
org.springframework.cloud
spring-cloud-starter-config
修改application.yml文件,增加eureka的服务注册功能,修改后的配置如下
server:
port: 8003
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9001.com:9001/eureka/
负载均衡实际是根据RestTemplate根据均衡算法进行调度不同地址上的同一个微服务的部署。所以修改ConfigBean,在RestTemplate上加@LoadBalanced注解。
@Configuration
public class ConfigBean {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
把microservice-consumer也注册到eureka服务中,需要在启动类上加@EnableEurekaClient注解
@SpringBootApplication
@EnableEurekaClient
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
修改controller层,不再通过地址和端口调取其它微服的应用,而是根据微服务的名来调取应用。修改后如下
@RestController
public class UserConsumerController {
// private static String REST_URL_PREFIX = "http://localhost:8002";
/*直接根据微服务名调用,而不再是根据地址和端口了,运用了eureka的发现功能*/
private static String REST_URL_PREFIX = "http://microservicecloud-provider";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value="/consumer/add")
public boolean addUser(User user){
Boolean flag = restTemplate.postForObject(REST_URL_PREFIX + "/add", user, Boolean.class);
return flag;
}
@RequestMapping(value="/consumer/get/{id}")
public User get(@PathVariable("id") int id){
User user = restTemplate.getForObject(REST_URL_PREFIX + "/get/" + id, User.class);
return user;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@RequestMapping(value="/consumer/list")
public List getList(){
List list = restTemplate.getForObject(REST_URL_PREFIX + "/getUser/list", List.class);
return list;
}
}
只是修改了private static String REST_URL_PREFIX = “http://microservicecloud-provider”;
测试:修改上述配置后,下面进行测试,启动microservice-eurake1、microservice-eurake2、microservice-eurake3微服务,然后启动microservice-provider提供者微服务,最后启动带Ribbon负载均衡配置的消费者(客户端)微服务microservice-consumer。从浏览器中发送请求http://localhost:8003/consumer/list
,响应页面如下
说明可以从消费端通过微服务名找到提供者微服务,然后进行调用。
2、Ribbon负载均衡
上面在消费端microservice-consumer配置好了Ribbon,提供者微服务目前只有一个。为减小提供者微服务的压力,现在再部署两个提供者微服务,当客户端发送请求时,由三个微服务中的一个随机的响应请求。
复制microservice-provider工程生成microservice-provider2和microservice-provider3
修改microservice-provider2的启动类为ProviderApplication2
修改microservice-provider3的启动类为ProviderApplication3
修改microservice-provider2的application.yml配置为
server:
port: 8003
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
#defaultZone: http://localhost:9001/eureka
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9003.com:9003/eureka/
instance:
instance-id: microservicecloud-provider8003 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservicecloud-provider
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
修改microservice-provider3的配置为
server:
port: 8004
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
#defaultZone: http://localhost:9001/eureka
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9003.com:9003/eureka/
instance:
instance-id: microservicecloud-provider8004 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservicecloud-provider
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
只是分别修改了server.port和eureka.instance.instance-id。
为方便观察哪一个提供者微服务响应的客户端请求,在提供者微服中分别打印两条日志,
microservice-provider微服务中UserServiceImpl类中getUser方法修改为如下:
@Override
public User getUser(int id) {
User user = userDao.getUser(id);
System.out.println("microservice-provider微服务在响应客户端请求……");
System.out.println("user : " + user);
return user;
}
microservice-provider2微服务中UserServiceImpl类中getUser方法修改为如下:
@Override
public User getUser(int id) {
User user = userDao.getUser(id);
System.out.println("microservice-provider2微服务在响应客户端请求……");
System.out.println("user : " + user);
return user;
}
microservice-provider3微服务中UserServiceImpl类中getUser方法修改为如下:
@Override
public User getUser(int id) {
User user = userDao.getUser(id);
System.out.println("microservice-provider3微服务在响应客户端请求……");
System.out.println("user : " + user);
return user;
}
测试:启动3个eureka的微服务集群,然后启动3个上述提供者微服务,最后启动消费者微服务microservice-consumer,分别从前端发起http://localhost:7001/consumer/get/{id}
多笔请求,id用数字代替,发现分别在三个提供者服务的console中输出如下内容:
microservice-provider3微服务在响应客户端请求……
user : User [id=1, name=lzj1, age=20]
microservice-provider3微服务在响应客户端请求……
user : User [id=2, name=lzj2, age=24]
microservice-provider微服务在响应客户端请求……
user : User [id=4, name=lzj4, age=30]
microservice-provider2微服务在响应客户端请求……
user : User [id=3, name=lzj3, age=26]
……
3个提供者微服务随机的响应客户端请求。
3、通过Ribbon的核心组件IRule定义查找消费端调用提供端微服务的策略
如没有指定轮询策略,默认是消费端随机调用提供端微服的策略,下面指定轮询调用策略。只需要在microservice-consumer中的ConfigBean类添加如下声明:
@Bean
public IRule myRule(){
return new RoundRobinRule(); //轮询策略
}
重新启动3个eureka微服务和3个提供者微服务,最后启动消费者微服务,重新测试,可知消费者微服务是轮询调用提供者的3个微服务的。
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。Feign是对Ribbon的包装,Feign集成了Ribbon。
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
Feign既然是对Ribbon的包装,那么Feign也是用在客户端的,即消费端的。下面建立集成Feign的消费端
复制microservice-consumer工程为microservice-consumer-feign
修改microservice-consumer-feign启动类的名字为FeignConsumerApplication;
microservice-consumer-feign的pom文件中增加对Feign的依赖:
org.springframework.cloud
spring-cloud-starter-feign
创建ConsumerService接口,用于包装microservicecloud-provider微服务,以后要调用microservicecloud-provider服务中的方法,只需要调用接口中对应的方法即可:
package com.lzj.springcloud.service;
import java.util.List;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.lzj.springcloud.entity.User;
/*以后调用microservicecloud-provider微服务中的方法,只需要调用下面对应的接口既可以了*/
@FeignClient(value="microservicecloud-provider")
public interface ConsumerService {
/*调用接口中的get方法,即可以向microservicecloud-provider微服务发送/get/{id}请求*/
@RequestMapping(value="/get/{id}", method=RequestMethod.GET)
public User get(@PathVariable("id") int id);
@RequestMapping(value="/add", method=RequestMethod.POST)
public boolean add(User user);
@RequestMapping(value="/getUser/list", method=RequestMethod.GET)
public List getAll();
}
修改microservice-consumer-feign中的controller层为
package com.lzj.springcloud.controller;
import java.util.List;
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 com.lzj.springcloud.entity.User;
import com.lzj.springcloud.service.ConsumerService;
@RestController
public class UserConsumerController {
// private static String REST_URL_PREFIX = "http://localhost:8002";
/*直接根据微服务名调用,而不再是根据地址和端口了,运用了eureka的发现功能*/
// private static String REST_URL_PREFIX = "http://microservicecloud-provider";
// @Autowired
// private RestTemplate restTemplate;
@Autowired
private ConsumerService service;
@RequestMapping(value="/consumer/add")
public boolean addUser(User user){
Boolean flag = service.add(user);
return flag;
}
@RequestMapping(value="/consumer/get/{id}")
public User get(@PathVariable("id") int id){
User user = service.get(id);
return user;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@RequestMapping(value="/consumer/list")
public List getList(){
List list = service.getAll();
return list;
}
}
修改启动类FeignConsumerApplication,在启动类上加启用Feign的注解:
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages="com.lzj.springcloud.service")
public class FeignConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApplication.class, args);
}
}
测试,启动microservice-eurake1、microservice-eurake2、microservice-eurake3集群,然后启动microservice-provider、microservice-provider2、microservice-provider3集群,最后启动microservice-consumer-feign微服务。
从浏览器中向消费端发送请求http://localhost:7001/consumer/get/2
,响应页面如下
Feign通过接口ConsumerService中的get方法调用Rest服务(之前是通过Ribbon+RestTemplate),并在eureka微服务中查找microservicecloud-provider服务,找到后,把请求http://localhost:7001/consumer/get/2
发送到microservicecloud-provider微服务。
如果一个请求需要调起多个服务时,其中一个服务不通或失败,当大量请求发生时,会导致请求延时和资源浪费。Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
Hystrix可用于服务熔断、服务降级、服务限流等作用。
1、服务熔断
当某个服务出现异常时,熔断该服务,快速返回指定的错误信息,当服务正常时,恢复熔断。
复制microservice-provider工程为microservice-provider-hystrix;
修改microservice-provider-hystrix的启动类为HystrixProviderApplication;
pom文件中添加hystrix的依赖
org.springframework.cloud
spring-cloud-starter-hystrix
application.yml配置如下:
server:
port: 8005
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
#defaultZone: http://localhost:9001/eureka
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9003.com:9003/eureka/
instance:
instance-id: microservicecloud-provider-hystrix #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservicecloud-provider
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
只修改了server.port和eureka.instance.instance-id;
修改UserController内容为:
package com.lzj.springcloud.controller;
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.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.lzj.springcloud.entity.User;
import com.lzj.springcloud.service.UserService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
@RestController
public class UserController {
@Autowired
private UserService service;
@RequestMapping(value="/get/{id}", method=RequestMethod.GET)
@HystrixCommand(fallbackMethod="hystrixGetUser") //一旦服务调用失败,就调用hystrixGetUser方法
public User getUser(@PathVariable("id") int id){
User user = service.getUser(id);
if(user == null){
throw new RuntimeException("不存在id=" + id + "对应的用户信息");
}
return user;
}
public User hystrixGetUser(@PathVariable("id") int id){
User user = new User(id, "不存在该用户", 0);
return user;
}
}
在启动类HystrixProviderApplication上添加注解@EnableCircuitBreaker;
启动microservice-eurake1、microservice-eurake2、microservice-eurake3服务,然后启动microservice-provider-hystrix服务,然后启动microservice-consumer-feign服务。
测试:从浏览器中发送请求:http://localhost:7001/consumer/get/18
,响应如下:
说明熔断起作用了,调用服务失败,返回了熔断指定的错误信息。
2、服务降级
在一个分布式系统中,当访问高峰期或资源有限时,需要关掉某个服务,若有请求访问该服务,不能因为系统服务关掉了,就一直中断在该调用服务处,这时就需要请求返回指定的错误信息。例如在分布式系统中有A、B两个服务,因为资源有限,需要关掉B服务,A服务在调用B服务时,没有调通,此时A返回指定的错误信息,注意不是在B服务端返回的,是A客户端返回的错误信息。看示例
复制microservice-consumer-feign服务为microservice-consumer-feign-hystrix;
microservice-consumer-feign-hystrix的pom文件中添加依赖:
org.springframework.cloud
spring-cloud-starter-hystrix
microservice-consumer-feign-hystrix服务中新建实现FallbackFactory的ConsumerServiceFallbackFactory类,在类上添加@Component,并传入ConsumerService接口,当调用ConsumerService中对应的方法失败后,自动调用ConsumerServiceFallbackFactory 中对应实现的ConsumerService方法,并在对应方法中定制调用服务失败后显示的错误信息。
package com.lzj.springcloud.service;
import java.util.List;
import org.springframework.stereotype.Component;
import com.lzj.springcloud.entity.User;
import feign.hystrix.FallbackFactory;
@Component
public class ConsumerServiceFallbackFactory implements FallbackFactory {
@Override
public ConsumerService create(Throwable arg0) {
// TODO Auto-generated method stub
return new ConsumerService() {
@Override
public List getAll() {
// TODO Auto-generated method stub
return null;
}
@Override
public User get(int id) {
User user = new User(id, "该用户不存在", 0);
return user;
}
@Override
public boolean add(User user) {
// TODO Auto-generated method stub
return false;
}
};
}
}
在microservice-consumer-feign-hystrix服务中ConsumerService的接口中@FeignClient中添加fallbackFactory属性。运用spring的AOP切面,当调用ConsumerService中方法失败后,执行fallbackFactory属性指定的ConsumerServiceFallbackFactory类中的对应方法,ConsumerService修改后如下:
package com.lzj.springcloud.service;
import java.util.List;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.lzj.springcloud.entity.User;
/*以后调用microservicecloud-provider微服务中的方法,只需要调用下面对应的接口既可以了*/
@FeignClient(value="microservicecloud-provider", fallbackFactory=ConsumerServiceFallbackFactory.class)
public interface ConsumerService {
/*调用接口中的get方法,即可以向microservicecloud-provider微服务发送/get/{id}请求*/
@RequestMapping(value="/get/{id}", method=RequestMethod.GET)
public User get(@PathVariable("id") int id);
@RequestMapping(value="/add", method=RequestMethod.POST)
public boolean add(User user);
@RequestMapping(value="/getUser/list", method=RequestMethod.GET)
public List getAll();
}
修改microservice-consumer-feign-hystrix服务的application.yml文件为:
server:
port: 7001
feign:
hystrix:
enabled: true
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9001.com:9001/eureka/
测试:首先启动microservice-eurake1、microservice-eurake2、microservice-eurake3集群服务,然后启动microservice-provider提供者服务,最后启动microservice-consumer-feign-hystrix消费者服务。从浏览器中发送请求
http://localhost:7001/consumer/get/2
下面模拟资源有限,关掉microservice-provider提供者服务,关掉提供者服务后,重新在浏览器中发送请求http://localhost:7001/consumer/get/2
,响应如下:
响应过程如下:当发送请求后,调用microservice-consumer-feign-hystrix服务中的UserConsumerController的get方法,然后调用ConsumerService中的get方法,该方法向microservice-provider服务发送请求/get/{id}
,由于microservice-provider服务被关掉了,请求失败,转而调用ConsumerServiceFallbackFactory中实现的对应方法
@Override
public User get(int id) {
User user = new User(id, "该用户不存在", 0);
return user;
}
因此,浏览器中得到的响应是指定的错误信息。
3、服务监控
hystrix除了应用于上述的服务熔断和降级,还可以应用于服务的实时监控。Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。示例如下
创建microservice-consumer-hystrix-dashbord微服务;
添加pom的依赖,如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-consumer
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-hystrix
org.springframework.cloud
spring-cloud-starter-hystrix-dashboard
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
application.yml配置文件如下:
server:
port: 7002
创建启动类HystrixDashbordConsumerApplication:
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashbordConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashbordConsumerApplication.class, args);
}
}
然后启动微服务,响应页面如下,说明创建用于监控其它服务的微服务成功:
测试:
首先启动microservice-eurake1、microservice-eurake2、microservice-eurake3集群服务,然后启动microservice-provider-hystrix服务,最后启动刚创建监控服务的服务microservice-consumer-hystrix-dashbord。
本例要监听提供者microservice-provider-hystrix服务的访问情况,下面把监控提供者服务的链接填入豪猪监控客户端中,如下
填入完毕后,点击Monitor stream按钮,开启监控页面。
从浏览器中连续的发送请求http://localhost:8005/get/1
(不停的刷新即可),监控页面如下:
图中的实心圆的颜色为绿色,表示健康,健康程度排序绿色>黄色>橙色>红色
,绿色表示最健康,红色表示最不健康。实心圆的大小表示了监控的服务访问量的大小,访问量越大,实心圆越大,反之,越小。图中每个颜色数字表示如下(从别处盗图)
Zuul路由包含了对请求的路由和过滤两个功能。
路由:路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口;
过滤:过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。
Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。Zuul服务最终也会注册进Eureka。
1、路由配置
建立一个microservice-zull微服务;
pom文件如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-zull
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-zuul
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
也要把Zuul微服务注册到Eureaka上面,application.yml文件配置如下:
server:
port: 6001
spring:
application:
name: microservice-zull
eureka:
client:
service-url:
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9001.com:9001/eureka/
instance:
instance-id: microservice-zull6001 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservice-zull
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
在host文件中添加127.0.0.1的映射,127.0.0.1 zull6001.com
用zull6001.com表示Zuul微服务的域名;
创建ZullApplication启动类,内容如下:
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableZuulProxy //启动Zuul
public class ZullApplication {
public static void main(String[] args) {
SpringApplication.run(ZullApplication.class, args);
}
}
测试:
在浏览器中发送http://localhost:8002/get/2
请求,直接请求microservice-provider微服务,响应如下:
在浏览器中发送http://zull6001.com:6001/microservicecloud-provider/get/2
请求,通过Zuul路由访问microservicecloud-provider服务,其中zull6001.com:6001
为microservice-zull微服务的域名和端口,microservicecloud-provider为要访问的微服务名(在application.yml中配置的),响应如下:
结果正常。
2、修改服务代理名称
上面通过路由访问服务的请求为http://zull6001.com:6001/microservicecloud-provider/get/2
,其中microservicecloud-provider为调用的服务名,向调用方暴露了具体的服务名。如果不想暴露服务名,可以为服务指定一个代号别名,例如可以通过发送请求http://zull6001.com:6001/provider/get/2
访问microservicecloud-provider服务,那么provider即为microservicecloud-provider的别名,在application.yml中配置如下:
server:
port: 6001
spring:
application:
name: microservice-zull
eureka:
client:
service-url:
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9001.com:9001/eureka/
instance:
instance-id: microservice-zull6001 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
zuul:
routes:
mydept.serviceId: microservicecloud-provider
mydept.path: /provider/**
info:
app.name: microservice-zull
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
添加了关于zuul的配置,在浏览器中发送http://zull6001.com:6001/provider/get/2请求,响应如下:
响应正常,此时通过发送带调用的微服务名的请求也是可以访问的。
3、忽略带真实服务名的请求
如果想拒绝访问中带服务名的请求,例如http://zull6001.com:6001/microservicecloud-provider/get/2
,使其不能再访问服务,只能通过指定的别名进行访问服务。
在application.yml中关于zuul的配置修改为如下(增加了ignored-services):
zuul:
ignored-services: microservicecloud-provider
routes:
mydept.serviceId: microservicecloud-provider
mydept.path: /provider/**
4、设置访问前缀
当设置完忽略真实服务名访问后,只能通过路由代理别名的形式进行访问,例如http://zull6001.com:6001/provider/get/2
,如果要在每次访问的时候,在代理服务名前面加一个前缀,例如http://zull6001.com:6001/MyDemo/provider/get/2
,MyDemo即为前缀。在application.yml中关于zuul的配置修改为如下(只是添加了prefix的配置):
zuul:
prefix: /MyDemo
ignored-services: microservicecloud-provider
routes:
mydept.serviceId: microservicecloud-provider
mydept.path: /provider/**
浏览器中发送http://zull6001.com:6001/MyDemo/provider/get/2
请求,响应如下:
一个分布式系统有可能包括非常多微服务,每个微服务都有独自的配置文件,当系统变更时,有可能需要修改很多服务的配置文件,导致运维繁琐,容易出问题,所以需要一套集中式的、动态的配置管理设施。spring cloud提供了Config来解决该问题。
1、建立Config服务端,与github通信
在github上建立一个respository,此地名为microservice-config,地址为:https://github.com/shuniversity/microservice-config.git;
把建立的respository 克隆到本地E:\demo\springcloud-config-repository
>git clone https://github.com/shuniversity/microservice-config.git
在clone到本地的仓库E:\demo\springcloud-config-repository\microservice-config中新建一个application.yml文件,内容为:
spring:
profiles:
active:
- dev
---
spring:
profiles: dev #开发环境
application:
name: microservice-config-dev
---
spring:
profiles: test #测试环境
application:
name: microservice-config-test
把新建的application.yml文件推送到github上的microservice-config仓库中:
git add .
git commit -m "init file"
git push origin master
新建microservice-config-server服务,pom文件配置为:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-config-server
org.springframework.cloud
spring-cloud-config-server
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
application.yml配置为:
server:
port: 4001
spring:
application:
name: microservice-config-server
cloud:
config:
server:
git:
uri: https://github.com/shuniversity/microservice-config.git
建立启动类ConfigServerApp
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApp {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApp.class, args);
}
}
测试:
创建完仓库和Config的服务端工程后,下面开始测试,首先启动创建好的Config服务工程,然后再浏览器中发送请求http://localhost:4001/application-test.yml
,响应如下:
在浏览器中发送http://localhost:4001/application-dev.yml
请求,响应如下:
可见通过Config的服务工程可以获取github上的配置内容。
2、创建Config的客户端
上面一步已经成功了创建服务端,并已能够与github进行通信,下面建立Config的客户端,与上一步创建的服务端通信,通过服务端获取github上的配置信息。目前已经创建的cousumer、provider、eurake等微服务都可以改成Config的客户端,然后修改后的微服务不用在各自的微服务中application.yml中配置独有的信息,统一的把配置放在github上,由运维人员统一进行配置。
下面把前面已经创建的microservice-consumer微服务复制成microservice-consumer-config-clent.yml,然后把microservice-consumer-config-clent.yml修改成Config客户端;
在本地仓库添加一个配置文件microservice-consumer-config-clent.yml,用户放到github上;
spring:
profiles:
active:
- dev
server:
port: 7001
spring:
profiles: dev
application:
name: microservice-consumer-config-clent
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9001.com:9001/eureka/
修改microservice-consumer-config-client中的pom文件,config的客户端需要下面的依赖,一定不能少:
具体关于SpringCloud介绍请参考其他文章,下面主要讲解SpringCloud搭建微服务的步骤。
本次案例主要分为以下几个微服务
提供者服务用于操作user表,进行增删改查;
消费者服务用于请求提供者服务,进行增删改查;
公共模块微服务,用于提供其他微服务共同需要的功能;
本次案例用的工具版本如下
eclipse : Mars
JDK : 1.8
SpringCloud : Dalston
SpringBoot : 1.5.9
首先建立一个microservice-parent总工程,总工程中并不做什么业务逻辑,总工程主要定义一个POM文件,将后续各个微服公用的一些jar包在总工程的pom中进行导入。为方便起见,建立一个working set用于归类所有的微服务,命名为springcloudset。然后建立总工程
New -> Maven -> Maven Project
注意,Packaging模式要选择jar
POM文件如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
pom
UTF-8
1.8
1.8
4.12
1.2.17
1.16.18
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR1
pom
import
org.springframework.boot
spring-boot-dependencies
1.5.9.RELEASE
pom
import
mysql
mysql-connector-java
5.1.46
com.alibaba
druid
1.1.0
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.0
ch.qos.logback
logback-core
1.2.3
junit
junit
${junit.version}
test
log4j
log4j
${log4j.version}
然后建立一个microservice-com的公共模块,用于提供后续微服需要的公共的东西。创建好公共模块,其它微服务需要的话,不用再创建,直接引用该模块即可。
1、下面在总工程下,即microservice-parent下创建第一个微服务microservice-com模块。
New -> Maven -> Maven Module
注意,Packaging选择pom模式,后续的微服都是如此。
pom.xml内容为:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-com
org.projectlombok
lombok
2、然后在该模块中建立一个User的bean,以供其它微服务进行调用。
package com.lzj.springcloud.entity;
public class User {
private int id;
private String name;
private int age;
public User() {
super();
}
public User(int id, String name, int age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
3、创建完公共微服后,执行mvn clean install进行装载。
创建完microservice-com模块后,在microservice-parent父工程中的pom.xml文件中增加了
microservice-com
1、与建立microservice-com服务类似,建立一个microservice-provider微服务
2、该服务用于提供直接操作数据库user表。首先在pom中配置依赖的包,操作数据库需要的数据库驱动、数据源等。
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-provider
com.lzj.springcloud
microservice-com
${project.version}
junit
junit
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-starter-jetty
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
在microservice-provider微服找那个需要引用microservice-com微服中User bean,所以在pom文件中引入了microservice-com的依赖。
com.lzj.springcloud
microservice-com
${project.version}
3、在microservice-provider需要操作数据库,在application.yml中配置mybatis和数据源如下:
server:
port: 8002
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
4、在src/main/resources目录下创建mybatis文件夹后新建mybatis.cfg.xml文件,内容如下:
5、创建Dao接口,用于操作user表的接口
package com.lzj.springcloud.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.lzj.springcloud.entity.User;
@Mapper
public interface UserDao {
public boolean addUser(User user);
public User getUser(int id);
public List getUsers();
}
6、在src/main/resources/mybatis目录下创建mapper目录,并在mapper目录下创建UserMapper.xml文件,内容如下:
insert into user(NAME, AGE) values(#{name}, #{age})
7、创建UserService服务接口
package com.lzj.springcloud.service;
import java.util.List;
import com.lzj.springcloud.entity.User;
public interface UserService {
public boolean addUser(User user);
public User getUser(int id);
public List getUsers();
}
8、创建UserServiceImpl接口的实现
package com.lzj.springcloud.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.lzj.springcloud.dao.UserDao;
import com.lzj.springcloud.entity.User;
import com.lzj.springcloud.service.UserService;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public boolean addUser(User user) {
boolean flag;
flag = userDao.addUser(user);
return flag;
}
@Override
public User getUser(int id) {
User user = userDao.getUser(id);
return user;
}
@Override
public List getUsers() {
List users = userDao.getUsers();
return users;
}
}
9、创建Controller层,用于相应REST请求
package com.lzj.springcloud.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.lzj.springcloud.entity.User;
import com.lzj.springcloud.service.UserService;
@RestController
public class UserController {
@Autowired
private UserService service;
@RequestMapping(value="/add", method=RequestMethod.POST)
public boolean addUser(@RequestBody User user){
boolean flag = service.addUser(user);
return flag;
}
@RequestMapping(value="/get/{id}", method=RequestMethod.GET)
public User getUser(@PathVariable("id") int id){
User user = service.getUser(id);
return user;
}
@RequestMapping(value="/getUser/list", method=RequestMethod.GET)
public List getUsers(){
List users = service.getUsers();
return users;
}
}
10、创建microservice-provider的启动类
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
11、测试microservice-provider微服
启动microservice-provider服务,然后在浏览器中发送请求 http://localhost:8002/getUser/list,相应结果如下
创建并配置完microservice-provider后,在microservice-parent的pom的文件中,module标签如下
microservice-com
microservice-provider
microservice-consumer
microservice-consumer服务用于请求microservice-provicer
创建与microservice-com类似,在microservice-parent下创建microservice-consumer微服。
1、pom配置如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-consumer
com.lzj.springcloud
microservice-com
${project.version}
org.springframework.boot
spring-boot-starter-web
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
热部署即为,工程每次修改完后,会重新部署。
2、application.yml配置文件如下:
配置访问端口
server:
port: 8003
3、配置RestTemplate的bean
RestTemplate用于模拟发送REST的客户端请求
package com.lzj.springcloud.configbean;
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();
}
}
3、配置controller层
package com.lzj.springcloud.controller;
import java.util.List;
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 com.lzj.springcloud.entity.User;
@RestController
public class UserConsumerController {
private static String REST_URL_PREFIX = "http://localhost:8002";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value="/consumer/add")
public boolean addUser(User user){
Boolean flag = restTemplate.postForObject(REST_URL_PREFIX + "/add", user, Boolean.class);
return flag;
}
@RequestMapping(value="/consumer/get/{id}")
public User get(@PathVariable("id") int id){
User user = restTemplate.getForObject(REST_URL_PREFIX + "/get" + "/id", User.class);
return user;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@RequestMapping(value="/consumer/list")
public List getList(){
List list = restTemplate.getForObject(REST_URL_PREFIX + "/getUser/list", List.class);
return list;
}
}
4、创建启动类
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
5、测试
在浏览器中发送 http://localhost:8003/consumer/add?name=lzj5&age=35
相应如下:
发送请求后,在数据库user表中插入了一条数据
至此需要的微服务已经全部创建完毕,在microservice-parent的pom文件中可以看到总工程下有三个微服务。
microservice-com
microservice-provider
microservice-consumer
eureka微服务用于注册和发现服务。
首先建立一个依赖于microservice-parent的microservice-eurake1工程,与上面建立方法一样。
1、pom文件配置如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-eurake1
org.springframework.cloud
spring-cloud-netflix-eureka-server
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
如果出现“Failed to read artifact descriptor for org.springframework.cloud:spring-cloud-st”问题,请参考解决“Failed to read artifact descriptor for org.springframework.cloud:spring-cloud-st”。
2、application.yml文件配置如下
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
port: 9001
3、创建启动类
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
注意,一定不要忘记@EnableEurekaServer
注解,该注解用于激活eureka的服务端。
现在可以启动microservice-eureka微服务了
正确创建eureka微服务后,启动页面如上图所示,只是还没有微服务注册进来。
5、把microservice-provider微服务注册进microservice-eureka服务中
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-config
microservice-provider中的pom文件如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-provider
com.lzj.springcloud
microservice-com
${project.version}
junit
junit
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-starter-jetty
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-config
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
eureka:
client:
service-url:
defaultZone: http://localhost:9001/eureka
microservice-provider中application.yml配置文件如下:
server:
port: 8002
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
defaultZone: http://localhost:9001/eureka
@EnableEurekaClient
注解。表示microservice-provider微服务启动时就启动eureka的客户端,该客户端自动的把microservice-provider服务注册进microservice-eureka1中。package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
6、测试microservice-provider服务已经注册进了microservice-eureka1服务。
启动microservice-eureka1微服务,然后再启动microservice-provider微服务,
在浏览器中请求http://localhost:9001/
,反馈网页如下:
可见网页中Application那一栏多了一个MICROSERVICECLOUD-PROVIDER服务,此处显示的服务名字是在microservice-provider服务中的application.yml文件中配置的
spring:
application:
name: microservicecloud-provider #微服务的名字
为追求完美者,下面完善注册服务的信息,如果只是想达到功能,7、8、9可以不用看。
7、修改注册服务的主机名称
我们看到在上述的测试中,注册进eureka中的MICROSERVICE-PROVIDER服务,在状态一栏里面显示的是主机名称:服务名称的形式,主机默认是我本人主机名称,如下图所示。
下面修改microservicecloud-provider中的application.yml文件,关于eureka的部分改为如下:
eureka:
client:
service-url:
defaultZone: http://localhost:9001/eureka
instance:
instance-id: microservicecloud-provider8002
重启服务,状态栏中显示的即为配置的instance-id名称。
8、eureka中显示注册微服务的ip信息
当鼠标放在注册微服务的链接上,左下角显示注册微服务的ip信息,如下所示
左下角显示了microservice-provider微服务的ip信息,要想达到此效果,只需要microservice-provider服务中application.yml中eureka的配置改为如下
eureka:
client:
service-url:
defaultZone: http://localhost:9001/eureka
instance:
instance-id: microservicecloud-provider8002 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
9、点击注册微服务链接,显示微服务的info信息
当点击上图划红线的链接处,显示下面erro信息
下面定制当点击链接时,显示指定信息
首先在microservice-parent的pom中增加
的配置,pom如下所示
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
pom
UTF-8
1.8
1.8
4.12
1.2.17
1.16.18
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR1
pom
import
org.springframework.boot
spring-boot-dependencies
1.5.9.RELEASE
pom
import
mysql
mysql-connector-java
5.1.46
com.alibaba
druid
1.1.0
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.0
ch.qos.logback
logback-core
1.2.3
junit
junit
${junit.version}
test
log4j
log4j
${log4j.version}
microservicecloud
src/main/resources
true
org.apache.maven.plugins
maven-resources-plugin
$
microservice-com
microservice-provider
microservice-consumer
microservice-eurake1
然后再microservice-provider的服务中增加如下依赖
org.springframework.boot
spring-boot-starter-actuator
最后在microservice-provider中application.yml中配置显示的info信息
server:
port: 8002
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
defaultZone: http://localhost:9001/eureka
instance:
instance-id: microservicecloud-provider8002 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservicecloud-provider
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
重启microservice-eurake1和microservicecloud-provider微服务,点击注册的微服务microservicecloud-provider的链接,显示如下页面
10、eureka的自我保护
过段时间,微服务没有用时,会出现下面红色的警告信息,即为eureka的自我保护。
默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,EurekaServer就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。
在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。当它收到的心跳数重新恢复到阈值以上时,该Eureka Server节点就会自动退出自我保护模式。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
在Spring Cloud中,可以使用eureka.server.enable-self-preservation = false 禁用自我保护模式。
第五步只是建立了一个eureka的微服务,如果当这个微服务down掉了,那么其它微服务就不能被注册和发现,整个系统就会down掉,所以下面建立多个eureka微服务,配置eureka集群,需要注册的微服务要注册到所有的eureka的微服务中,即注册到整个集群上,当一个eureka的微服务挂掉了,其它的eureka微服可以继续工作。
首先修改host文件,添加127.0.0.1的多个域名映射,方便后面模拟根据多个地址进行注册服务。
分别复制microservice-eurake1工程为microservice-eurake2和microservice-eurake3,
把microservice-eurake2服务中pom文件中的artifactId改为(因为之前该微服是复制过来的,如果是创建过来的就不用修改)
把microservice-eurake3服务中pom文件中的artifactId改为
修改microservice-eureka1的application.yml文件为
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/
server:
port: 9001
修改microservice-eureka2的application.yml文件为
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9003.com:9003/eureka/
server:
port: 9002
修改microservice-eureka3的application.yml文件为
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9002.com:9002/eureka/
server:
port: 9003
然后修改microservice-provider微服务application.yml中的defaultZone配置,把该微服务同时注册到三个eureka微服务中,即eureka集群中。
server:
port: 8002
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
#defaultZone: http://localhost:9001/eureka
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9003.com:9003/eureka/
instance:
instance-id: microservicecloud-provider8002 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservicecloud-provider
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
测试:运行microservice-eurake1、microservice-eurake2、microservice-eurake3、microservice-provider
从浏览器中访问eureka1的管理端http://localhost:9001/
可以看出,两个划横线的为microservice-eurake1的备份,microservice-provider为注册上来的服务。eureka的3个微服务组成了一个服务注册的集群,只有有一个能工作,能保证业务的执行。
Ribbon的负载均衡是应用于客户端的,即调用一方的,在本案例中就是应用于microservice-consumer微服务的,下面就对microservice-consumer微服务使用负载均衡。
1、microservice-consumer进行Ribbon配置
下面进行修改microservice-consumer微服务的配置
pom文件中增加Ribbon需要的依赖
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-ribbon
org.springframework.cloud
spring-cloud-starter-config
修改application.yml文件,增加eureka的服务注册功能,修改后的配置如下
server:
port: 8003
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9001.com:9001/eureka/
负载均衡实际是根据RestTemplate根据均衡算法进行调度不同地址上的同一个微服务的部署。所以修改ConfigBean,在RestTemplate上加@LoadBalanced注解。
@Configuration
public class ConfigBean {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
把microservice-consumer也注册到eureka服务中,需要在启动类上加@EnableEurekaClient注解
@SpringBootApplication
@EnableEurekaClient
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
修改controller层,不再通过地址和端口调取其它微服的应用,而是根据微服务的名来调取应用。修改后如下
@RestController
public class UserConsumerController {
// private static String REST_URL_PREFIX = "http://localhost:8002";
/*直接根据微服务名调用,而不再是根据地址和端口了,运用了eureka的发现功能*/
private static String REST_URL_PREFIX = "http://microservicecloud-provider";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value="/consumer/add")
public boolean addUser(User user){
Boolean flag = restTemplate.postForObject(REST_URL_PREFIX + "/add", user, Boolean.class);
return flag;
}
@RequestMapping(value="/consumer/get/{id}")
public User get(@PathVariable("id") int id){
User user = restTemplate.getForObject(REST_URL_PREFIX + "/get/" + id, User.class);
return user;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@RequestMapping(value="/consumer/list")
public List getList(){
List list = restTemplate.getForObject(REST_URL_PREFIX + "/getUser/list", List.class);
return list;
}
}
只是修改了private static String REST_URL_PREFIX = “http://microservicecloud-provider”;
测试:修改上述配置后,下面进行测试,启动microservice-eurake1、microservice-eurake2、microservice-eurake3微服务,然后启动microservice-provider提供者微服务,最后启动带Ribbon负载均衡配置的消费者(客户端)微服务microservice-consumer。从浏览器中发送请求http://localhost:8003/consumer/list
,响应页面如下
说明可以从消费端通过微服务名找到提供者微服务,然后进行调用。
2、Ribbon负载均衡
上面在消费端microservice-consumer配置好了Ribbon,提供者微服务目前只有一个。为减小提供者微服务的压力,现在再部署两个提供者微服务,当客户端发送请求时,由三个微服务中的一个随机的响应请求。
复制microservice-provider工程生成microservice-provider2和microservice-provider3
修改microservice-provider2的启动类为ProviderApplication2
修改microservice-provider3的启动类为ProviderApplication3
修改microservice-provider2的application.yml配置为
server:
port: 8003
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
#defaultZone: http://localhost:9001/eureka
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9003.com:9003/eureka/
instance:
instance-id: microservicecloud-provider8003 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservicecloud-provider
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
修改microservice-provider3的配置为
server:
port: 8004
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
#defaultZone: http://localhost:9001/eureka
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9003.com:9003/eureka/
instance:
instance-id: microservicecloud-provider8004 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservicecloud-provider
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
只是分别修改了server.port和eureka.instance.instance-id。
为方便观察哪一个提供者微服务响应的客户端请求,在提供者微服中分别打印两条日志,
microservice-provider微服务中UserServiceImpl类中getUser方法修改为如下:
@Override
public User getUser(int id) {
User user = userDao.getUser(id);
System.out.println("microservice-provider微服务在响应客户端请求……");
System.out.println("user : " + user);
return user;
}
microservice-provider2微服务中UserServiceImpl类中getUser方法修改为如下:
@Override
public User getUser(int id) {
User user = userDao.getUser(id);
System.out.println("microservice-provider2微服务在响应客户端请求……");
System.out.println("user : " + user);
return user;
}
microservice-provider3微服务中UserServiceImpl类中getUser方法修改为如下:
@Override
public User getUser(int id) {
User user = userDao.getUser(id);
System.out.println("microservice-provider3微服务在响应客户端请求……");
System.out.println("user : " + user);
return user;
}
测试:启动3个eureka的微服务集群,然后启动3个上述提供者微服务,最后启动消费者微服务microservice-consumer,分别从前端发起http://localhost:7001/consumer/get/{id}
多笔请求,id用数字代替,发现分别在三个提供者服务的console中输出如下内容:
microservice-provider3微服务在响应客户端请求……
user : User [id=1, name=lzj1, age=20]
microservice-provider3微服务在响应客户端请求……
user : User [id=2, name=lzj2, age=24]
microservice-provider微服务在响应客户端请求……
user : User [id=4, name=lzj4, age=30]
microservice-provider2微服务在响应客户端请求……
user : User [id=3, name=lzj3, age=26]
……
3个提供者微服务随机的响应客户端请求。
3、通过Ribbon的核心组件IRule定义查找消费端调用提供端微服务的策略
如没有指定轮询策略,默认是消费端随机调用提供端微服的策略,下面指定轮询调用策略。只需要在microservice-consumer中的ConfigBean类添加如下声明:
@Bean
public IRule myRule(){
return new RoundRobinRule(); //轮询策略
}
重新启动3个eureka微服务和3个提供者微服务,最后启动消费者微服务,重新测试,可知消费者微服务是轮询调用提供者的3个微服务的。
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。Feign是对Ribbon的包装,Feign集成了Ribbon。
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
Feign既然是对Ribbon的包装,那么Feign也是用在客户端的,即消费端的。下面建立集成Feign的消费端
复制microservice-consumer工程为microservice-consumer-feign
修改microservice-consumer-feign启动类的名字为FeignConsumerApplication;
microservice-consumer-feign的pom文件中增加对Feign的依赖:
org.springframework.cloud
spring-cloud-starter-feign
创建ConsumerService接口,用于包装microservicecloud-provider微服务,以后要调用microservicecloud-provider服务中的方法,只需要调用接口中对应的方法即可:
package com.lzj.springcloud.service;
import java.util.List;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.lzj.springcloud.entity.User;
/*以后调用microservicecloud-provider微服务中的方法,只需要调用下面对应的接口既可以了*/
@FeignClient(value="microservicecloud-provider")
public interface ConsumerService {
/*调用接口中的get方法,即可以向microservicecloud-provider微服务发送/get/{id}请求*/
@RequestMapping(value="/get/{id}", method=RequestMethod.GET)
public User get(@PathVariable("id") int id);
@RequestMapping(value="/add", method=RequestMethod.POST)
public boolean add(User user);
@RequestMapping(value="/getUser/list", method=RequestMethod.GET)
public List getAll();
}
修改microservice-consumer-feign中的controller层为
package com.lzj.springcloud.controller;
import java.util.List;
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 com.lzj.springcloud.entity.User;
import com.lzj.springcloud.service.ConsumerService;
@RestController
public class UserConsumerController {
// private static String REST_URL_PREFIX = "http://localhost:8002";
/*直接根据微服务名调用,而不再是根据地址和端口了,运用了eureka的发现功能*/
// private static String REST_URL_PREFIX = "http://microservicecloud-provider";
// @Autowired
// private RestTemplate restTemplate;
@Autowired
private ConsumerService service;
@RequestMapping(value="/consumer/add")
public boolean addUser(User user){
Boolean flag = service.add(user);
return flag;
}
@RequestMapping(value="/consumer/get/{id}")
public User get(@PathVariable("id") int id){
User user = service.get(id);
return user;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@RequestMapping(value="/consumer/list")
public List getList(){
List list = service.getAll();
return list;
}
}
修改启动类FeignConsumerApplication,在启动类上加启用Feign的注解:
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages="com.lzj.springcloud.service")
public class FeignConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApplication.class, args);
}
}
测试,启动microservice-eurake1、microservice-eurake2、microservice-eurake3集群,然后启动microservice-provider、microservice-provider2、microservice-provider3集群,最后启动microservice-consumer-feign微服务。
从浏览器中向消费端发送请求http://localhost:7001/consumer/get/2
,响应页面如下
Feign通过接口ConsumerService中的get方法调用Rest服务(之前是通过Ribbon+RestTemplate),并在eureka微服务中查找microservicecloud-provider服务,找到后,把请求http://localhost:7001/consumer/get/2
发送到microservicecloud-provider微服务。
如果一个请求需要调起多个服务时,其中一个服务不通或失败,当大量请求发生时,会导致请求延时和资源浪费。Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
Hystrix可用于服务熔断、服务降级、服务限流等作用。
1、服务熔断
当某个服务出现异常时,熔断该服务,快速返回指定的错误信息,当服务正常时,恢复熔断。
复制microservice-provider工程为microservice-provider-hystrix;
修改microservice-provider-hystrix的启动类为HystrixProviderApplication;
pom文件中添加hystrix的依赖
org.springframework.cloud
spring-cloud-starter-hystrix
application.yml配置如下:
server:
port: 8005
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
#defaultZone: http://localhost:9001/eureka
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9003.com:9003/eureka/
instance:
instance-id: microservicecloud-provider-hystrix #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservicecloud-provider
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
只修改了server.port和eureka.instance.instance-id;
修改UserController内容为:
package com.lzj.springcloud.controller;
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.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.lzj.springcloud.entity.User;
import com.lzj.springcloud.service.UserService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
@RestController
public class UserController {
@Autowired
private UserService service;
@RequestMapping(value="/get/{id}", method=RequestMethod.GET)
@HystrixCommand(fallbackMethod="hystrixGetUser") //一旦服务调用失败,就调用hystrixGetUser方法
public User getUser(@PathVariable("id") int id){
User user = service.getUser(id);
if(user == null){
throw new RuntimeException("不存在id=" + id + "对应的用户信息");
}
return user;
}
public User hystrixGetUser(@PathVariable("id") int id){
User user = new User(id, "不存在该用户", 0);
return user;
}
}
在启动类HystrixProviderApplication上添加注解@EnableCircuitBreaker;
启动microservice-eurake1、microservice-eurake2、microservice-eurake3服务,然后启动microservice-provider-hystrix服务,然后启动microservice-consumer-feign服务。
测试:从浏览器中发送请求:http://localhost:7001/consumer/get/18
,响应如下:
说明熔断起作用了,调用服务失败,返回了熔断指定的错误信息。
2、服务降级
在一个分布式系统中,当访问高峰期或资源有限时,需要关掉某个服务,若有请求访问该服务,不能因为系统服务关掉了,就一直中断在该调用服务处,这时就需要请求返回指定的错误信息。例如在分布式系统中有A、B两个服务,因为资源有限,需要关掉B服务,A服务在调用B服务时,没有调通,此时A返回指定的错误信息,注意不是在B服务端返回的,是A客户端返回的错误信息。看示例
复制microservice-consumer-feign服务为microservice-consumer-feign-hystrix;
microservice-consumer-feign-hystrix的pom文件中添加依赖:
org.springframework.cloud
spring-cloud-starter-hystrix
microservice-consumer-feign-hystrix服务中新建实现FallbackFactory的ConsumerServiceFallbackFactory类,在类上添加@Component,并传入ConsumerService接口,当调用ConsumerService中对应的方法失败后,自动调用ConsumerServiceFallbackFactory 中对应实现的ConsumerService方法,并在对应方法中定制调用服务失败后显示的错误信息。
package com.lzj.springcloud.service;
import java.util.List;
import org.springframework.stereotype.Component;
import com.lzj.springcloud.entity.User;
import feign.hystrix.FallbackFactory;
@Component
public class ConsumerServiceFallbackFactory implements FallbackFactory {
@Override
public ConsumerService create(Throwable arg0) {
// TODO Auto-generated method stub
return new ConsumerService() {
@Override
public List getAll() {
// TODO Auto-generated method stub
return null;
}
@Override
public User get(int id) {
User user = new User(id, "该用户不存在", 0);
return user;
}
@Override
public boolean add(User user) {
// TODO Auto-generated method stub
return false;
}
};
}
}
在microservice-consumer-feign-hystrix服务中ConsumerService的接口中@FeignClient中添加fallbackFactory属性。运用spring的AOP切面,当调用ConsumerService中方法失败后,执行fallbackFactory属性指定的ConsumerServiceFallbackFactory类中的对应方法,ConsumerService修改后如下:
package com.lzj.springcloud.service;
import java.util.List;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.lzj.springcloud.entity.User;
/*以后调用microservicecloud-provider微服务中的方法,只需要调用下面对应的接口既可以了*/
@FeignClient(value="microservicecloud-provider", fallbackFactory=ConsumerServiceFallbackFactory.class)
public interface ConsumerService {
/*调用接口中的get方法,即可以向microservicecloud-provider微服务发送/get/{id}请求*/
@RequestMapping(value="/get/{id}", method=RequestMethod.GET)
public User get(@PathVariable("id") int id);
@RequestMapping(value="/add", method=RequestMethod.POST)
public boolean add(User user);
@RequestMapping(value="/getUser/list", method=RequestMethod.GET)
public List getAll();
}
修改microservice-consumer-feign-hystrix服务的application.yml文件为:
server:
port: 7001
feign:
hystrix:
enabled: true
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9001.com:9001/eureka/
测试:首先启动microservice-eurake1、microservice-eurake2、microservice-eurake3集群服务,然后启动microservice-provider提供者服务,最后启动microservice-consumer-feign-hystrix消费者服务。从浏览器中发送请求
http://localhost:7001/consumer/get/2
下面模拟资源有限,关掉microservice-provider提供者服务,关掉提供者服务后,重新在浏览器中发送请求http://localhost:7001/consumer/get/2
,响应如下:
响应过程如下:当发送请求后,调用microservice-consumer-feign-hystrix服务中的UserConsumerController的get方法,然后调用ConsumerService中的get方法,该方法向microservice-provider服务发送请求/get/{id}
,由于microservice-provider服务被关掉了,请求失败,转而调用ConsumerServiceFallbackFactory中实现的对应方法
@Override
public User get(int id) {
User user = new User(id, "该用户不存在", 0);
return user;
}
因此,浏览器中得到的响应是指定的错误信息。
3、服务监控
hystrix除了应用于上述的服务熔断和降级,还可以应用于服务的实时监控。Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。示例如下
创建microservice-consumer-hystrix-dashbord微服务;
添加pom的依赖,如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-consumer
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-hystrix
org.springframework.cloud
spring-cloud-starter-hystrix-dashboard
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
application.yml配置文件如下:
server:
port: 7002
创建启动类HystrixDashbordConsumerApplication:
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashbordConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashbordConsumerApplication.class, args);
}
}
然后启动微服务,响应页面如下,说明创建用于监控其它服务的微服务成功:
测试:
首先启动microservice-eurake1、microservice-eurake2、microservice-eurake3集群服务,然后启动microservice-provider-hystrix服务,最后启动刚创建监控服务的服务microservice-consumer-hystrix-dashbord。
本例要监听提供者microservice-provider-hystrix服务的访问情况,下面把监控提供者服务的链接填入豪猪监控客户端中,如下
填入完毕后,点击Monitor stream按钮,开启监控页面。
从浏览器中连续的发送请求http://localhost:8005/get/1
(不停的刷新即可),监控页面如下:
图中的实心圆的颜色为绿色,表示健康,健康程度排序绿色>黄色>橙色>红色
,绿色表示最健康,红色表示最不健康。实心圆的大小表示了监控的服务访问量的大小,访问量越大,实心圆越大,反之,越小。图中每个颜色数字表示如下(从别处盗图)
Zuul路由包含了对请求的路由和过滤两个功能。
路由:路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口;
过滤:过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。
Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。Zuul服务最终也会注册进Eureka。
1、路由配置
建立一个microservice-zull微服务;
pom文件如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-zull
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-zuul
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
也要把Zuul微服务注册到Eureaka上面,application.yml文件配置如下:
server:
port: 6001
spring:
application:
name: microservice-zull
eureka:
client:
service-url:
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9001.com:9001/eureka/
instance:
instance-id: microservice-zull6001 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservice-zull
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
在host文件中添加127.0.0.1的映射,127.0.0.1 zull6001.com
用zull6001.com表示Zuul微服务的域名;
创建ZullApplication启动类,内容如下:
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableZuulProxy //启动Zuul
public class ZullApplication {
public static void main(String[] args) {
SpringApplication.run(ZullApplication.class, args);
}
}
测试:
在浏览器中发送http://localhost:8002/get/2
请求,直接请求microservice-provider微服务,响应如下:
在浏览器中发送http://zull6001.com:6001/microservicecloud-provider/get/2
请求,通过Zuul路由访问microservicecloud-provider服务,其中zull6001.com:6001
为microservice-zull微服务的域名和端口,microservicecloud-provider为要访问的微服务名(在application.yml中配置的),响应如下:
结果正常。
2、修改服务代理名称
上面通过路由访问服务的请求为http://zull6001.com:6001/microservicecloud-provider/get/2
,其中microservicecloud-provider为调用的服务名,向调用方暴露了具体的服务名。如果不想暴露服务名,可以为服务指定一个代号别名,例如可以通过发送请求http://zull6001.com:6001/provider/get/2
访问microservicecloud-provider服务,那么provider即为microservicecloud-provider的别名,在application.yml中配置如下:
server:
port: 6001
spring:
application:
name: microservice-zull
eureka:
client:
service-url:
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9001.com:9001/eureka/
instance:
instance-id: microservice-zull6001 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
zuul:
routes:
mydept.serviceId: microservicecloud-provider
mydept.path: /provider/**
info:
app.name: microservice-zull
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
添加了关于zuul的配置,在浏览器中发送http://zull6001.com:6001/provider/get/2请求,响应如下:
响应正常,此时通过发送带调用的微服务名的请求也是可以访问的。
3、忽略带真实服务名的请求
如果想拒绝访问中带服务名的请求,例如http://zull6001.com:6001/microservicecloud-provider/get/2
,使其不能再访问服务,只能通过指定的别名进行访问服务。
在application.yml中关于zuul的配置修改为如下(增加了ignored-services):
zuul:
ignored-services: microservicecloud-provider
routes:
mydept.serviceId: microservicecloud-provider
mydept.path: /provider/**
4、设置访问前缀
当设置完忽略真实服务名访问后,只能通过路由代理别名的形式进行访问,例如http://zull6001.com:6001/provider/get/2
,如果要在每次访问的时候,在代理服务名前面加一个前缀,例如http://zull6001.com:6001/MyDemo/provider/get/2
,MyDemo即为前缀。在application.yml中关于zuul的配置修改为如下(只是添加了prefix的配置):
zuul:
prefix: /MyDemo
ignored-services: microservicecloud-provider
routes:
mydept.serviceId: microservicecloud-provider
mydept.path: /provider/**
浏览器中发送http://zull6001.com:6001/MyDemo/provider/get/2
请求,响应如下:
一个分布式系统有可能包括非常多微服务,每个微服务都有独自的配置文件,当系统变更时,有可能需要修改很多服务的配置文件,导致运维繁琐,容易出问题,所以需要一套集中式的、动态的配置管理设施。spring cloud提供了Config来解决该问题。
1、建立Config服务端,与github通信
在github上建立一个respository,此地名为microservice-config,地址为:https://github.com/shuniversity/microservice-config.git;
把建立的respository 克隆到本地E:\demo\springcloud-config-repository
>git clone https://github.com/shuniversity/microservice-config.git
在clone到本地的仓库E:\demo\springcloud-config-repository\microservice-config中新建一个application.yml文件,内容为:
spring:
profiles:
active:
- dev
---
spring:
profiles: dev #开发环境
application:
name: microservice-config-dev
---
spring:
profiles: test #测试环境
application:
name: microservice-config-test
把新建的application.yml文件推送到github上的microservice-config仓库中:
git add .
git commit -m "init file"
git push origin master
新建microservice-config-server服务,pom文件配置为:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-config-server
org.springframework.cloud
spring-cloud-config-server
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
application.yml配置为:
server:
port: 4001
spring:
application:
name: microservice-config-server
cloud:
config:
server:
git:
uri: https://github.com/shuniversity/microservice-config.git
建立启动类ConfigServerApp
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApp {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApp.class, args);
}
}
测试:
创建完仓库和Config的服务端工程后,下面开始测试,首先启动创建好的Config服务工程,然后再浏览器中发送请求http://localhost:4001/application-test.yml
,响应如下:
在浏览器中发送http://localhost:4001/application-dev.yml
请求,响应如下:
可见通过Config的服务工程可以获取github上的配置内容。
2、创建Config的客户端
上面一步已经成功了创建服务端,并已能够与github进行通信,下面建立Config的客户端,与上一步创建的服务端通信,通过服务端获取github上的配置信息。目前已经创建的cousumer、provider、eurake等微服务都可以改成Config的客户端,然后修改后的微服务不用在各自的微服务中application.yml中配置独有的信息,统一的把配置放在github上,由运维人员统一进行配置。
下面把前面已经创建的microservice-consumer微服务复制成microservice-consumer-config-clent.yml,然后把microservice-consumer-config-clent.yml修改成Config客户端;
在本地仓库添加一个配置文件microservice-consumer-config-clent.yml,用户放到github上;
spring:
profiles:
active:
- dev
server:
port: 7001
spring:
profiles: dev
application:
name: microservice-consumer-config-clent
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9001.com:9001/eureka/
修改microservice-consumer-config-client中的pom文件,config的客户端需要下面的依赖,一定不能少:
‘’’
具体关于SpringCloud介绍请参考其他文章,下面主要讲解SpringCloud搭建微服务的步骤。
本次案例主要分为以下几个微服务
本次案例用的工具版本如下
eclipse : Mars
JDK : 1.8
SpringCloud : Dalston
SpringBoot : 1.5.9
首先建立一个microservice-parent总工程,总工程中并不做什么业务逻辑,总工程主要定义一个POM文件,将后续各个微服公用的一些jar包在总工程的pom中进行导入。为方便起见,建立一个working set用于归类所有的微服务,命名为springcloudset。然后建立总工程
New -> Maven -> Maven Project
注意,Packaging模式要选择jar
POM文件如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
pom
UTF-8
1.8
1.8
4.12
1.2.17
1.16.18
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR1
pom
import
org.springframework.boot
spring-boot-dependencies
1.5.9.RELEASE
pom
import
mysql
mysql-connector-java
5.1.46
com.alibaba
druid
1.1.0
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.0
ch.qos.logback
logback-core
1.2.3
junit
junit
${junit.version}
test
log4j
log4j
${log4j.version}
然后建立一个microservice-com的公共模块,用于提供后续微服需要的公共的东西。创建好公共模块,其它微服务需要的话,不用再创建,直接引用该模块即可。
1、下面在总工程下,即microservice-parent下创建第一个微服务microservice-com模块。
New -> Maven -> Maven Module
注意,Packaging选择pom模式,后续的微服都是如此。
pom.xml内容为:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-com
org.projectlombok
lombok
2、然后在该模块中建立一个User的bean,以供其它微服务进行调用。
package com.lzj.springcloud.entity;
public class User {
private int id;
private String name;
private int age;
public User() {
super();
}
public User(int id, String name, int age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
3、创建完公共微服后,执行mvn clean install进行装载。
创建完microservice-com模块后,在microservice-parent父工程中的pom.xml文件中增加了
microservice-com
1、与建立microservice-com服务类似,建立一个microservice-provider微服务
2、该服务用于提供直接操作数据库user表。首先在pom中配置依赖的包,操作数据库需要的数据库驱动、数据源等。
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-provider
com.lzj.springcloud
microservice-com
${project.version}
junit
junit
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-starter-jetty
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
在microservice-provider微服找那个需要引用microservice-com微服中User bean,所以在pom文件中引入了microservice-com的依赖。
com.lzj.springcloud
microservice-com
${project.version}
3、在microservice-provider需要操作数据库,在application.yml中配置mybatis和数据源如下:
server:
port: 8002
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
4、在src/main/resources目录下创建mybatis文件夹后新建mybatis.cfg.xml文件,内容如下:
5、创建Dao接口,用于操作user表的接口
package com.lzj.springcloud.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.lzj.springcloud.entity.User;
@Mapper
public interface UserDao {
public boolean addUser(User user);
public User getUser(int id);
public List getUsers();
}
6、在src/main/resources/mybatis目录下创建mapper目录,并在mapper目录下创建UserMapper.xml文件,内容如下:
insert into user(NAME, AGE) values(#{name}, #{age})
7、创建UserService服务接口
package com.lzj.springcloud.service;
import java.util.List;
import com.lzj.springcloud.entity.User;
public interface UserService {
public boolean addUser(User user);
public User getUser(int id);
public List getUsers();
}
8、创建UserServiceImpl接口的实现
package com.lzj.springcloud.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.lzj.springcloud.dao.UserDao;
import com.lzj.springcloud.entity.User;
import com.lzj.springcloud.service.UserService;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public boolean addUser(User user) {
boolean flag;
flag = userDao.addUser(user);
return flag;
}
@Override
public User getUser(int id) {
User user = userDao.getUser(id);
return user;
}
@Override
public List getUsers() {
List users = userDao.getUsers();
return users;
}
}
9、创建Controller层,用于相应REST请求
package com.lzj.springcloud.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.lzj.springcloud.entity.User;
import com.lzj.springcloud.service.UserService;
@RestController
public class UserController {
@Autowired
private UserService service;
@RequestMapping(value="/add", method=RequestMethod.POST)
public boolean addUser(@RequestBody User user){
boolean flag = service.addUser(user);
return flag;
}
@RequestMapping(value="/get/{id}", method=RequestMethod.GET)
public User getUser(@PathVariable("id") int id){
User user = service.getUser(id);
return user;
}
@RequestMapping(value="/getUser/list", method=RequestMethod.GET)
public List getUsers(){
List users = service.getUsers();
return users;
}
}
10、创建microservice-provider的启动类
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
11、测试microservice-provider微服
启动microservice-provider服务,然后在浏览器中发送请求 http://localhost:8002/getUser/list,相应结果如下
创建并配置完microservice-provider后,在microservice-parent的pom的文件中,module标签如下
microservice-com
microservice-provider
microservice-consumer
microservice-consumer服务用于请求microservice-provicer
创建与microservice-com类似,在microservice-parent下创建microservice-consumer微服。
1、pom配置如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-consumer
com.lzj.springcloud
microservice-com
${project.version}
org.springframework.boot
spring-boot-starter-web
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
热部署即为,工程每次修改完后,会重新部署。
2、application.yml配置文件如下:
配置访问端口
server:
port: 8003
3、配置RestTemplate的bean
RestTemplate用于模拟发送REST的客户端请求
package com.lzj.springcloud.configbean;
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();
}
}
3、配置controller层
package com.lzj.springcloud.controller;
import java.util.List;
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 com.lzj.springcloud.entity.User;
@RestController
public class UserConsumerController {
private static String REST_URL_PREFIX = "http://localhost:8002";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value="/consumer/add")
public boolean addUser(User user){
Boolean flag = restTemplate.postForObject(REST_URL_PREFIX + "/add", user, Boolean.class);
return flag;
}
@RequestMapping(value="/consumer/get/{id}")
public User get(@PathVariable("id") int id){
User user = restTemplate.getForObject(REST_URL_PREFIX + "/get" + "/id", User.class);
return user;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@RequestMapping(value="/consumer/list")
public List getList(){
List list = restTemplate.getForObject(REST_URL_PREFIX + "/getUser/list", List.class);
return list;
}
}
4、创建启动类
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
5、测试
在浏览器中发送 http://localhost:8003/consumer/add?name=lzj5&age=35
相应如下:
发送请求后,在数据库user表中插入了一条数据
至此需要的微服务已经全部创建完毕,在microservice-parent的pom文件中可以看到总工程下有三个微服务。
microservice-com
microservice-provider
microservice-consumer
eureka微服务用于注册和发现服务。
首先建立一个依赖于microservice-parent的microservice-eurake1工程,与上面建立方法一样。
1、pom文件配置如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-eurake1
org.springframework.cloud
spring-cloud-netflix-eureka-server
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
如果出现“Failed to read artifact descriptor for org.springframework.cloud:spring-cloud-st”问题,请参考解决“Failed to read artifact descriptor for org.springframework.cloud:spring-cloud-st”。
2、application.yml文件配置如下
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
port: 9001
3、创建启动类
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
注意,一定不要忘记@EnableEurekaServer
注解,该注解用于激活eureka的服务端。
现在可以启动microservice-eureka微服务了
正确创建eureka微服务后,启动页面如上图所示,只是还没有微服务注册进来。
5、把microservice-provider微服务注册进microservice-eureka服务中
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-config
microservice-provider中的pom文件如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-provider
com.lzj.springcloud
microservice-com
${project.version}
junit
junit
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-starter-jetty
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-config
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
eureka:
client:
service-url:
defaultZone: http://localhost:9001/eureka
microservice-provider中application.yml配置文件如下:
server:
port: 8002
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
defaultZone: http://localhost:9001/eureka
@EnableEurekaClient
注解。表示microservice-provider微服务启动时就启动eureka的客户端,该客户端自动的把microservice-provider服务注册进microservice-eureka1中。package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
6、测试microservice-provider服务已经注册进了microservice-eureka1服务。
启动microservice-eureka1微服务,然后再启动microservice-provider微服务,
在浏览器中请求http://localhost:9001/
,反馈网页如下:
可见网页中Application那一栏多了一个MICROSERVICECLOUD-PROVIDER服务,此处显示的服务名字是在microservice-provider服务中的application.yml文件中配置的
spring:
application:
name: microservicecloud-provider #微服务的名字
为追求完美者,下面完善注册服务的信息,如果只是想达到功能,7、8、9可以不用看。
7、修改注册服务的主机名称
我们看到在上述的测试中,注册进eureka中的MICROSERVICE-PROVIDER服务,在状态一栏里面显示的是主机名称:服务名称的形式,主机默认是我本人主机名称,如下图所示。
下面修改microservicecloud-provider中的application.yml文件,关于eureka的部分改为如下:
eureka:
client:
service-url:
defaultZone: http://localhost:9001/eureka
instance:
instance-id: microservicecloud-provider8002
重启服务,状态栏中显示的即为配置的instance-id名称。
8、eureka中显示注册微服务的ip信息
当鼠标放在注册微服务的链接上,左下角显示注册微服务的ip信息,如下所示
左下角显示了microservice-provider微服务的ip信息,要想达到此效果,只需要microservice-provider服务中application.yml中eureka的配置改为如下
eureka:
client:
service-url:
defaultZone: http://localhost:9001/eureka
instance:
instance-id: microservicecloud-provider8002 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
9、点击注册微服务链接,显示微服务的info信息
当点击上图划红线的链接处,显示下面erro信息
下面定制当点击链接时,显示指定信息
首先在microservice-parent的pom中增加
的配置,pom如下所示
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
pom
UTF-8
1.8
1.8
4.12
1.2.17
1.16.18
org.springframework.cloud
spring-cloud-dependencies
Dalston.SR1
pom
import
org.springframework.boot
spring-boot-dependencies
1.5.9.RELEASE
pom
import
mysql
mysql-connector-java
5.1.46
com.alibaba
druid
1.1.0
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.0
ch.qos.logback
logback-core
1.2.3
junit
junit
${junit.version}
test
log4j
log4j
${log4j.version}
microservicecloud
src/main/resources
true
org.apache.maven.plugins
maven-resources-plugin
$
microservice-com
microservice-provider
microservice-consumer
microservice-eurake1
然后再microservice-provider的服务中增加如下依赖
org.springframework.boot
spring-boot-starter-actuator
最后在microservice-provider中application.yml中配置显示的info信息
server:
port: 8002
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
defaultZone: http://localhost:9001/eureka
instance:
instance-id: microservicecloud-provider8002 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservicecloud-provider
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
重启microservice-eurake1和microservicecloud-provider微服务,点击注册的微服务microservicecloud-provider的链接,显示如下页面
10、eureka的自我保护
过段时间,微服务没有用时,会出现下面红色的警告信息,即为eureka的自我保护。
默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,EurekaServer就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。
在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。当它收到的心跳数重新恢复到阈值以上时,该Eureka Server节点就会自动退出自我保护模式。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
在Spring Cloud中,可以使用eureka.server.enable-self-preservation = false 禁用自我保护模式。
第五步只是建立了一个eureka的微服务,如果当这个微服务down掉了,那么其它微服务就不能被注册和发现,整个系统就会down掉,所以下面建立多个eureka微服务,配置eureka集群,需要注册的微服务要注册到所有的eureka的微服务中,即注册到整个集群上,当一个eureka的微服务挂掉了,其它的eureka微服可以继续工作。
首先修改host文件,添加127.0.0.1的多个域名映射,方便后面模拟根据多个地址进行注册服务。
分别复制microservice-eurake1工程为microservice-eurake2和microservice-eurake3,
把microservice-eurake2服务中pom文件中的artifactId改为(因为之前该微服是复制过来的,如果是创建过来的就不用修改)
把microservice-eurake3服务中pom文件中的artifactId改为
修改microservice-eureka1的application.yml文件为
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/
server:
port: 9001
修改microservice-eureka2的application.yml文件为
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9003.com:9003/eureka/
server:
port: 9002
修改microservice-eureka3的application.yml文件为
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9002.com:9002/eureka/
server:
port: 9003
然后修改microservice-provider微服务application.yml中的defaultZone配置,把该微服务同时注册到三个eureka微服务中,即eureka集群中。
server:
port: 8002
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
#defaultZone: http://localhost:9001/eureka
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9003.com:9003/eureka/
instance:
instance-id: microservicecloud-provider8002 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservicecloud-provider
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
测试:运行microservice-eurake1、microservice-eurake2、microservice-eurake3、microservice-provider
从浏览器中访问eureka1的管理端http://localhost:9001/
可以看出,两个划横线的为microservice-eurake1的备份,microservice-provider为注册上来的服务。eureka的3个微服务组成了一个服务注册的集群,只有有一个能工作,能保证业务的执行。
Ribbon的负载均衡是应用于客户端的,即调用一方的,在本案例中就是应用于microservice-consumer微服务的,下面就对microservice-consumer微服务使用负载均衡。
1、microservice-consumer进行Ribbon配置
下面进行修改microservice-consumer微服务的配置
pom文件中增加Ribbon需要的依赖
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-ribbon
org.springframework.cloud
spring-cloud-starter-config
修改application.yml文件,增加eureka的服务注册功能,修改后的配置如下
server:
port: 8003
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9001.com:9001/eureka/
负载均衡实际是根据RestTemplate根据均衡算法进行调度不同地址上的同一个微服务的部署。所以修改ConfigBean,在RestTemplate上加@LoadBalanced注解。
@Configuration
public class ConfigBean {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
把microservice-consumer也注册到eureka服务中,需要在启动类上加@EnableEurekaClient注解
@SpringBootApplication
@EnableEurekaClient
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
修改controller层,不再通过地址和端口调取其它微服的应用,而是根据微服务的名来调取应用。修改后如下
@RestController
public class UserConsumerController {
// private static String REST_URL_PREFIX = "http://localhost:8002";
/*直接根据微服务名调用,而不再是根据地址和端口了,运用了eureka的发现功能*/
private static String REST_URL_PREFIX = "http://microservicecloud-provider";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value="/consumer/add")
public boolean addUser(User user){
Boolean flag = restTemplate.postForObject(REST_URL_PREFIX + "/add", user, Boolean.class);
return flag;
}
@RequestMapping(value="/consumer/get/{id}")
public User get(@PathVariable("id") int id){
User user = restTemplate.getForObject(REST_URL_PREFIX + "/get/" + id, User.class);
return user;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@RequestMapping(value="/consumer/list")
public List getList(){
List list = restTemplate.getForObject(REST_URL_PREFIX + "/getUser/list", List.class);
return list;
}
}
只是修改了private static String REST_URL_PREFIX = “http://microservicecloud-provider”;
测试:修改上述配置后,下面进行测试,启动microservice-eurake1、microservice-eurake2、microservice-eurake3微服务,然后启动microservice-provider提供者微服务,最后启动带Ribbon负载均衡配置的消费者(客户端)微服务microservice-consumer。从浏览器中发送请求http://localhost:8003/consumer/list
,响应页面如下
说明可以从消费端通过微服务名找到提供者微服务,然后进行调用。
2、Ribbon负载均衡
上面在消费端microservice-consumer配置好了Ribbon,提供者微服务目前只有一个。为减小提供者微服务的压力,现在再部署两个提供者微服务,当客户端发送请求时,由三个微服务中的一个随机的响应请求。
复制microservice-provider工程生成microservice-provider2和microservice-provider3
修改microservice-provider2的启动类为ProviderApplication2
修改microservice-provider3的启动类为ProviderApplication3
修改microservice-provider2的application.yml配置为
server:
port: 8003
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
#defaultZone: http://localhost:9001/eureka
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9003.com:9003/eureka/
instance:
instance-id: microservicecloud-provider8003 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservicecloud-provider
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
修改microservice-provider3的配置为
server:
port: 8004
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
#defaultZone: http://localhost:9001/eureka
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9003.com:9003/eureka/
instance:
instance-id: microservicecloud-provider8004 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservicecloud-provider
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
只是分别修改了server.port和eureka.instance.instance-id。
为方便观察哪一个提供者微服务响应的客户端请求,在提供者微服中分别打印两条日志,
microservice-provider微服务中UserServiceImpl类中getUser方法修改为如下:
@Override
public User getUser(int id) {
User user = userDao.getUser(id);
System.out.println("microservice-provider微服务在响应客户端请求……");
System.out.println("user : " + user);
return user;
}
microservice-provider2微服务中UserServiceImpl类中getUser方法修改为如下:
@Override
public User getUser(int id) {
User user = userDao.getUser(id);
System.out.println("microservice-provider2微服务在响应客户端请求……");
System.out.println("user : " + user);
return user;
}
microservice-provider3微服务中UserServiceImpl类中getUser方法修改为如下:
@Override
public User getUser(int id) {
User user = userDao.getUser(id);
System.out.println("microservice-provider3微服务在响应客户端请求……");
System.out.println("user : " + user);
return user;
}
测试:启动3个eureka的微服务集群,然后启动3个上述提供者微服务,最后启动消费者微服务microservice-consumer,分别从前端发起http://localhost:7001/consumer/get/{id}
多笔请求,id用数字代替,发现分别在三个提供者服务的console中输出如下内容:
microservice-provider3微服务在响应客户端请求……
user : User [id=1, name=lzj1, age=20]
microservice-provider3微服务在响应客户端请求……
user : User [id=2, name=lzj2, age=24]
microservice-provider微服务在响应客户端请求……
user : User [id=4, name=lzj4, age=30]
microservice-provider2微服务在响应客户端请求……
user : User [id=3, name=lzj3, age=26]
……
3个提供者微服务随机的响应客户端请求。
3、通过Ribbon的核心组件IRule定义查找消费端调用提供端微服务的策略
如没有指定轮询策略,默认是消费端随机调用提供端微服的策略,下面指定轮询调用策略。只需要在microservice-consumer中的ConfigBean类添加如下声明:
@Bean
public IRule myRule(){
return new RoundRobinRule(); //轮询策略
}
重新启动3个eureka微服务和3个提供者微服务,最后启动消费者微服务,重新测试,可知消费者微服务是轮询调用提供者的3个微服务的。
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。Feign是对Ribbon的包装,Feign集成了Ribbon。
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
Feign既然是对Ribbon的包装,那么Feign也是用在客户端的,即消费端的。下面建立集成Feign的消费端
复制microservice-consumer工程为microservice-consumer-feign
修改microservice-consumer-feign启动类的名字为FeignConsumerApplication;
microservice-consumer-feign的pom文件中增加对Feign的依赖:
org.springframework.cloud
spring-cloud-starter-feign
创建ConsumerService接口,用于包装microservicecloud-provider微服务,以后要调用microservicecloud-provider服务中的方法,只需要调用接口中对应的方法即可:
package com.lzj.springcloud.service;
import java.util.List;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.lzj.springcloud.entity.User;
/*以后调用microservicecloud-provider微服务中的方法,只需要调用下面对应的接口既可以了*/
@FeignClient(value="microservicecloud-provider")
public interface ConsumerService {
/*调用接口中的get方法,即可以向microservicecloud-provider微服务发送/get/{id}请求*/
@RequestMapping(value="/get/{id}", method=RequestMethod.GET)
public User get(@PathVariable("id") int id);
@RequestMapping(value="/add", method=RequestMethod.POST)
public boolean add(User user);
@RequestMapping(value="/getUser/list", method=RequestMethod.GET)
public List getAll();
}
修改microservice-consumer-feign中的controller层为
package com.lzj.springcloud.controller;
import java.util.List;
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 com.lzj.springcloud.entity.User;
import com.lzj.springcloud.service.ConsumerService;
@RestController
public class UserConsumerController {
// private static String REST_URL_PREFIX = "http://localhost:8002";
/*直接根据微服务名调用,而不再是根据地址和端口了,运用了eureka的发现功能*/
// private static String REST_URL_PREFIX = "http://microservicecloud-provider";
// @Autowired
// private RestTemplate restTemplate;
@Autowired
private ConsumerService service;
@RequestMapping(value="/consumer/add")
public boolean addUser(User user){
Boolean flag = service.add(user);
return flag;
}
@RequestMapping(value="/consumer/get/{id}")
public User get(@PathVariable("id") int id){
User user = service.get(id);
return user;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@RequestMapping(value="/consumer/list")
public List getList(){
List list = service.getAll();
return list;
}
}
修改启动类FeignConsumerApplication,在启动类上加启用Feign的注解:
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages="com.lzj.springcloud.service")
public class FeignConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApplication.class, args);
}
}
测试,启动microservice-eurake1、microservice-eurake2、microservice-eurake3集群,然后启动microservice-provider、microservice-provider2、microservice-provider3集群,最后启动microservice-consumer-feign微服务。
从浏览器中向消费端发送请求http://localhost:7001/consumer/get/2
,响应页面如下
Feign通过接口ConsumerService中的get方法调用Rest服务(之前是通过Ribbon+RestTemplate),并在eureka微服务中查找microservicecloud-provider服务,找到后,把请求http://localhost:7001/consumer/get/2
发送到microservicecloud-provider微服务。
如果一个请求需要调起多个服务时,其中一个服务不通或失败,当大量请求发生时,会导致请求延时和资源浪费。Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
Hystrix可用于服务熔断、服务降级、服务限流等作用。
1、服务熔断
当某个服务出现异常时,熔断该服务,快速返回指定的错误信息,当服务正常时,恢复熔断。
复制microservice-provider工程为microservice-provider-hystrix;
修改microservice-provider-hystrix的启动类为HystrixProviderApplication;
pom文件中添加hystrix的依赖
org.springframework.cloud
spring-cloud-starter-hystrix
application.yml配置如下:
server:
port: 8005
mybatis:
config-location: "classpath:mybatis/mybatis.cfg.xml" # mybatis配置文件所在路径
mapper-locations:
- "classpath:mybatis/mapper/**/*.xml" # mapper映射文件
type-aliases-package: com.lzj.springcloud.entity # 别名类所在包
spring:
application:
name: microservicecloud-provider #微服务的名字
datasource:
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
url: "jdbc:mysql://localhost:3306/lzj" # 数据库名称
username: root
password: lzjlzj
dbcp2:
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
min-idle: 5 # 数据库连接池的最小维持连接数
eureka:
client:
service-url:
#defaultZone: http://localhost:9001/eureka
defaultZone: http://eureka9001.com:9001/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9003.com:9003/eureka/
instance:
instance-id: microservicecloud-provider-hystrix #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservicecloud-provider
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
只修改了server.port和eureka.instance.instance-id;
修改UserController内容为:
package com.lzj.springcloud.controller;
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.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.lzj.springcloud.entity.User;
import com.lzj.springcloud.service.UserService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
@RestController
public class UserController {
@Autowired
private UserService service;
@RequestMapping(value="/get/{id}", method=RequestMethod.GET)
@HystrixCommand(fallbackMethod="hystrixGetUser") //一旦服务调用失败,就调用hystrixGetUser方法
public User getUser(@PathVariable("id") int id){
User user = service.getUser(id);
if(user == null){
throw new RuntimeException("不存在id=" + id + "对应的用户信息");
}
return user;
}
public User hystrixGetUser(@PathVariable("id") int id){
User user = new User(id, "不存在该用户", 0);
return user;
}
}
在启动类HystrixProviderApplication上添加注解@EnableCircuitBreaker;
启动microservice-eurake1、microservice-eurake2、microservice-eurake3服务,然后启动microservice-provider-hystrix服务,然后启动microservice-consumer-feign服务。
测试:从浏览器中发送请求:http://localhost:7001/consumer/get/18
,响应如下:
说明熔断起作用了,调用服务失败,返回了熔断指定的错误信息。
2、服务降级
在一个分布式系统中,当访问高峰期或资源有限时,需要关掉某个服务,若有请求访问该服务,不能因为系统服务关掉了,就一直中断在该调用服务处,这时就需要请求返回指定的错误信息。例如在分布式系统中有A、B两个服务,因为资源有限,需要关掉B服务,A服务在调用B服务时,没有调通,此时A返回指定的错误信息,注意不是在B服务端返回的,是A客户端返回的错误信息。看示例
复制microservice-consumer-feign服务为microservice-consumer-feign-hystrix;
microservice-consumer-feign-hystrix的pom文件中添加依赖:
org.springframework.cloud
spring-cloud-starter-hystrix
microservice-consumer-feign-hystrix服务中新建实现FallbackFactory的ConsumerServiceFallbackFactory类,在类上添加@Component,并传入ConsumerService接口,当调用ConsumerService中对应的方法失败后,自动调用ConsumerServiceFallbackFactory 中对应实现的ConsumerService方法,并在对应方法中定制调用服务失败后显示的错误信息。
package com.lzj.springcloud.service;
import java.util.List;
import org.springframework.stereotype.Component;
import com.lzj.springcloud.entity.User;
import feign.hystrix.FallbackFactory;
@Component
public class ConsumerServiceFallbackFactory implements FallbackFactory {
@Override
public ConsumerService create(Throwable arg0) {
// TODO Auto-generated method stub
return new ConsumerService() {
@Override
public List getAll() {
// TODO Auto-generated method stub
return null;
}
@Override
public User get(int id) {
User user = new User(id, "该用户不存在", 0);
return user;
}
@Override
public boolean add(User user) {
// TODO Auto-generated method stub
return false;
}
};
}
}
在microservice-consumer-feign-hystrix服务中ConsumerService的接口中@FeignClient中添加fallbackFactory属性。运用spring的AOP切面,当调用ConsumerService中方法失败后,执行fallbackFactory属性指定的ConsumerServiceFallbackFactory类中的对应方法,ConsumerService修改后如下:
package com.lzj.springcloud.service;
import java.util.List;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.lzj.springcloud.entity.User;
/*以后调用microservicecloud-provider微服务中的方法,只需要调用下面对应的接口既可以了*/
@FeignClient(value="microservicecloud-provider", fallbackFactory=ConsumerServiceFallbackFactory.class)
public interface ConsumerService {
/*调用接口中的get方法,即可以向microservicecloud-provider微服务发送/get/{id}请求*/
@RequestMapping(value="/get/{id}", method=RequestMethod.GET)
public User get(@PathVariable("id") int id);
@RequestMapping(value="/add", method=RequestMethod.POST)
public boolean add(User user);
@RequestMapping(value="/getUser/list", method=RequestMethod.GET)
public List getAll();
}
修改microservice-consumer-feign-hystrix服务的application.yml文件为:
server:
port: 7001
feign:
hystrix:
enabled: true
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9001.com:9001/eureka/
测试:首先启动microservice-eurake1、microservice-eurake2、microservice-eurake3集群服务,然后启动microservice-provider提供者服务,最后启动microservice-consumer-feign-hystrix消费者服务。从浏览器中发送请求
http://localhost:7001/consumer/get/2
下面模拟资源有限,关掉microservice-provider提供者服务,关掉提供者服务后,重新在浏览器中发送请求http://localhost:7001/consumer/get/2
,响应如下:
响应过程如下:当发送请求后,调用microservice-consumer-feign-hystrix服务中的UserConsumerController的get方法,然后调用ConsumerService中的get方法,该方法向microservice-provider服务发送请求/get/{id}
,由于microservice-provider服务被关掉了,请求失败,转而调用ConsumerServiceFallbackFactory中实现的对应方法
@Override
public User get(int id) {
User user = new User(id, "该用户不存在", 0);
return user;
}
因此,浏览器中得到的响应是指定的错误信息。
3、服务监控
hystrix除了应用于上述的服务熔断和降级,还可以应用于服务的实时监控。Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。示例如下
创建microservice-consumer-hystrix-dashbord微服务;
添加pom的依赖,如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-consumer
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-hystrix
org.springframework.cloud
spring-cloud-starter-hystrix-dashboard
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
application.yml配置文件如下:
server:
port: 7002
创建启动类HystrixDashbordConsumerApplication:
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashbordConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashbordConsumerApplication.class, args);
}
}
然后启动微服务,响应页面如下,说明创建用于监控其它服务的微服务成功:
测试:
首先启动microservice-eurake1、microservice-eurake2、microservice-eurake3集群服务,然后启动microservice-provider-hystrix服务,最后启动刚创建监控服务的服务microservice-consumer-hystrix-dashbord。
本例要监听提供者microservice-provider-hystrix服务的访问情况,下面把监控提供者服务的链接填入豪猪监控客户端中,如下
填入完毕后,点击Monitor stream按钮,开启监控页面。
从浏览器中连续的发送请求http://localhost:8005/get/1
(不停的刷新即可),监控页面如下:
图中的实心圆的颜色为绿色,表示健康,健康程度排序绿色>黄色>橙色>红色
,绿色表示最健康,红色表示最不健康。实心圆的大小表示了监控的服务访问量的大小,访问量越大,实心圆越大,反之,越小。图中每个颜色数字表示如下(从别处盗图)
Zuul路由包含了对请求的路由和过滤两个功能。
路由:路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口;
过滤:过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。
Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。Zuul服务最终也会注册进Eureka。
1、路由配置
建立一个microservice-zull微服务;
pom文件如下:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-zull
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-zuul
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
也要把Zuul微服务注册到Eureaka上面,application.yml文件配置如下:
server:
port: 6001
spring:
application:
name: microservice-zull
eureka:
client:
service-url:
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9001.com:9001/eureka/
instance:
instance-id: microservice-zull6001 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
info:
app.name: microservice-zull
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
在host文件中添加127.0.0.1的映射,127.0.0.1 zull6001.com
用zull6001.com表示Zuul微服务的域名;
创建ZullApplication启动类,内容如下:
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableZuulProxy //启动Zuul
public class ZullApplication {
public static void main(String[] args) {
SpringApplication.run(ZullApplication.class, args);
}
}
测试:
在浏览器中发送http://localhost:8002/get/2
请求,直接请求microservice-provider微服务,响应如下:
在浏览器中发送http://zull6001.com:6001/microservicecloud-provider/get/2
请求,通过Zuul路由访问microservicecloud-provider服务,其中zull6001.com:6001
为microservice-zull微服务的域名和端口,microservicecloud-provider为要访问的微服务名(在application.yml中配置的),响应如下:
结果正常。
2、修改服务代理名称
上面通过路由访问服务的请求为http://zull6001.com:6001/microservicecloud-provider/get/2
,其中microservicecloud-provider为调用的服务名,向调用方暴露了具体的服务名。如果不想暴露服务名,可以为服务指定一个代号别名,例如可以通过发送请求http://zull6001.com:6001/provider/get/2
访问microservicecloud-provider服务,那么provider即为microservicecloud-provider的别名,在application.yml中配置如下:
server:
port: 6001
spring:
application:
name: microservice-zull
eureka:
client:
service-url:
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9001.com:9001/eureka/
instance:
instance-id: microservice-zull6001 #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
zuul:
routes:
mydept.serviceId: microservicecloud-provider
mydept.path: /provider/**
info:
app.name: microservice-zull
company.name: www.lzj.com
build.artifactId: $project.artifactId$
build.version: $project.version$
添加了关于zuul的配置,在浏览器中发送http://zull6001.com:6001/provider/get/2请求,响应如下:
响应正常,此时通过发送带调用的微服务名的请求也是可以访问的。
3、忽略带真实服务名的请求
如果想拒绝访问中带服务名的请求,例如http://zull6001.com:6001/microservicecloud-provider/get/2
,使其不能再访问服务,只能通过指定的别名进行访问服务。
在application.yml中关于zuul的配置修改为如下(增加了ignored-services):
zuul:
ignored-services: microservicecloud-provider
routes:
mydept.serviceId: microservicecloud-provider
mydept.path: /provider/**
4、设置访问前缀
当设置完忽略真实服务名访问后,只能通过路由代理别名的形式进行访问,例如http://zull6001.com:6001/provider/get/2
,如果要在每次访问的时候,在代理服务名前面加一个前缀,例如http://zull6001.com:6001/MyDemo/provider/get/2
,MyDemo即为前缀。在application.yml中关于zuul的配置修改为如下(只是添加了prefix的配置):
zuul:
prefix: /MyDemo
ignored-services: microservicecloud-provider
routes:
mydept.serviceId: microservicecloud-provider
mydept.path: /provider/**
浏览器中发送http://zull6001.com:6001/MyDemo/provider/get/2
请求,响应如下:
一个分布式系统有可能包括非常多微服务,每个微服务都有独自的配置文件,当系统变更时,有可能需要修改很多服务的配置文件,导致运维繁琐,容易出问题,所以需要一套集中式的、动态的配置管理设施。spring cloud提供了Config来解决该问题。Config的Server端用来连接github,Config的Client端通过Server端去github请求相关的配置信息。
1、建立Config服务端,与github通信
在github上建立一个respository,此地名为microservice-config,地址为:https://github.com/shuniversity/microservice-config.git;
把建立的respository 克隆到本地E:\demo\springcloud-config-repository
>git clone https://github.com/shuniversity/microservice-config.git
在clone到本地的仓库E:\demo\springcloud-config-repository\microservice-config中新建一个application.yml文件,内容为:
spring:
profiles:
active:
- dev
---
spring:
profiles: dev #开发环境
application:
name: microservice-config-dev
---
spring:
profiles: test #测试环境
application:
name: microservice-config-test
把新建的application.yml文件推送到github上的microservice-config仓库中:
git add .
git commit -m "init file"
git push origin master
新建microservice-config-server服务,pom文件配置为:
4.0.0
com.lzj.springcloud
microservice-parent
0.0.1-SNAPSHOT
microservice-config-server
org.springframework.cloud
spring-cloud-config-server
org.springframework
springloaded
org.springframework.boot
spring-boot-devtools
application.yml配置为:
server:
port: 4001
spring:
application:
name: microservice-config-server
cloud:
config:
server:
git:
uri: https://github.com/shuniversity/microservice-config.git
建立启动类ConfigServerApp
package com.lzj.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApp {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApp.class, args);
}
}
测试:
创建完仓库和Config的服务端工程后,下面开始测试,首先启动创建好的Config服务工程,然后再浏览器中发送请求http://localhost:4001/application-test.yml
,响应如下:
在浏览器中发送http://localhost:4001/application-dev.yml
请求,响应如下:
可见通过Config的服务工程可以获取github上的配置内容。
2、创建Config的客户端
上面一步已经成功了创建服务端,并已能够与github进行通信,下面建立Config的客户端,与上一步创建的服务端通信,通过服务端获取github上的配置信息。目前已经创建的cousumer、provider、eurake等微服务都可以改成Config的客户端,然后修改后的微服务不用在各自的微服务中application.yml中配置独有的信息,统一的把配置放在github上,由运维人员统一进行配置。
spring:
profiles:
active:
- dev
---
server:
port: 7003
spring:
profiles: dev
application:
name: microservice-consumer-config-clent
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9001.com:9001/eureka/
---
server:
port: 7004
spring:
profiles: test
application:
name: microservice-consumer-config-clent
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka9003.com:9003/eureka/,http://eureka9002.com:9002/eureka/,http://eureka9001.com:9001/eureka/
修改microservice-consumer-config-client中的pom文件,config的客户端需要下面的依赖,一定不能少:
org.springframework.cloud
spring-cloud-starter-config
在microservice-consumer-config-client工程中添加bootstrap.yml配置文件,bootstrap.yml为系统级的配置文件,application.yml为用户级的配置文件,bootstrap.yml的优先级高,服务启动时会优先加载。bootstrap中主要配置了通过Config服务端从github上获取配置信息,bootstrap.yml内容为:
spring:
cloud:
config:
name: microservice-consumer-config-client #需要从github上读取的资源名,没有yml后缀;microservice-consumer-config-client.yml为已经长传到github的配置文件
profile: dev
label: master
uri: http://127.0.0.1:4001 #config的服务端地址,等config的客户端和服务端启动后,客户端去查找这个指定的config的服务端,通过服务端获取github上配置文件信息
spring:
profiles: dev
application:
name: microservice-consumer-config-clent
http://127.0.0.1:7003/consumer/get/2
请求,响应如下:解析:上述微服务都启动后,microservice-consumer-config-client通过microservice-config-server微服务获取github上的microservice-consumer-config-clent.yml中的dev配置信息,获取后的配置信息把microservice-consumer-config-client微服务的端口配置成了7003,然后从浏览器中发送http://127.0.0.1:7003/consumer/get/2请求后,microservice-consumer-config-client微服务中的controller层截获请求后,然后去eurake集群上去查找要调用的microservice-provider微服务,然后调用microservice-provider中的对应接口方法,接口方法中操作数据库。
至此,目前一个简单的分布式微服务系统构建而成,整个系统服务如下:
本系统源码全部上传至github,地址为:
https://github.com/shuniversity/MySpringCloud