1.openfeign是声明式注解,完成服务与服务调用的组件 整合 ribbon和 eureka,
默认有远程调用负载均衡
先创建接口并对其注解
是一个声明式的web服务客户端
编程式写代码
2.使用
1.创建一个项目 不用使用feign的有eureka web lombok依赖写个controller
写个配置文件 应用名字要有,注册eureka到注册中心2.创建另外一个项目 创建一个feign文件夹 创建一个接口 加@FeignClients(value="my-provider")//应用名称 启动类加 EnableFeignClients(basepackages={top.jamsee.demo.feign}) 3.使用 需要添加 @Autowired 上面那个接口 4.feign的默认等待时间是等待1s 在使用的项目写配置文件 //改ribbon的超时时间,没有提示 ribbon: ReadTimeout: 3000 //服务处理数据 ConnectTimeout: 3000 #连接超时时间
3.手写feign(jdk)
#jdk代理(接口) 必须走invoke方法
#cglib(子类)代理
#在测试类写的 contextLoads()写 ,注入restTemplate
UserorderFeign userorderFeign=(UserorderFeign)Proxy.newProxyInstance(UserController.class.getClassLoader(),new Class[]{UserorderFeign.class},
new InvocationHandler{
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
//需要拿到ip port 和方法上面的注解
String name=method.getName();
GetMapping annotation =method.getAnnotation(GetMapping.class);
String[] paths=annotation.value();//得到路径
String path=path[0];
//得到类的注解
Class<?> aClass=method.getDeclaringClass();
FeignClient annotation1 = aClass.getAnnotation(FeignClient.class);
String applicationName=annotation1.value();
String url = "http://"+applicationName+"/"+path;
RestTemplate.getForgetForObject(url,String.class)
return "唱跳rap"
}
}
)
String s=o.doOrder();//只要调用方法必走invoke方法
//启动类使用
@Bean
@LoadBalanced
public RestTemplate restTamplate(){
return new RestTemplate();
}
4.feign传多个参数 @RequestParam必须写名字以防报错
1.controller 提供者
@GetMapping("testUrl/{name}/and/{age}")
public String testUrl(@PathVariable("name") String name,@PathVariable("age") String age){
//print
return "ok";
}
//一个参数,不是必须要写 ,基本参数加@RequestParam,对象 @RequestBody
@GetMapping("oneParam")
public String testUrl(@RequestParam(required= false) String name,@RequestParam(required= false) Integer age){
//print
return "ok";
}
//post传一个对象 editor ->general-->code completion -->match case可改对大小不敏感
@PostMapping("oneObjOneParam")
public String testUrl(@RequestBody Order order,@RequestParam("name") String name){
return "ok";
}
2.消费者写feign接口 x(报错)
//快速set对象
Order order =Order.builder()
.name()
.price(188D)
.builder();
3.不要单独传递时间,时间会变化
转成字符串
使用 jdk8的数据
LocalDate now =LocalDate.now();//年月日
LocalDateTime now1 =LocalDateTime.now();//年月日时分秒,会丢失秒
5.feign的源码分析(调试)
1.spring源码的 实现几个类就可以放入ioc容器里面操作
registerFeignClient
reflectiveFeign创建对象
2. 401需要权限 403token权限不够 400请求参数不符合固定
429 被限流 404路径不匹配 405方法不允许(post发成get) 302资源重定向
可以在 httpStatus.java看
3.可以看 Client.java的状态码
@Autowired
private RestTemplate restTemplate;
@Test
void contextLoads() {
ProviderUserFeign o =(ProviderUserFeign)Proxy.newProxyInstance(ProviderUserFeign.class.getClassLoader(), new Class[]{ProviderUserFeign.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
GetMapping annotation = method.getAnnotation(GetMapping.class);
String[] paths = annotation.value();
String path=paths[0];
Class<?> aClass = method.getDeclaringClass();
FeignClient annotation1 = aClass.getAnnotation(FeignClient.class);
String serviceName = annotation1.value();
String url="http://"+serviceName+"/"+path;
String forObject = restTemplate.getForObject(url, String.class);
return forObject;
}
});
String s = o.userdoOrder();
System.out.println(s);
}
6.开启feign日志打印
//在启动类下写 日志级别
@Bean
public logger.Level level(){
return Logger.Level.Full;
}
//开启日志功能(配置文件),500ms比较合理
logging:
level:
top.jamsee.feign.UserFeign: debug
7.服务雪崩(a需要调用b服务,b需要调用c c挂了,需要等待超时 链式调用)
(大量请求来,tomcat支持500并发)(一个服务挂了,其他服务都不可用)
(线程没有及时回收)(要及时熔断/调整等待时间[业务可能完成不了])
(上游服务知道下游服务状态)(选择备选方案)
- hystrix断路器 保护服务不雪崩
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
与feign ribbon一起使用
//使用 加hystrix依赖 都是eureka client
//复制配置文件 改应用名字
//在feign加个hystrix文件夹 实现接口 加 @Component
public String rent(){
}
//在接口注解加熔断的类
@FeignClient(value="provider",fallback = xxxHystrix.class)
//在消费者配置开启熔断,F版之前默认开启,sentinel出来了,
//上线后需要等待
feign:
hystrix:
enabled: true
//feign注入出现波浪线,因为有多个Bean
// 可以通过
@Autowired
@Qualifed("指定名字")
//或者 @Resource 按名字注入
//或者highlight 调成 syntax只提示语法错误
private xxx xxx;
9.手写断路器
1.拦截器状态机制(第一次必请求,多次没有成功就认为不可服务)
另外的服务恢复上线,半开,少量流量去调用,如果正常关断路器
2.写个枚举
public enum FishStatus{
CLOSE,
OPEN,
HALF_OPEN
}
3.添加切面的依赖
org.springframework.boot
spring-boot-starter-aop
4.controller有个接口
5. 写个aspect文件夹写切面
@Component
@Aspect
public class FishAspect{
// ..代表里面的所有参数
public static Map<String,Fish> fishMap =new HashMap<>();
static{
fishMap.put("order-service",new Fish());
}
{
//只管清除计数
poolExecutor.execute(()->{
while(true){
try{
TimeUnit.SECONDS.sleep(WINDOW_TIME);
}catch(InterruptedException e){
e.printStackTrace();
}
//断路器关闭,说明要去访问,要清零
if(this.status.equals(FishStatus.CLOSE)){
this.currentFailCount.set(0);
}else{
//半开/开不计次数
//线程可以不工作
synchronized(lock){
try{
lock.wait();//等待被用代码唤醒
sout//被唤醒
}catch(xxx x){
xxx
}
}
}
}
})
}
Random random =new Random();
public static final String POINT_CUT=
"execution(*top.jamsee.controller.xxController.doxx(..))";
@Around(value="@annotation(top.jamsee.anno.MyFish)"); //要切的注解
public Object fishAround(ProceedingJoinPoint joinPoint){
//模拟得到得到当前提供者断路器
Fish fish =fishMap.get("order-service");
FishStatus status = fish.getStatus();
switch(status){
case CLOSE:
try{
result=joinPoint.proceed();
return result;
}catch(Throwable throwable){
fish.addFailCount();
return "我是备胎";
}
case OPEN:
return "我是备胎";
case HALF_OPEN:
//模拟调用成功,关闭断路器
int i= random.nextInt(5); //0-4
if(i == 1){
try{
result =joinPoint.proceed();
fish.setStatus(FishStatus.CLOSE);
synchronized(fish.getLock()){
fish.getLock().notifyAll();//唤醒线程进行执行
}
}catch(Throwable throwable){
return "我是备胎";
}
}
default:
return "我是备胎";
}
}
}
6.建 anno写注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyFish{
}
@Data
public class Fish{
private FishStatus status= FishStatus.CLOSE;
public static final Integer WINDOW_TIME = 20;
public static final Integer MAX_FAIL_COUNT = 3;
//搞个线程池
private ThreadPoolExecutor poolExecutor =new ThreadPoolExecutor(
4, //核线程数
8, //线程池个数
30,//等待时间
TimeUnit.SECONDS,//单位
new LinkedBlockingQueue<>(2000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutors.AbortPolicy()
);
//可以保证线程安全
private AtomicInteger currentFailCount =new AtomicInteger (0);
//记录失败次数
public void addFailCount(){
int i =currentFailCount.incrementAndGet();
if(i>= MAX_FAIL_COUNT){
this.setStatus(FishStatus.OPEN);
//等时间窗口变成半开
//过段时间变为半开,多开一个线程保证系统不会卡死(多个访问)
//每次new线程比较浪费,用线程池
//new Thread(()->{
// })
poolExecutor.execute(()->{
try{
TimeUnit.SECONDS.sleep(WINDOW_TIME);
}catch(InterruptedException e){
e.printStackTrace();
}
this.setStatus(FishStatus.HALF_OPEN);
});
}
}
}
10.配置文件的配置(isolation的strategy:thread线程池隔离)(信号量隔离都用一个线程池,隔离不彻底,提供者都用一个计数器)
(处理用户请求,和发出处理请求是不同线程的)