编写代码只是软件开发的一小部分,更多的时间往往花在构建(build)和测试(test)。

为了提高软件开发的效率,构建和测试的自动化工具层出不穷。Travis CI 就是这类工具之中,市场份额最大的一个。

本文介绍 Travis CI 的基本用法。用好这个工具不仅可以提高效率,还能使开发流程更可靠和专业化,从而提高软件的价值。而且,它对于开源项目是免费的,不花一分钱,就能帮你做掉很多事情。

一、什么是持续集成?

Travis CI 提供的是持续集成服务(Continuous Integration,简称 CI)。它绑定 Github 上面的项目,只要有新的代码,就会自动抓取。然后,提供一个运行环境,执行测试,完成构建,还能部署到服务器。

持续集成指的是只要代码有变更,就自动运行构建和测试,反馈运行结果。确保符合预期以后,再将新代码"集成"到主干。

持续集成的好处在于,每次代码的小幅变更,就能看到运行结果,从而不断累积小的变更,而不是在开发周期结束时,一下子合并一大块代码。

二、使用准备

Travis CI 只支持 Github,不支持其他代码托管服务。这意味着,你必须满足以下条件,才能使用 Travis CI。

  • 拥有 GitHub 帐号
  • 该帐号下面有一个项目
  • 该项目里面有可运行的代码
  • 该项目还包含构建或测试脚本

如果这些条件都没问题,就可以开始使用 Travis CI 了。

首先,访问官方网站 travis-ci.org,点击右上角的个人头像,使用 Github 账户登入 Travis CI。

Travis 会列出 Github 上面你的所有仓库,以及你所属于的组织。此时,选择你需要 Travis 帮你构建的仓库,打开仓库旁边的开关。一旦激活了一个仓库,Travis 会监听这个仓库的所有变化。

三、.travis.yml

Travis 要求项目的根目录下面,必须有一个.travis.yml文件。这是配置文件,指定了 Travis 的行为。该文件必须保存在 Github 仓库里面,一旦代码仓库有新的 Commit,Travis 就会去找这个文件,执行里面的命令。

这个文件采用 YAML 格式。下面是一个最简单的 Python 项目的.travis.yml文件。

1
2
3

language: python
script: true

上面代码中,设置了两个字段。language字段指定了默认运行环境,这里设定使用 Python 环境。script字段指定要运行的脚本,script: true表示不执行任何脚本,状态直接设为成功。

Travis 默认提供的运行环境,请参考官方文档 。目前一共支持31种语言,以后还会不断增加。

下面是一个稍微复杂一点的.travis.yml

1
2
3
4
5

language: python
sudo: required
before_install: sudo pip install foo
script: py.test

上面代码中,设置了四个字段:运行环境是 Python,需要sudo权限,在安装依赖之前需要安装foo模块,然后执行脚本py.test

四、运行流程

Travis 的运行流程很简单,任何项目都会经过两个阶段。

  • install 阶段:安装依赖
  • script 阶段:运行脚本

4.1 install 字段

install字段用来指定安装脚本。

1
2

install: ./install-dependencies.sh

如果有多个脚本,可以写成下面的形式。

1
2
3
4

install:
- command1
- command2

上面代码中,如果command1失败了,整个构建就会停下来,不再往下进行。

如果不需要安装,即跳过安装阶段,就直接设为true

1
2

install: true

4.2、script 字段

script字段用来指定构建或测试脚本。

1
2

script: bundle exec thor build

如果有多个脚本,可以写成下面的形式。

1
2
3
4

script:
- command1
- command2

注意,scriptinstall不一样,如果command1失败,command2会继续执行。但是,整个构建阶段的状态是失败。

如果command2只有在command1成功后才能执行,就要写成下面这样。

1
2

script: command1 && command2

4.3 实例:Node 项目

Node 项目的环境需要写成下面这样。

1
2
3
4

language: node_js
node_js:
- "8"

上面代码中,node_js字段用来指定 Node 版本。

Node 项目的installscript阶段都有默认脚本,可以省略。

  • install默认值:npm install
  • script默认值:npm test

更多设置请看官方文档

4.4 部署

script阶段结束以后,还可以设置通知步骤(notification)和部署步骤(deployment),它们不是必须的。

部署的脚本可以在script阶段执行,也可以使用 Travis 为几十种常见服务提供的快捷部署功能。比如,要部署到 Github Pages,可以写成下面这样。

1
2
3
4
5
6
7

deploy:
provider: pages
skip_cleanup: true
github_token: $GITHUB_TOKEN # Set in travis-ci.org dashboard
on:
branch: master

其他部署方式,请看官方文档

4.5 钩子方法

Travis 为上面这些阶段提供了7个钩子。

  • before_install:install 阶段之前执行
  • before_script:script 阶段之前执行
  • after_failure:script 阶段失败时执行
  • after_success:script 阶段成功时执行
  • before_deploy:deploy 步骤之前执行
  • after_deploy:deploy 步骤之后执行
  • after_script:script 阶段之后执行

完整的生命周期,从开始到结束是下面的流程。

  1. before_install
  2. install
  3. before_script
  4. script
  5. aftersuccess or afterfailure
  6. [OPTIONAL] before_deploy
  7. [OPTIONAL] deploy
  8. [OPTIONAL] after_deploy
  9. after_script

下面是一个before_install钩子的例子。

1
2
3
4

before_install:
- sudo apt-get -qq update
- sudo apt-get install -y libxml2-dev

上面代码表示before_install阶段要做两件事,第一件事是要更新依赖,第二件事是安装libxml2-dev。用到的几个参数的含义如下:-qq表示减少中间步骤的输出,-y表示如果需要用户输入,总是输入yes

4.6 运行状态

最后,Travis 每次运行,可能会返回四种状态。

  • passed:运行成功,所有步骤的退出码都是0
  • canceled:用户取消执行
  • errored:before_installinstallbefore_script有非零退出码,运行会立即停止
  • failed :script有非零状态码 ,会继续运行

五、使用技巧

5.1 环境变量

.travis.ymlenv字段可以定义环境变量。

1
2
3
4
5

env:
- DB=postgres
- SH=bash
- PACKAGE_VERSION="1.0.*"

然后,脚本内部就使用这些变量了。

有些环境变量(比如用户名和密码)不能公开,这时可以通过 Travis 网站,写在每个仓库的设置页里面,Travis 会自动把它们加入环境变量。这样一来,脚本内部依然可以使用这些环境变量,但是只有管理员才能看到变量的值。具体操作请看官方文档

5.2 加密信息

如果不放心保密信息明文存在 Travis 的网站,可以使用 Travis 提供的加密功能。

首先,安装 Ruby 的包travis

1
2

$ gem install travis

然后,就可以用travis encrypt命令加密信息。

在项目的根目录下,执行下面的命令。

1
2

$ travis encrypt SOMEVAR=secretvalue

上面命令中,SOMEVAR是要加密的变量名,secretvalue是要加密的变量值。执行以后,屏幕上会输出如下信息。

1
2

secure: ".... encrypted data ...."

现在,就可以把这一行加入.travis.yml

1
2
3
4

env:
global:
- secure: ".... encrypted data ...."

然后,脚本里面就可以使用环境变量$SOMEVAR了,Travis 会在运行时自动对它解密。

travis encrypt命令的--add参数会把输出自动写入.travis.yml,省掉了修改env字段的步骤。

1
2

$ travis encrypt SOMEVAR=secretvalue --add

详细信息请看官方文档

5.3 加密文件

如果要加密的是文件(比如私钥),Travis 提供了加密文件功能。

安装命令行客户端以后,使用下面的命令登入 Travis CI。

1
2

$ travis login

然后,进入项目的根目录,使用travis encrypt-file命令加密那些想要加密的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

$ travis encrypt-file bacon.txt

encrypting bacon.txt for rkh/travis-encrypt-file-example
storing result as bacon.txt.enc
storing secure env variables for decryption

Please add the following to your build script (before_install stage in your .travis.yml, for instance):

openssl aes-256-cbc -K $encrypted_0a6446eb3ae3_key -iv $encrypted_0a6446eb3ae3_key -in bacon.txt.enc -out bacon.txt -d

Pro Tip: You can add it automatically by running with --add.

Make sure to add bacon.txt.enc to the git repository.
Make sure not to add bacon.txt to the git repository.
Commit all changes to your .travis.yml.

上面的代码对文件bacon.txt进行加密,加密后会生成bacon.txt.enc,该文件需要提交到代码库。此外,该命令还会生成一个环境变量$encrypted_0a6446eb3ae3_key,保存密钥,储存在 Travis CI,文件解密时需要这个环境变量。你需要把解密所需的openssl命令,写在.travis.ymlbefore_install字段里面。这些都写在上面的命令行提示里面。

--add参数可以自动把环境变量写入.travis.yml

1
2
3
4
5
6
7
8
9
10

$ travis encrypt-file bacon.txt --add

encrypting bacon.txt for rkh/travis-encrypt-file-example
storing result as bacon.txt.enc
storing secure env variables for decryption

Make sure to add bacon.txt.enc to the git repository.
Make sure not to add bacon.txt to the git repository.
Commit all changes to your .travis.yml.

详细信息请看官方文档,实际的例子可以参考下面两篇文章。

(完)

留言與分享

Redis使用详解

分類 后端, Redis

Redis使用详解

一、NoSQL简介

NoSQL分类

  • 键值(Key-Value)存储数据库:这一类数据库主要会使用到一个hash表,如Redis、Oracle BDB
  • 列存储数据库:通常是用来应对分布式存储的海量数据,键仍然存在但是他们指向了多个列,如HBase、Riak
  • 文档型数据库:该类型的数据模型是版本化的文档,比如JSON,允许之间进行嵌套,如MongoDB

非关系型数据库特点

  • 数据模型比较简单

  • 对于数据库性能要求较高

  • 不需要高度的数据一致性

二、Redis简介

以key-value形式存储,不一定遵循传统数据库的一些基本要求(非关系的、分布式的、开源的、水平可扩展的)

优点:对数据高并发读写

​ 对海量数据的高效率存储和访问

​ 对数据的可扩展性和高可用性

缺点:无法做太复杂的关系模型

Redis单线程:指处理我们的网络请求的时候只有一个线程来处理【文件刷盘等用的是多线程】

Redis单线程的好处

  • Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽,单线程实现比较简单

  • 单线程避免了不必要的上下文切换和竞争条件以及加锁释放锁操作

  • 使用多路I/O复用模型,非阻塞IO

三、Redis的安装

第一步:准备工作【解压tar包,创建Redis相关目录】

1
2
3
4
tar -zxvf redis-5.0.2.tar.gz 
mkdir /opt/redis
mkdir /opt/redis/conf
mkdir /opt/redis/data

第二步:编译redis

