首页 > 编程 > Java > 正文

springcloud之自定义简易消费服务组件

2019-11-26 09:56:08
字体:
来源:转载
供稿:网友

本次和大家分享的是怎么来消费服务,上篇文章讲了使用Feign来消费,本篇来使用rest+ribbon消费服务,并且通过轮询方式来自定义了个简易消费组件,本文分享的宗旨是:自定义消费服务的思路;思路如果有可取之处还请“赞”一下:

  1. Rest+Ribbon实现消费服务
  2.  Rest+轮询自定义简易消费组件
  3. 使用Scheduled刷新服务提供者信息

Rest+Ribbon实现消费服务

做为服务消费方准确的来说进行了两种主流程区分1)获取可以服务2)调用服务,那么又是如何获取服务的并且又是通过什么来调用服务的,下面我们来看一副手工图:

手工图上能够看出消费方先获取了服务方的真实接口地址,然后再通过地址去调用接口;然后对于微服务架构来说获取某一个类ip或端口然后去调用接口肯定是不可取的,因此微服务中产生了一种serviceid的概念;简单流程介绍完了,下面通过实例来分析;首先添加依赖如:

<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-web</artifactId></dependency><dependency>  <groupId>org.springframework.cloud</groupId>  <artifactId>spring-cloud-starter-eureka</artifactId></dependency>

再来我们通过上篇文章搭建的eureka_server(服务中心),eureka_provider(服务提供者)来做测试用例,这里我重新定义eureka_consumer_ribbon模块做为消费服务;先创建service层类和代码:

@Servicepublic class UserService implements UserInterface {  @Autowired  protected RestTemplate restTemplate;  @Override  public MoRp<List<MoUser>> getUsers(MoRq rq) {    return null;  }  @Override  public String getMsg() {    String str = restTemplate.getForObject("http://EUREKA-PROVIDER/msg", String.class);    return str;  }}

主要用到了RestTemplate的 restTemplate.getForObject 函数,然后需要定义个Controller来吧获取到的数据响应到页面上,为了简单这里仅仅只拿getMsg服务接口测试:

@RestControllerpublic class UserController {  @Autowired  private UserService userService;  @GetMapping("/msg")  public String getMsg(){    return userService.getMsg();  }}

最后我们在启动类添加入下代码,注意 @LoadBalanced 标记必须加,因为咋们引入的eureka依赖里面包含了ribbon(Dalston.RELEASE版本),ribbon封装了负载均衡的算法,如果不加这个注解,那后面rest方法的url就必须是可用的url路径了,当然这里加了注解就可以使用上面说的serviceId:

@SpringBootApplication@EnableDiscoveryClient //消费客户端public class EurekaConsumerRibbonApplication {  @Bean   @LoadBalanced //负载均衡  RestTemplate restTemplate(){    return new RestTemplate();  }    public static void main(String[] args) {    SpringApplication.run(EurekaConsumerRibbonApplication.class, args);  }}

下面来消费方显示的效果:

Rest+轮询自定义简易消费组件

自定义消费组件原来和面手工图差不多,就是先想法获取服务提供端真实的接口地址,然后通过rest去调用这个url,得到相应的结果输出;这里自定义了一个ShenniuBanlance的组件类:

