目录

  • 追本溯源
  • 概念说明
  • 需求分析
  • 核心功能
  • 代码实现
  • A用户模块
  • 本地DNS模块
  • CDN服务模块
  • 缓存服务模块
  • 原服务端
  • 总结提升


追本溯源

  CDN的发展源于对互联网内容传输速度和用户体验的需求。在互联网发展初期,内容的传输主要依赖于源服务器,但随着互联网用户规模的快速增长,单一服务器的容量和带宽很难满足全球用户的需求。为了解决这一问题,CDN技术应运而生。

概念说明

将内容缓存在终端用户附近

需求分析

  当我们去请求一个图片或者视频的时候,有时候会出现访问失败的问题,导致想要的资源获取不到。这时候就可以用到CDN了。对于资源的服务器来说我们可以把用户访问的域名注册到CDN中,这时候当我们在去访问资源的时候CDN会从我们距离我们最近访问最快的节点给我们返回资源,如果缓存服务器中没有我们想要的资源,那么缓存服务器会去请求原服务器,请求到资源之后保存到缓存服务器中,然后返回给用户,当用户第二次请求的时候就会从缓存服务器中直接返回了。

手写CDN基本原理_spring

核心功能

  1. 「内容加速 」:CDN通过将内容缓存在全球各地的节点服务器上,使用户可以从离自己最近的节点服务器获取内容,减少了网络延迟,提高了内容传输速度。
  2. 「 负载均衡 」:CDN通过在全球各地建立分布式节点服务器,可以将用户请求分散到不同的节点服务器上,实现负载均衡,提高了服务器的处理能力和可靠性。
  3. 「 高可用性 」:多个应用程序可以共享同一份配置信息,避免了配置信息的重复存储和管理。通过配置的分组和命名空间,可以实现不同应用程序之间的配置隔离和共享。

代码实现

A用户模块

请求想要的资源并集成了本地的DNS服务

import com.example.localdns.Controller.LocalDNSController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @BelongsProject: UserA
 * @BelongsPackage: com.example.usera
 * @Author: Wuzilong
 * @Description: A用户请求资源
 * @CreateTime: 2023-06-29 19:48
 * @Version: 1.0
 */
@RestController
@RequestMapping("/userA")
public class UserAController {


    @Autowired
    private Environment environment;

    @Autowired
    private LocalDNSController localDNS;

    @GetMapping(value = {"/getResource"})
    public void getResource(String key) {
        String property = environment.getProperty("server.name");
        String ipPath = localDNS.getIpAddress(property);
        String url = "http://" + ipPath + "/server/getResourceInfo?resourceName="+key;
        RestTemplate restTemplate = new RestTemplateBuilder().build();
        ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
        if (forEntity.getStatusCode() == HttpStatus.OK) {
            System.out.println("成功获取资源!资源为" + forEntity.getBody());
        }
    }

配置文件中需要配置连接nacos服务的地址以及本服务的信息

server:
  port: 8001
  name: www.wzl.com
  cdn: localhost:8005

在pom文件中引入我们自己封装的本地DNS服务

<dependency>
            <groupId>com.example</groupId>
            <artifactId>LocalDNS</artifactId>
            <version>1.3-20230706.142442-1</version>
        </dependency>

本地DNS模块

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

/**
 * @BelongsProject: LocalDNS
 * @BelongsPackage: com.example.localdns.Controller
 * @Author: Wuzilong
 * @Description: 描述什么人干什么事儿
 * @CreateTime: 2023-06-30 10:49
 * @Version: 1.0
 */
@RestController
@RequestMapping("/localDNS")
public class LocalDNSController {


        @Value("${server.cdn}")
        private String cdnServer;

        private Map<String, String> localDNS = new HashMap<>(){{
            put("www.wzl.com","localhost:9099");
        }};

        /**
        * @Author:Wuzilong
        * @Description: 将服务添加到CDN中,将域名进行加工起到域名加速的作用。并更新本地dns
        * @CreateTime: 2023/6/30 20:34
        * @param:
        * @return:
        **/
        @GetMapping("/addCDNServer")
        public void addCDNServer(String accelerateName) {
           String url="http://"+cdnServer+"/cdnServer/addDomainName?domainName="+accelerateName;
            RestTemplate restTemplate=new RestTemplateBuilder().build();
            ResponseEntity<Map<String,String>> forEntity = restTemplate.exchange(url, HttpMethod.GET, null,new ParameterizedTypeReference<Map<String, String>>() {});
            if (forEntity.getStatusCode() == HttpStatus.OK) {
                Map<String, String> body = forEntity.getBody();
                localDNS.putAll(body);
                localDNS.put(accelerateName, String.valueOf(body.keySet()).replaceAll("\\[|\\]", ""));
                System.out.println("已经将加速后的域名添加到本地DNS"+body);
            }
        }


