Java进阶:微服务SpringCloud

⑤ 部署独立,服务间虽然有调用,但要做到服务重启不影响其它服务。有利于持续集成和持续交付。每个服

⑥ 务都是独立的组件,可复用,可替换,降低耦合,易维护

微服务架构与SOA都是对系统进行拆分;微服务架构基于SOA思想,可以把微服务当做去除了ESB的SOA。ESB是SOA架构中的中心总线,设计图形应该是星形的,而微服务是去中心化的分布式软件架构。两者比较类似,但其实也有一些差别:

| 功能 | SOA | 微服务 |

| — | — | — |

| 组件大小 | 大块业务逻辑 | 单独任务或小块业务逻辑 |

| 耦合 | 通常松耦合 | 总是松耦合 |

| 管理 | 着重中央管理 | 着重分散管理 |

| 目标 | 确保应用能够交互操作 | 易维护、易扩展、更轻量级的交互 |

2. 服务调用方式

=====================================================================

2.1. RPC和HTTP


无论是微服务还是SOA,都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢?

常见的远程调用方式有以下2种:

  • RPC:Remote Produce Call远程过程调用,RPC基于Socket,工作在会话层。自定义数据格式,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型代表

  • Http:http其实是一种网络传输协议,基于TCP,工作在应用层,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议,也可以用来进行远程服务调用。缺点是消息封装臃肿,优势是对服务的提供和调用方没有任何技术限定,自由灵活,更符合微服务理念。

现在热门的Rest风格,就可以通过http协议来实现。

区别:RPC的机制是根据语言的API(language API)来定义的,而不是根据基于网络的应用来定义的。

如果你们公司全部采用Java技术栈,那么使用Dubbo作为微服务架构是一个不错的选择。

相反,如果公司的技术栈多样化,而且你更青睐Spring家族,那么Spring Cloud搭建微服务是不二之选。在我们的项目中,会选择Spring Cloud套件,因此会使用Http方式来实现服务间调用

2.2. Http客户端工具


既然微服务选择了Http,那么我们就需要考虑自己来实现对请求和响应的处理。不过开源世界已经有很多的http客户端工具,能够帮助我们做这些事情,例如:

  • HttpClient

  • OKHttp

  • URLConnection

不过这些不同的客户端,API各不相同。而Spring也有对http的客户端进行封装,提供了工具类叫RestTemplate,可以在Spring项目中使用RestTemplate进行服务调用。

2.3. Spring的RestTemplate


Spring提供了一个RestTemplate模板工具类,对基于Http的客户端进行了封装,并且实现了对象与json的序列化和反序列化,非常方便。RestTemplate并没有限定Http的客户端类型,而是进行了抽象,目前常用的3种都有支持:

  • HttpClient

  • OkHttp

  • JDK原生的URLConnection(默认的)

导入资料\http-demo 工程;在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述在这里插入图片描述

已经在导入的项目中的HttpDemoApplication 注册一个RestTemplate 对象,可以在启动类位置注册:

package com.itheima;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.context.annotation.Bean;

import org.springframework.web.client.RestTemplate;

@SpringBootApplication

public class HttpDemoApplication {

public static void main(String[] args) {

SpringApplication.run(HttpDemoApplication.class, args);

}

@Bean

public RestTemplate restTemplate() {

return new RestTemplate();

}

}

启动springboot项目,在项目中的测试类中直接@Autowired 注入:

package com.itheima.test;

import com.itheima.pojo.User;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.context.junit4.SpringRunner;

import org.springframework.web.client.RestTemplate;

@RunWith(SpringRunner.class)

@SpringBootTest

public class RestTemplateTest {

@Autowired

private RestTemplate restTemplate;

@Test

public void test() {

//如果要测试需要启动spring boot项目,以便获取数据

String url = “http://localhost/user/8”;

User user = restTemplate.getForObject(url, User.class);

System.out.println(user);

}

}

通过RestTemplate的getForObject()方法,传递url地址及实体类的字节码,RestTemplate会自动发起请求,接收响应,并且帮我们对响应结果进行反序列化。

在这里插入图片描述

了解完Http客户端工具,接下来就可以正式学习微服务了。

3. 初识Spring Cloud

=============================================================================

微服务是一种架构方式,最终肯定需要技术架构去实施。

微服务的实现方式很多,但是最火的莫过于Spring Cloud了。为什么?

  • 后台硬:作为Spring家族的一员,有整个Spring全家桶靠山,背景十分强大。

  • 技术强:Spring作为Java领域的前辈,可以说是功力深厚。有强力的技术团队支撑,一般人还真比不了

  • 群众基础好:可以说大多数程序员的成长都伴随着Spring框架,试问:现在有几家公司开发不用Spring?Spring Cloud与Spring的各个框架无缝整合,对大家来说一切都是熟悉的配方,熟悉的味道。

  • 使用方便:相信大家都体会到了SpringBoot给我们开发带来的便利,而Spring Cloud完全支持Spring Boot的开发,用很少的配置就能完成微服务框架的搭建

3.1. 简介


Spring Cloud是Spring旗下的项目之一,官网地址:http://projects.spring.io/spring-cloud/

Spring最擅长的就是集成,把世界上最好的框架拿过来,集成到自己的项目中。

Spring Cloud也是一样,它将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路由,负载均衡,熔断器,控制总线,集群状态等功能;协调分布式环境中各个系统,为各类服务提供模板性配置。其主要

涉及的组件包括:

  • Eureka:注册中心

  • Zuul、Gateway:服务网关

  • Ribbon:负载均衡

  • Feign:服务调用

  • Hystrix或Resilience4j:熔断器

以上只是其中一部分,架构图:在这里插入图片描述

3.2. 版本


Spring Cloud不是一个组件,而是许多组件的集合;它的版本命名比较特殊,是以A到Z的为首字母的一些单词(其实是伦敦地铁站的名字)组成:

在这里插入图片描述

我们在项目中,使用最新稳定的Greenwich版本。

4. 微服务场景模拟

======================================================================

首先,我们需要模拟一个服务调用的场景。方便后面学习微服务架构。

创建微服务父工程heima-springcloud、用户服务工程heima-service、服务消费工程consumer-demo。

  • 夫父工程—heima-springcloud:添加spring boot父坐标和管理其他组件的依赖

  • 用户服务工程—heima-service:整合mybatis查询数据库中用户数据;提供查询用户服务

  • 服务消费工程—consumer-demo:利用查询用户服务,获取用户数据并输出到浏览器

4.1. 创建父工程


微服务中需要同时创建多个项目,为了方便课堂演示,先创建一个父工程,然后后续的工程都以这个工程为父,实现maven的聚合。这样可以在一个窗口看到所有工程,方便讲解。在实际开发中,每个微服务可独立一个工程。

在这里插入图片描述

编写项目信息与保存位置:在这里插入图片描述

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns=“http://maven.apache.org/POM/4.0.0”

xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”

xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd”>

4.0.0

com.itheima

heima-springcloud

pom

1.0-SNAPSHOT

user-service

consumer-demo

org.springframework.boot

spring-boot-starter-parent

2.1.5.RELEASE

<java.version>1.8</java.version>

<spring-cloud.version>Greenwich.SR1</spring-cloud.version>

<mapper.starter.version>2.1.5</mapper.starter.version>

<mysql.version>8.0.22</mysql.version>

org.springframework.cloud

spring-cloud-dependencies

${spring-cloud.version}

pom

import

tk.mybatis

mapper-spring-boot-starter

${mapper.starter.version}

mysql

mysql-connector-java

${mysql.version}

org.springframework.cloud

spring-cloud-starter-config