/** * Created by shenniu on 2018/6 * <p> * rest+eureka+自定义client端 */@Componentpublic class ShenniuBanlance {  @Autowired  private RestTemplate restTemplate;  @Autowired  private DiscoveryClient discoveryClient;  /**   * 服务真实地址 ConcurrentHashMap<"服务应用名称", ("真实接口ip", 被访问次数)>   */  public static ConcurrentHashMap<String, List<MoService>> sericesMap = new ConcurrentHashMap<>();  /**   * 设置服务提供者信息到map   */  public void setServicesMap() {    //获取所有服务提供者applicationName    List<String> appNames = discoveryClient.getServices();    //存储真实地址到map    for (String appName :        appNames) {      //获取某个服务提供者信息      List<ServiceInstance> instanceInfos = discoveryClient.getInstances(appName);      if (instanceInfos.isEmpty()) {        continue;      }      List<MoService> services = new ArrayList<>();      instanceInfos.forEach(b -> {        MoService service = new MoService();        //被访问次数        service.setWatch(0L);        //真实接口地址        service.setUrl(b.getUri().toString());        services.add(service);      });      //如果存在就更新      sericesMap.put(appName.toLowerCase(), services);    }  }  /**   * 根据app获取轮询方式选中后的service   *   * @param appName   * @return   */  public MoService choiceServiceByAppName(String appName) throws Exception {    appName = appName.toLowerCase();    //某种app的服务service集合    List<MoService> serviceMap = sericesMap.get(appName);    if (serviceMap == null) {      //初始化所有app服务      setServicesMap();      serviceMap = sericesMap.get(appName);      if (serviceMap == null) {        throw new Exception("未能找到" + appName + "相关服务");      }    }    //筛选出被访问量最小的service 轮询的方式    MoService moService = serviceMap.stream().min(        Comparator.comparing(MoService::getWatch)    ).get();    //负载记录+1    moService.setWatch(moService.getWatch() + 1);    return moService;  }  /**   * 自动刷新 服务提供者信息到map   */  @Scheduled(fixedDelay = 1000 * 10)  public void refreshServicesMap() {    setServicesMap();  }  /**   * get请求服务获取返回数据   *   * @param appName   应用名称 ApplicationName   * @param serviceName 服务名称 ServiceName   * @param map     url上请求参数   * @param tClass   返回类型   * @param <T>   * @return   */  public <T> T getServiceData(      String appName, String serviceName,      Map<String, ?> map,      Class<T> tClass) {    T result = null;    try {      //筛选获取真实Service      MoService service = choiceServiceByAppName(appName);      //请求该service的url      String apiUrl = service.getUrl() + "/" + serviceName;      System.out.println(apiUrl);      result = map != null ?          restTemplate.getForObject(apiUrl, tClass, map) :          restTemplate.getForObject(apiUrl, tClass);    } catch (Exception ex) {      ex.printStackTrace();    }    return result;  }  /**   * Service信息   */  public class MoService {    /**     * 负载次数记录数     */    private Long watch;    /**     * 真实接口地址: http://xxx.com/api/add     */    private String url;    public Long getWatch() {      return watch;    }    public void setWatch(Long watch) {      this.watch = watch;    }    public String getUrl() {      return url;    }    public void setUrl(String url) {      this.url = url;    }  }}

以上就是主要的实现代码,代码逻辑:设置服务提供者信息到map-》根据app获取轮询方式选中后的service-》请求服务获取返回数据;轮询实现的原理是使用了一个负载记录数,每次被请求后自动+1,当要获取某个服务提供者时,通过记录数筛选出最小值的一个实例,里面存储有真实接口地址url;调用只需要这样(当然可以弄成注解来调用):

@Override  public String getMsg() {    String str = banlance.getServiceData(        "EUREKA-PROVIDER", "msg",        null,        String.class    );    return str;  }

这里需要注意由于我们在前面RestTemplate使用加入了注解 @LoadBalanced ,这样使得rest请求时必须用非ip的访问方式(也就是必须serviceid)才能正常响应,不然会提示错误如:

简单来说就是不用再使用ip了,因为有负载均衡机制;当我们去掉这个注解后,我们自定义的组件就能运行成功,效果图和实例1一样就不贴图了;

使用Scheduled刷新服务提供者信息

在微服务架构中,如果某台服务挂了之后,必须要及时更新client端的服务缓存信息,不然就可能请求到down的url去,基于这种考虑我这里采用了EnableSched标记来做定时刷新;首先在启动类增加 @EnableScheduling ,然后定义一个刷行服务信息的服务如:

/**   * 自动刷新 服务提供者信息到map    */  @Scheduled(fixedDelay = 1000 * 10)  public void refreshServicesMap() {    setServicesMap();  }

为了方便看测试效果,我们在server,provider(2个),consumer已经启动的情况下,再启动一个端口为2005的provider服务;然后刷新consumer接口看下效果:

这个时候能够看到调用2005端口的接口成功了,通过@Scheduled定时服务吧最新或者失效的服务加入|移除掉,就达到了咋们的需求了;如果你觉得该篇内容对你有帮助,不防赞一下,谢谢。希望对大家的学习有所帮助,也希望大家多多支持武林网。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表