spring cloud 学习笔记1
认识springcloud
版本号
spring cloud 的 版本号 是用单词来命名的,和传统的命名规则不一致,每个单词的首字母顺序为 A B C D…。
eureka
服务发现与注册
搭建eureka服务
1.创建子模块ms_eureka
2.引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
3.application.yml
server:
port: 9000
spring:
application:
name: ms-eureka
eureka:
client:
# 是否需要从eureka获取注册信息
fetch-registry: false
# 是否需要把该服务注册到eureka
register-with-eureka: false
4.启动类添加注解@EnableEurekaServer
@SpringBootApplication
@EnableEurekaServer
public class MsEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(MsEurekaApplication.class, args);
}
}
为eureka客户端注册服务到eureka服务端
- 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- application.yml
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/
- 启动类添加注解@EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient
public class MsUserApplication {
public static void main(String[] args) {
SpringApplication.run(MsUserApplication.class, args);
}
}
DiscoveryClient 实现电影微服务和用户微服务的解耦
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate ;
@Autowired
private DiscoveryClient discoveryClient ;
@GetMapping("/{userId}")
public User order(@PathVariable int userId) {
List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances("ms-user") ;
ServiceInstance serviceInstance = serviceInstanceList.get(0) ;
User user = restTemplate.getForObject("http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/user/"+userId, User.class) ;
return user ;
}
}
eureka集群实现
- application-cluster-1.yml
server:
port: 9000
eureka:
client:
# 是否需要从eureka获取注册信息
fetch-registry: true
# 是否需要把该服务注册到eureka
register-with-eureka: true
service-url:
defaultZone: http://localhost:8000/eureka
- application-cluster-2.yml
server:
port: 8000
eureka:
client:
# 是否需要从eureka获取注册信息
fetch-registry: true
# 是否需要把该服务注册到eureka
register-with-eureka: true
service-url:
defaultZone: http://localhost:9000/eureka
- 启动两台集群eureka服务
启动时添加参数
-Dspring.profiles.active=cluster-1
-Dspring.profiles.active=cluster-2
4.电影微服务和用户微服务注册到集群配置``
server:
port: 9002
spring:
application:
name: ms-movie
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/,http://localhost:8000/eureka/
server:
port: 9001
spring:
application:
name: ms-user
datasource:
url: jdbc:mysql://192.168.195.135:3306/study?useUnicode=true&characterEncoding=utf8
username: study
password: study123
jpa:
show-sql: true
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/,http://localhost:8000/eureka/
服务提供方
服务提供方要向eurekaServer注册服务,并完成续约等工作。
- 服务注册
eureka:
client:
# 是否需要把该服务注册到eureka
register-with-eureka: true
- 服务注册后需要向eureka发送心跳
eureka:
instance:
# 服务失效的间隔时间默认90秒
lease-expiration-duration-in-seconds: 90
# 服务续约的间隔时间默认30秒
lease-renewal-interval-in-seconds: 30
服务消费方
- 获取服务注册信息
eureka:
client:
# 是否需要从eureka获取注册信息
fetch-registry: true
- 更新注册信息
eureka:
client:
#刷新注册信息的间隔时间默认30s
registry-fetch-interval-seconds: 30
eureka服务失效剔除和自我保护
- 失效剔除
默认情况下每隔60s对失效的服务(默认90s未续约的服务)进行剔除,间隔时间配置参数为:
eureka:
server:
# 失效服务剔除间隔时间ms默认60s
eviction-interval-timer-in-ms: 60000
- 自我保护
eureka服务会统计15分钟内的心跳失败的服务实例是否超过15%。在生产情况下,由于网络原因,心跳失败,但其实服务可能是正常运行的,此时剔除掉是不恰当的做法,所以会进行保护起来,不予剔除,这种可以保证大多服务依然可用。
eureka:
server:
# 自我保护机制是否可用默认true
enable-self-preservation: true
SpringCloud 服务调用与负载均衡
方式
- RestTemplate + Ribbon
- OpenFeign + 内置Ribbon ----推荐使用
负载均衡组件Ribbon
Ribbon是Netflix 发布的负载均衡组件,提供了负载均衡的算法,如轮询,随机等,也可以自定义算法。查看ms-movie的pom.xml文件发现spring-cloud-starter-netflix-eureka-client依赖包已经导入了ribbon的依赖。
- 负载均衡的调用方式
// 负载均衡客户端对象
@Autowired
private LoadBalancerClient client ;
/**
* - 负载均衡的调用方式
* @param userId
* @return
*/
@GetMapping("/{userId}")
public User order(@PathVariable int userId) {
// 使用Ribbon选择一台服务实例(默认算法为轮询)
ServiceInstance serviceInstance = client.choose("ms-user") ;
System.out.println(serviceInstance.getPort());
// 使用restTemplate调用服务
User user = restTemplate.getForObject("http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/user/"+userId, User.class) ;
return user ;
}
- 负载均衡的调用方式(简化版)
@Bean
// 添加Ribbon负载均衡
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate() ;
}
@GetMapping("/{userId}")
public User order(@PathVariable int userId) {
// 使用restTemplate调用服务
// 直接使用服务名(默认算法为轮询)
User user = restTemplate.getForObject("http://ms-user/user/"+userId, User.class) ;
return user ;
}
-
轮询算法底层代码跟踪
拦截器会拦截请求地址,获取真正的请求服务,使用%服务器数量来获取。 -
修改负载均衡算法
#修改ribbon负载均衡算法 (随机算法)
ms-user:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
使用OpenFeign实现服务调用
OpenFeign是Netflix开发的声明式,模板化的http客户端,帮助我们快捷的调用http rest 的api。OpenFeign已经默认集成了Ribbon负载均衡。
调用步骤
- movie服务调用端引入依赖
<!-- 导入OpenFeign依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 启动类开启注解@EnableFeignClients
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class MsMovieApplication {
public static void main(String[] args) {
SpringApplication.run(MsMovieApplication.class, args);
}
// @Bean
// // 添加Ribbon负载均衡
// @LoadBalanced
// public RestTemplate restTemplate() {
// return new RestTemplate() ;
// }
}
- 编写代理接口
// 使用OpenFeign客户端
// 实现ms-user服务的动态代理
@FeignClient("ms-user")
public interface MsUserClient {
// 动态代理接口
// 接口方法为ms-user的UserController的方法
// @GetMapping("/user/{userId}") 路径应该完整
// @PathVariable(value="userId") 中的value不能省略
@GetMapping("/user/{userId}")
public User findById(@PathVariable(value="userId") int userId) ;
}
- 使用代理接口调用远程服务方法
@Autowired
private MsUserClient msUserClient;
/**
* - 负载均衡的调用方式(简化版)
* @param userId
* @return
*/
@GetMapping("/{userId}")
public User order(@PathVariable int userId) {
// 使用restTemplate调用服务
// 直接使用服务名
User user = msUserClient.findById(userId) ;
return user ;
}
熔断器Hystrix
Hystrix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败(雪崩效应)
雪崩效应
服务提供者的不可用导致服务消费者不可用,并且逐渐放大的过程
状态
- 关闭 正常请求
- 打开 失败达到阈值后
- 半开 打开一段时间后放行部分请求
Ribbon添加Hystrix熔断器
修改电影微服务(调用方添加熔断器)
- 导入Hystrix的依赖包
<!-- 导入Hystrix依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
- 使用@HystrixCommand声明fallback方法
- 编写fallback方法逻辑
需要注意的是fallback方法的参数和返回类型必须与order方法的参数和返回类型一致,否则会报错。
@GetMapping("/{userId}")
@HystrixCommand(fallbackMethod="fallback")
public User order(@PathVariable int userId) {
// 使用restTemplate调用服务
// 直接使用服务名
User user = restTemplate.getForObject("http://ms-user/user/"+userId, User.class) ;
return user ;
}
/**
* 熔断器回调方法
* @return
*/
public User fallback(int userId) {
System.out.println("falback....");
return null ;
}
- 在启动类添加@EnableHystrix注解
OpenFeign方式开启Hystrix(推荐使用)
- 添加依赖
- 开启配置
#默认OpenFeign的熔断器是关闭的,所以需要开启
feign:
hystrix:
enabled: true
- 编写fallback处理类,实现服务调用代理接口
/**
* 熔断器fallback类的实现
* @author eastqi
*
*/
@Component
public class MsUserUserControllerImpl implements MsUserUserController {
@Override
public User findById(int userId) {
User u = new User() ;
u.setUserName("fallback...");
return u;
}
}
- 在服务调用代理接口中指定编写的实现类
fallback=MsUserUserControllerImpl.class
// 使用OpenFeign客户端
// 实现ms-user服务的动态代理
// fallback 指定熔断器回调类
@FeignClient(value="ms-user",fallback=MsUserUserControllerImpl.class)
public interface MsUserUserController {
// 动态代理接口
// 接口方法为ms-user的UserController的方法
// @GetMapping("/user/{userId}") 路径应该完整
// @PathVariable(value="userId") 中的value不能省略
@GetMapping("/user/{userId}")
public User findById(@PathVariable(value="userId") int userId) ;
}
搭建Hystrix监控服务
- 创建ms_hystrix_monitor模块
- 引入相关依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 启动类开启@EnableHystrixDashboard
@SpringBootApplication
// 开启HystrixDashboard监控服务面板
@EnableHystrixDashboard
public class MsHystrixMonitorApplication {
public static void main(String[] args) {
SpringApplication.run(MsHystrixMonitorApplication.class, args);
}
}
服务调用方暴露端点
可以参考网上其他文章,感觉用的不是很多
Spring Cloud 网关
作用:地址统一管理、权限控制、负载均衡
认识Spring Clound Zuul
zuul是Netflix开源的微服务网关,可以和Eureka、Ribbon、Hystrix等组件配合使用:
身份认证与安全
审查与监控
动态路由
压力测试
负载分配
静态响应处理
多区域弹性
Zuul动态路由实现步骤
- 创建独立的网关微服务模块:ms-gateway
- 导入zuul和eureka的依赖(网关本身也要注册到Eureka)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
- 启动类添加@EnableZuulProxy
@SpringBootApplication
@EnableEurekaClient
// 开启网关代理功能
@EnableZuulProxy
public class MsGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(MsGatewayApplication.class, args);
}
}
- 配置application.yml路由规则
server:
port: 80
spring:
application:
name: ms-gateway
eureka:
client:
service-url:
defaultZone: http://localhost:9000/eureka/,http://localhost:8000/eureka/
instance:
prefer-ip-address: true
#zuul网关的动态路由配置
#如果path和service-id的名称是一致的,则下面可以不用配置
zuul:
routes:
ms-user:
#需要转发的路径
path: ms-user
#转发到的服务名称
service-id: ms-user
ms-movie:
path: ms-movie
service-id: ms-movie
- 改成通过网关的端口来调用
http://localhost:9002/order/1
===》
http://localhost/ms-movie/order/1
Zuul动态路由负载均衡
通过上一步的Zuul动态路由配置,已经负载均衡,Zuul是通过Ribbon实现负载均衡,所以默认策略是轮询
Zuul过滤器
实际上动态路由功能在运行时,它的路由映射和请求转发都是由几个不同的Zuul过滤器完成的。其中,路由映射主要通过pre类型的过滤器完成,它将请求路径与配置的路由规则进行匹配,以找到需要转发的目标地址;而请求转发的部分则是由routel类型的过滤器来完成,对pre类型过滤器获得的路由地址进行转发。所以Zuul过滤器可以说是Zuul实现API网关功能最为核心的部件。
- Zuul过滤器方法说明
public class MyFileter1 extends ZuulFilter {
/**
* - 是否执行
*/
@Override
public boolean shouldFilter() {
// TODO Auto-generated method stub
return false;
}
/**
* - 过滤器具体的逻辑
*/
@Override
public Object run() throws ZuulException {
// TODO Auto-generated method stub
return null;
}
/**
* 过滤器类型
* - pre 可以在请求被路由之前调用
* - routing 在路由请求时候被调用
* - post 在routing和error过滤器之后被调用
* - error 处理请求时发生错误是被调用
*/
@Override
public String filterType() {
// TODO Auto-generated method stub
return null;
}
/**
* -执行顺序,数值越小优先级越高
*/
@Override
public int filterOrder() {
// TODO Auto-generated method stub
return 0;
}
}
- Zuul过滤器-生命周期
- Zull过滤器的编写
@Component
public class MyFileter1 extends ZuulFilter {
/**
* - 是否执行
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* - 过滤器具体的逻辑
*/
@Override
public Object run() throws ZuulException {
System.out.println("run myfileter1...");
return null;
}
/**
* 过滤器类型
* - pre 可以在请求被路由之前调用
* - routing 在路由请求时候被调用
* - post 在routing和error过滤器之后被调用
* - error 处理请求时发生错误是被调用
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* -执行顺序,数值越小优先级越高
*/
@Override
public int filterOrder() {
return 1;
}
}
- Zuul过滤器实现权限认证
zuul已经实现很多过滤器:
自定义权限认证过滤器代码如下:
/**
* - 权限认证过滤器
* @author eastqi
*
*/
@Component
public class AuthFileter extends ZuulFilter {
/**
* - 是否执行
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* - 过滤器具体的逻辑
*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest() ;
HttpServletResponse response = ctx.getResponse() ;
String token = request.getParameter("token") ;
if(!"user".equals(token)) {
// 不继续转发微服务,直接给用户响应
ctx.setSendZuulResponse(false);
response.setStatus(401);
return null ;
}
return null;
}
/**
* 过滤器类型
* - pre 可以在请求被路由之前调用
* - routing 在路由请求时候被调用
* - post 在routing和error过滤器之后被调用
* - error 处理请求时发生错误是被调用
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* -执行顺序,数值越小优先级越高
*/
@Override
public int filterOrder() {
return 0;
}
}
- Zuul过滤器实现自定义异常处理
默认的异常处理过滤器为SendErrorFilter
5.1 先将Zuul定义的过滤器失效,这样才能使用我们自定义的过滤器;因为异常处理过滤器只能有一个生效。
# 将Zuul默认的异常过滤器失效
zuul:
SendErrorFilter:
error:
disable: true
5.2 编写代码
/**
* - 自定义异常处理过滤器
* @author eastqi
*
*/
@Component
public class MyErrorFileter extends ZuulFilter {
/**
* - 是否执行
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* - 过滤器具体的逻辑
*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
// 获取异常信息
Throwable throwable = ctx.getThrowable() ;
HttpServletResponse response = ctx.getResponse() ;
ResponseBean responseBean = new ResponseBean(false,throwable.getMessage()) ;
ObjectMapper mapper = new ObjectMapper() ;
try {
String json = mapper.writeValueAsString(responseBean) ;
response.setContentType("text/json;chatset=utf-8");
response.getWriter().write(json);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 过滤器类型
* - pre 可以在请求被路由之前调用
* - routing 在路由请求时候被调用
* - post 在routing和error过滤器之后被调用
* - error 处理请求时发生错误是被调用
*/
@Override
public String filterType() {
return FilterConstants.ERROR_TYPE;
}
/**
* -执行顺序,数值越小优先级越高
*/
@Override
public int filterOrder() {
return 0;
}
}
- Zuul网关与swagger2整合
6.1 gateway模块导入swagger2依赖
<!-- 导入swagger依赖 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
Zuul网关与swagger2整合
swagger2常用注解
常用注解:
- @Api()用于类;
表示标识这个类是swagger的资源
- @ApiOperation()用于方法;
表示一个http请求的操作
- @ApiParam()用于方法,参数,字段说明;
表示对参数的添加元数据(说明或是否必填等)
- @ApiModel()用于类
表示对类进行说明,用于参数用实体类接收
- @ApiModelProperty()用于方法,字段
表示对model属性的说明或者数据操作更改
- @ApiIgnore()用于类,方法,方法参数
表示这个方法或者类被忽略
- @ApiImplicitParam() 用于方法
表示单独的请求参数
- @ApiImplicitParams() 用于方法,包含多个 @ApiImplicitParam
用户模块集成swagger2
- 导入swagger2依赖
<!-- 导入swagger依赖 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
- 编写配置集成代码
@Configuration
@EnableSwagger2
public class MySwagger2Config {
@Bean
public Docket buildDocket() {
return new Docket(DocumentationType.SWAGGER_2)
// 文档信息
.apiInfo(buildApiInfo()).select()
// 需要生成文档的包路径
.apis(RequestHandlerSelectors.basePackage("")).paths(PathSelectors.any()).build();
}
/**
* - 构建文档信息
*
* @return
*/
private ApiInfo buildApiInfo() {
return new ApiInfoBuilder().title("用户微服务").description("用户微服务swagger2 UI接入").version("1.0").build();
}
}
- 启动类开启注解@EnableSwagger2
@SpringBootApplication
@EnableEurekaClient
@EnableSwagger2
public class MsUserApplication {
public static void main(String[] args) {
SpringApplication.run(MsUserApplication.class, args);
}
}
- 使用swagger2注解为对应控制器声明接口说明
@RestController
@RequestMapping("/user")
@Api(description="用户核心api")
public class UserController {
@Autowired
private UserServiceImpl userServiceImpl ;
@GetMapping()
@ApiOperation(value="查询所有用户")
public List<User> findAll() {
return userServiceImpl.findAll() ;
}
@ApiOperation(value="通过用户ID查询用户")
@GetMapping("/{userId}")
public User findById(@PathVariable int userId) {
System.out.println("cluster-2");
return userServiceImpl.findById(userId) ;
}
@PostMapping
@ApiOperation(value="保存用户")
public String save(@RequestBody User user) {
userServiceImpl.save(user);
return "save 成功" ;
}
@PutMapping("/{userId}")
@ApiOperation(value="更新用户")
public String update(@PathVariable int userId,@RequestBody User user) {
userServiceImpl.update(userId, user);
return "update 成功" ;
}
@DeleteMapping("/{userId}")
@ApiOperation(value="删除用户")
public String delete(@PathVariable int userId) {
userServiceImpl.delete(userId);
return "delete 成功" ;
}
}
- 访问验证
http://localhost:9001/swagger-ui.html
部署成功
电影微服务集成swagger2
同用户微服务
gateway模块集成swagger2
- 导入swagger2依赖(同用户微服务)
- 编写配置集成代码
@Component
@Primary
public class MySwaggerResourcesProvider implements SwaggerResourcesProvider {
private final RouteLocator routeLocator;
public MySwaggerResourcesProvider(RouteLocator routeLocator) {
this.routeLocator = routeLocator;
}
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<Route> routes = routeLocator.getRoutes();
routes.forEach(route -> {
resources.add(swaggerResource(route.getId(), route.getFullPath().replace("**", "v2/api-docs")));
});
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
}
- 启动类开启注解(同用户微服务)
- 访问验证
可以通过右上角的切换不同的微服务模块