org.projectlombok

lombok

org.springframework.boot

spring-boot-maven-plugin

通过scope的import可以继承spring-cloud-dependencies工程中的依赖

这里已经对大部分要用到的依赖的版本进行了 管理,方便后续使用

在这里插入图片描述

4.2. 服务提供者


新建一个项目user-service,对外提供查询用户的服务。

  1. 添加启动器依赖(web、通用Mapper)

  2. 创建启动引导类和配置文件

  3. 修改配置文件中的参数

  4. 编写测试代码(UserMapper、UserService、UserController)

  5. 测试

4.2.1. 创建module

选中父工程:heima-springcloud在这里插入图片描述

填写module信息:

在这里插入图片描述

注意,子模块要在父工程的下级目录:

4.2.2. 添加依赖

pom.xml 文件中的内容如下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns=“http://maven.apache.org/POM/4.0.0”

xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”

xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd”>

heima-springcloud

com.itheima

1.0-SNAPSHOT

4.0.0

user-service

org.springframework.boot

spring-boot-starter-web

tk.mybatis

mapper-spring-boot-starter

mysql

mysql-connector-java

项目结构:

在这里插入图片描述

4.2.3. 创建启动引导类

  1. 安装插件在这里插入图片描述

  2. 右键user-service工程,选择JBLSPringBootAppGen在这里插入图片描述在这里插入图片描述

  3. 生成后项目目录结构在这里插入图片描述

4.2.4. 编写配置文件

创建user-service\src\main\resources\application.yml 属性文件,这里我们采用了yaml语法,而不是properties:

application.yml

server:

port: 9091

spring:

datasource:

driverClassName: com.mysql.cj.jdbc.Driver

url: jdbc:mysql://localhost:3306/springcloud?characterEncoding=utf-8&serverTimezone=UTC&useSSL=false

username: root

password: 131415

mybatis:

type-aliases-package: com.itheima.user.pojo

4.2.5. 新建数据库

本地新建MySQL数据库springcloud

数据库语句:

/*

Navicat MySQL Data Transfer

Source Server : local3306

Source Server Version : 50622

Source Host : localhost:3306

Source Database : springboot_test

Target Server Type : MYSQL

Target Server Version : 50622

File Encoding : 65001

Date: 2019-04-14 18:39:07

*/

SET FOREIGN_KEY_CHECKS=0;


– Table structure for tb_user


DROP TABLE IF EXISTS tb_user;

CREATE TABLE tb_user (

id bigint(20) NOT NULL AUTO_INCREMENT,

user_name varchar(100) DEFAULT NULL COMMENT ‘用户名’,

password varchar(100) DEFAULT NULL COMMENT ‘密码’,

name varchar(100) DEFAULT NULL COMMENT ‘姓名’,

age int(10) DEFAULT NULL COMMENT ‘年龄’,

sex tinyint(1) DEFAULT NULL COMMENT ‘性别,1男性,2女性’,

birthday date DEFAULT NULL COMMENT ‘出生日期’,

note varchar(255) DEFAULT NULL COMMENT ‘备注’,

created datetime DEFAULT NULL COMMENT ‘创建时间’,

updated datetime DEFAULT NULL COMMENT ‘更新时间’,

PRIMARY KEY (id),

UNIQUE KEY username (user_name)

) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;


– Records of tb_user


INSERT INTO tb_user VALUES (‘1’, ‘zhangsan’, ‘123456’, ‘张三’, ‘30’, ‘1’, ‘1964-08-08’, ‘张三同学在学Java’, ‘2014-09-19 16:56:04’, ‘2014-09-21 11:24:59’);

INSERT INTO tb_user VALUES (‘2’, ‘lisi’, ‘123456’, ‘李四’, ‘21’, ‘2’, ‘1995-01-01’, ‘李四同学在传智学Java’, ‘2014-09-19 16:56:04’, ‘2014-09-19 16:56:04’);

INSERT INTO tb_user VALUES (‘3’, ‘wangwu’, ‘123456’, ‘王五’, ‘22’, ‘2’, ‘1994-01-01’, ‘王五同学在学php’, ‘2014-09-19 16:56:04’, ‘2014-09-19 16:56:04’);

INSERT INTO tb_user VALUES (‘4’, ‘zhangliu’, ‘123456’, ‘张六’, ‘20’, ‘1’, ‘1996-09-01’, ‘张六同学在传智播客学Java’, ‘2014-09-19 16:56:04’, ‘2014-09-19 16:56:04’);

INSERT INTO tb_user VALUES (‘5’, ‘lina’, ‘123456’, ‘李娜’, ‘28’, ‘1’, ‘1988-01-01’, ‘李娜同学在传智播客学Java’, ‘2014-09-19 16:56:04’, ‘2014-09-19 16:56:04’);

INSERT INTO tb_user VALUES (‘6’, ‘lilei’, ‘123456’, ‘李雷’, ‘23’, ‘1’, ‘1993-08-08’, ‘李雷同学在传智播客学Java’, ‘2014-09-20 11:41:15’, ‘2014-09-20 11:41:15’);

INSERT INTO tb_user VALUES (‘7’, ‘hanmeimei’, ‘123456’, ‘韩梅梅’, ‘24’, ‘2’, ‘1992-08-08’, ‘韩梅梅同学在传智播客学php’, ‘2014-09-20 11:41:15’, ‘2014-09-20 11:41:15’);

INSERT INTO tb_user VALUES (‘8’, ‘itcast’, ‘123456’, ‘传智播客’, ‘21’, ‘2’, ‘2008-07-08’, ‘传智播客搞IT教育’, ‘2014-09-20 11:41:15’, ‘2014-09-20 11:41:15’);

INSERT INTO tb_user VALUES (‘9’, ‘heima’, ‘123456’, ‘黑马’, ‘18’, ‘2’, ‘2012-08-08’, ‘黑马是传智播客高端品牌’, ‘2014-09-20 11:41:15’, ‘2014-09-20 11:41:15’);

INSERT INTO tb_user VALUES (‘10’, ‘linus’, ‘123456’, ‘林纳斯’, ‘45’, ‘2’, ‘1971-08-08’, ‘林纳斯搞了linux又搞git’, ‘2014-09-20 11:41:15’, ‘2014-09-20 11:41:15’);

INSERT INTO tb_user VALUES (‘11’, ‘leijun’, ‘123456’, ‘雷布斯’, ‘33’, ‘2’, ‘1983-08-08’, ‘小爱同学;are you ok’, ‘2014-09-20 11:41:15’, ‘2014-09-20 11:41:15’);

INSERT INTO tb_user VALUES (‘12’, ‘madaye’, ‘123456’, ‘马大爷’, ‘46’, ‘2’, ‘1980-08-08’, ‘马大爷花呗可以不还吗’, ‘2014-09-20 11:41:15’, ‘2014-09-20 11:41:15’);

4.2.6. 编写代码

编写 user-service\src\main\java\com\itheima\user\UserApplication.java 启动类:

package com.itheima.user;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication

@MapperScan(“com.itheima.user.mapper”)

public class UserApplication {

public static void main(String[] args) {

SpringApplication.run(UserApplication.class, args);

}

}

编写 user-service\src\main\java\com\itheima\user\pojo\User.java 实体类:

package com.itheima.user.pojo;

import lombok.Data;

import tk.mybatis.mapper.annotation.KeySql;

import javax.persistence.Id;

import javax.persistence.Table;

import java.util.Date;

@Data

@Table(name = “tb_user”)

