| 知乎专栏 |
.---> media [mp3, wma, wmv, rmvb, asf, divx]-\
/ +------------+
.-----> photo [gif, jpg, png, swf] ----> | Raid Array | <--.
/------------------- <---------\/ +------------+ \
user -> dns -> load balancing -> squid -> [cache] <----[html]----\ / |
\ \ \______<______/\ +-------------+ / |
\ \ \-----> web app ----> | html |--------^ |
\ \____________________________/\ | php,jsp,cgi | |
\ \ +-------------+ |
\ `-----> memcached [node1, node2, node(n)] |
\__________________________________________/\ |
`------> Database cluster |
+--------------------------------------+ \ |
| Author: neo chen <openunix#163.com> | `-------------+
| Nickname: netkiller |
| Homepage: http://netkiller.github.io |
+--------------------------------------+
一个网站由下面几部分组成:
网站组成
硬件 hardware
操作系统 operation system
应用软件 application software
网站程序以及开发语言 web program and script
硬件包括
网络硬件(路由器 route, 交换机 switch)
服务器 server
KVM over IP
其他包括,机柜等
操作系统包括
Windows Server
Linux
FreeBSD
Other(Sun,Novell,Sco...)
其中应用软件按平台分类
Windows 应用软件包括
dns (dns)
web (IIS)
ftp (IIS)
mail (Exchange)
database (SQL Server)
ldap (Active Directory)
Unix like 应用软件包括
dns (bind)
web (apache, lighttpd, tomcat)
ftp (proftpd, pureftpd, wu-ftp, vsftpd)
mail (sendmail,postfix, qmail)
database (PostgreSQL, MySQL)
ldap (OpenLDAP)
其他应用软件包括
cache (squid, nginx, memcached...)
web (jboss, weblogic...)
database (Oracle, DB2...)
网站程序以及开发语言
php
java (jsp)
.net (aspx)
fastcgi (python,perl,rubby,c/c++ ...)
怎样定义多大的网站叫大型网站呢?我也不知道,但凡大型网站都具备本文所提的几点。
门户网站的需求
海量用户访问
海量用户存储
国内外互通及南北互通
快速响应
7×24不间断运行
易于维护
门户网站的几个技术要点
智能域名服务器 Smart DNS
集群负载均衡 Cluster
缓存技术 Cache
静态化
图片服务器分离
压缩数据传输
时间同步
数据存储
集群有很多实现方法,分为硬件和软件,集群可以在不同网络层面上实现
实现IP轮循(Bind DNS)
硬件四层交换(硬件负载均衡设备 F5 BIG IP)
软件四层交换(linux virtual server)
应用层上实现(tomcat)
越是低层性能越好,越是上层功能更强
集群的分类
高可用性集群
负载均衡集群
超级计算集群
网站一般用到两种集群分别是高可用性集群和负载均衡集群
这是早期出现的负载均衡技术,直到现在,很多网站仍然使用DNS负载均衡。
你可通过ping命令观看它是如何工作的,例如你可反复ping个网域名。
C:\>ping www.163.com
Pinging www.cache.split.netease.com [220.181.28.52] with 32 bytes of data:
Reply from 220.181.28.52: bytes=32 time=226ms TTL=53
Reply from 220.181.28.52: bytes=32 time=225ms TTL=53
Reply from 220.181.28.52: bytes=32 time=226ms TTL=53
Reply from 220.181.28.52: bytes=32 time=226ms TTL=53
Ping statistics for 220.181.28.52:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 225ms, Maximum = 226ms, Average = 225ms
C:\>ping www.163.com
Pinging www.cache.split.netease.com [220.181.28.53] with 32 bytes of data:
Reply from 220.181.28.53: bytes=32 time=52ms TTL=52
Reply from 220.181.28.53: bytes=32 time=53ms TTL=52
Reply from 220.181.28.53: bytes=32 time=52ms TTL=52
Reply from 220.181.28.53: bytes=32 time=52ms TTL=52
Ping statistics for 220.181.28.53:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 52ms, Maximum = 53ms, Average = 52ms
C:\>ping www.163.com
Pinging www.cache.split.netease.com [220.181.28.50] with 32 bytes of data:
Reply from 220.181.28.50: bytes=32 time=51ms TTL=53
Reply from 220.181.28.50: bytes=32 time=52ms TTL=53
Reply from 220.181.28.50: bytes=32 time=52ms TTL=53
Reply from 220.181.28.50: bytes=32 time=51ms TTL=53
Ping statistics for 220.181.28.50:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 51ms, Maximum = 52ms, Average = 51ms
C:\>
DNS负载均衡主要优点
技术简单,容易实现,灵活,方便,成本低
Web服务器可以位于互联网的任意位置上,无地理限制。
DNS的主从结构非常稳定
可以有效的分散DDOS攻击。
你甚至可以在DNS服务商那里实现,自己不需要添加设备。而且没有带宽开销。
DNS负载均衡主要缺点
DNS负载均衡采用的是简单的轮循负载算法,不能够按照服务器节点的处理能力分配负载。
不支持故障转移(failover)和自动恢复failback ,如果某台服务器拓机,DNS仍会将用户解析到这台故障服务器上,导致不能响应客户端。
如果添加节点或撤出节点,不能即时更新到省市级DNS,可导致部分地区不能访问。
占用大量静态IP。
软件四层交换负载均衡为我们解决了几个问题
能够按照服务器节点的处理能力分配负载。
支持故障转移(failover)和自动恢复failback ,如果某节点拓机,调度器自动将它剔除,不响应客户端访问,当节点故障排除调度器立即恢复节点。
可以随时添加节点或撤出节点,即时生效,方便网站扩容。
软件四层交换负载均衡优点
仅仅需要一个静态IP。
节点位于私有网络上与WAN隔离,用户面对的只是调度器。
可以随时添加节点或撤出节点。
通过端口可以组建多个集群。
俗称:双机热备份
关键词:心跳线
两部服务器,或多部服务器,形成一个集群,当主服务器崩溃是,立即切换到其它节点上。
两部服务器要做到,内容实时同步,保持数据一直。
一般用 heartbeat + DRBD 实现。heartbeat负责切换服务器,DRBD用于同步数据。
负载均衡成熟产品
F5 Big IP
Array
这些设备可提供3,4,7层负载均衡HA,硬件已经压缩,HTTP头改写,URL改写...
其中3层交换部分多采用硬件实现。
首先要说明,很多缓存技术依赖静态化。下面展示了缓存可能出现的位置。
用户user -> 浏览器缓存 IE/Firefox Cache -> 逆向代理缓存 Reverse proxy Cache -> WEB服务器缓存 Apache cache -> 应用程序缓存 php cache -> 数据库缓存 database cache
当然交换机,网络适配器,硬盘上也有Cache 但这不是我们要讨论的范围。
缓存存储方式主要是内存和文件两种,后者是存于硬盘中。
网站上使用的缓存主要包括五种:
浏览器 缓存
逆向代理/CDN缓存
WEB服务器缓存
应用程序缓存
数据库缓存
将上面的缓存合理地,有选择性的使用可大大提高网站的访问能力。
总之,想让你的网站更快,更多并发,答案是cache,cache 再 cache
通过 Cache-Control 设置页面缓存时间
max-age max-age 格式写为:max-age=n,n是以秒为单位, 这个值是告知客户端GMT + N 后页面过期,缓存服务器在s-maxage值为空的时候也会使用这个参数的值。 s-maxage s-maxage的格式跟max-age一样,只不过他是给缓存服务器使用的。 must-revalidate 这个参数用来告知客户端和缓存服务器,在GET请求的时候必须与源服务器验证实体是否为最新版本。 Cache-Control:max-age=1200,s-maxage=3600
Last-Modified 这个参数提供了实体最近一次被修改的时间。这个名字起得不错,当实体被修改了之后,这个参数也就会被修改.
ETag
ETag ETag是根据内容生成的一段hash字符串,采用信息摘要算法,保证每一个页面有一个唯一字串。
expires
expires 是HTTP 1.0 中定义的,已经不能满足用户的需要在 HTTP 1.1 加入了max-age,建议使用 max-age替代expires
指令 含义 public 可以在任何地方缓存 private 只能被浏览器缓存 no-cache 不能在任何地方缓存 must-revalidate 缓存必须检查更新版本 proxy-revalidate 代理缓存必须检查更新版本 max-age 内容能够被缓存的时期,以秒表示 s-maxage 覆盖共享缓存的max-age设置
在Squid, Varnish, Apache, Lighttpd, Nginx 中都可是实现HTTP Cache-Control推送,每次修改都需要重新加载,不太灵活。
ExpiresActive On
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType text/css "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"
ExpiresByType application/x-javascript "access plus 1 month"
ExpiresByType application/x-shockwave-flash "access plus 1 month"
server.modules = (
...
"mod_expire",
...
)
$HTTP["url"] =~ "^/images/" {
expire.url = ( "" => "access 30 days" )
}
我喜欢自己控制TTL时间,且每个页面单独设置,可以随时调整设置。
在MVC框架中每个控制器下的方法都可以单独操作Cache
Class blog extend Controller{
blog(){
header('Cache-Control: max-age=28800');
}
list(){
header('Cache-Control: max-age=3600');
}
details(){
header('Cache-Control: max-age=160');
}
}
你还可以封装到Controller中
Class blog extend Controller{
blog(){
this->cache('28800');
}
list(){
this->cache('3600');
}
details(){
this->cache('160');
}
}
首先做一个Rewrite让程序接管所有图片请求
url.rewrite = ( "^/(.+)" => "/index.php/$1" )
然后程序通过PATHINFO取出图片URL
http://images.example.com/your/dir/test.jpg => http://images.example.com/index.php/your/dir/test.jpg
程序取出 /your/dir/test.jpg 设置 Content-type 并输出二进制流
详细参考
<?php
// Test image.
$images = '/test/foo.png';
$headers = apache_request_headers();
if (isset($headers['If-Modified-Since']) && (strtotime($headers['If-Modified-Since']) == filemtime($images))) {
header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($images)).' GMT', true, 304);
} else {
header('Content-Type: image/png');
print file_get_contents($fn);
if (file_exists($images)) {
header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($images)).' GMT', true, 200);
header("Cache-Control: max-age=3600, must-revalidate");
header('Content-Length: '.filesize($images));
header('Content-type: ' .mime_content_type($images));
flush();
readfile($images);
exit;
}
}
javascript 文件也可以使用类似方法处理
private function js($file){
if (file_exists($file)) {
header("Cache-Control: max-age=3600, must-revalidate");
header('Content-type: text/javascript');
flush();
readfile($file);
exit;
}
}
只要向浏览器输出过期时间HTTP协议头,不论是html还是动态脚本,都能被缓存。
HTML META
<meta http-equive="Expires" content=" Mon, 10 Jan 2000 00:00:00 GMT"/> <meta http-equive="Cache-Control" content="max-age=300"/> <meta http-equive="Cache-Control" content="no-cache"/>
动态脚本
Expires: Mon, 10 Jan 2000 00:00:00 GMT
Cache-Control: max-age=300
Cache-Control: no-cache
header("Expires: " .gmdate ("D, d M Y H:i:s", time() + 3600 * 24 * 7). " GMT");
header("Cache-Control: max-age=300");
header("Cache-Control: no-cache");
很多web server都提供 Expires 模块
![]() | 提示 |
|---|---|
|
有些浏览器可能不支持。 |
If-Modified-Since 小于 Last-Modified 返回 200
neo@neo-OptiPlex-780:/tmp$ curl -I http://www.163.com/ HTTP/1.1 200 OK Server: nginx Content-Type: text/html; charset=GBK Transfer-Encoding: chunked Vary: Accept-Encoding Expires: Mon, 16 May 2011 08:12:05 GMT Cache-Control: max-age=80 Vary: User-Agent Vary: Accept Age: 38 X-Via: 1.1 ls100:8106 (Cdn Cache Server V2.0), 1.1 lydx156:8106 (Cdn Cache Server V2.0) Connection: keep-alive Date: Mon, 16 May 2011 08:11:23 GMT
If-Modified-Since 大于 Last-Modified 返回 304
neo@neo-OptiPlex-780:/tmp$ curl -H "If-Modified-Since: Fri, 12 May 2012 18:53:33 GMT" -I http://www.163.com/ HTTP/1.0 304 Not Modified Content-Type: text/html; charset=GBK Cache-Control: max-age=80 Age: 41 X-Via: 1.0 ls119:80 (Cdn Cache Server V2.0), 1.0 lydx154:8106 (Cdn Cache Server V2.0) Connection: keep-alive Date: Mon, 16 May 2011 08:11:14 GMT Expires: Mon, 16 May 2011 08:11:14 GMT
neo@neo-OptiPlex-780:/tmp$ curl -I http://images.example.com/test/test.html HTTP/1.1 200 OK Cache-Control: s-maxage=7200, max-age=900 Expires: Mon, 16 May 2011 09:48:45 GMT Content-Type: text/html Accept-Ranges: bytes ETag: "1984705864" Last-Modified: Mon, 16 May 2011 09:01:07 GMT Content-Length: 22 Date: Mon, 16 May 2011 09:33:45 GMT Server: lighttpd/1.4.26
neo@neo-OptiPlex-780:/tmp$ curl -H 'If-None-Match: "1984705864"' -I http://images.example.com/test/test.html HTTP/1.1 304 Not Modified Cache-Control: s-maxage=7200, max-age=900 Expires: Mon, 16 May 2011 09:48:32 GMT Content-Type: text/html Accept-Ranges: bytes ETag: "1984705864" Last-Modified: Mon, 16 May 2011 09:01:07 GMT Date: Mon, 16 May 2011 09:33:32 GMT Server: lighttpd/1.4.26

