百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

cdn原理分析-本地搭建cdn模拟访问过程

nanshan 2024-12-18 15:59 13 浏览 0 评论

这一生听过许多道理,但还是过不好这一生,这是因为缺少真正的动手实践,光听道理,缺少动手实践的过程,学习难免会让人觉得味同嚼蜡,所以我的分享都比较倾向于实践,在一次次动手实践的过程中感受知识原本纯真的模样。

本节源码已经上传到github

shell复制代码https://github.com/HobbyBear/cdndemo

现在,让我们开始吧。

cdn原理介绍

首先,我们来看下为什么要用cdn,比如一个专注做视频播放或者图片阅览的网站,当用户浏览网站时,需要从网站拉取图片或者视频资源,这将产生流量费用,并且,如果用户里网站服务器越远,产生的流量费用将越高。而cdn的原理则是将视频或者图片资源缓存在离用户比较近的服务器上,这样既提升了响应速率,又节约了流量费。

来看下使用cdn后,用户访问网站的过程。

如上图所示,假设用户自己的想要加速的域名是web.cdn.test,如果用户想对这个域名进行加速,首先要去cdn服务商那里配置上这个加速域名源站服务地址,接着cdn服务商会生成一个域名地址,这里假设为web.cdn.test.c.lanpangzi,而用户自己需要去自己的dns服务商那里将加速域名web.cdn.test 指向这个新的域名地址,这种将一个域名指向另一个域名的记录被称作cname记录。

经过上述步骤后,对域名web.cdn.test的访问会被指向web.cdn.test.c.lanpangzi,但目前还有一个问题,新的域名web.cdn.test.c.lanpangzi应该由cdn服务商自己的dns调度系统去解析,这样cdn服务商才能将边缘节点的ip返回给用户,那本地dns服务器怎么知道web.cdn.test.c.lanpangzi这个域名要交给哪台机器去解析呢?

原因是这样的,cdn服务商在注册自己的主域名时(这里的主域名是langpangzi.) ,向dns服务器注册了一条ns记录,ns记录可以指定将某个域名的解析交给哪一台dns服务器解析,比如这里,cdn服务商就把c.langpangzi. 域名的解析指向了cdn服务商自己的dns服务器。

完成了这一步,用户自己的加速域名最终就会被cdn服务商的dns服务器去解析了,cdn服务商一般在全球各地都有自己的节点,所以它会根据用户的ip去筛选一个离用户比较近节点ip返回,这些节点被称作边缘节点 ,这样用户的请求就能就近访问了。

本地搭建一个cdn

我们把上面的请求过程与cdn架构在本地模拟下,把自己想象成一个cdn服务商,我们将会搭建cdn的域名调度中心,和边缘节点,然后允许用户提供加速域名和回源服务器给我进行配置。

搭建dns服务器

我们先来搭建一个dns服务器,因为无论是cdn还是用户本身都需要进行一些dns域名配置,你可以把当前这个dns服务器想象成第三方dns运营商。

我们用dnsmasq在本地进行dns服务器的搭建,我本地机器用的mac,安装命令如下:

shell复制代码brew install  dnsmasq

==> Dependencies
Build: pkg-config ?
==> Caveats
To start dnsmasq now and restart at startup:
  sudo brew services start dnsmasq
Or, if you don't want/need a background service you can just run:
  /opt/homebrew/opt/dnsmasq/sbin/dnsmasq --keep-in-foreground -C /opt/homebrew/etc/dnsmasq.conf -7 /opt/homebrew/etc/dnsmasq.d,*.conf