public class User {

// id

@Id

//开启主键自动回填

@KeySql(useGeneratedKeys = true)

private Long id;

// 用户名

private String userName;

// 密码

private String password;

// 姓名

private String name;

// 年龄

private Integer age;

// 性别,1男性,2女性

private Integer sex;

// 出生日期

private Date birthday;

// 创建时间

private Date created;

// 更新时间

private Date updated;

// 备注

private String note;

}

编写 user-service\src\main\java\com\itheima\user\mapper\UserMapper.java

package com.itheima.user.mapper;

import com.itheima.user.pojo.User;

import tk.mybatis.mapper.common.Mapper;

public interface UserMapper extends Mapper {

}

编写 user-service\src\main\java\com\itheima\user\service\UserService.java

package com.itheima.user.service;

import com.itheima.user.mapper.UserMapper;

import com.itheima.user.pojo.User;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

@Service

public class UserService {

@Autowired(required = false)

private UserMapper userMapper;

/**

  • 根据主键查询用户

  • @param id 用户id

  • @return 用户

*/

public User queryById(long id) {

return userMapper.selectByPrimaryKey(id);

}

}

添加一个对外查询的接口处理器

user-service\src\main\java\com\itheima\user\controller\UserController.java

package com.itheima.user.controller;

import com.itheima.user.pojo.User;

import com.itheima.user.service.UserService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

@RestController

@RequestMapping(“/user”)

public class UserController {

@Autowired

private UserService userService;

@GetMapping(“/{id}”)

public User queryById(@PathVariable Long id) {

return userService.queryById(id);

}

}

创建完上述代码后项目结构:在这里插入图片描述

4.2.5. 启动并测试

启动 user-service 项目,访问接口:http://localhost:9091/user/8在这里插入图片描述

4.3. 服务调用者


访问http://localhost:9091/user/8。使用RestTemplate获取http://localhost:9091/user/8中的数据。

  • 添加启动器依赖

  • 创建启动引导类(注册RestTemplate)和配置文件

  • 编写代码(ConsumerController访问服务获取数据)

  • 测试验证

4.3.1. 创建工程

与上面类似,这里不再赘述,需要注意的是,我们调用user-service 的功能,因此不需要mybatis相关依赖了。

在这里插入图片描述

pom.xml 文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns=“http://maven.apache.org/POM/4.0.0”

xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”

xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd”>

heima-springcloud

com.itheima

1.0-SNAPSHOT

4.0.0

consumer-demo

org.springframework.boot

spring-boot-starter-web

项目结构如下:

在这里插入图片描述

4.3.2. 创建启动引导类

在这里插入图片描述

4.3.3. 编写代码

在启动引导类ConsumerApplication中注册RestTemplate

具体如下:

package com.itheima.consumer;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.context.annotation.Bean;

import org.springframework.web.client.RestTemplate;

@SpringBootApplication

public class ConsumerApplication {

public static void main(String[] args) {

SpringApplication.run(ConsumerApplication.class, args);

}

@Bean

public RestTemplate restTemplate() {

return new RestTemplate();

}

}

创建实体类 consumer-demo\src\main\java\com\itheima\consumer\pojo\User.java

package com.itheima.consumer.pojo;

import lombok.Data;

import java.util.Date;

@Data

public class User {

private Long id;

private String userName; // 用户名

private String password; // 密码

private String name;// 姓名

private Integer age;// 年龄

private Integer sex;// 性别,1男性,2女性

private Date birthday;// 出生日期

private Date created;// 创建时间

private Date updated;// 更新时间

private String note;// 备注

}

编写consumer-demo\src\main\java\com\itheima\consumer\controller\ConsumerController.java ,

在controller中直接调用RestTemplate,远程访问user-service 的服务接口:

package com.itheima.consumer.controller;

import com.itheima.consumer.pojo.User;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.client.RestTemplate;

@RestController

@RequestMapping(“/consumer”)

public class ConsumerController {

@Autowired

private RestTemplate restTemplate;

@GetMapping(“/{id}”)

public User queryById(@PathVariable Long id) {

String url = “http://localhost:9091/user/” + id;

return restTemplate.getForObject(url, User.class);

}

}

服务调用者项目结构:

在这里插入图片描述

4.3.3. 启动测试

启动 consumer-demo 引导启动类;因为 consumer-demo 项目没有配置端口,那么默认就是8080,

我们访问:http://localhost:8080/consumer/8

在这里插入图片描述

一个简单的远程服务调用案例就实现了。

4.3. 思考问题


简单回顾一下,刚才我们写了什么:

user-service:对外提供了查询用户的接口

consumer-demo:通过RestTemplate访问http://locahost:9091/user/{id} 接口,查询用户数据存在什么问题?

  • 在consumer中,我们把url地址硬编码到了代码中,不方便后期维护

  • consumer需要记忆user-service的地址,如果出现变更,可能得不到通知,地址将失效

  • consumer不清楚user-service的状态,服务宕机也不知道

  • user-service只有1台服务,不具备高可用性

  • 即便user-service形成集群,consumer还需自己实现负载均衡

其实上面说的问题,概括一下就是分布式服务必然要面临的问题:

  • 服务管理

① 如何自动注册和发现

② 如何实现状态监管

③ 如何实现动态路由

  • 服务如何实现负载均衡

  • 服务如何解决容灾问题

  • 服务如何实现统一配置

以上的问题,都将在SpringCloud中得到答案。

5. Eureka注册中心

=========================================================================

5.1. 认识Eureka


首先我们来解决第一问题,服务的管理。

问题分析

在刚才的案例中,user-service对外提供服务,需要对外暴露自己的地址。而consumer-demo(调用者)需要记录服务提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在现在日益复杂的互联网环境,一个项目可能会拆分出十几,甚至几十个微服务。此时如果还人为管理地址,不仅开发困难,将来测试、发布上线都会非常麻烦,这与DevOps的思想是背道而驰的。

DevOps的思想是系统可以通过一组过程、方法或系统;提高应用发布和运维的效率,降低管理成本。

网约车

这就好比是 网约车出现以前,人们出门叫车只能叫出租车。一些私家车想做出租却没有资格,被称为黑车。而很多人想要约车,但是无奈出租车太少,不方便。私家车很多却不敢拦,而且满大街的车,谁知道哪个才是愿意载人的。一个想要,一个愿意给,就是缺少引子,缺乏管理啊。

此时滴滴这样的网约车平台出现了,所有想载客的私家车全部到滴滴注册,记录你的车型(服务类型),身份信息(联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。

此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合需求的车到你面前,为你服务,完美!

Eureka做什么?

Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。

同时,服务提供方与Eureka之间通过**“心跳”**机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。

这就实现了服务的自动注册、发现、状态监控。

5.2. 原理图


基本架构:

在这里插入图片描述

在这里插入图片描述

  • Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址

  • 提供者:启动后向Eureka注册自己信息(地址,提供什么服务)

  • 消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新

  • 心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态

5.3. 入门案例


**Eureka是服务注册中心,只做服务注册。

自身并不提供服务,也不消费服务。可以搭建Web工程使用Eureka,可以使用SpringBoot方式搭建。**

搭建步骤:

  1. 创建工程

  2. 添加启动器依赖

  3. 编写启动引导类(添加Eureka的服务注解)和配置文件

  4. 修改配置文件(端口、应用名称。。。。)

  5. 启动测试

5.3.1. 搭建eureka-server工程

接下来创建一个项目eureka-server ,启动一个Eureka Server Application服务注册中心。

在这里插入图片描述

项目中的pom.xml 文件如下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns=“http://maven.apache.org/POM/4.0.0”

xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”

xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd”>

heima-springcloud

com.itheima

1.0-SNAPSHOT

4.0.0

eureka-server

org.springframework.cloud

spring-cloud-starter-netflix-eureka-server

生成启动引导类eureka-server\src\main\java\com\itheima\EurekaServerApplication.java

在这里插入图片描述

package com.itheima;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

//声明当前应用为eureka服务

@EnableEurekaServer

@SpringBootApplication

public class EurekaServerApplication {

public static void main(String[] args) {

SpringApplication.run(EurekaServerApplication.class);

}

}

编写配置文件

eureka-server\src\main\resources\application.yml

server:

port: 10086

spring:

application:

name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)