1
2
3
4
5
#进入解压后的tar包执行
make
#执行结束之后进入src目录
cd src
make install PREFIX=/opt/redis

第三步:移动配置文件到conf目录

1
cp redis.conf /opt/redis/conf

额外配置:

  • redis开机自启
1
2
3
vim /etc/rc.local
#加入
/opt/redis/bin/redis-server /opt/redis/conf/redis.conf
  • 配置数据保存目录
1
2
3
vim /opt/redis/conf/redis.conf
#修改
dir ./ ---> dir /opt/redis/data/

四、Redis基本通用命令

命令 说明
KEYS [pattern] 查找出匹配的key【生成环境禁止使用,数据量太大阻塞生成环境】
DBSIZE 统计key总数【使用的是redis的内部计数,并不是全部扫描,生产可用】
EXISTS key [key …] 检查key是否存在【存在返回1,不存在返回0】
DEL key [key…] 删除key【成功删除返回1,不存在此key返回0】
EXPIRE key seconds 设置过期时间
TTL key 查看剩余的过期时间【-2代表已不存在,-1代表永不过期】
PERSIST key 取消key的过期设置
TYPE key 查询key的类型

五、Redis数据类型及其使用

注意:redis操作下标都是闭区间的

字符串【String】

String的值类型可以为字符类型、数字类型、bit类型

String类型是包含很多中类型的特殊类型,并且是二进制安全的。比如序列化的对象进行存储,比如一张图片进行二进制存储,比如一个简单地字符串,数值等等。

使用场景:缓存、计数器、分布式锁等

常用命令格式 描述
{ GET key },{ MGET key [key …] } 得到String
{ SET key value },{ MSET key value [key value …] } 设置String
{ SETNX key value },{ MSETNX key value [key value …] } 如果不存在则设置
SET key value XX 如果存在则设置
SETEX key second value 设置过期时间
{ INCR key },{ INCRBY key increment } 自增操作
{ DECR key },{ DECRBY key decrement } 自减操作
GETSET key value 得到就值设置新值
APPEND key value 追加字符串
STRLEN key 得到字符串长度,内部存有计数
GETRANGE key start end 得到指定长度的value
SETRANGE key offset value 设置指定偏移量字符串内容

哈希【Hash】

使用场景:存储具有一定结构化的数据

常用命令格式 描述
HGET key field 得到key对应field的value
HSET key field value 设置key对应field的value
HDEL key field 删除key对应field的value
HEXISTS key field 判断key的field是否存在
HLEN key 获取指定key的filed数量【内部计数,生成环境可用】
HMGET key field [field …] 批量获得hash的field对应的value
HMSET key field value [field value …] 批量设置hash的field和value
HINCRBY key field increment 增加指定increment的对应key的field
HGETALL key 获取key对应所有field和value【生成环境慎用】
HVALS key 返回key对应的所有value【生成环境慎用】
HKEYS key 返回key对应的所有field【生成环境慎用】
HSETNX key field value 不存在此key对应的field则设置

列表【List】

列表为有序、可重复结构。可指定位置插入和删除、也可从左右插入和弹出(模拟栈结构)

常用命令格式 描述
**LPUSH RPUSH** key value [value …]
LINSERT key BEFORE AFTER value newValue
**LPOP RPOP** key
LREM key count value 根据count值,从列表删除所有等于value的值【时间复杂度O(N)】
【count>0,从左到右删除count个】
【count<0,从右到坐删除count个】
【count=0,删除所有value相等的值】
LTRIM key start end 按照索引范围保留list,删除大链表有用【时间复杂度O(N)】
LRANGR key start end 获取列表指定索引范围内的值,数值为负则从右往左取值【时间复杂度O(N)】
LINDEX key index 获取列表指定索引的值,数值为负则从右取值【时间复杂度O(N)】
LLEN key 获取list长度【内部计数,生成环境可用】
LSET key index newValue 设置指定位置的值【时间复杂度O(N)】
**BLPOP RLPOP** key timeout

使用技巧:

  • LPUSH + LPOP = Stack
  • LPUSH + RPOP = Queue
  • LPUSH + LTRIM = Capped Collection【固定容量集合】
  • LPUSH + BRPOP = Block Queue

集合【Set】

Set无序、无重复、有集合间操作。

常用命令格式 描述
SADD key element [member …] 向集合key添加元素
SREM key element 删除集合key中的element元素
SCARD key 查询集合元素的个数【内部计数,生成环境可用】
SISMEMBER key element 集合中是否存在element元素
SRANDMEMBER key 随机得到一个元素
SMEMBERS key 获取集合所有元素【慎用】
SPOP key 随机弹出一个元素
{ SDIFF key [key …] }、{ SDIFFSTORE destination key [key …] } 返回/存储一个集合的全部成员,该集合是所有给定集合之间的差集
{ SINTER key [key …] }、{ SINTERSTORE destination key [key …] } 返回/存储一个集合的全部成员,该集合是所有给定集合的交集
{ SUNION key [key …] }、{ SUNIONSTORE destination key [key …] } 返回/存储一个集合的全部成员,该集合是所有给定集合的并集

使用技巧

  • SADD = Tagging

  • SPOP/SRANDMEMBER = Random Item

  • SADD + SINTER = Social Graph【共同关注、有着相同兴趣等】

有序集合【ZSet】

ZSet有序、无重复、包含分值与元素,有集合间操作。

使用场景:排行榜

常用命令格式 描述
ZADD key score member [score member …] 向集合添加元素
ZREM key member [member …] 移除集合中的元素
ZSCORE key member 得到元素的分数
ZINCRBY key increment member 增长元素的分数
ZCARD key 获得集合中元素的个数
ZRANK key member 成员按分值递减(从小到大)排列的排名
ZRANGE key start stop [WITHSCORES] 按score值递增(从小到大)排序,WITHSCORES返回分数
ZRANGEBYSCORE key max min [WITHSCORES] 返回分数范围内数据,按score值递增(从小到大)排序,WITHSCORES返回分数
ZCOUNT key min max 统计得到分数在min和max之间的元素个数
{ ZREMRANGEBYRANK key start stop }、{ ZREMRANGEBYSCORE key min max} 按照排名/分数范围删除元素
ZREVRANK key member 成员按分值递减(从大到小)排列的排名
ZREVRANGE key start stop [WITHSCORES] 按score值递增(从大到小)排序,WITHSCORES返回分数
ZREVRANGEBYSCORE key max min [WITHSCORES] 返回分数范围内数据,按score值递增(从大到小)排序,WITHSCORES返回分数

六、Redis高级功能

慢查询日志

Redis-Life-Cycle

  • 慢查询发生在第三阶段
  • 客户端超时不一定是慢查询导致,但慢查询可导致客户端超时

慢查询队列简介

  • 先进先出的队列
  • 固定长度
  • 长度不够时,丢弃最早记录
  • 保存在内存中

慢查询设置

  • slow-max-len【慢查询队列长度,默认128】

  • slowlog-log-slower-than【慢查询的阙值(微秒,1000000 微秒=1 秒),默认10000(10毫秒),建议为1000(1毫秒)】

更改慢查询参数建议使用CONFIG SET parameter value方式,而不是更改redis.conf文件重启redis

慢查询命令

常用命令格式 描述
slowlog get n 获取慢查询队列一条记录
slowlog len 获取慢查询队列长度
slowlog reset 清空慢查询队列

查询结果示例

Redis-Slow-Examples

Pipeline

Redis-Process

一般情况下客户端发送一条命令到Redis,Redis处理结束返回结果,即 N条命令 = N次网络时间 + N次计算时间

PipeLine将命令打包发送给客户端,Redis处理完返回结果,PipLine是一个异步处理方式,并不等待Redis返回。

Pipeline(N条命令) = 1次网络时间 + N次计算时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
* Jest执行参考
*/
long oldPipeline = Instant.now().toEpochMilli();
for (int i = 0; i < 100; i++) {
Pipeline pipeline = jedis.pipelined();
for (int j = 0; j < 100; j++) {
pipeline.sadd("S" + i * 100 + j, j + "");
}
//pipeline.sync();异步不接受返回结果
pipeline.syncAndReturnAll(); //异步接受返回结果
}
long nowPipeline = Instant.now().toEpochMilli();
System.out.println(nowPipeline - oldPipeline);

/**
* Spring Data Redis参考
*/
// 批量插入
redisTemplate.executePipelined(new RedisCallback<Void>() {
@Override
public Void doInRedis(RedisConnection connection) throws DataAccessException {
String keyPrefix = "pipeline-";
for (int i = 0; i < 10000; i++) {
String key = keyPrefix + i;
connection.set(key.getBytes(), String.valueOf(i).getBytes());
}
}
});

// 批量获取
List<Object> data = redisTemplate.executePipelined(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
String keyPrefix = "pipeline-";
for (int i = 0; i < 10000; i++) {
String key = keyPrefix + i;
connection.get(key.getBytes());
}
}
});

data.stream().forEach(System.out::println);

发布订阅

常用命令格式 描述
PUBLISH channel message 将信息 message 发送到指定的频道 channel
SUBSCRIBE channel [channel …] 订阅给定的一个或多个频道的信息
PSUBSCRIBE pattern [pattern …] 订阅一个或多个符合给定pattern的频道
UNSUBSCRIBE [channel [channel …]] 取消订阅

Bitmap

常用命令格式 描述
SETBIT key offset value 对 key所储存的字符串值,设置或清除指定偏移量上的位(bit)
GETBIT key offset 对 key所储存的字符串值,获取指定偏移量上的位(bit)
BITCOUNT key [start] [end] 计算给定字符串中,被设置为 1的比特位的数量
BITOP operation destkey key [key …] 对一个或多个保存二进制位的字符串 key进行位元操作,并将结果保存到 destkey上
operation 可以是 ANDORNOTXOR 这四种操作中的任意一种
BITPOS key bit [start] [end] 返回位图中第一个值为 bit的二进制位的位置

HyperLogLog

实质:用String类型实现,不能取出具体值,有错误率

作用:极小的空间实现独立数量统计

常用命令格式 描述
PFADD key element [element …] 将任意数量的元素添加到指定的HyperLogLog
PFCOUNT key [key …] 计算HyperLogLog有多少值
PFMERGE destkey sourcekey [sourcekey …] 将多个HyperLogLog合并为一个HyperLogLog

Geo

Geo(地理信息定位):存储经纬度,计算两地距离,范围计算等

Geo是使用ZSet实现

常用命令格式 描述
GEOADD key longitude latitude member [longitude latitude member …] longitude:经度,latitude:维度,member:标识
GEOPOS key member [member …] 返回经纬度
GEODIST key member1 member2 [unit] 返回两个给定位置之间的距离
GEORADIUS key longitude latitude radius m km
ZREM key member 删除成员

七、Redis持久化

RDB【Redis Database】

