Spring Cloud 快速入门(一)简介、与Dubbo对比、创建基础工程
1. Spring Cloud 简介
1.1 简介
我们从多个角度看什么是Spring Cloud:
1.1.1 官网简介
打开 Spring 官网 http://spring.io 首页的中部,可以看到 Spring Cloud 的简介。
【原文】Building distributed systems doesn’t need to be complex and error-prone(易错). Spring Cloud offers a simple and accessible(易接受的) programming model to the most common distributed system patterns(模式), helping developers build resilient(有弹性的), reliable(可靠的), and coordinated(协调的) applications. Spring Cloud is built on top of Spring Boot, making it easy for developers to get started and become productive quickly.
【翻译】构建分布式系统不需要复杂和容易出错。Spring Cloud 为最常见的分布式系统模式提供了一种简单且易于接受的编程模型,帮助开发人员构建有弹性的、可靠的、协调的应用程序。Spring Cloud 构建于 Spring Boot 之上,使得开发者很容易入手并快速应用于生产中。
SpringCloud架构图:
1.1.2 百度百科
Spring Cloud 是一系列框架的有序集合
。它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署。Spring Cloud 并没有重复制造*,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过 Spring Boot 风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者提供了一套简单易懂、易部署和易维护的分布式系统开发工具包。
1.1.3 总结
Spring Cloud 是什么?阿里高级框架师、Dubbo 项目的负责人刘军说,Spring Cloud 是微服务系统架构的一站式解决方案。
Spring Cloud 与 Spring Boot 是什么关系呢?Spring Boot 为 Spring Cloud 提供了代码实现环境,使用 Spring Boot将其它组件有机融合到了 Spring Cloud 的体系架构中了。所以说,Spring Cloud 是基于 Spring Boot 的、微服务系统架构的一站式解决方案
。
1.2 Spring Cloud 的国内使用情况
1.3 Spring Cloud 在线资源
- Spring Cloud 官网 https://projects.spring.io/spring-cloud/
- Spring Cloud 中文网 https://springcloud.cc/
- Spring Cloud 中国社区 http://springcloud.cn/
1.4 Spring Cloud 版本
1.4.1 版本号来源
Spring Cloud 的版本号并不是我们通常见的数字版本号,而是一些很奇怪的单词。这些单词均为英国伦敦地铁站的站名
。同时根据字母表的顺序来对应版本时间顺序,比如:最早的 Release 版本 Angel(天使),第二个 Release 版本 Brixton(英国地名),然后是 Camden、Dalston、Edgware,目前使用较多的是 Finchley(英国地名)版本,而最新版本为 Hoxton(英国地名),后续的快速入门篇,我们使用的都是目前最新版本 Hoxton。
除了版本号外还有一些其他的版本标记:
- SNAPSHOP:
快照版
,可以使用,但其仍处理连续不断的开发改进中,不建议使用。 - M:
里程碑版
。其也会标注上 PRE,preview,预览版,内测版,
不建议使用。 - RC:Release Candidate,
发行候选版
,主要是用于修复 BUG,一般该版本中不会再添加大的功能修改了。正式发行前的版本。 - SR:Service Release,
服务发行版,正式发行版
。一般还会被标注上 GA,General Available
1.4.2 Spring Cloud 与 Spring Boot 版本
某一版本的 Spring Cloud 要求必须要运行在某一特定 Spring Boot 版本下。它们的对应关系在 Spring Cloud 官网可以看到版本对应说明。
右边表示的是SpringBoot最低版本
2. Spring Cloud 与 Dubbo之间对比
2.1 Spring Cloud 与 Dubbo对比
首先给了一个常见的分布式架构图:
Dubbo 与 Spring Cloud 在不同节点上的技术实现、知识点对比:
路由网关
- Dubbo
反向代理Nginx
:静态代理、动静分离、负载均衡、虚拟主机 - Spring Cloud
Spring Cloud Zuul
:服务路由、请求过滤、令牌桶限流、多维请求限流、灰度发布、Zuul的高可用、负载均衡、服务降级Gateway
:暂时没研究
消费者集群
-
服务容错/熔断、降级
- Dubbo
Dubbo自身实现
:集群容错Cluster、Mock机制、声明式缓存 - Spring Cloud
Hystrix
: 执行隔离、fallbackMethod降级、fallbackFactory降级、Dashboard仪表盘、服务降级报警机制
- Dubbo
-
服务路由
- Dubbo
Dubbo自身实现的
:Router - Spring Cloud
消费者好像没有路由功能
- Dubbo
-
负载均衡
- Dubbo
Dubbo自身实现的
:LoadBalance - Spring Cloud
Ribbon
:IRule
- Dubbo
服务注册中心
- Dubbo
Zookeeper
(CP强一致性):Paxos算法、ZAB算法、Leader选举、Znode、Wather、ZkClient、CuratorRedis等其他中间间也可以做注册中心
- SpringCloud
Eureka
(AP高可用):服务发现Discovery、自我保护机制、Eureka集群
提供者集群
- Dubbo
默认用Zookeeper作为配置中心
- SpringCloud
Spring Cloud Config
:配置文件的自动更新、消息总线系统Spring Cloud Bus
缓存数据层
- 内存数据库Redis:Redis主从集群、Sentinel集群、缓存穿透、缓存雪崩、热点缓存、双重检测锁机制
DBMS
- MySQL主备集群:读写分离集群、MySQL- Proxy、MyCat分库分表
2.2 Spring Cloud 与 Dubbo 技术选型
Spring Cloud 与 Dubbo 均为微服务框架,开发团队在进行技术选型时,总会将它们进行对比,考虑应该选择哪一个。可以从以下几方面考虑:
架构完整度
- Dubbo 仅提供了服务注册与服务治理两个模块。Spring Cloud实现的功能Dubbo也都能实现,只不过Dubbo整合第三方框架时,部分框架Dubbo已经提供了整合机制,没有提供的整合的代码就需要自己实现。
- 而SpringCloud就不需要自己实现,没有兼容性问题。(可以类比自己组装台式机,自己组装,一个是比较麻烦,另一个问题是硬件之间存在兼容性问题,如果买品牌机,开发商已经帮你全装好了,并且不会有兼容问题都是经过测试的)
社区活跃度
- SpringCloug相对来说活跃度更高,维护成本就低,但是是英文的
通讯协议
-
Dubbo 通讯使用的是 RPC
,属于传输层,通信效率更高 -
Spring Cloud 是 HTTP REST
,属于应用层,比Dubbo通信效率低,但是现在也不是问题了,因为有Spring Cloud gRPC
(谷歌的) - 现在Dubbo想做成SpringCloud里面的一个模块:Spring Cloud Dubbo
技术改造与微服务开发
- 传统项目改成Dubbo项目,相对来说成本低
- 改成SpringCloud,基本等于重做,但是如果新项目建议用SpringCloud
3. 创建基础工程
加下来我们会创建一个服务提供者工程和一个服务消费者工程,作为后续快速入门篇知识讲解演示的基础工程。
本例实现了消费者对提供者的调用,但并未使用到Spring Cloud,但其为后续Spring Cloud的运行测试环境。使用 MySQL 数据库,使用 Spring Data JPA 作为持久层技术。
3.1 创建提供者工程 01-provider-8081
(1) 创建工程
创建一个 Spring Initializr 工程,并命名为 01-provider-8081。导入 Lombok、Web、JPA 及 MySQL 驱动依赖。
(2) 导入 Druid 依赖
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--修改MySQL驱动版本-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
(3) 定义实体类
@Data
@Entity // springdatajpa默认用的hibernate,可以自动建表
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler", "fieldHandler"})
// 客户端与服务器之间的数据交互,是由SpringMVC的HttpMessageConverter处理的
// 其中一个实现是处理 Jackson数据 -> 完成Java对象与JSON数据间的转换工作
// JPA的默认实现是Hibernate,而Hibernate默认对于对象的查询是基于延迟加载的
// Depart depart = service.findById(5); 这里的depart实际是一个javasist动态代理对象
// String name = depart.getName();
// 对于HttpMessageConverter,它拿到对象后会立即转成json,这个时候对象中有些数据是空的会报错
// 基于这种情况我们需要忽略掉一些属性就不会报错了
public class Depart {
@Id // 表示当前属性为自动建的表的主键
@GeneratedValue(strategy = GenerationType.IDENTITY) // 主键自动递增
private Integer id;
private String name;
}
(4) 定义 Repository 接口
// 第一个泛型是,当前Repository所操作的对象的类型
// 第二个泛型是,当前Repository所操作的对象的id类型
public interface DepartRepository extends JpaRepository<Depart, Integer> {
}
(5) 定义 Service 接口
public interface DepartService {
boolean saveDepart(Depart depart);
boolean removeDepartById(Integer id);
boolean modifyDepart(Depart depart);
Depart getDepartById(int id);
List<Depart> listAllDeparts();
}
(6) 定义 Service 实现类
@Service
public class DepartServiceImpl implements DepartService {
@Autowired
private DepartRepository repository;
//A、添加数据
// 插入
@Override
public boolean saveDepart(Depart depart) {
// 对于save()的参数,根据其id的不同,有以下三种情况:
// depart的id为null:save()执行的是插入操作
// depart的id不为null,且DB中该id存在:save()执行的是修改操作
// depart的id不为null,但DB中该id不存在:save()执行的是插入操作,
// 但其播入后的记录id值并不是这里指定的id,而是其根据指定的id生成策略所生成的id
Depart obj = repository.save(depart);
return obj != null ? true : false;
}
//B、 删除数据
@Override
public boolean removeDepartById(Integer id) {
if(repository.existsById(id)) {
// 在DB中指定的id若不存在,该方法会抛出异常
repository.deleteById(id);
return true;
}
return false;
}
//C、 修改数据
@Override
public boolean modifyDepart(Depart depart) {
Depart obj = repository.save(depart);
return obj != null ? true : false;
}
//D、根据 id 查询
@Override
public Depart getDepartById(int id) {
if(repository.existsById(id)) {
// 在DB中指定的id若不存在,该方法会抛出异常
return repository.getOne(id);
}
Depart depart = new Depart();
depart.setName("no this depart");
return depart;
}
//E、 查询所有
@Override
public List<Depart> listAllDeparts() {
return repository.findAll();
}
}
(7) 定义处理器
@RestController
@RequestMapping("/provider/depart")
public class DepartController {
@Autowired
private DepartService service;
@PostMapping("/save")
public boolean saveHandler(@RequestBody Depart depart) {
return service.saveDepart(depart);
}
@DeleteMapping("/del/{id}")
public boolean delHandler(@PathVariable("id") Integer id) {
return service.removeDepartById(id);
}
@PutMapping("/update")
public boolean updateHandler(@RequestBody Depart depart) {
return service.modifyDepart(depart);
}
@GetMapping("/get/{id}")
public Depart getHandler(@PathVariable("id") Integer id) {
return service.getDepartById(id);
}
@GetMapping("/list")
public List<Depart> listHandler() {
return service.listAllDeparts();
}
}
(8) 修改配置文件
server:
port: 8081
spring:
# 配置spring data jpa
jpa:
# 指定是否在spring容器启动时创建表,默认false
generate-ddl: true
# 指定在控制台是否显示SQL语句,默认false
show-sql: true
# 指定应用重启后不重新更新表内容
hibernate:
ddl-auto: none
# 配置数据源
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///test?useUnicode=true&characterEncoding=utf8
username: root
password: 111
# 配置日志
logging:
# 控制日志在控制台的输出
pattern:
console: level-%level %msg%n
# 控制日志的显示级别
level:
# 控制Spring Boot启动时显示的日志级别
root: info
# 控制Hibernate运行时的日志级别
org.hibernate: info
# 在show-sql为true时显示SQL中的动态参数值
org.hibernate.type.descriptor.sql.BasicBinder: trace
# 在show-sql为true时显示查询结果
org.hibernate.hql.internal.ast.exec.BasicExecutor: trace
# 控制自己代码运行时显示的日志级别
com.abc: debug
(9) 启动类更名
@SpringBootApplication
public class ApplicationProvider8081 {//方便和消费者区分
public static void main(String[] args) {
SpringApplication.run(ApplicationProvider8081.class, args);
}
}
(10) 测试
3.2 创建消费者工程 01-consumer-8080
(1) 创建工程
创建一个 Spring Initializr 工程,并命名为 01-consumer-8080,导入 Lombok 与 Web 依赖。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
(2) 定义实体类
//消费者端,不需要持久层相关注解了
@Data
public class Depart {
private Integer id;
private String name;
}
(3) 定义 JavaConfig 配置类
@Configuration
public class DepartCodeConfigure {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
(4) 定义处理器类
@RestController
@RequestMapping("/consumer/depart")
public class SomeController {
@Autowired
private RestTemplate restTemplate;
private static final String SERVICE_PROVIDER = "http://localhost:8081";
//A、添加数据
@PostMapping("/save")
public boolean saveHandler(@RequestBody Depart depart) {
String url = SERVICE_PROVIDER + "/provider/depart/save";
return restTemplate.postForObject(url, depart, Boolean.class);
}
//B、 删除与修改数据
@DeleteMapping("/del/{id}")
public void deleteHandler(@PathVariable("id") int id) {
String url = SERVICE_PROVIDER + "/provider/depart/del/" + id;
//RestTemplate的delete方法是没有返回值的
restTemplate.delete(url);
}
@PutMapping("/update")
public void updateHandler(@RequestBody Depart depart) {
String url = SERVICE_PROVIDER + "/provider/depart/update";
//RestTemplate的put也是没有返回值的
restTemplate.put(url, depart);
}
//C、 两个查询
@GetMapping("/get/{id}")
public Depart getByIdHandler(@PathVariable("id") int id) {
String url = SERVICE_PROVIDER + "/provider/depart/get/" + id;
return restTemplate.getForObject(url, Depart.class);
}
@GetMapping("/list")
public List<Depart> listHandler() {
String url = SERVICE_PROVIDER + "/provider/depart/list";
return restTemplate.getForObject(url, List.class);
}
}
(5) 启动类更名
@SpringBootApplication
public class ApplicationConsumer8080 {
public static void main(String[] args) {
SpringApplication.run(ApplicationConsumer8080.class, args);
}
}
(6) 测试
上一篇: 课时105.边框属性下(掌握)
下一篇: 杏林同学录(五)