        /**
        * @Author:Wuzilong
        * @Description: 获取域名的ip地址,通过cdn加速的域名获取之后还是一个域名那么需要再次解析域名直到得到ip地址为止
        * @CreateTime: 2023/7/1 10:27
        * @param:  域名
        * @return:  ip地址
        **/
        public String getIpAddress(String domainName){
            Boolean isRun=true;
            String ipAddress = localDNS.get(domainName);
            if (!ipAddress.contains(":")){

                ipAddress=this.getIpAddress(ipAddress);
                isRun=false;
            }
            if (isRun){
                String url="http://"+ipAddress+"/cdnServer/getNearbyIpAddress";
                RestTemplate restTemplate=new RestTemplateBuilder().build();
                ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
                if (forEntity.getStatusCode() == HttpStatus.OK) {
                    System.out.println("获取到了距离最近的ip地址,为:"+forEntity.getBody());
                    ipAddress=forEntity.getBody();

                }
            }
            return ipAddress;
        }
    }

CDN服务模块

  CDN服务端主要的服务:注册要加速度的服务和计算当前请求的ip地址距离哪个缓存服务器最近响应最快

import com.maxmind.geoip2.DatabaseReader;
import com.maxmind.geoip2.exception.GeoIp2Exception;
import com.maxmind.geoip2.model.CityResponse;
import com.maxmind.geoip2.record.Location;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @BelongsProject: CDNServer
 * @BelongsPackage: com.example.cdnserver.Controller
 * @Author: Wuzilong
 * @Description: 描述什么人干什么事儿
 * @CreateTime: 2023-06-30 20:56
 * @Version: 1.0
 */
@RestController
@RequestMapping("/cdnServer")
public class CdnServerController {

    @Value("${server.port}")
    private String serverPort;

    private List<String> cityIPs=new ArrayList<>(){{
        //需要改成各个地方服务器的ip地址也就是给用户提供的缓存服务器的ip地址
        add("0.0.0.0");
        add("1.1.1.1");
        add("2.2.2.2");
        add("3.3.3.3");
    }};


    // 地球半径(单位:千米)
    private static final double EARTH_RADIUS = 6371;
    /**
    * @Author:Wuzilong
    * @Description: 返回通过cdn加工之后的域名和cdn 调度中心的ip地址
    * @CreateTime: 2023/7/1 9:52
    * @param:  需简要加速的域名
    * @return:  返回通过cdn加工之后的域名和cdn 调度中心的ip地址
    **/
    @GetMapping("/addDomainName")
    public Map<String,String> addDomainName(String domainName) throws UnknownHostException {
        System.out.println("请求加速的域名为"+domainName);
        Map<String,String> CDNInfo=new HashMap<>();
        domainName=domainName+".cdn";
        CDNInfo.put(domainName, InetAddress.getLocalHost().getHostAddress()+":"+serverPort);
        System.out.println("加速后的域名为"+domainName);
        return CDNInfo;
    }


