Author: Zhai Helong
In the computer field, one of the first principles that should be considered when it comes to performance optimization actions is Using cache, a reasonable data caching mechanism can bring the following benefits:
1. Shorten the data acquisition path, cache hotspot data nearby for subsequent quick reading, thereby significantly improving processing efficiency;
2. Reduce the frequency of remote data acquisition, ease the pressure on back-end data services, and reduce the cost of network bandwidth between the front-end and back-end;
From the multi-level cache design of CPU hardware, to the rapid display of pages by browsers, to the popular commercial products such as CDN and cloud storage gateways, the caching concept is applied everywhere.
In the public network field, the caching mechanisms of mature products such as operating systems, browsers, and mobile APPs have greatly eliminated the problems faced by network providers such as China Telecom, China Mobile, China Unicom, and content providers. Providers such as major portal platforms and CDN vendors are facing service pressure. Only the operator's DNS can calmly face hundreds of millions of DNS resolutions per second, the network equipment cluster can easily bear the Tbit-level Internet bandwidth per second, and the CDN platform can quickly Handle billions of requests per second.
Faced with the company's current huge and growing domain name access scale, the author's team is constantly optimizing the cluster architecture and improving the performance of DNS software. It also urgently needs to promote various Optimize the domain name resolution request mechanism in a client-like environment. Therefore, we specially organized team members to research and write this guide article in order to give reasonable suggestions to the front-end development and operation personnel of the company, customers and partners, and optimize the overall DNS request. processes to increase business efficiency.
This article mainly discusses how to implement DNS resolution record caching locally on the client under different business and development language backgrounds. At the same time, based on the author's team's understanding of DNS itself and the company's network environment, Given some additional measures, ultimately working towards normalizing DNS resolution requests on the client side.
The client mentioned in this article generally refers to all objects that actively initiate network requests, including but Not limited to servers, PCs, mobile terminals, operating systems, command line tools, scripts, service software, user APPs, etc.
Domain Name System (Server/Service), domain name system (server/service), can be understood as a type of database service;
The client relies on the IP address to identify each other when communicating with the server. As a user of the client, it is difficult for humans to remember a large number of IP addresses, so easy-to-remember domain names such as www. jd.com, stores the mapping relationship between domain name and IP address in DNS for client query;
The client can only obtain the IP of the server by initiating a domain name resolution request to DNS After obtaining the address, you can initiate a network communication request to the IP address and truly obtain the service or content carried by the domain name.
Reference: Domain Name SystemDomain Name Resolution Process
Local DNS, local domain name server; public The network access environment is usually automatically assigned by the network provider (the provider has control and can even perform DNS hijacking, that is, tampering with the IP obtained from domain name resolution), and the intranet environment is automatically assigned by the IT department;
Usually Unix, Unix-like, and MacOS systems can check their own LDNS through /etc/resolv.conf. After the nameserver, it is stated that this file also supports user self-editing and modification to specify LDNS, such as what is common on the public network. Public DNS such as Google DNS, 114DNS, etc.; in a pure intranet environment, it is usually not recommended to modify it without consulting the IT department, which may cause the service to be unavailable; please refer to man resolv.conf Command result.
When domain name resolution is abnormal, the possibility of LDNS service abnormality or resolution hijacking should also be considered.
Reference: Modify TCP/IP settings (including DNS) in windows system;
DNS system can Dynamically provides the mapping relationship between domain names and IPs. The hosts file, which is common in various operating systems, is a static record file of the mapping relationship between domain names and IPs. Usually hosts records take precedence over DNS resolution, that is, when there is no local cache or cache misses. , the corresponding domain name record will be queried through hosts first. If there is no relevant mapping for hosts, DNS requests will continue to be initiated. For the control of this logic in the Linux environment, please refer to the C/C language DNS cache introduction section below.
So in actual work, the above default features are often used to write the mapping relationship between a specific domain name and a specific IP into the hosts file (commonly known as "fixed hosts"), which is used to bypass the DNS resolution process and map the target IP For targeted access (the effect is the same as curl's -x option, or wget's -e specified proxy option);
Time-To -Live, survival time value, this concept is applicable in many fields and may have different meanings.
The TTL descriptions involved in this article are all for data caching. It can be understood directly as the "validity period" of cached data. It starts from the time when the data is cached and exists in the cache for more than Data with a duration specified by TTL is considered expired data. When the data is called again, the validity will be confirmed or re-obtained immediately from the authoritative data source.
Because the caching mechanism is usually passively triggered and updated, if the back-end original authoritative data changes during the client's cache validity period, the client will not perceive it, and the performance will be certain in the business. There is a certain degree of data update delay and temporary inconsistency between cached data and authoritative data.
For the cache TTL of client-side DNS records, we recommend a value of 60s; at the same time, if it is a low-sensitivity business such as testing, or a business with infrequent domain name resolution adjustments, it can be extended appropriately. , even reaching the hour or day level;
The following survey results are recommended for developers to refer to in order to implement self-developed client DNS caching. Each development language may have different support for DNS caching. Let’s analyze them one by one here.
(1) glibc’s getaddrinfo function
The glibc library in Linux environment provides two Domain name resolution functions: gethostbyname function and getaddrinfo function. gethostbyname was a commonly used function in the past, but with the shift to IPv6 and threaded programming models, getaddrinfo becomes more useful because it not only parses IPv6 addresses but is also thread-safe. It is recommended to use getaddrinfo function.
Function prototype:
int getaddrinfo( const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
getaddrinfo function is a relatively low-level basic library function. Many domain name resolution functions in development languages rely on this function. Therefore, we introduce the processing logic of this function here. Trace this function system call through the strace command.
1) Find the nscd cache (see below for an introduction to nscd)
We are in a linux environment Through the strace command, you can see the following system call
//连接nscd socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3 connect(3, {sa_family=AF_LOCAL, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory) close(3)
Connect to the nscd service to query the DNS cache through the unix socket interface "/var/run/nscd/socket".
2) Query the /etc/hosts file
If the nscd service is not started or the cache misses, continue to query the hosts file and we should be able to See the following system call
//读取 hosts 文件 open("/etc/host.conf", O_RDONLY)= 3 fstat(3, {st_mode=S_IFREG|0644, st_size=9, ...}) = 0 ... open("/etc/hosts", O_RDONLY|O_CLOEXEC)= 3 fcntl(3, F_GETFD) = 0x1 (flags FD_CLOEXEC) fstat(3, {st_mode=S_IFREG|0644, st_size=178, ...}) = 0
3) Query the DNS service
Query the DNS server from the /etc/resolv.conf configuration ( nameserver), and then perform a DNS query to obtain the resolution results. We can see the following system call
//获取 resolv.conf 中 DNS 服务 IP open("/etc/resolv.conf", O_RDONLY)= 3 fstat(3, {st_mode=S_IFREG|0644, st_size=25, ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fef2abee000 read(3, "nameserver 114.114.114.114nn", 4096) = 25 ... //连到 DNS 服务,开始 DNS 查询 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("114.114.114.114")}, 16) = 0 poll([{fd=3, events=POLLOUT}], 1, 0)= 1 ([{fd=3, revents=POLLOUT}])
. Regarding whether the client should first search the /etc/hosts file or first obtain the DNS server from /etc/resolv.conf for query and resolution, It is controlled by /etc/nsswitch.conf:
#/etc/nsswitch.conf 部分配置 ... #hosts: db files nisplus nis dns hosts:files dns ...
You can actually see through the strace command that after the system calls nscd socket and before reading /etc/resolv.conf, it will be read This file
newfstatat(AT_FDCWD, "/etc/nsswitch.conf", {st_mode=S_IFREG|0644, st_size=510, ...}, 0) = 0 ... openat(AT_FDCWD, "/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3
4) Verification
#include <sys/socket.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <unistd.h> int gethostaddr(char * name); int main(int argc, char *argv[]){ if (argc != 2) { fprintf(stderr, "%s $host", argv[0]); return -1; } int i = 0; for(i = 0; i < 5; i++) { int ret = -1; ret = gethostaddr(argv[1]); if (ret < 0) { fprintf(stderr, "%s $host", argv[0]); return -1; } //sleep(5); } return 0; } int gethostaddr(char* name){ struct addrinfo hints; struct addrinfo *result; struct addrinfo *curr; int ret = -1; char ipstr[INET_ADDRSTRLEN]; struct sockaddr_in*ipv4; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; ret = getaddrinfo(name, NULL, &hints, &result); if (ret != 0) { fprintf(stderr, "getaddrinfo: %sn", gai_strerror(ret)); return ret; } for (curr = result; curr != NULL; curr = curr->ai_next) { ipv4 = (struct sockaddr_in *)curr->ai_addr; inet_ntop(curr->ai_family, &ipv4->sin_addr, ipstr, INET_ADDRSTRLEN); printf("ipaddr:%sn", ipstr); } freeaddrinfo(result); return 0; }
In summary, the getaddrinfo function combined with nscd can realize DNS caching.
(2) Domain name resolution function of libcurl library
libcurl library is a network transmission library commonly used by clients in c/c language , the curl command is implemented based on this library. This library also calls the getaddrinfo library function to implement DNS domain name resolution, and also supports nscd DNS caching.
int Curl_getaddrinfo_ex(const char *nodename, const char *servname, const struct addrinfo *hints, Curl_addrinfo **result) { ... error = getaddrinfo(nodename, servname, hints, &aihead); if(error) return error; ... }
The Java language is the main language used for the development of business systems in many companies. By writing a simple HTTP client program, test and verify whether the Java network library supports DNS caching. . The test verifies the two components HttpURLConnection and Apache httpcomponents-client in the Java standard library.
(1) Java standard library HttpURLConnection
import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; public class HttpUrlConnectionDemo { public static void main(String[] args) throws Exception { String urlString = "http://example.my.com/"; int num = 0; while (num < 5) { URL url = new URL(urlString); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setDoOutput(true); OutputStream os = conn.getOutputStream(); os.flush(); os.close(); if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { InputStream is = conn.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } System.out.println("rsp:" + sb.toString()); } else { System.out.println("rsp code:" + conn.getResponseCode()); } num++; } } }
测试结果显示 Java 标准库 HttpURLConnection 是支持 DNS 缓存,5 次请求中只有一次 DNS 请求。
(2)Apache httpcomponents-client
import java.util.ArrayList; import java.util.List; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.message.BasicNameValuePair; public class QuickStart { public static void main(final String[] args) throws Exception { int num = 0; while (num < 5) { try (final CloseableHttpClient httpclient = HttpClients.createDefault()) { final HttpGet httpGet = new HttpGet("http://example.my.com/"); try (final CloseableHttpResponse response1 = httpclient.execute(httpGet)) { System.out.println(response1.getCode() + " " + response1.getReasonPhrase()); final HttpEntity entity1 = response1.getEntity(); EntityUtils.consume(entity1); } } num++; } } }
测试结果显示 Apache httpcomponents-client 支持 DNS 缓存,5 次请求中只有一次 DNS 请求。
从测试中发现 Java 的虚拟机实现一套 DNS 缓存,即实现在 java.net.InetAddress 的一个简单的 DNS 缓存机制,默认为缓存 30 秒,可以通过 networkaddress.cache.ttl 修改默认值,缓存范围为 JVM 虚拟机进程,也就是说同一个 JVM 进程中,30秒内一个域名只会请求DNS服务器一次。同时 Java 也是支持 nscd 的 DNS 缓存,估计底层调用 getaddrinfo 函数,并且 nscd 的缓存级别比 Java 虚拟机的 DNS 缓存高。
# 默认缓存 ttl 在 jre/lib/security/java.security 修改,其中 0 是不缓存,-1 是永久缓存 networkaddress.cache.ttl=10 # 这个参数 sun.net.inetaddr.ttl 是以前默认值,目前已经被 networkaddress.cache.ttl 取代
随着云原生技术的发展,Go 语言逐渐成为云原生的第一语言,很有必要验证一下 Go 的标准库是否支持 DNS 缓存。通过我们测试验证发现 Go 的标准库 net.http 是不支持 DNS 缓存,也是不支持 nscd 缓存,应该是没有调用 glibc 的库函数,也没有实现类似 getaddrinfo 函数的功能。这个跟 Go语言的自举有关系,Go 从 1.5 开始就基本全部由 Go(.go) 和汇编 (.s) 文件写成的,以前版本的 C(.c) 文件被全部重写。不过有一些第三方 Go 版本 DNS 缓存库,可以自己在应用层实现,还可以使用 fasthttp 库的 httpclient。
(1)标准库net.http
package main import ( "flag" "fmt" "io/ioutil" "net/http" "time" ) var httpUrl string func main() { flag.StringVar(&httpUrl, "url", "", "url") flag.Parse() getUrl := fmt.Sprintf("http://%s/", httpUrl) fmt.Printf("url: %sn", getUrl) for i := 0; i < 5; i++ { _, buf, err := httpGet(getUrl) if err != nil { fmt.Printf("err: %vn", err) return } fmt.Printf("resp: %sn", string(buf)) time.Sleep(10 * time.Second)# 等待10s发起另一个请求 } } func httpGet(url string) (int, []byte, error) { client := createHTTPCli() resp, err := client.Get(url) if err != nil { return -1, nil, fmt.Errorf("%s err [%v]", url, err) } defer resp.Body.Close() buf, err := ioutil.ReadAll(resp.Body) if err != nil { return resp.StatusCode, buf, err } return resp.StatusCode, buf, nil } func createHTTPCli() *http.Client { readWriteTimeout := time.Duration(30) * time.Second tr := &http.Transport{ DisableKeepAlives: true,//设置短连接 IdleConnTimeout: readWriteTimeout, } client := &http.Client{ Timeout: readWriteTimeout, Transport: tr, } return client }
从测试结果来看,net.http 每次都去 DNS 查询,不支持 DNS 缓存。
(2)fasthttp 库
fasthttp 库是 Go 版本高性能 HTTP 库,通过极致的性能优化,性能是标准库 net.http 的 10 倍,其中一项优化就是支持 DNS 缓存,我们可以从其源码看到
//主要在fasthttp/tcpdialer.go中 type TCPDialer struct { ... // This may be used to override DNS resolving policy, like this: // var dialer = &fasthttp.TCPDialer{ //Resolver: &net.Resolver{ //PreferGo: true, //StrictErrors: false, //Dial: func (ctx context.Context, network, address string) (net.Conn, error) { //d := net.Dialer{} //return d.DialContext(ctx, "udp", "8.8.8.8:53") //}, //}, // } Resolver Resolver // DNSCacheDuration may be used to override the default DNS cache duration (DefaultDNSCacheDuration) DNSCacheDuration time.Duration ... }
可以参考如下方法使用 fasthttp client 端
func main() { // You may read the timeouts from some config readTimeout, _ := time.ParseDuration("500ms") writeTimeout, _ := time.ParseDuration("500ms") maxIdleConnDuration, _ := time.ParseDuration("1h") client = &fasthttp.Client{ ReadTimeout: readTimeout, WriteTimeout:writeTimeout, MaxIdleConnDuration: maxIdleConnDuration, NoDefaultUserAgentHeader:true, // Don't send: User-Agent: fasthttp DisableHeaderNamesNormalizing: true, // If you set the case on your headers correctly you can enable this DisablePathNormalizing:true, // increase DNS cache time to an hour instead of default minute Dial: (&fasthttp.TCPDialer{ Concurrency:4096, DNSCacheDuration: time.Hour, }).Dial, } sendGetRequest() sendPostRequest() }
(3)第三方DNS缓存库
这个是 github 中的一个 Go 版本 DNS 缓存库
可以参考如下代码,在HTTP库中支持DNS缓存
r := &dnscache.Resolver{} t := &http.Transport{ DialContext: func(ctx context.Context, network string, addr string) (conn net.Conn, err error) { host, port, err := net.SplitHostPort(addr) if err != nil { return nil, err } ips, err := r.LookupHost(ctx, host) if err != nil { return nil, err } for _, ip := range ips { var dialer net.Dialer conn, err = dialer.DialContext(ctx, network, net.JoinHostPort(ip, port)) if err == nil { break } } return }, }
(1)requests 库
#!/bin/python import requests url = 'http://example.my.com/' num = 0 while num < 5: headers={"Connection":"close"} # 开启短连接 r = requests.get(url,headers = headers) print(r.text) num +=1
(2)httplib2 库
#!/usr/bin/env python import httplib2 http = httplib2.Http() url = 'http://example.my.com/' num = 0 while num < 5: loginHeaders={ 'User-Agent': 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Maxthon/4.0 Chrome/30.0.1599.101 Safari/537.36', 'Connection': 'close'# 开启短连接 } response, content = http.request(url, 'GET', headers=loginHeaders) print(response) print(content) num +=1
(3)urllib2 库
#!/bin/python import urllib2 import cookielib httpHandler = urllib2.HTTPHandler(debuglevel=1) httpsHandler = urllib2.HTTPSHandler(debuglevel=1) opener = urllib2.build_opener(httpHandler, httpsHandler) urllib2.install_opener(opener) loginHeaders={ 'User-Agent': 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Maxthon/4.0 Chrome/30.0.1599.101 Safari/537.36', 'Connection': 'close' # 开启短连接 } num = 0 while num < 5: request=urllib2.Request('http://example.my.com/',headers=loginHeaders) response = urllib2.urlopen(request) page='' page= response.read() print response.info() print page num +=1
Python 测试三种库都是支持 nscd 的 DNS 缓存的(推测底层也是调用 getaddrinfo 函数),以上测试时使用 HTTP 短连接,都在 python2 环境测试。
针对 HTTP 客户端来说,可以优先开启 HTTP 的 keep-alive 模式,可以复用 TCP 连接,这样可以减少 TCP 握手耗时和重复请求域名解析,然后再开启 nscd 缓存,除了 Go 外,C/C++、Java、Python 都可支持 DNS 缓存,减少 DNS查询耗时。
这里只分析了常用 C/C++、Java、Go、Python 语言,欢迎熟悉其他语言的小伙伴补充。
在由于某些特殊原因,自研或非自研客户端本身无法提供 DNS 缓存支持的情况下,建议管理人员在其所在系统环境中部署DNS缓存程序;
现介绍 Unix/类 Unix 系统适用的几款常见轻量级 DNS 缓存程序。而多数桌面操作系统如 Windows、MacOS 和几乎所有 Web 浏览器均自带 DNS 缓存功能,本文不再赘述。
P.S. DNS 缓存服务请务必确保随系统开机启动;
name service cache daemon 即装即用,通常为 linux 系统默认安装,相关介绍可参考其 manpage:man nscd;man nscd.conf
(1)安装方法:通过系统自带软件包管理程序安装,如 yum install nscd
(2)缓存管理(清除):
1.service nscd restart 重启服务清除所有缓存;
2.nscd -i hosts 清除 hosts 表中的域名缓存(hosts 为域名缓存使用的 table 名称,nscd 有多个缓存 table,可参考程序相关 manpage)
较为轻量,可选择其作为 nscd 替代,通常需单独安装
(1)安装方法:通过系统自带软件包管理程序安装,如 yum install dnsmasq
(2)核心文件介绍(基于 Dnsmasq version 2.86,较低版本略有差异,请参考对应版本文档如 manpage 等)
(3)/etc/default/dnsmasq 提供六个变量定义以支持六种控制类功能
(4)/etc/dnsmasq.d/ 此目录含 README 文件,可参考;目录内可以存放自定义配置文件
(5)/etc/dnsmasq.conf 主配置文件,如仅配置 dnsmasq 作为缓存程序,可参考以下配置
listen-address=127.0.0.1#程序监听地址,务必指定本机内网或回环地址,避免暴露到公网环境 port=53 #监听端口 resolv-file=/etc/dnsmasq.d/resolv.conf#配置dnsmasq向自定义文件内的 nameserver 转发 dns 解析请求 cache-size=150#缓存记录条数,默认 150 条,可按需调整、适当增大 no-negcache #不缓存解析失败的记录,主要是 NXDOMAIN,即域名不存在 log-queries=extra #开启日志记录,指定“=extra”则记录更详细信息,可仅在问题排查时开启,平时关闭 log-facility=/var/log/dnsmasq.log #指定日志文件 #同时需要将本机 /etc/resolv.conf 第一个 nameserver 指定为上述监听地址,这样本机系统的 dns 查询请求才会通过 dnsmasq 代为转发并缓存响应结果。 #另 /etc/resolv.conf 务必额外配置 2 个 nameserver,以便 dnsmasq 服务异常时支持系统自动重试,注意 resolv.conf 仅读取前 3 个 nameserver
(6)缓存管理(清除):
1.kill -s HUP `pidof dnsmasq` 推荐方式,无需重启服务
2.kill -s TERM `pidof dnsmasq` 或 service dnsmasq stop
3.service dnsmasq force-reload 或 service dnsmasq restart
(7)官方文档:https://thekelleys.org.uk/dnsmasq/doc.html
以 linux 操作系统为例,常用的网络请求命令行工具常常通过调用 getaddrinfo() 完成域名解析过程,如 ping、telnet、curl、wget 等,但其可能出于通用性的考虑,均被设计为对同一个域名每次解析会发起两个请求,分别查询域名 A 记录(即 IPV4 地址)和 AAAA 记录(即 IPV6 地址)。
因目前大部分公司的内网环境及云上内网环境还未使用 ipv6 网络,故通常 DNS 系统不为内网域名添加 AAAA 记录,徒劳请求域名的 AAAA 记录会造成前端应用和后端 DNS 服务不必要的资源开销。因此,仅需请求内网域名的业务,如决定自研客户端,建议开发人员视实际情况,可将其设计为仅请求内网域名 A 记录,尤其当因故无法实施本地缓存机制时。
客户端需严格规范域名/主机名的处理逻辑,避免产生大量对不存在域名的解析请求(确保域名从权威渠道获取,避免故意或意外使用随机构造的域名、主机名),因此类请求的返回结果(NXDOMAIN)通常不被缓存或缓存时长较短,且会触发客户端重试,对后端 DNS 系统造成一定影响。
The above is the detailed content of DNS cache configuration recommendations for each development language. For more information, please follow other related articles on the PHP Chinese website!