一.Feign+Nacos实现服务间远程调用
背景:
服务提供方:sms-center
服务调用方:user-center
1.启动Nacos服务
2.服务提供方:sms-center
添加Nacos相关依赖
(不指定版本号可能出现依赖报错,服务注册不上去的情况)
<!-- 选用nacos时打开-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
启动类添加服务发现注解
@EnableDiscoveryClient
@SpringBootApplication
@EnableDiscoveryClient
public class SmsApplication {
public static void main(String[] args) {
SpringApplication.run(SmsApplication.class, args);
}
}
3.服务调用方:user-center
添加Nacos和OpenFeign相关依赖
<!-- 选用nacos时打开-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!--OpenFeign RPC调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
在服务调用方下编写服务提供方sms-center的service
@FeignClient("sms-center")
@Component
public interface SmsService {
/**
* Sms-center服务发送消息的接口
* @param
* @return
*/
@RequestMapping("/sms/send")
String sendMsg();
@RequestMapping("/sms/send2")
String sendMsg2(String msg);
}
@FeignClient
注解指定Nacos中服务提供方的名字
@Component
注解将该类作为bean交给Spring管理,方便服务调用方的controller注入
@RequestMapping
指定需要调用的服务提供方的接口
服务调用方的Controller注入SmsService
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private SmsService smsService;
@GetMapping("/sendMsg")
public String sendUserMsg(){
System.out.println("用户服务收到请求");
return smsService.sendMsg();
}
@PostMapping("/sendMsg2")
public String sendUserMsg2(@RequestBody String msg){
System.out.println("用户服务收到请求");
return smsService.sendMsg2(msg);
}
}
服务调用方的controller就像调用自己的service一样,但其实是service去调用了服务提供方的相关接口
启动类添加FeignClient和服务发现注解
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class,args);
}
}
访问服务调用方相关接口,能正确返回。
二.使用 Sentinel Dashboard进行流量控制
现假设sms-center的发送消息接口/sms/sendUser
容易产生拥堵,需要对其进行流量控制
1.启动Sentinel Dashboard
方式一:下载Sentinel的控制台jar包并运行java -jar sentinel-dashboard.jar
方式二:拉取Sentinel代码,编译运行dashboard
具体操作此处不再赘述,默认运行端口是:8080
2.本地代码接入Sentinel dashboard
1)引入Sentinel相关依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--下面这些依赖没有也不影响,列在这儿备用-->
<!-- <dependency>-->
<!-- <groupId>com.alibaba.csp</groupId>-->
<!-- <artifactId>sentinel-core</artifactId>-->
<!-- <version>1.8.1</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>com.alibaba.csp</groupId>-->
<!-- <artifactId>sentinel-annotation-aspectj</artifactId>-->
<!-- <version>1.8.0</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>com.alibaba.csp</groupId>-->
<!-- <artifactId>sentinel-datasource-nacos</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>com.alibaba.csp</groupId>-->
<!-- <artifactId>sentinel-web-servlet</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>com.alibaba.csp</groupId>-->
<!-- <artifactId>sentinel-transport-simple-http</artifactId>-->
<!-- <version>1.8.0</version>-->
<!-- </dependency>-->
2)编写yml或properties配置文件
以sms-center的application.yml
文件为例:
server:
port: 8002
spring:
application:
name: Sms-center
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
sentinel:
transport:
dashboard: localhost:8080 #配置Sentinel dashboard地址
3)接入限流埋点
-
HTTP埋点:
Sentinel starter 默认为所有的 HTTP 服务提供了限流埋点,如果只想对 HTTP 服务进行限流,那么只需要引入依赖,无需修改代码。
-
自定义埋点:
1.使用原始的
SphU.entry(xxx)
方法进行埋点,使用这种方法需要结合try catch finally代码块,并且最后必须entry.exit()2.使用
@SentinelResource
注解对需要保护的资源进行限流埋点
我采用@SentinelResource
注解进行埋点:
/**
* 发送用户消息(用于测试Feign远程调用以及Sentinel流量控制)
*
* @param user
* @return
*/
@SentinelResource(value = "sendUser", blockHandler = "sendMsgBlockHandler")
@PostMapping("/sendUser")
public String sendMsgTest(@RequestBody User user) {
String result = smsService.sendUserMsg(user);
return result;
}
public String sendMsgBlockHandler(User user, BlockException e) {
log.warn("该接口被限流了:" + e);
return "该接口被限流了";
}
@SentinelResource
:
- value:资源名称-必填
- blockHandler:触发流控后调用的方法,public修饰,返回值和参数必须和原方法相同,且添加BlockException的参数
- blockHandlerClass:触发流控处理的类,里面的方法必须是static的(适用于建一个类来专门编写限流处理的方法,避免放在controller里面)
Smscontroller:
@SentinelResource(value = "sendUser", blockHandlerClass = {SmsBlockHandler.class}, blockHandler = "sendMsgBlockHandler")
@PostMapping("/sendUser")
public String sendMsgTest(@RequestBody User user) {
String result = smsService.sendUserMsg(user);
return result;
}
SmsBlockHandler:
@Slf4j
public class SmsBlockHandler {
public static String sendMsgBlockHandler(User user, BlockException e) {
log.warn("该接口被限流了:" + e);
return "该接口被限流了";
}
}
3.启动服务提供方和调用方
**访问服务调用方的接口
注意,Sentinel是Lazy Load策略的,服务虽然配置了Sentinel Dashboard的地址,但在第一次调用前,Sentinel的控制台的菜单是不会有相关组件的。
这个超级坑,我一直以为是自己服务没能正确接入Sentinel。
访问受到保护的接口,观察Sentinel控制台 > 簇点链路
为相应资源添加流控策略:
为了方便效果演示,我设置QPS=1
疯狂访问刚才添加了限流策略的接口,观察返回结果和控制台:
可以看到,流控成功了