博主老规矩,先写一个首语
spring系列微服务通信以rest为主,2.0以下版本以eureka做服务注册中心为主,2.0以上eureka不在维护,博主团队使用了阿里的Nacos。在博主上一篇博客已经简单的介绍了从0到1搭建一个项目,然后注册在eureka上。
注意:博主不会公开企业级内部的代码,博主会将它变成比较简单的demo,提供设计思想,供大家学习。因为每个企业或者团队思想差异,会造成框架与流程也变得不一样。企业级的代码反倒会变得臃肿~
spring微服务系列如果是MVC同步式变成,几乎在通信层面都会使用Feign组件(在异步webflux方面使用webclinet)
我们首先创建两个项目, demo-one与demo-two
我们引入feign的组件包,顺便将测试包也引入
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.boot
spring-boot-starter-test
test
我们来为demo-one写一个hello world接口
@GetMapping("/say/hello")
public String sayHello() {
return "hello world";
}
我们在demo-two中写相应的feign接口以及测试类(关于api抽离,后续博主会为搭建展现,目前只是简单介绍一些组件的使用,后续会涉及到组件包抽离、自定义starter、全程异步话webflux企业运用等)
// demo-one的服务名是DEMO-ONE
@FeignClient(name = "DEMO-ONE")
public interface DemoOneApi {
@GetMapping("/say/hello")
public String sayHello();
}
然后我们来编写一个简单的测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Boot.class)
@EnableFeignClients(basePackages = {"com.github.codemperor.demo.two"}) //这一行需要加哦~
public class FeignTest {
@Autowired
private DemoOneApi demoOneApi;
@Test
public void demoOneFeignTest() {
System.out.println(demoOneApi.sayHello());
}
}
然后我们启动eureka以及demo-one
运行测试类,发现打印hello word,说明两个服务通信成功
我们来思考一个很重要的问题:
多服务通信的时候,我们在demo-two中拷贝了demo-one的接口(有点类似dubbo rpc),然后利用interface进行通信,那么问题来了:
一般情况下,部分小公司会直接提取一个api(参考博主的code项目),变成一个jar,但这样的方式,不够自动化,所以需要一些api scan机制,配合网关集中管理所有api。
至于底层实现原理,和rpc是不同的:
这里就不画图了,feign只是封装的调用层,在开始调用的时候,feign会去获取调用目标地址,然后直接
http://xx.xx.xx.xx/say/hello
以java远程socket作为最底层通信协议,那么rpc其实利用的是序列化以及反射原理。为什么要拷贝接口,是因为需要获取接口信息,简单来说,如果知道class信息以及方法名称,就可以利用java反射来执行方法。
那么我们可以模拟这个过程:
@RunWith(SpringJUnit4ClassRunner.class)
public class RpcTest {
@Test
public void rpcTest() throws Exception {
// 第一个参数是类,第二个是执行方法, 第二个是参数
var fromClient = List.of("com.github.codemperor.demo.one.bean.RemoteBean", "sayHello", "你好呀");
var clazz = Class.forName(fromClient.get(0));
// 为了不暴漏接口序列化的信息,我们可以设定协议,比如A代表了RemoteBean这个类,那就变成:
if ("A".equalsIgnoreCase(fromClient.get(0))) {
var clazz1 = RemoteBean.class;
// 后面使用class1
}
Object ob = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getMethod(fromClient.get(1), String.class);
method.invoke(ob, fromClient.get(2));
}
}
所以这里就涉及到rpc的通信协议。我们可以利用netty等底层通信框架(或者java socket)来模拟一次rpc过程。
这里要明确一点,feign不是hystrix,只是对它进行了简易的封装,依然考的是反射等底层机制
我们先来写一个接口,并且让它睡5秒
@GetMapping("/feign/fallback")
public String sayFeignFallback() throws InterruptedException {
Thread.sleep(5000);
return "say feign fallback";
}
然后和上面一样,在demo-two中把这个接口弄过来,然后加上fallback机制
@FeignClient(name = "DEMO-ONE", fallbackFactory = DemoOneApiFallback.class)
public interface DemoOneApi {
@GetMapping("/say/hello")
public String sayHello();
@GetMapping("/feign/fallback")
public String sayFeignFallback();
}
可以看到,我们加入了DemoOneApiFallback作为熔断降级后的业务处理(这里注意一点,如果降级后,还需要继续调用另外的服务,另外的服务也GG了,然后又回到熔断这里,形成死循环,这个时候你可能会被打死)
@Component
public class DemoOneApiFallback implements FallbackFactory {
@Override
public DemoOneApi create(Throwable throwable) {
return new DemoOneApi() {
@Override
public String sayHello() {
return null;
}
@Override
public String sayFeignFallback() {
return "对方挂了斯密达, 我先回一个~~";
}
};
}
}
demo-two调用demo-one, 自然demo-two要开启feign的熔断,我们这里设定3秒内不回答就gg:
时间的设定(nacos可以直接利用timeout机制实时pull的方式,可以动态修改各种配置(配置中心也可以)),需要根据业务以及网络架构延时来制定,比如博主digital团队是小业务,所以业务一般在接口级,而接口都是云内网调用,一般即使加上数据库,也只有50ms,所以博主这边设置1s内获取不到就等于超时,走熔断~
feign:
client:
config:
default: #服务名,填写default为所有服务
connectTimeout: 10000
readTimeout: 10000
hystrix:
enabled: true
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
我们来写一个测试接口,demo-one接口设定5秒内返回,熔断设定3秒内不回答就走熔断:
@Test
public void demoOneFeignFallbackTest() {
System.out.println(demoOneApi.sayFeignFallback());
}
结果果然走了熔断,修改熔断时间,变为10秒,发现走了接口返回
这一篇博客依然是从0到1搭建以及理解的基础知识(感觉博主自己坑了自己)
后续鉴权中心、网关设定、核心包sdk抽离感觉遥遥无期啊~~博主抓紧结束基础篇。
附上学习代码:https://gitee.com/_madi/codemperor.git