eureka:

client:

service-url:

EurekaServer的地址,现在是自己的地址,如果是集群,需要写其它Server的地址。

defaultZone: http://127.0.0.1:10086/eureka

不注册自己

register-with-eureka: false

不拉取服务

fetch-registry: false

启动服务

启动 eureka-server 访问:http://127.0.0.1:10086

在这里插入图片描述

在这里插入图片描述

5.3.2. 服务注册

在服务提供工程上添加Eureka客户端依赖:自动将服务注册到EurekaServer服务地址列表。

  1. 添加依赖

  2. 改造启动引导类;添加开启Eureka客户端发现的注解

  3. 修改配置文件;设置Eureka服务地址

注册服务,就是在服务上添加Eureka的客户端依赖,客户端代码会自动把服务注册到EurekaServer中。

  • 添加依赖

我们在user-service中添加Eureka客户端依赖:

org.springframework.cloud

spring-cloud-starter-netflix-eureka-client

  • 修改启动类

在启动类上开启Eureka客户端功能

通过添加@EnableDiscoveryClient 来开启Eureka客户端功能

@SpringBootApplication

@MapperScan(“com.itheima.user.mapper”)

@EnableDiscoveryClient // 开启Eureka客户端发现功能

public class UserServiceDemoApplication {

public static void main(String[] args) {

SpringApplication.run(UserServiceDemoApplication.class, args);

}

}

  • 修改配置文件

编写user-service\src\main\resources\application.yml配置文件为如下:

server:

port: 9091

spring:

datasource:

driver-class-name: com.mysql.jdbc.Driver

url: jdbc:mysql://localhost:3306/springcloud

username: root

password: root

application:

#应用名

name: user-service

mybatis:

type-aliases-package: cn.itcast.user.pojo

eureka:

client:

service-url:

defaultZone: http://localhost:10086/eureka

注意:

这里我们添加了spring.application.name属性来指定应用名称,将来会作为服务的id使用。

  • 测试

重启 user-service 项目,访问Eureka监控页面

在这里插入图片描述

我们发现user-service服务已经注册成功了

5.3.3. 服务发现

在服务消费工程上添加Eureka客户端依赖:可以使用工具类根据服务名称获取对应的服务地址列表。

  1. 添加依赖

  2. 修改启动引导类;添加开启Eureka客户端发现的注解

  3. 修改配置文件;设置Eureka服务地址

  4. 改造处理器类Controller,可以使用工具类DiscoveryClient根据服务名称获取对应服务地址列表。

接下来我们修改consumer-demo ,尝试从EurekaServer获取服务。

方法与服务提供者类似,只需要在项目中添加EurekaClient依赖,就可以通过服务名称来获取信息了!

  • 添加依赖

找到 consumer-demo\pom.xml 添加如下依赖

org.springframework.cloud

spring-cloud-starter-netflix-eureka-client

  • 修改启动类

修改 consumer-demo\src\main\java\com\itheima\consumer\ConsumerApplication.java 开启Eureka客户端

package com.itheima.consumer;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

import org.springframework.context.annotation.Bean;

import org.springframework.web.client.RestTemplate;

@SpringBootApplication

@EnableDiscoveryClient

public class ConsumerApplication {

public static void main(String[] args) {

SpringApplication.run(ConsumerApplication.class, args);

}

@Bean

public RestTemplate restTemplate() {

return new RestTemplate();

}

}

  • 新增配置文件

新增 consumer-demo\src\main\resources\application.yml 配置文件

server:

port: 8080

spring:

application:

name: consumer-demo # 应用名称

eureka:

client:

service-url: # EurekaServer地址

defaultZone: http://127.0.0.1:10086/eureka

  • 修改处理器

修改 consumer-demo\src\main\java\com\itheima\consumer\controller\ConsumerController.java 代码,

用DiscoveryClient类的方法,根据服务名称,获取服务实例。

package com.itheima.consumer.controller;

import com.itheima.consumer.pojo.User;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.cloud.client.ServiceInstance;

import org.springframework.cloud.client.discovery.DiscoveryClient;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController

@RequestMapping(“/consumer”)

public class ConsumerController {

@Autowired

private RestTemplate restTemplate;

@Autowired

private DiscoveryClient discoveryClient;

@GetMapping(“{id}”)

public User queryById(@PathVariable Long id) {

String url = “http://localhost:9091/user/” + id;

//获取eureka中注册的user-service实例列表

List serviceInstanceList =

discoveryClient.getInstances(“user-service”);

ServiceInstance serviceInstance = serviceInstanceList.get(0);

url = “http://” + serviceInstance.getHost() + “:” + serviceInstance.getPort()

  • “/user/” + id;

return restTemplate.getForObject(url, User.class);

}

}

  • Debug跟踪运行

重启 consumer-demo 项目;然后再浏览器中再次访问 http://localhost:8080/consumer/8 ;在代码中debug跟进查看最终拼接要访问的URL:在这里插入图片描述

5.4. Eureka详解


5.4.1. 基础架构

Eureka架构中的三个核心角色:

  • 服务注册中心

Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的eureka-server

  • 服务提供者

提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。本例中就是我们实现的user-service

  • 服务消费者

消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。本例中就是我们实现的consumer-demo

5.4.2. 高可用的Eureka Server

Eureka Server即服务的注册中心,在刚才的案例中,我们只有一个EurekaServer,事实上EurekaServer也可以是一个集群,形成高可用的Eureka中心。

服务同步

多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。

而作为客户端,需要把信息注册到每个Eureka中:

在这里插入图片描述

如果有三个Eureka,则每一个EurekaServer都需要注册到其它几个Eureka服务中,例如:有三个分别为10086、10087、10088,则:

  • 10086要注册到10087和10088上

  • 10087要注册到10086和10088上

  • 10088要注册到10086和10087上

动手搭建高可用的EurekaServer

我们假设要搭建两台EurekaServer的集群,端口分别为:10086和10087

  1. 修改原来的EurekaServer配置;修改 eureka-server\src\main\resources\application.yml 如下:

server:

port: ${port:10086}

spring:

application:

name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)

eureka:

client:

service-url:

EurekaServer的地址,现在是自己的地址,如果是集群,需要写其它Server的地址。

