非关系型数据库有关的知识,学习后端必备。

NoSQL

Not Only SQL,

关系型数据库与非关系型数据库的区别

关系型数据库以表格的形式存在,以行和列的形式存取数据,关系型数据库这一系列的行和列被称为表,无数张表组成了数据库

关系型数据库能够支持复杂的 SQL 查询,能够体现出数据之间、表之间的关联关系;关系型数据库也支持事务,便于提交或者回滚。

常见的关系型数据库有 Oracle、DB2、Microsoft SQL Server、MySQL等。

非关系型数据库(感觉翻译不是很准确)称为 NoSQL,也就是 Not Only SQL,不仅仅是 SQL。

非关系型数据库不需要写一些复杂的 SQL 语句,其内部存储方式是以 key-value 的形式存在可以把它想象成电话本的形式,每个人名(key)对应电话(value)。

非关系型数据库不需要经过 SQL 的重重解析,所以性能很高;非关系型数据库的可扩展性比较强,数据之间没有耦合性,遇见需要新加字段的需求,就直接增加一个 key-value 键值对即可。

常见的非关系型数据库主要有 Hbase、Redis、MongoDB 等。

从SQL到NoSQL

如果您是应用程序开发人员,则可能在使用关系数据库管理系统 (RDBMS) 和结构化查询语言 (SQL) 方面有一些经验。在您开始使用 Amazon DynamoDB 时,您既会遇到许多相似之处,也会遇到许多不同之处。NoSQL 是一个术语,用于描述高度可用的、可扩展的并且已针对高性能进行优化的非关系数据库系统。有别于关系模型,NoSQL 数据库(如 DynamoDB)使用替代模型进行数据管理,例如键-值对或文档存储。

Amazon DynamoDB 支持 PartiQL,后者一种与 SQL 兼容的开源查询语言,使您可以轻松、高效地查询数据,无论数据存储在何处或以何种格式存储。使用 PartiQL,您可以轻松处理关系数据库中的结构化数据、采用开放数据格式的半结构化和嵌套数据,甚至可以处理允许不同行中使用不同属性的 NoSQL 或文档数据库中的无模式数据。

Mongo

Mongo 是目前最流行的面向文档型数据库,

安装

mac 下命令行启动

brew tap mongodb/bew
brew install mongodb-community
brew services start mongodb-community

windows 下在 Mongo/bin 目录下运行

mongo

mongo shell 的命令行

show dbs     列出所有DB
use dbname   切换当前DB
show tables      列出当前DB的所有表/集合
show collections  列出当前DB的所有表/集合
show users   列出当前DB的所有用户
show profile 列出当前DB的所有慢查询
show logs     列出运行日志

执行命令

* db.serverStatus()                                查看mongod运行状态信息
* db.stats()                                       查看db元数据
* db.collection.stats()                            查看集合元数据
* db.collection.insert() / update / remove / find  对集合增删改查
* db.collection.createIndex()                      创建索引
* db.collection.dropIndex()                        删除索引
* db.dropDatabase()                                删除DB
* db.printReplicationInfo()
* db.printSlaveReplicationInfo()                   查看复制集同步信息
* rs.status()                                      查看复制集当前状态
* rs.conf()                                        查看复制集配置
* rs.initiate()                                    初始化复制集
* rs.reconfig()                                    重新配置复制集
* rs.add() / rs.remove()                           增加/删除复制集节点
* sh.enableSharding()                              对DB启用分片
* sh.shardCollection()                             对集合进行分片
* sh.status()                                      查看sharding状态信息

GUI 工具

mac 下使用 Robo 3T gui 工具

与 Sql 对比

基本概念

SQL 术语/概念 MongoDB 术语/概念 解析/说明
database database 数据库
table collection 数据表/集合
row document 数据记录/文档
column field 数据记录行/文档
index index 索引
table joins 表连接,MongoDB不支持
primary key primary key 主键,MongoDB自动将_id字段设置为主键

查询语句

