etcd_selector.go
package clientselector
import (
"errors"
"math/rand"
"net"
"net/rpc"
"net/url"
"strconv"
"strings"
"time"
"golang.org/x/net/context"
"github.com/coreos/etcd/client"
"github.com/smallnest/rpcx"
)
// EtcdClientSelector is used to select a rpc server from etcd.
//etcd 负载均衡器 结构体
type EtcdClientSelector struct {
EtcdServers []string //etcd 客户端连接地址列表
KeysAPI client.KeysAPI //etcd 连接客户端
ticker *time.Ticker //周期执行器
sessionTimeout time.Duration //连接超时时间
BasePath string //should endwith serviceName 服务路径或者服务名称
Servers []string //具体微服务地址列表
Group string //组名
clientAndServer map[string]*rpc.Client //map key 就提微服务地址 value 客户端连接
metadata map[string]string //
Latitude float64 //纬度
Longitude float64 //经度
WeightedServers []*Weighted //权重
SelectMode rpcx.SelectMode //选择器
dailTimeout time.Duration //连接超时时间
rnd *rand.Rand //序列号
currentServer int //当前第几个服务
len int //服务个数
HashServiceAndArgs HashServiceAndArgs //权重选择器
Client *rpcx.Client //rpcx客户端
}
// NewEtcdClientSelector creates a EtcdClientSelector
// 创建EtcdClientSelector结构体
func NewEtcdClientSelector(etcdServers []string, basePath string, sessionTimeout time.Duration, sm rpcx.SelectMode, dailTimeout time.Duration) *EtcdClientSelector {
selector := &EtcdClientSelector{
EtcdServers: etcdServers,
BasePath: basePath,
sessionTimeout: sessionTimeout,
SelectMode: sm,
dailTimeout: dailTimeout,
clientAndServer: make(map[string]*rpc.Client),
metadata: make(map[string]string),
rnd: rand.New(rand.NewSource(time.Now().UnixNano()))}
selector.start()
return selector
}
//SetClient set a Client in order that clientSelector can uses it
//客户端设置
func (s *EtcdClientSelector) SetClient(c *rpcx.Client) {
s.Client = c
}
//SetSelectMode sets SelectMode
//设置重选算法
func (s *EtcdClientSelector) SetSelectMode(sm rpcx.SelectMode) {
s.SelectMode = sm
}
//AllClients returns rpc.Clients to all servers
//直接返回微服务对应的直连客户端集合
func (s *EtcdClientSelector) AllClients(clientCodecFunc rpcx.ClientCodecFunc) []*rpc.Client {
var clients []*rpc.Client
for _, sv := range s.Servers {
ss := strings.Split(sv, "@")
//创建直连的客户端 http kcp (基于udp实现的协议) 或者https 以及golang支持的协议
c, err := rpcx.NewDirectRPCClient(s.Client, clientCodecFunc, ss[0], ss[1], s.dailTimeout)
if err == nil {
clients = append(clients, c)
}
}
return clients
}
//获取一个ETCD客户端
func (s *EtcdClientSelector) start() {
cli, err := client.New(client.Config{
Endpoints: s.EtcdServers,
Transport: client.DefaultTransport,
HeaderTimeoutPerRequest: s.sessionTimeout,
})
if err != nil {
return
}
s.KeysAPI = client.NewKeysAPI(cli)
s.pullServers()
// s.ticker = time.NewTicker(s.sessionTimeout)
// go func() {
// for range s.ticker.C {
// s.pullServers()
// }
// }()
go s.watch()
}
func (s *EtcdClientSelector) watch() {
watcher := s.KeysAPI.Watcher(s.BasePath, &client.WatcherOptions{
Recursive: true,
})
for {
res, err := watcher.Next(context.Background())
if err != nil {
break
}
//services are changed, we pull service again instead of processing single node
if res.Action == "expire" {
s.pullServers()
if !res.Node.Dir {
// clientAndServer delete the invalid client connection
removedServer := strings.TrimPrefix(res.Node.Key, s.BasePath+"/")
delete(s.clientAndServer, removedServer)
}
} else if res.Action == "set" || res.Action == "update" {
s.pullServers()
} else if res.Action == "delete" {
s.pullServers()
}
}
}
//
func (s *EtcdClientSelector) pullServers() {
//获取key对应的
resp, err := s.KeysAPI.Get(context.TODO(), s.BasePath, &client.GetOptions{
Recursive: true,
Sort: true,
})
//
if err == nil && resp.Node != nil {
if len(resp.Node.Nodes) > 0 {
var servers []string
for _, n := range resp.Node.Nodes {
servers = append(servers, strings.TrimPrefix(n.Key, s.BasePath+"/"))
}
s.Servers = servers
s.createWeighted(resp.Node.Nodes)
//set weight based on ICMP result
if s.SelectMode == rpcx.WeightedICMP {
for _, w := range s.WeightedServers {
server := w.Server.(string)
ss := strings.Split(server, "@")
host, _, _ := net.SplitHostPort(ss[1])
rtt, _ := Ping(host)
rtt = CalculateWeight(rtt)
w.Weight = rtt
w.EffectiveWeight = rtt
}
}
s.len = len(s.Servers)
if s.len > 0 {
s.currentServer = s.currentServer % s.len
}
} else {
// when the last instance is down, it should be deleted
s.clientAndServer = map[string]*rpc.Client{}
}
}
}
func (s *EtcdClientSelector) createWeighted(nodes client.Nodes) {
s.WeightedServers = make([]*Weighted, len(s.Servers))
var inactiveServers []int
for i, n := range nodes {
key := strings.TrimPrefix(n.Key, s.BasePath+"/")
s.WeightedServers[i] = &Weighted{Server: key, Weight: 1, EffectiveWeight: 1}
s.metadata[key] = n.Value
if v, err := url.ParseQuery(n.Value); err == nil {
w := v.Get("weight")
state := v.Get("state")
group := v.Get("group")
if (state != "" && state != "active") || (s.Group != group) {
inactiveServers = append(inactiveServers, i)
}
if w != "" {
if weight, err := strconv.Atoi(w); err == nil {
s.WeightedServers[i].Weight = weight
s.WeightedServers[i].EffectiveWeight = weight
}
}
}
}
s.removeInactiveServers(inactiveServers)
}
func (s *EtcdClientSelector) removeInactiveServers(inactiveServers []int) {
i := len(inactiveServers) - 1
for ; i >= 0; i-- {
k := inactiveServers[i]
removedServer := s.Servers[k]
s.Servers = append(s.Servers[0:k], s.Servers[k+1:]...)
s.WeightedServers = append(s.WeightedServers[0:k], s.WeightedServers[k+1:]...)
c := s.clientAndServer[removedServer]
if c != nil {
delete(s.clientAndServer, removedServer)
c.Close() //close connection to inactive server
}
}
}
func (s *EtcdClientSelector) getCachedClient(server string, clientCodecFunc rpcx.ClientCodecFunc) (*rpc.Client, error) {
c := s.clientAndServer[server]
if c != nil {
return c, nil
}
ss := strings.Split(server, "@") //
c, err := rpcx.NewDirectRPCClient(s.Client, clientCodecFunc, ss[0], ss[1], s.dailTimeout)
s.clientAndServer[server] = c
return c, err
}
// Select returns a rpc client
func (s *EtcdClientSelector) Select(clientCodecFunc rpcx.ClientCodecFunc, options ...interface{}) (*rpc.Client, error) {
if s.len == 0 {
return nil, errors.New("No available service")
}
switch s.SelectMode {
case rpcx.RandomSelect:
s.currentServer = s.rnd.Intn(s.len)
server := s.Servers[s.currentServer]
return s.getCachedClient(server, clientCodecFunc)
case rpcx.RoundRobin:
s.currentServer = (s.currentServer + 1) % s.len //not use lock for performance so it is not precise even
server := s.Servers[s.currentServer]
return s.getCachedClient(server, clientCodecFunc)
case rpcx.ConsistentHash:
if s.HashServiceAndArgs == nil {
s.HashServiceAndArgs = JumpConsistentHash
}
s.currentServer = s.HashServiceAndArgs(s.len, options)
server := s.Servers[s.currentServer]
return s.getCachedClient(server, clientCodecFunc)
case rpcx.WeightedRoundRobin, rpcx.WeightedICMP:
server := nextWeighted(s.WeightedServers).Server.(string)
return s.getCachedClient(server, clientCodecFunc)
case rpcx.Closest:
closestServers := getClosestServer(s.Latitude, s.Longitude, s.metadata)
selected := s.rnd.Intn(len(closestServers))
return s.getCachedClient(closestServers[selected], clientCodecFunc)
default:
return nil, errors.New("not supported SelectMode: " + s.SelectMode.String())
}
}
etcd_selector.go的更多相关文章
随机推荐
- obj-c编程15[Cocoa实例01]:一个会发声的随机数生成器
哇!终于到了obj-c编程系列的第15篇喽,一路走过来满不容易的哦!(怎么个意思,这才哪到哪啊!),为了能够更好的练习obj-c在Cocoa框架上的编程,接下来会以N篇Cocoa实例的博文来巩固和记忆 ...
- rails常用命令备忘
rails new xxx 创建一个新rails项目 rails generate scaffold xxx 创建表模型,视图,控制器和迁移的"脚手架" rake db:migra ...
- Java核心技术第四章——2.final 和 static
final实例域 实例域(对象的属性)可修饰为final.修饰为final后,在构建对象时必须初始化这个实例域.若没有在实例域进行初始化,那么必须在每个构造器内初始化这个实例域(否则会编译错误). 表 ...
- JAVA全套学习视频
链接: https://pan.baidu.com/s/1miE7kvQ 密码: jj8x
- Spring 框架的优点及缺点
首先Spring 是一个框架,使用Spring并不代表代码质量的提高,就像盖房子选择用上海的地皮还是北京的地皮一样,房子质量与土地所在的城市无关,与房子的具体设计方案和选料有关. 使用Spring 等 ...
- Eclipse+Resin开发环境迁移中发生的一些问题
换新机器了,系统也从XP升级到64位WIn7.某些旧工具直接无法用了.下面简单谈一下标题的内容 1.非泛型的容器类引入在JDK1.7以下编译好像已经不行了.比如Java.util.ArrayList这 ...
- Nginx 怎么给一台服务器,配置两个域名?详细的解说+截图教程
一. 环境.条件准备 一台云服务器(我的是腾讯的centos7) 至少两个域名.(我的是simuhunluo.xyz和simuhunluo.top.这两个域名之间没有任何关系,我是在阿里 ...
- Java虚拟机-垃圾收集器
垃圾收集器(Garbage Collection, GC)的诞生引导出了三个问题: 哪些内存需要回收? 什么时候回收? 如何回收? 对于线程独占的三个区域(程序计数器.虚拟机栈.本地方法栈)不用过多的 ...
- java数据库基本操作(sqlserver 2000为例)
一.环境搭建 1.下载对应数据库连接驱动包并引入. 2.如果在web中调用必须在tomcat中也放入对应的驱动包. 3.在jre的lib\ext中也加入对应的驱动包. 二.连接数据库 public s ...
- 谣传QQ被黑客DDOS攻击,那么Python如何实现呢?
于2018-5-10日晚 网络流传黑客DDOS攻击了QQ服务器,导致大家聊天发送内容时出现感叹号.我们都知道一般情况下出现感叹号都是你的网络不稳定,或者...别人已经删除你了.然而昨晚很奇怪,发出的内 ...