superLiS

superLiS 查看完整档案

杭州编辑南京农业大学  |  电子信息科学与技术 编辑  |  填写所在公司/组织 juejin.im/user/5d149662f265da1b695d70eb 编辑
编辑

微信公众号:让我思考一下

个人动态

superLiS 赞了文章 · 10月28日

RabbitMq 最全的性能调优笔记

RabbitMq 性能调优笔记

[TOC]

避免雷区

要避免流控机制触发

  • 服务端默认配置是当内存使用达到40%,磁盘空闲空间小于50M,即启动内存报警,磁盘报警;报警后服务端触发流控(flowcontrol)机制。
  • 一般地,当发布端发送消息速度快于订阅端消费消息的速度时,队列中堆积了大量的消息,导致报警,就会触发流控机制。
  • 触发流控机制后,RabbitMQ服务端接收发布来的消息会变慢,使得进入队列的消息减少
  • 与此同时RabbitMQ服务端的消息推送也会受到极大的影响,测试发现,服务端推送消息的频率会大幅下降,等待下一次推送的时间,有时等1分钟,有时5分钟,甚至30分钟。
  • 一旦触发流控,将导致RabbitMQ服务端性能恶化,推送消息也会变得非常缓慢;
  • 因此要做好数据设计,使得发送速率和接收速率保持平衡,而不至于引起服务器堆积大量消息,进而引发流控。通过增加服务器集群节点,增加消费者,来避免流控发生,治标不治本,而且成本高。
  • 服务器单节点,单网卡全双工情况下,测试发现发布速度过快,压满发布PC机带宽,对于服务器来说,下行(接收)带宽也会压满,可是上行(转发递送)带宽却出现了明显的下降,似乎有一个争抢。这可能是导致触发流控的原因。

从底层取数据一定要非常及时

订阅端每隔500MS调用一次amqp_consume_message接口函数从socket上获取数据,正常情况下,服务器每次会推送几百条消息,而且推送的频率会比较高;

导致订阅端的本机socket缓冲区会很快存满,导致很多消息无法进行缓存,而被丢掉;

发布消息条数调用amqp_comsume_message间隔(MS)实际接收条数
630500269
695470269
513460269
503450503

消息大小不要超过4MB

  • 客户端与RabbitMQ服务端的最大帧是128K,但消息大小却可支持数MB,这是可能是因为底层做了拆包组包的,目前我还未查看底层代码。
  • 用线程来模拟50个发布者和50个订阅者;
消息包大小由1K到10MB,当包大小达到4.5MB时,服务器的性能出现明显的异常,传输率尤其是每秒订阅消息的数量,出现波动,不稳定;同时有一部分订阅者的TCP连接出现断开的现象。可能是客户端底层或者RabbitMQ服务端在进行拆包,组包的时候,出现了明显的压力,而导致异常的发生。
  • 超过4MB的消息,最好先进行分包

consume时预取参数的大小对consume性能影响很大

具体可参见官方博客

磁盘也可能形成瓶颈

磁盘也可能形成瓶颈,如果单台机器队列很多,确认只在必要时才使用duration(持久化),避免把磁盘跑满;

队列的消息大量累积后

队列的消息大量累积后,发送和消费速度都会受到影响,导致服务进一步恶化,采用的方法是,额外的脚本监控每个队列的消息数,超过限额会执行purge操作,简单粗暴但是有效的保证了服务稳定;

调优

单机限制

由于用线程模拟大量发布者,且是服务器单节点,受客户端主机网卡的限制,发布线程没有速度控制,导致有大量数据发送,服务器带宽下行速率也满负荷,上行带宽却明显低于下行速率,导致服务器内存有大量消息堆积,进而触发RabbitMQ服务器paging操作,才出现了上述不稳定和订阅者断开现象。

对发布端做适当流量控制,断开连接现象不再出现,但每秒消息数仍然不稳定

模式对性能的影响

分析三种模式 directfanouttopic

不同的模式对于新建交换机、新建队列、绑定等操作性能影响不大,

但是在direct模式下明显消息发布的性能比其他模式强很多,并且消息发送到相同队列比发送到不同队列性能稍好

持久化对消息性能的影响

在消息持久化模式下:

发布:13888msg/s 
订阅:15384msg/s

在消息非持久化模式下:

发布:18867msg/s 
订阅:26315msg/s

问题分析/解决方案

问题分析:

可以看到RabbitMQ的内存 占用占用已经使用了7.8G 允许的值为 .6G左右

