最近,在学习微服务架构,看了很多相关的资料,可一直都没有真正动手操作。所以今天,我创建了一个简单的web应用程序示例,让我们通过这个例子来更好地感受微服务的系统架构魅力。这款应用程序做的非常简单:提供一批网上招聘的URL,我们的Web应用就能找到工作描述的文字,并生成一个Word Cloud(词云:许多特定意义的词)。在某些特定的职位招聘中,能够掌握专业技能或流行词汇对HR的人员来说是非常有用的。
微服务应该是独立的、无状态的应用程序,每个应用程序都只关注于某件小事。在这个示例的应用程序中,有以下几个任务:
1)从url指定的页面中检索内容;
2)从工作描述中提取所有词语;
3)创建一个word cloud。
建立这么简单的微服务花费不了多少时间,在下面会详细描述。在实际应用中,我们不可能在网上直接公开发布这些服务,因为没有身份验证、无法防止DOS攻击,没办法控制使用的用户。此外,我还准备提供一个带用户界面的app。所以我添加了一个MVC服务器,它将创建一个表示层。在微服务架构里,这实现也类似于API网关的模式。
由于微服务不需要大量的web应用程序组件,比如Session或用户管理等,使用Flask或Tornado建立Web应用似乎都是不错的选择。以为最近总是听到Tornado,我对它很好奇,所以选择使用它。关于如何使用Tornado创建Web应用程序,网上有很多例子,其中也包括一些谈论微服务的例子。基于这些示例,再加上最常用的语言是JSON,我编写了以下代码:
i mport json
import tornado.ioloop
import tornado.web
from bs4 import BeautifulSoup
class WordsHandler(tornado.web.RequestHandler):
def get(self):
f = open("dice_job_page.html", "r")
html = f.read()
soup = BeautifulSoup(html, "html5lib")
job_desc = soup.find("div", id="jobdescSec")
job_text = job_desc.stripped_strings
words = ' '.join(job_text)
json_response = json.dumps({'data':words})
self.write(json_response)
def make_app():
return tornado.web.Application([
(r"/api/v1/words", WordsHandler),
])
if __name__ == "__main__":
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
这是最简单的代码,当执行此文件时,响应端口8888上的HTTP GET请求,该服务读取一个本地文件,使用html5lib和BeautifulSoup解析它,并返回JSON包装中的单词。
可以使用curl从命令行测试服务:
$curl http://localhost:8888/api/v1/words
就是这样,我建立了一个微服务。我很兴奋。我几乎完成了!好的,也许它不应该每次从本地文件返回相同的响应。这似乎很容易解决,让我们继续。。
我觉得我需要多增加一些处理逻辑,服务不仅需要接受和响应输入内容,而且作为HTTP服务,它还应该返回至少一个状态代码。而且,每次通过发出请求来测试核心逻辑(提取文本),这看起来很麻烦。最后,虽然这并没有很多代码,但是将函数代码与框架隔离似乎是一个好主意,从而为其他服务设置约定,其中一些服务可能涉及更复杂的逻辑。
最后,我写了另一个文件,看起来是这样的:
def get_words(html):
try:
soup = BeautifulSoup(html, "html5lib")# (1)
job_desc = soup.find("div", id="jobdescSec")# (2)
if not job_desc:
return None
else:
job_text = job_desc.stripped_strings# (3)
words = ' '.join(job_text)# (4)
json_response = json.dumps({'data':words})# (5)
return json_response
except Exception as e:
return None
class WordsHandler(tornado.web.RequestHandler):
def get(self):
self.write("Breaking with all conventions, this API does not support GET")
def post(self):
html = self.get_argument("html")
json_response = get_words(html)
if not json_response:
self.set_status(HTTP_STATUS_NO_CONTENT, 'There was no content')
else:
self.write(json_response)
self.set_header('Content-Type', 'application/json')
self.set_status(HTTP_STATUS_OK)
前面一到五行代码与原始版本完全相同。它们被隔离在一个名为get_words的函数中,该函数可以在不运行Tornado的情况下独立地进行单元测试。在处理程序本身代码中,有一些代码用于返回状态代码并设置其他HTTP头。如果有必要,还可以增加更多。
而设置和启动Tornado的代码则保留在原始文件中。
另外两个用于抓取页面内容和生成word Cloud的服务的代码结构也是大体相同的。
这里展示仅仅是URL抓取的代码。
def get_data(url):
if not url:
return None
try:
response = requests.get(url)
response64 = base64.encodebytes(response.content)
return response64.decode()
except Exception as e:
return None
class URLHandler(tornado.web.RequestHandler):
def get(self):
url = self.get_argument("url")
data = get_data(url)
if not data:
self.set_status(HTTP_STATUS_NO_CONTENT, 'There was no content')
else:
self.write({'data': data})
self.set_header('Content-Type', 'application/json')
self.set_status(HTTP_STATUS_OK)
如果你想知道,为什么response.content是Base64编码的,因为它是一个字节数组,不是直接的JSON可序列化的。
这里是Make Wordcloud 代码。它使用word_cloud项目。
def get_image(words):
if not words:
return None
try:
# Generate a word cloud image using the word_cloud library
wordcloud = WordCloud(max_font_size=80, width=960, height=540).generate(words)
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis("off")
pf = io.BytesIO()
plt.savefig(pf, format='jpg')
jpeg64 = base64.b64encode(pf.getvalue())
return jpeg64.decode()
except Exception as ex:
return None
class WordCloudHandler(tornado.web.RequestHandler):
def get(self):
self.write("Breaking with all conventions, this API does not support GET")
def post(self):
words = self.get_argument("words")
image = get_image(words)
if not image:
self.set_status(HTTP_STATUS_NO_CONTENT,'There was no content')
else:
self.write(json.dumps({'data':image}))
self.set_header('Content-Type', 'application/json')
self.set_status(HTTP_STATUS_OK)
微服务建好之后,我只需要创建视图控制器来接收用户提交的url,使用这些微服务构建响应,并向用户发送响应。
我使用Django来构建应用服务器,因为我只想关注我需要的功能,而其他的内容可以由web应用程序来管理。
代码是这样的:
class WordCloudView(TemplateView):
template_name = "cloudfun/wordcloud.html"
form_class = WordCloudForm
def get(self, request, *args, **kwargs):
form = self.form_class()
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
results = None
if form.is_valid():
url = form.cleaned_data['urls'].strip()
resp = requests.post('http://localhost:8888/api/v1/fromurl',data={'url':url})
html = resp.json()['data']
resp = requests.post('http://localhost:8887/api/v1/words',data={'html':html})
words = resp.json()['data']
resp = requests.post('http://localhost:8886/api/v1/wordcloud',data={'words':words})
results= resp.json()['data']
return render(request, self.template_name, {'form': form, 'results': results})
这个视图类的初始版本假设用户只输入了一个URL。这些服务都被hardcode到控制器中(稍后详细介绍)。一个微服务的响应直接插入到下一个微服务中。Django类非常简单,它只有两行:
class WordCloudForm(forms.Form):
urls = forms.CharField(.Textarea)
the wordcloud.html 也没模板也很简单
{% block content %}
Paste URLs into the following:
<form action="{% url 'wordcloud' %}" method="post">
{% csrf_token %}
{{ form}}
<input type="submit" value="OK">
</form>
{% if results %}
<div><img alt="Embedded Image"
src="data:image/png;base64,{{ results }}" /></div>
{% endif %}
{% endblock %}
我现在已经编写了足够的代码来获取用户提交的URL,并向它们显示一个word cloud。是时候检验一下了。
我启动了三个微服务作为后台python任务:
$ python microservices/cloud_creator/api_server.py &
$ python microservices/fetch_url/api_server.py &
$ python microservices/dice_scraper /api_server.py &
我在浏览器中启动了Django服务器和页面http://localhost:8000/cloudfun,使用从Dice.com网站获取的URL,然后单击OK。
它工作!
我在浏览器中看到了下面的图片。
从这个简单的微服务示例中,我被微服务的魅力吸引住了。它让我们思考,怎么样将一个大的系统分解成离散的服务,这也就是所谓的关注点分离。我们可以想象,如果您正在构建一个电子商务页面,需要获取商品搜索结果,您可能会启动十几个异步子请求,这些子请求都返回可以组装成一个页面的各种信息数据。在我的脑海里,我想象着一辆F1赛车停在一个维修站,一群工人猛扑上去,然后迅速把它恢复到正常状态,继续前行。
我花费了一个下午的时间完成上面的示例,还有一些代码需要改进。最大的问题是服务的位置被硬编码到视图控制器中。
当然,关注点分离长期以来一直是软件工程关注的焦点。面向对象编程也建议这么做。然后是CORBA,一个由10个IBM工程师组成的团队花了6个月的时间来功能。接下来是web Service和SOAP。当我在2001年为法国电信工作时,我对SOAP进行了评估,可以保证了互操作性。于是我使用Java Web Service来与.Net服务通信。结果发现各式各样的问题,我记得那简直地狱。人们一直在幻想Web服务的扩散,通过使用WSDL编写的服务契约自动被发现。会有航班预订网络服务,金融服务,如果有一个服务瘫痪了,系统就可以查到另一个,令人兴奋的东西。
快进15年,我们来到了微服务领域。但是,从我所看到的情况来看,微服务现在被限制为组织内客户提供服务,而不是对于开放互联网上的任何客户。以后或许微服务会走入互联网。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有