03.微服务feign和hystrix

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并发)(一个服务挂了,其他服务都不可用)
        (线程没有及时回收)(要及时熔断/调整等待时间[业务可能完成不了])
         (上游服务知道下游服务状态)(选择备选方案)
  1. 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线程池隔离)(信号量隔离都用一个线程池,隔离不彻底,提供者都用一个计数器)
(处理用户请求,和发出处理请求是不同线程的)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值