前端微服务

814 阅读11分钟

前端本来是没有微服务的概念,这个是从后端借鉴过来的。为了解释清前端的微服务,我分为3个部分:

  1. 什么是http服务器?
  2. 集群,分布式?
  3. 微服务

1 http服务

浏览器是http(s)的客户端,目的是连接远程的http服务器,然后服务器返回浏览器数据.浏览器接收数据解析数据之后展现出来.我们看到的外在表现就是,浏览器访问一个url,然后就得到相应的web页面

同样我们知道,浏览器与http服务器是通过http协议,传输层是tcp协议,关于http协议简单的介绍一下

一个标准的HTTP请求由以下几个部分组成:

<request-line>
<headers>
<CRLF>
[<request-body><CRLF>]

大家打开windows(fiddler)或iOS青花瓷(Charles)就可以看到完整的请求。

在HTTP请求中,第一行是请求行(request-line),用来说明请求类型、要访问的资源(URL)以及使用的HTTP版本;紧接着是多行头部(headers)信息,用来说明服务器要使用的附加信息;头部信息之后是一个回车换行符(\r\n),用于标明头部信息的结束。以上是必须内容,根据需要可在头部信息结束之后增加主体数据(request-body);主体数据之后是一个回车换行符(\r\n),用于标明主体数据的结束。

http服务器端接收到request之后,能够响应并给予反馈response,那么这就是一个合格的http服务器。例如,我们可以在node里启动一个最基础的http服务器,只需要非常简单的几行代码,如下:

## http.js
const http = require('http');
http.createServer(function (req, res) {
  console.log('访问路径是:' + req.url);
  console.log('客户端地址:', req.socket.remoteAddress, req.socket.remotePort);

  var url = req.url;
 
  if (url === '/') {
    res.end('index page');
  }
}).listen(3000);
console.log('server has started...');

运行node http.js,在浏览器输入:http://localhost:3000/index.html,打开Chrome的network,可以看到如下信息:

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: max-age=0
Connection: keep-alive
Host: localhost:3000
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36

收到请求数据之后,服务器解析;然后服务器就知道了客户端的要求:根目录/index.html文件。服务器根据访问路径,读取文件或直接把内容发送给浏览器就好了。在向客户端response过程中,是html还是css或是image服务器可以定义返回的文件类型。

另外,解析和渲染html文件从来都是浏览器的事,这点大家要有认知,哪怕像上面的代码,只写了“index page”,客户端(浏览器)只要接收到的服务器的response,这个http服务就成立、可用。

2 集群,分布式

提供服务之后,随着业务量的扩大,随之而来的就是性能上的考虑:并发、高可用、吞吐量等。为了提高服务性能,大家常常能听到的概念:集群,分布式。

概念: 集群是个物理形态,分布式是个工作方式。

  • 分布式:一个业务分拆多个子业务,部署在不同的服务器上

  • 集群:同一个业务,部署在多个服务器上

他们的区别大体如下:

  • 分布式是指将不同的业务分布在不同的地方。而集群指的是将几台服务器集中在一起,实现同一业务。

  • 分布式是以缩短单个任务的执行时间来提升效率的,而集群则是通过提高单位时间内执行的任务数来提升效率。

如果一个任务由 10 个子任务组成,每个子任务单独执行需 1 小时,则在一台服务器上执行该任务需 10 小时。


好的设计,比如阿里的异地多活应该是分布式和集群的结合,先分布式再集群,根据三大运营商的特点,寻找最近的服务器节点。并提供服务。

看到这里,很多同学会得出结论:分布式TMD不就是微服务吗?

3 微服务

微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。

确实分布式和微服务很接近也容易混淆,偶尔我也会用“分发”来说明微服务。容易理解 ;)

因为分布式属于微服务架构,将原本某个大型服务的模块拆分成一个一个独立的服务单元,通过对外的接口来交互。微服务的设计还可以让某个模块的升级和BUG不影响现有的系统业务。等等……

