前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一个微服务架构的简单示例

一个微服务架构的简单示例

作者头像
程序你好
发布2018-07-20 16:40:25
3.7K0
发布2018-07-20 16:40:25
举报
文章被收录于专栏:程序你好

最近,在学习微服务架构,看了很多相关的资料,可一直都没有真正动手操作。所以今天,我创建了一个简单的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,我编写了以下代码:

代码语言:javascript
复制
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从命令行测试服务:

代码语言:javascript
复制
 $curl http://localhost:8888/api/v1/words

就是这样,我建立了一个微服务。我很兴奋。我几乎完成了!好的,也许它不应该每次从本地文件返回相同的响应。这似乎很容易解决,让我们继续。。

我觉得我需要多增加一些处理逻辑,服务不仅需要接受和响应输入内容,而且作为HTTP服务,它还应该返回至少一个状态代码。而且,每次通过发出请求来测试核心逻辑(提取文本),这看起来很麻烦。最后,虽然这并没有很多代码,但是将函数代码与框架隔离似乎是一个好主意,从而为其他服务设置约定,其中一些服务可能涉及更复杂的逻辑。

最后,我写了另一个文件,看起来是这样的:

代码语言:javascript
复制
           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抓取的代码。

代码语言:javascript
复制
            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项目。

代码语言:javascript
复制
        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应用程序来管理。

代码是这样的:

代码语言:javascript
复制
            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类非常简单,它只有两行:

代码语言:javascript
复制
            class WordCloudForm(forms.Form):            
                urls = forms.CharField(.Textarea)

the wordcloud.html 也没模板也很简单

代码语言:javascript
复制
            {% 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任务:

代码语言:javascript
复制
            $ 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年,我们来到了微服务领域。但是,从我所看到的情况来看,微服务现在被限制为组织内客户提供服务,而不是对于开放互联网上的任何客户。以后或许微服务会走入互联网。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-06-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序你好 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
测试服务
测试服务 WeTest 包括标准兼容测试、专家兼容测试、手游安全测试、远程调试等多款产品,服务于海量腾讯精品游戏,涵盖兼容测试、压力测试、性能测试、安全测试、远程调试等多个方向,立体化安全防护体系,保卫您的信息安全。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档