追本溯源
CDN的发展源于对互联网内容传输速度和用户体验的需求。在互联网发展初期,内容的传输主要依赖于源服务器,但随着互联网用户规模的快速增长,单一服务器的容量和带宽很难满足全球用户的需求。为了解决这一问题,CDN技术应运而生。
概念说明
CDN(Content Delivery Network)是一种通过在全球各地建立分布式节点服务器,将内容缓存到离用户最近的节点服务器上,以提供快速、高效的内容传输和分发服务的技术。CDN通过将内容存储在离用户最近的节点服务器上,减少了用户请求的网络延迟和带宽消耗,提高了内容的传输速度和用户体验。简单来说就是将内容缓存在终端用户附近。
需求分析
当我们去请求一个图片或者视频的时候,有时候会出现访问失败的问题,导致想要的资源获取不到。这时候就可以用到CDN了。对于资源的服务器来说我们可以把用户访问的域名注册到CDN中,这时候当我们在去访问资源的时候CDN会从我们距离我们最近访问最快的节点给我们返回资源,如果缓存服务器中没有我们想要的资源,那么缓存服务器会去请求原服务器,请求到资源之后保存到缓存服务器中,然后返回给用户,当用户第二次请求的时候就会从缓存服务器中直接返回了。
核心功能
- 「内容加速 」:CDN通过将内容缓存在全球各地的节点服务器上,使用户可以从离自己最近的节点服务器获取内容,减少了网络延迟,提高了内容传输速度。
- 「 负载均衡 」:CDN通过在全球各地建立分布式节点服务器,可以将用户请求分散到不同的节点服务器上,实现负载均衡,提高了服务器的处理能力和可靠性。
- 「 高可用性 」:多个应用程序可以共享同一份配置信息,避免了配置信息的重复存储和管理。通过配置的分组和命名空间,可以实现不同应用程序之间的配置隔离和共享。
代码实现
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 "新资源添加成功!"; } }
总结提升
CDN是一种通过在全球建立分布式节点服务器,将内容缓存到离用户最近的节点上,提供快速、高效的内容传输和分发服务的技术。CDN通过加速内容传输、负载均衡、提高可用性、增强安全性等功能,提供了更好的用户体验和网站性能。随着互联网的发展,CDN将继续发挥重要作用,为用户提供更快速、可靠的内容访问服务。
🎯 此文章对你有用的话记得留言+点赞+收藏哦🎯