​ RDB是Redis用来进行持久化的一种方式,是把当前内存中的数据集快照写入磁盘,也就是 Snapshot 快照(数据库中所有键值对数据)。恢复时是将快照文件直接读到内存里。

触发机制

  • save

    手动触发,同步命令,会阻塞线程

  • bgsave

    手动触发,fork出一个子进程,异步命令,不会阻塞线程【阻塞仅仅会发生在fork出子进程的阶段】

  • 自动

    1
    2
    3
    save 900 1            #900秒改变1个就生成rdb文件
    save 300 10 #300秒改变10个就生成rdb文件
    save 60 10000 #60秒改变10000个就生成rdb文件

    一般情况下建议关闭自动策略

  • 全量复制

    从节点执行全量复制操作的时候,主节点会自动触发bgsave命令生存rdb文件并发送给从节点

  • debug reload

    在执行debug reload重新加载redis的时候,也会自动触发bgsave

  • shutdown

    默认情况下执行shutdown命令,如果没有开启AOF持久化功能,就会自动执行bgsave

RDB配置参数

命令格式 描述
rdbcompression 压缩RDB文件,默认yes
rdbchecksum RDB文件是否进行校验,默认yes
dbfilename dump.rdb RDB文件名【可使用dump-端口号.rdb区分不同的redis实例】
dir ./ RDB文件存储的目录
stop-writes-on-bgsave-error bgsave出现错误时是否停止写入,默认yes

AOF【Append-Only File】

​ AOF是一个追加写入的日志文件从而实现持久化的方式,生成的AOF文件是可识别的纯文本文件。Redis默认使用RDB持久,开启AOF持久化需要设置appendonlyyes

AOF文件生成策略

  • always 不丢失数据,每次更新记录数据就进行io操作

  • everysec 可能会丢失1s数据,但io小

  • no 不启用AOF

AOF重写

​ AOF支持AOF文件重写(从内存中读取的数据,并非读取上次的AOF文件进行重写)。AOF重写可以减少硬盘占用、加速恢复速度。

Aof-Rewrite

AOF配置参数

命令格式 描述
appendonly 是否开启AOF,默认no
appendfilename 生成AOF文件明【可使用appendonly-端口号.aof区分不同的redis实例】
appendfsync 刷盘策略,默认everysecond
dir 保存文件的目录,默认./
no-appendfsync-on-rewrite AOF重写过程中是否禁止append操作,默认no允许append
auto-aof-rewrite-min-size 进行AOF时文件最小尺寸,默认64mb
auto-aof-rewrite-percentage 下次进行AOF操作时的增量,默认100
aof-load-truncated AOF文件结尾不完整,Redis重启忽略不完整记录,默认yes

命令

  • bgrewriteaof 手动进行AOF重写操作
  • aof_current_size 查看AOF当前尺寸

redis-cli info Persistence可以查看统计信息aof_current_size【当前AOF文件大小】和aof_base_size【上次重写AOF文件大小】

RDB与AOF比较

命令 RDB AOF
启动优先级
体积
恢复
数据安全性 丢数据 根据策略决定
轻重

混合持久化

Redis 4.0 开始支持 rdb 和 aof 的混合持久化(默认开启),这样做的好处是可以结合 rdb 和 aof 的优点, 快速加载同时避免丢失过多的数据。缺点 aof 里面的 rdb 部分就是压缩格式不再是 aof 格式,可读性差。

  • 配置开启混合持久化aof-use-rdb-preamble yes

  • 命令开启混合持久化config set aof-use-rdb-preamble yes

开启混合持久化时,aof rewrite 的时候就直接把 rdb 的内容写到 aof 文件开头。aof 文件内容会变成如下

1
2
3
4
5
6
7
8
9
10
11
12
13
+------------------------+
| |
| |
| RDB |
| FORMAT |
| |
| |
| |
+------------------------+
| |
| AOF |
| FORMAT |
+------------------------+

持久化相关优化

fork操作优化

  • 控制redis实例最大可用内存:maxmemory

  • 合理配置Linux内存分配策略:vm.overcommit_momory=1(默认0,当内存少时fork阻塞不进行)

  • 降低fork频率:例如放宽AOF重写自动触发时机,不必要的全量复制

子进程

  • cpu

    开销:RDB和AOF文件生成,属于CPU密集型

    优化:不做CPU绑定,不和CPU密集型服务部署

  • 内存

    开销:fork内存开销,使用了linux的copy-on-write【父进程未发生改变的内存页,不进行copy-write】

    优化:echo never > /sys/kernel/mm/transparent_hugepage/enabled【不分配大内存页】

  • 硬盘

    开销:AOF和RDB文件写入,可以结合iostat,iotop分析

    优化:不和高硬盘负载服务部署一起,no-appendfsync-on-rewrite设置为yes

八、Redis主从复制原理和优化

  • 一个master可以有多个slave
  • 一个slave只能有一个master
  • 数据流向是单向的,master到slave

主从实现两种方式

  • 在从机上执行slaveof masterIp masterPort,此命令是异步。slaveof no one结束从属关系。

  • 修改redis配置文件

    1
    2
    slaveof masterIp masterPort               #配置主从
    slave-read-only yes #从节点只读

主从状态查看info replication

全量复制和部分复制

redis4后使用psync2实现复制使redis重启也可使用部分同步,还为解决在主库故障时候从库切换为主库时候使用部分同步机制。redis从库默认开启复制积压缓冲区功能,以便从库故障切换变化master后,其他落后该从库可以从缓冲区中获取缺少的命令。该过程的实现通过两组replid、offset替换原来的master runid和offset变量实现:

  • 第一组:master_replid和master_repl_offset

    如果redis是主实例,则表示为自己的replid和复制偏移量; 如果redis是从实例,则表示为自己主实例的replid1和同步主实例的复制偏移量。

  • 第二组:master_replid2和second_repl_offset

    无论主从,都表示自己上次主实例repid1和复制偏移量;用于兄弟实例或级联复制,主库故障切换psync

判断是否使用部分复制条件:如果从库提供的master_replid与master的replid不同,且与master的replid2不同,或同步速度快于master; 就必须进行全量复制,否则执行部分复制。

以下常见的主从切换都可以使用部分复制:

  • 一主一从发生切换,A->B 切换变成 B->A
  • 一主多从发生切换,兄弟节点变成父子节点时
  • 级别复制发生切换, A->B->C 切换变成 B->C->A

全量复制开销

  • bgsave时间
  • RDB文件网络时间
  • 从节点清空数据时间
  • 从节点加载RDB时间
  • 可能的AOF重写时间

当从库与主库断开时间过长导致自己的偏移量不在master_repl_offset允许的范围之内,会触发全量复制

主从相关参数配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
##############从库##############

slaveof <masterip> <masterport>
#设置该数据库为其他数据库的从数据库

masterauth <master-password>
#主从复制中,设置连接master服务器的密码(前提master启用了认证)

slave-serve-stale-data yes
# 当从库同主库失去连接或者复制正在进行,从库有两种运行方式:
# 1) 如果slave-serve-stale-data设置为yes(默认设置),从库会继续相应客户端的请求
# 2) 如果slave-serve-stale-data设置为no,除了INFO和SLAVOF命令之外的任何请求都会返回一个错误"SYNC with master in progress"

slave-priority 100
#当主库发生宕机时候,哨兵会选择优先级最高的一个称为主库,从库优先级配置默认100,数值越小优先级越高

slave-read-only yes
#从节点是否只读;默认yes只读,为了保持数据一致性,应保持默认


##############主库##############

repl-disable-tcp-nodelay no
#在slave和master同步后(发送psync/sync),后续的同步是否设置成TCP_NODELAY假如设置成yes,则redis会合并小的TCP包从而节省带宽,但会增加同步延迟(40ms),造成master与slave数据不一致假如设置成no,则redis master会立即发送同步数据,没有延迟
#前者关注性能,后者关注一致性

repl-ping-slave-period 10
#从库会按照一个时间间隔向主库发送PING命令来判断主服务器是否在线,默认是10秒

repl-backlog-size 1mb
#复制积压缓冲区大小设置

repl-backlog-ttl 3600
#master没有slave一段时间会释放复制缓冲区的内存,repl-backlog-ttl用来设置该时间长度。单位为秒。

min-slaves-to-write 3
min-slaves-max-lag 10
#设置某个时间断内,如果从库数量小于该某个值则不允许主机进行写操作,以上参数表示10秒内如果主库的从节点小于3个,则主库不接受写请求,min-slaves-to-write 0代表关闭此功能。

主从配置问题

maxmomory不一致导致丢失数据

数据结构参数优化只有优化了主机,从机未配置导致内存不一致,数据错误或丢失

九、Redis Sentinel

  • 主观下线:Sentinel根据配置条件,发现redis节点达到故障标准,则此Sentinel认为此redis节点下线
  • 客观下线:当Sentinel中认为此redis客观下线的总数达到配置阙值,则认为此节点客观下线

安装Sentinel

配置sentinel.conf文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#没有开启bind和密码的情况下,保护模式默认被开启。只接受来自环回IPv4和IPv6地址的连接。拒绝外部连接
bind 127.0.0.1 192.168.1.1
protected-mode no

#端口
port 26379

#是否守护进程模式运行
daemonize no

#pid以及日志文件位置
pidfile /var/run/redis-sentinel.pid
logfile ""

#工作目录
dir /tmp

#监控的master
#mymaster指此主从组的名称【Sentinel可以监控多个主从组】
#最后一个数字代码多少个sentinel主观认为此master宕机为客观宕机事实
sentinel monitor mymaster 127.0.0.1 6379 2

#当多少毫秒master不返回ping结果即认为主观宕机
sentinel down-after-milliseconds mymaster 30000

#master重新选举之后slave并发复制master数据的并发量
sentinel parallel-syncs mymaster 1

#故障转移超时时间,默认为3分钟
sentinel failover-timeout mymaster 180000

#当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会调用这个脚本
#如果脚本以“1”退出,则稍后重试执行(最多可执行10次),脚本的最长运行时间为60秒
#sentinel notification-script mymaster /var/redis/notify.sh

#当master因failover而发生改变,这个脚本将被调用,通知相关的客户端关于master地址已经发生改变的信息
#sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

#不允许使用SENTINEL SET设置notification-script和client-reconfig-script
sentinel deny-scripts-reconfig yes

Sentinel三个定时任务

  • 每10秒每个sentinel对master和slave执行info
    • 发现slave
    • 确认主从关系
  • 每2秒每个sentinel通过master节点的channel交换信息(pub/sub模式)
    • 通过__sentinel__:hello频道交互
    • 交互各节点的“看法”及自身信息
  • 每1秒每个sentinel对其他sentinel和redis节点执行ping
    • 心跳检查、失败依据

Master选举过程

第一步:Sentinel选举出leader

