微服务架构4个核心问题?
解决方案
Spring Cloud 生态! SpringBoot
Spring Cloud NetFlix:一站式解决方案!
Apache Dubbo Zookeeper:半自动,需要整合别人的(异步,RPC)
Dubbo这个方案并不完善。。。。
新概念:服务网格~Server
pom.xml
配置打包方式pom
<packaging>pompackaging>
配置坐标
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<junit.version>4.12junit.version>
<log4j.version>1.2.17log4j.version>
<lombok.version>1.16.18lombok.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Greenwich.SR1version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.1.4.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.32version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.16version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.2version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>${junit.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>${log4j.version}version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-coreartifactId>
<version>1.2.3version>
dependency>
dependencies>
dependencyManagement>
添加坐标
<dependencies>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
dependencies>
数据库
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for dept
-- ----------------------------
DROP TABLE IF EXISTS `dept`;
CREATE TABLE `dept` (
`deptno` bigint(0) NOT NULL AUTO_INCREMENT,
`dname` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`db_source` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`deptno`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
insert into dept(dname, db_source) values('开发部', DATABASE());
insert into dept(dname, db_source) values('人事部', DATABASE());
insert into dept(dname, db_source) values('财务部', DATABASE());
insert into dept(dname, db_source) values('市场部', DATABASE());
insert into dept(dname, db_source) values('运维部', DATABASE());
pojo
@Data
@NoArgsConstructor
public class Dept implements Serializable { // Dept 实体类 orm 类表关系映射
// 主键
private Long deptno;
private String dname;
// 这个数据存在在哪个数据库的字段~微服务,一个服务对应一个数据库,同一个信息被存在不同的数据库
private String dbSource;
public Dept(String dname){
this.dname = dname;
}
}
坐标
<dependencies>
<dependency>
<groupId>com.lzygroupId>
<artifactId>springcloud-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<scope>lzy.springcloud.testscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-coreartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jettyartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
dependencies>
application.yaml
server:
port: 8001
#mybaits-plus
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_ID
type-aliases-package: com.lzy.springcloud.pojo
#spring的配置
spring:
application:
name: springcloud-provider-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: lige0612
mapper
service
public interface DeptService extends IService<Dept> {
}
@Service
public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements DeptService {
}
controller
// 提供Resultful服务
@RestController
@RequestMapping("/dept")
public class DeptController {
@Autowired
private DeptService deptService;
@PostMapping("/add")
public boolean addDept(@RequestBody Dept dept){
return deptService.save(dept);
}
@GetMapping("/get/{id}")
public Dept get(@PathVariable("id") Long id){
LambdaQueryWrapper<Dept> lqw = new LambdaQueryWrapper<>();
lqw.eq(Dept::getDeptno, id);
return deptService.getOne(lqw);
}
@GetMapping("/list")
public List<Dept> list(){
return deptService.list();
}
}
主启动器
@SpringBootApplication
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001 .class, args);
}
}
然后可以启动一下,试一试接口能不能正常工作,注意是get还是post请求
创建springcloud-consumer-dept-80
坐标
<dependencies>
<dependency>
<groupId>com.lzygroupId>
<artifactId>springcloud-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
dependencies>
application.yaml
server:
port: 80
Config
@Configuration
public class ConfigBean {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
controller
@RestController
@RequestMapping("/consumer/dept")
public class DeptConsumerController {
// 消费者不应该有service层~
// RestTemplate..供我们直接调用
@Autowired
private RestTemplate restTemplate;
// 固定前缀
private static final String REST_URL_PREFIX = "http://localhost:8001";
@GetMapping("/get/{id}")
public Dept get(@PathVariable("id") Long id){
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
}
@PostMapping("/add")
public boolean add(Dept dept){
return Boolean.TRUE.equals(restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class));
}
@GetMapping("/list")
public List list(){
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class);
}
}
主启动器
@SpringBootApplication
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class, args);
}
}
创建springcloud-eureka-7001
坐标
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eureka-serverartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
dependencies>
application.yaml
server:
port: 7001
# EurekaService_7001
eureka:
instance:
hostname: localhost #Eureka服务端的实例名字
client:
register-with-eureka: false # 表示是否向Eureka注册中心注册自己
fetch-registry: false # fetch-registry如果为false,则表示自己为注册中心
service-url: # 监控页面~
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
在provider中配置
坐标
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
<version>1.4.6.RELEASEversion>
dependency>
application.yaml
# Eureka的配置,服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
主启动器
加上 @EnableEurekaClient 注解
这样将server端和client端启动起来,访问
localhost:7001
就会看到下面,注册信息
然后有点小配置
# Eureka的配置,服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
instance:
instance-id: springcloud-provider-dept-8001 # 修改Eureka上面的默认描述信息!
# info配置
info:
app.name: lzy
company.name: blog.lzy.com
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
这样你点击默认描述信息那里就会看到下面
查看注册的一些信息
在controller中能看,别的方式估计也可以
// 获取一些配置的信息,得到具体的微服务
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/info")
public Object discovery(){
// 获取微服务列表的清单
List<String> services = discoveryClient.getServices();
System.out.println("Discovery => services" + services);
// 得到一个具体的微服务信息,通过微服务的获取到信息
List<ServiceInstance> instances = discoveryClient.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
for (ServiceInstance instance : instances) {
System.out.println(instance.getHost() + "\t" + instance.getPort() + "\t" + instance.getUri() + "\t" + instance.getServiceId());
}
return this.discoveryClient;
}
主启动器中加@EnableEurekaClient注解
当访问的时候,就可以打印出消息了
一句话总结: 某时刻某一个微服务不可以用了,eureka不会立刻清理,依旧会对该微服务的信息进行保存!
不再删除服务注册表中的数据
(也就是不会注销任何微服务)。当网络故障恢复后,该EurekaServer节点会自动退出自我保护模式。保护服务注册表中的信息
,不再注销任何服务实例。当它收到的心跳数重新恢复到闻值以上时
,该EurekaServer节点就会自动退出自我保护模式。它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话: 好死不如赖活着安全保护措施
。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮和稳定修改hosts文件
配置集群
7001
server:
port: 7001
# EurekaService_7001
eureka:
instance:
hostname: eureka7001 #Eureka服务端的实例名字
client:
register-with-eureka: false # 表示是否向Eureka注册中心注册自己
fetch-registry: false # fetch-registry如果为false,则表示自己为注册中心
service-url: # 监控页面~
# 单机
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 集群
defaultZone: http://eureka7002:7002/eureka/, http://eureka7003:7003/eureka/
7002
server:
port: 7002
# EurekaService_7001
eureka:
instance:
hostname: eureka7002 #Eureka服务端的实例名字
client:
register-with-eureka: false # 表示是否向Eureka注册中心注册自己
fetch-registry: false # fetch-registry如果为false,则表示自己为注册中心
service-url: # 监控页面~
# 单机
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 集群
defaultZone: http://eureka7001:7001/eureka/, http://eureka7003:7003/eureka/
7003
server:
port: 7003
# EurekaService_7001
eureka:
instance:
hostname: eureka7003 #Eureka服务端的实例名字
client:
register-with-eureka: false # 表示是否向Eureka注册中心注册自己
fetch-registry: false # fetch-registry如果为false,则表示自己为注册中心
service-url: # 监控页面~
# 单机
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 集群
defaultZone: http://eureka7001:7001/eureka/, http://eureka7002:7002/eureka/
配置服务端
server:
port: 8001
#mybaits-plus
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_ID
type-aliases-package: com.lzy.springcloud.pojo
#spring的配置
spring:
application:
name: springcloud-provider-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: lige0612
# Eureka的配置,服务注册到哪里
eureka:
client:
service-url:
defaultZone:
# 集群 http://eureka7001:7001/eureka/,http://eureka7002:7002/eureka/,http://eureka7003:7003/eureka/
instance:
instance-id: springcloud-provider-dept-8001 # 修改Eureka上面的默认描述信息!
# info配置
info:
app.name: lzy
company.name: blog.lzy.com
将项目都启动起来,访问各自的监控页面,ok!
著名的CAP理论指出,一个分布式系统不可能同时满足C (一致性)、A (可用性)、P (容错性)由于分区容错性P在分布式系统中是必须要保证的,因此我们只能在A和C之间进行权衡
Zookeeper保证的是CP
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是zk会出现这样一种情况,当mastel节点因为网络故障与其他节点失去联系时,剩杀节点会重新进行leader选举。问题在于,选举leader的时间太长30~120S,且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因为网络问题使得zk集群失去master节点是较大概率会发生的事件,虽然服务最终能够恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。
Eureka保证的是AP
Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册时,如果发现连接失败,则会自动切换至其他节点,只要有一台Eureka还在,就能保住注册服务的可用性,只不过查到的信息可能不是最新的,除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
因此,Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪
LB,即负载均衡(Load Balance),在微服务或分布式集群上经常使用的一种应用
负载均衡简单的就是说能把用户的请求平摊到多个服务器上,从而达到系统的HA(高可用)
负载均衡简单分类
在consumer中操作
坐标
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-ribbonartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
<version>1.4.6.RELEASEversion>
dependency>
applicaiton.yaml
server:
port: 80
# Eureka
eureka:
client:
fetch-registry: true # 不向Eureka注册自己
service-url:
defaultZone: http://eureka7001:7001/eureka/,http://eureka7002:7002/eureka/, http://eureka7003:7003/eureka/
主启动类
// Ribbon 和 Eureka 整合以后,客户端可以直接使用,不用关心IP地址和端口号
@SpringBootApplication
@EnableEurekaClient
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class, args);
}
}
RestTemplate
@Configuration
public class ConfigBean {
// 配置负载均衡实现
@Bean
@LoadBalanced // Ribbon负载均衡
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
这样当我们去访问
http://localhost/consumer/dept/list
就可以显示
但是这里显示的是Db01,看不出来它是怎么搞的负载均衡,所以要搭建一个服务提供集群,再建两个服务提供者,两个数据库,修改配置
这样就可以看到轮询的负载均衡了
自定义负载均衡算法
@Configuration
public class LRule {
@Bean
public IRule myRule(){
return new RandomRule();
}
}
public class MyRule extends AbstractLoadBalancerRule {
// 每个服务,访问5次就,访问下一个服务
private int total = 0; // 被调用的次数
private int currentIndex = 0; // 当前是谁提供的服务
@SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
} else {
Server server = null;
while(server == null) {
if (Thread.interrupted()) {
return null;
}
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
return null;
}
// int index = this.chooseRandomInt(serverCount);
// server = (Server)upList.get(index);
// ===== ===== ===== ===== ===== ===== ===== =====
if(total < 5){
server = upList.get(currentIndex);
total++;
}else{
total = 0;
currentIndex ++;
if(currentIndex > upList.size()){
currentIndex = 0;
}
upList.get(currentIndex);
}
// ===== ===== ===== ===== ===== ===== ===== =====
if (server == null) {
Thread.yield();
} else {
if (server.isAlive()) {
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
主启动类
这样启动了以后就可以去试试,看看是不是每个访问5下,切换到下一个服务
,从这里面学到了,要会看源码,然后仿照的去写
feign是声明式的web service客户端,它让微服务之间的调用变得更简单了,类controller调用service。SpringCloud集成了Ribbon和Eureka,可在使用Feign时提供负载均衡的http客户端。
只需要创建一个接口,然后添加注解即可!
feign ,主要是社区,大家都习惯面向接口编程。这个是很多开发人员的规范。调用微服务访问两种方法
先搞一个80端的Feign
坐标
<dependencies>
<dependency>
<groupId>com.lzygroupId>
<artifactId>springcloud-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-ribbonartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-feignartifactId>
<version>1.4.6.RELEASEversion>
dependency>
dependencies>
controller
@RestController
@RequestMapping("/consumer/dept")
public class DeptConsumerController {
@Autowired
private DeptClientService deptClientService;
@GetMapping("/get/{id}")
public Dept get(@PathVariable("id") Long id){
return this.deptClientService.queryById(id);
}
@PostMapping("/add")
public boolean add(Dept dept){
return this.deptClientService.add(dept);
}
@GetMapping("/list")
public List<Dept> list(){
return this.deptClientService.queryAll();
}
}
主启动类
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.lzy.springcloud"})
public class FeignDeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(FeignDeptConsumer_80.class, args);
}
}
然后在api的那个类中,搞一个server
<dependencies>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-feignartifactId>
<version>1.4.6.RELEASEversion>
dependency>
dependencies>
server
@Component
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")
public interface DeptClientService {
@GetMapping("/dept/list")
public List<Dept> queryAll();
@GetMapping("/dept/get/{id}")
public Dept queryById(@PathVariable("id") Long id);
@PostMapping("/dept/add")
public boolean add(Dept dept);
}
和上面使用Ribbon感觉是一样的,使用Ribbon的时候,也不能这么说,之前的那种方式,我觉得也是把server层相当于放到了消费者里面了,通过统一配置服务的名字和请求url,用RestTemplate去Eureka中去获取服务,现在使用了Feign,他还是用的Ribbon的负载均衡,但是请求方式变了,现在api层,搞一个service的接口,把你要用的接口都写好,使用Feign配置好服务的名字,用GetMapper/PostMapper替代使用RestTemplate,也就是相当于把服务先搞到了api层,然后在消费者层,你只需要像之前一样把api的server注入到里面,然后通过server去调用对应的方法即可,我觉得这样也不错,两种方式各有优劣
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务这就是所谓的“扇出”、如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒中内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生事多的级联故噎,这些都表示需要对故喧和延识进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程字或系统。
Spring Cloud Hystrix 是一款优秀的服务容错与保护组件,也是 Spring Cloud 中最重要的组件之一。
Spring Cloud Hystrix 是基于 Netflix 公司的开源组件 Hystrix 实现的,它提供了熔断器功能,能够有效地阻止分布式微服务系统中出现联动故障,以提高微服务系统的弹性。Spring Cloud Hystrix 具有服务降级、服务熔断、线程隔离、请求缓存、请求合并以及实时故障监控等强大功能。
Hystrix [hɪst’rɪks],中文含义是豪猪,豪猪的背上长满了棘刺,使它拥有了强大的自我保护能力。而 Spring Cloud Hystrix 作为一个服务容错与保护组件,也可以让服务拥有自我保护的能力,因此也有人将其戏称为“豪猪哥”。
在微服务系统中,Hystrix 能够帮助我们实现以下目标:
新建一个hystrix的provider
坐标
<dependencies>
<dependency>
<groupId>com.lzygroupId>
<artifactId>springcloud-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<scope>lzy.springcloud.testscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-coreartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jettyartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrixartifactId>
<version>1.4.6.RELEASEversion>
dependency>
dependencies>
controller
注解@HystrixCommand
// 提供Resultful服务
@RestController
@RequestMapping("/dept")
public class DeptController {
@Autowired
private DeptService deptService;
@GetMapping("/get/{id}")
@HystrixCommand(fallbackMethod = "get_Hystrix")
public Dept get(@PathVariable("id") Long id){
LambdaQueryWrapper<Dept> lqw = new LambdaQueryWrapper<>();
lqw.eq(Dept::getDeptno, id);
Dept one = deptService.getOne(lqw);
if(one == null){
throw new RuntimeException("id=>" + id + "不存在该用户,或者信息无法找到~");
}
return one;
}
public Dept get_Hystrix(@PathVariable("id") Long id){
Dept dept = new Dept();
dept.setDeptno(id);
dept.setDname("id=>" + id + "没有对应的信息,null--@Hystrix");
dept.setDbSource("no this database in MySQL");
return dept;
}
}
主启动类
@SpringBootApplication
@EnableEurekaClient // 自动注册到Eureka
@EnableDiscoveryClient // 服务发现
@EnableCircuitBreaker // 添加对熔断的支持
public class DeptProviderHystrix_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProviderHystrix_8001.class, args);
}
}
启动注册集群和刚整好的项目,访问,当出错的时候,就会自动跳转到我们预先搞的处理错误的方法里面,兜底
api端
DeptClientServiceFallbackFactory
// 降级~
@Component
public class DeptClientServiceFallbackFactory implements FallbackFactory {
@Override
public DeptClientService create(Throwable throwable) {
return new DeptClientService() {
@Override
public List<Dept> queryAll() {
return null;
}
@Override
public Dept queryById(Long id) {
Dept dept = new Dept();
dept.setDeptno(id);
dept.setDname("id=>" + id + "没有对应的信息,客户端提供了降级的信息,这个服务现在已经被关闭了");
dept.setDbSource("没有数据!");
return dept;
}
@Override
public boolean add(Dept dept) {
return false;
}
};
}
}
DeptClientService
@Component
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT", fallbackFactory = DeptClientServiceFallbackFactory.class)
public interface DeptClientService {
@GetMapping("/dept/list")
public List<Dept> queryAll();
@GetMapping("/dept/get/{id}")
public Dept queryById(@PathVariable("id") Long id);
@PostMapping("/dept/add")
public boolean add(Dept dept);
}
api这里的feign中集成了Hystrix,也就是说在客户端做了一个服务降级
feign的80的客户端里面的application.yaml, 开启hystrix
server:
port: 80
# Eureka
eureka:
client:
fetch-registry: true # 不向Eureka注册自己
service-url:
defaultZone: http://eureka7001:7001/eureka/,http://eureka7002:7002/eureka/, http://eureka7003:7003/eureka/
feign:
hystrix:
enabled: true
启动Eureka集群,然后启动没有服务熔断的8001的provider,启动feign的80客户端,一开始我们能正常访问,但是当你某个业务并发量太大,为了不让其崩掉,我们需要先手动停掉其他的服务,把资源都供并发量大的服务去,所以现在当我们把8001服务停掉,再去访问的时候,就会出现降级的提示
服务熔断和服务降级降级的区别
服务熔断:应对雪崩效应的链路自我保护机制。可看作降级的特殊情况
服务降级:系统有限的资源的合理协调
修改一下没有feign的80客户端
依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrixartifactId>
<version>1.4.6.RELEASEversion>
dependency>
做一个降级方法
@RestController
@RequestMapping("/consumer/dept")
public class DeptConsumerController {
// 消费者不应该有service层~
// RestTemplate..供我们直接调用
@Autowired
private RestTemplate restTemplate;
// 固定前缀
// private static final String REST_URL_PREFIX = "http://localhost:8001";
// 通过Ribbon去实现的时候,我们这里的地址,应该是一个变量,通过服务名来访问
private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";
@HystrixCommand(fallbackMethod = "getProductFallBack")
@GetMapping("/get/{id}")
public Dept get(@PathVariable("id") Long id){
Dept forObject = restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class);
if( forObject == null){
throw new RuntimeException("id=>" + id + "不存在该用户,或者信息无法找到~");
}
return forObject;
}
public Dept getProductFallBack(@PathVariable("id") Long id){
Dept dept = new Dept();
dept.setDeptno(id);
dept.setDname("id=>" + id + "没有对应的信息,客户端提供了降级的信息,这个服务现在已经被关闭了");
dept.setDbSource("没有数据!");
return dept;
}
}
配置
hystrix:
command:
default:
execution:
isolation:
strategy: ExecutionIsolationStrategy.SEMAPHORE #信号量隔离
thread:
timeoutInMilliseconds: 2000 # 默认的连接超时时间1秒,若1秒没有返回数据,自动的触发降级逻辑
主启动类
// Ribbon 和 Eureka 整合以后,客户端可以直接使用,不用关心IP地址和端口号
@SpringBootApplication
@EnableCircuitBreaker
// @RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = MyRule.class)
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class, args);
}
}
测试一下,当将provider停掉以后,弹出我们降级后的方法,和Feign差不多
这里我尝试了一下同时启用服务熔断和服务降级,当我们请求本身发生错误的时候,就会触发服务端的熔断机制,如果是服务端关闭了也是出现了错误的时候,就自动触发的服务降级,我咋感觉有点像那个鲁棒性,程序更加健壮了
服务熔断:服务端~ 某个服务超时或者异常,引起熔断~ 保险丝~
服务降级:客户端~ 从整体网站请求负载考虑~,当某个服务熔断或者关闭之后,服务将不再被调用,此时在客户端就可以准备一个FallbackFactory,返回一个默认的值(缺省值),整体的服务水平下降,但是还能用
创建一个监控的服务
坐标
<dependencies>
<dependency>
<groupId>com.lzygroupId>
<artifactId>springcloud-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-ribbonartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrixartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrix-dashboardartifactId>
<version>1.4.6.RELEASEversion>
dependency>
dependencies>
主启动类
@SpringBootApplication
@EnableHystrixDashboard
public class DeptConsumerDashboard_9001 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumerDashboard_9001.class, args);
}
}
然后在集成了Hystrix的provider中的主启动器上加一个Bean
@SpringBootApplication
@EnableEurekaClient // 自动注册到Eureka
@EnableDiscoveryClient // 服务发现
@EnableCircuitBreaker // 添加对熔断的支持
public class DeptProviderHystrix_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProviderHystrix_8001.class, args);
}
// 增加一个Servlet
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
registrationBean.addUrlMappings("/actuator/hystrix.stream");
return registrationBean;
}
}
当你启动Eureka集群并且启动监控的服务,访问监控服务的
localhost:你设置的端口
,就可以看到
然后启动集成Hystrix的provider,访问
localhost:你的端口号/actuator/hystrix.stream
就可以看到了
把
localhost:你的端口号/actuator/hystrix.stream
按照下面
最后一边请求你监控的服务,就可以看到监控到的信息了
新建一个项目算是一个服务zuul
坐标
<dependencies>
<dependency>
<groupId>com.lzygroupId>
<artifactId>springcloud-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-ribbonartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrixartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-hystrix-dashboardartifactId>
<version>1.4.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zuulartifactId>
<version>1.4.6.RELEASEversion>
dependency>
dependencies>
配置文件
server:
port: 9527
spring:
application:
name: springcloud-zuul
eureka:
client:
service-url:
defaultZone: http://eureka7001:7001/eureka/,http://eureka7002:7002/eureka/,http://eureka7003:7003/eureka/
instance:
instance-id: springcloud-zuul-9527 # 修改Eureka上面的默认描述信息!
prefer-ip-address: true
info:
app.name: lzy
company.name: blog.lzy.com
zuul:
routes:
mydept.serviceId: springcloud-provider-dept
mydept.path: /mydept/**
# ignored-services: springcloud-provider-dept # 不能再使用路径去访问了
ignored-services: "*"
prefix: /lzy # http://www.lzy.com:9527/lzy/mydept/dept/get/1 又加了一个公共前缀
主启动类
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication_9527 {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication_9527.class, args);
}
}
http://localhost:8001/dept/get/1
http://localhost/consumer/dept/get/1
http://www.lzy.com:9527/lzy/mydept/dept/get/1
我感觉加了这层Zuul之后,消费端的请求方式应该也要发生变化,感觉之前那个RestTemplate也可以达到这个效果,不知道后面是不是把这个Zuul和RestTemplate合并到一起
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务,由于每个服务都需要必要的配置信息才能运行,所以一套集中式的,动态的配置管理设施是必不可少的。SpringCloud提供了ConfigServer来解决这个问题,我们每一个微服务自己带着一个application.yml,那上百的的配置文件要修改起来,岂不是要发疯!
https://gitee.com/help/articles/4191
和https://gitee.com/help/articles/4122
spring:
profiles:
active: dev
---
spring:
profiles: dev
application:
name: springcloud-config-dev
---
spring:
profiles: test
application:
name: springcloud-config-test
就可以看到了
创建一个config的新的项目
坐标
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-config-serverartifactId>
<version>2.1.1.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
dependencies>
配置文件
server:
port: 3344
spring:
application:
name: springcloud-config-server
# 连接远程仓库
cloud:
config:
server:
git:
uri: https://gitee.com/lzy612/springcloud-config.git # 这里要用https
skip-ssl-validation: true #可以使 Git 服务器 SSL 证书的服务器验证配置失效。
配置连接仓库的地址,我还关了验证SSL证书的配置
主启动类
@SpringBootApplication
@EnableConfigServer
public class Config_Server_3344 {
public static void main(String[] args) {
SpringApplication.run(Config_Server_3344.class, args);
}
}
然后通过访问
http://localhost:3344/application-test.yaml或者
http://localhost:3344/application-dev.yaml就可看到相应的配置文件
这个访问地址还支持下面这些风格的访问方式
新建一个客户端来读取配置
坐标
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
<version>2.1.1.RELEASEversion>
dependency>
dependencies>
上传一个配置文件 config-client.yaml
spring:
profiles:
active: dev
---
server:
port: 8201
spring:
profiles: dev
application:
name: springcloud-provider-dept
# Eureka的配置,服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://eureka7001:7001/eureka/,http://eureka7002:7002/eureka/,http://eureka7003:7003/eureka/
instance:
instance-id: springcloud-provider-dept-8001 # 修改Eureka上面的默认描述信息!
prefer-ip-address: true
---
server:
port: 8202
spring:
profiles: test
application:
name: springcloud-provider-dept
# Eureka的配置,服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://eureka7001:7001/eureka/,http://eureka7002:7002/eureka/,http://eureka7003:7003/eureka/
instance:
instance-id: springcloud-provider-dept-8001 # 修改Eureka上面的默认描述信息!
prefer-ip-address: true
这里默认是dev的配置
配置文件(bootstrap、application)
bootstrap
# 系统级别的配置
spring:
cloud:
config:
name: config-client # 需要从git上读取的资源名称,不需要后缀
# 这里拿的就是上面的dev的8201的配置
uri: http://localhost:3344
profile: dev
label: master
application
# 用户级别的配置
spring:
application:
name: springcloud-config-server-3355
主启动类
@SpringBootApplication
public class Config_Client_3355 {
public static void main(String[] args) {
SpringApplication.run(Config_Client_3355.class, args);
}
}
controller
@RestController
public class ConfigClientController {
@Value("${spring.application.name}")
private String applicationName;
@Value("${eureka.client.service-url.defaultZone}")
private String eurekaServer;
@Value("${server.port}")
private String port;
@RequestMapping("/config")
public String getInfo(){
return "applicationName = " + applicationName + " eurekaServer = " + eurekaServer + " port = " + port;
}
}
启动config-sever,启动config-client端,访问
http://localhost:8201/config
,可以看到config-client读取的配置就是你git远端上指定的dev的配置属性的值,这样就简介的实现了配置文件的统一git管理,不用你在本地搞,本地负责的就是取config-server中读取配置好的配置即可
现在将我们的一个Eureka和一个prodiver继承上Config
将配置文件上传到git上
config-eureka
spring:
profiles:
active: dev
---
server:
port: 7001
spring:
profiles: dev
application:
name: springcloud-config-eureka
# EurekaService_7001
eureka:
instance:
hostname: eureka7001 #Eureka服务端的实例名字
client:
register-with-eureka: false # 表示是否向Eureka注册中心注册自己
fetch-registry: false # fetch-registry如果为false,则表示自己为注册中心
service-url: # 监控页面~
# 单机
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 集群
defaultZone: http://eureka7002:7002/eureka/, http://eureka7003:7003/eureka/
---
server:
port: 7001
spring:
profiles: test
application:
name: springcloud-config-eureka
# EurekaService_7001
eureka:
instance:
hostname: eureka7001 #Eureka服务端的实例名字
client:
register-with-eureka: false # 表示是否向Eureka注册中心注册自己
fetch-registry: false # fetch-registry如果为false,则表示自己为注册中心
service-url: # 监控页面~
# 单机
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 集群
defaultZone: http://eureka7002:7002/eureka/, http://eureka7003:7003/eureka/
config-dept
spring:
profiles:
active: dev
---
server:
port: 8001
#mybaits-plus
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_ID
type-aliases-package: com.lzy.springcloud.pojo
#spring的配置
spring:
profiles: dev
application:
name: springcloud-config-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db01?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: lige0612
# Eureka的配置,服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://eureka7001:7001/eureka/,http://eureka7002:7002/eureka/,http://eureka7003:7003/eureka/
instance:
instance-id: springcloud-provider-dept-8001 # 修改Eureka上面的默认描述信息!
prefer-ip-address: true
# info配置
info:
app.name: lzy
company.name: blog.lzy.com
---
server:
port: 8001
#mybaits-plus
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_ID
type-aliases-package: com.lzy.springcloud.pojo
#spring的配置
spring:
profiles: dev
application:
name: springcloud-config-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db02?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: lige0612
# Eureka的配置,服务注册到哪里
eureka:
client:
service-url:
defaultZone: http://eureka7001:7001/eureka/,http://eureka7002:7002/eureka/,http://eureka7003:7003/eureka/
instance:
instance-id: springcloud-provider-dept-8001 # 修改Eureka上面的默认描述信息!
prefer-ip-address: true
# info配置
info:
app.name: lzy
company.name: blog.lzy.com
创建一个config版本的Eureka,除了配置文件不复制其他的都复制即可
坐标
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
<version>2.1.1.RELEASEversion>
dependency>
配置文件
bootstrap
# 系统级别的配置
spring:
cloud:
config:
name: config-eureka # 需要从git上读取的资源名称,不需要后缀
uri: http://localhost:3344
profile: dev
label: master
bootstrap中的profile的值,才是你在git上的config中取的东西,这个找环境配置不是由git上面决定的,是由这里决定的,我尝试了一下,我把git上面的改成dev1,(用的是db01数据库),然后我再本地用的是test,然后访问出来的数据就是db02的,说明了我上述说的应该是对的,当你本地用的是dev的,然后git端修改dev中配置为db03的时候,本地热部署一下,访问的也就是db03了,说明git上那个指定配置的没啥用,还是要看本地用谁才是谁
,不过这样总的来说还是突出了一点就是配置文件与程序解耦
application
# 用户级别的配置
spring:
application:
name: springcloud-config-eureka-7001
创建一个config版本的provider,除了配置文件不复制其他的都复制即可
坐标
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-configartifactId>
<version>2.1.1.RELEASEversion>
dependency>
配置文件
bootstrap
# 系统级别的配置
spring:
cloud:
config:
name: config-dept # 需要从git上读取的资源名称,不需要后缀
uri: http://localhost:3344
profile: dev
label: master
application
# 用户级别的配置
spring:
application:
name: springcloud-config-provider-dept-8001
启动config版本的eureka,启动config的provider,启动config的Server,启动一个80客户端
当你的eureka、provider正常启动,能登录到监控中心即可,访问一下http://localhost/consumer/dept/get/1
,成功!