SpringCloud实践

大家谈起的微服务,大多来讲说的只不过是种架构方式。其实现方式很多种:Spring Cloud,Dubbo,华为的Service Combo,Istio 。

那么这么多的微服务架构产品中,我们为什么要用Spring Cloud?因为它后台硬、技术强、群众基础好,使用方便;

技术架构演变

(1)单一应用架构

当网站流量很小时,只需要一个应用,所有功能部署在一起,减少部署节点成本的框架称之为集中式框架。此时,用于简化增删改查工作量的数据访问框架(ORM)是影响项目开发的关键。

1563128795202

(2)垂直应用架构

当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。

1563128890388

(3)分布式服务架构

当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。

1563129132684

(4)面向服务(SOA)架构

典型代表有两个:流动计算架构和微服务架构;

流动计算架构:

当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。流动计算架构的最佳实践阿里的Dubbo。

微服务架构

与流动计算架构很相似,除了具备流动计算架构优势外,微服务架构中的微服务可以独立部署,独立发展。且微服务的开发不会限制于任何技术栈。微服务架构的最佳实践是SpringCloud。

1563128667644

SpringCloud简介

(1)SpringCloud介绍

Spring Boot擅长的是集成,把世界上最好的框架集成到自己项目中

Spring Cloud本身也是基于SpringBoot开发而来,SpringCloud是一系列框架的有序集合,也是把非常流行的微服务的技术整合到一起,是属于微服务架构的一站式技术解决方案

Spring Cloud包含了:

注册中心:Eureka、consul、Zookeeper、nacos

负载均衡:Ribbon

熔断器:Hystrix

服务通信:Feign

网关:Gateway

配置中心 :config,nacos

消息总线:Bus

集群状态等等功能。

Spring Cloud协调分布式环境中各个微服务,为各类服务提供支持。

(2)Spring Cloud的版本

1563019084631

版本说明:

SpringCloud是一系列框架组合,为了避免与框架版本产生混淆,采用新的版本命名方式,形式为大版本名+子版本名称
大版本名用伦敦地铁站名
子版本名称三种
SNAPSHOT:快照版本,尝鲜版,随时可能修改
M版本,MileStone,M1表示第一个里程碑版本,一般同时标注PRE,表示预览版
SR,Service Release,SR1表示第一个正式版本,同时标注GA(Generally Available),稳定版

(3)SpringCloud与SpringBoot版本匹配关系

SpringBoot SpringCloud
1.2.x Angel版本
1.3.x Brixton版本
1.4.x Camden版本
1.5.x Dalston版本、Edgware
2.0.x Finchley版本
2.1.x Greenwich GA版本 (2019年2月发布)

鉴于SpringBoot与SpringCloud关系,SpringBoot建议采用2.1.x版本

spring cloud 和dubbo对比

1607669938278

SpringCloud 总架构图

1558286268166

微服务调用

RPC和HTTP

常见远程调用方式:

RPC:(Remote Produce Call)远程过程调用

1.基于Socket
2.自定义数据格式
3.速度快,效率高
4.典型应用代表:Dubbo,WebService,ElasticSearch集群间互相调用

HTTP:网络传输协议

1.基于TCP/IP
2.规定数据传输格式
3.缺点是消息封装比较臃肿、传输速度比较慢
4.优点是对服务提供和调用方式没有任何技术限定,自由灵活,更符合微服务理念

RPC和HTTP的区别:RPC是根据语言API来定义,而不是根据基于网络的应用来定义。

Http客户端工具:

常见Http客户端工具:HttpClient、OKHttp、URLConnection。

Spring的RestTemplate

(1)RestTemplate介绍

  • RestTemplate是Rest的HTTP客户端模板工具类
  • 对基于Http的客户端进行封装
  • 实现对象与JSON的序列化与反序列化
  • 不限定客户端类型,目前常用的3种客户端都支持:HttpClient、OKHttp、JDK原生URLConnection(默认方式)