原因:只需要一个Sentinel完成故障转移

选举:通过sentinel is-master-down-by-addr命令都希望自己成为领导者

  • 每个做主观下线的Sentinel节点向其它Sentinel节点发送命令,要求它给自己投票
  • 收到命令的Sentinel节点如果没有同意其它Sentinel节点发送的命令,则同意投票否则拒绝
  • 如果该Sentinel节点发现自己的票数已经超过Sentinel半数,那么它将成为leader
  • 如果此过程未选出leader则等待一段时间继续选举

第二步:故障转移选举Master

  • 从slave节点中选出一个“合适”的节点作为新的master节点

    • 选择slave-prority最高的slave节点,如果存在则返回【一般不修改】
    • 选择复制偏移量最大的slave节点,如果存在则返回
    • 选择runId最小的slave节点
  • 对上面的slave节点执行slaveof no one命令让其成为master节点

  • 向剩余的slave节点发送slaveof命令,让它们成为master节点的slave节点,复制规则和paraller-sync参数有关

  • 更新对原来master节点配置为slave,并保存对其“关注”,当其恢复后命令它去复制新的master节点

手动下线master机器sentinel failover <masterName>

十、Redis Cluster

数据分布概论

Data-Distribution

分布方式 特点 典型产品
哈希分布 数据分散度高
Memcache

Redis Cluster

|
| 键值分布业务无关 |
| 无法顺序访问 |
| 支持批量操作 |
| 顺序分布 | 数据分散度易倾斜 |

BigTable

HBase

|
| 键值分布业务相关 |
| 可顺序访问 |
| 支持批量操作 |

哈希分布方式

节点取余分区

Delivery-Partition

进行取模运算,将余数相等的放入同一节点,简单易操作,增加节点时数据偏移,导致数据的前移达到80%,翻倍扩容可以使数据迁移从80%降到50%

一致性hash分区

Consistency-Hash

为系统中每个节点分配一个token,范围一般在0~2的32次方,这些token构成哈希环。数据读写执行节点查找操作时,先根据key计算hash值,然后顺时针找到第一个大于等于该哈希值的token节点,往往一个节点会对应多个token。加减节点会造成哈希环中部分数据无法命中,需要手动处理或者忽略这些数据,常用于缓存场景

虚拟哈希分区

Slot-Partition

虚拟分槽使用哈希函数把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。槽数范围远远大于节点数(redisCluster槽的范围是0~16383),每一个节点负责维护一部分槽以及所映射的键值数据

基本架构

cluster-architecture

  • 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽

  • 节点的fail是通过集群中超过半数的master节点检测失效时才生效

  • 客户端与redis节点直连,不需要中间proxy层.客户端连接集群中任何一个可用节点即可

  • redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->key

安装Cluster

修改redis配置文件

1
2
cluster-enable yes
cluster-config-file nodes-${port}.conf

原生安装

  • 启动所有节点

  • 执行redis-cli -p ${port} cluster meet ${ip} ${port}使节点相遇

  • 执行cluster addslots slot [slot...]分配槽

    1
    2
    3
    4
    5
    6
    7
    8
    start=$1
    end=$2
    port=$3
    for slot in `seq ${start} ${end}`
    do
    echo "slot:${slot}"
    /opt/redis/bin/redis-cli -p ${port} cluster addslots ${slot}
    done
  • 执行redis-cli -p ${port} cluster replicate ${nodeId}执行主从分配

集群命令安装

  • redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

    –cluster-replicas代表集群的每个主节点的从节点个数

    ruby安装已废弃

cluster配置参数

1
2
3
4
cluster-enable yes
cluster-config-file nodes-${port}.conf
cluster-node-timeout 15000
cluster-require-full-coverage yes #必须集群所有节点能提供服务才提供服务

集群伸缩

扩展集群

扩展步骤原理

  1. 对目标节点发送cluster setslot {slot} importing {targetNodeId}命令,让目标节点准备导入槽的数据
  2. 对源节点发送cluster setslot {slot} migrating {sourceNodeId}命令,让源节点准备槽数据的导出
  3. 源节点上循环执行cluster getkeyinslot {slot} count 命令,每次获取属于这个槽中键的个数
  4. 在源节点上执行migrate {sourceIp} {sourcePort} key 0 {timeout}命令,迁移指定的key
  5. 重复执行步骤3~4直到槽下所有数据完成迁移
  6. 向集群内所有主节点发送cluster setslot {slot} node {targetNodeId}命令,通知槽分配给目标节点

Cluster-Expend

扩展执行步骤

  • 加入集群

    执行redis-cli -p ${port} cluster meet ${ip} ${port}将节点加入集群

  • 设置主从关系

    redis-cli -p ${port} cluster replicate ${nodeId}设置主从关系

  • 任意节点执行迁移槽命令,后续过程根据提示进行

    redis-cli --cluster reshard {ip}:{port}

  • 查看节点分配情况

    redis-cli -p {prot} cluster nodes | grep master

集群缩容

Cluster-Shrink

迁移槽命令和扩展集群的迁移命令相同,迁移完成之后使用redis-cli cluster forget {downNodeId}下线节点

  • 迁移数据

    redis-cli --cluster reshard {ip}:{port} --cluster-from {sourceNodeId} --cluster-to {targetNodeId} --cluster-slots {slotsNum}

  • 下线节点

    redis-cli --cluster del-node {ip}:{port} {shutdownNodeId}

客户端路由

使用cluster keyslot ${key}可查看key对应的hash值

moved重定向

Cluster-Move

ask重定向

Cluster-Ask

smart客户端

实现原理:追求性能

  1. 从集群中选一个可运行节点,使用cluster slots初始化槽和节点映射
  2. 将cluster slots的结果映射到本地缓存,为每个节点创建JedisPool
  3. 准备执行命令(使用CRC16计算key对应的槽,找到映射节点执行)

Smart-Client

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
Set<HostAndPort> nodes = new HashSet<HostAndPort>();
nodes.add(new HostAndPort("192.168.1.158", 7000));
nodes.add(new HostAndPort("192.168.1.158", 7001));
nodes.add(new HostAndPort("192.168.1.158", 7002));
nodes.add(new HostAndPort("192.168.1.158", 7003));
nodes.add(new HostAndPort("192.168.1.158", 7004));
nodes.add(new HostAndPort("192.168.1.158", 7005));
JedisCluster jedisCluster = new JedisCluster(nodes);
jedisCluster.set("hello", "cluster");
System.out.println(jedisCluster.get("hello"));
jedisCluster.close();
}

批量操作

Redis主要提供了以下几种批量操作方式:

  • 批量get/set(multi get/set)
  • 管道(pipelining)
  • 事务(transaction)
  • 基于事务的管道(transaction in pipelining)

批量操作必须key在同一个槽,导致以上用法异常苛刻

方案一:传统的串行IO操作,也就说n个key,分n次串行操作来获取key,复杂度是o(n)

方案二:将mget操作(n个key),利用已知的hash函数算出key对应的节点,这样就可以得到一个这样的关系:Map<node, somekeys>,也就是每个节点对应的一些keys,这样将之前的o(n)的效率降低到o(node.size())

MuliCmd-Serial-Io

方案三:在方案二的基础上将串行取数据改为并行取数据,进一步提高效率

MuliCmd-parell-Io

方案四:通过redis自带的hashtag功能,强制一批key分配到某台机器上【不建议,大量数据会造成数据倾斜】

1
2
3
//使用{user}作为key,使key统一
jedisCluster.mset("{user}1001","zhangsan","{user}1002","lisi","{user}1003","wangwu");
List<String> users = jedisCluster.mget("{user}1001","{user}1002","{user}1003");

故障转移

故障发现

  • 通过ping/pong信息实现故障发现,当半数以上持有槽的主节点都标记某节点主观下线则为客观下线【向集群广播下线节点的fail消息】
  • 客观下线发送通知故障节点的从节点触发故障转义流程

故障恢复

  • 资格审查
    • 每个从节点检查与故障主节点的断线时间
    • 超过cluster-node-timeout * cluster-slave-validity-factor取消资格
    • cluster-slave-validity-factor默认是10
  • 准备选举时间
    • 最接近主节点的偏移量的从节点率先发起选举,稍后其他从节点发起选举
  • 选举投票
    • 收集票数大于N/2+1即为选举成功
  • 替换主节点
    • 当前从节点取消复制变为主节点(slave no one)
    • 执行clusterDelSlot撤销故障主节点负责的槽,并执行clusterAddSlot把这些槽分配给自己
    • 向集群广播自己的pong消息,表明已经替换了故障从节点

集群运维问题

集群完整性

cluster-require-full-coverage yes默认为yes

  • 集群中16384个槽全部可用:保证集群完整性
  • 节点故障或者正在故障转移,集群不可使用

大多数情况下业务无法容忍,建议cluster-require-full-coverage设置为no

PubSub广播

任意节点发布消息所有节点都会订阅到消息,消耗带宽较多。JedisCluster只会订阅任意一个节点

数据倾斜

造成的原因:

  • 节点槽分配不均
  • 不同槽对应的键值数量差异较大【可能存在hashTag】
  • 包含bigkey
  • 内存相关配置不一致

使用redis-cli --cluster info {ip}:{port}可以查看key、slot分布情况

使用redis-cli --cluster rebalance {ip}:{port}进行数据平衡【慎用】

从机读写问题

在集群模式下从节点不接受任何读写请求

  • 命令会重定向到负责槽的主节点
  • readonly命令可以读取【连接级别】

数据迁移

官方工具不只能从单机向集群迁移,不支持断点续传,不支持在线迁移,单线程影响速度,不建议使用官方工具

十一、缓存设计与优化

缓存使用的成本

  • 数据不一致:缓存层和数据层有时间窗口不一致,和更新策略有关
  • 代码维护成本:多了一层缓存逻辑
  • 运维成本:例如Redis Cluster

缓存更新策略

redis里面存储的过期时间,都是绝对时间点,所以如果两台机器时钟不同步,那么超过的数据会全部删除。

  • slaves不会独立删除数据,而是等待master给它发送删除指令的时候,再删除数据
  • 如果slave当选为master的时候,会先淘汰keys,然后再成为master

  • 设置maxmemory-policy值指定算法

    更新策略 解释
    volatile-lru 过期的键使用LRU策略剔除,没有可删除对象则退回到noeviction
    allkeys-lru 所有键均使用LRU策略剔除,直到腾出足够空间
    volatile-lfu 过期的键使用LFU策略剔除,没有可删除对象则退回到noeviction
    allkeys-lfu 所有键均使用LFU策略剔除,直到腾出足够空间
    volatile-random 过期的键使用随机策略剔除,没有可删除对象则退回到noeviction
    allkeys-random 所有键均使用随机策略剔除,直到腾出足够空间
    volatile-ttl 剔除TTL最小的键,没有可删除对象则退回到noeviction
    noeviction 默认策略,不做任何事,返回写错误

    LRU【Least Recently Used】最近最少被使用

    LFU【Least Frequently Used】最不常用

  • 被动更新

    当客户端方位key的时候,主动检测这些key是否过期,过期就删除

  • 主动更新

    每秒检测10次以下操作,测试随机的20个keys进行相关过期检测,删除所有的过期的keys,如果有多于25%的keys过期,重复此操作

