victoriaMetrics中的一些Sao操作

快速获取当前时间

victoriaMetrics中有一个fasttime库,用于快速获取当前的Unix时间,实现其实挺简单,就是在后台使用一个goroutine不断以1s为周期刷新表示当前时间的变量currentTimestamp,获取的时候直接原子加载该变量即可。其性能约是time.Now()的8倍。

其核心方式就是将主要任务放到后台运行,通过一个中间变量来传递运算结果,以此来通过异步的方式提升性能,但需要业务能包容一定的精度偏差。

func init() {
go func() {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for tm := range ticker.C {
t := uint64(tm.Unix())
atomic.StoreUint64(&currentTimestamp, t)
}
}()
} var currentTimestamp = uint64(time.Now().Unix()) // UnixTimestamp returns the current unix timestamp in seconds.
//
// It is faster than time.Now().Unix()
func UnixTimestamp() uint64 {
return atomic.LoadUint64(&currentTimestamp)
}

计算结构体的哈希值

hashUint64函数中使用xxhash.Sum64计算了结构体Key的哈希值。通过unsafe.Pointer将指针转换为*[]byte类型,byte数组的长度为unsafe.Sizeof(*k)unsafe.Sizeof()返回结构体的字节大小。

如果一个数据为固定的长度,如h的类型为uint64,则可以直接指定长度为8进行转换,如:bp:=([8]byte)(unsafe.Pointer(&h))

需要注意的是unsafe.Sizeof()返回的是数据结构的大小而不是其指向内容的数据大小,如下返回的slice大小为24,为slice首部数据结构SliceHeader的大小,而不是其引用的数据大小(可以使用len获取slice引用的数据大小)。此外如果结构体中有指针,则转换成的byte中存储的也是指针存储的地址。

slice := []int{1,2,3,4,5,6,7,8,9,10}
fmt.Println(unsafe.Sizeof(slice)) //24
type Key struct {
Part interface{}
Offset uint64
} func (k *Key) hashUint64() uint64 {
buf := (*[unsafe.Sizeof(*k)]byte)(unsafe.Pointer(k))
return xxhash.Sum64(buf[:])
}

将字符串添加到已有的[]byte中

使用如下方式即可:

str := "1231445"
arr := []byte{1, 2, 3}
arr = append(arr, str...)

将int64的数组转换为byte数组

直接操作了底层的SliceHeader

func int64ToByteSlice(a []int64) (b []byte) {
sh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
sh.Data = uintptr(unsafe.Pointer(&a[0]))
sh.Len = len(a) * int(unsafe.Sizeof(a[0]))
sh.Cap = sh.Len
return
}

并发访问的sync.WaitGroup

并发访问的sync.WaitGroup的目的是为了在运行时添加需要等待的goroutine

// WaitGroup wraps sync.WaitGroup and makes safe to call Add/Wait
// from concurrent goroutines.
//
// An additional limitation is that call to Wait prohibits further calls to Add
// until return.
type WaitGroup struct {
sync.WaitGroup
mu sync.Mutex
} // Add registers n additional workers. Add may be called from concurrent goroutines.
func (wg *WaitGroup) Add(n int) {
wg.mu.Lock()
wg.WaitGroup.Add(n)
wg.mu.Unlock()
} // Wait waits until all the goroutines call Done.
//
// Wait may be called from concurrent goroutines.
//
// Further calls to Add are blocked until return from Wait.
func (wg *WaitGroup) Wait() {
wg.mu.Lock()
wg.WaitGroup.Wait()
wg.mu.Unlock()
} // WaitAndBlock waits until all the goroutines call Done and then prevents
// from new goroutines calling Add.
//
// Further calls to Add are always blocked. This is useful for graceful shutdown
// when other goroutines calling Add must be stopped.
//
// wg cannot be used after this call.
func (wg *WaitGroup) WaitAndBlock() {
wg.mu.Lock()
wg.WaitGroup.Wait() // Do not unlock wg.mu, so other goroutines calling Add are blocked.
} // There is no need in wrapping WaitGroup.Done, since it is already goroutine-safe.

时间池

高频次创建timer会消耗一定的性能,为了减少某些情况下的性能损耗,可以使用sync.Pool来回收利用创建的timer

