0%

1 数学建模模型分类

数学建模竞赛常用模型有:评价模型、预测模型、优化模型和数理统计模型

评价模型

评价模型用于对某个系统、方案或决策进行评估。通过构建合适的指标评价方法,评价模型能够对不同方案的优劣进行比较和分析。在数学建模比赛中,评价模型通常适用于这类问题:根据问题的特点和需求,设计合适的评价标准和指标,对不同方案或模型的性能进行评估和比较,以帮助做出决策。

问题示例:

  • 国赛长江水质的综合评价、
  • 上海世博会影响力的定量评估问题、
  • 美赛“最好大学教练“问题。

评价模型简介总览

预测模型

预测模型能够根据过去的数据和观察结果,对未来的趋势、行为或结果进行预测和推断,适用于对趋势做预测或者规划类题目。预测模型常用于分析时间序列数据、趋势预测、行为模式预测等问题。在数学建模比赛中,预测模型可以根据给定的数据集或者特定规律,构建合适的数学模型,进行未来趋势预测,从而帮助做出决策或规划。

预测模型介绍总览

数理统计模型

数理统计模型用于对数据进行分析、总结和推断。它能够通过建立概率模型和统计分布,对数据的特征、关系和不确定性进行描述和推断。在数学建模比赛中,数理统计模型可以通过对给定数据集的统计分析,推断出数据的分布规律、相关性、假设检验等,为问题提供支持和解决方案

优化模型

优化模型旨在找到使某个目标函数取得最大或最小值的最优解。优化模型适用于求解最佳决策、资源分配、排产安排等问题。在数学建模比赛中,优化模型可以通过建立数学规划模型,确定决策变量、约束条件和目标函数,利用求解方法寻找最优解或次优解,以优化问题的方案或决策。

模型总览

并查集

在计算机科学中,并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。有一个联合-查找算法(union-find algorithm)定义了两个用于此数据结构的操作:

  • Find:确定元素属于哪一个子集。这个确定方法就是不断向上查找找到它的根节点,它可以被用来确定两个元素是否属于同一子集。

  • Union:将两个子集合并成同一个集合。

由于支持这两种操作,一个不相交集也常被称为联合-查找数据结构(union-find data structure)或合并-查找集合(merge-find set)。其他的重要方法如MakeSet,用于建立单元素集合。有了这些方法,许多经典的划分问题可以被解决。

并查集的引入

并查集的重要思想在于,用集合中的一个元素代表集合。我曾看过一个有趣的比喻,把集合比喻成帮派,而代表元素则是帮主。接下来我们利用这个比喻,看看并查集是如何运作的。

最开始,所有大侠各自为战。他们各自的帮主自然就是自己。(对于只有一个元素的集合,代表元素自然是唯一的那个元素)

现在1号和3号比武,假设1号赢了(这里具体谁赢暂时不重要),那么3号就认1号作帮主(合并1号和3号所在的集合,1号为代表元素)。

现在2号想和3号比武(合并3号和2号所在的集合),但3号表示,别跟我打,让我帮主来收拾你(合并代表元素)。不妨设这次又是1号赢了,那么2号也认1号做帮主。

现在我们假设4、5、6号也进行了一番帮派合并,江湖局势变成下面这样:

现在假设2号想与6号比,跟刚刚说的一样,喊帮主1号和4号出来打一架(帮主真辛苦啊)。1号胜利后,4号认1号为帮主,当然他的手下也都是跟着投降了。好了,比喻结束了。如果你有一点图论基础,相信你已经觉察到,这是一个树状的结构,要寻找集合的代表元素,只需要一层一层往上访问父节点(图中箭头所指的圆),直达树的根节点(图中橙色的圆)即可。根节点的父节点是它自己。我们可以直接把它画成一棵树:

用这种方法,我们可以写出最简单版本的并查集代码。

初始化

1
2
3
4
5
6
void init(int n){
vector<int> fa(n+1,0);
for(int i = 1;i<=n; ++i){
fa[i]=i;
}
}

假如有编号为1, 2, 3, ..., n的n个元素,我们用一个数组fa[]来存储每个元素的父节点(因为每个元素有且只有一个父节点,所以这是可行的)。一开始,我们先将它们的父节点设为自己。

查询

