简栈

拥抱AI,持续成长

我们在新做项目的时候,需要对我们的服务有有一些性能指标,比如:SLA(需要达到多少个9)、QPSTPS等。因为这些量化的数字让我们更加的了解我们的系统。

我们如何压测?其实个人觉得有2种场景。

第一种:是我们明确的知道目标,看我们通过大量的并发看我们是否有达到。如果没有达到,我们需要通过水平扩容、性能优化等让其达到。

第二种:是我们不知道目标,通过压测可以知道一个固定配置下的单机单服务的最大性能,让我们对它有一个彻底的认识。为后面的目标做更多的铺垫与准备,或者跟行业水平对比,看看差距有多少。

如何用wrk进行压测?

Github地址:https://github.com/wg/wrk,该项目也是开源项目,关注的人还不少,有30.4K。咨询了一下身边的同事,使用它的人还不少。主要的语言的是C语言。

http://static.cyblogs.com/Jietu20211023-153923.jpg

安装
1
2
3
4
5
6
git clone https://github.com/wg/wrk

make

-- 拷贝wrk到bin
cp wrk /usr/sbin/wrk
压测脚本
阅读全文 »

背景

项目中开始用Go,最近写了一下Demo,发现语法还是非常好用,大部分比Java还是简洁很多,也有一些很细节的约定。比如:

  • 字母大小写控制是全包还是本包内访问
  • 变量定义了就一定要使用,否则就会编译不通过等等

更好的就是方法可以返回多个值,这个跟Java比较就是减少很多的封装。因为Go的线程模型特点,用来写一些需要高并发、高性能的项目还是非常好的。所以,趁这个机会也好好的深入了解下。现在也是把PythonPHPGo等都学习一遍,每种语言都有它的优缺点,其实都还挺不错的。

针对于Go语言里:&*的区别,什么时候该用什么做一个总结。

指针

我们经常会听到别人说Go是值传递,某某某是引用传递,某某某是指针传递,等等各种各样的说法。

那么首先他们的区别是什么呢?什么是指针?指针其实也是一个变量,只不过这个变量里面存的不是intfloatstruct,而是一个地址address,然后在这个address上所存储的数据可以通过指针来被阅读到。

OK,指针变量存储的是一个地址,地址从哪里来的?那就得问一个变量的地址怎么取得呢?在变量前面加上一个&符号就行。

好的,指针变量存储了这个地址了,那这个地址所存储的值怎么被阅读到呢?也就是指针所指向的值怎么拿到呢?在指针变量前面加上一个*符号就行。

阅读全文 »

背景

如果说你的数据量并发量不大,或者你的数据量很少没有到千万级别,也许pt-oscgh-osc,online-ddl这些工具都用不着。但是,如果你的数据量很大,数据又很热。如果你没有这些工具,你可能无法完成对一个数据库新增一个字段或者任何一个简单的DDL语句。

简单的分析一下,为了保证数据一致性问题,我们在哪儿都会遇到锁的问题,锁是用来保证顺序性的。谁先拥有锁,谁就可以先执行。锁也会存在力度问题,它跟你要做的一件事情息息相关,我们也会在性能上去做取舍,所有就好了行锁、表锁等。

Waiting for table metadata lock

说一下我遇到的这个场景,数据量数据大概在800W左右,但是表非常的热,长事务也很多。当我要对一个表新增字段的时候,这个时候如果你经验不够足,可能就会“量成大祸”。一般在做DDL会出现:Waiting for table metadata lock。

如果长时间获取不到锁的话,就出现一个可怕的情况:

  • 如果前面的事务未提交,当前是获取不到锁,就不可以执行DDL语句
  • DDL语句未执行之前,后面的请求全部是被hold住的

这样子就会导致一前一后同时夹击,导致整个业务不可用。那么出现Waiting for table metadata lock可能是由哪些原因导致的?

场景一:长事物运行,阻塞DDL,继而阻塞所有同表的后续操作

通过show processlist可以看到TableA上有正在进行的操作(包括读),此时alter table语句无法获取到metadata 独占锁,会进行等待。