策略 一致性 维护成本
算法剔除 最差
被动更新 较差
主动更新

低一致性:最大内存和淘汰策略

高一致性:超时剔除和主动更新结合,最大内存和淘汰策略兜底

缓存穿透&缓存雪崩&无底洞

缓存穿透

Cache-Miss

特点:

当缓存和数据库中都没有数据的时候,当查询Redis没有数据的时候,会继续查询数据库,数据库也没有数据,当大量查询请求发生或遭到恶意攻击时,这些访问全部透过Redis,并且数据库也没有数据,这种现象称为“缓存穿透”。

解决方案:

  1. 缓存空对象,storage返回一个空对象,将键存储在缓存层,下次请求此键之间返回空对象
    • 需要更多的键,建议设置过期时间
    • 缓存层和存储层数据“短期”不一致
  2. 布隆过滤器

缓存击穿

特点:

当Redis的热点数据key失效时,大量并发查询直接打到数据库,此时数据库负载压力骤增,这种现象称为“缓存击穿”

hotkey-expir

解决方案:

1.设置key值永不过期

2.使用互斥锁,查到后就回填缓存

缓存雪崩

特点:

缓存雪崩是指在短时间内,有大量缓存同时过期,导致大量的请求直接查询数据库,从而对数据库造成了巨大的压力,严重情况下可能会导致数据库宕机的情况叫做缓存雪崩。

crash

解决方案:

  • 随机设置key过期时间
  • 随机延时,让一部分查询先将数据缓存起来
  • 设置key值永不过期

无底洞问题

现象:增加节点机器性能没提升反而下降

Cache-Node-Add

解决方案参考批量操作

热点key的重建优化

Hot-Key

现象:热点key缓存重建过程过长导致浪费了不必要的资源

解决方案:

  1. 互斥锁【使用redis构建锁机制】

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    String get(String key) {
    String value = redis.get(key);
    if(value == null) {
    String mutexKey = "mutex:key:"+key;
    if(redis.SetParams.setParams().ex(180).nx()) {
    value = db.get(key);
    redis.set(key,value);
    redis.delete(mutexKey);
    }else {
    Thread.sleep(1000);
    get(key);
    }
    }
    return value;
    }
  2. 永不过期

    • 缓存层面:没有设置过期时间
    • 功能层面:为每个value添加逻辑过期时间,但发现超过逻辑过期时间后,会使用单独的线程取构建缓存
方案 优点 缺点
互斥锁 思路简单 代码复杂度增加
保证一致性 存在死锁的风险
永不过期 基本杜绝热点key重建问题 不保证一致性
逻辑过期时间增加维护成本和内存成本

十二、Redis布隆过滤器

布隆过滤器的原理

Bloom-Filter

  1. 首先需要k个hash函数,每个函数可以把key散列成为1个整数
  2. 初始化时,需要一个长度为n比特的数组,每个比特位初始化为0
  3. 某个key加入集合时,用k个hash函数计算出k个散列值,并把数组中对应的比特位置为1
  4. 判断某个key是否在集合时,用k个hash函数计算出k个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,认为在集合中

优点:不需要存储key,节省空间

缺点:算法判断key在集合中时,有一定的概率key其实不在集合中,且无法删除

google-guava库实现了java版的布隆过滤器

Redis布隆过滤器

使用插件的方式部署

redis4.0之后支持使用插件的方式使用Bloom filters和cuckoo filters,redis4.0之前需手动使用代码的方式编写布隆过滤器,安装步骤参考下列链接

GitHub地址 使用文档

建议在conf配置文件中配置,不使用redis-server --loadmodule /path/to/rebloom.so启动

1
loadmodule /path/to/rebloom.so

布隆过滤器命令

命令 说明
BF.RESERVE {key} {error_rate} {size} 创建布隆过滤器,error_rate为错误率,size为预期数据大小
BF.ADD {key} {item} 添加item到指定布隆过滤器
BF.MADD {key} {item} [item …] 批量添加item到指定布隆过滤器
BF.EXISTS {key} {item} 判断item是否存在与指定布隆过滤器
BF.MEXISTS {key} {item} [item …] 批量判断item是否存在与指定布隆过滤器

JavaAPI

导入maven依赖

1
2
3
4
5
<dependency>
<groupId>com.redislabs</groupId>
<artifactId>jrebloom</artifactId>
<version>1.1.0</version>
</dependency>

Api使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
JedisPool jedisPool = new JedisPool("192.168.1.158", 6379);
Client client = new Client(jedisPool);
client.createFilter("main6", 10000, 0.000001);
//批量插入数据
for (int i = 0; i <100; i++) {
String values[] = new String[100];
for (int j = 0; j < 100; j++) {
String value = "item" + (i * 100 +j);
values[j] = value;
}
client.addMulti("main6", values);
}
//判断是否存在
System.out.println(client.exists("main6", "item1"));
System.out.println(client.exists("main6", "a"));
jedisPool.close();
}

十三、Redis开发规范

BigKey处理

发现BigKey

  • debug object {key} 查看指定key的详细信息
  • redis-cli --bigkeys 扫描出BigKey【全表扫描,阻塞,建议从节点本地执行】

BigKey删除

场景:当key非常大时,delete命令执行十分缓慢,会发生阻塞【过期bigkey也是进行删除操作也会阻塞】

redis4.0之后:可以使用unlink命令进行后台删除,不阻塞前台

生命周期管理

  • 使用OBJECT IDLETIME {key}查看key的闲置时间

  • 过期时间不易集中

命令优化

有遍历需求可以使用hscansscanzscan代替【这些扫描命令在field较少时COUNT参数不会生效

必要情况下使用monitor命令监控,注意时间不要过长

Java客户端优化

参数名 含义 默认值 建议
testWhileIdle 是否开启空闲资源检测 false true
timeBetweenEvictionRunsMillis 空闲资源检测周期 -1 自选,也可使用JedisPoolConfig中的默认值
minEvictiableIdleTimeMillis 资源池中资源最小空闲时间 30分钟 自选,也可使用JedisPoolConfig中的默认值
numTestsPerEvictionRun 做空闲资源检测每次的采样数 3 自选,如果设置为-1则为全部做空闲检测

maxIdle需要设置为接近maxTotal

预估maxTotal方法的例子:

一次命令时间平均耗时1ms,一个连接QPS大约1000,业务期忘的QPS时50000,理论上maxTotal=50000/1000=50

十四、内存管理

内存消耗

内存统计

执行info memory命令可以查看内存信息

主要属性名 属性说明
used_memory 实际存储数据的内存总量
used_memory_rss redis进程占用的总物理内存
maxmemory 最大内存
maxmemory_policy 内存剔除策略
mem_fragmentation_ratio used_memory_rss/used_memory比值,表示内存碎片率

内存消耗划分

Memory-Used

  • 客户端缓冲区设置规则

    client-output-buffer-limit

    :客户端类型,分为三种

    ​ (a)normal:普通客户端

    ​ (b)slave:用从节点用于复制,伪裝成客户端

    ​ ©pubsub:发布订阅客户端 :如客户使用的输出冲区大于hardlimit客户端会被立即关闭

    :如果客户端使用的输出缓冲区超过了 并且持续了,客户会被立即关闭

    • 普通客户端默认:client-output-buffer-limit normal 0 0 0
    • salve客户端默认:client-output-buffer-limit slave 256mb 64mb 60
    • pubsub客户端默认:client-output-buffer-limit slave 32mb 8mb 60
  • 复制缓冲区:用于slave和master断开重连时不进行全量复制保存偏移数据使用

    默认为repl-backlog-size 1mb

  • AOF缓冲区:AOF重写期间,AOF缓冲区,没有容量限制

内存回收策略

删除过期值

  • 惰性删除:访问key->expired dict->del key【先在过期表中找,发现过期删除key,返回null】
  • 定时删除:每秒运行10次,采样删除

超过maxmemory使用maxmemory-policy进行控制,参见缓存更新策略

十五、开发运维事项

Linux内核优化

vm.overcommit_memory

Redis建议vm.overcommit_memory = 1(影响fork操作)

立即生效:

永久生效:vm.overcommit_memory = 1写入到/etc/sysctl.conf文件中

含义
0 表示内核将检查是否有足够可用的内存。如果有足够可用的内存,内存申请通过,否则内存申请失败,并返回错误给进程
1 表示内核允许超量使用内存直到用完为止
2 表示内存绝不过量使用,即整个系统内存不能超过swap+50%的RAM值

swappiness

策略
0 Linux3.5及以上:宁愿OOM Killer也不用swap
Linux3.5及以下:宁愿swap也不用OOM Killer
1 Linux3.5及以上:宁愿swap也不用OOM Killer
60 默认值
100 操作系统会主动使用swap

建议:Linux3.5以上vm.swappiness = 1,否则vm.swappiness = 0

立即生效:echo {bestValue} > /proc/sys/vm/swappiness

永久生效:vm.swappiness = {bestValue} 写入到/etc/sysctl.conf

THP(Transparent huge page)

建议禁用,Centos7在/sys/kernel/mm/transparent_hugepage/enabled下设置为never即可

THP为大内存页时fork子线程时Copy-On-Write可能造成延迟

ulimit

建议将Redis启动用户的文件句柄限制调成10032,限制文件etc/security/limits

TCP backlog

建议将/proc/sys/net/core/somaxconn系统TCP backlog的限制设置为与Redis一样默认511

Redis安全问题

在配置文件中配置

1
2
#将命令更改为另一个字符串,原命令失效。如果字符串为空则表示禁用此命令
rename-command {cmd} {str}

附各种数据类型的内部编码:

Data-Encoding

附Redis云平台CacheCloud【一键部署、监控、运维、数据迁移工具等】 GitHub项目地址

留言與分享

全文搜索属于最常见的需求,开源的 Elasticsearch (以下简称 Elastic)是目前全文搜索引擎的首选。

它可以快速地储存、搜索和分析海量数据。维基百科、Stack Overflow、Github 都采用它。

Elastic 的底层是开源库 Lucene。但是,你没法直接用 Lucene,必须自己写代码去调用它的接口。Elastic 是 Lucene 的封装,提供了 REST API 的操作接口,开箱即用。

本文从零开始,讲解如何使用 Elastic 搭建自己的全文搜索引擎。每一步都有详细的说明,大家跟着做就能学会。

