redis 禁用O(n) 复杂度方法

某公司技术部发生2起本年度PO级特大事故,造成公司资金损失400万,原因如下:

由于工程师直接操作上线redis,执行:

keys * wxdb(此处省略)cf8*

这样的命令,导致redis锁住,导致CPU飙升,引起所有支付链路卡住,等十几秒结束后,所有的请求流量全部挤压到了rds数据库中,使数据库产生了雪崩效应,发生了数据库宕机事件。

redis开发规范中有一条铁律如下所示:

线上Redis禁止使用Keys正则匹配操作!

线上执行正则匹配操作,引起缓存雪崩,最终数据库宕机的原因:

  1. redis是单线程的,其所有操作都是原子的,不会因并发产生数据异常;

  2. 使用高耗时的Redis命令是很危险的,会占用唯一的一个线程的大量处理时间,导致所有的请求都被拖慢。(例如时间复杂度为O(N)的KEYS命令,严格禁止在生产环境中使用);

有上面两句作铺垫,原因就显而易见了!

运维人员进行keys *操作,该操作比较耗时,又因为redis是单线程的,所以redis被锁住;

此时QPS比较高,又来了几万个对redis的读写请求,因为redis被锁住,所以全部Hang在那;

因为太多线程Hang在那,CPU严重飙升,造成redis所在的服务器宕机;

所有的线程在redis那取不到数据,一瞬间全去数据库取数据,数据库就宕机了;

需要注意的是,同样危险的命令不仅有keys *,还有以下几组:

FLUSHALL  清空整个 Redis 服务器的数据(删除所有数据库的所有 key )。
FLUSHDB   清空当前数据库中的所有 key。
CONFIG SET  调整 Redis 服务器的配置(configuration)而无须重启。

因此,一个合格的redis运维或者开发,应该懂得如何禁用上面的命令。所以我一直觉得出现新闻中那种情况的原因,一般是人员的水平问题。

如何禁用redis命令

就是在redis.conf中,在SECURITY这一项中,我们新增以下命令:

rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command KEYS ""
rename-command CONFIG ""

另外,对于FLUSHALL命令,需要设置配置文件中appendonly no,否则服务器是无法启动。

注意了,上面的这些命令可能有遗漏,大家可以查官方文档。除了Flushdb这类和redis安全隐患有关的命令意外,但凡发现时间复杂度为O(N)的命令,都要慎重,不要在生产上随便使用。例如hgetall、lrange、smembers、zrange、sinter等命令,它们并非不能使用,但这些命令的时间复杂度都为O(N),使用这些命令需要明确N的值,否则也会出现缓存宕机。

改良建议

业内建议使用scan命令来改良keys和SMEMBERS命令:

Redis2.8版本以后有了一个新命令scan,可以用来分批次扫描redis记录,这样肯定会导致整个查询消耗的总时间变大,但不会影响redis服务卡顿,影响服务使用。

具体使用,大家详情可以自己查阅下面这份文档:

http://doc.redisfans.com/key/scan.html

代码实例:

# php redis 扩展
$redis = new Redis();
$redis->connect(config('database.redis.default.host'), config('database.redis.default.port'));
$redis->auth(config('database.redis.default.password'));
$redis->select(config('database.redis.default.database'));
$redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
$it = NULL;
while($arr_keys = $redis->hScan($Key, $it)) {
    foreach($arr_keys as $str_field => $str_value) {
          //TODO  
    }
}

# laravel  predis 驱动

$it = NULL;
while($arr_keys = Redis::hScan($Key, $it)) {
    foreach($arr_keys[1] as $str_field => $str_value) {
        //TODO
        $it = $arr_keys[0];
        if($arr_keys[0] == 0){
            break;
        }
    }
}

Ubuntu 18.04 手动安装最新版 Redis

Redis ,全称 REmote DIctionary Server ,是一个由 Salvatore Sanfilippo 编写的开源高性能 key-value 存储系统。Redis 是基于内存的 Key-Value 数据库,比 Memcache 更先进,支持多种数据结构,高效,快速。用 Redis 可以很轻松解决高并发的数据访问问题,做为时时监控信号处理也非常不错。本文详细介绍如何在 Ubuntu 18.04 上手动安装最新版 Redis 。以下操作是在 root 账号下进行的,非 root 账号需提升到 root 权限。

安装 Redis

Ubuntu 18.04 默认源中的 Redis 版本不是最新版,要想通过 apt-get install 的方式安装最新版,首先添加 Redis 源。

添加 Redis 源

首先安装依赖:

apt-get install software-properties-common -y

使用如下命令添加 Redis 源:

