需求

客户端那边更新了源站资源,需要对cdn目录进行刷新,好测试新资源,每次找到运维,然后运维登陆cdn控制台进行刷新操作,这样很麻烦,所以我们准备将cdn刷新功能接入到自动化运维平台,然后分配相关人员执行权限,这样的话能减少沟通成本提升工作效率.


概念

CDN(Content Delivery Network) 即内容分发网络,通过在现有的Internet中增加一层新的网络架构,部署边缘服务器,将网站的内容发布到最接近用户的Cache服务器,使用户可以就近取得所需的内容,实现用户就近访问,有效提升网站的访问效果,安全性和稳定性.

CDN的实现需要依赖多种网络技术的支持,其中负载均衡技术,动态内容分发与复制技术,缓存技术是比较主要的几个.

其中动态内容分发与复制技术影响最大:

----首先,网站访问响应速度取决于许多因素,如网络的带宽是否有瓶颈,传输途中的路由是否有阻塞和延迟,网站服务器的处理能力及访问距离等.多数情况下,网站响应速度和访问者与网站服务器之间距离有密切的关系,如果访问者和网站之间的距离过远的话,它们之间的通信一样需要经过重重的路由转发和处理,网络延迟不可避免.


----缓存技术

缓存技术已经不是一种新鲜技术,web缓存服务通过几种方式来改善用户的响应时间,如代理缓存服务,透明代理缓存服务,使用重定向的透明代理缓存服务等.通过web缓存服务,用户访问网页时可以将广域网的流量降至最低.对于公司内联网用户来说,这意味着将内容在本地缓存,而无须通过专用的广域网来检索网页.对于Internet用户来说,这意味着将内容存储在他们的ISP的缓存器中,而无须通过Internet来检索网页,这样无疑会提高用户的访问速度,CDN的核心作用正是提高网络的访问,所以,缓存技术将是CDN所采用的又一个主要技术.

各地的Cache服务器保存着源站静态内容的一份有效拷贝,网民无需直接访问源站,就可以在离自己最近的Cache服务器上获得新鲜正确的内容.目前缓存服务器可以有多种选择,大名鼎鼎的squid,还有nginx(ncache)都可以用作Cache服务器,使得大多数的访问都能在Cache设备上获得,而不需要直接请求源服务端获取.

综上,CDN从技术上解决由于网络带宽小,用户访问量大,网点分布不均等原因造成的用户访问网站响应速度慢的问题,这也是我们在生产环境中使用CDN的主要原因(除了专门从事CDN服务的公司,大多情况下生产环境都会购买相应的CDN服务,毕竟因为某个服务去搭建全球性的CDN站点是得不偿失的).


传统的CDN厂商:网宿,帝联,蓝汛,Aegins(安捷)

以云CDN为代表的:阿里云,腾讯云,百度云,360,七牛云




cdn主流架构

工作流程

cdn刷新功能接入_CDN


工作流程

当代理服务器中有客户端需要的数据时:

1)客户端向代理服务器发送数据请求;

2)代理服务器检查自己的数据缓存;

3)代理服务器在缓存中找到了用户想要的数据,取出数据;

4)代理服务器将从缓存中取得的数据返回给客户端。

当代理服务器中没有客户端需要的数据时:

1)客户端向代理服务器发送数据请求;

2)代理服务器检查自己的数据缓存;

3)代理服务器在缓存中没有找到用户想要的数据;

4)代理服务器向Internet 上的远端服务器发送数据请求;

5)远端服务器响应,返回相应的数据;

6)代理服务器取得远端服务器的数据,返回给客户端,并保留一份到自己的数据缓存中。


云CDN一般架构

CDN+对象存储(源站)   CDN+ECS(安装nginx做web服务器)


七牛云cdn刷新为例

参考开发文档链接:缓存刷新与查询_API 文档_CDN - 七牛开发者中心 (qiniu.com)

