spring cloud 入门
自己学习spring cloud的一篇笔记…
简介
Spring Cloud将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路由, 负载均衡,熔断器,控制总线,集群状态等功能;协调分布式环境中各个系统,为各类服务提供模板性配置。其主要涉及的组件包括:
- Eureka:注册中心
- Zuul、Gateway:服务网关
- Ribbon:负载均衡
- Feign:服务调用
- Hystrix或Resilience4j:熔断器
1.搭建服务提供方,消费方
1.1 准备工作
创建一个空的maven工程,在pom文件中添加如下
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/>
</parent>
<properties>
<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>5.1.46</mysql.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- springCloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 通用Mapper启动器 -->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${mapper.starter.version}</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
在这里创建spring boot脚手架,并对接下来将要用到的工具进行了版本的管理.
1.2 创建服务提供者
创建一个新的module service,继承刚才创建的父工程,用以提供服务.在pom中添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
创建application.yml文件
server:
port: 9091
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql:///springcloud
username: root
password: 123
application:
name: user-service
mybatis:
type-aliases-package: com.example.pojo
创建启动类 com.example.ServiceApplication
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan("com.example.mapper")//配置包扫描
public class ServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}
编写user实体类 com.example.pojo.User
package com.example.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;
}
编写mapper接口,继承Mapper类,通过继承该类,可以不用写sql语句调用基本的数据库操作
package com.example.mapper;
import com.example.pojo.User;
import tk.mybatis.mapper.common.Mapper;
public interface UserMapper extends Mapper<User> {
}
编写服务类 com.example.service.UserService 提供查询服务
package com.example.service;
import com.example.mapper.UserMapper;
import com.example.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
UserMapper mapper;
public User findById(int id) {
return mapper.selectByPrimaryKey(id);
}
}
提供一个对外查询的控制器 com.example.controller.UserController
package com.example.controller;
import com.example.pojo.User;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
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
UserService service;
@RequestMapping("/{id}")
public User findById(@PathVariable int id) {
return service.findById(id);
}
}
启动服务,通过访问链接http://localhost:9091/user/1 ,测试服务,前提示数据库中已有id=1的数据.
1.3创建服务消费者
创建新的module --> consume,同样继承父工程 编写pom.xml ,添加web启动器依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
编写yml
server:
port: 8081
spring:
application:
name: user-consumer
创建启动器类,并在其中注册RestTemplate
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
Spring提供了一个RestTemplate模板工具类,对基于Http的客户端进行了封装,并且实现了对象与json的序列化和
反序列化,非常方便。RestTemplate并没有限定Http的客户端类型,而是进行了抽象,目前常用的3种都有支持:
HttpClient
OkHttp
JDK原生的URLConnection(默认的)
创建pojo实体类
package com.example.pojo;
import lombok.Data;
import java.util.Date;
@Data
public class User {
// id
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;
}
创建控制器 com.example.controller.UserController,通过RestTemplate远程调用服务
package com.example.controller;
import com.example.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("/user")
public class UserController {
@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);
}
}
运行服务提供者和服务消费者 通过访问http://localhost:8081/user/1 可以获取由服务提供者提供的查询服务.
2.搭建注册中心
2.1搭建eureka-server module
在pom.xml中添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
编写启动器Application com.example.Application
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
//声明当前应用为Eureka服务
@EnableEurekaServer
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
编写配置文件
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 #不拉取
这时候可以通过访问 http://127.0.0.1:10086 查看eureka 界面
2.2 注册服务
在service pom中添加eureka依赖
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
通过在启动类上添加 @EnableDiscoveryClient 注解来开启Eureka客户端功能
@EnableDiscoveryClient
@SpringBootApplication
@MapperScan("com.example.mapper")
public class ServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceApplication.class, args);
}
}
编写yml文件 ,添加eureka 配置
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
yml中 spring application name 来指定应用名称,将来会作为服务的id使用
2.3 服务发现
在消费方pom中添加依赖,与服务提供方一样
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
一样的,在启动类上添加 @EnableDiscoveryClient 注解来开启Eureka客户端功能 ,在yml中添加注册中心地址;
代码同上>>>>注册服务代码;
修改控制器,用DiscoveryClient类的方法,根据服务名称,获取服务实例
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
@GetMapping("{id}")
public User queryById(@PathVariable Long id){
//String url = "http://localhost:9091/user/" + id;
List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances("user-service");
ServiceInstance serviceInstance = serviceInstanceList.get(0);
String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort()
+ "/user/" + id;
return restTemplate.getForObject(url, User.class);
}
}
此时,将服务提供方,消费方,注册中心打开,消费方就可以通过服务名从注册中心获取服务提供方url了;
当有多个eureka注册中兴需要搭建集群时,我们将注册中心yml中配置做修改即可
eureka:
client:
service-url:
# eureka服务地址;如果是集群则是其它服务器地址,后面要加/eureka
defaultZone: http://127.0.0.1:10086/eureka
# 是否注册自己,自身不提供服务所以不注册
#register-with-eureka: false
# 是否拉取服务
#fetch-registry: false
注意把register-with-eureka和fetch-registry修改为true或者注释掉
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
3.负载均衡
3.1 开启负载均衡
直接在服务消费方启动器中修改–>在RestTemplate的配置方法上添加 @LoadBalanced 注解
@SpringBootApplication
@EnableDiscoveryClient
public class ComsumeApplication {
public static void main(String[] args) {
SpringApplication.run(ComsumeApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
修改 消费方控制器 调用方式,不再手动获取ip和端口,而是直接通过服务名称调用;
@GetMapping("{id}")
public User queryById(@PathVariable Long id){
//String url = "http://localhost:9091/user/" + id;
// List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances("user-service");
// ServiceInstance serviceInstance = serviceInstanceList.get(0);
// String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort()
// + "/user/" + id;
String url = "http://user-service/user/" + id;
return restTemplate.getForObject(url, User.class);
}
4.熔断器Hystrix
Hystrix解决雪崩问题的手段主要是服务降级,包括:
线程隔离
服务熔断
添加熔断器 ,首先在消费端引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
在启动类 ConsumerApplication 上添加注解:@EnableCircuitBreaker
//@SpringBootApplication
//@EnableDiscoveryClient
//@EnableCircuitBreaker//熔断器
@SpringCloudApplication//组合了上面的三个注解
public class ComsumeApplication {
public static void main(String[] args) {
SpringApplication.run(ComsumeApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
在控制器中编写降级逻辑
@GetMapping("{id}")
@HystrixCommand(fallbackMethod = "queryByIdFallback")//添加注解
public String queryById(@PathVariable Long id){
String url = "http://user-service/user/" + id;
return restTemplate.getForObject(url, String.class);
}
public String queryByIdFallback(Long id){
return "请求失败!网络正忙";
}
}
@DefaultProperties(defaultFallback = “defaultFallBack”):在类上指明统一的失败降级方法;该类中所有方法
返回类型要与处理失败的方法的返回类型一致当方法较多时可以采用此方法
@RestController
@RequestMapping("/user")
@DefaultProperties(defaultFallback = "defaultFallback")
public class UserController {
@Autowired
DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
@GetMapping("{id}")
//@HystrixCommand(fallbackMethod = "queryByIdFallback")
@HystrixCommand
public String queryById(@PathVariable Long id){
String url = "http://user-service/user/" + id;
return restTemplate.getForObject(url, String.class);
}
public String queryByIdFallback(Long id){
return "请求失败!网络正忙";
}
public String defaultFallback(){
return "默认回复!";
}
}
Hystrix的默认超时时长为1,我们可以通过配置修改这个值;修改 yml 添加如下配置:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000 #熔断器等待时间
当访问时间超过2秒时会开启熔断