defaultZone: ${defaultZone:http://127.0.0.1:10086/eureka}

不注册自己

register-with-eureka: false

不拉取服务

fetch-registry: false

所谓的高可用注册中心,其实就是把EurekaServer自己也作为一个服务,注册到其它EurekaServer上,这样多个EurekaServer之间就能互相发现对方,从而形成集群。因此我们做了以下修改:

注意把register-with-eureka和fetch-registry修改为true或者注释掉

在上述配置文件中的${}表示在jvm启动时候若能找到对应port或者defaultZone参数则使用,若无则使用后面的默认值

把service-url的值改成了另外一台EurekaServer的地址,而不是自己

  1. 另外一台在启动的时候可以指定端口port和defaultZone配置:在这里插入图片描述

修改原来的启动配置组件;在如下界面中的 VM options 中

设置 -Dport=10086 -DdefaultZone=http:127.0.0.1:10087/eureka

在这里插入图片描述

复制一份并修改;在如下界面中的 VM options 中

设置 -Dport=10087 -DdefaultZone=http:127.0.0.1:10086/eureka

在这里插入图片描述

  1. 启动测试;同时启动两台eureka server在这里插入图片描述

  2. 客户端注册服务到集群

因为EurekaServer不止一个,因此user-service 项目注册服务或者consumer-demo 获取服务的时候,service-url参数需要修改为如下:

eureka:

client:

service-url: # EurekaServer地址,多个地址以’,'隔 开

defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka

为了方便上课和后面内容的修改,在测试完上述配置后可以再次改回单个eureka server的方式。

配置eureka客户端user-service的注册、续约等配置项。配置eureka客户端consumer-demo的获取服务间隔时间;了解失效剔除和组我保护

① Eureka客户端工程

○ user-service 服务提供

● 服务地址使用ip方式

● 续约

○ consumer-demo 服务消费

● 获取服务地址的频率

② Eureka服务端工程 eureka-server

○ 失效剔除

○ 自我保护

5.4.3. Eureka客户端

服务提供者要向EurekaServer注册服务,并且完成服务续约等工作。

服务注册

服务提供者在启动时,会检测配置属性中的: eureka.client.register-with-erueka=true 参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,EurekaServer会把这些信息保存到一个双层Map结构中。

  • 第一层Map的Key就是服务id,一般是配置中的spring.application.name 属性

  • 第二层Map的key是服务的实例id。一般host+ serviceId + port,例如: localhost:user-service:8081

  • 值则是服务的实例对象,也就是说一个服务,可以同时启动多个不同实例,形成集群。

默认注册时使用的是主机名或者localhost,如果想用ip进行注册,可以在user-service 中添加配置如下:

instance:

更倾向使用ip地址而不是host名

prefer-ip-address: true

IP地址

ip-address: 127.0.0.1

修改完后先后重启user-service 和consumer-demo ;在调用服务的时候就已经变成ip地址;需要注意的是:不是在eureka中的控制台服务实例状态显示。

服务续约

在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renew);

有两个重要参数可以修改服务续约的行为;可以在 user-service 中添加如下配置项:

eureka:

instance:

续约间隔,默认30秒

lease-renewal-interval-in-seconds: 30

服务失效时间,默认90秒

lease-expiration-duration-in-seconds: 90

  • lease-renewal-interval-in-seconds:服务续约(renew)的间隔,默认为30秒

  • lease-expiration-duration-in-seconds:服务失效时间,默认值90秒

也就是说,默认情况下每隔30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会定时(eureka.server.eviction-interval-timer-in-ms设定的时间)从服务列表中移除,这两个值在生产环境不要修改,默认即可。

获取服务列表

当服务消费者启动时,会检测eureka.client.fetch-registry=true 参数的值,如果为true,则会从EurekaServer服务的列表拉取只读备份,然后缓存在本地。并且每隔30秒会重新拉取并更新数据。可以在consumer-demo项目中通过下面的参数来修改

eureka:

client:

registry-fetch-interval-seconds: 30

5.4.5. 失效剔除和自我保护

如下的配置都是在Eureka Server服务端进行:

服务下线

当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务中心接受到请求之后,将该服务置为下线状态。

失效剔除

有时我们的服务可能由于内存溢出或网络故障等原因使得服务不能正常的工作,而服务注册中心并未收到“服务下线”的请求。相对于服务提供者的“服务续约”操作,服务注册中心在启动时会创建一个定时任务,默认每隔一段时间(默认为60秒)将当前清单中超时(默认为90秒)没有续约的服务剔除,这个操作被称为失效剔除。

可以通过eureka.server.eviction-interval-timer-in-ms 参数对其进行修改,单位是毫秒。

自我保护

我们关停一个服务,很可能会在Eureka面板看到一条警告:在这里插入图片描述

这是触发了Eureka的自我保护机制。当服务未按时进行心跳续约时,Eureka会统计服务实例最近15分钟心跳续约的比例是否低于了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka在这段时间内不会剔除任何服务实例,直到网络恢复正常。生产环境下这很有效,保证了大多数服务依然可用,不过也有可能获取到失败的服务实例,因此服务调用者必须做好服务的失败容错。

可以通过下面的配置来关停自我保护:

eureka:

server:

enable-self-preservation: false # 关闭自我保护模式(缺省为打开)

6. 负载均衡Ribbon

=========================================================================

在刚才的案例中,我们启动了一个user-service ,然后通过DiscoveryClient来获取服务实例信息,然后获取ip和端口来访问。

但是实际环境中,往往会开启很多个user-service 的集群。此时获取的服务列表中就会有多个,到底该访问哪一个呢?

一般这种情况下就需要编写负载均衡算法,在多个实例列表中进行选择。

不过Eureka中已经集成了负载均衡组件:Ribbon,简单修改代码即可使用。

什么是Ribbon:

在这里插入图片描述

接下来,我们就来使用Ribbon实现负载均衡。

6.1. 启动两个服务实例


首先我们配置启动两个user-service 实例,一个9091,一个9092。在这里插入图片描述

Eureka监控面板:在这里插入图片描述

6.2. 开启负载均衡


因为Eureka中已经集成了Ribbon,所以我们无需引入新的依赖。

直接修改 consumer-demo\src\main\java\com\itheima\consumer\ConsumerApplication.java

在RestTemplate的配置方法上添加@LoadBalanced 注解:

@Bean

@LoadBalanced

public RestTemplate restTemplate(){

return new RestTemplate();

}

修改consumer-demo\src\main\java\com\itheima\consumer\controller\ConsumerController.java 调用方式,不再手动获取ip和端口,而是直接通过服务名称调用;

@GetMapping(“{id}”)

public User queryById(@PathVariable(“id”) Long id){

String url=“http://user-service/user/”+id;

User user=restTemplate.getForObject(url,User.class);

return user;

}

访问页面,查看结果;并可以在9091和9092的控制台查看执行情况:

了解:Ribbon默认的负载均衡策略是轮询。SpringBoot也帮提供了修改负载均衡规则的配置入口在consumerdemo的配置文件中添加如下,就变成随机的了:

user-service:

ribbon:

NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

格式是: {服务名称}.ribbon.NFLoadBalancerRuleClassName

6.3. 源码跟踪


为什么只输入了service名称就可以访问了呢?之前还要获取ip和端口。

显然是有组件根据service名称,获取到了服务实例的ip和端口。因为consumer-demo 使用的是RestTemplate,spring的负载均衡自动配置类LoadBalancerAutoConfiguration.LoadBalancerInterceptorConfig 会自动配置负载均衡拦截器(在spring-cloud-commons-**.jar包中的spring.factories中定义的自动配置类), 它就是LoadBalancerInterceptor ,这个类会在对RestTemplate的请求进行拦截,然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id。

我们进行源码跟踪:在这里插入图片描述

继续跟入execute方法:发现获取了9092端口的服务在这里插入图片描述

再跟下一次,发现获取的是9091、9092之间切换:在这里插入图片描述

多次访问consumer-demo 的请求地址;然后跟进代码,发现其果然实现了负载均衡。

7. 熔断器Hystrix

=========================================================================

7.1. 简介


Hystrix 在英文里面的意思是 豪猪,它的logo 看下面的图是一头豪猪,它在微服务系统中是一款提供保护机制的组

件,和eureka一样也是由netflix公司开发。

主页:https://github.com/Netflix/Hystrix/

在这里插入图片描述

那么Hystrix的作用是什么呢?具体要保护什么呢?

Hystrix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。

7.2. 雪崩问题