mongo sql 说明
db.users.find() select * from users user表中查询所有数据
db.users.find({“username” : “joe”, “age” : 27}) select * from users where “username” = “joe” and age = 27 查找username = joeage = 27的人
db.users.find({}, {“username” : 1, “email” : 1}) select username, email from users 查找username,email2个子项
db.users.find({“age” : {“$gt” : 18}}) select * from users where age >18 查找age > 18的会员
db.users.find({“age” : {“$gte” : 18}}) select * from users where age >=18 查找age >= 18的人
db.users.find({“age” : {“$lt” : 18}}) select * from users where age <18 查找age < 18的人
db.users.find({“age” : {“$lte” : 18}}) select * from users where age <=18 查找age <= 18的人
db.users.find({“username” : {“$ne” : “joe”}}) select * from users where username <> “joe” 查找 username != joe的会员
db.users.find({“ticket_no” : {“$in” : [725, 542, 390]}}) select * from users where ticket_no in (725, 542, 390) 符合tickt_no在此范围的结果
db.users.find({“ticket_no” : {“$nin” : [725, 542, 390]}}) select * from users where ticket_no not in (725, 542, 390) 符合tickt_no不在此范围的结果
db.users.find({“name” : /joey^/}) select * from users where name like “joey%” 查找前4个字符为joey的人

基本操作

为什么 Mongo 使用 B 树/B+树

MongoDB如何优雅地删除大量数据

删除大量数据,无论是在哪种数据库中,都是一个普遍性的需求。除了正常的业务需求,我们需要通过这种方式来为数据库 “瘦身”。

为什么要 “瘦身” 呢?

  1. 表的数据量到达一定量级后,数据量越大,表的查询性能会越差。

    毕竟数据量越大,B + 树的层级会越高,需要的 IO 也会越多。

  2. 表的数据有冷热之分,将很多无用或很少用到的数据存储在数据库中会消耗数据库的资源。

    譬如会占用缓存;会增加备份集的大小,进而影响备份的恢复时间等。

所以,对于那些无用的数据,我们会定期删除。

对于那些很少用到的数据,则会定期归档。归档,一般是将数据写入到归档实例或抽取到大数据组件中。归档完毕后,会将对应的数据从原实例中删除。

一般来说,这种删除操作涉及的数据量都比较大。

对于这类删除操作,很多开发童鞋的实现就是一个简单的 DELETE 操作。看上去,简单明了,干净利落。

但是,这种方式,危害性却极大。

以 MySQL 为例:

  • 会造成大事务

    大事务会导致主从延迟,而主从延迟又会影响数据库的高可用切换。

  • 回滚表空间会不断膨胀

    在 MySQL 8.0 之前,回滚表空间默认是放到系统表空间中,而系统表空间一旦” 膨胀 “,就不会收缩。

  • 锁定的记录多

    相对而言,更容易导致锁等待。

即使是分布式数据库,如 TiDB,如果一次删除了大量数据,这批数据在进行 Compaction 时有可能会触发流控。

所以,对于线上的大规模删除操作,建议分而治之。具体来说,就是批量删除,每次只删除一部分数据,分多次执行。

在 MongoDB 中删除数据,可通过以下三种方式:

  • db.collection.remove()

    删除单个文档或满足条件的所有文档。

  • db.collection.deleteMany()

    删除满足条件的所有文档。

  • db.collection.bulkWrite()

    批量操作接口,可执行批量插入、更新、删除操作。

其中

  1. 执行最慢的是 remove,执行最快的是 bulkWrite,前者差不多是后者的 2.79 倍。
  2. deleteMany 和 bulkWrite 的执行效率差不多,但就语法而言,前者比后者简洁。

虽然是批量删除,但在 MySQL 中,如果没控制好节奏,还是很容易导致主从延迟。在 MongoDB 中,其实也有类似的担忧,不过我们可以通过 Write Concern 进行规避。

Write Concern,可理解为写安全策略,简单来说,它定义了一个写操作,需要在几个节点上应用(Apply)完,才会给客户端反馈

https://www.cnblogs.com/ivictor/p/15457454.html

缺点

不支持事务

Redis

redis 是用 c 语言开发的一个开源的高性能键值对数据库。它通过提供多种键值数据类型来满足不同场景下的存储需求

redis 的应用场景:

缓存(数据查询、短连接、新闻内容、商品内容等等)

分布式集群架构中的 session 分离

聊天室的在线好友列表

任务队列(秒杀、抢购、12306 等等)

应用排行榜

网站访问统计

数据过期处理(精确到毫秒)

安装

使用 linux wget 命令安装

wget http://download.redis.io/releases/redis-3.0.0.tar.gz

将 redis-3.0.0 tar 包拷贝到/usr/local 下

cp redis-3.0.0.tar.gz /usr/local

解压源码

tar -zxvf redis-3.0.0.tar.gz

进入解压后的目录,编译、安装到指定目录

cd /usr/local/redis-3.0.0
make PREFIX=/usr/local/redis install

Redis bin:

redis-benchmark:redis 性能测试工具

redis-check-aof:AOF 文件修复工具

redis-check-rdb:RDB 文件修复工具

redis-cli: redis 命令行客户端

redisconf:redis 配置文件

redis-sentinal:redis 集群管理工具

redis-server:redis

启动服务

redis - server;

Redis 的数据结构

redis 有五种数据结构:

string:简单的 kv 缓存。最常规的 set/get 操作,value 可以是 String 也可以是数字。一般做一些复杂的计数功能的缓存。

hash:存放对象。

list:有序列表,可以存储列表型数据

set:无序集合,自动去重

zset:排序的 set,可以自定义排序规则。sorted set 多了一个权重参数 score,集合中的元素能够按 score 进行排列。可以做排行榜应用,取 TOP N 操作。

redis 内部结构

  • dict 本质上是为了解决算法中的查找问题(Searching)是一个用于维护 key 和 value 映射关系的数据结构,与很多语言中的 Map 或 dictionary 类似。本质上是为了解决算法中的查找问题(Searching)
  • sds sds 就等同于 char * 它可以存储任意二进制数据,不能像 C 语言字符串那样以字符’\0’来标识字符串的结 束,因此它必然有个长度字段。
  • skiplist (跳跃表) 跳表是一种实现起来很简单,单层多指针的链表,它查找效率很高,堪比优化过的二叉平衡树,且比平衡树的实现,
  • quicklist
  • ziplist 压缩表 ziplist 是一个编码后的列表,是由一系列特殊编码的连续内存块组成的顺序型数据结构,

为什么 Redis 使用单线程模型,在 4.0 之后的版本加了多线程

Redis 作为一个内存服务器,它需要处理很多来自外部的网络请求,它使用 I/O 多路复用机制同时监听多个文件描述符的可读和可写状态,一旦收到网络请求就会在内存中快速处理,由于绝大多数的操作都是纯内存的,所以处理的速度会非常地快。

Redis 4.0 之后的版本,情况就有了一些变动,新版的 Redis 服务在执行一些命令时就会使用『主处理线程』之外的其他线程,例如 UNLINKFLUSHALL ASYNCFLUSHDB ASYNC 等非阻塞的删除操作。

为什么大部分命令使用单线程

Redis 从一开始就选择使用单线程模型处理来自客户端的绝大多数网络请求,这种考虑其实是多方面的,作者分析了相关的资料,发现其中最重要的几个原因如下:

  1. 使用单线程模型能带来更好的可维护性,方便开发和调试;
  2. 使用单线程模型也能并发的处理客户端的请求;
  3. Redis 服务中运行的绝大多数操作的性能瓶颈都不是 CPU;

Redis 选择使用单线程模型处理客户端的请求主要还是因为 CPU 不是 Redis 服务器的瓶颈,所以使用多线程模型带来的性能提升并不能抵消它带来的开发成本和维护成本,系统的性能瓶颈也主要在网络 I/O 操作上;而 Redis 引入多线程操作也是出于性能上的考虑,对于一些大键值对的删除操作,通过多线程非阻塞地释放内存空间也能减少对 Redis 主线程阻塞的时间,提高执行的效率。

https://draveness.me/whys-the-design-redis-single-thread/

Redis 的过期策略

设置过期时间

在设置 key 时为 key 设置一个过期时间,到期后缓存就会失效,

定时删除

redis 默认每秒会进行 10 次过期扫描,但不是扫描所有的 key,而是使用一种简单的贪心政策:

从过期字典里随机选取 20 个 key;

删除这 20 个 key 中已经过期的 key;

如果过期的 key 比率超过 1/4,则重复步骤一。

这样保证了过期扫描不会循环过度,redis 还增加了扫描时间的上限,默认不会超过 25ms

惰性删除

在获取某个 key 时,redis 会检查一下,如果这个 key 设置了过期时间并且已经过期了,就会删除这个 key,不会返回任何数据。

内存淘汰策略

redis 为用户提供了几种内存淘汰策略:

Noeviction:内存不足以容纳新数据时,新写入的数据会报错,del 和 read 请求可以正常执行。这是默认的淘汰策略。

Alleys-lru:当内存不足以容纳新数据时,移除最近最少使用的 key(针对全部的 key,没有设置过期时间的 key 也会被淘汰)

Alleys-random:当内存不足以容纳新数据时,随机移除某个 key(针对全部的 key)

Volatile-lru:当内存不足以容纳新数据时,在设置了过期时间的 key 中,移除最近最少使用的 key

Volatile-random:当内存不足以容纳新数据时,在设置了过期时间的 key 中,随机移除 key

Volatile-ttl:当内存不足以容纳新数据时,在设置了过期时间的 key 中,优先移除有更早过期时间的 key

持久化

Redis 有 RDB(快照)和 AOF(日志)两种持久化的机制,RDB 是一次全量备份,AOF 是连续增量备份

RDB 会生成多个数据文件,每个数据文件都代表某个时刻中 redis 的数据。但是使用 RDB 进行持久化是每隔一段时间生成一次数据文件,如果宕机,就会丢失一段时间的数据。

redis 底层使用操作系统的多进程机制 COW 机制来实现 RDB 持久化,redis 在持久化的时候会调用 glibc 的函数 fork 一个子进程,RDB 持久化过程完全交给子进程处理,父进程继续处理客户端的需求。当父进程对某个页面的数据进行修改时,COW 机制会将页面复制一份出来,父页面修改的是复制出来的页面,子进程的页面是没有变化的,还是进程产生的数据,子进程可以安心地遍历数据进行序列化写磁盘,这就是 RDB 持久化被称为快照的原因。

AOF 记录的 redis 服务器的顺序指令序列,只会记录对内存修改的指令记录到日志文件中,在 redis 重启之后就通过 AOF 日志的命令重放,以此来重新构建整个数据集。

AOF 的日志会越来越长,在 redis 重启回复数据时重放整个 AOF 日志也会很耗时,导致长时间 redis 无法对外提供服务,所以 redis 提供了指令对 AOF 日志进行瘦身,进行瘦身时会基于当前内存中的数据,来重写构造一个更小的 AOF 文件,并将旧的膨胀的很大的日志文件删除。

缓存雪崩与缓存穿透、缓存击穿、缓存预热

缓存雪崩是指缓存整体崩溃,导致请求全部走到数据库,数据库崩溃同时导致系统崩溃

解决方案:

使用 redis-cluster 实现高可用,防止 redis 崩溃

使用本地缓存 ehcache 和 hystrix 限流、降级等,防止大量请求到后台

使用 redis 持久化,在 redis 崩溃重启后能快速回复数据

缓存穿透:

缓存穿透是指别人发送的恶意请求,每次在缓存和数据库中查询数据都查不到,这样就会导致大量的恶意请求直接将数据库搞崩

解决方案:每次到数据库中查询时就算没查到数据也设置一个空值到缓存中。

缓存击穿:

是指一个 key 非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个 key 在失效的瞬间,持续的大并发就穿破缓存,直接请求数据。

解决方案:

两种方式:

  1. 不设置过期时间。在设置热点 key 的时候,不给 key 设置过期时间即可。不过还有另外一种方式也可以达到 key 不过期的目的,就是正常给 key 设置过期时间,不过在后台同时启一个定时任务去定时地更新这个缓存。
  2. 使用了加锁的方式,锁的对象就是 key,这样,当大量查询同一个 key 的请求并发进来时,只能有一个请求获取到锁,然后获取到锁的线程查询数据库,然后将结果放入到缓存中,然后释放锁,此时,其他处于锁等待的请求即可继续执行,由于此时缓存中已经有了数据,所以直接从缓存中获取到数据返回,并不会查询数据库

缓存预热

就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

解决思路:

  • 直接写个缓存刷新页面,上线时手工操作下;
  • 数据量不大,可以在项目启动的时候自动进行加载;
  • 定时刷新缓存;

缓存更新

除了缓存服务器自带的缓存失效策略之外(Redis 默认的有 6 中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:

  • 定时去清理过期的缓存;
  • 当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。

两者各有优劣,第一种的缺点是维护大量缓存的 key 是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。

缓存降级

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。

降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)

以参考日志级别设置预案:

  • 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
  • 警告:有些服务在一段时间内成功率有波动(如在 95~100%之间),可以自动降级或人工降级,并发送告警;
  • 错误:比如可用率低于 90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
  • 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

服务降级的目的,是为了防止 Redis 服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis 出现问题,不去数据库查询,而是直接返回默认值给用户。

Memcache 与 Redis 的区别都有哪些?

1)、存储方式 Memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。Redis 有部份存在硬盘上,redis 可以持久化其数据

2)、数据支持类型 memcached 所有的值均是简单的字符串,redis 作为其替代者,支持更为丰富的数据类型 ,提供 list,set,zset,hash 等数据结构的存储

3)、使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

4). value 值大小不同:Redis 最大可以达到 512M;memcache 只有 1mb。

5)redis 的速度比 memcached 快很多

6)Redis 支持数据的备份,即 master-slave 模式的数据备份。

单副本与多副本

redis 单副本,采用单个 redis 节点部署架构,没有备用节点实时同步数据,不提供数据持久化和备份策略,适用于数据可靠性要求不高的纯缓存业务场景。

优点:

  • 架构简单,部署方便
  • 高性价比:缓存使用时无需备用节点(单实例可用性可以用 supervisor 或 crontab 保证),当然为了满足业务的高可用性,也可以牺牲一个备用节点,但同时刻只有一个实例对外提供服务
  • 高性能

缺点:

  • 不保证数据的可靠性
  • 在缓存使用,进程重启后,即使由备用的节点解决高可用性,但是仍然不能解决缓存预热的问题,因此不适合数据可靠性要求高的业务
  • 高性能受限于单核 CPU 的处理能力 (redis 是单线程机制),CPU 成为主要瓶颈,所以适合操作命令简单、排序、计算较少的场景。也可以考虑用 memcached 替代

多副本

redis 多副本,采用主从 (replication) 部署架构,相较于单副本而言最大的特定就是主从实例间数据实时同步,并且提供数据持久化和备份策略。主从实例部署在不用物理服务器上,根据公司的基础环境配置,可以实现同时对外提供服务和读写分离策略

优点:

  • 高可靠性:

    • 一方面,采用双机主备架构,能够在主库出现故障的时候自动进行主备切换,从库提升为主库提供服务,保证服务平稳运行;
    • 另一方面,开启数据持久化功能和配置合理的备份策略,能有效的解决数据误操作和数据异常丢失的问题

缺点:

  • 故障恢复复杂,如果没有 RedisHA 系统(需要开发),当主库节点出现故障时,需要手动将一个从节点晋升为主节点,同时需要通知业务方变更配置,并且需要让其他重复节点去复制新主库节点,整个过程需要认为干预,比较繁琐
  • 主库的写能力受到单机的限制,可以考虑分片
  • 主库的存储能力受到单机的限制,可以考虑 Pika;
  • 原生复制的弊端在早期的版本中会比较突出,比如:redis 复制中断后,slave 会发起 psync,此时如果同步不成功,则会进行增量同步,主库提前执行全量备份的同时可能会造成毫秒或者秒级的卡顿;又由于 COW 机制,导致极端情况下的主库内存会溢出,程序异常退出或者宕机;主库节点生成备份文化导致服务器磁盘 IO 和 CPU (压缩) 资源消耗;发送数 GB 大小的备份文件会导致服务器出口带宽暴增,阻塞请求;建议升级到最新版本

多机 redis 如何保证数据一致

主从复制,读写分离

一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。

redis 事务

Redis 事务功能是通过 MULTI、EXEC、DISCARD 和 WATCH 四个原语实现的

Redis 会将一个事务中的所有命令序列化,然后按顺序执行。

  1. redis 不支持回滚“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
  2. 如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
  3. 如果在一个事务中出现运行错误,那么正确的命令会被执行。

注:redis 的 discard 只是结束本次事务,正确命令造成的影响仍然存在.

1)MULTI 命令用于开启一个事务,它总是返回 OK。MULTI 执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当 EXEC 命令被调用时,所有队列中的命令才会被执行。

2)EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值 nil 。

3)通过调用 DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。

4)WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到 EXEC 命令。

事务包含三个阶段:

  1. 事务开启,使用 MULTI , 该命令标志着执行该命令的客户端从非事务状态切换至事务状态 ;
  2. 命令入队,MULTI 开启事务之后,客户端的命令并不会被立即执行,而是放入一个事务队列 ;
  3. 执行事务或者丢弃。如果收到 EXEC 的命令,事务队列里的命令将会被执行 ,如果是 DISCARD 则事务被丢弃。

Redis 事务包含两种模式 : 事务模式Lua 脚本

Redis 的事务模式具备如下特点:

  • 保证隔离性;
  • 无法保证持久性;
  • 具备了一定的原子性,但不支持回滚;
  • 一致性的概念有分歧,假设在一致性的核心是约束的语意下,Redis 的事务可以保证一致性。

但 Lua 脚本更具备实用场景,它是另一种形式的事务,他具备一定的原子性,但脚本报错的情况下,事务并不会回滚。Lua 脚本可以保证隔离性,而且可以完美的支持后面的步骤依赖前面步骤的结果

Lua 脚本模式的身影几乎无处不在,比如分布式锁、延迟队列、抢红包等场景

使用 Lua 脚本的好处 :

  • 减少网络开销。将多个请求通过脚本的形式一次发送,减少网络时延。
  • 原子操作。Redis 会将整个脚本作为一个整体执行,中间不会被其他命令插入。
  • 复用。客户端发送的脚本会永久存在 Redis 中,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。

Redis Lua 脚本常用命令:

序号 命令及描述
1 EVAL script numkeys key [key ...] arg [arg ...] 执行 Lua 脚本。
2 EVALSHA sha1 numkeys key [key ...] arg [arg ...] 执行 Lua 脚本。
3 SCRIPT EXISTS script [script ...] 查看指定的脚本是否已经被保存在缓存当中。
4 SCRIPT FLUSH 从脚本缓存中移除所有脚本。
5 SCRIPT KILL 杀死当前正在运行的 Lua 脚本。
6 SCRIPT LOAD script 将脚本 script 添加到脚本缓存中,但并不立即执行这个脚本。

redis 线程模型

为什么 Redis 的操作是原子性的,怎么保证原子性的?

对于 Redis 而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。

Redis 的操作之所以是原子性的,是因为 Redis 是单线程的。(Redis 新版本已经引入多线程,这里基于旧版本的 Redis)

Redis 本身提供的所有 API 都是原子操作,Redis 中的事务其实是要保证批量操作的原子性。

多个命令在并发中也是原子性的吗?

不一定, 将 get 和 set 改成单命令操作,incr 。使用 Redis 的事务,或者使用 Redis+Lua==的方式实现.

redis 分布式锁

Redis 为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对 Redis 的连接并不存在竞争关系 Redis 中可以使用 SETNX 命令实现分布式锁。

将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 SETNX 不做任何动作

解锁:使用 del key 命令就能释放锁

解决死锁:

  • 通过 Redis 中 expire()给锁设定最大持有时间,如果超过,则 Redis 来帮我们释放锁。
  • 使用 setnx key “当前系统时间+锁持有的时间”和 getset key “当前系统时间+锁持有的时间”组合的命令就可以实现。

错误:Warning: no config file specified, using the default config. In order to specify a config file

redis-server redis.windows.conf

错误:creating server tcp listening socket 127.0.0.1:6379: bind No error

Redis - cli.exe;
shutdown;
exit;

redis集群

Redis 3.0 以后,节点之间通过去中心化的方式提供了完整的 sharding、replication (复制机制仍复用原有机制,只是 cluster 具备感知主备的能力)、failover 解决方案,称为 Redis Cluster。即将 proxy/sentinel 的工作融合到了普通的 Redis 节点里。

一个 Redis Cluster 由多个 Redis 节点组构成。不同节点组服务的数据无交集(每一个节点组对应数据 sharding 的一个分片)。节点组内部为主备两类节点,两者数据准实时一致,通过异步化的主备复制机制保证。一个节点组有且仅有一个 master 节点(读写服务),同时有 0 到多个 slave 节点(读服务)。

Redis Cluster 通过分片的方式保存键值对,整个数据库被分为 16384 个槽(slot),数据库中的键都属于这 16384 个槽中的一个,每个节点可以处理 0 个或最多 16384 个槽(每个节点处理的槽都是不相同的)。

https://juejin.cn/post/7032067553291665421

https://www.cnblogs.com/jian0110/p/14002555.html

数据分片

redis cluster3.0 自带的集群,特点在于他的分布式算法不是一致性 hash,而是 hash 槽的概念,以及自身支持节点设置从节点

Redis 集群有 16384 个哈希槽 , 每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽. 集群的每个节点负责一部分 hash 槽 , 举个例子 , 比如当前集群有 3 个节点 , 那么:

节点 A 包含 0 到 5500 号哈希槽

节点 B 包含 5501 到 11000 号哈希槽.

节点 C 包含 11001 到 16384 号哈希槽.

这种结构很容易添加或者删除节点. 比如如果我想新添加个节点 D, 我需要从节点 A, B, C 中得部分槽到 D 上. 如果我像移除节点 A, 需要将 A 中得槽移到 B 和 C 节点上 , 然后将没有任何槽的 A 节点从集群中移除即可.

由于从一个节点将哈希槽移动到另一个节点并不会停止服务 , 所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态.

主从复制模型

为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型 , 每个节点都会有 N-1 个复制品.

在我们例子中具有 A,B,C 三个节点的集群 , 在没有复制模型的情况下 , 如果节点 B 失败了,那么整个集群就会以为缺少 5501-11000 这个范围的槽而不可用.

然而如果在集群创建的时候(或者过一段时间)我们为每个节点添加一个从节点 A1,B1,C1, 那么整个集群便有三个 master 节点和三个 slave 节点组成,这样在节点 B 失败后,集群便会选举 B1 为新的主节点继续服务,整个集群便不会因为槽找不到而不可用了

不过当 B 和 B1 都失败后,集群是不可用的.

https://blog.51cto.com/server110/1922969

redis 集群方案

1.twemproxy,大概概念是,它类似于一个代理方式, 使用时在本需要连接 redis 的地方改为连接 twemproxy, 它会以一个代理的身份接收请求并使用一致性 hash 算法,将请求转接到具体 redis,将结果再返回 twemproxy。

缺点:twemproxy 自身单端口实例的压力,使用一致性 hash 后,对 redis 节点数量改变时候的计算值的改变,数据无法自动移动到新的节点。

2.codis,目前用的最多的集群方案,基本和 twemproxy 一致的效果,但它支持在 节点数量改变情况下,旧节点数据可恢复到新 hash 节

GUI

https://github.com/RedisInsight/RedisInsight

Garnet

https://github.com/microsoft/garnet

Garnet 是微软研究院基于 C# .NET8.0 开发的一种新型远程缓存存储系统,它设计目的是实现极速、可扩展和低延迟。Garnet 能够在单节点内进行线程扩展,并支持分片集群执行,具备复制、检查点、故障转移和事务处理功能。它可以在主内存以及分层存储(如 SSD 和 Azure 存储)上运行。Garnet 提供丰富的 API 接口和强大的可扩展性模型。

Garnet 使用 Redis 的 RESP 协议作为其主要通信协议,因此可以使用大多数编程语言中现成的 Redis 客户端,例如 C# 中的 StackExchange.Redis。与其他开源缓存存储相比,Garnet 在性能、延迟、可扩展性和持久性方面都有显著提升。

需要注意的是,Garnet 是微软研究院的一个研究项目,应当作为研究项目来对待。尽管如此,我们是一群对此充满热情的研究人员和开发人员,目前正全职工作于此,以使其尽可能稳定和高效。我们的目标是围绕 Garnet 建立一个活跃的社区。实际上,Garnet 的质量已经足够高,以至于微软的几个一线团队和平台团队已经在内部部署了 Garnet 多个月。

https://www.cnblogs.com/InCerry/p/18083820/garnet_introduce

如果你觉得我的文章对你有帮助的话,希望可以推荐和交流一下。欢迎關注和 Star 本博客或者关注我的 Github