一、安装

Elastic 需要 Java 8 环境。如果你的机器还没安装 Java,可以参考这篇文章,注意要保证环境变量JAVA_HOME正确设置。

安装完 Java,就可以跟着官方文档安装 Elastic。直接下载压缩包比较简单。

1
2
3
4

$ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.5.1.zip
$ unzip elasticsearch-5.5.1.zip
$ cd elasticsearch-5.5.1/

接着,进入解压后的目录,运行下面的命令,启动 Elastic。

1
2

$ ./bin/elasticsearch

如果这时报错"max virtual memory areas vm.maxmapcount [65530] is too low",要运行下面的命令。

1
2

$ sudo sysctl -w vm.max_map_count=262144

如果一切正常,Elastic 就会在默认的9200端口运行。这时,打开另一个命令行窗口,请求该端口,会得到说明信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

$ curl localhost:9200

{
"name" : "atntrTf",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "tf9250XhQ6ee4h7YI11anA",
"version" : {
"number" : "5.5.1",
"build_hash" : "19c13d0",
"build_date" : "2017-07-18T20:44:24.823Z",
"build_snapshot" : false,
"lucene_version" : "6.6.0"
},
"tagline" : "You Know, for Search"
}

上面代码中,请求9200端口,Elastic 返回一个 JSON 对象,包含当前节点、集群、版本等信息。

按下 Ctrl + C,Elastic 就会停止运行。

默认情况下,Elastic 只允许本机访问,如果需要远程访问,可以修改 Elastic 安装目录的config/elasticsearch.yml文件,去掉network.host的注释,将它的值改成0.0.0.0,然后重新启动 Elastic。

1
2

network.host: 0.0.0.0

上面代码中,设成0.0.0.0让任何人都可以访问。线上服务不要这样设置,要设成具体的 IP。

二、基本概念

2.1 Node 与 Cluster

Elastic 本质上是一个分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个 Elastic 实例。

单个 Elastic 实例称为一个节点(node)。一组节点构成一个集群(cluster)。

2.2 Index

Elastic 会索引所有字段,经过处理后写入一个反向索引(Inverted Index)。查找数据的时候,直接查找该索引。

所以,Elastic 数据管理的顶层单位就叫做 Index(索引)。它是单个数据库的同义词。每个 Index (即数据库)的名字必须是小写。

下面的命令可以查看当前节点的所有 Index。

1
2

$ curl -X GET 'http://localhost:9200/_cat/indices?v'

2.3 Document

Index 里面单条的记录称为 Document(文档)。许多条 Document 构成了一个 Index。

Document 使用 JSON 格式表示,下面是一个例子。

1
2
3
4
5
6

{
"user": "张三",
"title": "工程师",
"desc": "数据库管理"
}

同一个 Index 里面的 Document,不要求有相同的结构(scheme),但是最好保持相同,这样有利于提高搜索效率。

2.4 Type

Document 可以分组,比如weather这个 Index 里面,可以按城市分组(北京和上海),也可以按气候分组(晴天和雨天)。这种分组就叫做 Type,它是虚拟的逻辑分组,用来过滤 Document。

不同的 Type 应该有相似的结构(schema),举例来说,id字段不能在这个组是字符串,在另一个组是数值。这是与关系型数据库的表的一个区别。性质完全不同的数据(比如productslogs)应该存成两个 Index,而不是一个 Index 里面的两个 Type(虽然可以做到)。

下面的命令可以列出每个 Index 所包含的 Type。

1
2

$ curl 'localhost:9200/_mapping?pretty=true'

根据规划,Elastic 6.x 版只允许每个 Index 包含一个 Type,7.x 版将会彻底移除 Type。

三、新建和删除 Index

新建 Index,可以直接向 Elastic 服务器发出 PUT 请求。下面的例子是新建一个名叫weather的 Index。

1
2

$ curl -X PUT 'localhost:9200/weather'

服务器返回一个 JSON 对象,里面的acknowledged字段表示操作成功。

1
2
3
4
5

{
"acknowledged":true,
"shards_acknowledged":true
}

然后,我们发出 DELETE 请求,删除这个 Index。

1
2

$ curl -X DELETE 'localhost:9200/weather'

四、中文分词设置

首先,安装中文分词插件。这里使用的是 ik,也可以考虑其他插件(比如 smartcn)。

1
2

$ ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v5.5.1/elasticsearch-analysis-ik-5.5.1.zip

上面代码安装的是5.5.1版的插件,与 Elastic 5.5.1 配合使用。

接着,重新启动 Elastic,就会自动加载这个新安装的插件。

然后,新建一个 Index,指定需要分词的字段。这一步根据数据结构而异,下面的命令只针对本文。基本上,凡是需要搜索的中文字段,都要单独设置一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

$ curl -X PUT 'localhost:9200/accounts' -d '
{
"mappings": {
"person": {
"properties": {
"user": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
},
"title": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
},
"desc": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
}
}
}
}
}'

上面代码中,首先新建一个名称为accounts的 Index,里面有一个名称为person的 Type。person有三个字段。

  • user
  • title
  • desc

这三个字段都是中文,而且类型都是文本(text),所以需要指定中文分词器,不能使用默认的英文分词器。

Elastic 的分词器称为 analyzer。我们对每个字段指定分词器。

1
2
3
4
5
6

"user": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
}

上面代码中,analyzer是字段文本的分词器,search_analyzer是搜索词的分词器。ik_max_word分词器是插件ik提供的,可以对文本进行最大数量的分词。

五、数据操作

5.1 新增记录

向指定的 /Index/Type 发送 PUT 请求,就可以在 Index 里面新增一条记录。比如,向/accounts/person发送请求,就可以新增一条人员记录。

1
2
3
4
5
6
7

$ curl -X PUT 'localhost:9200/accounts/person/1' -d '
{
"user": "张三",
"title": "工程师",
"desc": "数据库管理"
}'

服务器返回的 JSON 对象,会给出 Index、Type、Id、Version 等信息。

1
2
3
4
5
6
7
8
9
10

{
"_index":"accounts",
"_type":"person",
"_id":"1",
"_version":1,
"result":"created",
"_shards":{"total":2,"successful":1,"failed":0},
"created":true
}

如果你仔细看,会发现请求路径是/accounts/person/1,最后的1是该条记录的 Id。它不一定是数字,任意字符串(比如abc)都可以。

新增记录的时候,也可以不指定 Id,这时要改成 POST 请求。

1
2
3
4
5
6
7

$ curl -X POST 'localhost:9200/accounts/person' -d '
{
"user": "李四",
"title": "工程师",
"desc": "系统管理"
}'

上面代码中,向/accounts/person发出一个 POST 请求,添加一个记录。这时,服务器返回的 JSON 对象里面,_id字段就是一个随机字符串。

1
2
3
4
5
6
7
8
9
10

{
"_index":"accounts",
"_type":"person",
"_id":"AV3qGfrC6jMbsbXb6k1p",
"_version":1,
"result":"created",
"_shards":{"total":2,"successful":1,"failed":0},
"created":true
}

注意,如果没有先创建 Index(这个例子是accounts),直接执行上面的命令,Elastic 也不会报错,而是直接生成指定的 Index。所以,打字的时候要小心,不要写错 Index 的名称。

5.2 查看记录

/Index/Type/Id发出 GET 请求,就可以查看这条记录。

1
2

$ curl 'localhost:9200/accounts/person/1?pretty=true'

上面代码请求查看/accounts/person/1这条记录,URL 的参数pretty=true表示以易读的格式返回。

返回的数据中,found字段表示查询成功,_source字段返回原始记录。

1
2
3
4
5
6
7
8
9
10
11
12
13

{
"_index" : "accounts",
"_type" : "person",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"user" : "张三",
"title" : "工程师",
"desc" : "数据库管理"
}
}

如果 Id 不正确,就查不到数据,found字段就是false

1
2
3
4
5
6
7
8
9

$ curl 'localhost:9200/weather/beijing/abc?pretty=true'

{
"_index" : "accounts",
"_type" : "person",
"_id" : "abc",
"found" : false
}

5.3 删除记录

删除记录就是发出 DELETE 请求。

1
2

$ curl -X DELETE 'localhost:9200/accounts/person/1'

这里先不要删除这条记录,后面还要用到。

5.4 更新记录

更新记录就是使用 PUT 请求,重新发送一次数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

$ curl -X PUT 'localhost:9200/accounts/person/1' -d '
{
"user" : "张三",
"title" : "工程师",
"desc" : "数据库管理,软件开发"
}'

{
"_index":"accounts",
"_type":"person",
"_id":"1",
"_version":2,
"result":"updated",
"_shards":{"total":2,"successful":1,"failed":0},
"created":false
}

上面代码中,我们将原始数据从"数据库管理"改成"数据库管理,软件开发"。 返回结果里面,有几个字段发生了变化。

1
2
3
4

"_version" : 2,
"result" : "updated",
"created" : false

可以看到,记录的 Id 没变,但是版本(version)从1变成2,操作类型(result)从created变成updatedcreated字段变成false,因为这次不是新建记录。

六、数据查询

6.1 返回所有记录

使用 GET 方法,直接请求/Index/Type/_search,就会返回所有记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

$ curl 'localhost:9200/accounts/person/_search'

{
"took":2,
"timed_out":false,
"_shards":{"total":5,"successful":5,"failed":0},
"hits":{
"total":2,
"max_score":1.0,
"hits":[
{
"_index":"accounts",
"_type":"person",
"_id":"AV3qGfrC6jMbsbXb6k1p",
"_score":1.0,
"_source": {
"user": "李四",
"title": "工程师",
"desc": "系统管理"
}
},
{
"_index":"accounts",
"_type":"person",
"_id":"1",
"_score":1.0,
"_source": {
"user" : "张三",
"title" : "工程师",
"desc" : "数据库管理,软件开发"
}
}
]
}
}

上面代码中,返回结果的 took字段表示该操作的耗时(单位为毫秒),timed_out字段表示是否超时,hits字段表示命中的记录,里面子字段的含义如下。

  • total:返回记录数,本例是2条。
  • max_score:最高的匹配程度,本例是1.0
  • hits:返回的记录组成的数组。

返回的记录中,每条记录都有一个_score字段,表示匹配的程序,默认是按照这个字段降序排列。

6.2 全文搜索

Elastic 的查询非常特别,使用自己的查询语法,要求 GET 请求带有数据体。

1
2
3
4
5

$ curl 'localhost:9200/accounts/person/_search' -d '
{
"query" : { "match" : { "desc" : "软件" }}
}'

上面代码使用 Match 查询,指定的匹配条件是desc字段里面包含"软件"这个词。返回结果如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