因为 vm_memory_high_watermark 值设置的是0.4 也就是物理内存的40% ;服务器为16G * 40% = 6.4G

一般在产生的原因是长期的生产者发送速率大于消费者消费速率导致. 触发了RabbitMQ 的流控;

解决方案:

  1. 增加消费者端的消费能力,或者增加消费者(根本解决)
  2. 控制消息产生者端的发送速率(不太现实)
  3. 增加mq的内存(治标不治本)

参数调优

vm_memory_high_watermark:用于配置内存阈值,建议小于0.5,因为Erlang GC在最坏情况下会消耗一倍的内存。

vm_memory_high_watermark_paging_ratio:用于配置paging阈值,该值为1时,直接触发内存满阈值,block生产者。

IO_THREAD_POOL_SIZE:CPU大于或等于16核时,将Erlang异步线程池数目设为100左右,提高文件IO性能。

hipe_compile:开启Erlang HiPE编译选项(相当于Erlang的jit技术),能够提高性能20%-50%。在Erlang R17后HiPE已经相当稳定,RabbitMQ官方也建议开启此选项。

queue_index_embed_msgs_below:RabbitMQ 3.5版本引入了将小消息直接存入队列索引(queue_index)的优化,消息持久化直接在amqqueue进程中处理,不再通过msg_store进程。由于消息在5个内部队列中是有序的,所以不再需要额外的位置索引(msg_store_index)。该优化提高了系统性能10%左右。

queue_index_max_journal_entries:journal文件是queue_index为避免过多磁盘寻址添加的一层缓冲(内存文件)。对于生产消费正常的情况,消息生产和消费的记录在journal文件中一致,则不用再保存;对于无消费者情况,该文件增加了一次多余的IO操作。

最佳线程

  • 生产者使用多线程发送数据到queue三到五个线程性能发送最佳,超过它也不能提高生产的发送速率。
  • 消费者的数据处理,使用二线程接收性能是最佳的,如数据处理程序处理比较复杂的逻辑,建议多开启几个线程进行数据接收。
  • 在发送接收队列中,因为发送的速率总比接收的速率要快,因此考虑在接收端配置比发送端更多的线程,个人认为:接收者线程 = 发送者线程 X 1.5

数据恢复

  • RabbitMq在做数据恢复时,遇到过一次数据恢复不了后就没有办法复现。数据恢复的时间会随着数据量的大小成直线增长。

集群

没测

用iptables适当的限制连接

查看原文

赞 11 收藏 7 评论 3

superLiS 发布了文章 · 2019-12-02

Rust完全限定语法与消歧义:调用相同名称的方法

Rust既不能避免一个trait与另一个trait拥有相同名称的方法,也不能阻止为同一类型同时实现这两个trait。甚至可以直接在类型上实现开始已经有的同名方法!当然,当调用这些同名方法时,你必须要告诉Rust我们使用哪一个。

下面示例代码说明了具体用法:

pub trait Airplane {
    fn speed(&self){
        println!("airplane default speed=800");
    }

    fn state(){
        println!("fly state");
    }
}

pub trait Boat {
    fn speed(&self);
    fn state(){
        println!("boat state");
    }
}

struct Airboat;

impl Airboat{
    fn speed(&self) {
        println!("airboat speed 0~800");
    }
    fn state(){
        println!("airboat state");
    }
}

impl Airplane for Airboat {
    fn speed(&self){
        println!("airboat in airplane state speed=800");
    }
}

impl Boat for Airboat {
    fn speed(&self){
        println!("airboat in boat state speed=60");
    }
}

fn main() {
    let a = Airboat;
    Airboat::state();
    a.speed();  //默认调用自身的实现
    <Airboat as Airplane>::state(); //完全限定语法
    Airplane::speed(&a);    //显示语法指定调用Airplane方法
    <Airboat as Airplane>::speed(&a);
    <Airboat as Boat>::state();
    Boat::speed(&a);
}

运行结果:

airboat state
airboat speed 0~800
fly state
airboat in airplane state speed=800
airboat in airplane state speed=800
boat state
airboat in boat state speed=60
通常,完全限定语法定义为:<Type as Trait>::function(receiver_if_method, next_arg, ...)

在存在调用相同名称方法时,重要的是告诉编译器你调用的是具体那个方法,上面的示例代码给出了调用相同名称时的调用方法。

查看原文

赞 0 收藏 0 评论 0

superLiS 发布了文章 · 2019-11-29

Rust写时复制Cow<T>