(2)入门案例

1563020200215

我们可以使用RestTemplate实现上图中的请求远程调用

(1)搭建itheima-rest-provider

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">
<modelVersion>4.0.0</modelVersion>

<groupId>com.itheima</groupId>
<artifactId>itheima-rest-provider</artifactId>
<version>1.0-SNAPSHOT</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

</project>

创建com.itheima.pojo.User

package com.itheima.pojo;

import java.io.Serializable;

public class User implements Serializable {
private Integer id;
private String name;

public User() {
}

public User(Integer id, String name) {
this.id = id;
this.name = name;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

application.yml

server:
port: 8001

创建com.itheima.controller.UserController,代码如下:

package com.itheima.controller;

import com.itheima.pojo.User;
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;

/**
* @author ljh
* @version 1.0
* @date 2020/12/11 15:08
* @description 标题
* @package com.itheima.controller
*/
@RestController
@RequestMapping("/user")
public class UserController {

@GetMapping("/{id}")
public User findById(@PathVariable(name = "id") Integer id) {
//模拟从数据库中获取用户信息
return new User(id, "zhangsan");
}
}

创建启动类,并启动工程

package com.itheima;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* @author ljh
* @version 1.0
* @date 2020/12/11 15:08
* @description 标题
* @package com.itheima
*/
@SpringBootApplication
public class RestProviderApplication {
public static void main(String[] args) {
SpringApplication.run(RestProviderApplication.class, args);
}
}

浏览器访问,如下图所示:

1607670828183

(2)创建itheima-rest-consumer 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">
<modelVersion>4.0.0</modelVersion>

<groupId>com.itheima</groupId>
<artifactId>itheima-rest-consumer</artifactId>
<version>1.0-SNAPSHOT</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

</project>

创建启动类,并在启动类中创建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;

/**
* @author ljh
* @version 1.0
* @date 2020/12/11 15:08
* @description 标题
* @package com.itheima
*/
@SpringBootApplication
public class RestConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RestConsumerApplication.class, args);
}

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

创建POJO: 和provider的POJO一致即可

package com.itheima.pojo;

import java.io.Serializable;

public class User implements Serializable {
private Integer id;
private String name;

public User() {
}

public User(Integer id, String name) {
this.id = id;
this.name = name;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

创建controller用于测试远程调用:

package com.itheima.controller;

import com.itheima.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;

/**
* @author ljh
* @version 1.0
* @date 2020/12/11 15:08
* @description 标题
* @package com.itheima.controller
*/
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;

@GetMapping("/{id}")
public User findById(@PathVariable(name = "id") Integer id) {
//使用restTemplate模拟浏览器发送请求获取远程微服务【provider】的用户的信息
User userFromProvider = restTemplate.getForObject("http://localhost:8001/user/" + id, User.class);
//返回前端查看
return userFromProvider;
}
}

启动工程:并测试如下结果:

1607671275442

由此 通过restTemplate实现远程调用。

模拟微服务业务场景

模拟开发过程中的服务间关系。抽象出来,开发中的微服务之间的关系是生产者和消费者关系。

**总目标:模拟一个最简单的服务调用场景,场景中保护微服务提供者(Producer)和微服务调用者(Consumer)**,方便后面学习微服务架构

注意:实际开发中,每个微服务为一个独立的SpringBoot工程。

1563027834881

目标

  • 创建父工程

  • 搭建服务提供者

  • 搭建服务消费者