阅读全文 »

代码分支管理

大家在做微服务拆分后,难免会导致Application项目以及一些二房包的数量加剧,10+个项目我想应该是很容易的超过。然后这些细粒度的拆分后就会导致发布版本时候的麻烦。

展示一下现阶段我们的一个git的分支流程图,仅供参考。

http://static.cyblogs.com/git_flow_publish_20210415.jpg

简单说明一下:

  • dev环境每次都是从master拉取一个分支,取名为dev-{发布日期}-{sequenceId}sequenceId01开始叠加,避免一个版本需要反复拉取多次。
  • 到了test阶段,也是为了收敛(前期严格一点)。提测的时候代码需要合并到test分支来。
  • UAT阶段,还是需要从master拉取分支,如果出现要重新拉取分支的情况下,还是严格拉取master分支的代码。主要是为了与master保持一致,避免把别人的覆盖掉。取名为release-{发布日期}-{sequenceId},规则同上。
  • 上生产后,验证完毕后需要把代码合并到master分支,并且打包tag

但是在这个过程中,需要有拉取新分支,合并分支,批量删除分支,打tag等等繁琐的操作,项目一多如果有一个批量脚本就更好了。下面我就列举一些平时使用最多的几个,仅供参考:

批量脚本

01-批量拉取新分支

比如一个新的迭代要开始了,就需要从master拉取dev分支。

阅读全文 »

背景

相信很多程序员对于 Linux 系统都不陌生,即使自己的日常开发机器不是 Linux,那么线上服务器也大部分都是的,所以,掌握常用的 Linux 命令也是程序员必备的技能。

但是,怕就怕很多人对于部分命令只是一知半解,使用不当就能导致线上故障。

前段时间,我们的线上应用报警,频繁 FGC,需要紧急处理问题,于是有同事去线上重启机器(正常程序应该是先采集堆 dump,然后再重启,方便排查是否存在内存泄露等问题)。

但是在重启过程中,同事发现正常的重启命令应用无反应,然后尝试使用 kill 命令 “杀” 掉 Java 进程,但是仍然无效。于是他私自决定使用 “kill -9“ 结束了进程的生命。

虽然应用进程被干掉了,但是随之而来带来了很多问题,首先是上游系统突然发生大量报警,对应开发找过来说调用我们的 RPC 服务无响应,频繁超时。

后来,我们又发现系统中存在部分脏数据,有些在同一个事务中需要完整更新的数据,只更新了一半…

为什么正常的 kill 无法 “杀掉” 进程,而 kill -9 就可以?为什么 kill -9 会引发这一连串连锁反应?正常的 kill 执行时,JVM 会如何处理的呢?

要搞清楚这些问题,我们要先从 kill 命令说起。

kill 命令

阅读全文 »

一、Kafka消费者组是什么?

Consumer GroupKafka提供的可扩展且具有容错性的消费者机制。在组内多个消费者实例(Consumer Instance ),它们共享一个公共的ID即 Group ID 。组内的所有消费者协调在一起消费订阅主题(Subscribed Topics)的所有分区(Partition)。当然一个分区只能有同一个消费者组的一个Consumer 实例消费。
Consumer Group 有三个特性:

  • Consumer Group 下可以有一个或多个Consumer 实例。 这里的实例可以是一个单独的进程,也可以是同一进程下的线程;
  • Group ID 是一个字符串, 在Kafka集群中唯一标识,Consumer Group
  • Consumer Group
    下所有实例订阅主体的单个分区,只能分配给组内某个Consumer实例消费。同一个分区消息可能被多个Group 消费。

二、Kafka消费者组解决了哪些问题?