写时复制(Copy on Write)技术是一种程序中的优化策略,多应用于读多写少的场景。主要思想是创建对象的时候不立即进行复制,而是先引用(借用)原有对象进行大量的读操作,只有进行到少量的写操作的时候,才进行复制操作,将原有对象复制后再写入。这样的好处是在读多写少的场景下,减少了复制操作,提高了性能。

Rust中对应这种思想的是智能指针Cow<T>,定义如下:

pub enum Cow<'a, B> 
where
    B: 'a + ToOwned + 'a + ?Sized, 
 {
    Borrowed(&'a B),    //用于包裹引用
    Owned(<B as ToOwned>::Owned),   //用于包裹所有者
}

可以看到是一个枚举体,包括两个可选值,一个是“借用”,一个是“所有”。具体含义是:以不可变的方式访问借用内容,在需要可变借用或所有权的时候再克隆一份数据。

下面举个例子说明Cow<T>的应用:

use std::borrow::Cow;

fn abs_all(input: &mut Cow<[i32]>) {
    for i in 0..input.len() {
        let v = input[i];
        if v < 0 {
            input.to_mut()[i] = -v;
        }
    }

    println!("value: {:?}", input);
}

fn main() {
    // 只读,不写,没有发生复制操作
    let a = [0, 1, 2];
    let mut input = Cow::from(&a[..]);
    abs_all(&mut input);
    assert_eq!(input, Cow::Borrowed(a.as_ref()));

    // 写时复制, 在读到-1的时候发生复制
    let b = [0, -1, -2];
    let mut input = Cow::from(&b[..]);
    abs_all(&mut input);
    assert_eq!(input, Cow::Owned(vec![0,1,2]) as Cow<[i32]>);

    // 没有写时复制,因为已经拥有所有权
    let mut input = Cow::from(vec![0, -1, -2]);
    abs_all(&mut input);
    assert_eq!(input, Cow::Owned(vec![0,1,2]) as Cow<[i32]>);
    
    let v = input.into_owned();
    assert_eq!(v, [0, 1, 2]);
}

上面这个用例已经讲明了Cow<T>的使用,下面我们继续探索一下Cow<T>的实现细节。重点关注to_mutinto_owned的实现。

  • to_mut :就是返回数据的可变引用,如果没有数据的所有权,则复制拥有后再返回可变引用;
  • into_owned :获取一个拥有所有权的对象(区别与引用),如果当前是借用,则发生复制,创建新的所有权对象,如果已拥有所有权,则转移至新对象。
impl<B: ?Sized + ToOwned> Cow<'_, B> {
    #[stable(feature = "rust1", since = "1.0.0")]
    pub fn to_mut(&mut self) -> &mut <B as ToOwned>::Owned {
        // 如果时借用,则进行复制;如果已拥有所有权,则无需进行复制
        match *self {
            Borrowed(borrowed) => {
                *self = Owned(borrowed.to_owned());
                match *self {
                    Borrowed(..) => unreachable!(),
                    Owned(ref mut owned) => owned,
                }
            }   
            Owned(ref mut owned) => owned,  //这里解释了上个例子中,已拥有所有权的情况,无需再复制
        }
    }
    
    #[stable(feature = "rust1", since = "1.0.0")]
    pub fn into_owned(self) -> <B as ToOwned>::Owned {
        // 如果当前是借用,则发生复制,创建新的所有权对象,如果已拥有所有权,则转移至新对象。
        match self {
            Borrowed(borrowed) => borrowed.to_owned(),
            Owned(owned) => owned,
        }
    }
}
查看原文

赞 0 收藏 0 评论 0

superLiS 发布了文章 · 2019-08-14

Rust生命周期bound用于泛型的引用

在实际编程中,可能会出现泛型引用这种情况,我们会编写如下的代码:

struct Inner<'a, T> {
    data: &'a T,
}

会产生编译错误:

error[E0309]: the parameter type `T` may not live long enough
  --> src/main.rs:16:5
   |
15 | struct Inner<'a, T> {
   |                  - help: consider adding an explicit lifetime bound `T: 'a`...
16 |     data: &'a T,
   |     ^^^^^^^^^^^
   |
note: ...so that the reference type `&'a T` does not outlive the data it points at
  --> src/main.rs:16:5
   |
16 |     data: &'a T,
   |     ^^^^^^^^^^^

为什么会编译错误呢?因为T可以是任意类型,T自身也可能是一个引用,或者是一个存放了一个或多个引用的类型,而他们各自可能有着不同的生命周期。Rust编译器不能确认T会与'a存活的一样久。