具有代表性的逆向代理服务器:
Squid
Nginx
Varnish
Apache cache module
其它逆向代理服务器
一些提供cache的硬件设备
最近几年出现了的 China Cache 服务商,也称CDN
很多CDN厂商使用Squid 二次开发做为CDN节点,通过全球负载均衡使用分发
这些CDN厂商主要做了一下二次开发
logs 日志集中
流量限制
push,pull操作
url 刷新
s-maxage 与 max-age用法类似,s-maxage针对代理服务器缓存。同样适用于CDN
s-maxage 与 max-age 组合使用可以提高CDN性能
方向代理一般都支持PURGE协议,Squid,Varnish等等向管理端口发送 PURGE 即可是使用页面刷新
PURGE http://netkiller.github.net/index.html
有些方向代理如:Varnish 可以使用正则表达式
同时这些代理服务器都承受管理命令
squid: squidclient
varnish: varnishadm
例如这样的URL
http://images.example.com/logo.gif http://images.example.com/product.jpg
我们可以通过Rewrite或PATHINFO等技术做为静态化。例如首次版本
http://images.example.com/logo.1.gif => logo.gif http://images.example.com/product.1.jpg => product.jpg
原图发生变化后,版本递增
http://images.example.com/logo.2.gif => logo.gif http://images.example.com/product.2.jpg => product.jpg
就的URL将丢弃
http://images.example.com/logo.1.gif http://images.example.com/product.1.jpg
CDN 就回源去下面的URL,并且取到的是新图
http://images.example.com/logo.2.gif http://images.example.com/product.2.jpg
F5 Big-IP, Array 等设备都提供硬件加速,其原理与squid, apache提供的功能大同小异
其中Array 页面压缩采用硬件压缩卡实现,SSL加速也采用硬件实现
在这个领域百花齐放,相信你一定能找到适合你的。这些cache会为你提供一些api,来访问它。
代表性的 memcached 据我所是sina广泛使用,腾讯也曾经使用过后来开发了TC(Tencent Cache),台湾雅虎则使用APC Cache。
另外模板引擎也有自己的缓存系统
www 负责静态文件浏览, 台数不定, 可以采用零成本的DNS轮询, 或者4层LVS, 或者7层HAProxy, 还可以用F5, Array 等负载均衡设备.
cms 负责静态文件生成. 生成后的文件要同步到www中, 或者采用网络共享, 再者使用分布式文件系统, 总之将生成的文件交给www服务器, 根据你压力横向扩展即可
img 负责图片文件浏览. 通过给图片加版本号, 结局图片更新问题, 这样更新网站不用频繁刷新 CDN
这里不谈论负载均衡, 以及存储方案, 有情绪可以延伸阅读: http://netkiller.github.com/architect/index.html
你掌握了这个方案, 你可以很容易实现 向"京东商城", "VANCL凡客诚品", "走秀网" 这样的网站
这些网站的特点是: 浏览量大, 数据存储主要是图片, 登录/注册与购物车,用户中心 访问量只占到 5% 使用负责均衡很容易解决.
静态化网站可不避免的使用ajax做局部更新, ajax请求也要考虑缓存问题
静态化另一个目的是改善SEO
首次访问服务器
访问www服务器
nginx 判断文件是否存在,如果存在将文件显示出来
如果文件不存在,去cms服务器上查找, 如果存在便返回给www服务器,并显示出来
如果cms上文件不存在,cms服务器便使用rewrite生成该文件, 同时将内容返回给www服务器,www将内容缓存在自己的服务器上,并将内容显示出来
第二次访问
访问www服务器
nginx 判断文件是否存在,如果存在将文件显示出来
如果文件不存在,去cms服务器上查找, 如果存在便返回给www服务器,并显示出来
如果cms上文件不存在,cms服务器便使用rewrite生成该文件, 同时将内容返回给www服务器,www将内容缓存在自己的服务器上,并将内容显示出来
静态化方法包括:
生成方式
抓取方式
伪静态化
混合方式
主要由程序实现
例如
content = "<html><title>my static</title><body>hello world</body></html>" file = open( your static file) file.write(content) file.close()
主要由程序实现
程序中抓取
content = get_url('http://netkiller.8800.org/index.php')
file = open( index.html)
file.write(content)
file.close()
使用软件抓取,不仅限于wget。
wget http://netkiller.8800.org/index.php -O index.html
这时只给出简单例子,使用复杂参数实现更复杂的拾取,然后将脚本加入crontab中可。
伪静态化是主要是通过在URL上做一些手脚,使你看去是静态的,实质上它是动态脚本。
伪静态化实现主要包括两种方法:
Rewrite rule
path_info
下面是一个PATH_INFO例子
http://netkiller.8800.org/zh-cn/photography/browse/2009.html
根本就不存在这个目录'zh-cn/photography/browse/'和文件'2009.html'
下面是一个Rewrite例子
http://example.org/bbs/thread-1003872-1-1.html
其实目前网站使用的基本上都是上面几种方法混合方式。
例如首先将动态url(example.org/news.php?cid=1&id=1) 通过rewrite转换为 (example.org/new_1_1.html)
接下来就比较容易解决了,一种方法是使用wget example.org/new_1_1.html,另一种方法你无需静态化,直接使用squid规则配置让他永不过期
如何使用 cdn 来缓存你的网站内容
让你的网页缓存在 cdn 节点上的方式有下面几种
让cdn的客服帮你配置缓存的规则, 他们很喜欢一刀切, 例如所有html都缓存2小时
在他们管理后台自行使用正则配置缓存的时间, 这个他们一般不会提供, 某些公司的CDN会提供这个功能. 非常方便.
通过HTTP头自行控制缓存时间, 一般是使用 max-age / s-maxage / Last-Modified 判断缓存时间
我比较喜欢最后一种, 通常我们使用max-age 与 s-maxage 同时使用, 这样我可以按照我的意向来决定文件的缓存时间 这里有更详细的解释说明.
下面给出一个精简后的配置例子
如果文件不存在就会连接后端cms服务器生成文件,并且显示出来,同时加上缓存. 生成的文件会从cms中同步到www服务器上.
你可以采用
rsync同步方案
nfs/samba/gluster 共享方案
iscsi 共享存储方案
分布式文件系统方案
参考阅读: 分布式文件系统 , Netkiller Linux Storage 手札
upstream cms.mydomain.com {
server 192.168.2.11 weight=5 max_fails=3 fail_timeout=30s;
server 192.168.2.21 weight=5 max_fails=3 fail_timeout=30s;
server 192.168.2.23 backup;
server 192.168.2.23 down;
}
server {
listen 80;
server_name www.mydomain.com;
charset utf-8;
access_log /var/log/nginx/www.mydomain.com.access.log main;
location / {
root /www/mydomain.com/www.mydomain.com;
index index.html index.htm;
if ($request_uri ~* "\.(ico|css|js|gif|jpe?g|png|html)$") {
expires 1d;
}
if ($request_uri ~* "\.(xml|json)$") {
expires 1m;
}
valid_referers none blocked *.mydomain.com;
if ($invalid_referer) {
#rewrite ^(.*)$ http://www.mydomain.com/cn/$1;
return 403;
}
proxy_intercept_errors on;
if (!-f $request_filename) {
proxy_pass http://cms.mydomain.com;
break;
}
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
CMS 内容管理系统的主要功能
内容分类管理
内容模板管理
内容编辑与发布
内容生成
服务应该实现
当发现目录中文件不存, 通过rewrite生成html, 这样可能根据需要生成html页面
当页面更新的时候,应该通过api 刷新cdn的缓存, 图片的版本好应该加一
将页面分成多个模块, 通过SSI拼装页面, 避免有重大改版时, 整站生成HTML.
避免使用seesion技术, 这样在负载均衡的时候可以使用最小连接数算法
例如:
rewrite ^/product/(phone|notebook)/(\d+).html /product/$1.php?id=$2 last;
URL 唯一, url设计要考虑唯一性, 不要出现同一个url处理两个任务, 例如下面的例子, 每个用户的profile一个URL, 当被访问的时候便可以缓存在CDN或者用户浏览器上.
http://www.mydomain.com/profile/neo.html http://www.mydomain.com/profile/jam.html
server {
listen 80;
server_name www.mydomain.com;
#charset koi8-r;
access_log /var/log/nginx/www.mydomain.com.access.log main;
location / {
root /www/mydomain.com/www.mydomain.com;
index index.html;
}
}
server {
listen 80;
server_name cms.mydomain.com;
charset utf-8;
access_log /var/log/nginx/cms.mydomain.com.access.log main;
location / {
root /www/mydomain.com/cms.mydomain.com;
index index.html index.php;
}
location ~ ^/(cn|tw)/(comment|affiche)/.*\.html {
root /www/mydomain.com/www.mydomain.com;
if (!-f $request_filename) {
rewrite ^/(cn|tw)/(comment|affiche)/(\d+).html /publish/$2.php?id=$3&lang=$1 last;
}
}
location /xml/ {
root /www/mydomain.com/www.mydomain.com/xml;
}
location ~ ^/(config|include|crontab)/ {
deny all;
break;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
location ~ \.php$ {
root /www/mydomain.com/cms.mydomain.com;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /www/mydomain.com/cms.mydomain.com$fastcgi_script_name;
include fastcgi_params;
fastcgi_param DOCUMENT_ROOT /www/mydomain.com/cms.mydomain.com;
fastcgi_param HOSTNAME cms.mydomain.com;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
location ~ /\.ht {
deny all;
}
}
img.mydomain.com
server {
listen 80;
server_name img.mydomain.com;
charset utf-8;
access_log /var/log/nginx/img.mydomain.com.access.log main;
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|ico)$
{
expires 7d;
}
location ~ .*\.(js|css)$
{
expires 1d;
}
location ~ .*\.(html|htm)$
{
expires 15m;
}
location / {
root /img/mydomain.com/img.mydomain.com;
index index.html;
rewrite "/theme/([0-9] {4})([0-9] {2})([0-9] {2})/(.+)\.(.+)\.(.+)" /theme/$1/$2/$3/$4.$6;
rewrite "/news/([0-9] {4})([0-9] {2})([0-9] {2})/(.+)\.(.+)\.(.+)" /news/$1/$2/$3/$4.$6;
rewrite "/product/([0-9] {4})([0-9] {2})([0-9] {2})/(.+)\.(.+)\.(.+)" /product/$1/$2/$3/$4.$6;
}
}
/theme/2012/08/15/images.1.jpg 实际上就是 /theme/2012/08/15/images.jpg 文件
/theme/2012/08/15/images.2.jpg 也是 /theme/2012/08/15/images.jpg
/theme/2012/08/15/images.3.jpg 也是 /theme/2012/08/15/images.jpg
但CDN与你的浏览器会每次下载新的文件, 这样只要更新CDN中的html页面即可, 不用去理睬图片, 你的浏览器会用新的地址下载图片. 这样就解决了烦琐的刷新工作.
为什么要将图片服务器分离出来?
图片通常比较大,下载需要更长的时间,而web容器并发数也是相当宝贵的仅次于数据库。
传统浏览器一个窗口只占用一个链接数,目前主流浏览器都支持多线程下载,下载HTML页面同时,采用多线程下载其它多媒体数据。
我们举一个例子,你的服务器并发能力只用1000,早期浏览器不支持多线程,所以同一时刻,你的服务器可以承受1000个人同时访问。 但现在不同了,基本所有的浏览器都支持多线程,假如你的页面中有9张小图片,同一时刻你的服务器仅仅能应付1000/10 = 100个用户。
所以我们要将图片和其他多媒体文件分离出来,单独使用一台服务器处理请求。
![]() | 提示 |
|---|---|
|
图片服务器建议使用lighttpd与squid缓存配合使用效果更好或购买CDN的服务。 |
日期有利于归档
/www/images /www/images/2008 /www/images/2008/01 /www/images/2008/01/01
分类不同用途的文件
/www/images /www/images/theme/2009 # article id 000001 /www/images/article/2009/01/000001 # product id 00001 /www/images/product/2009/01/01/00001 # member name neo /www/images/member/2009/01/01/neo
根据你的数据量,创建目录深度, 并且目录深度有规律可循。
虽然64bit 文件系统不限制文件数量与目录深度,但是我还是建议按我的方式规划目录。
这样规划目录便于缓存控制,如:
images/2008/* 永久缓存
images/2009/* 缓存一个月
images/2010/* 缓存一小时
images/2010/06/* 缓存5分钟
某天我的前同事给我打电话,说他们的负载很高,经查发现网站首页有20M,原因是首页直接引用高清图片,没有安装分辨率生成缩图。于是我便想出了下面的方案。
我认为方案需求有如下几个要素:
图片压缩
尺寸修改
图片缓存
带宽因素
例如用户使用手机访问网站,手机屏幕尺寸非常多样化,常见的有QVGA(320×240)、HGVA(480×320)、WVGA(800×480)、QCIF(176×144)、SVGA(640x480)、WXGA(1280×800)。如果一个用户的手机屏幕是320×240,打开网站后显示1027*768图片很不切合实际。同时用户也多出不少带宽开销。
我们需要给用户更好的体验,就要多从用户的角度去考虑,如根据用户网速,带宽,分辨率,为用户提供更适合他终端的多媒体资源。
B/S结构应用程序无法获取客户端的分辨率等信息,我们将采用Javascript取出参数,然后告知服务器端。
有下面几种实现方式:
通过cookie
post传递给服务器,然后存储在session中
get 传递给服务器,然后存储在session中
仅举一个例子
<script type="text/javascript">
$(function(){
var width=window.screen.height;
var height=window.screen.width;
$.post('http://www.example.com/screen/resize.html',{w:width,h:height});
});
</script>
HTML页面中的图片的引用路径
<img src="http://img.example.com/sample.jpg" />
图片服务器rewrite处理
http://img.example.com/sample.jpg => http://img.example.com/index.php/sample.jpg
index.php会首先载入sample.jpg文件,然后综合网速,带宽,分辨率等因素,重新压缩图片,修改尺寸,发送mime头,输出正文。
为了防止图片地址冲突,我们首先需要URL唯一化,这样每访问一次会生成一张符合你需求尺寸的图片。
http://img.example.com/sample_(width)x(height)_(quality).jpg
<img src="http://img.example.com/sample_1980x1080_100.jpg" /> <img src="http://img.example.com/sample_800x600_80.jpg" /> <img src="http://img.example.com/sample_640x480_50.jpg" />
配置nginx通过try_files配置项可以实现检查静态文件是否存在,如果不存在边调用index.php生成图片,当再次访问时会直接读取静态文件,不会再重新生成。
server {
listen 80;
server_name inf.example.com;
charset utf-8;
access_log /var/log/nginx/inf.example.com.access.log main;
error_log /var/log/nginx/inf.example.com.error.log;
location / {
root /www/example.com/inf.example.com/images;
index index.html;
try_files $uri $uri/ /index.php?_url=$request_uri;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
location ~ /index\.php$ {
root html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /www/example.com/inf.example.com/frontend/public$fastcgi_script_name;
include fastcgi_params;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
location ~ /\.ht {
deny all;
}
}
通过这种方法还可以实现更复杂的需求,例如调整亮度,对比度,饱和度,色阶,图层叠加等等......
服务器将html或脚本输出压缩,用户从服务器取得数据后由浏览器解压
压缩数据传输实现方法:
apache mod_deflate
lighttpd compress module
SSL 加密传输,为电子商务提供交易安全保护,什么时候该使用 SSL呢:
使用SSL
用户登录
购物流程
支付
什么时候不使用SSL? 经过SSL加密后,你就失去了很多功能,你不能在对页面做Cache/CDN,SSL加密与解密需要耗费你的服务器CPU与内存资源,能不使用尽量不使用。
对于SSL消耗你服务器资源这方面有两个方案解决
将SSL证书安装到CDN上,目前蓝讯,网宿等等CDN厂商都提供SSL服务。我与上两家技术人员沟通过,也安装了证书实际测试一下,你可以放心是使用。
将SSL证书安装到负载均衡设备,这些设备都采用专用硬件处理SSL请求,我测试过F5,Array,Banggoo
采用上面两种方案,无需改变你目前的服务器配置,他们的原理是
user (https://www.example.com) --> CDN or SLB (SSL) --> http://www.example.com
用户访问https,到达CDN或者负载均衡,CDN/SLB 通过http://请求源站,然后将内容SSL加密,返回给用户,这样用户得到的是加密内容。
用户提交数据,交给CDN/SLB,CDN/SLB将SSL加密数据卸载证书,然后将解密后数据发回源站。
CDN与SLB加载卸载证书原理很简单,不难理解。
我来教你DIY一个,你可以使用Squid,Nginx,Apache等等反向代理服务器,将证书安装在反向代理上,请求源站仍然采用http。
![]() | SSL注意事项 |
|---|---|
|
你如果认为把SSL挂载到网站前端就,大功告成,完事了,那你错了。 幸运的话你会成功,但有时的时候你发现你的证书不被信任。如果你是个细心的人,你会发现单个图片,或者你创建换成测试文件 echo helloworld > index.html 证书都是OK的。 这个问题出在你的html页面中,安装有SSL证书的网站,不能有外链js,flash等等不安全内容。 |
每个搜索关键字都应该有一个惟一的URL,例如
https://www.google.com.hk/search?sourceid=chrome&ie=UTF-8&q=netkiller&sei=9v-QT_q1L6SZiAel2bGnBA&gbv=2 https://www.google.com.hk/search?aq=f&sourceid=chrome&ie=UTF-8&q=neo https://www.google.com.hk/search?sourceid=chrome&ie=UTF-8&q=bg7nyt
每搜索一次新的关键字就会产生一条唯一的URL,这样就可以实现反向代理缓存,甚者通过HTTP头,实现浏览器段的缓存。
<meta name="robots" content="noarchive">
例 1.1. example robots.txt
http://www.google.com/robots.txt
User-agent: * Disallow: /search Disallow: /groups Disallow: /images Disallow: /catalogs Disallow: /catalogues Disallow: /news Allow: /news/directory Disallow: /nwshp Disallow: /setnewsprefs? Disallow: /index.html? Disallow: /? Disallow: /addurl/image? Disallow: /pagead/ Disallow: /relpage/ Disallow: /relcontent Disallow: /imgres Disallow: /imglanding Disallow: /keyword/ Disallow: /u/ Disallow: /univ/ Disallow: /cobrand Disallow: /custom Disallow: /advanced_group_search Disallow: /googlesite Disallow: /preferencessection Disallow: /setprefs Disallow: /swr Disallow: /url Disallow: /default Disallow: /m? Disallow: /m/? Disallow: /m/blogs? Disallow: /m/ig Disallow: /m/images? Disallow: /m/local? Disallow: /m/movies? Disallow: /m/news? Disallow: /m/news/i? Disallow: /m/place? Disallow: /m/setnewsprefs? Disallow: /m/search? Disallow: /m/swmloptin? Disallow: /m/trends Disallow: /wml? Disallow: /wml/? Disallow: /wml/search? Disallow: /xhtml? Disallow: /xhtml/? Disallow: /xhtml/search? Disallow: /xml? Disallow: /imode? Disallow: /imode/? Disallow: /imode/search? Disallow: /jsky? Disallow: /jsky/? Disallow: /jsky/search? Disallow: /pda? Disallow: /pda/? Disallow: /pda/search? Disallow: /sprint_xhtml Disallow: /sprint_wml Disallow: /pqa Disallow: /palm Disallow: /gwt/ Disallow: /purchases Disallow: /hws Disallow: /bsd? Disallow: /linux? Disallow: /mac? Disallow: /microsoft? Disallow: /unclesam? Disallow: /answers/search?q= Disallow: /local? Disallow: /local_url Disallow: /froogle? Disallow: /products? Disallow: /products/ Disallow: /froogle_ Disallow: /product_ Disallow: /products_ Disallow: /print Disallow: /books Disallow: /bkshp?q= Allow: /booksrightsholders Disallow: /patents? Disallow: /patents/ Allow: /patents/about Disallow: /scholar Disallow: /complete Disallow: /sponsoredlinks Disallow: /videosearch? Disallow: /videopreview? Disallow: /videoprograminfo? Disallow: /maps? Disallow: /mapstt? Disallow: /mapslt? Disallow: /maps/stk/ Disallow: /maps/br? Disallow: /mapabcpoi? Disallow: /maphp? Disallow: /places/ Disallow: /maps/place Disallow: /help/maps/streetview/partners/welcome/ Disallow: /lochp? Disallow: /center Disallow: /ie? Disallow: /sms/demo? Disallow: /katrina? Disallow: /blogsearch? Disallow: /blogsearch/ Disallow: /blogsearch_feeds Disallow: /advanced_blog_search Disallow: /reader/ Allow: /reader/play Disallow: /uds/ Disallow: /chart? Disallow: /transit? Disallow: /mbd? Disallow: /extern_js/ Disallow: /calendar/feeds/ Disallow: /calendar/ical/ Disallow: /cl2/feeds/ Disallow: /cl2/ical/ Disallow: /coop/directory Disallow: /coop/manage Disallow: /trends? Disallow: /trends/music? Disallow: /trends/hottrends? Disallow: /trends/viz? Disallow: /notebook/search? Disallow: /musica Disallow: /musicad Disallow: /musicas Disallow: /musicl Disallow: /musics Disallow: /musicsearch Disallow: /musicsp Disallow: /musiclp Disallow: /browsersync Disallow: /call Disallow: /archivesearch? Disallow: /archivesearch/url Disallow: /archivesearch/advanced_search Disallow: /base/search? Disallow: /base/reportbadoffer Disallow: /base/s2 Disallow: /urchin_test/ Disallow: /movies? Disallow: /codesearch? Disallow: /codesearch/feeds/search? Disallow: /wapsearch? Disallow: /safebrowsing Allow: /safebrowsing/diagnostic Allow: /safebrowsing/report_error/ Allow: /safebrowsing/report_phish/ Disallow: /reviews/search? Disallow: /orkut/albums Disallow: /jsapi Disallow: /views? Disallow: /c/ Disallow: /cbk Disallow: /recharge/dashboard/car Disallow: /recharge/dashboard/static/ Disallow: /translate_a/ Disallow: /translate_c Disallow: /translate_f Disallow: /translate_static/ Disallow: /translate_suggestion Disallow: /profiles/me Allow: /profiles Disallow: /s2/profiles/me Allow: /s2/profiles Allow: /s2/photos Allow: /s2/static Disallow: /s2 Disallow: /transconsole/portal/ Disallow: /gcc/ Disallow: /aclk Disallow: /cse? Disallow: /cse/panel Disallow: /cse/manage Disallow: /tbproxy/ Disallow: /comparisonads/ Disallow: /imesync/ Disallow: /shenghuo/search? Disallow: /support/forum/search? Disallow: /reviews/polls/ Disallow: /hosted/images/ Disallow: /hosted/life/ Disallow: /ppob/? Disallow: /ppob? Disallow: /ig/add? Disallow: /adwordsresellers Disallow: /accounts/o8 Allow: /accounts/o8/id Disallow: /topicsearch?q= Disallow: /xfx7/ Disallow: /squared/api Disallow: /squared/search Disallow: /squared/table Disallow: /toolkit/ Allow: /toolkit/*.html Disallow: /qnasearch? Disallow: /errors/ Disallow: /app/updates Disallow: /sidewiki/entry/ Disallow: /quality_form? Disallow: /labs/popgadget/search Disallow: /buzz/post Sitemap: http://www.gstatic.com/s2/sitemaps/profiles-sitemap.xml Sitemap: http://www.google.com/hostednews/sitemap_index.xml Sitemap: http://www.google.com/ventures/sitemap_ventures.xml Sitemap: http://www.google.com/sitemaps_webmasters.xml Sitemap: http://www.gstatic.com/trends/websites/sitemaps/sitemapindex.xml Sitemap: http://www.gstatic.com/dictionary/static/sitemaps/sitemap_index.xml
User-agent: * Allow: * Disallow: /management/ Sitemap: http://netkiller.sourceforge.net/sitemaps.xml.gz
#!/bin/bash
DOMAIN="http://www.netkiller.cn"
PUBLIC_HTML=~/public_html
if [ ! -z $1 ]; then
DOMAIN=$1
fi
lastmod=`date "+%Y-%m-%d"`
echo '<?xml version="1.0" encoding="UTF-8"?>'
echo '<?xml-stylesheet type="text/xsl" href="gss.xsl"?>'
echo '<urlset xmlns="http://www.google.com/schemas/sitemap/0.84" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.google.com/schemas/sitemap/0.84 http://www.google.com/schemas/sitemap/0.84/sitemap.xsd">'
#echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'
for htmlfile in $(find $PUBLIC_HTML/ -type f -name "*.html")
do
url=`echo $htmlfile | sed -e "s:$PUBLIC_HTML/::"`
echo ' <url>'
echo ' <loc>'${DOMAIN}'/'${url}'</loc>'
echo ' <lastmod>'${lastmod}'</lastmod>'
echo ' <changefreq>daily</changefreq>'
echo ' <priority>0.5</priority>'
echo ' </url>'
done
for htmlfile in $(find $PUBLIC_HTML/ -type f -name "*.epub")
do
url=`echo $htmlfile | sed -e "s:$PUBLIC_HTML/::"`
echo ' <url>'
echo ' <loc>'${DOMAIN}'/'${url}'</loc>'
echo ' <lastmod>'${lastmod}'</lastmod>'
echo ' <changefreq>daily</changefreq>'
echo ' <priority>0.5</priority>'
echo ' </url>'
done
for htmlfile in $(find $PUBLIC_HTML/ -type f -name "*.mobi")
do
url=`echo $htmlfile | sed -e "s:$PUBLIC_HTML/::"`
echo ' <url>'
echo ' <loc>'${DOMAIN}'/'${url}'</loc>'
echo ' <lastmod>'${lastmod}'</lastmod>'
echo ' <changefreq>daily</changefreq>'
echo ' <priority>0.5</priority>'
echo ' </url>'
done
for htmlfile in $(find $PUBLIC_HTML/ -type f -name "*.chm")
do
url=`echo $htmlfile | sed -e "s:$PUBLIC_HTML/::"`
echo ' <url>'
echo ' <loc>'${DOMAIN}'/'${url}'</loc>'
echo ' <lastmod>'${lastmod}'</lastmod>'
echo ' <changefreq>daily</changefreq>'
echo ' <priority>0.5</priority>'
echo ' </url>'
done
for htmlfile in $(find $PUBLIC_HTML/ -type f -name *.pdf)
do
url=`echo $htmlfile | sed -e "s:$PUBLIC_HTML/::"`
echo ' <url>'
echo ' <loc>'${DOMAIN}'/'${url}'</loc>'
echo ' <lastmod>'${lastmod}'</lastmod>'
echo ' <changefreq>daily</changefreq>'
echo ' <priority>0.5</priority>'
echo ' </url>'
done
echo "</urlset>"
方案1: User -> Squid ->Web Server
修改squid源码,加入iconv(big5,gb2312,html page)
e.g.1 user (gb.example.org) -> Squid (big5->gb2312) -> web server e.g.2 user (big5.example.org) -> Squid (gb2312->big5) -> web server
2000年入行,我完整亲历了中国互联网Web技术从蛮荒到成熟、从单体到分布式、从服务端渲染到前后端分离的完整迭代周期。从最初手写C/Perl CGI、PHP模版开发,到经历JSP、JavaEE重型架构,再到深耕十年的前后端分离、微服务架构,在行业全员追逐前端工程化、微服务、客户端渲染的浪潮中,我在AI时代做出了一个看似"反常"的逆向决定:放弃极致的前后端分离、舍弃臃肿的微服务架构,重新回归服务器端渲染(SSR)+ 单体服务,甚至重拾PHP经典开发模式。
技术从不会一成不变,没有永远先进的架构,只有适配时代的方案。走过二十余年技术轮回,我终于明白:所有架构迭代,都是为了适配当下的硬件、网络、业务与生产效率。
Web前端的发展,本质是页面渲染、数据交互、开发效率的迭代进化,整个历程清晰分为三个核心阶段,每个阶段的痛点,倒逼出了下一轮技术变革:
这是Web最原始的开发模式,没有任何模板工具、没有前端框架。所有页面结构、数据展示,全部依靠后端代码硬拼接HTML字符串。C、Perl、PHP、Java代码中充斥着大量HTML标签拼接逻辑,后端既要处理业务逻辑、数据库读写,还要手写页面布局、样式结构。
这种模式弊端极其明显:HTML与业务代码高度耦合,代码极度混乱、维护成本极高;页面修改需要改动后端代码,每一次微调都需要重新部署;重复代码极多,无法复用,开发效率低下到极致。
为了解决原始拼接的痛点,JSP、Thymeleaf、Freemarker、Jinja2、PHP模版等技术应运而生。核心变革是实现了视图与业务代码的初步分离,后端专注处理数据、业务逻辑,模板文件专注负责页面结构渲染,通过模板语法动态绑定数据、循环渲染页面。
这是初代服务器渲染的黄金模式,彻底告别了混乱的字符串拼接,页面可复用、可独立维护,开发效率大幅提升。但依旧存在固有短板:页面整体刷新机制固化,每次访问、每次交互都需要整页重载;动态交互能力薄弱,复杂表单、实时刷新、局部交互几乎无法实现;随着页面复杂度提升,模板嵌套臃肿,页面加载全量传输资源,网络加载速度慢。
当年行业抛弃传统服务端渲染、拥抱前后端分离,核心是为了解决模板臃肿、交互卡顿、耦合严重的问题。但经过十年落地,我们发现:传统SSR的部分旧问题依旧存在,同时分离架构带来的新痛点,早已成为研发效率、项目运维、数据安全的沉重负担。
前后端分离看似优化了体验,但基础性能问题始终没有根治:
重复渲染与刷新问题:客户端每次路由跳转、页面访问,都需要重新加载前端资源、请求接口、渲染页面,即便只是少量数据变更,也需要重复执行客户端渲染逻辑,冗余消耗严重;
加载性能瓶颈:前端工程化后打包资源体积庞大,首次访问需要全量加载JS、CSS、静态资源,网络传输耗时久,白屏、加载卡顿成为常态,低配设备、弱网环境体验极差;
开发冗余繁琐:虽然告别了后端HTML拼接,但前端需要重复编写页面结构、数据渲染、状态管理、异常处理逻辑,前后端两套逻辑重复开发,冗余工作量大幅增加。
这是前后端分离时代最致命的问题,也是我决定回归SSR的核心原因:
岗位拆分,协作成本指数级上涨:前后端彻底分离后,催生了独立的前端、后端岗位。原本一人可完成的全栈开发工作,变成两个团队、两类岗位的协同工作。日常开发需要频繁对齐接口字段、数据格式、状态码、异常逻辑,联调排错耗时远超开发本身。前端需要懂接口适配、后端需要兼容前端渲染,全栈能力要求变相提高,团队沟通成本、协作内耗极其严重。
数据安全风险全面爆发:传统SSR架构中,所有数据处理、逻辑过滤、权限校验都在服务端完成,用户最终只能看到渲染后的HTML页面,原始数据、核心业务逻辑完全隔离。而前后端分离架构下,后端需要对外暴露大量明文JSON接口,前端直接获取原始数据。一旦接口权限控制疏忽、参数校验不严,就会出现数据泄露、越权查询、接口爬虫抓取等安全问题,数据防护难度呈几何倍数提升。
工程化臃肿,启动与部署效率极低:现代前端项目依赖海量插件、构建工具,每次修改代码需要重新build、打包、编译,开发、测试、部署链路冗长。后端SpringBoot、FastAPI项目每次代码微调,都需要重启服务、重新加载环境,频繁等待编译启动,极大打断开发节奏,无法实现"修改即生效、即时看效果"。
AI场景适配性极差,重复工作量爆炸:AI时代业务大多是动态内容生成、实时问答、知识库渲染,前后端分离模式下,不仅后端要写AI调用、数据处理接口,前端还要单独写一套渲染、流式展示、状态交互逻辑,一套业务两套代码,AI杠杆效应完全被无效内耗抵消。
很多人认为SSR是"落后技术",但这是固有思维的误区。早年传统SSR的笨重、低效、体验差等问题,在今天早已被全新技术栈彻底解决。如今IPv6、HTTP3、AJAX局部刷新、现代HTML模板、分级缓存体系的成熟落地,叠加AI时代的业务特性,让服务端渲染重新具备了碾压客户端渲染的绝对优势。
我选择回归SSR,不是复古守旧,而是顺势而为,用成熟技术解决当下的研发痛点,最大化借助AI提升开发效率。同时,我彻底摒弃了臃肿的微服务架构,重新回归高效简洁的单体服务架构。架构从不是越复杂越好,适配AI时代"快速迭代、快速落地、轻量化开发"的需求,才是核心王道。
经过多项目落地验证,我打磨出一套适配AI时代、兼顾开发效率与用户体验的全新架构方案,彻底解决前后端分离的所有痛点,同时规避传统老SSR的体验缺陷,实现高效开发、极速访问、安全可控。
全面复用现代HTML模板引擎(Thymeleaf、Jinja2、PHP原生模板),后端直接动态渲染完整HTML页面,无需前端独立工程、无需每次修改重新build dist打包。同时对服务端渲染的动态页面、静态片段做分级缓存,避免重复渲染、重复计算,大幅提升响应速度,省去前端繁琐的编译打包流程,修改代码即时生效。
在Web服务器层面统一配置缓存策略,通过ETag、文件修改时间、Cache-Control响应头,对所有JS、CSS、图片等静态资源做强制缓存与协商缓存。用户二次访问全程走本地缓存,几乎实现零加载等待,彻底解决传统页面全量加载慢的问题。
我并非退回纯整页刷新的老式SSR,而是采用SSR首屏渲染 + AJAX局部交互的混合模式:首屏由服务端一次性渲染完整内容,保障秒开与SEO;页面后续动态交互、数据更新,通过AJAX局部请求完成,避免整页刷新。同时对所有JSON接口数据,同样配置ETag、过期时间、缓存策略,减少重复AI调用、重复数据库查询,大幅降低服务器压力。
全站开启HTTP3协议与IPv6网络支持,依托多路复用、0-RTT握手、低延迟传输的特性,搭配本地缓存体系,实现静态资源永久缓存、动态内容轻量请求,网络传输效率远超传统HTTP1.1架构。
目前这套逆向架构方案,我已经在多个商业项目中完整落地,效果远超传统前后端分离架构:
Springboot Webflux + Thymeleaf:适配高并发AI问答、内容生成项目,响应式架构保障高并发性能,模板渲染高效稳定;
Python FastAPI + Jinja2:适配轻量化AI工具、知识库项目,开发简洁、迭代快速。
而我的下一个项目,我决定彻底回归PHP技术栈。深耕Java、Python多年,我愈发厌烦SpringBoot、FastAPI的繁琐启动、重启加载、环境配置问题。PHP最核心的优势就是即用即走、修改即生效、无需重启服务,极简的开发模式,完美适配AI时代快速开发、快速验证、快速落地的需求,能让我把所有精力放在业务和AI能力落地,而非消耗在工程化冗余操作上。
从业二十余年,见证了技术的起起落落:CGI、PHP风靡一时,随后被JavaEE取代;前后端分离、微服务称霸十年,如今在AI场景下弊端尽显。我深刻明白一个道理:技术没有新旧高低,只有适配与否;架构无需追逐潮流,只需贴合时代。
前后端分离、微服务是互联网高速扩张、大型团队协作、复杂交互场景下的最优解,但在AI时代,我们的核心需求已经改变:不再需要极致的客户端复杂交互,而是需要快速开发、快速落地、低运维成本、高安全、高首屏速度、可AI赋能的轻量化架构。
AI给了所有技术人一次逆天改命的机会,我们不再需要堆砌繁琐的工程化代码、对接无尽的接口、处理冗余的架构问题,而是要学会用技术架构加杠杆,借助AI放大自身开发能力。
放弃盲目跟风的前后端分离、舍弃臃肿冗余的微服务,重回服务器渲染、回归单体简洁架构、重拾高效极简的PHP模式,不是倒退,而是历经千帆后的精准取舍。在AI新时代,简单、高效、落地快,才是最好的架构。
(注:文档部分内容可能由 AI 生成)