微服务与分布式的细微差别是,微服务的应用不一定是分散在多个服务器上(集群),也可以是同一个服务器上。

问题又来了,同一个服务器咋个搞?反向代理 我挂了baidu百科链接,大家自行查阅。

到这里,搞清楚一台服务器的做法,业务体量扩大,从 http服务 => 服务器节点 => 集群( 负载均衡Load Balance | LVS + n个服务器 )。不断升级的过程中,大型微服务的架构自然成型。

前端微服务的解决方案

让我们回到最初的node代码:

if (url === '/') {
  res.end('index page');
}

我可以改写成任何我想返回的内容:

if (url === '/') {
    res.write('hello ');
    res.write('world! ');
    res.write('I can send you anything! ');
  res.end();
}

如果使用了Express|Koa 也可以render.file 。所以,“访问路径”或者说“路由”只是客户端在http协议下请求的内容(request)。很容易理解吧?至于http服务要怎么返回什么,如何返回?完全是由http服务决定。可以返回SPA页面也可以是SSR内容页面,本质上都是返回给客户端的html+statics。毫无区别!!

搞清楚这个概念之后,延伸到nginx上,大家是不是就明白了?node是req.url;nginx是通过location(路由)来进行request处理。如下:

server {
    listen 80;
    server_name local.com;
    root  /root/html;

    index index.html index.htm index.php;

    charset utf-8;

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log on;
    access_log /var/log/lile.com;
    sendfile off;

    location ~ \.(gif|jpg|png|css|js|swf|plist|xml)$ {
        expires 90d;
    }

    location / {
        add_header Access-Control-Allow-Origin *;
    }

    location ~/webapp {
        # 绝对路径
        root /root/html/webapp;
        index index.html index.htm;
    }

    # 前端history方案,暂时采取此方案,需要打包和vue的路由都需要添加相关配置
    location ~/webapp {
        # 绝对路径
        alias /root/html/webapp;
        index index.html index.htm;
    }

    # 方案2 location = /admin/audition.html
    location ~/admin/audition.html {
        # 绝对路径
        alias /root/html/webapp/audition.html;
    }

    # 现有配置
    location ~ /admin {
        # upstream 可用来配置服务,如: proxy_pass http://localhost:2001; 这也是微服务的基础
        proxy_pass upstream;
        ...
        ...
    }
}

在一台http-server下就是在“路由”这个层级进行处理,通过反向代理,可以返回spa(html+statics),可以返回处理函数,可以返回类方法(读取数据库表,操作数据库表 CURD ),也可以分发到其它服务。比如Vue或React项目的本地API跨域,在本地启动的http-proxy-server服务,只是拿到真实API地址上的数据返回给Ajax请求而已。

到现在,单台机器的微服务的架构就很清晰了,大家在自己的电脑上就可以独立去做。建立一个完整的博客平台服务:

├── Entry(node by http-proxy-server | nginx by proxy_pass)
|   ├── API-service (php port: 8001) 纯API
|   |   ├── index.php(router)
|   ├── web-service (spa port: 8002) 博客前台就是一般用户的博客浏览和编写
|   |   ├── index.html(router)
|   ├── ssr-service2(ssr port: 8003) 中台可分层、分级别、各种业务
|   |   ├── index.html(router)
|   |   ├── pay.html(router)
├── Entry2
...

// 服务体量升级,从本地的端口服务改成服务器节点,使用nginx的upstream即可,在最后的附录有说明和文档链接

入口有一层路由的处理,反向代理到spa或其它服务也会有自己的路由处理;这部分容易搞混。

记住:类似图3,入口是进行服务分发,不同的任务交给对应的http服务,每个服务还有自己的处理,在前端的项目中,spa或ssr有自己的路由处理,就是对不同的request或请求对应了不同服务。

总结和问题