  • 服务消费者使用RestTemplate调用服务提供者

创建父工程

(1)新建工程springcloud-parent

1607674621323

(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">
<modelVersion>4.0.0</modelVersion>

<groupId>com.itheima</groupId>
<artifactId>springcloud-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<!--父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<!--SpringCloud包依赖管理-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

创建服务提供者(producer)工程

每个微服务工程都是独立的工程,连数据库都是独立的,所以我们一会要单独为该服务工程创建数据库。采用Mybatis作为持久层

工程创建步骤:

1.准备表结构
2.创建工程
3.引入依赖
4.创建Pojo
5.创建Mapper/Dao
6.创建Service,并调用Dao
7.创建Controller,并调用Service
8.创建application.yml文件
9.创建启动类
10.测试

(1)建表

producer工程是一个独立的微服务,一般拥有独立的controller、service、dao、数据库,我们在springcloud数据库新建表结构信息,如下:

-- 使用springcloud数据库
USE springcloud;
-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(100) DEFAULT NULL COMMENT '用户名',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`name` varchar(100) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`sex` int(11) DEFAULT NULL COMMENT '性别,1男,2女',
`birthday` date DEFAULT NULL COMMENT '出生日期',
`created` date DEFAULT NULL COMMENT '创建时间',
`updated` date DEFAULT NULL COMMENT '更新时间',
`note` varchar(1000) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='用户信息表';
-- ----------------------------
-- Records of tb_user
-- ----------------------------
INSERT INTO `tb_user` VALUES ('1', 'zhangsan', '123456', '张三', '13', '1', '2006-08-01', '2019-05-16', '2019-05-16', '张三');
INSERT INTO `tb_user` VALUES ('2', 'lisi', '123456', '李四', '13', '1', '2006-08-01', '2019-05-16', '2019-05-16', '李四');

(2)在springcloud-parent新建user-provider工程

选中springcloud-parent工程->New Modul->Maven->输入坐标名字,如下步骤:

1563028264001

1607674869906

引入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">
<parent>
<artifactId>springcloud-parent</artifactId>
<groupId>com.itheima</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>user-provider</artifactId>

<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<!--web起步包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--MySQL驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--测试包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

</dependencies>


</project>

(3)User对象创建

创建com.itheima.pojo.User,代码如下:

package com.itheima.pojo;

import java.util.Date;

public class User {
private Integer id;//主键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;//备注

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public Integer getSex() {
return sex;
}

public void setSex(Integer sex) {
this.sex = sex;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public Date getCreated() {
return created;
}

public void setCreated(Date created) {
this.created = created;
}

public Date getUpdated() {
return updated;
}

public void setUpdated(Date updated) {
this.updated = updated;
}

public String getNote() {
return note;
}

public void setNote(String note) {
this.note = note;
}
}

(4)dao
创建com.itheima.dao.UserDao,代码如下:

package com.itheima.dao;

import com.itheima.pojo.User;
import org.apache.ibatis.annotations.Select;

/**
* @author ljh
* @version 1.0
* @date 2020/12/11 16:25
* @description 标题
* @package com.itheima.dao
*/
public interface UserDao {
@Select(value = "select * from tb_user where id=#{id}")
public User findById(Integer id);
}

(5)Service层

创建com.itheima.service.UserService接口,代码如下:

public interface UserService {
public User findById(Integer id);
}

创建com.itheima.service.impl.UserServiceImpl代码如下:

@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public User findById(Integer id) {
return userDao.findById(id);
}
}

(6)控制层

创建com.itheima.controller.UserController,代码如下:

package com.itheima.controller;

import com.itheima.pojo.User;
import com.itheima.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;

/**
* @author ljh
* @version 1.0
* @date 2020/12/11 16:28
* @description 标题
* @package com.itheima.controller
*/
@RestController
@RequestMapping("/user")
public class UserController {

@Autowired
private UserService userService;

/**
* 根据ID查询用户的信息
*
* @param id
* @return
*/
@GetMapping("/{id}")
public User findById(@PathVariable(name = "id") Integer id) {
return userService.findById(id);
}
}

(7)application.yml配置

spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/springcloud?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
application:
name: user-provider
server:
port: 18081

(8)启动类创建

创建com.itheima.UserProviderApplication启动类,并启动

package com.itheima;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* @author ljh
* @version 1.0
* @date 2020/12/11 16:25
* @description 标题
* @package com.itheima
*/
@SpringBootApplication
@MapperScan(basePackages = "com.itheima.dao")
public class UserProviderApplication {
public static void main(String[] args) {
SpringApplication.run(UserProviderApplication.class, args);
}
}

测试:<http://localhost:18081/user/1>

1607675747258

创建服务消费者(consumer)工程

在该工程中使用RestTemplate来调用user-provider微服务。

实现步骤:

1.创建工程
2.引入依赖
3.创建Pojo
4.创建启动类,同时创建RestTemplate对象,并交给SpringIOC容器管理
5.创建application.yml文件,指定端口
6.编写Controller,在Controller中通过RestTemplate调用user-provider的服务
7.启动测试

(1)工程搭建

选中springcloud-parent工程->New Modul->Maven->输入坐标名字,如下步骤:

1563028974269

1607675804277

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">
<parent>
<artifactId>springcloud-parent</artifactId>
<groupId>com.itheima</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>user-consumer</artifactId>

<!--依赖包-->
<dependencies>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>

(2)创建User对象

在src下创建com.itheima.domain.User,代码如下:

package com.itheima.pojo;

import java.util.Date;

/**
* @author ljh
* @version 1.0
* @date 2020/12/11 16:37
* @description 标题
* @package com.itheima.pojo
*/
public class User {
private Integer id;//主键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;//备注

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public Integer getSex() {
return sex;
}

public void setSex(Integer sex) {
this.sex = sex;
}

public Date getBirthday() {
return birthday;
}

public void setBirthday(Date birthday) {
this.birthday = birthday;
}

public Date getCreated() {
return created;
}

public void setCreated(Date created) {
this.created = created;
}

public Date getUpdated() {
return updated;
}

public void setUpdated(Date updated) {
this.updated = updated;
}

public String getNote() {
return note;
}

public void setNote(String note) {
this.note = note;
}
}

(3)创建启动引导类

在src下创建com.itheima.UserConsumerApplication,代码如下:

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;

/**
* @author ljh
* @version 1.0
* @date 2020/12/11 16:37
* @description 标题
* @package com.itheima
*/
@SpringBootApplication
public class UserConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(UserConsumerApplication.class, args);
}

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

创建application.yml,并配置端口为18082

server:
port: 18082
spring:
application:
name: user-consumer

(4)创建控制层,在控制层中调用user-provider

在src下创建com.itheima.controller.UserController,代码如下:

package com.itheima.controller;

import com.itheima.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;

/**
* @author ljh
* @version 1.0
* @date 2020/12/11 16:38
* @description 标题
* @package com.itheima.controller
*/
@RestController
@RequestMapping(value = "/consumer")
public class UserController {

@Autowired
private RestTemplate restTemplate;

/****
* 在user-consumer服务中通过RestTemplate调用user-provider服务
* @param id
* @return
*/
@GetMapping(value = "/{id}")
public User findById(@PathVariable(value = "id") Integer id) {
String url = "http://localhost:18081/user/" + id;
return restTemplate.getForObject(url, User.class);
}

}

启动测试:

请求地址:<http://localhost:18082/consumer/1>

1563029284043

思考问题

user-provider:对外提供用户查询接口

user-consumer:通过RestTemplate访问接口查询用户数据

存在的问题:

  1. 在服务消费者中,我们把url地址硬编码到代码中,不方便后期维护
  2. 在服务消费者中,不清楚服务提供者的状态(user-provider有可能没有宕机了)
  3. 服务提供者只有一个服务,即便服务提供者形成集群,服务消费者还需要自己实现负载均衡
  4. 服务提供者的如果出现故障,是否能够及时发现:

其实上面说的问题,概括一下就是微服务架构必然要面临的问题

  • 服务管理:自动注册与发现、状态监管
  • 服务负载均衡
  • 熔断器