SpringCloud是Spring旗下的项目之一,官网地址:http://projects.spring.io/spring-cloud/
Spring最擅长的就是集成,把世界上最好的框架拿过来,集成到自己的项目中。
SpringCloud也是一样,它将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路由,负载均衡,熔断器,控制总线,集群状态等等功能。其主要涉及的组件包括:
以上只是其中一部分,架构图:
Eureka:
Eureka是一个基于REST(Representational State Transfer)的服务,主要用于AWS cloud, 提供服务定位(locating services)、负载均衡(load balancing)、故障转移(failover of middle-tier servers)。我们把它叫做Eureka Server. Eureka也提供了基于Java的客户端组件,Eureka Client,内置的负载均衡器可以实现基本的round-robin负载均衡能力。在Netflix,一个基于Eureka的更复杂的负载均衡器针对多种因素(如流量、资源利用率、错误状态等)提供加权负载均衡,以实现高可用(superior resiliency).
打个比方来说:Eureka就好比是图书管理员,负责管理、记录图书(服务提供方)的信息。浏览者不需要自己寻找书籍,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的图书告诉你。同时,服务提供方与Eureka之间通过“心跳”
机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。
这就实现了服务的自动注册、发现、状态监控。
Zuul:
Zuul相当于是第三方调用(app应用端和PC端)和服务提供方之间的防护门。作为前端服务(Edge Service也称边缘服务,前端服务的作用是对后端服务做必要的聚合和裁剪后暴露给外部不同的设备,如PC,Pad或者Phone),Zuul旨在实现动态路由,监控,弹性和安全性。它具备根据需求将请求路由到多个AWS自动弹性伸缩组的能力
Ribbon:
Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具。所以,对Spring Cloud Ribbon的理解和使用,对于我们使用Spring Cloud来构建微服务非常重要。
Hystix:
在一个分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,如何能够保证在一个依赖出问题的情况下,不会导致整体服务失败,这个就是Hystrix需要做的事情。Hystrix提供了熔断、隔离、Fallback、cache、监控等功能,能够在一个、或多个依赖同时出现问题时保证系统依然可用。
Feign:
Feign是spring cloud中服务消费端的调用框架,通常与ribbon,hystrix等组合使用。相当于整合了ribbon,hystrix,提供了更简单的调用方式。
SpringCloud的版本命名比较特殊,因为它不是一个组件,而是许多组件的集合,它的命名是以A到Z的为首字母的一些单词组成:
我们在项目中,会是以Finchley的版本。
其中包含的组件,也都有各自的版本,如下表:
Component | Edgware.SR3 | Finchley.RC1 | Finchley.BUILD-SNAPSHOT |
---|---|---|---|
spring-cloud-aws | 1.2.2.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-bus | 1.3.2.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-cli | 1.4.1.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-commons | 1.3.3.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-contract | 1.2.4.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-config | 1.4.3.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-netflix | 1.4.4.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-security | 1.2.2.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-cloudfoundry | 1.1.1.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-consul | 1.3.3.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-sleuth | 1.3.3.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-stream | Ditmars.SR3 | Elmhurst.RELEASE | Elmhurst.BUILD-SNAPSHOT |
spring-cloud-zookeeper | 1.2.1.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-boot | 1.5.10.RELEASE | 2.0.1.RELEASE | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-task | 1.2.2.RELEASE | 2.0.0.RC1 | 2.0.0.RELEASE |
spring-cloud-vault | 1.1.0.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-gateway | 1.0.1.RELEASE | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
spring-cloud-openfeign | 2.0.0.RC1 | 2.0.0.BUILD-SNAPSHOT |
新建两个项目,一个为服务提供方,一个为服务消费方。模拟一个服务调用的场景。方便后面学习微服务架构
3.1 创建服务提供方
新建一个项目,对外提供查询用户的服务。跟之前一样,先新建一个空的工程(File -->New --> Project--> Empty Project ...),在空的工程里面,File --> New --> Module ...
Next : 填写组织结构
Next:添加web依赖:
Next:添加mybatis依赖:
Next:填写项目位置:
生成的项目结构(其中的包是自己新建的):
依赖也已经全部自动引入,当然,因为要使用通用mapper,所以我们需要手动加一条依赖:
tk.mybatis
mapper-spring-boot-starter
2.0.2
完整的pom文件如下:
4.0.0
com.springcloud.demo
user-service-demo
0.0.1-SNAPSHOT
jar
user-service-demo
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
2.1.0.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-jdbc
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.2
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-test
test
tk.mybatis
mapper-spring-boot-starter
2.0.2
org.springframework.boot
spring-boot-maven-plugin
编写测试代码:
controller下:创建UserController.java
package com.springcloud.userservice.controller;
import com.springcloud.userservice.pojo.User;
import com.springcloud.userservice.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;
//根据id查询数据
@GetMapping("/{id}")
public User queryById(@PathVariable("id") String id) {
return this.userService.queryById(id);
}
}
service下创建UserService.java
package com.springcloud.userservice.service;
import com.springcloud.userservice.mapper.UserMapper;
import com.springcloud.userservice.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User queryById(String id) {
return this.userMapper.selectByPrimaryKey(id);
}
}
Mapper下创建UserMapper.java
package com.springcloud.userservice.mapper;
import com.springcloud.userservice.pojo.User;
import tk.mybatis.mapper.common.Mapper;
@org.apache.ibatis.annotations.Mapper
public interface UserMapper extends Mapper {
}
pojo下创建User.java
package com.springcloud.userservice.pojo;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
private String username;
private String password;
private String company;
private Integer age;
private Integer sex;
public String getId() {
return id;
}
public void setId(String 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 getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
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;
}
}
我们将application.properties的后缀改成 .yml ;用yaml语法,跟之前的相比结构更清晰
application.yml文件配置如下:
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/ssm?useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
hikari:
maximum-pool-size: 20
minimum-idle: 10
mybatis:
type-aliases-package: com.springcloud.userservice.pojo
启动测试:
访问 localhost:8080/user/123456
好了,现在我们服务提供方创建完成并且测试通过。
3.1 创建服务调用方
创建名字为consumer-demo的调用方项目(在添加依赖的时候只选择web依赖);与上面类似,这里不再赘述,需要注意的是,我们调用user-service的功能,因此不需要mybatis相关依赖了。 由于需要调用另一个服务,所以还需要引入OkHttp,用来发起对服务提供者的调用。
注意:该项目创建完成之后,由于该空工程里面有了两个springboot项目,所以idea工具会在右下角智能的提醒我们是否打开 run dashboard , 它是什么,这里可以同时显示多个springboot项目,非常方便。 如下图所示:
如果错过这个,参考https://blog.csdn.net/dwhdome/article/details/83414391末尾手动打开。
OkHttp:
OkHttp是一款优秀的HTTP框架,它支持get请求和post请求。支持基于Http的文件上传和下载,支持加载图片,支持下载文件透明的GZIP压缩,支持响应缓存避免重复的网络请求,支持使用连接池来降低响应延迟问题。
pom文件如下:
4.0.0
com.springcloud.demo
consumer-demo
0.0.1-SNAPSHOT
jar
consumer-demo
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
2.1.0.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
com.squareup.okhttp3
okhttp
3.9.0
org.springframework.boot
spring-boot-maven-plugin
创建controller等目录结构,创建完成后结构如下:
3.1.1 首先在启动类中注册RestTemplate
:
由于要使用OkHttp,所以要把他注入到spring管理
ConsumerDemoApplication.java
package com.springcloud.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class ConsumerDemoApplication {
@Bean
public RestTemplate restTemplate() {
// 这次我们使用了OkHttp客户端,只需要注入工厂即可
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
public static void main(String[] args) {
SpringApplication.run(ConsumerDemoApplication.class, args);
}
}
3.1.2 编写UserDao.java
注意,这里不是调用mapper查数据库,而是通过RestTemplate远程调用user-service-demo中的接口:
package com.springcloud.consumer.dao;
import com.springcloud.consumer.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
@Component
public class UserDao {
@Autowired
private RestTemplate restTemplate;
//既然是服务调用方,那我们肯定是不再去自己写查询数据库的方法了
// 而是调用服务提供方的方法获取我们需要的数据
//这里是用了OkHttp客户端进行调用的
public User queryUserById(String id){
String url = "http://localhost:8080/user/" + id;
return this.restTemplate.getForObject(url, User.class);
}
}
3.1.3 编写ConsumerService.java ,循环查询UserDAO信息:
package com.springcloud.consumer.service;
import com.springcloud.consumer.dao.UserDao;
import com.springcloud.consumer.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class ConsumerService {
@Autowired
private UserDao userDao;
public List querUserByIds(List ids){
List users = new ArrayList<>();
for (String id : ids) {
User user = this.userDao.queryUserById(id);
users.add(user);
}
return users;
}
}
3.1.4 编写ConsumerController.java
@RestController
@RequestMapping("consume")
public class ConsumerController {
@Autowired
private UserService userService;
@GetMapping
public List consume(@RequestParam("ids") List ids) {
return this.userService.queryUserByIds(ids);
}
}
3.1.5 修改启动端口,不能与user-service-demo相同。
application.yml
server:
port: 8081
到这里服务调用方也编写完成,其中User实体类从服务提供方项目中复制过来即可,接下来我们启动两个项目:
user-service-demo: 启动的是8080端口;
consumer-demo :启动的是8081端口
现在我们请求consumer-demo项目,看看服务调用方能否拿到服务提供方的数据
请求 http://localhost:8081/consume?ids=123456
好的,调用成功,一个简单的远程服务调用案例就实现了。
demo链接:https://pan.baidu.com/s/1H7mkWGh3AN8jc60eXZPKeg 提取码:eh2y
简单回顾一下,刚才我们写了什么:
use-service-demo:一个提供根据id查询用户的微服务
consumer-demo:一个服务调用者,通过RestTemplate远程调用user-service-demo
流程如下:
存在什么问题?
在consumer中,我们把url地址硬编码到了代码中,不方便后期维护
consumer需要记忆user-service的地址,如果出现变更,可能得不到通知,地址将失效
consumer不清楚user-service的状态,服务宕机也不知道
user-service只有1台服务,不具备高可用性
即便user-service形成集群,consumer还需自己实现负载均衡
其实上面说的问题,概括一下就是分布式服务必然要面临的问题:
服务管理
如何自动注册和发现
如何实现状态监管
如何实现动态路由
服务如何实现负载均衡
服务如何解决容灾问题
服务如何实现统一配置
以上问题springcloud提供了一系列的解决方案,下面的博文会慢慢讲到。