add-apt-repository ppa:chris-lea/redis-server -y

安装 Redis

使用如下命令安装 Redis:

apt-get update && apt-get install redis-server -y

通过以上命令便可安装最新版 Redis ,终端中输入以下命令查询 Redis 版本:

redis-server -v

命令执行后返回 Redis 版本如下,已经是最新版本:

Redis server v=5.0.3 sha=00000000:0 malloc=jemalloc-5.1.0 bits=64 build=45d60903d31a0894

Redis 安装完成后服务已经自动启动,通过以下命令查看 Redis 服务状态:

systemctl status redis

命令执行后会返回类似下面的信息,可以看到 Redis 服务已经启动,而且重启系统服务也会自动运行:

redis-server.service - Advanced key-value store
   Loaded: loaded (/lib/systemd/system/redis-server.service; enabled; vendor preset: enabled)
   Active: active (running) since Wed 2019-01-19 10:48:52 UTC; 12s ago
     Docs: http://redis.io/documentation,
           man:redis-server(1)
  Process: 2421 ExecStop=/bin/kill -s TERM $MAINPID (code=exited, status=0/SUCCESS)
  Process: 2424 ExecStart=/usr/bin/redis-server /etc/redis/redis.conf (code=exited, status=0/SUCCESS)
 Main PID: 2445 (redis-server)
    Tasks: 4 (limit: 4704)
   CGroup: /system.slice/redis-server.service
           └─2445 /usr/bin/redis-server 127.0.0.1:6379

配置 Redis 密码

修改配置文件,设置 Redis 密码

配置 Redis 密码将启用 auth 命令,该命令需要客户端进行身份验证才能访问数据库,大大提高安全性。配置 Redis 密码是通过修改 Redis 配置文件完成的。Redis 配置文件为 /etc/redis/redis.conf 。在 redis.conf 文件中有一条对 requirepass 的注释警告:

# Warning: since Redis is pretty fast an outside user can try up to
# 150k passwords per second against a good box. This means that you should
# use a very strong password otherwise it will be very easy to break.

因此,指定一个非常强大和非常长的值作为密码很重要,可以使用 openssl 命令生成一个随机密码:

printf "$(openssl rand 60 | openssl base64 -A) n" 

命令执行后终端返回一串随机字符串,例如:

EDyHgJqw1maAYr+bbjmIKF+1lm9EnGegFrzdy2zNfAdgvCCi1Wz+Xezs1YzVpHPpDZqyl2uNSwRIKSFn

将该随机字符串做为 Redis 密码(注意,以下命令执行时请将随机字符串修改为自己获取的真实随机字符串):

sed -i 's/# requirepass foobared/requirepass EDyHgJqw1maAYr+bbjmIKF+1lm9EnGegFrzdy2zNfAdgvCCi1Wz+Xezs1YzVpHPpDZqyl2uNSwRIKSFn/' /etc/redis/redis.conf

重启 Redis 服务,使密码生效:

systemctl restart redis.service

测试 Redis 密码

终端中输入执行以下命令进入 Redis 客户端:

redis-cli

终端中返回结果如下:

root@timelate:~# redis-cli
127.0.0.1:6379> 

输入 ping 并回车,终端中返回结果如下:

127.0.0.1:6379> ping
(error) NOAUTH Authentication required.

提示需要认证,说明密码已经生效。继续在终端中输入以下命令,进行密码认证:

auth EDyHgJqw1maAYr+bbjmIKF+1lm9EnGegFrzdy2zNfAdgvCCi1Wz+Xezs1YzVpHPpDZqyl2uNSwRIKSFn

命令执行后终端返回结果如下,提示认证成功:

127.0.0.1:6379> auth EDyHgJqw1maAYr+bbjmIKF+1lm9EnGegFrzdy2zNfAdgvCCi1Wz+Xezs1YzVpHPpDZqyl2uNSwRIKSFn
OK

终端中输入 exit 并回车,退出 Redis 客户端。

通过以上步骤便可在 Ubuntu 18.04 上手动安装最新版 Redis ,本文结束。

redis(一)–在centos7.5下编译安装redis5.0

1、安装依赖

首先要安装redis需要依赖的软件包

yum install gcc*

2、下载并解压

首先下载

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

然后解压开包

tar -zxvf redis-5.0.0.tar.gz

cd redis-5.0.0

执行命令make MALLOC=libc

如果不添加MALLOC=libc就会提示下边的错误。

未分类

3、启停redis

启动redis

./src/redis-server $PWD/redis.conf

这样启动不会是后台启动

nohup ./src/redis-server $PWD/redis.conf &

这样就是后台启动

以上启动是一种方法,还有一种方法在配置文件中进行修改。