到这里,大部分同学的疑问应该都解开了,我所在的团队有一位同学提了非常好的问题,如下:

我对微服务简单的理解就是:每个人负责一条单线
1、产品是一条线,微服务是每一个小块,那前端开发人员只是对某一业务线熟悉,对整个项目理解欠缺。
2、多条业务线有关联性,那开发人员又是负责单线,在不熟悉业务下,关联点如何开展。
3、微服务,是不是对开发人员项目结构上有一定的约束,比如共享数据,组件化,模块化。

问题是经过思考的。确实,这就微服务本身的弊端:多个服务之间相对独立,彻底专精某项工作,互相也毫无关联。那么如何来解决数据共享和协作呢?


请思考三秒


没有服务,那咱增加服务哈 :)按微服务思路,每项服务专注解决某类问题,缺啥补啥。

是不是很惊喜?很意外?又是情理之中。很多时候计算机的解决方案往往如此,说穿了特别简单。

下面把这位同学的问题,逐一回答并延伸。

1、产品是一条线,微服务是每一个小块,那前端开发人员只是对某一业务线熟悉,对整个项目理解欠缺。
答:是的,每个人(前、后端、产品)是支撑某个业务服务的一个及其微小的服务模块;在现代企业分工越来越细的情况下,这种情况是肯定的。每个人只会知道关于自己的或本专业的部分。微服务更是如此!

2、多条业务线有关联性,那开发人员又是负责单线,在不熟悉业务下,关联点如何开展。
答:如果是微服务,那么通过某个服务获取相关信息,没有服务建立服务;如果是工作,文档、提问和交流是唯一途径。有人能够带着你是最好的,所以新人入职一般会有个“老师”带着熟悉。

3、微服务,是不是对开发人员项目结构上有一定的约束,比如共享数据,组件化,模块化。
答:是的,高度模块化的基础服务才能组合成复杂的业务。举个例子,开发打包时,经常需要安装依赖包,在node环境里,相对各自独立,组合起来就可以提供你想要的服务。ABC是一类,ABD又是另一类,在node这个平台下几乎可以有无数种组合。但!!平台、规则需要先定好。工作中也是如此,好的协作流程才能串联起来各个部门(服务),否则只会是一大堆bug一大堆坑!!

附录

location的语法结构:

location [ = | ~ | ~* | ^~ ] uri { ...... } 如下:
location = /uri =表示精准匹配,只要完全匹配上才能生效。
location /uri 不带任何修饰符,也表示前缀匹配,但是在正则匹配之后
location / 通用匹配,任何未匹配到其他location的请求都会匹配到
location ^~ /uri 开头对URL路径进行前缀匹配,并且在正则之前。一旦匹配到最长匹配,则不再查找其他匹配项
location ~ pattern 开头表示区分大小写的正则匹配
location ~* pattern 开头表示不区分大小写的正则匹配,如果有多个location匹配,则选择匹配最长的那个

精准匹配: 相等(=)
字符串匹配:字符串匹配(空格)、匹配开头(^~)
正则匹配: 区分大小写匹配(~)、不区分大小写匹配(~)、区分大小写不匹配(!~)、不区分大小写不匹配(!~

优先级:
精准匹配 > 字符串匹配(长 > 短,^~匹配是最长匹配则停止匹配) > 正则匹配(先后顺序)

upstream的语法结构:

upstream name { ’server‘ service weight max_conns max_fails、 fail_timeout}
如下:

upstream test {
    server 127.0.0.1:8050    weight=1  max_fails=1 max_conns=800; fail_timeout=20;
    server 127.0.0.1:8060    weight=1  max_fails=1009;
}
## service 每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除
## weight 指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况
## max_conns 可以根据服务的好坏来设置最大连接数,防止挂掉,比如1000,我们可以设置800
## max_fails:失败多少次 认为主机已挂掉则,踢出,公司资源少的话一般设置2~3次,多的话设置1次

nginx官方文档