前言
假设不存在数据库,数据都存在一个文本文件里,想搜索"腾讯云开发者社区",要怎么去搜索?
在Linux中,有grep、awk等命令来查找;使用一些高级语言,像Java,Python也可以通过IO流来读取一个文件。到这为止,有什么可以优化的点吗?
从外存,内存到缓存
在计算机当中,数据是存在磁盘的。当在Windows平台上进行解压缩文件操作时,打开任务管理器可看到清晰的数据读写,通常是MB/s的级别。
而内存呢?切到内存上,只能看见3200MT/s。换算后,内存与外存在带宽上就能相差几百倍。寻址能力更是相差几万倍。可见二者差距。
DDR4:常见的工作频率为2133MHz,单通道带宽可达25.6GB/s(2133MHz * 64bit/8)。 MT/s /2 = MHz;
回到开头的话题,抛出第一个疑问点:假如文件足够大时,硬盘(读写)率先成为瓶颈,代码该怎样去解决呢?先来看数据库的优化的做法。
数据库中的存储优化
现在有一张表,很多行数据,存在物理磁盘中。数据库中用了很多4k的小块,这里的4k和磁盘的对齐方式正好匹配,一次I/O对应一块区域。结合上图,可以想到数据库的建表中的索引,再加上数据库的独特的B 树结构,减少很多磁盘的寻址,IO的流量。同时,索引也是小块page。
索引就如查字典一样,无论多么复杂的字,都可以几步内检索到。
对齐方式验证:新建一个空白文档,输入一堆数字保存后 文件大小:1.14KB(不到4KB),占用空间:4KB。
缓存
B 树和data page就是万能方案了吗?并不是。假设有10个人,100个,1000个人来查字典,一本字典有点显得捉襟见肘。实际上,有很多人查的字是重复的,我们就可以让前来查字典的人先去问查过字典的人,这样就形成了缓存。
缓存在一定程度上弥补了硬盘的缺陷,当然也带来了更多的管理问题。就缓存用的数据库来说,Redis已经成为首选。
Redis
先来看看他有多火吧!
键值对型(key-value)的第一,总数据库的第六。
为什么是 Redis
在与前端进行交互时,较为常用的是JSON格式。那试着用这种方式与缓存交互?
存入数字,字符串,列表都不在话下,但取出来就犯愁了。一个大列表只取一条数据,I/O很快成为瓶颈。那么这种类似JSON一样传输文件的就是Memcache。
全部数据返回优点不太现实,于是尝试在“值”加入类型,再给于一定增删改查方法,这种数据向计算移动的做法。恭喜,到此已经入门了Redis的存储方式。
Redis 的字符串
先来看一段代码
代码语言:shell复制set key1 9 # 存一个key为key1,值为9的对象
object encoding key1 # 查看类型,结果是int
append key1 'a' # key1末尾追加字符串'a',
get key1 # 9a
object encoding key1 # raw
这就是Redis的字符串,get、set都是String类型的内置方法。但他又与传统的类型不同,从2和6行,变化了的编码方式可以看出来,这是一种动态类型的字符串,Redis用SDS来存储。
SDS(Simple Dynamic String)简单动态字符串。
重写字符串有什么好处呢?
①解决遗留问题
取得字符串长度是一个很常见的操作,C语言的实现方式是遍历(Redis由C开发),Redis直接用一个length来维护。
代码语言:c复制struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used:已经使用长度 */
uint8_t alloc; /* excluding the header and null terminator:简单理解为总长度 */
unsigned char flags; /* 3 lsb of type, 5 unused bits :SDS编码类型,当前类型为sdshdr8*/
char buf[]; /* 实际存储内容 */
};
②优化内存
从上述代码中还可以看到__attribute__ ((__packed__))
这样一段,其实是为了告诉编译器,不使用字节对其,而使用紧凑型分配内存。
③二进制安全
在IO中读写文件,分为两种,字符型和字节型。字符型代表就是txt文本。字节流就很多了,图片,java编译后的class文件。字节流可以通过一定编码格式变成字符,而反过来就很难操作。Redis正是采用字节流。
使用上有什么亮点呢?
比如去存储一个中国的“中”字,UTF-8编码会占三个字节,GBK编码就会占两个字节。假如你在链接时使用了--raw
,这样就解锁了ASCII编码,取出来的值又变成了其他方式。
团队在使用时要统一编码格式。 还有很多优化的点,篇幅有限,先略过。
云上的 Redis
笔者作为开发人员,只熟悉一些简单部署命令,很多时候需要一个面板来操作。加上Redis是内存数据库,加在后台服务的机器上,很影响性能,所以很有必要去云上选购一台Redis。
- 内网低延迟 无论是数据库或是其他服务,通过外网连接,网速都将会成为性能瓶颈。云厂商早就想到了这一点,所以勾选的时候注意服务器省市和分区。购买调节参数时,换配置会重置分区以及其他选项需注意。
- 全面的控制面板 自动备份,主备替换,一件导入配置等独特的功能满足基本所需。
- 数据看板 有种看到图表就掌控大全的舒适感。
案例:热门排行榜
给几个热门话题做一个排行榜,需要准备什么?
- 话题唯一
- 热度数值
- 实时排行
- 高频次的IO
Redis中恰好有这样一个数据类型,完美契合,那就是Sorted Set。
数据结构
大多数语言都有set结构,它是一种不重复元素的集合,而Redis的Sorted Set在唯一性的基础上,又增加了有序性。
底层结构
- listpack(7.0版本之前是ziplist)
- skiplist dict
代码语言:c复制这个顺序也代表着,前者适合较小范围的场景。
/* ZSETs use a specialized version of Skiplists: zsets使用一种特殊版本的skiplists */
typedef struct zskiplistNode {
sds ele;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned long span;
} level[];
} zskiplistNode;
- ele 真实数据,可以用来存储话题
- score 分数,可以存放热度值或其加权计算后的数值
具体操作
Java 中可以使用三种方式连接Redis
- Jedis
- Lettuce
- Redisson
- Spring Data Redis(基于Jedis)
由于API不同,所以下面主要来说具体Redis的命令。
向“热搜”榜单添加话题与分数
代码语言:shell复制ZADD '热搜' 1 "黑神话:悟空"
ZADD '热搜' 2 "腾讯云"
ZADD '热搜' 3 "云游戏"
获取热搜榜
代码语言:shell复制ZREVRANGE '热搜' 0 9 WITHSCORES
返回结果
代码语言:txt复制 1) "云游戏"
2) "3"
3) "腾讯云"
4) "2"
5) "黑神话:悟空"
6) "1"
更新热搜榜
代码语言:shell复制ZINCRBY '热搜' 999 "黑神话:悟空"
再去获取排行榜,已经变成了
代码语言:txt复制 1) "黑神话:悟空"
2) "1000"
3) "云游戏"
4) "3"
5) "腾讯云"
6) "2"
到现在为止,已经获得了一个简单的热搜榜。
one more thing
Redis的功能不止于此,这些都来自于他独特的数据类型以及架构。不过,最常用的还是缓存功能。经过常见八股文的洗礼,就可以成为一个高手!