# By default Redis does not run as a daemon. Use 'yes' if you need it.

# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.

daemonize yes

默认的是daemonize no,将no 改成yes。

关闭redis

./src/redis-cli -h ip_address -p ip_port shutdown

在关闭的时候shutdown必须要在此命令的最后边,否则这个命令会识别成默认的127.0.0.1去关闭。

4、redis 启动脚本

一般在解压包的utils中会有一个简单的启动脚本

cp /usr/local/src-new/redis-5.0.0/utils/redis_init_script redis

将文件中的启动命令等相关参数进行更改。

Redis Cluster 实践

背景

redis 这个 gem 在最新的 4.1 版中开始支持 Redis Cluster。

开发环境

我们需要在开发环境搭建一个 Redis Cluster,最方便快捷的方法是使用 docker 和 docker-compose,并且使用配置好的 image。

在 docker-compose.yml 中添加下列代码

version: '3'
services:
  redis-cluster:
    image: grokzen/redis-cluster
    environment:
     - IP=0.0.0.0
    ports:
      - '7000-7007:7000-7007'

使用 docker-compose up -d 启动服务即可。

更新代码

在 Gemfile 中设置 redis gem 的版本为 4.1

gem "redis", "~> 4.1"

在 application.rb 中修改 session_store 和 cache_store