    @GetMapping("/getNearbyIpAddress")
    public String getNearbyIpAddress() throws IOException, GeoIp2Exception {
        // 创建一个指向GeoIP2数据库文件的File对象
        File database = new File("C:\\Users\\Administrator\\Desktop\\city\\GeoLite2-City_20230704\\GeoLite2-City.mmdb");//解析ip地址的文件路径

        // 使用数据库文件创建一个DatabaseReader对象,用于查询城市的地理位置信息
        DatabaseReader reader = new DatabaseReader.Builder(database).build();
        //获取请求方的ip地址,需要调整成动态获取的
        String remoteAddr="111.111.111.111";

        // 查询单独的IP地址的地理位置信息
        InetAddress ip = InetAddress.getByName(remoteAddr);
        CityResponse response = reader.city(ip);
        // 获取城市的经纬度坐标
        Location location1 = response.getLocation();
        double lat1 = Math.toRadians(location1.getLatitude());
        double lon1 = Math.toRadians(location1.getLongitude());
        String nearestIp="";
        double minDistance =Double.MAX_VALUE;
        // 遍历各个城市的IP地址,计算距离并更新最小距离和对应的城市
        for (String cityIP : cityIPs) {
            // 初始化最小距离和对应的城市
            InetAddress cityIpAddress = InetAddress.getByName(cityIP);
            CityResponse cityResponse = reader.city(cityIpAddress);
            Location location2 = cityResponse.getLocation();
            double lat2 = Math.toRadians(location2.getLatitude());
            double lon2 = Math.toRadians(location2.getLongitude());

            double dlon = lon2 - lon1;
            double dlat = lat2 - lat1;

            double a = Math.sin(dlat / 2) * Math.sin(dlat / 2) +
                    Math.cos(lat1) * Math.cos(lat2) *
                            Math.sin(dlon / 2) * Math.sin(dlon / 2);
            double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

            double distance = EARTH_RADIUS * c;

            System.out.println("和"+cityResponse.getCity().getName()+"的距离是:" + cityIP+",距离是:"+distance);

            if (distance < minDistance) {
                minDistance = distance;
                nearestIp = cityIP;
            }

        }
        System.out.println("距离最短的城市对应的IP是:" + nearestIp);
        return nearestIp;
    }
}

缓存服务模块

  CDN是给用户提供访问响应最快距离最近的服务器,如果缓存服务器中没有用户想要的资源需要向原服务器继续请求,然后保存到缓存服务器中一份,方便下次访问相同的资源能够直接返回用户想要的资源,提高用户的体验。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

/**
 * @BelongsProject: CDNLoadBalanceSlave1
 * @BelongsPackage: com.example.cdnloadbalanceslave1.Controller
 * @Author: Wuzilong
 * @Description: 描述什么人干什么事儿
 * @CreateTime: 2023-06-30 10:04
 * @Version: 1.0
 */
@RestController
@RequestMapping("/server")
public class LoadBalanceSlaveController  {
    @Autowired
    private Environment environment;
    
    //用来存储想访问的资源的-缓存版本
    private Map<String,String> resourceInfo=new HashMap<>();

    @GetMapping(value = {"/getResourceInfo"})
    public String getResourceInfo(String resourceName){
        String value = resourceInfo.get(resourceName);
        if (!StringUtils.isEmpty(value)){
            return resourceInfo.get(resourceName);
        }else{
            String originalService = environment.getProperty("server.url");
            String url="http://"+originalService+"server/getResourceInfo?resourceName="+resourceName;
            RestTemplate restTemplate = new RestTemplateBuilder().build();
            ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
            if (forEntity.getStatusCode() == HttpStatus.OK) {
                System.out.println("从原服务器获取资源成功,原服务器的ip是:" + originalService);
                resourceInfo.put(resourceName, forEntity.getBody());
                return forEntity.getBody();
            }
        }
        return "非常抱歉,没有获取到您想要的资源!";
    }
}

原服务端

  用来存储用户想要的数据,用户直接向原服务器进行添加资源。确保原服务器中一定有用户想要的资源。

import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

/**
 * @BelongsProject: CDNLoadBalance
 * @BelongsPackage: com.example.cdnloadbalance.Controller
 * @Author: Wuzilong
 * @Description: 原服务器
 * @CreateTime: 2023-06-29 21:11
 * @Version: 1.0
 */
@RestController
@RequestMapping("/server")
public class OriginalServerController {

    //缓存进来的资源
    private Map<String, String> resourceData = new HashMap<>(){{
        put("wzl","武梓龙");
        put("hhh","哈哈哈");
    }};


    /**
    * @Author:Wuzilong
    * @Description: 从缓存中没有获取到数据,直接请求原服务器的数据
    * @CreateTime: 2023/7/5 19:23
    * @param:
    * @return:
    **/
    @GetMapping(value = {"/getResourceInfo"})
    public String getResourceInfo(String resourceName){
        String value = resourceData.get(resourceName);
        if (StringUtils.isEmpty(value)){
            return null;
        }
        return value;
    }


    /**
    * @Author:Wuzilong
    * @Description: 新添加进来的信息
    * @CreateTime: 2023/7/5 19:33
    * @param:  新资源的键值对信息
    * @return: 资源添加成功提示语
    **/
    @PostMapping(value = {"/setResourceInfo"})
    public String setResourceInfo(@RequestBody Map<String, String> resourceInfo){
        resourceData.putAll(resourceInfo);
        return "新资源添加成功!";
    }

}