微服务中,服务间调用关系错综复杂,一个请求,可能需要调用多个微服务接口才能实现,会形成非常复杂的调用链路:在这里插入图片描述

如图,一次业务请求,需要调用A、P、H、I四个务,这四个服务又可能调用其它服务。

如果此时,某个服务出现异常:

在这里插入图片描述

例如: 微服务I 发生异常,请求阻塞,用户请求就不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞:

在这里插入图片描述

服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应。

这就好比,一个汽车生产线,生产不同的汽车,需要使用不同的零件,如果某个零件因为种种原因无法使用,那么就会造成整台车无法装配,陷入等待零件的状态,直到零件到位,才能继续组装。 此时如果有很多个车型都需要这个零件,那么整个工厂都将陷入等待的状态,导致所有生产都陷入瘫痪。一个零件的波及范围不断扩大。

Hystrix解决雪崩问题的手段主要是服务降级,包括:

-线程隔离

  • 服务熔断

7.3. 线程隔离&服务降级


7.3.1. 原理

线程隔离示意图:在这里插入图片描述

解读:

  • Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队,加速失败判定时间。

  • 用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理,什么是服务降级?

服务降级:优先保证核心服务,而非核心服务不可用或弱可用。

用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息) 。

服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。

触发Hystrix服务降级的情况:

  • 线程池已满

  • 请求超时

7.3.2. 动手实践

  1. 引入依赖

在consumer-demo 消费端系统的pom.xml文件添加如下依赖:

org.springframework.cloud

spring-cloud-starter-netflix-hystrix

  1. 开启熔断

在启动类ConsumerApplication 上添加注解:@EnableCircuitBreaker

@SpringBootApplication

@EnableDiscoveryClient

@EnableCircuitBreaker

public class ConsumerApplication {

// …

}

可以看到,我们类上的注解越来越多,在微服务中,经常会引入上面的三个注解,于是Spring就提供了一个组合注解:@SpringCloudApplication

在这里插入图片描述

因此,我们可以使用这个组合注解来代替之前的3个注解。

@SpringCloudApplication

public class ConsumerApplication {

// …

}

  1. 编写降级逻辑

当目标服务的调用出现故障,我们希望快速失败,给用户一个友好提示。因此需要提前编写好失败时的降级处理逻辑,要使用HystrixCommand来完成。

改造consumer-demo\src\main\java\com\itheima\consumer\controller\ConsumerController.java 处理器类,如下:

package com.itheima.consumer.controller;

import com.itheima.consumer.pojo.User;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.cloud.client.ServiceInstance;

import org.springframework.cloud.client.discovery.DiscoveryClient;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController

@RequestMapping(“/consumer”)

@Slf4j

public class ConsumerController {

@Autowired

private RestTemplate restTemplate;

@Autowired

private DiscoveryClient discoveryClient;

@GetMapping(“{id}”)

@HystrixCommand(fallbackMethod = “queryByIdFallback”)

public String queryById(@PathVariable Long id) {

String url = “http://localhost:9091/user/” + id;

//获取eureka中注册的user-service实例列表

/*List serviceInstanceList =

discoveryClient.getInstances(“user-service”);

ServiceInstance serviceInstance = serviceInstanceList.get(0);

url = “http://” + serviceInstance.getHost() + “:” + serviceInstance.getPort()

  • “/user/” + id;*/

url = “http://user-service/user/” + id;

return restTemplate.getForObject(url, String.class);

}

public String queryByIdFallback(Long id) {

log.error(“查询用户信息失败。id:{}”, id);

return “对不起,网络太拥挤了!”;

}

}

*/

要注意;因为熔断的降级逻辑方法必须跟正常逻辑方法保证:相同的参数列表和返回值声明。

失败逻辑中返回User对象没有太大意义,一般会返回友好提示。所以把queryById的方法改造为返回String,反正也是Json数据。这样失败逻辑中返回一个错误说明,会比较方便。

说明:

@HystrixCommand(fallbackMethod = “queryByIdFallBack”):用来声明一个降级逻辑的方法

测试:

当user-service 正常提供服务时,访问与以前一致。但是当将user-service 停机时,会发现页面返回了降级处理

信息:

在这里插入图片描述

  1. 默认的Fallback

刚才把fallback写在了某个业务方法上,如果这样的方法很多,那岂不是要写很多。所以可以把Fallback配置加在类上,实现默认fallback;

再次改造 consumer-demo\src\main\java\com\itheima\consumer\controller\ConsumerController.java

package com.itheima.consumer.controller;

import com.itheima.consumer.pojo.User;

import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.cloud.client.ServiceInstance;

import org.springframework.cloud.client.discovery.DiscoveryClient;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController

@RequestMapping(“/consumer”)

@Slf4j

@DefaultProperties(defaultFallback = “defaultFallback”)

public class ConsumerController {

@Autowired

private RestTemplate restTemplate;

@Autowired

private DiscoveryClient discoveryClient;

@GetMapping(“{id}”)

//@HystrixCommand(fallbackMethod = “queryByIdFallback”)

@HystrixCommand

public String queryById(@PathVariable Long id) {

String url = “http://localhost:9091/user/” + id;

//获取eureka中注册的user-service实例列表

/*List serviceInstanceList =

discoveryClient.getInstances(“user-service”);

ServiceInstance serviceInstance = serviceInstanceList.get(0);

url = “http://” + serviceInstance.getHost() + “:” + serviceInstance.getPort()

  • “/user/” + id;*/

url = “http://user-service/user/” + id;

return restTemplate.getForObject(url, String.class);

}

public String queryByIdFallback(Long id) {

log.error(“查询用户信息失败。id:{}”, id);

return “对不起,网络太拥挤了!”;

}

public String defaultFallback() {

return “默认提示:对不起,网络太拥挤了!”;

}

}

@DefaultProperties(defaultFallback = “defaultFallBack”):在类上指明统一的失败降级方法;该类中所有方法返回类型要与处理失败的方法的返回类型一致。在这里插入图片描述

  1. 超时设置

在之前的案例中,请求在超过1秒后都会返回错误信息,这是因为Hystrix的默认超时时长为1,我们可以通过配置修

改这个值;修改 consumer-demo\src\main\resources\application.yml 添加如下配置:

hystrix:

command:

default:

execution:

isolation:

thread:

timeoutInMilliseconds: 2000

这个配置会作用于全局所有方法。为了方便复制到yml配置文件中,可以复制hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=2000 到yml文件中会自动格式化后再进行修改。为了触发超时,可以在user-service\src\main\java\com\itheima\user\service\UserService.java 的方法中休眠2秒;

@Service

public class UserService {

@Autowired

private UserMapper userMapper;

public User queryById(Long id) {

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}

return userMapper.selectByPrimaryKey(id);

}

}

测试:

在这里插入图片描述

可以发现,请求的时长已经到了2s+,证明配置生效了。如果把修改时间修改到2秒以下,又可以正常访问。

7.4 服务熔断


7.4.1. 熔断原理

在服务熔断中,使用的熔断器,也叫断路器,其英文单词为:Circuit Breaker

熔断机制与家里使用的电路熔断原理类似;当如果电路发生短路的时候能立刻熔断电路,避免发生灾难。在分布式系统中应用服务熔断后;服务调用方可以自己进行判断哪些服务反应慢或存在大量超时,可以针对这些服务进行主动熔断,防止整个系统被拖垮。

Hystrix的服务熔断机制,可以实现弹性容错;当服务请求情况好转之后,可以自动重连。通过断路的方式,将后续请求直接拒绝,一段时间(默认5秒)之后允许部分请求通过,如果调用成功则回到断路器关闭状态,否则继续打开,拒绝请求的服务。

