springcloud之自定义简易消费服务组件
本次和大家分享的是怎么来消费服务,上篇文章讲了使用feign来消费,本篇来使用rest+ribbon消费服务,并且通过轮询方式来自定义了个简易消费组件,本文分享的宗旨是:自定义消费服务的思路;思路如果有可取之处还请“赞”一下:
- rest+ribbon实现消费服务
- rest+轮询自定义简易消费组件
- 使用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层类和代码:
@service public 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服务接口测试:
@restcontroller public 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端 */ @component public 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定时服务吧最新或者失效的服务加入|移除掉,就达到了咋们的需求了;如果你觉得该篇内容对你有帮助,不防赞一下,谢谢。希望对大家的学习有所帮助,也希望大家多多支持。