{
"took":3,
"timed_out":false,
"_shards":{"total":5,"successful":5,"failed":0},
"hits":{
"total":1,
"max_score":0.28582606,
"hits":[
{
"_index":"accounts",
"_type":"person",
"_id":"1",
"_score":0.28582606,
"_source": {
"user" : "张三",
"title" : "工程师",
"desc" : "数据库管理,软件开发"
}
}
]
}
}

Elastic 默认一次返回10条结果,可以通过size字段改变这个设置。

1
2
3
4
5
6

$ curl 'localhost:9200/accounts/person/_search' -d '
{
"query" : { "match" : { "desc" : "管理" }},
"size": 1
}'

上面代码指定,每次只返回一条结果。

还可以通过from字段,指定位移。

1
2
3
4
5
6
7

$ curl 'localhost:9200/accounts/person/_search' -d '
{
"query" : { "match" : { "desc" : "管理" }},
"from": 1,
"size": 1
}'

上面代码指定,从位置1开始(默认是从位置0开始),只返回一条结果。

6.3 逻辑运算

如果有多个搜索关键字, Elastic 认为它们是or关系。

1
2
3
4
5

$ curl 'localhost:9200/accounts/person/_search' -d '
{
"query" : { "match" : { "desc" : "软件 系统" }}
}'

上面代码搜索的是软件 or 系统

如果要执行多个关键词的and搜索,必须使用布尔查询

1
2
3
4
5
6
7
8
9
10
11
12

$ curl 'localhost:9200/accounts/person/_search' -d '
{
"query": {
"bool": {
"must": [
{ "match": { "desc": "软件" } },
{ "match": { "desc": "系统" } }
]
}
}
}'

七、参考链接

(完)

留言與分享

ansible教程-hands-on

分類 devops, ansible

Ansible 学习笔记

简介

  • 基于Python开发的自动化运维工具
  • 集合了众多运维工具(puppet、cfengine、chef、func、fabric)的优点
  • 基于模块工作,本身没有批量部署能力,批量部署由运行的模块实现
  • 提供自动化运维框架

作用

  • 批量系统配置
  • 批量程序部署
  • 批量运行命令

框架组成

  • Connection Plugins:负责和被监控端实现通信
  • Host Inventory:定义监控主机的配置文件
  • 模块
    • 核心模块
    • command模块
    • 自定义模块
  • 插件:完成记录日志邮件等功能
  • Playbook:剧本,非必需,可让节点一次性运行多个任务

架构图

Ansible架构图

架构字段解释

  • Ansible:核心程序
  • Host Inventory:主机清单(可定义主机组和主机)
  • 模块:实际执行任务的组件
  • Playbook:Yaml定义的剧本文件(类似shell脚本)
  • Connect Plugin:连接插件

工作原理

  1. 控制端通过inventory定义主机组
  2. 通过编写playbook或AD-HOC命令
  3. 使用SSH将模块推送到被控端
  4. 被控端执行任务(要求被控端有Python2环境)
  5. 任务完成后返回结果

命令执行过程

  1. 加载配置文件(/etc/ansible/ansible.cfg)
  2. 查找主机配置文件
  3. 加载对应模块文件
  4. 生成临时Python脚本并传输到远程主机
  5. 存储在远程主机的~/.ansible/tmp/目录
  6. 添加执行权限
  7. 执行并返回结果
  8. 删除临时文件

执行流程理解图

执行流程

特性

  • No Agents:无需在被控端安装客户端
  • No Server:无服务端架构
  • Modules in Any Languages:支持任意语言开发模块
  • YAML:使用YAML语言编写playbook
  • SSH by Default:默认基于SSH工作
  • Multi-tier Solution:支持多级指挥

配置文件

配置文件优先级(从高到低):

  1. 项目目录下的ansible.cfg
  2. 用户家目录下的.ansible.cfg
  3. 默认的/etc/ansible/ansible.cfg

主要配置项

1
2
3
4
5
6
7
8
9
10
11
#inventory = /etc/ansible/hosts      # 主机列表
#library = /usr/share/my_modules/ # 模块库目录
#remote_tmp = ~/.ansible/tmp # 远程临时目录
#local_tmp = ~/.ansible/tmp # 本地临时目录
#forks = 5 # 并发数
#sudo_user = root # 默认sudo用户
#ask_sudo_pass = True # 是否询问sudo密码
#ask_pass = True # 是否询问SSH密码
#remote_port = 22 # 远程端口
host_key_checking = False # 跳过主机指纹检查
log_path = /var/log/ansible.log # 日志路径

优点

  • 轻量级,客户端无需安装agent
  • 批量任务可写成脚本且无需分发
  • 基于Python,维护简单
  • 支持sudo

环境搭建

主机规划

主机 IP 角色
h1 192.168.50.60 master
h2 192.168.50.61 host1
h3 192.168.50.62 host2
h4 192.168.50.63 host3

SSH免密登录配置

1
2
ssh-keygen -t dsa -f ~/.ssh/id_dsa -P ""
ssh-copy-id -i ~/.ssh/id_dsa.pub root@192.168.50.61

安装

1
yum install -y ansible

主机清单配置

1
2
3
4
5
6
7
[hosts]
192.168.50.61
192.168.50.62
192.168.50.63

[local]
127.0.0.1

测试

1
2
ansible webservers -m command -a 'uptime'
ansible all -m ping

常用模块

setup模块

查看远程主机基本信息

1
ansible webservers -m setup

fetch模块

从主机获取文件

1
ansible 192.168.50.61 -m fetch -a 'src=/root/t2 dest=/root'

file模块

设置文件属性

选项:

  • force: 强制创建软链接(yes|no)
  • group: 文件属组
  • mode: 文件权限
  • owner: 文件属主
  • path: 文件路径(必选)
  • recurse: 递归设置属性(仅目录)
  • src: 源文件路径(仅state=link时)
  • dest: 链接目标路径(仅state=link时)
  • state:
    • directory: 创建目录
    • file: 不创建文件
    • link: 创建软链接
    • hard: 创建硬链接
    • touch: 创建空文件或更新修改时间
    • absent: 删除文件/目录

示例:

1
2
# 创建符号链接
ansible hosts -m file -a "src=/etc/resolv.conf dest=/root/resolv.conf state=link"

copy模块

复制文件到远程主机

选项:

  • backup: 覆盖前备份(yes|no)
  • content: 直接设置文件内容(替代src)
  • dest: 目标绝对路径(必选)
  • directory_mode: 递归设置目录权限
  • force: 强制覆盖(yes|no)
  • src: 本地源文件路径

示例:

1
ansible hosts -m copy -a "src=/etc/ansible/ansible.cfg dest=/tmp/ansible.cfg owner=root group=root mode=0755"

command模块

在远程主机执行命令

选项:

  • creates: 文件存在时不执行
  • free_form: Linux命令
  • chdir: 执行前切换目录
  • removes: 文件不存在时不执行
  • executable: 指定shell路径

示例:

1
2
ansible webservers -m command -a "ls -al /tmp"
ansible webservers -m command -a 'useradd abc'

shell模块

支持管道操作的命令执行

示例:

1
2
3
4
5
# 执行远程脚本
ansible hosts -m shell -a "/root/test.sh"

# 批量添加用户密码
ansible hosts -m shell -a 'useradd abc && echo 123 |passwd --stdin abc'

cron模块

管理计划任务

选项:

  • minute/hour/day/month/weekday: 时间设置
  • job: 任务命令
  • name: 任务名称
  • user: 执行用户
  • state: present(添加)|absent(删除)

示例:

1
2
3
4
5
# 创建任务
ansible all -m cron -a "minute='*/5' job='/usr/sbin/ntpdate 192.168.50.60 &>/dev/null' name='sync time'"

# 删除任务
ansible all -m cron -a "name='sync time' state=absent"

hostname模块

管理主机名

示例:

1
2
# 修改主机名
ansible 192.168.50.63 -m hostname -a "name=t4"

yum模块

软件包管理

示例:

1
2
3
4
5
# 安装tree
ansible all -m yum -a "name=tree"

# 卸载
ansible all -m yum -a 'name=tree state=absent'

service模块

服务管理

示例:

1
2
# 启动ntpdate服务
ansible all -m service -a "name=ntpdate state=started enabled=true"

group模块

用户组管理

示例:

1
2
# 添加系统组
ansible all -m group -a "name=gansible system=true"

user模块

用户管理

选项:

  • name: 用户名
  • state: present(新增)|absent(删除)
  • force: 删除时是否删除家目录
  • system: 是否系统用户
  • uid: 指定UID
  • shell: 指定shell
  • home: 指定家目录

示例:

1
2
# 添加系统用户
ansible all -m user -a "name=ccc system=true"

YAML语法

  • 数据序列化格式
  • 基本结构:
    1
    2
    3
    4
    key: value
    - item1
    - item2
    - item3
    例如: {name: jerry, age: 21}

Playbook

核心元素

  • Tasks: 任务列表
  • Variables: 变量
  • Templates: 模板文件
  • Handlers: 条件触发任务
  • Roles: 角色

基础组件

  • Hosts: 目标主机
  • remote_user: 执行用户
  • sudo_user: sudo用户
  • tasks: 任务列表

运行Playbook

1
2
3
4
5
6
7
8
# 语法检查
ansible-playbook --syntax-check playbook.yaml

# 测试运行
ansible-playbook -C playbook.yaml

# 实际运行
ansible-playbook playbook.yaml

示例

1. 添加用户和组

1
2
3
4
5
6
7
- hosts: all
remote_user: root
tasks:
- name: add a group
group: name=test system=test
- name: add a user
user: name=test group=test system=true

2. 配置HTTP服务

1
2
3
4
5
6
7
8
9
- hosts: hosts
remote_user: root
tasks:
- name: install http
yum: name=httpd state=latest
- name: install conf
copy: src=/root/httpd.conf dest=/etc/httpd/conf/httpd.conf.bak
- name: start
service: name=httpd state=reloaded enabled=true

更多模块

  • synchronize: 使用rsync同步文件
  • raw: 直接执行命令(类似shell)

模块帮助

# 列出所有模块
ansible-doc -l

# 查看模块帮助
ansible-doc -s MOD_NAME

留言與分享

ansible教程-概念

分類 devops, ansible

Ansible 概念

这些概念适用于 Ansible 的所有用途。在使用 Ansible 或阅读文档之前,您应该了解它们。

  • [控制节点]

  • [被管理节点]

  • [清单]

  • [剧本]

    • [剧集]

      • [角色]

      • [任务]

      • [处理器]

  • [模块]

  • [插件]

  • [集合]

[控制节点]

运行 Ansible CLI 工具(ansible-playbookansibleansible-vault 等)的机器。您可以使用任何满足软件要求的计算机作为控制节点——笔记本电脑、共享桌面和服务器都可以运行 Ansible。您也可以在称为执行环境的容器中运行 Ansible。