1
2
3
4
5
6
int find(int& x){
if(fa[x] == x) return x;
else{
return find(fa[x]);
}
}

我们用递归的写法实现对代表元素的查询:一层一层访问父节点,直至根节点(根节点的标志就是父节点是本身)。要判断两个元素是否属于同一个集合,只需要看它们的根节点是否相同即可。

合并

1
2
3
void merge(int i, int j) {
fa[find(i)] = find(j); // 修改的是根节点
}

合并操作也是很简单的,先找到两个集合的代表元素,然后将前者的父节点设为后者即可。当然也可以将后者的父节点设为前者,这里暂时不重要。本文末尾会给出一个更合理的比较方法。

路径压缩(发生在查询时)

随着merge次数的增加,形成的并查集可能会成为一条长长的链,随着链越来越长,我们想要从底部找到根节点的时间复杂度会成为线性。

怎么解决呢?我们可以使用路径压缩的方法。既然我们只关心一个元素对应的根节点,那我们希望每个元素到根节点的路径尽可能短,最好只需要一步,那么这就是我们的路径压缩

思想是:在查询的过程中,把沿途的的每个节点的父节点都设为根节点,下一次查询时就可以以\(O(1)\)获得根节点

1
2
3
4
5
6
7
int find(int& x){
if(fa[x]==x) return x;
else{
fa[x] = find(fa[x]);
return fa[x];
}
}

按秩合并

有些人可能有一个误解,以为路径压缩优化后,并查集始终都是一个 菊花图 (只有两层的树的俗称)。但其实,由于路径压缩只在 查询 时进行,也只压缩 一条路径,所以并查集最终的结构仍然可能是比较复杂的。例如,现在我们有一棵较复杂的树需要与一个单元素的集合合并: 假如这时我们要merge(7,8),如果我们可以选择的话,是把7的父节点设为8好,还是把8的父节点设为7好呢?

当然是后者。因为如果把7的父节点设为8,会使树的 深度(树中最长链的长度)加深,原来的树中每个元素到根节点的距离都变长了,之后我们寻找根节点的路径也就会相应变长。虽然我们有路径压缩,但路径压缩也是会消耗时间的。而把8的父节点设为7,则不会有这个问题,因为它没有影响到不相关的节点。

这启发我们:我们应该把简单的树往复杂的树上合并,而不是相反。因为这样合并后,到根节点距离变长的节点个数比较少。

我们用一个数组rank[]记录每个根节点对应的树的深度(如果不是根节点,其rank相当于以它作为根节点的子树的深度)。一开始,把所有元素的rank()设为1。合并时比较两个根节点,把rank较小者往较大者上合并。路径压缩和按秩合并如果一起使用,时间复杂度接近O(n),但是会破坏rank的准确性。

初始化(按秩合并)

1
2
3
4
5
6
7
void init(int n){
vector<int> fa(n+1,0);
vector<int> rank(n+1,1);
for(int i = 1;i<=n:==i){
fa[i]=i;
}
}

合并

1
2
3
4
5
6
7
8
9
10
11
12
void merge(int i,int j){
int x=find(i),y=find(j);
if(rank[x]<=rank[y]){
fa[x] = y;
}
else{
fa[y] = x;
}
if(rank[x]==rank[y]&&x!=y){
rank[y]++;
}
}

总结

  1. 并查集主要解决的分组管理一类的问题,如果问题能抽象成组与组之间的问题,一般情况下可考虑并查集,并查集的常见思路

是否在一个组; 在一个组的条件; 路径和组在图中都属于连通域,上述均可替换为路径,问题不变;

  1. 并查集的主要难点:

Union时,有的问题已经告诉了分组的信息,有的问题则需要自行挖掘; 一般情况下,并查集底层为一个1d数组,有的问题需要对元素进行编号或者转化与之对应; 在不清楚并查集中到底会存放多少数据时,底层也可以map

  1. 并查集功能是Union也就是将两个组合并成一个组,对于拆分的情况,可以逆序思考问题,例如leetcode803 打砖块

  2. 根据问题,也可在并查集底层使用其他数据结构,帮助解决问题;

例题

等式方程的可满足性

题目

给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为4,并采用两种不同的形式之一:"a==b""a!=b"。在这里,ab 是小写字母(不一定不同),表示单字母变量名。

只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回true,否则返回 false