class Opt_Refresh_Cdn:
    # 账户ak,sk
    def __init__(self):
        self.access_key = 'xxx'
        self.secret_key = 'xxxx'
        self.auth = qiniu.Auth(access_key=self.access_key, secret_key=self.secret_key)
        self.cdn_manager = CdnManager(self.auth)
        self.dbopt = OptMango(mongobase='mongodb://xxx:27017', user='xxx',
                       passwd='xxxxx', authdb='xxxx', db='xxxx')

    def refresh_url(self, proj, urls):
        """
        七牛云cdn操作刷新url
        :return:
        :param proj:
        :param urls:
        """
        # noinspection PyBroadException
        try:
            refresh_url_result = self.cdn_manager.refresh_urls(urls)
            res = refresh_url_result[0]
            if res["code"] == 200 and res["error"] == "success":
                """调用成功的话入库"""
                # 组成mongo数据,里面有项目proj,url资源路径,刷新类型refresh_type,状态status,提交时间commit_time信息
                # 这里只能代表调用成功,但是最终执行刷新结果状态需要调用七牛云相关接口确定,参考文档:https://developer.qiniu.com/fusion/1229/cache-refresh
                # 所以调用成功后这里入库mongo记录status的值暂时为空,后面使用定时任务通过七牛云接口获取任务状态来更新对应任务的status值
                for url in urls:
                    opt_log = {"requestId": res["requestId"], "proj": proj, "url": url, "refresh_type": "文件",
                               "status": "", "commit_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
                               "urlSurplusDay": res["urlSurplusDay"], "dirSurplusDay": res["dirSurplusDay"]}
                    opt_res = self.dbopt.insert_one_rec("cdn_opt_log", opt_log)
                    if not opt_res:
                        return {"success": False, "msg": "cdn操作记录入库失败,请联系管理员"}
                return {"success": True, "msg": "刷新文件调用成功"}
            else:
                logger.error({"刷新文件调用报错: {}".format(str(res))})
                return {"success": False, "msg": "刷新文件调用失败,请联系管理员"}
        except Exception as E:
            logger.error("刷新文件调用报错: {}".format(str(E)))
            return {"success": False, "msg": "刷新文件调用失败,请联系管理员"}

    def refresh_dir(self, proj, dirs):
        """
        七牛云cdn操作刷新目录
        :param proj:
        :param dirs:
        :return:
        """
        # noinspection PyBroadException
        try:
            refresh_dir_result = self.cdn_manager.refresh_dirs(dirs)
            res = refresh_dir_result[0]
            if res["code"] == 200 and res["error"] == "success":
                """调用成功的话入库"""
                # 组成mongo数据,里面有项目proj,url资源路径,刷新类型refresh_type,状态status,提交时间commit_time信息
                # 这里只能代表调用成功,但是最终执行刷新结果状态需要调用七牛云相关接口确定,参考文档:https://developer.qiniu.com/fusion/1229/cache-refresh
                # 所以调用成功后这里入库mongo记录status的值暂时为空,后面使用定时任务通过七牛云接口获取任务状态来更新对应任务的status值
                for d in dirs:
                    opt_log = {"requestId": res["requestId"], "proj": proj, "url": d, "refresh_type": "目录",
                               "status": "", "commit_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
                               "urlSurplusDay": res["urlSurplusDay"], "dirSurplusDay": res["dirSurplusDay"]}
                    opt_res = self.dbopt.update_one_rec("cdn_opt_log", opt_log, opt_log)
                    if not opt_res:
                        return {"success": False, "msg": "cdn操作记录入库失败,请联系管理员"}
                return {"success": True, "msg": "刷新目录调用成功"}
            else:
                # 七牛云的sdk执行失败,错误返回
                logger.error("刷新目录调用报错: {}".format(str(res)))
                return {"success": False, "msg": "刷新目录调用失败,请联系管理员"}
        except Exception as E:
            logger.error("刷新目录调用报错: {}".format(str(E)))
            return {"success": False, "msg": "刷新目录调用失败,请联系管理员"}
        
	def update_cdn_refresh_status():
        """
        定时刷新cdn刷新任务状态的值
        第一步:从mongo cdn_opt_log表获取status字段值为空或为处理中的所有记录
        第二步: 遍历这些记录,取出其中的requestId作为请求payload,对七牛云接口发起请求查询对应任务的刷新状态state的值然后更新对应任务的status字段
        的值,state返回值有3种情况,分别是success/processing/failure,根据返回值以此判断更新mongo status字段的数据分别为成功,处理中,失败
        :return:
        """
        def get_cdn_refresh_list(payload):
            """
            内部方法,获取cdn刷新任务状态
            :return:
            :param payload:
            """
            # 账户ak,sk
            auth = qiniu.Auth(access_key=self.access_key, secret_key=self.secret_key)
            access_token = auth.token_of_request("https://fusion.qiniuapi.com/v2/tune/refresh/list", payload)
            headers = {
                "Authorization": f"QBox {access_token}",
                "Content-Type": "application/json"
            }

            res_json = requests.post("https://fusion.qiniuapi.com/v2/tune/refresh/list",
                                     headers=headers, json=payload).json()
            return res_json

        f1 = {"$or": [{"status": ""}, {"status": "处理中"}]}
        m_res = self.dbopt.find_rec_all("cdn_opt_log", f1)
        if m_res:
            opt_cdn_list = list(m_res)
            for item in opt_cdn_list:
                body = {"requestId": item["requestId"]}
                res = get_cdn_refresh_list(body)
                if res["code"] == 200 and res["error"] == "success":
                    state = res['items'][0]['state']
                    if state == "success":
                        item["status"] = "成功"
                    elif state == "processing":
                        item["status"] = "处理中"
                    elif state == "failure":
                        item["status"] = "失败"
                    m_res = self.dbopt.update_one_rec("cdn_opt_log", body, item)
        return {"success": True, "msg": "刷新cdn任务状态任务执行完成"}

注明:OptMango()我在MongoDB从入门到进阶_彭阳的技术博客_51CTO博客 (使用python中的pymongo模块操作mongodb数据库实战代码)代码封住的, 当然你也可以将cdn刷新操作记录放到mysql里,不过你可能需要提前设计下表,当然也有看到同学使用go语言编写cdn刷新功能,然后将编译后的文件放到jenkins上供他人执行,想法都是非常好的.