// Get returns a timer for the given duration d from the pool.
//
// Return back the timer to the pool with Put.
func Get(d time.Duration) *time.Timer {
if v := timerPool.Get(); v != nil {
t := v.(*time.Timer)
if t.Reset(d) {
logger.Panicf("BUG: active timer trapped to the pool!")
}
return t
}
return time.NewTimer(d)
} // Put returns t to the pool.
//
// t cannot be accessed after returning to the pool.
func Put(t *time.Timer) {
if !t.Stop() {
// Drain t.C if it wasn't obtained by the caller yet.
select {
case <-t.C:
default:
}
}
timerPool.Put(t)
} var timerPool sync.Pool

访问限速

victoriaMetrics的vminsert作为vmagentvmstorage之间的组件,接收vmagent的流量并将其转发到vmstorage。在vmstorage卡死、处理过慢或下线的情况下,有可能会导致无法转发流量,进而造成vminsert CPU和内存飙升,造成组件故障。为了防止这种情况,vminsert使用了限速器,当接收到的流量激增时,可以在牺牲一部分数据的情况下保证系统的稳定性。

victoriaMetrics的源码中对限速器有如下描述:

Limit the number of conurrent f calls in order to prevent from excess memory usage and CPU thrashing

限速器使用了两个参数:maxConcurrentInsertsmaxQueueDuration,前者给出了突发情况下可以处理的最大请求数,后者给出了某个请求的最大超时时间。需要注意的是Do(f func() error)是异步执行的,而ch又是全局的,因此会异步等待其他请求释放资源(struct{})。

可以看到限速器使用了指标来指示当前的限速状态。同时使用cgroup.AvailableCPUs()*4 (即runtime.GOMAXPROCS(-1)*4)来设置默认的maxConcurrentInserts长度。

当该限速器用在处理如http请求时,该限速器并不能限制底层上送的请求,其限制的是对请求的处理。在高流量业务处理中,这也是最消耗内存的地方,通常包含数据读取、内存申请拷贝等。底层的数据受/proc/sys/net/core/somaxconn和socket缓存区的限制。

var (
maxConcurrentInserts = flag.Int("maxConcurrentInserts", cgroup.AvailableCPUs()*4, "The maximum number of concurrent inserts. Default value should work for most cases, "+
"since it minimizes the overhead for concurrent inserts. This option is tigthly coupled with -insert.maxQueueDuration")
maxQueueDuration = flag.Duration("insert.maxQueueDuration", time.Minute, "The maximum duration for waiting in the queue for insert requests due to -maxConcurrentInserts")
) // ch is the channel for limiting concurrent calls to Do.
var ch chan struct{} // Init initializes concurrencylimiter.
//
// Init must be called after flag.Parse call.
func Init() {
ch = make(chan struct{}, *maxConcurrentInserts) //初始化limiter,最大突发并行请求量为maxConcurrentInserts
} // Do calls f with the limited concurrency.
func Do(f func() error) error {
// Limit the number of conurrent f calls in order to prevent from excess
// memory usage and CPU thrashing.
select {
case ch <- struct{}{}: //在channel中添加一个元素,表示开始处理一个请求
err := f() //阻塞等大请求处理结束
<-ch //请求处理完之后释放channel中的一个元素,释放出的空间可以用于处理下一个请求
return err
default:
} //如果当前达到处理上限maxConcurrentInserts,则需要等到其他Do(f func() error)释放资源。
// All the workers are busy.
// Sleep for up to *maxQueueDuration.
concurrencyLimitReached.Inc()
t := timerpool.Get(*maxQueueDuration) //获取一个timer,设置等待超时时间为 maxQueueDuration
select {
case ch <- struct{}{}: //在maxQueueDuration时间内等待其他请求释放资源,如果获取到资源,则回收timer,继续处理
timerpool.Put(t)
err := f()
<-
return err
case <-t.C: //在maxQueueDuration时间内没有获取到资源,定时器超时后回收timer,丢弃请求并返回错误信息
timerpool.Put(t)
concurrencyLimitTimeout.Inc()
return &httpserver.ErrorWithStatusCode{
Err: fmt.Errorf("cannot handle more than %d concurrent inserts during %s; possible solutions: "+
"increase `-insert.maxQueueDuration`, increase `-maxConcurrentInserts`, increase server capacity", *maxConcurrentInserts, *maxQueueDuration),
StatusCode: http.StatusServiceUnavailable,
}
}
} var (
concurrencyLimitReached = metrics.NewCounter(`vm_concurrent_insert_limit_reached_total`)
concurrencyLimitTimeout = metrics.NewCounter(`vm_concurrent_insert_limit_timeout_total`) _ = metrics.NewGauge(`vm_concurrent_insert_capacity`, func() float64 {
return float64(cap(ch))
})
_ = metrics.NewGauge(`vm_concurrent_insert_current`, func() float64 {
return float64(len(ch))
})
)