分析

分析题目可知,可组等式方程或有关系或无关系,那么因此对所有的方程进行管理,该背景下可考虑使用并查集解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
lass UnionFind {
private:
vector<int> parent(26,0);

public:
UnionFind() {
parent.resize(26);
//对范围内的赋值0
iota(parent.begin(), parent.end(), 0);
}

int find(int index) {
if (index == parent[index]) {
return index;
}
parent[index] = find(parent[index]);
return parent[index];
}

void unite(int index1, int index2) {
parent[find(index1)] = find(index2);
}
};

class Solution {
public:
bool equationsPossible(vector<string>& equations) {
UnionFind uf;
for (const string& str: equations) {
if (str[1] == '=') {
int index1 = str[0] - 'a';
int index2 = str[3] - 'a';
uf.unite(index1, index2);
}
}
for (const string& str: equations) {
if (str[1] == '!') {
int index1 = str[0] - 'a';
int index2 = str[3] - 'a';
if (uf.find(index1) == uf.find(index2)) {
return false;
}
}
}
return true;
}
};

参考文献: >并查集

1 框架需求分析

本框架主要为适用于IO密集类型的服务器开发而设计,同时作为框架,一是对于开发者而言应该是简单容易上手层次分明,二是框架本身而言容易扩展,能够依据开发人员的需求添加业务处理。

为了保证框架能够实现基本的要求,其总体需求如下:

  • 框架能够较好的应对并发场景。
  • 开发人员基于该框架能够轻松的实现自己的业务处理逻辑。

接下来我们会一步一步的构建起这个框架,告诉大家这个框架是怎么实现的,而不是一下子出来一个完整的框架,让大家不知道怎么入手,同时也在这个过程中提出一些问题,让大家对框架更加熟悉,面试中能够回答出来一些关键点。

阅读全文 »

1 安装

  • 1.在ubuntu上直接使用apt-get install安装

    1
    sudo apt-get install rabbitmq-server

  • 2.启用RabbitMQ管理插件(可选):

    1
    sudo rabbitmq-plugins enable rabbitmq_management
    启用后,可以通过浏览器访问http://<服务器IP>:15672来管理RabbitMQ(默认用户名和密码都是guest)。

  • 3.安装C++客户端库,为了使用C++与RabbitMQ进行交互,需要安装一个第三方库,这里用SimpleAmqpClient作为例子(如果你要C++程序要连接RabbitServer必须做的步骤)

    1
    2
    3
    4
    git clone https://github.com/alanxz/SimpleAmqpClient.git
    mkdir simpleamqpclient-build
    cd simpleamqpclient-build
    cmake ..
    在cmake过程中如果报缺失什么依赖,直接安装就好,需要Boost库,librabbitmq-dev等等,cmake..成功后执行一下命令安装:
    1
    sudo make install

  • 4.启动Rabbit服务器

    1
    sudo service rabbitmq-server start
    之后,我们可以从localhost:5672访问,用户名密码都默认是guest

阅读全文 »

什么是Manacher算法(马拉车算法)

Manachar算法主要是处理字符串中关于回文串的问题的,它本质上是对枚举中心法确定回文串的优化,使得算法可以在 O(n) 的时间处理出以字符串中每一个字符为中心的回文串半径。

由于回文字符串以其长度来分,可以分为奇回文(其长度为奇数)、偶回文(其长度为偶数),一般情况下需要分两种情况来寻找回文,马拉车算法为了简化这一步,对原始字符串进行了处理,在每一个字符的左右两边都加上特殊字符(肯定不存在于原字符串中的字符),让字符串变成一个奇回文简化算法

阅读全文 »

什么是KMP算法

KMP算法一种改进的模式匹配算法,是D.E.Knuth、J.H.Morris、V.R.Pratt于1977年联合 发表。KMP算法的作用是在一个已知字符串中查找子串的位置,也叫做串的模式匹配,后文主串和模式串匹配, 子串和模板串匹配。先在开头约定,本文用pat表示模式串,长度为 Mtxt 表示文本串,长度为N。KMP 算法是在 txt 中查找子串 pat,如果存在,返回这个子串的起始索引,否则返回 -1

