Redis底层原理--02. 内存映射数据结构
Redis底层原理--01. Redis 中的数据结构
简单的字符串
1. 设计要点
在 C 语言中,字符串可以用一个 \0 结尾的 char 数组来表示。 比如说,hello world 在 C 语言中就可以表示为 “hello world\0” 。
这种简单的字符串表示在大多数情况下都能满足要求,但是,它并不能高效地支持长度计算和 追加(append)这两种操作:
每次计算字符串长度(strlen(s))的复杂度为 θ(N) 。
对字符串进行 N 次追加,必定需要对字符串进行 N 次内存重分配(realloc)。
在 Redis 内部,字符串的追加和长度计算并不少见,而 APPEND 和 STRLEN 更是这两种操 作在 Redis 命令中的直接映射,这两个简单的操作不应该成为性能的瓶颈。
另外,Redis 除了处理 C 字符串之外,还需要处理单纯的字节数组,以及服务器协议等内容, 所以为了方便起见,Redis 的字符串表示还应该是二进制安全的:程序不应对字符串里面保存 的数据做任何假设,数据可以是以 \0 结尾的 C 字符串,也可以是单纯的字节数组,或者其他 格式的数据。
考虑到这两个原因,Redis 使用 sds 类型替换了 C 语言的默认字符串表示: sds 既可以高效地 实现追加和长度计算,并且它还是二进制安全的。
2. sds 数据结构
Redis系列--布隆过滤器
在 Redis 的使用场景中,基本的架构图如下:
如果在缓存中查询不到数据,会直接到 DB 中查询,查询的数据再插入到缓存中。例如我们根据 orderId 查询对应的订单,具体伪代码如下:
1 | OrderEntity getOrder(String orderId){ |
如果这是时候客户端查询的 orderId 绝大多数都不在缓存中,这样就会带来一个 缓存穿透 的问题。如果有客户端恶意攻击,例如客户端使用 UUID 来访问我们的数据或者接口,势必会导致访问压力都落到 DB 上面,导致 DB 压力的上升。
为了解决这个缓存穿透,可以在 Redis 和 DB 中间增加一个过滤器,在访问 DB 前询问下过滤器,然后再决定是否查询 DB,具体结构图如下:
hash过滤
Hash 是一个简单 key — value 结构,如果在数据量不大的情况下,可以尝试把 DB 中的数据中的单列存储在缓存中
1 | 00001 1 |
在客户端访问时,可以先到 hash 中看下是否有值,然后根据过滤器返回值来确定是否查询 DB。
使用 Docker-Compose 打包多个应用
准备工作
这次部署两个项目,一个是 web 站点项目,一个是爬虫的后台应用项目。
- 两个项目的 jar 包,分别为 app.jar,spider.jar
- 两个应用都依赖 redis 和 mysql
- mysql 初始化需要的 sql 文件
docker file 文件
docker file 是 使用 docker 部署应用的命令。 具体的命令可以参考 Docker 命令.
由于是两个文件,所以需要两个不同的 Dockerfile
.具体的 build 的内容如下:
Dockerfile web 的 build 文件
1 | FROM java:8 |
爬虫的文件
一个正则表达式导致 CPU 高的问题排查过程
这篇文章记录一个正则表达是导致 CPU 高的问题排查。由于无法直接使用线上的代码测试,所以我自己把代码整理了下来,具体代码如下:
1 | public class AppMain { |
当运行程序的时候,我们可以看到 java 的进程占用了 CPU 了 82.1%
,由于我使用的服务器是 1核+2G, 所以 load avg 占用也很高。
使用 top -H -p 4214 查看各个线程占用的情况
hex
使用 printf '%x\n' 4217
把进程转成 16 进制值为 1079。执行 jstack 4214|grep 1079 -A 100
得到线程的堆栈信息:
1 | "main" #1 prio=5 os_prio=0 tid=0x00007f943004c800 nid=0x1079 runnable [0x00007f9439fe0000] |
从上面的堆栈信息可以看出来是正则的递归调用,导致了很深的堆栈。
查看最终的堆栈入口:at org.rz.search.spider.AppMain.main(AppMain.java:13)
可以断定问题是正则匹配的原因。
为什么一个正则会导致CPU飙高?
Linux TOP 命令详解
TOP 命令的含义
TOP 命令是常用的 Linux 性能监控的命令,执行后,界面如下:
第一行
1 | top - 14:09:04 up 3 days, 21:20, 0 users, load average: 0.52, 0.58, 0.59 |
当前时间(date)、系统已运行时间(last reboot)、当前登录用户的数量(who )、最近5、10、15分钟内的平均负载
CPU load的含义
load average: 0.52, 0.58, 0.59
代表 1min
5min
15min
的 CPU 的平均负载
一般超过 1 代表拥堵,正常控制在 0.7 以下。如果是多核心,需要除于核心数。
这个负载的含义是什么呢? 0.52 代表是什么意思?
简单聊聊 MyCat 分库分表
MySQL 分库分表的方式
对于分库分表来说,具体有两种方式:垂直拆分和水平拆分
。
垂直拆分主要是业务的细化和独立,和业务联系比较密切。所以本文只讨论更通用的水平拆分。
为什么分库分表
- 降低单机 MySQL 的性能
- 降低单表或者单库的数据量,减少数据库的查询压力
- 突破单机的容量限制
分库分表的方式
范围区分(range)
:按月\按区\按其他的等特殊的属性维度进行分片预定义范围
:预估有多少数据的容量,对数据进行范围的分配,0-100->A 101-200->B取模 Hash
:对指定的字段进行取模运算,匹配对应的库和表。
分库分表带来的问题
- 数据的维护成本高
- 跨库的业务join
- 分布式事务的性能低下
- 自增 id 的生成问题
- 非分片字段查询的轮询的浪费
- 多节点排序问题
分库分表的中间件
对于分库分表的中间件有很多,Shardingsphere,Tddl,MyCat,cobar。从架构上分,主要分为两种:JDBC应用方式
和Proxy模式
。
JDBC应用模式
是基于客户端的分片,有客户端根据Sql
和规则,决定具体执行的 sql 的服务器。代表有Shardingsphere,Tddl
JDBC应用模式 优点:
MySQL 索引失效问题
索引失效的情况:
- 使用 like ‘%abc’或者like ‘%abc%’
- 查询列参与了函数计算(并没有使用函数索引)
- 数据不够离散,扫描的行数和加载索引的成本超过了全表扫描
- 联合索引没有使用最左匹配,或者在范围运算(>,<,<>)等运算的后面
- where中索引列有运算
除了上面的几个明显的问题外,还有索引的选择问题。MySQL 在执行一段 sql 的时候,会先决定使用哪一个索引,如果 选了一个性能比较差的索引,即使走了索引,也会带来性能问题。
对上面的第 4 条做一个例子说明:
- 定义 abcd 字段一个联合索引
- 如果使用 a>0 and b=1 .. 则 a 本身走索引,但 a 后面的字段都不走索引
- a=1 and b=1 and c>1 and d=1 这个例子 只有 d 不走索引,如果 索引顺序更改为 abdc 则都会走索引。
准备工作
1 | create database ITTest; |