传统的消息系统中,有两种消息引擎模型:点对点模型(消息队列)、发布/订阅模型
传统的两种消息系统各有优势,我们里对比一下:

  • 传统的消息队列模型的缺陷在于消息一旦被消费,就会从队列中删除,而且只能被下游的一个Consumer消费。严格的说这不是它的缺陷,
    这是它的一个特性。但很显然这种模型的伸缩性(Scalability)很差,因为下游的多个Consumer 都要“抢”
    这个共享消息队列的消息;
  • 发布/订阅模型,允许消息被多个Consumer 消费,但它的问题也是伸缩性不高,因为订阅者都必须订阅所有主体的所有分区。

Kafka 为规避传统消息两种模型的缺点,引入了 Consumer Group 机制:

  • Consumer Group 订阅多个主题后,组内的每个实例不要求一定要订阅主题的所有分区,它只会消费部分分区中的消息;
  • Consumer Group 之间彼此队里,互不影响,它们可以订阅同一组主题而互不干涉。加上Broker端的消息留存机制,KafkaConsumer Group 完美的避开了伸缩性差的问题;
  • kafka 是用Consumer Group机制,实现了,传统两大消息引擎。如果所有实例属于同一个Group,那么它实现的就是消息队列模型;如果所有实例分别属于不同的Group,且订阅了相同的主题,那么它就实现了发布/订阅模型;

三、Consumer Group 实例数量多少才合理?

最理想的情况是Consumer实例的数量应该等于该Group订阅主题的分区总数。例如:Consumer Group 订阅了 3个主题,分别是A、B、C,它们的分区数依次是1、2、3,那么通常情况下,为该Group 设置6Consumer实例是比较理想的情形。

阅读全文 »

举个例子

在讲重排序之前,先来看一个例子:

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
49
package com.cyblogs.thread;

import java.util.HashSet;
import java.util.Set;

/**
* Created with leetcode-cn
*
* @Description: 验证重排序代码
* @Author: chenyuan
* @Date: 2021/3/26
* @Time: 15:05
*/
public class VolatileSerialCase {

static int x = 0, y = 0;
static int a = 0, b = 0;

public static void main(String[] args) throws InterruptedException {
// 用set来保存数据,保证不会重复
Set<String> resultSet = new HashSet<String>();

for (int i = 0; i < 10000000; i++) {
x = 0;
y = 0;
a = 0;
b = 0;

Thread one = new Thread(() -> {
a = y;
x = 1;
});


Thread two = new Thread(() -> {
b = x;
y = 1;
});

one.start();
two.start();
one.join();
two.join();
// 等待2个线程都跑完了再把结果添加到Set中去
resultSet.add("a=" + a + ",b=" + b);
System.out.println(resultSet);
}
}
}

上面一段代码是非常经典来讲CPU对指令重排序的案例。因为我们经过一段时间的Run出的结果很惊讶:

1
[a=0,b=0, a=1,b=0, a=0,b=1, a=1,b=1]

对于a=1,b=1的出现,是会让人非常的奇怪的。出现这个情况,那代码执行的顺序可能是:

1
2
3
4
5
6
7
8
9
10
Thread one = new Thread(() -> {
a = y; // 第3步
x = 1; // 第1步
});

Thread two = new Thread(() -> {
b = x; // 第4步
y = 1; // 第2步
});
// 也就是说,在2个线程中,都出现了下面的代码执行到了上面的代码前面去了。

如果是这样子的话,那我们还敢写多线程的代码吗?如果没有一定的规范与约定,那肯定是没人可以写好代码。

其实这些约定都是在JSR-133内存模型与线程规范里面,它就像是Java的产品需求文档或者说明书。

http://static.cyblogs.com/Jietu20210327-174611.jpg

阅读全文 »

Paxos解决什么问题

大家对Paxos的看法基本是“晦涩难懂”,虽然论文和网上文章也很多,但总觉得“云山雾罩”,也不知道其具体原理以及到底能解决什么问题。

究其原因,一方面是很多Paxos的资料都是在通过形式化的证明去论证算法的正确性,自然艰深晦涩;另一方面,基于Paxos的成熟工程实践并不多。本章试图由浅入深,从问题出发,一点点地深入Paxos的世界。

一个基本的并发问题

