golang实现高性能ip代理池管理

354 阅读3分钟

功能介绍

最近想用golang实现一个高性能的ip代理池管理的功能。大致包含的功能点如下:

  1. 提供一个录入ip的接口,可以批量的导入ip池进入系统中
  2. 对ip池内的ip进行定时的校验其可用性,无效的丢掉

image.png

实现

先定义ip代理的结构

type Proxy struct {
	IP       string
	Port     int
	Protocol string
}

导数实现

大致的逻辑如下:

  1. 定义一个导入ip池的方法,可以批量分页的将大量的ip信息输入到项目内
  2. 通过导入ip的方法获得的ip信息直接放到一个带有缓存的待校验的channel里
  3. 从待校验的channel里挨个取出ip信息进行有效性校验,校验成功的再放入可用IP的channel里,失败的标记ip失效
  4. 从可用ip的channel里取出ip信息返回给调用方

下面看代码:

定义Importer

type Importer func(page int, size int) ([]Proxy, error)

再定义一些全局变量

// 实例化的Importer
var importer Importer
// 待校验的ip池
var uncheckIpPool chan Proxy
// 可用Ip池
var availableIpPool chan Proxy

执行导入

func importerRun() {
	page, size := 1, 20
	for {
		// 获取ip信息
		proxies, err := importer(page, size)
		if err != nil {
			continue
		}
		if len(proxies) < size {
			// 不够一页了,从头开始取
			page = 1
		} else {
			page++
		}
		
		// 将数据写入待校验的ip池
		for _, v := range proxies {
			uncheckIpPool <- v
		}
	}
}

校验实现

对待校验的ip池内的数据进行校验,成功后放入可用ip池。

// checkRun 执行校验
func checkRun() {
	for {
		p := <-uncheckIpPool
		now := time.Now().Unix()
		if now < p.CheckTimestamp+300 {
			log.Printf("%s 校验未过期", p.String())
			availableIpPool <- p
		}
		if check(p) {
			p.CheckTimestamp = time.Now().Unix()
			availableIpPool <- p
		}
	}
}

// check 校验
func check(p Proxy) bool {
	urlPath := fmt.Sprintf("http://%s:%d", p.IP, p.Port)
	proxyUrl, err := url.Parse(urlPath)
	if err != nil {
		log.Println("Error parsing proxy URL:", err)
		return false
	}
	log.Printf("check %s \n", urlPath)
	// 创建一个Transport对象
	transport := &http.Transport{
		Proxy: http.ProxyURL(proxyUrl),
	}

	// 创建一个http.Client对象并设置Transport
	client := &http.Client{
		Transport: transport,
	}

	// 创建一个GET请求
	req, err := http.NewRequest("GET", "http://www.baidu.com", nil)
	if err != nil {
		log.Println("Error creating request:", err)
		return false
	}

	// 发送请求并获取响应
	resp, err := client.Do(req)
	if err != nil {
		log.Println("Error sending request:", err)
		return false
	}
	defer resp.Body.Close()

	res := resp.StatusCode == http.StatusOK
	log.Println(res)
	return res 
}

取数实现

取数就比较简单了,直接从可用ip池里拿一个就行

func GetProxy() Proxy {
	p := <-availableIpPool
	return p
}

初始化

在调用上面的一些方法之前要先对包进行初始化

初始化的配置结构如下:

type ProxyConfig struct {
	Importer            Importer
	UncheckIpPoolSize   int
	AvailableIpPoolSize int
}

初始化方法

func InitPool(cfg ProxyConfig) error {
	if cfg.Importer == nil {
		return fmt.Errorf("importer 不能为空")
	}
	importer = cfg.Importer
	// 初始化可用ip池缓存
	availableIpPoolSize := cfg.AvailableIpPoolSize
	if cfg.AvailableIpPoolSize <= 0 {
		availableIpPoolSize = 100
	}
	availableIpPool = make(chan Proxy, availableIpPoolSize)

	// 初始化待校验ip池缓存
	uncheckIpPoolSize := cfg.UncheckIpPoolSize
	if uncheckIpPoolSize <= 0 {
		uncheckIpPoolSize = 100
	}
	uncheckIpPool = make(chan Proxy, uncheckIpPoolSize)
	go importerRun()
	go checkRun()
	return nil
}

测试

实现已经完成了,就做个简单的测试吧

使用gin框架写个简单接口,输出一个可用的代理ip

package main

import (
	"github.com/gin-gonic/gin"
	"iproxy/core"
	"log"
)

// ip池
var proxies = []core.Proxy{
	core.NewProxy("43.138.20.156", 80),
	core.NewProxy("120.234.203.171", 9002),
	core.NewProxy("68.79.25.157", 8089),
	core.NewProxy("180.103.127.9", 7890),
	core.NewProxy("27.185.0.164", 999),
	core.NewProxy("182.92.73.106", 80),
	core.NewProxy("182.106.220.252", 9091),
	core.NewProxy("39.107.33.254", 8090),
}

func main() {
	cfg := core.ProxyConfig{
		UncheckIpPoolSize:   5,
		AvailableIpPoolSize: 5,
		Importer:            getImporter(),
	}
	err := core.InitPool(cfg)
	if err != nil {
		log.Printf("core.InitPool error:%+v \n", err)
		return
	}
	router := gin.Default()
	router.GET("/get_proxy", func(context *gin.Context) {
		ip := core.GetProxy()
		log.Printf("get_proxy result: %s \n", ip.String())
		context.JSONP(200, gin.H{
			"code":    200,
			"success": true,
			"data":    core.GetProxy(),
		})
	})
	router.Run(":9090")
}

func getImporter() core.Importer {
	return func(page int, size int) ([]core.Proxy, error) {
		return proxies, nil
	}
}

执行效果如下图:

image.png 可以看到,接口的耗时不超过1毫秒,效率非常高,因为所有的取数逻辑,都是纯内存操作.

本文为原创,转载请注明出处: golang实现高性能ip代理池管理