==> Analytics
install: 0 (30 days), 0 (90 days), 0 (365 days)
install-on-request: 0 (30 days), 0 (90 days), 0 (365 days

接着需要对其配置文件进行修改,配置上游服务器,以及配置当前dns服务器能够解析的域名

从安装的信息可以看出,其配置文件是在/opt/homebrew/etc/dnsmasq.conf 这个位置,这里我直接给出我的配置信息

shell复制代码# 配置上行DNS,对应no-resolv
resolv-file=/Users/xiongchuanhong/dnsmasq.conf
# 运行进程以哪个用户身份运行,直接用root,因为dnsmasq的配置涉及到权限问题
user=root
# 配置dnsmqsq运行日志,对排错很重要
log-facility=/Users/xiongchuanhong/logs/dnsmasq.log
# resolv.conf内的DNS寻址严格按照从上到下顺序执行,直到成功为止
strict-order
# DNS解析hosts时对应的hosts文件,对应no-hosts
addn-hosts=/etc/hosts
cache-size=1024
# 多个IP用逗号分隔,192.168.x.x表示本机的ip地址,只有127.0.0.1的时候表示只有本机可以访问。
# 通过这个设置就可以实现同一局域网内的设备,通过把网络DNS设置为本机IP从而实现局域网范围内的DNS泛解析(注:无效IP有可能导至服务无法启动)192.168.17.150 是我本地机器内网ip
listen-address=127.0.0.1,192.168.17.150
# 相当于ns记录,c.lanpangzi的域名以及其子域名都会由本地1053端口的进程去进行解析
server=/c.lanpangzi/127.0.0.1#1053
# cname记录,访问web.cdn.test 的域名都会指向web.cdn.test.c.lanpangzi 
cname=web.cdn.test,web.cdn.test.c.lanpangzi
# 重要!!这一行就是你想要泛解析的域名配置.
#address=/hello.me/127.0.0.1

上面resolv-file 所指向的文件是要配置的上游dns服务器,/Users/xiongchuanhong/dnsmasq.conf配置如下:

shell复制代码(base) ?  ~ cat /Users/xiongchuanhong/dnsmasq.conf
 nameserver 8.8.8.8

当本地解析不了域名,那么dnsmasq会询问它的上游dns服务器,所以我们把它配置成谷歌的域名解析系统。

注意Users/xiongchuanhong/dnsmasq.conf和Users/xiongchuanhong/logs/dnsmasq.log文件都需要用root用户去创建,不然到时候运行会报权限错误。

接着启动dnsmasq服务

shell复制代码sudo brew services start  dnsmasq

然后再把机器的dns服务器ip改成127.0.0.1

接着测试下修改后的网络访问有没有问题

shell复制代码(base) ?  ~ ping www.baidu.com
PING www.a.shifen.com (14.119.104.189): 56 data bytes
64 bytes from 14.119.104.189: icmp_seq=0 ttl=54 time=36.413 ms
64 bytes from 14.119.104.189: icmp_seq=1 ttl=54 time=11.351 ms
64 bytes from 14.119.104.189: icmp_seq=2 ttl=54 time=11.339 ms
64 bytes from 14.119.104.189: icmp_seq=3 ttl=54 time=16.660 ms

域名解析正常,接着进行下面的步骤。

搭建回源服务器和cdn边缘节点

接着我们来快速搭建下回源服务器和cdn的边缘节点,这里我是直接借用了nginx来进行搭建,因为nginx可以用作静态资源服务器,所以可以直接启动一个nginx容器把它作为源服务器,提供图片资源的访问。 并且nginx也能提供缓存服务,这也刚好符合边缘节点缓存资源的需求。由于边缘节点需要回源到源服务器,但是容器的ip又是不固定的,所以这里我直接用docker-compose对这两个容器进行了编排,这样在写nginx配置文件的时候,便可以用容器名代替ip了。

docker-compose.yaml配置文件

shell复制代码version: "3"  
services:  
  sourceserver:  
    image: nginx:latest  
    container_name: source-server  
    hostname: source-server  
    ports:  
      - '8080:80'  
    volumes:  
      - ./sourceserver/nginx.conf:/etc/nginx/nginx.conf  
      - ./sourceserver/logs:/var/log/nginx  
      - ./sourceserver/imgs:/imgs  
  
  edgenode:  
    image: nginx:latest  
    container_name: edgenode  
    hostname: edgenode  
    ports:  
      - '80:80'  
    volumes:  
      - ./edgenode/nginx.conf:/etc/nginx/nginx.conf  
      - ./edgenode/logs:/var/log/nginx  
      - ./edgenode/cache:/cache

源站服务器的nginx配置

shell复制代码  
worker_processes  1;  
error_log   /var/log/nginx/error.log;  
events {  
    worker_connections  1024;  
}  
http {  
    default_type  application/octet-stream;  
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '  
                      '$status $body_bytes_sent "$http_referer" '  
                      '"$http_user_agent" "$http_x_forwarded_for"';  
    # 静态资源配置  
    server {  
        listen       80;  
        access_log  /var/log/nginx/access.log  main ;  
        location /static {  
            alias   /imgs;  
        }  
    }  
}

边缘节点的nginx配置

在server模块里,我们配置 proxy_pass 转发路径时,直接用源服务器的容器名代替了ip,http://sourceserver。当本地没有缓存时,就会通过http://sourceserver 访问源站服务器。

注意下,在真实环境里,边缘节点的源站服务器名和加速域名肯定不是写死的,是用户配置在cdn服务商的数据库里,然后边缘节点再从服务器里读取的。

并且边缘节点要求只有用户的加速域名才能访问,所以在转发请求前还要判断下域名是不是用户的加速域名。

shell复制代码worker_processes  1;  
error_log   /var/log/nginx/error.log;  
events {  
    worker_connections  1024;  
}  
http {  
    log_format  main  '$host- $remote_addr - $remote_user [$time_local] "$request" '  
                      '$status $body_bytes_sent "$http_referer" '  
                      '"$http_user_agent" "$http_x_forwarded_for"';  
      proxy_cache_path  /cache levels=1:2 keys_zone=lanpangzi-cache:20m max_size=50g inactive=168h;  
      proxy_cache lanpangzi-cache;  
      proxy_cache_valid 168h;  
  server {  
      root /static;  
      listen       80;  
      location ~* .(css|js|png|jpg|jpeg|gif|gz|svg|mp4|ogg|ogv|webm|htc|xml|woff)$ {  
        if ($host != 'web.cdn.test')  {  
             return 403;  
           }  
      access_log  /var/log/nginx/access.log  main;  
      proxy_pass http://sourceserver;  
    }  
  }  
}

这样就快速搭建好了源服务器和边缘节点,我们到时候直接用docker-compose up命令便可以启动容器,边缘节点监听了本地80端口。

搭建cdn的域名调度系统

最后,我们来搭建下cdn的域名调度系统,防止遗忘,我们再来串联下配置cdn的过程,用户拥有自己的加速域名和源站服务器,并且将这两个告诉了cdn服务商,cdn服务商就产生一个新域名,这个新域名需要配置为cname记录,与用户的加速域名关联起来,这条cname记录作我们在搭建dnsmasq时已经写死在了配置文件里。

shell复制代码cname=web.cdn.test,web.cdn.test.c.lanpangzi

注意,现实中,cdn服务商新生成的域名需要用户自己去其dns服务商那里去进行配置

同时,cdn服务商生成的新域名解析因为配置的ns记录,已经将新的这个域名解析指向了自己的dns域名调度系统,到时候通过这个系统便可以返回给用户最近的边缘节点的ip。

这条ns记录我们也写死在了dnsmasq的配置文件里。

shell复制代码server=/c.lanpangzi/127.0.0.1#1053

到时候我们本地会在1053端口启动一个进程用于对c.lanpangzi域名进行解析。

接着来思考下如何写一个域名调度系统,我们的服务通信需要遵循dns协议,由于我们的边缘节点是部署到本地,ip是127.0.0.1,所以当解析到请求时要询问web.cdn.test.c.lanpangzi这个域名时,直接返回本地这个ip即可。 那对于其他域名如何处理,当然是直接丢掉,因为cdn的域名调度系统,只提供对自己cdn服务商生成的域名进行解析。

用golang简单实现代码如下:

这里我直接在代码里将cname域名以及边缘节点的ip写死在代码里了(重在模拟cdn请求过程),只要是web.cdn.test.c.lanpangzi.这个域名就返回边缘节点ip,由于我们都是部署到本地,所以边缘节点ip就是127.0.0.1了,dns的解析用了现成的golang库。

go复制代码import (  
   "github.com/miekg/dns"  
   "log"   "net")  
  
var cdnConfig = map[string]string{  
   "web.cdn.test.c.lanpangzi.": "127.0.0.1",  
}  
  
// 处理到来的请求  
func handler(writer dns.ResponseWriter, req *dns.Msg) {  
   var resp dns.Msg  
   resp.SetReply(req) // 创建应答  
   for _, question := range req.Question {  
      ip := cdnConfig[question.Name]  
      if len(ip) == 0 {  
         return  
      }  
      recordA := dns.A{  
         Hdr: dns.RR_Header{  
            Name:   question.Name,  
            Rrtype: dns.TypeA,  
            Class:  dns.ClassINET,  
            Ttl:    100,  
         },  
         A: net.ParseIP(ip).To4(), // 全部解析为127.0.0.1  
      }  
      resp.Answer = append(resp.Answer, &recordA) // 写入应答  
   }  
   err := writer.WriteMsg(&resp) // 回写信息  
   if err != nil {  
      return  
   }  
}  
  
func main() {  
   dns.HandleFunc(".", handler)                   // 绑定函数  
   err := dns.ListenAndServe(":1053", "udp", nil) // 启动  
   if err != nil {  
      log.Println(err)  
   }  
}

测试

接着,我们就可以测试下现在搭建的这个cdn系统了,虽然我们将很多配置信息都写死在了代码里,但你可以假装他们是自动配置上的。

来看下现在的代码框架.

cnddns就是模拟的cdn调度中心了,edgenode和sourceserver放的都是nginx的配置文件,docker-compose.yaml到时候会启动他们。

启动源站服务器,边缘节点

shell复制代码(base) ?  cdndemo git:(master) ? docker-compose up 
Starting edgenode      ... done
Starting source-server ... done
Attaching to edgenode, source-server
source-server   | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
source-server   | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
source-server   | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh

启动cdn调度中心

shell复制代码(base) ?  cdndemo git:(master) ? cd cdndns            
(base) ?  cdndns git:(master) ? go run main.go  

测试访问

我们已经把本地机器的dns服务器改为dnsmasq了,接着就是最后测试下访问是否生效。

我在浏览器连续进行了两次访问

shell复制代码http://web.cdn.test/static/1781686486866_.pic.jpg

接着查看下nginx的access.log

边缘节点的日志

shell复制代码web.cdn.test- 172.22.0.1 - - [16/Jun/2023:08:53:12 +0000] "GET /static/1781686486866_.pic.jpg HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" "-"  
web.cdn.test- 172.22.0.1 - - [16/Jun/2023:08:53:31 +0000] "GET /static/1781686486866_.pic.jpg HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" "-"

源站服务器日志

shell复制代码sourceserver- 172.22.0.3 - - [16/Jun/2023:08:53:12 +0000] "GET /static/1781686486866_.pic.jpg HTTP/1.0" 200 265084 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" "-"

可以看见当第二次访问的时候,源站服务器上没有日志了,边缘节点仍然有访问日志,说明第二次访问已经直接从边缘节点读取到了图片信息。

还是强烈建议把源码下下来亲自实验一遍,你会更加深刻。

相关推荐

删库之后不要着急跑路,教你神不知鬼不觉找回数据

在工作中,我们误删数据或者数据库,我们一定需要跑路吗?我看未必,程序员一定要学会自救,神不知鬼不觉的将数据找回。在mysql数据库中,我们知道binlog日志记录了我们对数据库的所有操作,所以...

数据库告警不可用,增删改受阻(数据库限制删除)

前言:昨晚,突然出现服务不可用告警,查看日志上线报文入库到数据库很慢并受阻,出现数据不同步问题。排查问题查看发现服务都是在执行update、insert这些DML命令的时候,报的数据库执行超时。经过一...

Binlog实现MySQL复制,5个关键步骤,务必掌握!

复制是MySQL最重要的功能之一,MySQL集群的高可用、负载均衡和读写分离都是基于复制来实现的。Binlog就是实现主从复制的关键,主数据库将修改操作记录到Binlog中,从数据库通过解...

MySQL数据实时增量同步到Elasticsearch

Mysql到Elasticsearch的数据同步,一般用ETL来实现,但性能并不理想,目前大部分的ETL是定时查询Mysql数据库有没有新增数据或者修改数据,如果数据量小影响不大,但如果几百万上千万的...

MySQL 数据库恢复:如何执行时间点恢复(PITR)以挽救受损数据?

天津鸿萌科贸发展有限公司从事数据安全服务二十余年,致力于为各领域客户提供专业的数据恢复、数据备份、数据取证、数据迁移、网络安全、数据清除等解决方案,并针对企业面临的数据安全风险,提供专业的相关数据安全...

阿里面试:MySQL Binlog有哪些格式?底层原理?优缺点?

binlog的格式也有三种:STATEMENT、ROW、MIXED,下面我详解binlog三种模式@mikechenStatement模式Statement模式:是基于SQL语句的复制(statem...

快速带你读懂MySQL的binlog写入机制

深入讲解MySQL中的重要日志binlog的写入机制以及影响IO性能的关键配置,并且介绍了如何利用binlog去恢复数据,保证MySQL的可靠性。Q:binlog写入时机binlog的写入逻辑并...

MySQL 误删除数据恢复全攻略:基于 Binlog 的实战指南

在MySQL的世界里,二进制日志(Binlog)就是我们的"时光机"。它默默记录着数据库的每一个重要变更,就像一位忠实的史官,为我们在数据灾难中提供最后的救命稻草。本文将带您深入掌握如...

一文了解MySQL Binlog(一文了解肝脏有益和有害的食物)

MySQL的Binlog日志是一种二进制格式的日志,Binlog记录所有的DDL和DML语句(除了数据查询语句SELECT、SHOW等),以Event的形式记录,同时记录语句执行时...

数据丢失?别慌!MySQL备份恢复攻略

想象一下,某个晴朗的午后,你正享受着咖啡,突然接到紧急电话:你的网站或APP彻底挂了!系统崩溃,界面全白。虽然心头一紧,但你或许还能安慰自己:系统崩溃只是暂停服务,数据还在,修复修复就好了。然而,如果...

Mysql中的bin log、redo log、undo log的区别

最近在整理面试题,在看mvcc的时候看到了undolog,今天索性把这三个log都记录一遍。MySQL的逻辑架构说之前先说一下MySQL的基本架构,MySQL主要分为两层:Server层和存储引...

binlog日志定时清理(binlog清理规则)

binlog日志binlog是MySQL数据库的一种日志文件,用于记录所有对数据的修改操作。binlog全称为binarylog,它以二进制格式记录MySQL服务器上所有的修改操作,包括对哪个数据库...

茶水间炸锅了!菜鸟误删用户表,运维老张的MySQL救命三招!

(公司茶水间,运维老张、开发小王和新人小李围着咖啡机)小李:(紧张兮兮)张哥!我...我好像把测试库的用户表删了!下午演示咋办啊?老张:(淡定喝咖啡)慌啥?昨晚的备份是吃干饭的?走,教你恢复!一、基础...

解决运维痛点,提高运维安全性-雷池 SafeLine WAF新功能身份认证

雷池介绍使用雷池SafeLineWAF已经两年多了,在1.5.x版本时就已经开始测试使用,并在推出LTS版本后转入LTS分支。近期雷池SafeLineWAF重点更新了身份认证功能,并提供了SS...

【Docker 新手入门指南】第十五章:常见故障排除

一、前期准备:收集关键信息在排查问题前,建议先获取以下系统数据,便于精准定位故障:1.系统基础信息#查看Docker版本(确认是否为最新稳定版)dockerversion#查看...

取消回复欢迎 发表评论: