Redis基础篇
一.起源
Redis作者antirez,2008年做网站访问记录,统计每天的用户量,页面浏览数,访客的IP,访客使用的操作系统等等。最开始用的是MySQL,实在太慢了,自己就写了基于内存的List,就是Redis。
为什么叫Redis? 全称Remote Dictionary Service。翻译成中文远程字典服务。
二.Redis的定位与特性
1.SQL与NoSQL
SQL关系型数据库特点:
- 以表格的形式并基于行存储数据,是一个二维的模式;
- 存储的是结构化的数据,数据存储具有固定的格式,数据需要适应表结构;
- 表于表之间存在关联关系;
- 大部分关系型数据库都支持
**SQL**
的操作,支持复杂的关联查询; - 通过支持
**ACID**
来提供严格或实时的数据一致性。
SQL关系型数据库缺点:
- 如果要实现扩容,就只能向上(垂直)扩容;例如磁盘限制了数据的存储,就需要通过扩展硬件的方式扩大磁盘容量。不支持动态扩缩容,水平扩容需要复杂的技术来实现,例如分库分表;
- 表结构修改困难导致存储的数据格式受到限制;
- 在高并发和高数据量的情况下,关系型数据库通常会把数据持久化到磁盘,但磁盘的读写压力会比较大。
为了解决关系型数据库带来的一些列问题,就出现了非关系型数据库。**NOSQL**
最开始不提供 **SQL**
** **数据库,在后来慢慢地发生了变化。
NoSQL非关系型数据库特点:
- 存储非结构化的数据(文本、图片、音频、视频等);
- 表与表之间没有关联,可扩展性强;
- 遵循
**BASE**
理论来保证数据的最终一致性;Basically Available
基本可用**Soft-State**
:软状态**Eventually Consistent**
最终一致性
- 支持海量数据的存储和高并发的高效读写操作;
- 支持分布式,对数据进行分片存储,扩缩容简单
NoSQL非关系型数据库的分类:
**Key-Value**
形式存储数据(**Redis、Memcache**
);- 文档存储(
**MongoDB**
); - 列存储(
**HBase**
); - 图存储(
Neo4j
) - 对象存储以及
**XML**
存储等
2.Redis 的特性
1、为什么要把数据放在内存中?
1)内存的速度更快,10w QPS 2)减少计算的时间,减轻数据库压力
2、如果是用内存的数据结构作为缓存,为什么不用HashMap或者Memcached?
1)更丰富的数据类型 2)支持多种编程语言 3)功能丰富:持久化机制、内存淘汰策略、事务、发布订阅、pipeline、lua 4)支持集群、分布式
3、Memcached和Redis的主要区别是什么?
Memcached只能存储KV、没有持久化机制、不支持主从复制、是多线程的。
3.Redis安装启动
1、服务安装
安装好 Redis 的服务: 1、《CentOS7 安装 Redis 6.0.9 单实例》 https://gper.club/articles/7e7e7f7ff3g5bgccg69
做完以后可以 clone 三台机器搭建 Setinel 架构: 2、《Redis6.0.9 一主二从 Sentinel 监控配置》 https://gper.club/articles/7e7e7f7ff3g5bgccg68
3、还可以在单机实例的机器上,再装一个伪集群: 《CentOS 7 单机安装 Redis Cluster6.0.9(3 主 3 从伪集群)》 https://gper.club/articles/7e7e7f7ff3g5bgcdg60
4、阿里云 CentOS7 Docker 安装 Redis https://gper.club/articles/7e7e7f7ff7g5egc5g6c er.club/ar ticles/7e7e7f7ff7g5egc5g
2、服务启动
src目录下,直接启动 ./redis-server
后台启动(指定配置文件) 1、redis.conf修改两行配置 daemonize yes bind 0.0.0.0 2、启动Redis redis-server /usr/local/soft/redis-6.0.9/redis.conf
总结:redis的参数可以通过三种方式配置,一种是redis.conf,一种是启动时携带参数,一种是config set。
3、客户端工具
Redis Desktop Manager
4、基本操作
Redis默认有16个库(0-15)。可以在配置文件redis.conf中修改。
代码语言:javascript复制database 16
因为没有完全隔离,不像数据库的 database,不适合把不同的库分配给不同的业务 使用。默认使用第一个db0。在集群里面只能使用第一个db。 切换数据库 select 0 清空当前数据库 flushdb 清空所有数据库 flushall
Redis的存储我们叫做key-value存储,或者叫做字典结构。key的最大长度限制是 512M,值的限制不同,有的是用长度限制的,有的是用个数限制的。 我们先从key的基本操作入手,包括大家最熟悉的get set。
命令怎么学习比较好?比如Linux命令,你们是怎么学习的?我有几个建议: 1、多练习,长时间不用肯定会忘。 2、分类成体系(千万不要按照字母顺序学),比如文件命令、网络命令、用户命令 Redis 可以按数据类型分。 3、学会查命令(Tab可以提示,–help可以查看参数)。
既然大家天天都做数据库的CRUD,我们来看看Redis的CRUD。 注意看返回值,有的情况下返回0或者nil代表是不成功的。
代码语言:javascript复制#存值(如果对同一个key set多次会直接覆盖旧值)
set qingshan 2673
#取值
get qingshan
#查看所有键
keys *
#获取键总数(生产环境数据量大,慎用)
dbsize
#查看键是否存在
exists qingshan
#删除键
del qingshan huihui
#重命名键
rename qingshan penyuyan
#查看类型
type qingshan
那么,Redis一共有几种数据类型?(注意我们说的是数据类型不是数据结构) String、Hash、Set、List、Zset、Hyperloglog、Geo、Streams
4.Redis基本类型
最基本也是最常用的数据类型就是String。set和get命令就是String的操作命令。 Reids的字符串被叫做二进制安全的字符串,为什么是Binary-safe Strings呢? 下面对于所有的数据类型我们都会从4个维度来分析:存储类型、操作命令、存储结构、应用场景。
1、String字符串**
存储类型 可以用来存储INT(整数)、float(单精度浮点数)、String(字符串)。
操作命令
代码语言:javascript复制# 获取指定范围的字符
getrange snail 0 1
# 获取值长度
strlen snail
# 字符串追加内容
append snail good
# 设置多个值(批量操作,原子性)
mset snail 0 xiaobai 666
# 获取多个值
mget snail xiaobai
# 设置值。如果key存在,则不成功
setnx snail 11
# 基于此可实现分布式锁。用del key释放锁。
# 但如果释放锁的操作失败了,导致其他节点永远获取不到锁,怎么办?
# 加过期时间。单独用expire加过期,也失败了,无法保证原子性,怎么办?多参数
set key value [expiration EX seconds|PX milliseconds][NX|XX]
#使用参数的方式
set k1 v1 EX 10 NX
# (整数)值递增(值不存在会得到1)
incr snail
incrby snail 100
# (整数)值递减
decr snail
decrby snail 100
# 浮点数增量
set mf 2.6
incrbyfloat mf 7.3
存储(实现)原理 数据模型
Redis是KV的数据库,Key-Value我们一般会用什么数据结构来存储它?哈希表。Redis 的最外层确实是通过 hashtable实现的(我们把这个叫做外层的哈希)。 在Redis里面,这个哈希表怎么实现呢?我们看一下C语言的源码(dict.h47行) 每个键值对都是一个 dictEntry(怪不得叫远程字典服务),通过指针指向key的存储结构和value的存储结构,而且next存储了指向下一个键值对的指针。
代码语言:javascript复制# dict.h 47行
typedef struct dictEntry {
void *key;/* key关键字定义*/
union {
void *val; /* value定义 */
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next; /*指向下一个键值对节点 */
} dictEntry;
实际上最外层是redisDb,redisDb里面放的是dict,后面hash我们会把这部分串起来,源码server.h 661行
代码语言:javascript复制typedef struct redisDb {
dict *dict; /* 所有的键值对 */ /* The keyspace for this DB */
dict *expires; /* 设置了过期时间的键值对 */ /* Timeout of keys with a timeout set */
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/
dict *ready_keys; /* Blocked keys that received a PUSH */
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
int id; /* Database ID */
long long avg_ttl; /* Average TTL, just for stats */
unsigned long expires_cursor; /* Cursor of the active expire cycle. */
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;
这里以set hello word为例,因为key是字符串,Redis自己实现了一个字符串类型,叫做SDS,所以hello指向了一个SDS的结构。
value是world,同样是一个字符串,是不是也用SDS存储呢? 当value存储一个字符串的时候,Redis并没有直接使用SDS存储,而是存储在redisObject中。 实际上五种常用的数据类型的任何一种的value,都是通过redisObject来存储的。 最终redisObject再通过一个指针指向实际的数据结构,比如字符串或者其他。
redisObject定义:
代码语言:javascript复制//源码src/server.h 622行
typedef struct redisObject {
unsigned type:4; /* 对象的类型,包含:OBJ_STRING、OBJ_LIST、OBJ_HASH、OBJ_SET、OBJ_ZSET*/
unsigned encoding:4; /* 具体的数据结构 */
/* 24位, 对象最后一次被命名程序访问的时间,与内存回收有关 */
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
int refcount; /* 引用计数。当refcount为0的时候,表示该对象已经不被任何对象引用,则可以进行垃圾回收了 */
void *ptr; /* 指向对象实际的数据结构 */
} robj;
用type命令看到的类型就是type的内容: type snail -> String 为什么一个value会有一钟对外的类型,还有一种实际的编码呢?我们刚才的字符串使用SDS存储,那么这个redisObject的value就会指向一个SDS:
那么实际的编码到底是什么呢?
代码语言:javascript复制set number 1
set snail "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
set xiaobai yes
type number
type xiaobai
type snail
object encoding number
object encoding snail
object encoding xiaobai
虽然对外都是String,用的String的命令,但是出现了三种不同的编码。 这三种编码有什么区别呢? 1、int ,存储8个自己的长整型(long , 2^63-1)。 2、embstr,代表embstr格式的SDS,存储小于44个字节的字符串。 3、raw,存储大于44个字节的字符串。
代码语言:javascript复制/* object.c*/
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
问题 1、什么是 SDS?
Redis 中字符串的实现。 在 3.2 以后的版本中,SDS 又有多种结构(sds.h):sdshdr5、sdshdr8、sdshdr16、sdshdr32、sdshdr64,用于存储不同的长度的字符串,分别代表 25=32byte,28=256byte,216=65536byte=64KB,232byte=4GB。
代码语言:javascript复制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 */ /* 当前字符数组的属性、用来标识到底是sdshdr8还是sdshdr16等 */
char buf[];/* 字符串真正的值 */
};
问题 2、为什么 Redis 要用 SDS 实现字符串
代码语言:javascript复制因为C语言没有字符串类型,只能用字符数组char[]实现。
1.使用字符数组必须先给目标变量分配足够的空间,否则可能会溢出。
2.如果要获取字符长度,必须遍历字符数组,时间复杂度O(n)。
3.C字符串长度的变更会对字符数组做内存重分配。
4.通过从字符串开始到结尾碰到的第一个'