大家谈起的微服务,大多来讲说的只不过是种架构方式。其实现方式很多种:Spring Cloud,Dubbo,华为的Service Combo,Istio 。
那么这么多的微服务架构产品中,我们为什么要用Spring Cloud?因为它后台硬、技术强、群众基础好,使用方便;
技术架构演变
(1)单一应用架构
当网站流量很小时,只需要一个应用,所有功能部署在一起,减少部署节点成本的框架称之为集中式框架。此时,用于简化增删改查工作量的数据访问框架(ORM)是影响项目开发的关键。

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

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

(4)面向服务(SOA)架构
典型代表有两个:流动计算架构和微服务架构;
流动计算架构:
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。流动计算架构的最佳实践阿里的Dubbo。
微服务架构
与流动计算架构很相似,除了具备流动计算架构优势外,微服务架构中的微服务可以独立部署,独立发展。且微服务的开发不会限制于任何技术栈。微服务架构的最佳实践是SpringCloud。

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的版本

版本说明:
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对比

SpringCloud 总架构图

微服务调用
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)入门案例

我们可以使用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
创建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;
@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;
@SpringBootApplication public class RestProviderApplication { public static void main(String[] args) { SpringApplication.run(RestProviderApplication.class, args); } }
|
浏览器访问,如下图所示:

(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;
@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;
@RestController @RequestMapping("/consumer") public class ConsumerController { @Autowired private RestTemplate restTemplate;
@GetMapping("/{id}") public User findById(@PathVariable(name = "id") Integer id) { User userFromProvider = restTemplate.getForObject("http://localhost:8001/user/" + id, User.class); return userFromProvider; } }
|
启动工程:并测试如下结果:

由此 通过restTemplate实现远程调用。
模拟微服务业务场景
模拟开发过程中的服务间关系。抽象出来,开发中的微服务之间的关系是生产者和消费者关系。
**总目标:模拟一个最简单的服务调用场景,场景中保护微服务提供者(Producer)和微服务调用者(Consumer)**,方便后面学习微服务架构
注意:实际开发中,每个微服务为一个独立的SpringBoot工程。

目标
创建父工程
(1)新建工程springcloud-parent

(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> <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数据库新建表结构信息,如下:
USE springcloud;
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='用户信息表';
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->输入坐标名字,如下步骤:


引入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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <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; private String username; private String password; private String name; private Integer age; private Integer sex; 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;
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;
@RestController @RequestMapping("/user") public class UserController {
@Autowired private UserService userService;
@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;
@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>

创建服务消费者(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->输入坐标名字,如下步骤:


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> <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;
public class User { private Integer id; private String username; private String password; private String name; private Integer age; private Integer sex; 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;
@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;
@RestController @RequestMapping(value = "/consumer") public class UserController {
@Autowired private RestTemplate restTemplate;
@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>

思考问题
user-provider:对外提供用户查询接口
user-consumer:通过RestTemplate访问接口查询用户数据
存在的问题:
- 在服务消费者中,我们把url地址硬编码到代码中,不方便后期维护
- 在服务消费者中,不清楚服务提供者的状态(user-provider有可能没有宕机了)
- 服务提供者只有一个服务,即便服务提供者形成集群,服务消费者还需要自己实现负载均衡
- 服务提供者的如果出现故障,是否能够及时发现:
其实上面说的问题,概括一下就是微服务架构必然要面临的问题
- 服务管理:自动注册与发现、状态监管
- 服务负载均衡
- 熔断器