Hystrix的熔断状态机模型:在这里插入图片描述

状态机有3个状态:

  • Closed:关闭状态(断路器关闭),所有请求都正常访问。

  • Open:打开状态(断路器打开),所有请求都会被降级。Hystrix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次数最少不低于20次。

  • Half Open:半开状态,不是永久的,断路器打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,则会关闭断路器,否则继续保持打开,再次进行休眠计时

7.4.2 动手实践

为了能够精确控制请求的成功或失败,在consumer-demo 的处理器业务方法中加入一段逻辑;

修改 consumer-demo\src\main\java\com\itheima\consumer\controller\ConsumerController.java

@GetMapping(“{id}”)

@HystrixCommand

public String queryById(@PathVariable(“id”) Long id){

if(id==1){

throw new RuntimeException(“太忙了”);

}

String url=“http://user-service/user/”+id;

String user=restTemplate.getForObject(url,String.class);

return user;

}

这样如果参数是id为1,一定失败,其它情况都成功。(不要忘了清空user-service中的休眠逻辑)

我们准备两个请求窗口:

当我们疯狂访问id为1的请求时(超过20次),就会触发熔断。断路器会打开,一切请求都会被降级处理。

此时你访问id为2的请求,会发现返回的也是失败,而且失败时间很短,只有20毫秒左右;因进入半开状态之后2是可以的。

在这里插入图片描述

不过,默认的熔断触发要求较高,休眠时间窗较短,为了测试方便,我们可以通过配置修改熔断策略:

配置熔断策略:

hystrix:

command:

default:

circuitBreaker:

errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%

sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒

requestVolumeThreshold: 10 # 熔断触发最小请求次数,默认值是20

execution:

isolation:

thread:

timeoutInMilliseconds: 2000 # 熔断超时设置,默认为1秒

为了方便复制上述配置,可以使用如下格式复制到yml文件中会自动格式化:

hystrix.command.default.circuitBreaker.requestVolumeThreshold=10

hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=10000

hystrix.command.default.circuitBreaker.errorThresholdPercentage=50

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=2000

上述的配置项可以参考 HystrixCommandProperties 类中。

8. Feign

====================================================================

在前面的学习中,使用了Ribbon的负载均衡功能,大大简化了远程调用时的代码:

String url = “http://user-service/user/” + id;

User user = this.restTemplate.getForObject(url, User.class)

如果就学到这里,你可能以后需要编写类似的大量重复代码,格式基本相同,无非参数不一样。有没有更优雅的方式,来对这些代码再次优化呢?

这就是接下来要学的Feign的功能了。

8.1. 简介


Feign也叫伪装:

Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。

项目主页:https://github.com/OpenFeign/feign

8.2. 快速入门


8.2.1. 导入依赖

在consumer-demo 项目的pom.xml 文件中添加如下依赖

org.springframework.cloud

spring-cloud-starter-openfeign

8.2.2. Feign的客户端

在consumer-demo 中编写如下Feign客户端接口类:

package com.itheima.consumer.client;

import com.itheima.consumer.pojo.User;

import org.springframework.cloud.openfeign.FeignClient;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(“user-service”)

public interface UserClient {

@GetMapping(“/user/{id}”)

User queryById(@PathVariable(“id”) Long id);

}

  • 首先这是一个接口,Feign会通过动态代理,帮我们生成实现类。这点跟mybatis的mapper很像

  • @FeignClient ,声明这是一个Feign客户端,同时通过value 属性指定服务名称

  • 接口中的定义方法,完全采用SpringMVC的注解,Feign会根据注解帮我们生成URL,并访问获取结果

  • @GetMapping中的/user,请不要忘记;因为Feign需要拼接可访问的地址

编写新的控制器类ConsumerFeignController ,使用UserClient访问:

package com.itheima.consumer.controller;

import com.itheima.consumer.client.UserClient;

import com.itheima.consumer.pojo.User;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

@RestController

@RequestMapping(“/cf”)

public class ConsumerFeignController {

@Autowired

private UserClient userClient;

@GetMapping(“/{id}”)

public User queryById(@PathVariable Long id) {

return userClient.queryById(id);

}

}

8.2.3. 开启Feign功能

在ConsumerApplication 启动类上,添加注解,开启Feign功能

package com.itheima.consumer;

import org.springframework.boot.SpringApplication;

import org.springframework.cloud.client.SpringCloudApplication;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;

import org.springframework.cloud.openfeign.EnableFeignClients;

import org.springframework.context.annotation.Bean;

import org.springframework.web.client.RestTemplate;

/*@SpringBootApplication

@EnableDiscoveryClient

@EnableCircuitBreaker*/

@SpringCloudApplication

@EnableFeignClients//开启Feign功能

public class ConsumerApplication {

public static void main(String[] args) {

SpringApplication.run(ConsumerApplication.class, args);

}

@Bean

@LoadBalanced

public RestTemplate restTemplate() {

return new RestTemplate();

}

}

Feign中已经自动集成了Ribbon负载均衡,因此不需要自己定义RestTemplate进行负载均衡的配置。

8.2.4. 启动测试

访问接口:http://localhost:8080/cf/2在这里插入图片描述

正常获取到了结果。

8.3. 负载均衡


Feign中本身已经集成了Ribbon依赖和自动配置:

在这里插入图片描述

因此不需要额外引入依赖,也不需要再注册RestTemplate 对象。

Fegin内置的ribbon默认设置了请求超时时长,默认是1000,我们可以通过手动配置来修改这个超时时长:

ribbon:

ReadTimeout: 2000 # 读取超时时长

ConnectTimeout: 1000 # 建立链接的超时时长

因为ribbon内部有重试机制,一旦超时,会自动重新发起请求。如果不希望重试,可以添加配置:

修改 consumer-demo\src\main\resources\application.yml 添加如下配置:

ribbon:

ConnectTimeout: 1000 # 连接超时时长

ReadTimeout: 2000 # 数据通信超时时长

MaxAutoRetries: 0 # 当前服务器的重试次数

MaxAutoRetriesNextServer: 0 # 重试多少次服务

OkToRetryOnAllOperations: false # 是否对所有的请求方式都重试

重新给UserService的方法设置上线程沉睡时间2秒可以测试上述配置

8.4. Hystrix支持


Feign默认也有对Hystrix的集成:

在这里插入图片描述

只不过,默认情况下是关闭的。需要通过下面的参数来开启;修改 consumer-demo\src\main\resources\application.yml 添加如下配置:

feign:

hystrix:

enabled: true # 开启Feign的熔断功能

但是,Feign中的Fallback配置不像Ribbon中那样简单了。

  1. 首先,要定义一个类,实现刚才编写的UserFeignClient,作为fallback的处理类

package com.itheima.consumer.client.fallback;

import com.itheima.consumer.client.UserClient;

import com.itheima.consumer.pojo.User;

import org.springframework.stereotype.Component;

@Component

public class UserClientFallback implements UserClient {

@Override

public User queryById(Long id) {

User user = new User();

user.setId(id);

user.setName(“用户异常”);

return user;

}

}

  1. 然后在UserFeignClient中,指定刚才编写的实现类

@FeignClient(value = “user-service”, fallback = UserClientFallback.class)

public interface UserClient {

@GetMapping(“/user/{id}”)

User queryById(@PathVariable(“id”) Long id);

}

  1. 重启测试

重启启动 consumer-demo 并关闭user-service 服务,然后在页面访问:http://localhost:8080/cf/8在这里插入图片描述

8.5. 请求压缩


Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数即可开启请求与响应的压缩功能:

feign:

compression:

request:

enabled: true # 开启请求压缩