Rails.application.configure do
  # Redis Session Store (redis-rails Gem)
  config.session_store :redis_store, servers: { cluster: %w[redis://127.0.0.1:7000] }, expires_in: 1.month

  # Redis Cache Store (redis-rails Gem)
  config.cache_store = :redis_store, { cluster: %w[redis://127.0.0.1:7000] }

  # Redis Cache Store (ActiveSupport)
  config.cache_store = :redis_cache_store, { cluster: %w[redis://127.0.0.1:7000] }
end

在其他地方使用 Redis Cluster

redis = Redis.new(cluster: %w[redis://127.0.0.1:7000])

分片

从单机变成集群之后,数据分片了,分散到不同机器,但是实际需求要求相关联的 key 分配到相同机器。

因为业务或者性能问题,我们会使用 Redis 的 pipelined、multi 和 Rails.cache.read_multi,这些场景下都需要 key 分配到相同机器。

但是 Redis Cluster 的分片方案是服务端提供分片,规则不是客户端控制的。

针对这些场景 Redis Cluster 提供了 Keys hash tags 的功能,简单来说是:

当一个 key 包含 {} 的时候,就不对整个 key 做 hash,而仅对 {} 包括的字符串做 hash。

redis = Redis.new(cluster: %w[redis://127.0.0.1:7000])

redis.mget('key1', 'key2')
#=> Redis::CommandError (CROSSSLOT Keys in request don't hash to the same slot)

redis.mget('{key}1', '{key}2')
#=> [nil, nil]

总结

本文只是一个简单的例子,不能在生产环境中直接使用。

在生产环境需要考虑 Redis 的各个客户端,同时要考虑数据迁移和兼容的问题,升级到 Redis Cluster 要非常谨慎和细心。

Redis数据库解析,读懂直接去见面试官

上一篇,我们简单了解过NoSQL数据库下文档型数据库MongoDB,今天我们来介绍下NoSQL数据库下一款缓存数据库Redis。谈到Redis,应该没有一个技术开发者会否认,在当今的技术架构中,Redis已然成为使用最广泛的缓存,它支持复杂的数据结构,支持持久化,支持主从集群,支持高可用……

在介绍Redis之前,我们不妨先简要的了解下缓存。随着互联网的普及,信息内容愈加复杂,用户数量和访问数量不断增加,所以我们的应用服务器和数据库服务器所做的工作也就越来越多,然而服务器的工作能力是有限的,数据库每秒处理请求的次数也是有限的,进而有效利用有限资源来实现最大限度的吞吐量成为急需解决的问题。那么我们就可以考虑引入缓存,实现每一个请求都可以通过缓存完成高效处理工作。

所谓缓存,就是如果数据没有发生本质的话,我们就无需去数据库中查询,而直接就可以通过内存读取数据,这样就可以大大降低数据库的读写次数,工作效率能提高很多。Redis作为一款缓存数据库,大量使用内存间的数据快速读写,支持高并发大吞吐,并能够对缓存提供持久化支持。下面我们从多维度来了解下Redis。

什么是Redis

Redis的全称是Remote Dictionary Server,由意大利人 Salvatore Sanfilippo 使用C语言开发。Redis是完全免费开源的,遵守BSD协议。本质上讲,Redis是一个key_value型单线程数据库,可以用作数据库、缓存和消息的中间件,属当前最受欢迎的NoSql数据库之一,也被人们称为数据结构服务器。

未分类

Redis的数据类型

Redis之所以备受欢迎,其中最大的魅力在于它支持保存五种基本数据结构类型。string(字符串),hash(哈希),list(列表),set(集合)和zset(sorted set有序集合)。

1.string

它是redis的最基本的数据类型,一个键对应一个值,需要注意是一个键值最大存储512MB。一般用于一些复杂的计数功能的缓存;

2.hash

redis hash是一个键值对的集合,是一个string类型的field和value的映射表,这里的value存放的是结构化的对象,操作其中某个字段十分方便,适合用于存储对象;

3.list

是Redis类相对简单的字符串列表,通过list可以做简单的消息列队功能。list对应的是一个双向列表,按照插入顺序排序;

4.set

是string类型的无序集合,集合中的数据不能重复出现,所以可以用来做全局的去重功能;

5.zset

是string类型的有序集合,同set一样不可重复。有序集合中排序因子为每个元素附带一个double型的分数,根据分数对元素进行升序排序。如果多个元素有相同的分数,就会通过字典序完成升序排序,因此sorted set十分适合做排行榜应用。sorted set也可用来做延时任务。

未分类

速度解析

Redis作为一款单线程模型数据库,还有另外一处魅力所在,那就是快。很多人会对此产生疑问,为什么Redis是单线程还很快,关于这个问题,我们从以下几点展开分析:

  1. Redis使用标准C语言写,所有数据都在内存中完成,读写速度分别达到10万/20万,是已知性能最快的Key-Value DB ;

  2. 单线程操作,避免了频繁的上下文切换,也不用考虑在多进程或者多线程中导致的切换而消耗 cpu;

  3. 核心采用基于非阻塞的 IO 多路复用机制,单个线程通过跟踪每个I/O的状态,来管理多个I/O流。

过期删除策略

在Redis内,我们可以使用EXPIRE或EXPIREAT设置key的过期时间,Redis内存达到maxmemory限制后,Redis内存就会施行过期数据淘汰策略。过期删除策略通常有三种:定时删除、惰性删除、定期删除,目前Redis使用的过期键删除策略为惰性删除和定期删除,两种策略配合使用。

惰性删除,指当某个key值过期之后,该key值不会马上被删除,只有当读或者写一个已经过期的key时,才会触发惰性删除策略,此时该key完成删除。

定期删除,指每隔一段时间就会扫描一定数量数据库的expires字典内一定数量的key,并删除里面过期的key。

由于惰性删除无法保证过期数据被及时删除掉,所以Redis采用惰性删除,定期删除相结合的方法,可以帮助实现定期主动淘汰已过期的key,从而使cpu和内存资源达到最优的平衡效果。

InfoQ 中文站曾就Redis应用采访过新浪微博开放平台资深工程师唐福林,唐福林在采访中表示,个人是十分认可Redis丰富数据结构和高速读写功能这两大优势的,并且在长期的技术研发中,新浪微博会不断推进RedisCounter的开发和一些存储列表类数据的 eRedis 的定制开发。Redis深受众多技术开发者的喜爱,在我们熟悉的TitanFramework中,只需实现StorageDataProcessor,满足Redis获取接口中已经实现的manager方法进行数据操作,就能实现Redis的完美接入。

一文让你明白Redis持久化

网上虽然已经有很多类似的介绍了,但我还是自己总结归纳了一下,自认为内容和细节都是比较齐全的。

文章篇幅有 4k 多字,货有点干,断断续续写了好几天,希望对大家有帮助。不出意外地话,今后会陆续更新 Redis 相关的文章,和大家一起学习。

好了,下面开始回归正文:

Redis 一共有 2 种持久化方式,分别是 RDB 和 AOF,下面我来详细介绍两种方式在各个过程所做的事情,特点等等。

1. RDB 持久化

RDB 持久化是 Redis 默认的持久化方式。

它所生成的 RDB 文件是一个压缩的二进制文件,通过该文件可以还原生成 RDB 文件时的数据库状态

PS:数据库状态是指 Redis 服务器的非空数据库以及他们键值对的统称

1.1 RDB 文件的创建

有两个命令可以生成 RDB 文件,一个是 SAVE、另一个是 BGSAVE。

两者的区别在于:前者会阻塞 Redis 服务器进程,直到 RDB 文件创建完毕为止。

而在服务器进程阻塞期间,服务器是不能处理任何命令请求的。

后者则不会阻塞服务器进程,因为是通过 fork 一个子进程,并让其去创建 RDB 文件,而服务器进程(父进程)继续则继续处理命令请求。

当写完数据库状态后,新 RDB 文件就会原子地替换旧的 RDB 文件。

此处小提问:如果在执行 BGSAVE 期间,客户端发送 SAVE、BGSAVE 或 BGREWRITEAOF 命令给服务端,服务端会如何处理呢?
答案:在执行 BGSAVE 期间,上述三个命令都不会被执行。

详细原因:前两个会被直接拒绝,原因是为了避免父子进程同时执行两个 rdbSave 调用,防止产生竞争条件。 
而 BGREWRITEAOF 命令则是会被延迟到 BGSAVE 命令执行之后再执行。 
但如果是 BGREWRITEAOF 命令正在执行,此时客户端发送 BGSAVE 命令则会被拒绝。 
因为 BGREWRITEAOF 和 BGSAVE 都是由子进程执行的,所以在操作方面没有冲突的地方,不能同时执行的原因是性能上的考虑——并发出两个子进程,并且这两个子进程都会同时执行大量 io(磁盘写入)操作

1.2 RDB 文件的载入

RDB 文件的载入是在服务器启动时自动执行的,所以没有用于载入的命令,期间阻塞主进程。

只要没有开启 AOF 持久化功能,在启动时检测到有 RDB 文件,就会自动载入。

当服务器有开启 AOF 持久化功能时,服务器将会优先使用 AOF 文件来还原数据库状态。原因是 AOF 文件的更新频率通常比 RDB 文件的更新频率高。

1.3 自动间隔性保存

对于 RDB 持久化而言,我们一般都会使用 BGSAVE 来持久化,毕竟它不会阻塞服务器进程。

在 Redis 的配置文件,有提供设置服务器每隔多久时间来执行 BGSAVE 命令。

Redis 默认是如下配置:

save 900 1 // 900 秒内,对数据库至少修改 1 次。下面同理 
save 300 10 
save 60 10000

只要满足其中一种情况,服务器就会执行 BGSAVE 命令。

2. AOF 持久化

我们从上面的介绍知道,RDB 持久化通过保存数据库状态来持久化。而 AOF 与之不同,它是通过保存对数据库的写命令来记录数据库状态。

比如执行了 set key 123,Redis 就会将这条写命令保存到 AOF 文件中。

在服务器下次启动时,就可以通过载入和执行 AOF 文件中保存的命令,来还原服务器关闭前的数据库状态了。

总体流程和 RDB 持久化一样 —— 都是创建一个 xxx 文件、在服务器下次启动时就载入这个文件来还原数据

那么,AOF 持久化具体是怎么实现的呢?

2.1 AOF 持久化实现

AOF 持久化功能的实现可以分为 3 个步骤:命令追加、文件写入、文件同步

其中命令追加很好理解,就是将写命令追加到 AOF 缓冲区的末尾。

那文件写入和文件同步怎么理解呢?刚开始我也一脸懵逼,终于在网上找到了答案,参考见文末,有兴趣的读者可以去看看。

先不卖关子了,简单一句话解释就是:前者是缓冲区内容写到 AOF 文件,后者是将 AOF 文件保存到磁盘

ok,明白什么意思之后,我们稍微详细看下这两个东西是什么鬼。

在《Redis设计与实现》中提到,Redis 服务器进程就是一个事件循环,这个循环中的文件事件(socket 的可读可写事件)负责接收客户端的命令请求,以及向客户端发送命令结果。

因为服务器在处理文件事件时,可能会发生写操作,使得一些内容会被追加到 AOF 缓冲区末尾。所以,在服务器每次结束一个事件循环之前 ,都会调用 flushAppendOnlyFile 方法。

这个方法执行以下两个工作:

  • WRITE:根据条件,将缓冲区内容写入到 AOF 文件。
  • SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。

两个步骤都需要根据一定的条件来执行,而这些条件由 Redis 配置文件中的 appendfsync 选项来决定的,一共有三个选择:

  1. appendfsync always:每执行一个命令保存一次
  2. appendfsync everysec(默认,推荐):每一秒钟保存一次
  3. appendfsync no:不保存

下面说下三个的区别:

  • appendfsync always:每次执行完一个命令之后, WRITE 和 SAVE 都会被执行
  • appendfsync everysec:SAVE 原则上每隔一秒钟就会执行一次。
  • appendfsync no:每次执行完一个命令之后, WRITE 会执行,SAVE 都会被忽略,只会在以下任意一种情况中被执行:
    • Redis 被关闭
    • AOF 功能被关闭
    • 系统的写缓存被刷新(可能是缓存已经被写满,或者定期保存操作被执行。完成依赖 OS 的写入,一般为 30 秒左右一次)

而对于操作特性来分析的话,则是如下情况:

未分类

既然 AOF 持久化是通过保存写命令到文件的,那随着时间的推移,这个 AOF 文件记录的内容就越来越多,文件体积也就越来越大,对其进行数据还原的时间也就越来越久。

针对这个问题,Redis 提供了 AOF 文件重写功能。

2.2 AOF 重写

通过该功能来创建一个新的 AOF 文件来代替旧文件。并且两个文件所保存的数据库状态一样,但新文件不会包含任何冗余命令,所以新文件要比旧文件小得多。

而为什么新文件不会包含任何冗余命令呢?

那是因为这个重写功能是通过读取服务器当前的数据库状态来实现的。虽然叫做「重写」,但实际上并没有对旧文件进行任何读取修改。

比如旧文件保存了对某个 key 有 4 个 set 命令,经过重写之后,新文件只会记录最后一次对该 key 的 set 命令。因此说新文件不会包含任何冗余命令

因为重写涉及到大量 IO 操作,所以 Redis 是用子进程来实现这个功能的,否则将会阻塞主进程。该子进程拥有父进程的数据副本,可以避免在使用锁的情况下,保证数据的安全性。

那么这里又会存在一个问题,子进程在重写过程中,服务器还在继续处理命令请求,新命令可能会对数据库进行修改,这会导致当前数据库状态和重写后的 AOF 文件,所保存的数据库状态不一致

为了解决这个问题,Redis 设置了一个 AOF 重写缓冲区。在子进程执行 AOF 重写期间,主进程需要执行以下三个步骤:

  1. 执行客户端的请求命令
  2. 将执行后的写命令追加到 AOF 缓冲区
  3. 将执行后的写命令追加到 AOF 重写缓冲区

当子进程结束重写后,会向主进程发送一个信号,主进程接收到之后会调用信号处理函数执行以下步骤:

  1. 将 AOF 重写缓冲区内容写入新的 AOF 文件中。此时新文件所保存的数据库状态就和当前数据库状态一致了
  2. 对新文件进行改名,原子地覆盖现有 AOF 文件,完成新旧文件的替换。

当函数执行完成后,主进程就继续处理客户端命令。

因此,在整个 AOF 重写过程中,只有在执行信号处理函数时才会阻塞主进程,其他时候都不会阻塞。

3. 选择持久化方案的官方建议

到目前为止,Redis 的两种持久化方式就介绍得差不多了。可能你会有疑惑,在实际项目中,我到底要选择哪种持久化方案呢?下面,我贴下官方建议:

通常,如果你要想提供很高的数据保障性,那么建议你同时使用两种持久化方式。如果你可以接受灾难带来的几分钟的数据丢失,那么你可以仅使用 RDB。

很多用户仅使用了 AOF,但是我们建议,既然 RDB 可以时不时的给数据做个完整的快照,并且提供更快的重启,所以最好还是也使用 RDB。

在数据恢复方面:
RDB 的启动时间会更短,原因有两个:

  1. RDB 文件中每一条数据只有一条记录,不会像 AOF 日志那样可能有一条数据的多次操作记录。所以每条数据只需要写一次就行了。
  2. RDB 文件的存储格式和 Redis 数据在内存中的编码格式是一致的,不需要再进行数据编码工作,所以在 CPU 消耗上要远小于 AOF 日志的加载。

注意:

上面说了 RDB 快照的持久化,需要注意:在进行快照的时候(save),fork 出来进行 dump 操作的子进程会占用与父进程一样的内存,真正的 copy-on-write,对性能的影响和内存的耗用都是比较大的。比如机器 8G 内存,Redis 已经使用了 6G 内存,这时 save 的话会再生成 6G,变成 12G,大于系统的 8G。这时候会发生交换;要是虚拟内存不够则会崩溃,导致数据丢失。所以在用 redis 的时候一定对系统内存做好容量规划。

目前,通常的设计思路是利用复制(Replication)机制来弥补 aof、snapshot 性能上的不足,达到了数据可持久化。即 Master 上 Snapshot 和 AOF 都不做,来保证 Master 的读写性能,而 Slave 上则同时开启 Snapshot 和 AOF 来进行持久化,保证数据的安全性。

总结

文章知识点有点多和杂,我总结一下,帮助他们回顾内容:

  • RDB 持久化是 Redis 默认持久化方式,通过保存数据库键值对来记录状态来持久化,由 SAVE 和 BGSAVE 命令来创建 RDB 文件。前者阻塞 Redis 主进程,后者不会。
  • RDB 可以在配置文件设置每隔多久时间来执行 BGSAVE 命令
  • AOF 通过追加写命令来保存当前数据库状态。其持久化功能的实现可以分为 3 个步骤:命令追加(到 AOF 缓冲区)、文件写入(缓冲区内容写到 AOF 文件)、文件同步(AOF 文件保存磁盘)
  • 其中文件同步和保存可以通过配置文件的 appendfsync 选项来决定
    为了解决 AOF 文件越来越大的问题,Redis 提供了 AOF 重写功能,并且不会阻塞主进程。
  • 为了解决 AOF 重写过程中,新 AOF 文件所保存的数据库状态和当前数据库状态可能不一致的问题,Redis 引入了 AOF 重写缓冲区,用于保存子进程在重写 AOF 文件期间产生的新的写命令。
  • 最后是官方对于两种持久化方式选择的一些建议

在 Linux 中打扮你的冬季 Bash 提示符

你的 Linux 终端可能支持 Unicode,那么为何不利用它在提示符中添加季节性的图标呢?

欢迎再次来到 Linux 命令行玩具日历的另一篇。如果这是你第一次访问该系列,你甚至可能会问自己什么是命令行玩具?我们对此比较随意:它会是终端上有任何有趣的消遣,对于任何节日主题相关的还有额外的加分。

也许你以前见过其中的一些,也许你没有。不管怎样,我们希望你玩得开心。

今天的玩具非常简单:它是你的 Bash 提示符。你的 Bash 提示符?是的!我们还有几个星期的假期可以盯着它看,在北半球冬天还会再多几周,所以为什么不玩玩它。

目前你的 Bash 提示符号可能是一个简单的美元符号( $),或者更有可能是一个更长的东西。如果你不确定你的 Bash 提示符是什么,你可以在环境变量 $PS1 中找到它。要查看它,请输入:

echo $PS1

对于我而言,它返回:

[u@h W]$

uhW 分别是用户名、主机名和工作目录的特殊字符。你还可以使用其他一些符号。为了帮助构建你的 Bash 提示符,你可以使用 EzPrompt,这是一个 PS1 配置的在线生成器,它包含了许多选项,包括日期和时间、Git 状态等。

你可能还有其他变量来组成 Bash 提示符。对我来说,$PS2 包含了我命令提示符的结束括号。有关详细信息,请参阅 这篇文章。

要更改提示符,只需在终端中设置环境变量,如下所示:

$ PS1='u is cold: '
jehb is cold:

要永久设置它,请使用你喜欢的文本编辑器将相同的代码添加到 /etc/bashrc 中。

那么这些与冬季化有什么关系呢?好吧,你很有可能有现代一下的机器,你的终端支持 Unicode,所以你不仅限于标准的 ASCII 字符集。你可以使用任何符合 Unicode 规范的 emoji,包括雪花 ❄、雪人 ☃ 或一对滑雪板。你有很多冬季 emoji 可供选择。

  • 圣诞树
  • 外套
  • 鹿手套
  • 圣诞夫人
  • 圣诞老人
  • 围巾
  • 滑雪者
  • 滑雪板
  • 雪花
  • 雪人
  • 没有雪的雪人
  • 包装好的礼物

选择你最喜欢的,享受冬天的欢乐。有趣的事实:现代文件系统也支持文件名中的 Unicode 字符,这意味着技术上你可以将你下个程序命名为 ❄❄❄❄❄.py。只是说说,不要这么做。

解决Linux环境下执行脚本时报错:/bin/bash^M: 坏的解释器: 没有那个文件或目录

一、问题描述

我在Windows 10 系统下编辑了一个发送消息到企业微信的shell脚本文件,然后copy到了远程的Linux服务器,当运行的时候报错了。如下所示:

未分类

root@ubuntu116:/data/gitlabData/auto_back_shell# ./qiyewechat-notifier.sh 
-bash: ./qiyewechat-notifier.sh: /bin/bash^M: 坏的解释器: 没有那个文件或目录
root@ubuntu116:/data/gitlabData/auto_back_shell# 

二、错误原因

这个文件在Windows 下编辑过,在Windows下每一行结尾是nr,而Linux下则是n,所以才会有 多出来的r。

三、修改错误

使用指令sed -i 's/r$//' xxxxxxx.sh,上面的指令会把 xxxxxxx.sh 中的r 替换成空白!
实操一下:

未分类

root@ubuntu116:/data/gitlabData/auto_back_shell# sed -i 's/r$//' qiyewechat-notifier.sh 
您在 /var/mail/root 中有新邮件
root@ubuntu116:/data/gitlabData/auto_back_shell# ./qiyewechat-notifier.sh -h 
./qiyewechat-notifier.sh: 非法选项 -- h
Usage:
  qiyewechat.sh [-u USER] [-t TITLE] [-c CONTENT] [-d DETAIL] [-p PICTURE]
Description:
    USER, 用户.
    TITLE, 标题.
    CONTENT, 内容.
    DETAIL, 细节.
    PICTURE, 图片.
root@ubuntu116:/data/gitlabData/auto_back_shell# 

如上所示,执行了下面的脚本之后,

sed -i 's/r$//' qiyewechat-notifier.sh

qiyewechat-notifier.sh就可以正常运行了!

四、附录

qiyewechat-notifier.sh的部分代码如下所示:

未分类

#!/bin/bash

#用法提示
usage() {
    echo "Usage:"
    echo "  qiyewechat.sh [-u USER] [-t TITLE] [-c CONTENT] [-d DETAIL] [-p PICTURE]"
    echo "Description:"
    echo "    USER, 用户."
    echo "    TITLE, 标题."
    echo "    CONTENT, 内容."
    echo "    DETAIL, 细节."
    echo "    PICTURE, 图片."
    exit -1
}


# 获取脚本执行时的选项
while getopts u:t:c:d:p: option
do
   case "${option}"  in
                u) USER=${OPTARG};;
                t) TITLE=${OPTARG};;
                c) CONTENT=${OPTARG};;
                d) DETAIL=${OPTARG};;
                p) PICTURE=${OPTARG};;
                h) usage;;
                ?) usage;;
   esac
   echo $option
   echo $OPTARG

done

HBuilderX技巧:Git插件,手把手教你扩展git命令

官方的git插件,只是列出了常用的git命令,并不涵盖所有。

遇到缺失的怎么办?

小编教您如何扩展git命令

git插件配置入口在哪里?

如下图片所示,点击最后一个【插件配置】,即打开git插件配置文件package.json

未分类

解析GIT命令菜单

{
        "id":"GIT_TAG",                          // Git命令ID
        "name":"git tag",                         // 外部命令显示的名称
        "command":["git", "tag"],             // 命令,参数使用逗号分割即可
        "workingDir":"${projectDir}",       // 工作目录,一般两个参数 ${projectDir}: 项目目录;${file}:文件 workingDir也可以省略
        "key":"",            // 快捷键
        "showInParentMenu":false,       // 是否显示在上一级菜单中,默认false
        "onDidSaveExecution": false,    // 是否保存时执行,默认false
        "type":"terminal"                        // 默认terminal
      }

如上解说,只需要配置一个id、name、command,就可以组成一个新的命令

特别说明:

比如切换分支、创建tag、创建分支,都需要输入啊,别担心。

在命令中加上${userInput},就可以调起一个输入框。如下

"command":["git", "tag", "${userInput:请输入tag名称}"]

示例: git tag示例

比如git tag是一个比较重要的操作,官方没提供,那我们自己手动加上吧

例子1: 打tag标签

{
        "id":"GIT_TAG_CREATE",
        "name":"git tag 打标签",
        "command":["git", "tag", "${userInput:请输入tag名称}"],
        "workingDir":"${projectDir}",
        "key":"",
        "showInParentMenu":false,
        "onDidSaveExecution": false,
        "type":"terminal"
      }

例子2:git show查看标签信息

{
        "id":"GIT_SHOW",
        "name":"git show 查看标签信息",
        "command":["git", "show","${userInput:请输入要查看的tag名称}"],
        "workingDir":"${projectDir}",
        "key":"",
        "showInParentMenu":false,
        "onDidSaveExecution": false,
        "type":"terminal"
      }

例子3:git tag 查看标签列表

{
        "id":"GIT_TAG_LIST",
        "name":"git tag 查看标签列表",
        "command":["git", "tag"],
        "workingDir":"${projectDir}",
        "key":"",
        "showInParentMenu":false,
        "onDidSaveExecution": false,
        "type":"terminal"
      },

例子4: git reset 回退到指定版本

{
        "id":"GIT_RESET_COMMIT",
        "name":"git reset 回退到指定版本",
        "command":["git", "reset", "--hard", "${userInput:请输入commit id}"],
        "workingDir":"${projectDir}",
        "key":"",
        "showInParentMenu":false,
        "onDidSaveExecution": false,
        "type":"terminal"
      }

注意事项

编辑插件配置后,并不能马上生效,需要重启软件。
请大家将自己修改后的配置文件,另存一份,以防更新后丢失。

git配置文件

小编将自己的git配置文件,放到github上了,请大家关注

https://github.com/yi-heng/MyConfig/blob/master/HBuilderX/git%E6%8F%92%E4%BB%B6%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6.json