• 将基础服务进行了抽取,系统间相互调用,提高了代码复用和开发效率

缺点:

  • 系统间耦合度变高,调用关系错综复杂,难以维护

1.4. 面向服务架构(SOA)


SOA(Service Oriented Architecture)面向服务的架构:它是一种设计方法,其中包含多个服务, 服务之间通过相互依赖最终提供一系列的功能。一个服务 通常以独立的形式存在与操作系统进程中。各个服务之间 通过网络调用。

SOA结构图:在这里插入图片描述

ESB(企业服务总线),简单 来说 ESB 就是一根管道,用来连接各个服务节点。为了集 成不同系统,不同协议的服务,ESB 做了消息的转化解释和路由工作,让不同的服务互联互通。

SOA缺点:每个供应商提供的ESB产品有偏差,自身实现较为复杂;应用服务粒度较大,ESB集成整合所有服务和协议、数据转换使得运维、测试部署困难。所有服务都通过一个通路通信,直接降低了通信速度。

1.5. 微服务架构


微服务架构是使用一套小服务来开发单个应用的方式或途径,每个服务基于单一业务能力构建,运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API,并能够通过自动化部署机制来独立部署。这些服务可以使用不同的编程语言实现,以及不同数据存储技术,并保持最低限度的集中式管理。

微服务结构图:在这里插入图片描述

API Gateway网关是一个服务器,是系统的唯一入口。为每个客户端提供一个定制的API。API网关核心是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。如它还可以具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。通常,网关提供RESTful/HTTP的方式访问服务。而服务端通过服务注册中心进行服务注册和管理。

微服务的特点:

  • 单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责

  • 微:微服务的服务拆分粒度很小,例如一个用户管理就可以作为一个服务。每个服务虽小,但“五脏俱全”。

  • 面向服务:面向服务是说每个服务都要对外暴露Rest风格服务接口API。并不关心服务的技术实现,做到与平台

和语言无关,也不限定用什么技术实现,只要提供Rest的接口即可。

  • 自治:自治是说服务间互相独立,互不干扰

① 团队独立:每个服务都是一个独立的开发团队,人数不能过多。

② 技术独立:因为是面向服务,提供Rest接口,使用什么技术没有别人干涉

③ 前后端分离:采用前后端分离开发,提供统一Rest接口,后端不用再为PC、移动端开发不同接口

④ 数据库分离:每个服务都使用自己的数据源

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

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

微服务架构与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;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

分享

1、算法大厂——字节跳动面试题

2、2000页互联网Java面试题大全

3、高阶必备,算法学习

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;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-BaKWkZ6w-1711698089092)]
[外链图片转存中…(img-pSBRKgLx-1711698089093)]
[外链图片转存中…(img-yCVlpOKt-1711698089093)]
[外链图片转存中…(img-I1lIj3m7-1711698089093)]
[外链图片转存中…(img-qAEJnDqN-1711698089094)]
[外链图片转存中…(img-41zfpLwP-1711698089094)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-IykXEDJQ-1711698089094)]

分享

1、算法大厂——字节跳动面试题

[外链图片转存中…(img-6cq1JweW-1711698089095)]

2、2000页互联网Java面试题大全

[外链图片转存中…(img-Ekdc9YoE-1711698089095)]

3、高阶必备,算法学习

[外链图片转存中…(img-ysmNVtjc-1711698089095)]

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