SpringCloud微服务架构这几年已经使用非常广泛了,如下图是目前大多公司使用的基础业务架构图,通过围绕SpringCloud这个架构核心来做的各种部署和设计
但是随着业务量的增加,和部门团队的多样化,很多业务我们可能使用python或者golang实现从而更方便或者效率更高。比如国内某滴公司或者其他一些公司会使用到一些地图类的搜索算法,而这些算法大多使用python语言来做实现开源。
因此有上面这个例子引出我们这篇文章的思考,如何把python实现的微服务或者golang实现的微服务联合起我们已经的服务架构里,以实现更好的服务实现和平滑的访问切换,客户端无感知的状态
通过我们对基础架构图的了解来看实现结合多种语言来实现微服务的架构,比较简单的一种方案是通过nginx来配置反向代理实现,访问不同的语言的架构服务。设计图如下
这种设计的优点比较明显的就是不用考虑其他语言实现的微服务,直接通过nginx的配置,访问到对应的服务上;缺点是需要单独的配置不同的二级域名的访问,而且对于微服务的治理这块就是一块缺失的。因为这种方案的实现比较简单这里就不做过多介绍,虽然简单但是确实最可靠的一种方案。不会出现很多幺蛾子。
第二种方案实现的架构图如下,也就是需要把其他语言实现的微服务加入到erueka的注册中心治理中,如果实现了这样的服务架构,那么对于客户端来说就是无感知的。对于服务端来说也是比较友好的。不同的团队或者个人负责不同的微服务的维护,同时兼容了个人掌握的技能,真正实现技能的充分发挥
但是实现这样架构的前提是,不同语言需要维护其对于注册中心的注册以及心跳的检测等逻辑,而且每个注册中心都有自己不同的协议,如果开发过程更换注册中心的话,这一项也是一个需要考虑的点。不过现在一般轮子各路大神都已经造好, 切换起来成本也不是很高。具体参考下面实现部分
特别注意的是这里探讨的技术实现目前还是处于实验阶段,虽然能建立如上图所示的基础架构,但是没有完全使用在生成环境中,生成环境最靠谱的做法还是第一种架构设计
erueka客户端的实现,通过java的源码可发现其主要是通过http协议和注册中心REST请求通讯从而维护了一套客户端注册,心跳,断开等场景处理
com.netflix.discovery.DiscoveryClient
java 核心实现在这里
POST /eureka/apps/{APP_NAME}
{
"instance":{
"instanceId":"192.168.1.107:golang-example:10000",
"hostName":"192.168.1.107",
"ipAddr":"192.168.1.107",
"app":"golang-example",
"port":{
"@enabled":"true",
"$":10000
},
"securePort":{
"@enabled":"true",
"$":443
},
"status":"UP",
"overriddenStatus":"UNKNOWN",
"dataCenterInfo":{
"name":"MyOwn",
"@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo"
}
}
}
心跳主要的目的就是维护注册中心持续的关注此客户端,否则一段时间注册中心收不到来自客户端的心跳请求的时候就会默认删除对此客户端的维护
PUT /eureka/apps/{APP_NAME}/{INSTANCE_ID}?status=UP&lastDirtyTimestamp={TIMESTAMP}
GET /eureka/apps
或者 /eureka/apps/delta
前者获取全部的已注册的服务信息,后者则是获取增量的一些注册信息。<application>
<name>GOMOUDLEname>
<instance>
<instanceId>192.168.0.22:gomoudle:8506instanceId>
<hostName>192.168.0.22hostName>
<app>GOMOUDLEapp>
<ipAddr>192.168.0.22ipAddr>
<status>UPstatus>
<overriddenstatus>UNKNOWNoverriddenstatus>
<port enabled="true">8506port>
<securePort enabled="false">7002securePort>
<countryId>1countryId>
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
<name>MyOwnname>
dataCenterInfo>
<leaseInfo>
<renewalIntervalInSecs>10renewalIntervalInSecs>
<durationInSecs>30durationInSecs>
<registrationTimestamp>1561625268208registrationTimestamp>
<lastRenewalTimestamp>1561687102598lastRenewalTimestamp>
<evictionTimestamp>0evictionTimestamp>
<serviceUpTimestamp>1561624932375serviceUpTimestamp>
leaseInfo>
<metadata>
<SERVICE__VERSION__CODE>DEFAULTSERVICE__VERSION__CODE>
<VERSION>0.1.0VERSION>
<NODE__GROUP__ID>0NODE__GROUP__ID>
<PRODUCT__ENV__CODE>DEFAULTPRODUCT__ENV__CODE>
<PRODUCT__VERSION__CODE>DEFAULTPRODUCT__VERSION__CODE>
<PRODUCT__CODE>DEFAULTPRODUCT__CODE>
metadata>
<homePageUrl>http://192.168.0.22:8506homePageUrl>
<statusPageUrl>http://192.168.0.22:8506/infostatusPageUrl>
<vipAddress>gomoudlevipAddress>
<secureVipAddress>gomoudlesecureVipAddress>
<isCoordinatingDiscoveryServer>falseisCoordinatingDiscoveryServer>
<lastUpdatedTimestamp>1561625268208lastUpdatedTimestamp>
<lastDirtyTimestamp>1561625268207lastDirtyTimestamp>
<actionType>ADDEDactionType>
instance>
application>
当我们拿到如上这些已经注册到注册中心的客户端服务,就可以根据其ip进行http的数据通讯
当服务终止的时候需要从注册中心中移除,使用如下协议请求
DELETE /eureka/apps/{APP_NAME}/{INSTANCE_ID}
通过如上基础的于注册中心的通讯就能建立起一个eureka的客户端,由于其通讯协议是基于http的消息协议,也就为我们实现跨语言的微服务使用同一个注册中心打下了基础。需要注意的是在java里实现心跳连接和定时刷新服务列表是使用了heartbeatExecutor
、cacheRefreshExecutor
两个线程池来完成的。相应的我们在其他语言里也可以按照相同的设计原理来实现
python的一个比较轻量级的web服务框架tornado
,可以快速搭建起基础的简易的http服务。
github.com/xuanbo/eureka-client
这里也有其他开发人员实现了eureka的python客户端,同时还支持跨服务的访问,基于这两个框架基础就很容实现python的微服务并注册到eureka注册中心,详细代码如下
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import string
import tornado.web
import py_eureka_client.eureka_client as eureka_client
from tornado.options import define, options
#当前服务监听的端口
define("port", default=8505, help="run on the given port", type=int)
#当前在eureka中注册的名称
define("appName", default='python', help="app name in eureka", type=string)
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.write('[GET] python server...')
def eurekaclient():
tornado.options.parse_command_line()
#注册eureka服务
eureka_client.init(eureka_server="http://127.0.0.1:8502/eureka/",
app_name=options.appName,
instance_port=options.port,
# 调用其他服务时的高可用策略,可选,默认为随机
ha_strategy=eureka_client.HA_STRATEGY_RANDOM,
data_center_name='MyOwn'
)
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
print("eureka exec")
if __name__ == "__main__":
eurekaclient()
使用的代码比较简单,有兴趣的话我们自己可以也去实践一下实现对于服务注册到eureka的这个流程。这里我本地的注册中心端口是8502,网关端口是8504
http://localhost:8504/python/ 当访问网关加上我们的python应用名称能访问通的话,说明已经ok了。
注意需要把我们注册到注册中心的应用名称这里叫python
加入到网关服务的配置中 zuul.routes.python=/python/**
和python的流程类似,只是换了一种语言实现而已。具体流程和python的完全一致就不做特别的介绍。主要还是实现eureka客户端的这款是我们需要关注的核心,也是有待改造和完善的这里我们只做基础研究使用
package main
import (
"fmt"
"net/http"
eureka "github.com/xuanbo/eureka-client"
)
func main() {
// create eureka client
client := eureka.NewClient(&eureka.Config{
DefaultZone: "http://127.0.0.1:8502/eureka/",
App: "gomoudle",
Port: 8506,
RenewalIntervalInSecs: 10,
DurationInSecs: 30,
Metadata: map[string]interface{}{
"VERSION": "0.1.0",
"NODE_GROUP_ID": 0,
"PRODUCT_CODE": "DEFAULT",
"PRODUCT_VERSION_CODE": "DEFAULT",
"PRODUCT_ENV_CODE": "DEFAULT",
"SERVICE_VERSION_CODE": "DEFAULT",
},
})
// start client, register、heartbeat、refresh
client.Start()
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
// full applications from eureka server
//apps := client.Applications
//b, _ := json.Marshal(apps)
name := "hello welcome go moudle"
_, _ = writer.Write([]byte(name))
})
// start http server
if err := http.ListenAndServe(":8506", nil); err != nil {
fmt.Println(err)
}
}
谈起架构我们都知道没有一种架构是能适应所有的引用场景,只有根据我们自己的实际场景选择最适合我们的架构方案才是最优的。对于这里提到的两种架构方案,都有可取之处也有一定弊端。但是确都是可以通过我们不断的完善架构设计和代码设计来解决的。这里只是用eureka注册中心来举例,如果有兴趣可以尝试一下使用其他注册中心如何来实现跨语言的多微服务结合的使用原理大多是相通的。生命在于折腾!!
随大流来一波微信赞赏码