目录
- 追本溯源
- 概念说明
- 需求分析
- 核心功能
- 代码实现
- A用户模块
- 本地DNS模块
- CDN服务模块
- 缓存服务模块
- 原服务端
- 总结提升
追本溯源
CDN的发展源于对互联网内容传输速度和用户体验的需求。在互联网发展初期,内容的传输主要依赖于源服务器,但随着互联网用户规模的快速增长,单一服务器的容量和带宽很难满足全球用户的需求。为了解决这一问题,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 "新资源添加成功!";
}
}