springcloud项目搭建
上次通过springcloud搭建了注册中心微服务,紧接上文我们来搭建微服务之前服务调用项目,分别新建commerce-core(核心依赖模块)、commerce-exception(统一异常处理中心)、commerce-shop(商品服务模块),将commerce-shop服务注册到Nacos注册中心。完成商品服务发现
微服务通信
同步通信
RESTful API:RESTful 通信使用 HTTP 协议,以 JSON格式来传输数据,具有轻量级、高效、可扩展性等优势,是许多系统之间接口通信的首选方式。(springcloud使用)
RPC:RPC(远程过程调用)是一种用于在微服务之间进行通信的方式,它可以让一个服务调用另一个服务的方法,而无需知道它的实现细节。(Dubbo)
异步通信
消息队列:消息队列是一种用于在微服务之间传递消息的方式,它可以让微服务发送和接收消息,从而实现解耦和异步通信。(RocketMq、Stream)
OpenFeign介绍
OpenFeign是一种声明式的Web Service客户端,它提供了一种非常简单的方式来调用远程HTTP服务。它使用Feign定义可插拔的接口,并通过注解定义服务的调用行为。Feign的可插拔性使用编码器和解码器支持不同的编码格式。另外,Feign还集成了Ribbon,实现了客户端负载均衡的调用远程服务。官网地址
项目搭建
分别搭建commerce-core(核心依赖模块)、commerce-exception(统一异常处理中心)、commerce-shop(商品服务模块)如下图所示:
在core项目中引入OpenFeign的核心依赖
<!-- 远程调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 负载均衡-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
分别在user项目和shop项目的application中增加支持OpenFeign的注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ShopApplication {
public static void main(String[] args) {
SpringApplication.run(ShopApplication.class,args);
}
}
package com.commerce.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.context.config.annotation.RefreshScope;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class CommerceUserApplication {
public static void main(String[] args) {
SpringApplication.run(CommerceUserApplication.class,args);
}
}
项目完善
在user项目里新建一个api接口,通过shop项目进行user项目的调用。
user项目
编写用户服务层
package com.commerce.user.controller;
import cn.hutool.http.HttpStatus;
import com.commerce.core.ResponseDTO;
import com.commerce.dtos.UserDto;
import lombok.extern.slf4j.Slf4j;
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(value = "/user")
@Slf4j
public class UserController {
@GetMapping("/getUser/{id}")
public ResponseDTO<UserDto> getUser(@PathVariable(value = "id")Long id){
log.info("id是:{}",id);
UserDto userDto = new UserDto();
userDto.setId(id);
userDto.setName("测试");
userDto.setPassword("123456");
userDto.setNickName("昵称");
return new ResponseDTO<>(HttpStatus.HTTP_OK,userDto,null);
}
}
在core项目中封装一个用户对象
package com.commerce.dtos;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class UserDto {
private Long id;
private String name;
private String password;
private String nickName;
}
shop项目新建一个userService服务通过OpenFeign进行微服务调用。
package com.commerce.shop.service;
import com.commerce.config.FeignConfiguration;
import com.commerce.dtos.UserDto;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "commerce-user", configuration = FeignConfiguration.class,fallback = UserServiceFallback.class)
public interface UserService {
@GetMapping(value = "/user/getUser/{id}")
UserDto getUser(@PathVariable("id") Long str);
}
package com.commerce.shop.controller;
import cn.hutool.http.HttpStatus;
import com.commerce.core.ResponseDTO;
import com.commerce.dtos.UserDto;
import com.commerce.shop.service.UserService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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;
/**
1. 商品类
*/
@RestController
@RequestMapping(value = "shop")
@Slf4j
@AllArgsConstructor
public class ShopController {
private final UserService userService;
@GetMapping(value = "get/{id}")
public ResponseDTO<String> getShop(@PathVariable(value = "id")Long id){
log.info("接收的id是:{}",id);
// 先通过id查询用户 通过用户查找商品
UserDto user = userService.getUser(id);
System.out.println(user);
return new ResponseDTO(HttpStatus.HTTP_OK,user, null);
}
}
启动user项目和shop项目 保证两个服务都成功被nacos访问通过接口访问,实现shop微服务对user微服务的调用。
OpenFeign结果自定义
众所周知,微服务都是基于RestApi进行接口统一返回,针对不同的状态码也有不同的含义,为了方便开发我们进行统一的封装。
- 编写Config的配置文件
@Configuration(proxyBeanMethods = false)//禁用Spring AOP的动态代理。
public class FeignConfiguration {
@Bean
public Decoder feignDecoder() {
return new FeignResultDecoder();
}
}
- 创建结果自定义的编码器
package com.commerce.config;
import cn.hutool.json.JSONUtil;
import com.commerce.core.ResponseDTO;
import feign.FeignException;
import feign.Response;
import feign.Util;
import feign.codec.DecodeException;
import feign.codec.Decoder;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Type;
@Slf4j
public class FeignResultDecoder implements Decoder {
@SneakyThrows
@Override
public Object decode(Response response, Type type) throws FeignException {
if (response.body() == null) {
throw new DecodeException(response.status(), "没有返回有效的数据", response.request());
}
String bodyStr = Util.toString(response.body().asReader(Util.UTF_8));
// 注意这块你自己封装的Result
ResponseDTO result = JSONUtil.toBean(bodyStr,ResponseDTO.class);
log.info("result:{}",result);
log.info("type是:{}",type);
log.info("{}",result.getData());
//对结果进行转换
//如果返回错误,且为内部错误,则直接抛出异常
if (!result.getResponseMeta().isSuccess()) {
throw new DecodeException(response.status(), "接口返回错误:" + result.getResponseMeta().getMessage(),
response.request());
}
return JSONUtil.toBean(JSONUtil.parseObj(result.getData()), Class.forName(type.getTypeName()));
}
}
通过Decoder接口获取Feign调用的接口的数据,并通过统一的处理流程将其封装成ResponseDTO(项目中的Api统一返回)实例,根据实际情况正确判断请求状态并向用户返回指定类。
比如 请求用户通过统一解码可以获取user的类。
UserDto getUser(@PathVariable(“id”) Long str)
OpenFeign熔断和降级
在微服务中很容易出现类似网络错误、超时等连接问题,熔断和降级就是在出现异常的时候进行拦截、集中处理或者重试,防止程序在异常的时候崩溃。
OpenFeign的新版本需要加入 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.2.10.RELEASE</version> </dependency>
hystrix实现服务的降级
虽然hystrix不在发布新版本,但是bug也不是很多,面对一般的业务足以支撑,Alibab的Sentinel配置繁琐,适合大型系统中,后续会补充。
在项目中可以看到有个UserServiceFallback的类,当有异常时会熔断到当前类下进行处理返回提示或熔断数据。
package com.commerce.shop.service;
import com.commerce.dtos.UserDto;
import org.springframework.stereotype.Component;
@Component
public class UserServiceFallback implements UserService{
@Override
public UserDto getUser(Long str) {
System.out.println("替代方案");
return new UserDto();
}
}
后续我们将逐渐完善,增加统一配置中心,客户端的负载均衡,微服务网关、分布式事务、消息中心、微服务安全等介绍和使用,.