几个最基本的概念:

  • 字符串的前缀:从主串下标0开始的子串称为主串的前缀
  • 字符串的后缀:从主串下标大于0的位置到结尾的子串称为主串的后缀
  • 目标串:也就是主串,简单说就是那条比较长的串txt
  • 模式串:也就是那条短的,用来匹配的串pat
  • kmp算法的目的:在\(O(m+n)\)的时间复杂度的内进行串匹配,也就是在目标串中找到模式串,并返回目标串中模式串的第一个字符下标
阅读全文 »

1. 消息队列的优缺点?

  • 异步处理 - 相比于传统的串行、并行方式,消息队列使服务端能够异步处理请求,提高了系统吞吐量。
  • 应用解耦 - 系统间通过消息通信,不用关心其他系统的处理。
  • 流量削锋 - 可以通过消息队列长度控制请求量;可以缓解短时间内的高并发请求。
  • 日志处理 - 解决大量日志传输。
  • 消息通讯 - 消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等。

解耦、异步、削峰是什么?

  • 解耦:A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃…A 系统跟其它各种乱七八糟的系统严重耦合,A 系统产生一条比较关键的数据,很多系统都需要 A 系统将这个数据发送过来。如果使用 MQ,A 系统产生一条数据,发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费。如果新系统需要数据,直接从 MQ 里消费即可;如果某个系统不需要这条数据了,就取消对 MQ 消息的消费即可。这样下来,A 系统压根儿不需要去考虑要给谁发送数据,不需要维护这个代码,也不需要考虑人家是否调用成功、失败超时等情况。

-** 异步:**A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地写库要 3ms,BCD 三个系统分别写库要 300ms、450ms、200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s,用户感觉搞个什么东西,慢死了慢死了。用户通过浏览器发起请求。如果使用 MQ,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一个请求到返回响应给用户,总时长是 3 + 5 = 8ms

  • 削峰:将请求统统放到消息队列里去,服务器从消息队列中取任务,这样可以减少高峰时期对服务器压力。

缺点

  • 系统可用性降低:加入个消息队列进去,那消息队列宕机了了,你的系统也就无法使用。因此,系统可用性会降低;
  • 系统复杂度提高:加入了消息队列,要多考虑很多方面的问题,比如如何保证消息不被重复消费、如何保证消息可靠性传输等。因此,需要考虑的东西更多,复杂性增大。

2. RabbitMQ怎么保证消息的可靠性?

RabbitMQ消息的传输会经过三个阶段从生成者到Rabbit服务器的队列再到消费者,因此我们在三个阶段采取来保证消息的可靠性:

  • 生产者丢失数据:在消息还未到达RabbitMQ服务器上时,未防止RabbitMQ服务器宕机导致的消息丢失,我们可以将信道设置成 confirm 模式(发送方确认模式)一旦channel进入confirm模式,所有在该信道上发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后;rabbitMQ就会发送一个ACK给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了;如果rabbitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。 >- 另外一个方案是走事务机制,送消息前,开启事务(channel.txSelect()),然后发送消息,如果发送过程中出现什么异常,事务就会回滚(channel.txRollback()),如果发送成功则提交事务(channel.txCommit())。然而,这种方式有个缺点:吞吐量下降;

  • 消息队列丢数据:开启RabbitMQ的持久化策略,将消息持久化进磁盘。和confirm机制配合使用,你可以在消息持久化磁盘后,再给生产者发送一个Ack信号。这样,如果消息持久化磁盘之前,rabbitMQ阵亡了,那么生产者收不到Ack信号,生产者会自动重发。 >操作和简单:1、将queue的持久化标识durable设置为true,则代表是一个持久的队列;2、 发送消息的时候将deliveryMode=2

  • 消费者丢失数据:消费者丢数据一般是因为采用了自动确认消息模式,我们将它改为手动确认即可

3. 交换机Exchange有哪些模式

  • fanout交换器:它会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中;
  • direct交换器:direct类型的交换器路由规则很简单,它会把消息路由到那些Routing_Key完全匹配的队列中;
  • topic交换器:匹配规则比direct更灵活。
  • headers交换器:根据发送消息内容的headers属性进⾏匹配(由于性能很差,不实⽤)

3. 消息基于什么传输?