优先级控制

victoriaMetrics的pacelimiter库实现了优先级控制。主要方法由IncDecWaitIfNeeded。低优先级任务需要调用WaitIfNeeded方法,如果此时有高优先级任务(调用Inc方法),则低优先级任务需要等待高优先级任务结束(调用Dec方法)之后才能继续执行。

// PaceLimiter throttles WaitIfNeeded callers while the number of Inc calls is bigger than the number of Dec calls.
//
// It is expected that Inc is called before performing high-priority work,
// while Dec is called when the work is done.
// WaitIfNeeded must be called inside the work which must be throttled (i.e. lower-priority work).
// It may be called in the loop before performing a part of low-priority work.
type PaceLimiter struct {
mu sync.Mutex
cond *sync.Cond
delaysTotal uint64
n int32
} // New returns pace limiter that throttles WaitIfNeeded callers while the number of Inc calls is bigger than the number of Dec calls.
func New() *PaceLimiter {
var pl PaceLimiter
pl.cond = sync.NewCond(&pl.mu)
return &pl
} // Inc increments pl.
func (pl *PaceLimiter) Inc() {
atomic.AddInt32(&pl.n, 1)
} // Dec decrements pl.
func (pl *PaceLimiter) Dec() {
if atomic.AddInt32(&pl.n, -1) == 0 {
// Wake up all the goroutines blocked in WaitIfNeeded,
// since the number of Dec calls equals the number of Inc calls.
pl.cond.Broadcast()
}
} // WaitIfNeeded blocks while the number of Inc calls is bigger than the number of Dec calls.
func (pl *PaceLimiter) WaitIfNeeded() {
if atomic.LoadInt32(&pl.n) <= 0 {
// Fast path - there is no need in lock.
return
}
// Slow path - wait until Dec is called.
pl.mu.Lock()
for atomic.LoadInt32(&pl.n) > 0 {
pl.delaysTotal++
pl.cond.Wait()
}
pl.mu.Unlock()
} // DelaysTotal returns the number of delays inside WaitIfNeeded.
func (pl *PaceLimiter) DelaysTotal() uint64 {
pl.mu.Lock()
n := pl.delaysTotal
pl.mu.Unlock()
return n
}