可以使用多个控制节点,但 Ansible 本身并不协调它们之间的工作,有关此类功能,请参见AAP

[被管理节点]

也称为“主机”,这些是您旨在用 Ansible 管理的目标设备(服务器、网络设备或任何计算机)。

除非您使用ansible-pull,否则通常不会在被管理节点上安装 Ansible,但这很少见,也不是推荐的设置。

[清单]

由一个或多个“清单源”提供的被管理节点列表。您的清单可以指定每个节点的特定信息,例如 IP 地址。它还用于分配组,这既允许在剧集中选择节点,也允许批量分配变量。

要了解有关清单的更多信息,请参阅使用清单部分。有时,清单源文件也称为“主机文件”。

[剧本]

它们包含剧集(这是 Ansible 执行的基本单元)。这既是“执行概念”,也是我们用来描述ansible-playbook操作的文件的方式。

剧本是用 YAML 编写的,易于阅读、编写、共享和理解。要了解有关剧本的更多信息,请参阅Ansible 剧本

[剧集]

Ansible 执行的主要上下文,此剧本对象将被管理节点(主机)映射到任务。剧集包含变量、角色和已排序的任务列表,并且可以重复运行。它基本上由对映射的主机和任务的隐式循环组成,并定义如何迭代它们。

[角色]

可在剧集内部使用的可重用 Ansible 内容(任务、处理器、变量、插件、模板和文件)的有限分发。

要使用任何角色资源,必须将角色本身导入剧集。

[任务]

应用于被管理主机的“操作”的定义。您可以使用ansibleansible-console(两者都会创建一个虚拟剧集)一次执行单个任务。

[处理器]

任务的一种特殊形式,仅在之前的任务发出通知并且导致“已更改”状态时才执行。

[模块]

Ansible 复制到并在每个被管理节点上执行(如有需要)的代码或二进制文件,以完成每个任务中定义的操作。

每个模块都有其特定的用途,从管理特定类型数据库上的用户到管理特定类型网络设备上的 VLAN 接口。

您可以使用任务调用单个模块,也可以在剧本中调用多个不同的模块。Ansible 模块分组在集合中。要了解 Ansible 包含多少集合,请参阅集合索引

[插件]

扩展 Ansible 核心功能的代码片段。插件可以控制您如何连接到被管理节点(连接插件)、操作数据(过滤器插件),甚至控制在控制台中显示的内容(回调插件)。

有关详细信息,请参阅使用插件

[集合]

Ansible 内容的分发格式,可以包含剧本、角色、模块和插件。您可以通过Ansible Galaxy安装和使用集合。

要了解有关集合的更多信息,请参阅使用 Ansible 集合

集合资源可以彼此独立且离散地使用。

留言與分享

ansible教程-入门篇

分類 devops, ansible

Ansible 简介

Ansible 提供开源自动化,可降低复杂性并在任何地方运行。使用 Ansible 可以自动执行几乎任何任务。以下是 Ansible 的一些常见用例

  • 消除重复并简化工作流程

  • 管理和维护系统配置

  • 持续部署复杂的软件

  • 执行零停机滚动更新

Ansible 使用简单的、人类可读的脚本(称为剧本)来自动化您的任务。您在剧本中声明本地或远程系统的期望状态。Ansible 确保系统保持在该状态。

作为自动化技术,Ansible 围绕以下原则设计

无代理架构

通过避免在 IT 基础设施上安装额外软件来降低维护开销。

简单性

自动化剧本使用简单的 YAML 语法,代码就像文档一样易于阅读。Ansible 也是分散式的,使用 SSH 和现有的操作系统凭据来访问远程机器。

可扩展性和灵活性

通过模块化设计轻松快速地扩展您自动化的系统,该设计支持各种操作系统、云平台和网络设备。

幂等性和可预测性

当系统处于您的剧本描述的状态时,即使多次运行剧本,Ansible 也不会更改任何内容。

使用 Ansible 开始自动化

通过创建自动化项目、构建清单和创建“Hello World”剧本开始使用 Ansible。

  1. 安装 Ansible。

    1
    pip install ansible
  2. 在您的文件系统上创建一个项目文件夹。

    1
    mkdir ansible_quickstart && cd ansible_quickstart

    使用单一目录结构可以更轻松地添加到源代码管理,以及重用和共享自动化内容。

构建清单

清单将受管节点组织在集中式文件中,这些文件为 Ansible 提供系统信息和网络位置。使用清单文件,Ansible 可以使用单个命令管理大量主机。

要完成以下步骤,您需要至少一个主机系统的 IP 地址或完全限定域名 (FQDN)。出于演示目的,主机可以在容器或虚拟机中本地运行。您还必须确保您的公共 SSH 密钥已添加到每个主机上的 authorized_keys 文件中。

继续 Ansible 入门并按如下步骤构建清单

  1. 在您在 上一步中创建的 ansible_quickstart 目录中创建一个名为 inventory.ini 的文件。

  2. inventory.ini 文件添加一个新的 [myhosts] 组,并指定每个主机系统的 IP 地址或完全限定域名 (FQDN)。

    1
    2
    3
    4
    [myhosts]
    192.0.2.50
    192.0.2.51
    192.0.2.52
  3. 验证您的清单。

    1
    ansible-inventory -i inventory.ini --list
  4. Ping 清单中的 myhosts 组。

    1
    ansible myhosts -m ping -i inventory.ini

    注意

    如果控制节点和受管节点上的用户名不同,请使用 ansible 命令传递 -u 选项。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    192.0.2.50 | SUCCESS => {
    "ansible_facts": {
    "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
    }
    192.0.2.51 | SUCCESS => {
    "ansible_facts": {
    "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
    }
    192.0.2.52 | SUCCESS => {
    "ansible_facts": {
    "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
    }

INI 或 YAML 格式的清单

您可以使用 INI 文件或 YAML 创建清单。在大多数情况下,例如上一步中的示例,对于少量受管节点,INI 文件简单易读。

随着受管节点数量的增加,使用 YAML 格式创建清单变得更合理。例如,以下是 inventory.ini 的等效项,它声明受管节点的唯一名称并使用 ansible_host 字段

1
2
3
4
5
6
7
8
myhosts:
hosts:
my_host_01:
ansible_host: 192.0.2.50
my_host_02:
ansible_host: 192.0.2.51
my_host_03:
ansible_host: 192.0.2.52

构建清单的技巧

  • 确保组名有意义且唯一。组名也区分大小写。

  • 避免在组名中使用空格、连字符和前导数字(使用 floor_19,而不是 19th_floor)。

  • 根据主机的**什么**、**哪里**和**何时**逻辑地将主机分组到您的清单中。

    什么

    根据拓扑结构对主机分组,例如:db、web、leaf、spine。

    哪里

    按地理位置对主机分组,例如:数据中心、区域、楼层、建筑物。

    何时

    按阶段对主机分组,例如:开发、测试、过渡、生产。

使用元组

使用以下语法创建一个元组来组织清单中的多个组

以下清单说明了数据中心的结构基础。此示例清单包含一个 network 元组,其中包括所有网络设备,以及一个 datacenter 元组,其中包括 network 组和所有 Web 服务器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
leafs:
hosts:
leaf01:
ansible_host: 192.0.2.100
leaf02:
ansible_host: 192.0.2.110

spines:
hosts:
spine01:
ansible_host: 192.0.2.120
spine02:
ansible_host: 192.0.2.130

network:
children:
leafs:
spines:

webservers:
hosts:
webserver01:
ansible_host: 192.0.2.140
webserver02:
ansible_host: 192.0.2.150

datacenter:
children:
network:
webservers:

创建变量

变量设置受管节点的值,例如 IP 地址、FQDN、操作系统和 SSH 用户,因此您无需在运行 Ansible 命令时传递它们。

变量可以应用于特定主机。

1
2
3
4
5
6
7
8
webservers:
hosts:
webserver01:
ansible_host: 192.0.2.140
http_port: 80
webserver02:
ansible_host: 192.0.2.150
http_port: 443

变量也可以应用于组中的所有主机。

1
2
3
4
5
6
7
8
9
10
webservers:
hosts:
webserver01:
ansible_host: 192.0.2.140
http_port: 80
webserver02:
ansible_host: 192.0.2.150
http_port: 443
vars:
ansible_user: my_server_user

创建剧本

剧本是 Ansible 用于部署和配置受管节点的自动化蓝图,采用 YAML 格式。

剧本

一系列定义 Ansible 执行操作顺序的剧目,自上而下,以实现总体目标。

剧目

一个有序的任务列表,映射到清单中的受管节点。

任务

对单个模块的引用,定义 Ansible 执行的操作。

模块

Ansible 在受管节点上运行的代码或二进制单元。Ansible 模块按集合分组,每个模块都有一个完全限定的集合名称 (FQCN)

完成以下步骤以创建用于 ping 主机并打印“Hello world”消息的剧本

  1. 在您之前创建的 ansible_quickstart 目录中创建一个名为 playbook.yaml 的文件,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    - name: My first play
    hosts: myhosts
    tasks:
    - name: Ping my hosts
    ansible.builtin.ping:

    - name: Print message
    ansible.builtin.debug:
    msg: Hello world
  2. 运行您的剧本。

    1
    ansible-playbook -i inventory.ini playbook.yaml

Ansible 返回以下输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
PLAY [My first play] ****************************************************************************

TASK [Gathering Facts] **************************************************************************
ok: [192.0.2.50]
ok: [192.0.2.51]
ok: [192.0.2.52]

TASK [Ping my hosts] ****************************************************************************
ok: [192.0.2.50]
ok: [192.0.2.51]
ok: [192.0.2.52]

TASK [Print message] ****************************************************************************
ok: [192.0.2.50] => {
"msg": "Hello world"
}
ok: [192.0.2.51] => {
"msg": "Hello world"
}
ok: [192.0.2.52] => {
"msg": "Hello world"
}

PLAY RECAP **************************************************************************************
192.0.2.50: ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.0.2.51: ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
192.0.2.52: ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

在此输出中,您可以看到

  • 您为剧目和每个任务指定的名称。您应始终使用易于验证和排查剧本问题的描述性名称。

  • “收集事实”任务隐式运行。默认情况下,Ansible 会收集有关您的清单的信息,以便在剧本中使用。

  • 每个任务的状态。每个任务的状态为 ok,表示它已成功运行。

  • 剧目摘要,总结了每个主机上所有任务的结果。在此示例中,共有三个任务,因此 ok=3 表示每个任务都已成功运行。

恭喜,您已开始使用 Ansible!

留言與分享

  • 第 1 頁 共 1 頁
作者的圖片

Kein Chan

這是獨立全棧工程師Kein Chan的技術博客
分享一些技術教程,命令備忘(cheat-sheet)等


全棧工程師
資深技術顧問
數據科學家
Hit廣島觀光大使


Tokyo/Macau