由于TCP连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。 RabbitMQ使⽤信道的方式来传输数据。信道是建⽴在真实的TCP连接内的虚拟连接,且每条 TCP连接上的信道数量没有限制。

  • RabbitMQ采用类似非阻塞IO(Non-blocking I/O)做法,选择TCP连接复⽤,不仅可以减少性能开销,同时也便于管理。
  • 每个线程把持一个信道,所以信道复用了Connection的TCP连接。同时RabbitMQ可以确保每个线程的私密性,就像拥有独立的连接一样。

4. 如何避免消息重复投递或重复消费?

消费端处理消息的业务逻辑保持幂等性。只要保持幂等性,不管来多少条重复消息,最后处理的结果都一样。如保证每条消息都有唯一编号且保证消息处理成功与去重表的日志同时出现。利用一张日志表来记录已经处理成功的消息的 ID,如果新到的消息 ID 已经在日志表中,那么就不再处理这条消息。

5. 如何保证消息的顺序

  • (1)保证生产者 - MQServer - 消费者是一对一对一的关系。 缺陷:
    • 并行度就会成为消息系统的瓶颈(吞吐量不够)
    • 更多的异常处理,比如:只要消费端出现问题,就会导致整个处理流程阻塞,我们不得不花费更多的精力来解决阻塞的问题。
  • (2)通过合理的设计或者将问题分解来规避。
    • 不关注乱序的应用实际大量存在
    • 队列无序并不意味着消息无序 所以从业务层面来保证消息的顺序而不仅仅是依赖于消息系统,是一种更合理的方式。

6. 如果有一个消息一直得不到消费,比如客户端宕机且设置手动确认,然后一直未手动确认,此时怎么办?

在RabbitMQ中,当消费者未正常消费消息,由于未收到来自消费者的确认,就会进行重试策略,但重试策略必须有一个次数,不然一直有这样的消息得不到消费,一直占用队列资源就造成吞吐量的低下。

因此当一个消息在队列中达到一定的重试次数后仍然无法被成功处理,或者因为某些原因(如TTL过期、队列达到最大长度等)被队列拒绝返回nack时,RabbitMQ可以将这些消息发送到另一个交换机(Dead Letter Exchange),进而路由到一个专门的队列(Dead Letter Queue, DLQ),即死信队列

DLQ中的消息代表了那些经过多次尝试仍无法被成功处理的消息。你可以有一个专门的消费者来监听DLQ,并对这些消息进行进一步的处理,例如记录日志、发送告警或者进行人工干预。

7. RabbitMQ基本概念

  • Broker: 简单来说就是消息队列服务器实体
  • Exchange: 消息交换机,它指定消息按什么规则,路由到哪个队列
  • Queue: 消息队列载体,每个消息都会被投入到一个或多个队列
  • Binding: 绑定,它的作用就是把exchange和queue按照路由规则绑定起来
  • Routing Key: 路由关键字,exchange根据这个关键字进行消息投递
  • VHost: vhost 可以理解为虚拟 broker ,即 mini-RabbitMQ server。其内部均含有独立的 queue、exchange 和 binding 等,但最最重要的是,其拥有独立的权限系统,可以做到 vhost 范围的用户控制。当然,从 RabbitMQ 的全局角度,vhost 可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同的 vhost 中)。
  • Producer: 消息生产者,就是投递消息的程序
  • Consumer: 消息消费者,就是接受消息的程序
  • Channel: 消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务

8. 什么原因会导致 MQ 消息积压?

MQ消息积压是指生产者发送的消息在Broker端大量堆积,无法被消费者及时消费,导致业务功能无法正常使用。以下是一些导致MQ消息积压的常见原因:

-** 流量变大而服务器配置偏低:当消息的产生速度大于消费速度时,如果RabbitMQ服务器配置较低,就可能导致消息积压。 - 消费者故障:如果消费者出现宕机或网络问题,导致无法及时消费消息,消息会持续堆积。 - 程序逻辑设计问题:如果生产者持续生产消息,但消费者由于某种原因(如处理逻辑耗时过长)消费能力不足,也会造成消息积压。 - 新上线的消费者功能存在BUG:新上线的消费者功能如果有缺陷,可能导致消息无法被正常消费,从而引发消息堆积。 - 配置不合理:消息队列的容量设置过小或消费者的线程数设置过少,都可能导致消息积压。 - 生产者推送大量消息**:在特定场景下,如大促活动,生产者可能短时间内推送大量消息至Broker,如果消费者的消费能力不足以应对这种突发流量,也会导致消息堆积。

