Spring Cloud Netflix 微服务全家桶(二)
一、Restful风格的API
/**
* http://xxx/users Get 约定 获取列表信息
* http://xxx/users/{id} Get 例如:http://xxx/users/1 获取Id=1的user信息
* Delete 删除
* Put 修改
*
* 可以引入版本的概念
* http://xxx/v1/users
* http://xxx/v2/users
* 可以同时存在多个版本的URL 方便迭代
*
* 针对单表不再重复crud =======> Spring Data Rest(Jpa + Restful)
*
*/
- 为了防止URL地址出现变化不好修改 一般常用两种方法
- 记录API 生成API文档 例如:Swagger、Yapi等…
- 通过Jar包记录URL 进行Jar包依赖
二、远程服务调用 RestTemplate
2.1 使用方式
-
依赖注入
@Bean @LoadBalanced
-
通过资源地址调用服务
“http://provider/getHi” 底层自动解析成Ip+Port
2.2 getForEntity方法
-
参数
- URL地址
- 返回值类型 URL接口返回值类型
-
返回ResponseEntity对象(Spring封装Rest响应对象 包括状态码和资源)
- Http请求头信息
- Http响应码
- Http响应体
2.3 getForObject 方法
-
参数
- URL地址
- 返回值类型 URL接口返回值类型
-
返回值
- 调用的服务接口返回的数据 根据第二个参数封装
2.4 带参数的远程调用
-
消费方代码
- 通过占位符的方式
String url = "http://provider/getObj?name={1}"; Person obj = restTemplate.getForObject(url, Person.class, "lilianjie"); return obj; // 可以通过这种方式将lijianjie参数传递到服务的提供方
- 通过Map的方式
String url = "http://provider/getObj?name={name}"; Map<String, String> map = Collections.singletonMap("name", "chenglong"); Person obj = restTemplate.getForObject(url, Person.class, map); return obj; // 可以通过这种方式将chenglong参数传递到服务的提供方
2.5 Post 等其他请求
-
消费方代码
// 发送Post请求 // restTemplate.postForEntity() // restTemplate.postForLocation() // restTemplate.postForObject() // 注意 PostForLocation处理 // 这里 服务的生产方必须在response Header中赋值才可以传递 // 消费方代码 ========================================= URI location = restTemplate.postForLocaion(url, map, Person.class); System.out.println(location); // 生产方代码 ========================================= URI uri = new URI("https://www.baidu.com/s?wd=" + person.getName()); // 如果不添加响应头 消费方获取不到返回值 response.setHeader("Location", uri); return uri;
2.6 全局拦截器
拦截调用以及返回
-
拦截器代码
public class LoggingClientInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest reqest, byte[] body, ClientHttpRequestExecution execution) throws IOException { System.out.println("拦截请求!"); System.out.println(request.getURI()); ClientHttpResponse response = execution.execute(request, body); System.out.println(response.getHeader()); return response; } }
-
将拦截器添加到RestTemplate中
@Bean @LoadBalanced RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); // 由此可见可以添加多个拦截器 restTemplate.getInterceptors().add(new LoggingClientInterceptor()); return restTemplate; }
三、声明式服务调用Feign
3.1 Feign和Open Feign的区别
- Feign本身不支持Spring Mvc的注解
- Open Fegin是Spring Cloud在Feign基础上支持了Spring Mvc的注解,通过解析注解值,动态代理生成实现类,在实现类中做负载均衡并调用其他服务
3.2 声明式服务调用
-
配置类启动Feign
@SpringBootApplication @EnableFeignClients
-
创建远程调用接口
// url即远程调用的服务名称 // 通过IP+Port直接调用 不需要Eureka 也没有负载均衡 // @FeignClient(name = "xxoo", url = "http://localhost:81") // 通过Eureka + ribbon 可以负载均衡 // @FeignClient(name = "xxoo", url = "http://user-provider") @FeignClient(name = "user-provider") public interface UserApi { @GetMapping("/alive") public String alive(); }
-
前端控制器
@Autowired UserApi userApi; @GetMapping("/alive") public String alive() { // 调用远程服务的alive方法 return userApi.alive(); }
-
优化
-
服务提供方 提供接口Jar包
-
服务消费方 通过pom引入接口的Jar包 不需要自己编写接口地址
-
提供方和消费方都面向抽出的接口编程
-
消费方接口继承抽出来的接口
@FeignClient(name = "user-provider") public interface ConsumerApi extends UserApi { }
- 提供方控制器实现抽出来的接口
@RestController public class UserController implements UserApi { @Override public String alive() { return "xxxx"; } }
-
注意问题:
-
Feign使用时 接口的参数需要添加@RequestParam注解 否则调用不通(会将Get请求转成Post请求)
-
解决方案:
-
方案一:请求上添加注解(Feign的API接口上添加)
-
方案二:pom添加依赖 替换底层实现(未验证)
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>
-
-
原因:未添加@RequestParam注解时 Feign解析地址未将参数拼接
-
-
Post请求则不需要添加在参数中添加注解
四、相关思考
4.1 Spring Cloud 默认使用Http请求的好处?
-
异构平台
即可跨平台,可跨语言 不需要考虑其他服务是否是Java实现
-
可插拔
服务之间的调用是弱引用 服务的生产方下线对于消费方没有很大影响
长连接服务之间的依赖性太强
Http请求特点:
- 每次请求不一定到哪一个服务
- Http请求是无状态的
带来的好处:
- 可用性无限提高
弊端:
- 降低了一定的数据一致性(网络传输 通过Json序列化效率比RPC协议Byte数组低很多)
4.2 关于异构的Ribbon负载均衡问题
-
provider为PHP consumer为Java
此时可以支持ribbon 无任何影响
-
consumer为PHP
此时当然不可用ribbon ribbon是Java的组件