前端本来是没有微服务的概念,这个是从后端借鉴过来的。为了解释清前端的微服务,我分为3个部分:
- 什么是http服务器?
- 集群,分布式?
- 微服务
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官方文档