了解决MQ消息积压问题,可以采取以下策略:

  • 扩容:纵向扩容,增加服务器资源,如内存和CPU;横向扩容,将单机改为集群模式,增加集群节点,并增加消费者数量。
  • 优化程序逻辑:确保生产者和消费者的逻辑设计合理,避免生产者过快生产消息或消费者处理消息过慢。
  • 监控和报警:建立有效的监控和报警机制,及时发现并解决消息积压问题。

8. 有几百万消息持续积压几小时,怎么办?

消息积压处理办法:临时紧急扩容

  • 1.先修复 consumer 的问题,确保其恢复消费速度,然后将现有 cnosumer 都停掉。
  • 2.新建一个topic,临时建立好原先 10 倍的 queue 数量。
  • 3.然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的 10 倍数量的 queue。
  • 4.接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据
  • 5.等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的 consumer 机器来消费消息。

9 与其他消息队列的区别比较?

ctiveMQ:的社区算是比较成熟,但是较目前来说,ActiveMQ 的性能比较差,而且版本迭代很慢,不推荐使用。

RabbitMQ:在吞吐量方面虽然稍逊于 Kafka 和 RocketMQ ,但是由于它基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。但是也因为 RabbitMQ 基于 erlang 开发,所以国内很少有公司有实力做erlang源码级别的研究和定制。如果业务场景对并发量要求不是太高(十万级、百万级),那这四种消息队列中,RabbitMQ 一定是你的首选。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。

RocketMQ:阿里出品,Java 系开源项目,源代码我们可以直接阅读,然后可以定制自己公司的 MQ,并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。RocketMQ 社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准 JMS 规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ 挺好的。

kafka:特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。

10 RabbitMQ 上的⼀个 queue 中存放的 message 是否有数量限制?

可以认为是没有限制,因为限制取决于机器的内存,但是消息过多会导致处理效率的下降。

0概述一下你认识的Redis?

Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载 在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。 因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能 最快的Key-Value DB。 Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value 的最大限制是1GB,不像 memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能。比方说用他的List来做FIFO双向链表,实现一个轻量级的高性 能消息队列服务,用他的Set可 以做高性能的tag系统等等。 另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一 个功能加强版的 memcached来用。 Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据 的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。

0.1 Redis 与其他 key - value 缓存产品有以下三个特点

  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  • Redis支持数据的备份,即master-slave模式的数据备份。

0.2 Redis 优势

  • 性能极高 。Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型。 Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets数据类型操作。
  • 操作的原子性。 Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
  • 丰富的特性。Redis还支持 publish/subscribe, 通知, key 过期等等特性

1. Redis数据类型

Redis提供了String,Hash,List,Set,Zset五种数据类型。

1.1 String

String数据结构是最简单的key-value类型,value不仅可以是String,也可以是数字,包括整数,浮点数和二进制数。

string 数据结构是简单的 key-value 类型。虽然 Redis 是用 C 语言写的,但是 Redis 并没有使用 C 的字符串表示,而是自己构建了一种 简单动态字符串(simple dynamic string,SDS)。相比于 C 的原生字符串:

  • Redis 的 SDS 不光可以保存文本数据还可以保存二进制数据
  • 并且获取字符串长度复杂度为 O(1)(C 字符串为 O(N))
  • 除此之外,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。因为 SDS 在拼接字符串之前会检查 SDS 空间是否满足要求,如果空间不够会自动扩容,所以不会导致缓冲区溢出的问题

主要的应用有:缓存,计数(比如用户的访问次数、热点文章的点赞转发数量等等),共享session和限速。

阅读全文 »

准备工作

安装Redis

  1. 在ubuntu安装Redis
    1
    sudo apt-get install redis-server
  2. Redis服务器连接默认没有密码,可通过更改/etc/redis/redis.conf内的# requirepass foobared来指定登录密码,比如去掉注释后更改密码为requirepass 123456,指定了密码为123456
    1
    2
    3
    sudo vim /etc/redis/redis.conf
    #修改密码
    requirepass 123456
  3. 运行远程连接redis,redis默认只允许本地连接,你可以通过注释掉bind 127.0.0.1::1来支持远程登录。
阅读全文 »