本次和大家分享的是怎么来消费服务,上篇文章讲了使用feign来消费,本篇来使用rest+ribbon消费服务,并且通过轮询方式来自定义了个简易消费组件,本文分享的宗旨是:自定义消费服务的思路;思路如果有可取之处还请“赞”一下:
- rest+ribbon实现消费服务
- rest+轮询自定义简易消费组件
- 使用scheduled刷新服务提供者信息
rest+ribbon实现消费服务
做为服务消费方准确的来说进行了两种主流程区分1)获取可以服务2)调用服务,那么又是如何获取服务的并且又是通过什么来调用服务的,下面我们来看一副手工图:
手工图上能够看出消费方先获取了服务方的真实接口地址,然后再通过地址去调用接口;然后对于微服务架构来说获取某一个类ip或端口然后去调用接口肯定是不可取的,因此微服务中产生了一种serviceid的概念;简单流程介绍完了,下面通过实例来分析;首先添加依赖如:
1
2
3
4
5
6
7
8
|
<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层类和代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@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服务接口测试:
1
2
3
4
5
6
7
8
9
10
11
12
|
@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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@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的组件类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
/**
* 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;调用只需要这样(当然可以弄成注解来调用):
1
2
3
4
5
6
7
8
9
10
|
@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 ,然后定义一个刷行服务信息的服务如:
1
2
3
4
5
6
7
|
/**
* 自动刷新 服务提供者信息到map
*/
@scheduled (fixeddelay = 1000 * 10 )
public void refreshservicesmap() {
setservicesmap();
}
|
为了方便看测试效果,我们在server,provider(2个),consumer已经启动的情况下,再启动一个端口为2005的provider服务;然后刷新consumer接口看下效果:
这个时候能够看到调用2005端口的接口成功了,通过@scheduled定时服务吧最新或者失效的服务加入|移除掉,就达到了咋们的需求了;如果你觉得该篇内容对你有帮助,不防赞一下,谢谢。希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:http://www.cnblogs.com/wangrudong003/p/9134870.html