response:

enabled: true # 开启响应压缩

同时,我们也可以对请求的数据类型,以及触发压缩的大小下限进行设置:

feign:

compression:

request:

enabled: true # 开启请求压缩

mime-types: text/html,application/xml,application/json # 设置压缩的数据类型

min-request-size: 2048 # 设置触发压缩的大小下限

注:上面的数据类型、压缩大小下限均为默认值。

8.6. 日志级别


前面讲过,通过logging.level.xx=debug 来设置日志级别。然而这个对Fegin客户端而言不会产生效果。因为@FeignClient 注解修改的客户端在被代理时,都会创建一个新的Fegin.Logger实例。我们需要额外指定这个日志的级别才可以。

  1. 在consumer-demo 的配置文件中设置com.itheima包下的日志级别都为debug修改 consumer-demo\src\main\resources\application.yml 添加如下配置:

logging:

level:

com.itheima: debug

  1. 在consumer-demo 编写FeignConfig配置类,定义日志级别

package com.itheima.consumer.config;

import feign.Logger;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

public class FeignConfig {

@Bean

Logger.Level feignLoggerLevel() {

//记录所有请求和响应的明细,包括头信息、请求体、元数据

return Logger.Level.FULL;

}

}

这里指定的Level级别是FULL,Feign支持4种级别:

① NONE:不记录任何日志信息,这是默认值。

② BASIC:仅记录请求的方法,URL以及响应状态码和执行时间

③ HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息

④ FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

  1. 在consumer-demo 的UserClient 接口类上的@FeignClient注解中指定配置类:

package com.itheima.consumer.client;

import com.itheima.consumer.client.fallback.UserClientFallback;

import com.itheima.consumer.config.FeignConfig;

import com.itheima.consumer.pojo.User;

import org.springframework.cloud.openfeign.FeignClient;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(value = “user-service”, fallback = UserClientFallback.class,

configuration = FeignConfig.class)

public interface UserClient {

@GetMapping(“/user/{id}”)

User queryById(@PathVariable Long id);

}

  1. 重启项目,访问:http://localhost:8080/cf/8 ;即可看到每次访问的日志:在这里插入图片描述

9. Spring Cloud Gateway网关

=====================================================================================

9.1. 简介


  • Spring Cloud Gateway是Spring官网基于Spring 5.0、

  • Spring Boot 2.0、Project Reactor等技术开发的网关服

务。

Spring Cloud Gateway基于Filter链提供网关基本功能:安全、监控/埋点、限流等。

Spring Cloud Gateway为微服务架构提供简单、有效且统一的API路由管理方式。

Spring Cloud Gateway是替代Netflix Zuul的一套解决方案。Spring Cloud Gateway组件的核心是一系列的过滤器,通过这些过滤器可以将客户端发送的请求转发(路由)到对应的微服务。 Spring Cloud Gateway是加在整个微服务最前沿的防火墙和代理器,隐藏微服务结点IP端口信息,从而加强安全保护。Spring Cloud Gateway本身也是一个微服务,需要注册到Eureka服务注册中心。

网关的核心功能是:过滤和路由

9.2. Gateway加入后的架构


在这里插入图片描述

不管是来自于客户端(PC或移动端)的请求,还是服务内部调用。一切对服务的请求都可经过网关,然后再由

网关来实现 鉴权、动态路由等等操作。Gateway就是我们服务的统一入口。

9.3. 核心概念


  • 路由(route) 路由信息的组成:由一个ID、一个目的URL、一组断言工厂、一组Filter组成。如果路由断言为真,说明请求URL和配置路由匹配。

  • 断言(Predicate) Spring Cloud Gateway中的断言函数输入类型是Spring 5.0框架中的ServerWebExchange。Spring Cloud Gateway的断言函数允许开发者去定义匹配来自于Http Request中的任何信息比如请求头和参数。

  • 过滤器(Filter) 一个标准的Spring WebFilter。 Spring Cloud Gateway中的Filter分为两种类型的Filter,分别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理。

9.4. 快速入门


9.4.1. 新建工程

填写基本信息:

在这里插入图片描述

打开 heima-springcloud\heima-gateway\pom.xml 文件修改为如下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns=“http://maven.apache.org/POM/4.0.0”

xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

http://maven.apache.org/xsd/maven-4.0.0.xsd">

heima-springcloud

com.itheima

1.0-SNAPSHOT

4.0.0

com.itheima

heima-gateway

org.springframework.cloud

spring-cloud-starter-gateway

org.springframework.cloud

spring-cloud-starter-netflix-eureka-client

9.4.2. 编写启动类

在heima-gateway中创建com.itheima.gateway.GatewayApplication 启动类

package com.itheima.gateway;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication

@EnableDiscoveryClient

public class GatewayApplication {

public static void main(String[] args) {

SpringApplication.run(GatewayApplication.class, args);

}

}

9.4.3 编写配置

创建heima-gateway\src\main\resources\application.yml 文件,内容如下:

server:

port: 10010

spring:

application:

name: api-gateway

eureka:

client:

service-url:

defaultZone: http://127.0.0.1:10086/eureka

instance:

prefer-ip-address: true

9.4.4. 编写路由规则

需要用网关来代理user-service 服务,先看一下控制面板中的服务状态:在这里插入图片描述

  • ip为:127.0.0.1

  • 端口为:9091

修改heima-gateway\src\main\resources\application.yml 文件为:

server:

port: 10010

spring:

application:

name: api-gateway

cloud:

gateway:

routes:

路由id,可以随意写

  • id: user-service-route

代理的服务地址

uri: http://127.0.0.1:9091

路由断言,可以配置映射路径

predicates:

  • Path=/user/**

eureka:

client:

service-url:

defaultZone: http://127.0.0.1:10086/eureka

instance:

prefer-ip-address: true

将符合Path 规则的一切请求,都代理到 uri 参数指定的地址

本例中,我们将路径中包含有 /user/** 开头的请求,代理到http://127.0.0.1:9091

9.4.5. 启动测试

访问的路径中需要加上配置规则的映射路径,我们访问:http://localhost:10010/user/8

在这里插入图片描述

9.5. 面向服务的路由


在刚才的路由规则中,把路径对应的服务地址写死了!如果同一服务有多个实例的话,这样做显然不合理。

应该根据服务的名称,去Eureka注册中心查找 服务对应的所有实例列表,然后进行动态路由!

9.5.1 修改映射配置,通过服务名称获取

因为已经配置了Eureka客户端,可以从Eureka获取服务的地址信息。

修改heima-gateway\src\main\resources\application.yml 文件如下:

server:

port: 10010

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java SpringCloud微服务是一种基于Spring Cloud框架的微服务架构。它通过将一个大型应用程序拆分为多个小型的、自治的服务来实现系统的解耦和扩展性。每个微服务都具有独立的数据库和业务逻辑,并且可以独立部署和扩展。Spring Cloud提供了一系列的组件和工具,如服务注册与发现、负载均衡、熔断器、配置管理等,来支持微服务的开发和管理。其中,Spring Cloud Bus是Spring Cloud中的消息总线方案,它结合Spring Cloud Config可以实现配置信息的自动更新,提供了一种方便的方式来管理微服务的配置。 为了进一步学习和了解Java微服务的实战应用,可以关注小编的博文《Java微服务——SpringCloud实战篇》,它将介绍如何整合Gateway、Config和Bus等组件来构建和管理Java微服务。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Java微服务——SpringCloud概念篇](https://blog.csdn.net/friend_X/article/details/116127269)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [Java微服务篇2——SpringCloud](https://blog.csdn.net/qq_43842093/article/details/120171070)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值