victoriaMetrics中的一些Sao操作的更多相关文章

  1. JavaScript jQuery 中定义数组与操作及jquery数组操作

    首先给大家介绍javascript jquery中定义数组与操作的相关知识,具体内容如下所示: 1.认识数组 数组就是某类数据的集合,数据类型可以是整型.字符串.甚至是对象Javascript不支持多 ...

  2. IE7中使用Jquery动态操作name问题

    问题:IE7中无法使用Jquery动态操作页面元素的name属性. 在项目中有出现问题,某些客户的机器偶尔会有,后台取不到前台的数据值. 然开发和测试环境总是不能重现问题.坑爹之处就在于此,不能重现就 ...

  3. JavaScript中常见的数组操作函数及用法

    JavaScript中常见的数组操作函数及用法 昨天写了个帖子,汇总了下常见的JavaScript中的字符串操作函数及用法.今天正好有时间,也去把JavaScript中常见的数组操作函数及用法总结一下 ...

  4. JavaScript中常见的字符串操作函数及用法

    JavaScript中常见的字符串操作函数及用法 最近几次参加前端实习生招聘的笔试,发现很多笔试题都会考到字符串的处理,比方说去哪儿网笔试题.淘宝的笔试题等.如果你经常参加笔试或者也是一个过来人,相信 ...

  5. 无法更新 EntitySet“SoreInfo_Table”,因为它有一个 DefiningQuery,而 <ModificationFunctionMapping> 元素中没有支持当前操作的 <InsertFunction> 元素。

    无法更新 EntitySet"SoreInfo_Table",因为它有一个 DefiningQuery,而 <ModificationFunctionMapping> ...

  6. [转]js中confirm实现执行操作前弹出确认框的方法

    原文地址:http://www.jb51.net/article/56986.htm 本文实例讲述了js中confirm实现执行操作前弹出确认框的方法.分享给大家供大家参考.具体实现方法如下: 现在在 ...

  7. 传递给数据库 'master' 中的日志扫描操作的日志扫描号无效

    错误:连接数据库的时候提示:SQL Server 检测到基于一致性的逻辑 I/O 错误 校验和不正确 C:\Documents and Settings\Administrator>" ...

  8. 【转】《深入理解计算机系统》C程序中常见的内存操作有关的典型编程错误

    原文地址:http://blog.csdn.net/slvher/article/details/9150597 对C/C++程序员来说,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构 ...

  9. php大力力 [024节]PHP中的字符串连接操作(2015-08-27)

    2015-08-27 php大力力024.PHP中的字符串连接操作 PHP中的字符串连接操作  阅读:次   时间:2012-03-25 PHP字符串的连接的简单实例 时间:2013-12-30 很多 ...

随机推荐

  1. Dubbo 如何优雅停机?

    Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,所以如果使用 kill -9 PID 等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID 时,才 会执行.

  2. elasticsearch 了解多少,说说你们公司 es 的集群架构,索 引数据大小,分片有多少,以及一些调优手段 ?

    面试官:想了解应聘者之前公司接触的 ES 使用场景.规模,有没有做过比较大 规模的索引设计.规划.调优. 解答: 如实结合自己的实践场景回答即可. 比如:ES 集群架构 13 个节点,索引根据通道不同 ...

  3. spring 事务实现方式有哪些?

    Spring提供了编程式事务和声明式事务两种实现方式, 编程式事务允许用户在代码中精确定义事务的边界, 而声明式事务(基于AOP)有助于用户将操作与事务规则进行解耦. 简单地说,编程式事务侵入到了业务 ...

  4. java-集合-realdo-集合一对多

    school类: package setdone; import java.util.List; public class School { private String schoolname; pr ...

  5. C++ | 简单工厂模式 | 复数计算器

    简单工厂模式最直观的一个应用便是实现一个计算器的程序. 比如,公司让你给计算器添加一个幂运算的功能,你只需要设计一个幂运算的类,并实现幂运算的逻辑,然后让该类继承自运算类即可. 简单工厂模式: 简单工 ...

  6. C++重载输入流、输出流运算符

    在c++中类的私有成员是不能被直接访问的,需要通过类中提供的成员函数简介的操作这些数据.同时C++ 能够使用流提取运算符 >> 和流插入运算符 << 来输入和输出内置的数据类型 ...

  7. C/C++头文件以及避免头文件包含造成的重定义方法

    C 头文件 头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享.有两种类型的头文件:程序员编写的头文件和编译器自带的头文件. 在程序中要使用头文件,需要使用 C 预处 ...

  8. C#和TS/JS的对比学习02:函数与方法

    程序本质上,就是由数据和处理数据的方法构成.函数和方法,这两个名词虽然字面不同,但意义上其实没有区别.只是因为它们出现的地方有异,给予了不同的名称,比如在全局环境中,叫函数,在对象或类中,叫方法.而C ...

  9. location中的各个属性

    http://172.16.20.218:8080/m/MGU20201126001-001/index.html?username=admin&password=123#/write   浏 ...

  10. 前端加密办法之混淆js加密

    每个页面都是有数据和页面结构以及样式组成,对于单页面的作者来说这中分离的感觉会明显一点,那当我们通过ajax请求拿到数据之后我们一般就是把数据渲染到页面,但是数据接口又不想那么明显是一个json字符串 ...