先看一个基本的并发问题,如图116所示。假设有一个KV存储集群,三个客户端并发地向集群发送三个请求。请问,最后在get(X)的时候,X应该等于几?

http://static.cyblogs.com/Jietu20210228-000345.jpg

图116(K,V)集群多写答案是:X=1、X=3或X=5都是对的!但X=4是错的!因为从客户端角度来看,三个请求是并发的,但三个请求到达服务器的顺序是不确定的,所以最终三个结果都有可能。

这里有很关键的一点:把答案换一种说法,即如果最终集群的结果是X=1,那么当Client1发送X=1的时候,服务器返回X=1;当Client2发送X=3的时候,服务器返回X=1;当Client3发送X=5的时候,服务器返回X=1。相当于Client1的请求被接受了,Client2、Client3的请求被拒绝了。如果集群最终结果是X=3或者X=5,是同样的道理。而这正是Paxos协议的一个特点。

什么是“时序”

把问题进一步细化:假设KV集群有三台机器,机器之间互相通信,把自己的值传播给其他机器,三个客户端分别向三台机器发送三个请求,如图117所示。

阅读全文 »

Spring是一个非常优秀的开源项目,而且基本是互联网的标配。随着这几年的源码阅读习惯,有用一套自己的源码阅读笔记项目已经是水到渠成。今天就来在本地编译一份,以便于后面记录核心笔记用。

环境准备

1.准备好源代码

为什么贴这些url地址,是因为提醒我们所有人,官网与github是我们最先关注的地方。

gradle.properties

1
2
3
4
5
version=5.3.5-SNAPSHOT
org.gradle.jvmargs=-Xmx1536M
org.gradle.caching=true
org.gradle.parallel=true
kotlin.stdlib.default.dependency=false

从上面可以看出来,我们现在用到是spring 5.3.5-SNAPSHOT版本。后面会遇到一些编译问题,避免大家踩坑,我直接说。这里依赖的JDK需要在JDK11。

2.JDK准备

下载JDK11的版本:https://www.oracle.com/java/technologies/javase-jdk11-downloads.html

阅读全文 »

二值状态统计

这里的二值状态就是指集合元素的取值就只有 0 和 1 两种。在签到打卡的场景中,我们只用记录:

  • 签到(1)
  • 未签到(0)

所以它就是非常典型的二值状态,在签到统计时,每个用户一天的签到用 1 个 bit 位就能表示,一个月(假设是 31 天)的签到情况用 31 个 bit 位就可以,而一年的签到也只需要用 365 个 bit 位,根本不用太复杂的集合类型。

这个时候,我们就可以选择 Bitmap。这是 Redis 提供的扩展数据类型。我来给你解释一下它的实现原理。Bitmap 本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型。String 类型是会保存为二进制的字节数组,所以,Redis 就把字节数组的每个 bit 位利用起来,用来表示一个元素的二值状态。

你可以把 Bitmap 看作是一个 bit 数组。Bitmap 提供了 GETBIT/SETBIT 操作,使用一个偏移值 offsetbit 数组的某一个 bit 位进行读和写。不过,需要注意的是,Bitmap 的偏移量是从 0 开始算的,也就是说 offset 的最小值是 0。当使用 SETBIT 对一个 bit 位进行写操作时,这个 bit 位会被设置为 1。

Bitmap 还提供了 BITCOUNT 操作,用来统计这个 bit 数组中所有“1”的个数。那么,具体该怎么用 Bitmap 进行签到统计呢?我还是借助一个具体的例子来说明。

统计一个人签到

假设我们要统计 ID 3000 的用户在 2020 年 8 月份的签到情况,就可以按照下面的步骤进行操作。

  • 第一步,执行下面的命令,记录该用户 8 月 3 号已签到。

    1
    SETBIT uid:sign:3000:202008 2 1 
  • 第二步,检查该用户 8 月 3 日是否签到。

    1
    GETBIT uid:sign:3000:202008 2 
  • 第三步,统计该用户在 8 月份的签到次数。

    1
    BITCOUNT uid:sign:3000:202008
阅读全文 »
0%