所以编译器才提示我们:T的生命周期可能不够长,建议我们对泛型T进行生命周期bound,限定T的生命周期与'a一样长。

之后我们根据编译器的提示更改代码:

trait Print {
    fn output(&self);
}

struct Object<'a>{
    data: &'a i32,
}

impl<'a> Print for Object<'a> {
    fn output(&self) {
        println!("Object data: {}", self.data);
    }
}

struct Inner<'b, T: 'b + Print> {
    pub a: &'b T,
}

fn main() {
    let data = 10;
    {
        let a = Object{data: &data};
        let inner = Inner{a: &a};
        inner.a.output();
    }
}

对于生命周期bound,可以结合trait bound来理解。Rust的泛型有个特点就是trait boundtrait bound可以对泛型进行某些限制(只有实现了指定trait的类型才符合要求)。同样,我们也可以像泛型那样为生命周期参数增加限制,这被称为“生命周期bound”(lifetime bounds)。生命周期bound帮助Rust编译器验证泛型的引用不会存在的比其引用的数据更久。

上面的代码中,struct Inner<'b, T: 'b + Print>这行代码就是表示对泛型T同时进行trait bound和生命周期bound。

Rust通过生命周期参数注解引用来帮助编译器理解不同引用的生命周期如何相互联系。从而使编译器能够判断引用是否有效。

关注微信公众号,定期推送新文章!

查看原文

赞 0 收藏 0 评论 0

superLiS 发布了文章 · 2019-08-13

【Rust】RefCell和内部可变性

RefCell

Rust在编译阶段会进行严格的借用规则检查,规则如下:

  • 在任意给定时间,要么只能有一个可变引用,要么只能有多个不可变引用。
  • 引用必须总是有效。

即在编译阶段,当有一个不可变值时,不能可变的借用它。如下代码所示:

fn main() {
    let x = 5;
    let y = &mut x;
}

会产生编译错误:

error[E0596]: cannot borrow immutable local variable `x` as mutable
  --> src/main.rs:32:18
   |
31 |     let x = 5;
   |         - consider changing this to `mut x`
32 |     let y = &mut x;
   |                  ^ cannot borrow mutably

但是在实际的编程场景中可能会需要在有不可变引用时改变数据的情况,这时可以考虑Rust中的内部可变性。其借用规则检查由编译期推迟到运行期。对应的,在编译期借用规则检查不通过,则会产生编译错误;而运行期借用规则检查不通过,则会panic,且有运行期的代价。

所以实际代码中使用RefCell<T>的情况是当你确定你的代码遵循借用规则,而编译器不能理解和确定的时候。代码仍然要符合借用规则,只不过规则检查放到了运行期。

RefCell代码实例1:

use std::cell::RefCell;

fn main() {
    let x = RefCell::new(5u8);
    assert_eq!(5, *x.borrow());
    {
        let mut y = x.borrow_mut();
        *y = 10;
        assert_eq!(10, *x.borrow());
        let z = x.borrow();     //编译时会通过,但运行时panic!
    }
}

运行结果:

thread 'main' panicked at 'already mutably borrowed: BorrowError', libcore/result.rs:983
:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.

可以看到在运行时进行了借用检查,并且panic!

RefCell代码实例2:

#[derive(Debug, Default)]
struct Data {
    a: u8,
    b: RefCell<u8>,
}

impl Data {
    // 编译通过
    pub fn value_b(&self) -> u8 {
        let mut cache = self.b.borrow_mut();
        if *cache != 0 {
            return *cache;
        }
        *cache = 100;
        *cache
    }

    //编译错误:cannot mutably borrow field of immutable binding
    pub fn value_a(&self) -> u8 {
        if self.a != 0 {
            return self.a;
        }

        self.a = 100;
        self.a
    }
}

fn main() {
    let value = Data::default();
    println!("{:?}", value);
    value.value_b();
    println!("{:?}", value);
}

value_a注释掉运行结果如下:

Data { a: 0, b: RefCell { value: 0 } }
Data { a: 0, b: RefCell { value: 100 } }

很多时候我们只能获取一个不可变引用,然而又需要改变所引用数据,这时用RefCell<T>是解决办法之一。

内部可变性

内部可变性(Interior mutability)是Rust中的一个设计模式,它允许你即使在有不可变引用时改变数据,这通常是借用规则所不允许的。为此,该模式在数据结构中使用unsafe代码来模糊Rust通常的可变性和借用规则。当可以确保代码在运行时会遵守借用规则,即使编译器不能保证的情况,可以选择使用那些运用内部可变性模式的类型。所涉及的 unsafe 代码将被封装进安全的 API 中,而外部类型仍然是不可变的。

欢迎关注微信公众号,定期推送新文章!
查看原文

赞 0 收藏 0 评论 0

superLiS 发布了文章 · 2019-08-12

Rust关联类型与默认泛型类型参数

一、关联类型(associated types)

我们阅读Rust程序的时候,有时候会出现如下的代码:

trait Iterator {
    type Item; 
    fn next(&mut self) -> Option<Self::Item>;
}

下面是上面代码的注解:Iterator trait 有一个关联类型 ItemItem是一个占位类型,同时 next 方法会返回 Option<Self::Item>类型的值。这个 trait的实现者会指定 Item的具体类型。

这里的type用法就是关联类型。

关联类型(associated types)是一个将类型占位符与 trait 相关联的方式,这样 trait 的方法签名中就可以使用这些占位符类型。trait 的实现者会针对特定的实现在这个类型的位置指定相应的具体类型。如此可以定义一个使用多种类型的 trait,直到实现此 trait 时都无需知道这些类型具体是什么。

使用关联类型的代码示例如下:

pub trait Watch {
    type Item;
    fn inner(&self) -> Option<Self::Item>;
}

struct A {
    data: i32,
}

impl Watch for A {
    type Item = i32;
    fn inner(&self) -> Option<Self::Item> {
        Some(self.data)
    }
}

struct B {
    data: String,
}

impl Watch for B {
    type Item = String;
    fn inner(&self) -> Option<Self::Item> {
        Some(self.data.clone())
    }
}

fn main() {
    let a = A{data: 10};
    let b = B{data: String::from("B")};
    assert_eq!(Some(10), a.inner());
    assert_eq!(Some(String::from("B")), b.inner());
}

二、默认泛型类型参数

我们还会碰到如下类型的代码:

#[lang = "add"]
pub trait Add<RHS = Self> {
    type Output;
    
    #[must_use]
    fn add(self, rhs: RHS) -> Self::Output;
}

这里Add<RHS = Self>是默认泛型类型参数,表示如果不显示指定泛型类型,就默认泛型类型为Self

当使用泛型类型参数时,可以为泛型指定一个默认的具体类型。如果默认类型就足够的话,这消除了为具体类型实现 trait 的需要。为泛型类型指定默认类型的语法是在声明泛型类型时使用 <PlaceholderType=ConcreteType>。

使用默认泛型类型参数的示例代码如下:

pub trait Watch<Inner=String> {
    type Item;
    fn inner(&self) -> Option<Self::Item>;
    fn info(&self) -> Inner;
}

struct A {
    data: i32,
}

impl Watch<i32> for A {
    type Item = i32;
    fn inner(&self) -> Option<Self::Item> {
        Some(self.data)
    }
    fn info(&self) -> i32 {
        println!("A inner is {}", self.data);
        self.data
    }
}

struct B {
    data: String,
}

impl Watch for B {
    type Item = String;
    fn inner(&self) -> Option<Self::Item> {
        Some(self.data.clone())
    }
    fn info(&self) -> String {
        println!("B inner is {}", self.data);
        self.data.clone()
    }
}

fn main() {
    let a = A{data: 10};
    let b = B{data: String::from("B")};
    assert_eq!(10, a.info());
    assert_eq!(Some(String::from("B")), b.inner());
}
关注微信公众号,定期推送文章!
查看原文

赞 0 收藏 0 评论 0

superLiS 关注了专栏 · 2019-07-23

马蜂窝技术

欢迎关注马蜂窝技术,我们将定期分享马蜂窝技术团队先进的技术实践,期待您与马蜂窝一起见证技术对行业的赋能和改变。

关注 6564

superLiS 关注了专栏 · 2019-07-23

疯狂的技术宅

本专栏文章首发于公众号:前端先锋 。

关注 24568

superLiS 关注了标签 · 2019-07-23

linux

Linux是一种自由和开放源代码的类Unix计算机操作系统。目前存在着许多不同的Linux,但它们全都使用了Linux内核。Linux可安装在各种各样的计算机硬件设备,从手机、平板电脑、路由器和视频游戏控制台,到台式计算机,大型机和超级计算机。

Linux家族家谱图,很全很强大!! 图中可以清楚的看出各个Linux发行版的血缘关系。无水印原图:http://url.cn/5ONhQb

关注 64278

认证与成就

  • 获得 0 次点赞
  • 获得 0 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 0 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2019-07-23
个人主页被 62 人浏览