Redis学习笔记(全)

PunkLu 2019年12月03日 88次浏览

Redis笔记

开始使用Redis

安装启动Redis

cd /usr/local

安装gcc编译器
sudo yum -y install gcc gcc-c++ libstdc++-devel 
make MALLOC=libc

安装wget下载工具
yum install wget

下载redis源码
wget http://download.redis.io/releases/redis-5.0.5.tar.gz

解压
tar -xvzf redis-5.0.5.tar.gz
cd redis-5.0.5

编译
make
cd src
make install PREFIX=/usr/local/redis
cd ..
mkdir /usr/local/redis/etc
mv redis.conf /usr/local/redis/etc

设置以守护进程的方式在后台启动redis
vi /usr/local/redis/etc/redis.conf //将daemonize no 改成daemonize yes

启动redis
cd  /usr/local/redis
./bin/redis-server /usr/local/redis/etc/redis.conf

关闭redis:
不要使用kill -9 ,而是使用/bin/redis-cli shutdown,这样可以尽可能地防止数据丢失。

使用/bin/redis-cli时可以通过-h和-p指定要连接到的主机名/ip地址以及要连接的端口号。

设置Redis开机自启动

编写脚本:vim /etc/init.d/redis

#!/bin/bash
#chkconfig: 22345 10 90
#description: Start and Stop redis

REDISPORT=8530
EXEC=/usr/local/redis-5.0.5/src/redis-server
CLIEXEC=/usr/local/redis-5.0.5/src/redis-cli

PIDFILE=/var/run/redis.pid
CONF="/usr/local/redis/etc/conf/redis.conf"

case "$1" in
    start)
        if [ -f $PIDFILE ];then
            echo "$PIDFILE exists,process is already running or crashed"
        else
            echo "Starting Redis server..."
            $EXEC $CONF
        fi
        ;;
    stop)
        if [ ! -f $PIDFILE ];then
            echo "$PIDFILE does not exist,process is not running"
        else
            PID=$(cat $PIDFILE)
            echo "Stopping..."
            $CLIEXEC -p $REDISPORT shutdown
            while [ -x /proc/${PID} ]
                do
                    echo "Waiting for Redis to shutdown..."
                    sleep 1
                done
                echo "Redis stopped"
        fi
        ;;
    restart)
        "$0" stop
        sleep 3
        "$0" start
        ;;
    *)
        echo "Please use start or stop or restart as first argument"
        ;;
esac

赋予脚本执行权限:

chmod +x /etc/init.d/redis

把脚本添加进系统服务列表:

chkconfig --add redis
chkconfig redis on
 
查看所有注册的脚本文件
chkconfig --list##

获取服务器信息

./bin/redis-cli
127.0.0.1:6379> INFO

或直接在shell命令行中使用redis-cli INFO命令

INFO命令的返回信息的全部段落:

名称描述
ServerRedis服务器的基本信息
Clients客户端连接的状态和指标
Memoory大致的内存消耗指标
Persistence数据持久化相关的状态和指标
Stats总体统计数据
Replication主从复制相关的状态和指标
CPUCPU使用情况
ClusterRedis Cluster的状态
Keyspace数据库相关的统计数据

构建Redis监控应用的常用实践手段之一,就是通过定期地使用INFO命令来获取信息。

理解Redis事件模型

​ Redis极大程度地获益于其单进程、非阻塞、多路复用的I/O模型。当然,在某些情况下,Redis也会创建线程或子进程来执行某些任务。

​ Redis包含了一个简单但强大的异步事件库,称为ae。该库中封装了不同操作系统的polling机制,如epoll、kqueue和select等。

​ 以linux中的epoll为例。首先调用epoll_create通知操作系统内核我们要使用epoll。然后,调用epoll_ctl把文件描述符(FD)和所关注的事件类型传给内核。之后,调用epoll_wait等待通过epoll_ctl所设置的文件描述符上所关注的事件。当文件描述符被更新后,内核会向应用程序发送通知。唯一需要做的就是为所关注的这些事件创建事件处理函数/回调函数。

​ 显而易见,在polling的过程中没有线程或子进程的创建和交互。因此,该模型的关键优点就在于它是一个轻量级上下文切换的I/O模型。在上下文切换上花费的代价不大。但是,在polling模型中,在一条命令被处理完成前,Redis不能处理其他的命令。因此在使用redis时,意外的延迟问题是最让人头痛的。

数据类型

​ 在使用Redis进行应用设计和开发时,首先应该考虑的是,Redis原生支持的哪种数据类型最适合我们的场景。

​ 为了更好地说明这些数据类型及其操作,将展示一个Relp示例程序。Relp是一个供用户评论和推荐优秀餐厅、购物中心或其他服务的应用。在Relp中,可以浏览一个城市中不同的餐厅,找到在一定距离范围内排名前10的健身房,给本地服务打分和发表评论意见等。会把Relp所涉及的数据存储到Redis中。

使用字符串(string)类型

​ 字符串类型是Redis的基本数据类型之一。Redis中所有的键都必须是字符串。

​ 在Relp中,可以将餐厅名称和地址分别用作键和值。例如,假如想设置"Extreme Pizza"餐厅的地址:

127.0.0.1:6379> SET "Extreme Pizza" "300 Broadway,New York,NY"
OK

使用GET命令可以轻松地取回字符串中的值:

127.0.0.1:6379> GET "Extreme Pizza"
"300 Broadway,New York,NY"

当GET一个不存在的键时会返回(nil):

127.0.0.1:6379> GET "Yummy Pizza"
(nil)

​ STRLEN命令返回字符串的长度,例如,假如想获取"Extreme Pizza"地址的长度,可以使用:

127.0.0.1:6379> STRLEN “Extreme Pizza”
(integer) 26

​ 对不存在的键执行STRLEN命令会返回0.

​ Redis还提供了一些命令来直接操作字符串,使用这些命令的好处是,不需要通过GET命令来读取一个字符串的值,再用SET命令将(处理后的)字符串写回去。

​ 1、使用APPEND命令可以向一个键的字符串值末尾追加字符串:

127.0.0.1:6379> APPEND "Extreme Pizza" “ 10011”
(integer) 32

127.0.0.1:6379> GET "Extreme Pizza"
"300 Broadway,New York,NY 10011"

​ 2、使用SETRANGE命令可以覆盖字符串值的一部分。例如,假如想更新"Extreme Pizza"的地址,可以使用:

127.0.0.1:6379> SETRANGE "Extreme Pizza" 14 "Washington, DC 20009"
(integer) 34
127.0.0.1:6379> GET "Extreme Pizza"
"300 Broadway,Washington,DC 20009"

​ SETRANGE命令会覆盖字符串的一部分(从指定的偏移开始,直到整个字符串的末尾)。在Redis中,字符串的偏移是从0开始的。SETRANGE命令会在覆盖完成后返回新字符串的长度。

扩展

​ 如果某个键已存在,那么SET命令会覆盖该键此前对应的值。有时可能不希望在键存在的时候盲目地覆盖键。这时,可以使用EXIST命令来测试键的存在性。事实上,Redis提供了SETNX命令(简称为不存在时SET),用于原子性地、仅在键不存在时设置键的值。如果键的值设置成功,则SETNX返回1,如果键已存在,则返回0且不覆盖原来的值。

​ SET命令中的NX选项与SETNX一样。相反地,SET命令的XX选项表示仅在键已存在时才设置值。

​ 可以通过使用MSET和MGET命令来一次性地设置和获取多个键的值。使用MGET的优点在于整个操作是原子性的,意味着所有的键都是在客户机和服务器之间的一次通信中设置的,可以节省网络开销。

​ MSET和MGET的用法为:

MSET KEY VALUE [KEY VALUE...]
MGET KEY VALUE [KEY VALUE...]

127.0.0.1:6379> MSET "Sakura Sushi" "123 Ellis St, Chicago,IL" "Green Curry Thai" "456 American Way,Seattle,WA"

OK

127.0.0.1:6379> MGET "Sakura Sushi" "Green Curry Thai" "nonexistent"

1) "123 Ellis St,Chicago,IL"
2) "456 American Way,Seattle,WA"
3) (nil)

Redis内部编码格式

​ Redis使用了三种不同的编码方式来存储字符串对象,并会根据每个字符串值自动决定所使用的编码方式:

​ 1、int:用于能够使用64位有符号整数表示的字符串

​ 2、embstr:用于长度小于或等于44字节的字符串,这种类型的编码在内存和性 能方面更有效率

​ 3、raw:用于长度大于44字节的字符串。

​ 可以使用 OBJECT ENCODING 键名称的方法查看Redis值的内部编码格式。

使用列表(list)类型

​ 列表可被用作栈或者队列。

​ 使用一个列表来存储用户最喜欢的餐厅。使用LPUSH在列表的左端插入两个餐厅的名称:

127.0.0.1:6379> LPUSH favorite_restaurants "PF Chang's" "Olive Garden"

(integer) 2

如果要获取列表中所有餐厅的名称,可以使用LRANGE:

127.0.0.1:6379> LRANGE favorite_restaurants 0 -1
1) "Olive Garden"
2) "PF Chang's"

如果要在列表的右端插入餐厅的名称,可以使用RPUSH:

127.0.0.1:6379> RPUSH favorite_restaurants "Outback Steakhouse" "Red Lobster"
(integer) 4
127.0.0.1:6379> LRANGE favorite_restaurants 0 -1
1) "Olive Garden"
2) "PF Chang's"
3) "Outback Steakhouse"
4) "Red Lobster"

如果要在"PF Chang's"之后插入一个新餐厅的名称,可以使用LINSERT:

127.0.0.1:6379> LINSERT favorite_restaurants AFTER "PF Chang's" "Indian Tandoor"
(integer) 5
127.0.0.1:6379> LRANGE favorite_restaurants 0 -1
1) "Olive Garden"
2) "PF Chang's"
3) "Indian Tandoor"
4) "Outback Steakhouse"
5) "Red Lobster"

如果要获取列表中位于索引位置3处餐厅的名称,可以使用LINDEX:

127.0.0.1:6379> LINDEX favorite_restaurants 3
"Outback Steakhouse"

如果仅想在列表存在时才将元素插入到列表中,那么可以使用LPUSHX和RPUSHX命令。

可以使用LPOP或RPOP从列表中删除一个元素。这两个命令会从列表的左端或右端移除第一个元素,并返回其值。当对不存在的键执行LPOP或RPOP时,将返回nil:

127.0.0.1:6379> LPOP favorite_restaurants
"Olive Garden"
127.0.0.1:6379> RPOP favorite_restaurants
"Red Lobster"
127.0.0.1:6379> LPOP non_existent
(nil)

可以使用LINDEX命令从列表中获取位于指定索引处的元素,也可以使用LRANGE命令获取一个范围内的元素。

Redis中列表的索引定义:

​ 假设有N个元素,列表的索引可以按照从左到右的方式指定为0~N-1,也可以按照从右到左的方式指定为-1~-N。因此,0~-1就表示整个列表。

LTRIM命令可用于在删除列表中的多个元素的同时,只保留由Start和end索引所指定范围内的元素。

127.0.0.1:6379> LRANGE favorite_restaurants 0 -1
1) "PF Chang's"
2) "Indian Tandoor"
3) "Outback Steakhouse"
127.0.0.1:6379> LTRIM favorite_restaurants 1 -1
OK
127.0.0.1:6379> LRANGE favorite_restaurants 0 -1
1) "Indian Tandoor"
2) "Outback Steakhouse"

可以使用LSET命令设置列表中指定索引位置处元素的值:

127.0.0.1:6379> LSET favorite_restaurants 1 "Longhorn Steakhouse"
OK
127.0.0.1:6379> LRANGE favorite_restaurants 0 -1
1) "Indian Tandoor"
2) "Longhorn Steakhouse"

使用哈希(Hash)类型

​ 哈希表示字段和值之间的映射关系。Redis数据集本身就可以被看作一个哈希。在本案例中,将使用哈希来存储餐厅的信息,例如地址、电话号码和评分等。

​ 使用HMSET命令来设置"Kyoto Ramen"餐厅的属性信息:

127.0.0.1:6379> HMSET "Kyoto Ramen" "address" "801 Mission St,San JOSE,CA" "phone" "555-123-6543" "rating" "5.0"
OK

​ 使用HMGET命令从一个哈希中获取多个字段对应的值:

127.0.0.1:6379> HMGET "Kyoto Ramen" "address" "phone" "rating"
1) "801 Mission St,San JOSE,CA"
2) "555-123-6543"
3) "5.0"

​ 使用HGET命令从一个哈希中获取某个字段对应的值:

127.0.0.1:6379> HMGET "Kyoto Ramen" "rating"
"5.0"

​ 使用HEXISTS命令测试一个哈希中是否存在某个字段:

127.0.0.1:6379> HEXISTS "Kyoto Ramen" "phone"
(integer) 1
127.0.0.1:6379> HEXISTS "Kyoto Ramen" "hours"
(integer) 0

​ 使用HGETALL命令获取一个哈希中的所有字段和值:

127.0.0.1:6379> HGETALL "Kyoto Ramen"
1) "address"
2) "801 Mission St,San JOSE,CA"
3) "phone"
4) "555-123-6543"
5) "rating"
6) "5.0"

​ 可以使用HSET命令设置单个字段的值,此命令可用于修改现有字段的值或添加新的字段:

127.0.0.1:6379> HSET "Kyoto Ramen" "rating" "4.9"
(integer) 0
127.0.0.1:6379> HSET "Kyoto Ramen" "status" "open"
(integer) 1
127.0.0.1:6379> HMGET "Kyoto Ramen" "rating" "status"
1) "4.9"
2) "open"

​ 使用HDEL命令从哈希中删除字段:

127.0.0.1:6379> HDEL "Kyoto Ramen" "address" "phone"
(integer) 2
127.0.0.1:6379> HGETALL "Kyoto Ramen"
1) "rating"
2) "4.9"

​ 默认情况下,HSET和HMSET会覆盖现有的字段。HSETNX命令则仅在字段不存在的情况下才设置字段的值,可用于防止HSET的默认覆盖行为:

127.0.0.1:6379> HSETNX "Kyoto Ramen" "phone" "555-555-0001"
(integer) 0
127.0.0.1:6379> HGET "Kyoto Ramen" "phone"
"555-555-0001"

​ 对于不存在的键或字段,HMGET和HGET将返回nil:

127.0.0.1:6379> HMGET "Kyoto Ramen" "rating" "hours"
1) "4.9"
2)(nil)

127.0.0.1:6379> HGET "Little Sheep Mongolian" "address"
(null)

使用集合(set)类型

​ 集合类型是由唯一、无序对象组成的集合(collection)。Redis的值对象可以是字符串集合。在本案例中,将餐厅的标签保存在Redis中,并演示Redis用于集合操作的基本命令。

​ 使用SADD命令给"Original Buffalo Wings"餐厅添加标签:

127.0.0.1:6379> SADD "Original Buffalo Wings" "affordable" "spicy" "busy" "great taste"
(integer) 4

​ 使用SISMEMBER 测试一个元素是否位于集合中:

127.0.0.1:6379> SISMEMBER "Original Buffalo Wings" "busy"
(integer) 1

​ 使用SREM命令从集合中删除元素,例如,让我们从餐厅的标签中删除"busy"和"spicy":

127.0.0.1:6379> SREM "Original Buffalo Wings" "busy" "spicy"
(integer) 2
127.0.0.1:6379> SISMEMBER "Original Buffalo Wings" "busy"
(integer) 0
127.0.0.1:6379> SISMEMBER "Original Buffalo Wings" "spicy"
(integer) 0

​ 使用SCARD命令获取集合中成员的数量:

127.0.0.1:6379> SCARD "Original Buffalo Wings"
(integer) 2

​ Redis提供了一组集合运算相关的命令,SUNION和SUNIONSTORE用于计算并集,SINTER和SINTERSTORE用于计算交集,SDIFF和SDIFFSTORE用于计算差集。不带STORE后缀的命令只返回相应操作的结果集合,而带STORE后缀的命令则会将结果存储到一个指定的键中。

​ 再举一个使用SINTER和SINTERSTORE命令的例子,给另一家餐厅"Big Bear Wings"添加标签,然后获取"Original Buffalo Wings"和"Big Bear Wings"餐厅共有的标签:

127.0.0.1:6379> SMEMBERS "Original Buffalo Wings"
1) "affordable"
2) "great taste"
127.0.0.1:6379> SADD "Big Bear Wings" "affordable" "spacious" "great music"
(integer) 3
127.0.0.1:6379> SINTER "Original Buffalo Wings" "Big Bear Wings"
1) "affordable"
127.0.0.1:6379> SINTERSTORE "common_tags" "Original Buffalo Wings" "Big Bear Wings"
(integer) 1
127.0.0.1:6379> SMEMBERS "common_tags"
1) "affordable"

使用有序集合(sorted set)类型

​ 与集合相比,有序集合是一个类似但更复杂的数据类型。这种集合中的每个元素都拥有一个可用于排序的权重,并且可以按顺序从集合中获取元素。对于需要一直保持数据有序的场景,使用这种原生的有序特性很方便。

​ 使用ZADD命令将点评数和每个餐厅的名字放入一个有序集合中:

127.0.0.1:6379> ZADD ranking:restaurants 100 "Olive Garden" 23 "PF Chang's" 34 "Outback Steakhouse" 45 "Red Lobster" 88 "Longhorn Steakhouse"
(integer) 5

​ 然后使用ZREVRANGE命令来获取这个排名:

127.0.0.1:6379> ZREVRANGE ranking:restaurants 0 -1 WITHSCORES
1) "Olive Garden"
2) ”100"
3) "Longhorn Steakhouse"
4) "88"
5) "Red Lobster"
6) "45"
7) "Outback Steakhouse"
8) "34"
9) "PF Chang's"
10) "23"

​ 如果Relp中的某个用户点了赞,可以使用命令ZINCRBY对餐厅的投票数加一:

127.0.0.1:6379> ZINCRBY ranking:restaurants 1 "Red Lobster"

​ 如果某个用户想要浏览某个特定餐厅的排名和投票数,可以使用ZREVRANK和ZSCORE命令:

127.0.0.1:6379> ZREVRANK ranking:restaurants "Olive Garden"
(integer) 0
127.0.0.1:6379> ZSCORE ranking:restaurants "Olive Garden"
"100"

​ 当出现了另一个来自不同数据源的、更靠谱的餐厅排名时,如果需要合并这两个排名的话,可以使用ZUNIONSTORE命令:

127.0.0.1:6379> ZADD ranking2:restaurants 50 "Olive Garden" 33 "PF Chang's" 55 "Outback Steakhouse" 190 "Kung Pao House"
(integer) 4

使用1:2的权重合并ranking:restaurants和ranking2:restaurants。即ranking:restaurants * 1 + ranking2:restaurants * 2
127.0.0.1:6379> ZUNIONSTORE totalranking 2 ranking:restaurants ranking2:restaurants WEIGHTS 1 2
(integer) 6
127.0.0.1:6379> ZREVRANGE totalranking 0 -1 WITHSCORES
1) "Kung Pao House"
2) "380"
3) "Olive Garden"
4) "200"
5) "Outback Steakhouse"
6) "144"
7) "PF Chang's"
8) "89"
9) "Longhorn Steakhouse"
10) "88"
11) "Red Lobster"
12) "46"

​ 在ZADD命令中使用NX选项,能够实现在不更新已存在成员的情况下只添加新的成员。类似的,选项XX允许在不向集合中增加新元素的情况下更新集合。还需要注意的是,多个不同的成员可能具有相同的权重。在这种情况下,Redis将按照字典顺序进行排序。

​ 可以使用ZRAVRANGE命令查找出排名前三的餐厅:

127.0.0.1:6379> ZREVRANGE totalranking 0 2 WITHSCORES
1) "Kung Pao House"
2) "380"
3) "Olive Garden"
4) "200"
5) "Outback Steakhouse"
6) "144"

使用HyperLogLog类型

​ 在日常的数据处理场景中,“唯一计数”是一项常见的任务。在Redis中,虽然可以使用集合来进行唯一计数。但是当数据量增大到上千万时,就会出现内存消耗过高、性能下降的问题。如果不需要获取数据集的内容,只是想得到不同值的个数,就可以使用HyperLogLog数据类型来优化使用集合类型时存在的内存和性能问题。

​ 若要计算Relp中到访名为Olive Garden的餐厅的不同客户数,可以通过PFADD命令把用户id加入到一个HyperLogLog中:

127.0.0.1:6379> PFADD "Counting:Olive Garden" "0000123"
(integer) 1
127.0.0.1:6379> PFADD "Counting:Olive Garden" "0023992"
(integer) 2

​ 然后,使用PFCOUNT命令获取该餐厅的不同到访者数量:

127.0.0.1:6379> PFCOUNT "Counting:Olive Garden"
(integer) 2

​ 再举个更加复杂的例子,如果想要展示Olive Garden在一个星期中的独立访客数,并将其作为每周流行度的标志。那么,可以每天生成一个HyperLogLog,然后用PFMERGE命令把这七天的数据合并为一个:

127.0.0.1:6379> PFADD "Counting:Olive Garden:20191125" "0023992" "0023991" "0045992"
(integer) 1
127.0.0.1:6379> PFADD "Counting:Olive Garden:20191126" "0023992" "0023991" "0045992"
(integer) 1
127.0.0.1:6379> PFADD "Counting:Olive Garden:20191127" "0024492" "0023211" "0045292"
(integer) 1
127.0.0.1:6379> PFADD "Counting:Olive Garden:20191128" "0023999" "0063991" "0045922"
(integer) 1
127.0.0.1:6379> PFADD "Counting:Olive Garden:20191129" "0023292" "0023991" "0045921"
(integer) 1
127.0.0.1:6379> PFADD "Counting:Olive Garden:20191130" "0043282" "0023984" "0045092"
(integer) 1
127.0.0.1:6379> PFADD "Counting:Olive Garden:20191201" "0023992" "0023991" "0045992"
(integer) 1

合并这一周的访问量:
127.0.0.1:6379> PFMERGE "Counting:Olive Garden:20191125week" "Counting:Olive Garden:20191125" "Counting:Olive Garden:20191126" "Counting:Olive Garden:20191127" "Counting:Olive Garden:20191128" "Counting:Olive Garden:20191129" "Counting:Olive Garden:20191130" "Counting:Olive Garden:20191201" 
OK

127.0.0.1:6379> PFCOUNT "Couting:Olive Garden:20191125week"
(integer) 14

​ HLL算法返回的基数可能不准确(标准差小于1%)。

使用Geo类型

​ Redis从3.2版本开始正式引入了Geo(地理位置)相关的API,用于支持存储和查询这些地理位置相关场景中的坐标。

​ 可以使用GEOADD命令把位于加州的五家餐厅增加到名为restaurants:CA的Geo集合中:

127.0.0.1:6379> GEOADD restaurants:CA -121.896321 37.916750 "Olive Garden" -117.910937 33.804047 "P.F. Chang's" -118.508020 34.453276 "Outback Steakhouse" -119.152439 34.264558 "Red Lobster" -122.276909 39.458300 "Longhorn Charcoal Pit"
(integer) 5

​ 使用GEOPOS命令从Geo集合中获取指定成员的坐标:

127.0.0.1:6379> GEOPOS restaurants:CA "Red Lobster"
1)  1) "-119.1524389386177063"
	2) "34.26455707283378871"

​ 假设客人当前经纬度是-121.923170/37.878506,如果想知道距当前位置5km以内的餐厅,那么可以使用:

127.0.0.1:6379> GEORADIUS restaurants:CA -121.923170 37.878506 5 km
1) "Olive Garden"

​ 有时会需要比较两家餐厅之间的距离,此时,可以使用GEODIST命令:

127.0.0.1:6379> GEODIST restaurants:CA "P.F Chang's" "Outback Steakhouse" km
"90.7557"  // 表示距离为90.7557千米

​ 可以通过GEORADIUSBYMENBER命令查找GEO集合中距离此GEO中的另一个位置距离小于某个范围的位置。例如,可以搜索Geo集合中距离"Outback Steakhouse" 距离小于100km的餐厅,而"Outback Steakhouse"本身就是Geo集合的成员之一:

127.0.0.1:6379> GEORADIUSBYMEMBER restaurants:CA "Outback Steakhouse" 100 km
1) "Red Lobster"
2) "Outback Steakhouse"
3) "P.F Chang's"

数据特性

使用位图(bitmap)

​ 位图是由比特位组成的数组。Redis中的位图不是一种新的数据类型,它实际的底层数据类型是字符串。因为字符串本质上二进制大对象,所以可以将其视作位图。同时因为位图存储的是布尔信息,所以在某些情况下可以节省大量的内存空间。

​ 当Relp中一个ID为100的用户使用过餐厅预定功能后,就可以设置相应的比特位:

127.0.0.1:6379> SETBIT "users_tried_reservation" 100 1
(integer) 0

​ 使用如下的命令判断用户id为400的用户是否曾经使用过在线下单功能:

127.0.0.1:6379> GETBIT "users_tried_online_orders" 400

​ 使用BITCOUNT命令计算有多少用户曾经使用过某个特定功能:

127.0.0.1:6379> BITCOUNT "users_tried_reservation"
(integer) 1

​ BITOP命令用于进行位操作,该命令支持四种操作:AND、OR、XOR和NOT。位运算的结果会被存储在一个目标键中:

127.0.0.1:6379> BITOP AND "users_tried_both_reservation_and_online_orders" "users_tried_online_orders" "users_tried_reservation"
(integer) 13
127.0.0.1:6379> BITCOUNT "users_tried_both_reservation_and_online_orders"
(integer) 0

设置键的过期时间

​ 将距离用户当前位置最近的五个餐厅的ID存储到一个Redis列表中,由于用户的位置可能经常改变,所以应该在这个列表上设置一个过期时间。在过期后,需要再次使用用户的当前位置获取餐厅列表。

​ 创建一个名为"closest_restaurants_ids"的餐厅ID的列表:

127.0.0.1:6379> LPUSH "closest_restaurants_ids" 100 200 233 543 222
(integer) 5

​ 使用EXPIRE命令将键的超时时间设置为300秒:

127.0.0.1:6379> EXPIRE "closest_restaurants_ids" 300
(integer) 1

​ 可以使用TTL命令,在键过期前查看剩余时间:

127.0.0.1:6379> TTL "closest_restaurants_ids"
(integer) 269

​ 等待300秒后,直到键过期之后,再使用EXISTS命令判断键是否存在时将返回0:

127.0.0.1:6379> EXISTS "closest_restaurants_ids"
(integer) 0

​ 在一个键过期后,当客户端试图访问已过期键时,Redis会立即将其从内存中删除。Redis这种删除键的方式被称为被动过期。对于那些已经过期且永远不会再被访问到的键,Redis还会定期地运行一个基于概率的算法来进行主动删除。更具体的说,Redis会随机选择设置了过期时间的20个键。在这20个被选中的键中,已过期的会被立即删除。如果选中的键中有超过25%的键已经过期且被删除,那么Redis会再次随机选择20个键并重复这个过程。默认情况下,上述过程每秒运行10次(可通过配置文件中hz的值进行设置)。

​ 可以通过以下的方式清除一个键的过期时间:

​ 1、使用PERSIST命令使其成为持久的键

​ 2、键的值被替换或删除。包括SET、GETSET和*STORE在内的命令去清除过期时间。不过,修改列表、集合或哈希的元素却不会清除过期时间。

​ 3、被另一个没有过期时间的键重命名。

​ 如果键存在但未设置过期时间,则TTL命令返回-1。如果键不存在,则返回-2。

使用SORT命令

​ Redis列表或集合中的元素是无序的,而有序集合中的元素是根据其权重排序的。有时可能需要获取一个Redis列表或集合的已排序副本,或者以某种非权重顺序对有序集合中的元素进行排序。Redis为这些需求提供了一个方便的命令SORT。

​ 如果所有的元素都是数值,可以简单地运行SORT命令按升序对元素排序。假设在Relp中将用户喜爱餐厅的ID存储在Redis集合中,那么可以使用:

127.0.0.1:6379> SADD "user:123:favorite_restaurants_ids" 200 365 104 455 333
(integer) 5
127.0.0.1:6379> SORT "user:123:favorite_restaurants_ids"
1) "104"
2) "200"
3) "333"
4) "365"
5) "455"

​ 如果存在非数值的元素且想按字典顺序对它们进行排序,那么需要增加修饰符ALPHA。假设把餐厅的名称存储在集合中并希望对餐厅名称按照字典顺序排序:

127.0.0.1:6379> SADD "user:123:favorite_restaurants" "Dunkin Donuts" "Subway" "KFC" "Burger King" "Wendy's"
(integer) 5
127.0.0.1:6379> SORT "user:123:favorite_restaurants" ALPHA
1) "Burger King"
2) "Dunkin Donuts"
3) "KFC"
4) "Subway"
5) "Wendy's"

​ 在使用SORT命令时添加元素符DESC会按降序返回元素。默认情况下,SORT命令会排序并返回所有元素。但是,可以通过使用LIMIT修饰符来限制返回元素的数量。在使用LIMIT修饰符时,需要同时指定起始偏移量(要跳过元素的数量)和数量(返回元素的数量),例如,找出用户最喜欢的前三家餐厅:

127.0.0.1:6379> SORT "user:123:favorite_restaurants" ALPHA LIMIT 0 3
1) "Burger King"
2) "Dunkin Donuts"
3) "KFC"

​ 有时,可能不希望按值对元素进行排序,而是按在某些其他键中定义的权重来对元素进行排序。比如,可能需要按照定义在形如restaurants_rating_200(200是餐厅的id)的键中的评级对用户喜欢的餐厅进行排序。这也可以使用SORT完成:

127.0.0.1:6379> SET "restaurants_rating_200" 4.3
127.0.0.1:6379> SET "restaurants_rating_365" 4.0
127.0.0.1:6379> SET "restaurants_rating_104" 4.8
127.0.0.1:6379> SET "restaurants_rating_455" 4.7
127.0.0.1:6379> SET "restaurants_rating_333" 4.6

对之前的user:123:favorite_restaurants_ids集合按照restaurants_rating_*中的评分升序排列
127.0.0.1:6379> SORT "user:123:favorite_restaurants_ids" BY restaurants_rating_* DESC
1) "104"
2) "455"
3) "333"
4) "200"
5) "365"

​ 在某些情况下,这种外部键中的值更为有用。以上面的示例为例,假设将餐厅的名称存储在形如restaurants_name_200(200是餐厅ID)的键中,可以使用SORT命令的GET选项来获取餐厅的名称:

127.0.0.1:6379> SET "restaurants_name_200" "Ruby Tuesday"
127.0.0.1:6379> SET "restaurants_name_365" "TGI Friday"
127.0.0.1:6379> SET "restaurants_name_104" "Applebee's"
127.0.0.1:6379> SET "restaurants_name_455" "Red Lobster"
127.0.0.1:6379> SET "restaurants_name_333" "Boiling Crab"
127.0.0.1:6379> SORT "user:123:favorite_restaurants_ids" BY restaurants_rating_* DESC GET restaurants_name_*
1) "Applebee's"
2) "Red Lobster"
3) "Boiling Crab"
4) "Ruby Tuesday"
5) "TGI Friday"

GET 选项可被多次使用,GET # 代表获取元素本身

​ SORT命令还有一个STORE选项,把排序的结果当作列表保存到指定的键中:

127.0.0.1:6379> SORT "user:123:favorite_restaurants_ids" BY restaurant_rating_* DESC GET restaurant_name_* STORE user:123:favorite_restaurants_names:sort_by_rating
(integer) 5

理解Redis事务

​ 关系数据库中的事务是一组需要原子化执行的操作,但在Redis中,事务的概念是另外一回事。它们之间的关键区别在于,Redis没有回滚功能。且如果Redis执行过程中发生了错误,位于错误命令之后的其他命令将继续执行,而不会回滚。

使用发布订阅(PubSub)

​ 在发布-订阅模式中,想要发布事件(event)的发布者(publisher)会把消息(message)发布到一个PubSub频道(channel),这个频道会把事件投递(deliver)给对这个频道感兴趣的每一个订阅者(subscriber)。

​ 举一个推荐消息推送系统的例子。打开三个控制台模拟两个订阅者SUBer1、SUBer2和一个发布者PUBer。

​ 1、在SUBer1中订阅"restaurants:Chinese"频道

127.0.0.1:6379> SUBSCRIBE restaurants:Chinsese
Reading message... 
1) "subscribe"
2) "restaurants:Chinese"
3) (integer) 1

​ 2、在SUBer2中订阅"restaurants:Chinese"和"restaurants:Thai"频道

127.0.0.1:6379> SUBSCRIBE restaurants:Chinese restaurants:Thai
Reading messages...
1) "subscribe"
2) "restaurants:Chinese"
3) (ineteger) 1
1) "subscribe"
2) "restaurants:Thai"
3) (integer) 2

​ 3、在PUBer中向"restaurants:Chinese"频道发布消息

127.0.0.1:6379> PUBLISH restaurants:Chinese "Beijing roast duck discount tomorrow"
(integer) 2

​ 4、两个订阅者将收到以下的消息:

1) "message"
2) "restaurants:Chinese"
3) "Beijing roast duck discount tomorrow"

​ 5、在PUBer中向"restaurants:Thai"频道发布消息

127.0.0.1:6379> PUBLISH restaurants:Thai "3$ for Tom yum soap in this weekend!"
(integer) 1

​ 6、只有订阅了"restaurats:Thai"频道的SUBer2才会收到这条消息

1)"message"
2) "restaurants:Thai"
3) "3$ for Tom yum soap in this weekend!"

​ 可以使用PSUBSCRIBE命令订阅匹配指定模式的频道。要取消订阅频道,可以使用UNSUBSCRIBE命令。

​ 另一个重要的命令是PUBSUB,用于进行频道管理。例如,可以通过PUBSUB CHANNELS命令获取当前活跃的频道。

​ 当频道上没有活跃的订阅者时,频道将会被删除。PubSub相关的机制均不支持持久化,如果服务器出于某种原因退出,那么所有的这些对象都将丢失。

​ 此外,在消息投递和处理场景中,如果频道没有订阅者,那么被发到频道上的消息将被丢弃。也就是说,Redis并没有保证消息投递可靠性的机制。

​ 总之,Redis的PubSub功能不适合重要消息的投递场景。

使用Redis进行开发

Redis常见应用场景

会话存储

​ 因为与关系数据库相比Redis的访问延迟非常低,所以使用Redis来保存session数据堪称是完美的会话存储机制。此外,Redis中对键过期的支持可以天然地用于会话的超时管理。

分析

​ Redis还可以用于分析和统计的场景,例如,如果想要计算有多少用户查看了一个餐厅,那么简单地使用INCR命令增加统计餐厅被查看数地计数器即可。因为所有的Redis命令都是原子的,所以无需担心竞态。

排行榜

​ 使用Redis有序集合,可以轻松地实现一个排行榜。

队列

​ Redis的列表及其PUSH/POP命令(阻塞类型)可以用来实现简单的任务队列。

最新的N个记录

​ 道理同队列

缓存

​ 为了限制缓存的大小,可以对缓存中的记录设置过期时间或应用诸如最近最少使用(Least Recently Used,LRU)的收回策略。

复制

配置Redis的复制机制

​ Redis提供了一个复制机制,使得数据能够从一个Redis服务器(master,主实例)复制到一个或多个其他的Redis服务器中(slave,从实例)。复制提高了系统的容错能力,还可以用于对系统进行水平扩展,可以通过增加多个Redis只读从实例来减轻主实例的压力。同时,Redis的复制机制是Redis Cluster的基础,而Redis Cluster在此基础上提高了高可用性。

​ 按照开始使用Redis中的安装启动Redis中的步骤安装完Redis之后,为Redis从实例准备一个配置文件,可以将redis.conf复制一份并重命名为redis-slave.conf后进行以下的修改:

port 6380
pidfile /var/run/redis_6380.pid
slaveof 127.0.0.1 6379

​ 其中,slaveof 127.0.0.1 6379 表示当前实例是作为127.0.0.1这台主机上的端口为6379的Redis主实例的从实例。此外,slaveof同时也可以在redis-cli中直接运 行,可以动态地将当前的Redis实例变为另一个实例的从实例。

​ 然后创建/redis/slave目录:

mkdir -p /redis/slave

​ 然后启动Redis的主实例和从实例:

启动主实例
cd /usr/local/redis
./bin/redis-server etc/redis.conf

启动从实例
./bin/redis-server etc/redis-slave.conf

​ 使用redis-cli打开两个终端,并分别连接到主实例127.0.0.1:6379和从实例127.0.0.1:6380:

./bin/redis-cli -p 6379
127.0.0.1:6379> 

./bin/redis-cli -p 6380
127.0.0.1:6380>

​ 在两个终端上执行INFO REPLICATION可以看到结果里的rule分别是master和slave。

​ 在主实例上,创建一个新的键:

127.0.0.1:6379> SET "new_key" "value"

​ 在从实例上,尝试获取新键的值:

127.0.0.1:6380> GET "new_key"
"value"

​ 在从实例上,尝试创建一个新的键:

127.0.0.1:6380> SET "new_key_2" "value2"
(error) READONLY You can't write against a read only slave

​ 关闭从实例:

127.0.0.1:6380> shutdown save 

​ 在主实例上,创建另一个新的键:

127.0.0.1:6379> SET "another_new_key" "another_value"
OK

​ 重启从实例:

./bin/redis-server etc/redis-slave.conf

​ 检查从实例上是否存在another_new_key:

127.0.0.1:6380> GET "another_new_key"
"another_value"

如上所示,当主从实例网络连接通畅且建立了复制关系后,主实例会把其接收到的写入命令转发给从实例以实现实例之间的数据同步。

在Redis的复制机制中,共有两种重新同步机制:

  1. 部分重新同步
  2. 完全重新同步

​ 当一个Redis的从实例启动并连接到主实例时,从实例总是会尝试通过发送(master_replid;master_repl_offset)请求进行部分重新同步。(master_replid;master_repl_offset)表示与主实例同步的最后一个快照。如果主实例接受部分重新同步的请求,那么它会从从实例停止时的最后一个偏移处开始增量地进行命令同步。否则,则需要进行完全重新同步。当从实例第一次连接到它的主实例时,总是需要进行完全重新同步。在进行完全重新同步时,为了将所有的数据复制到从实例中,主实例需要将数据转储到一个RDB文件中,然后将这个文件发送给从实例。从实例接收到RDB文件后,会先将内存中的所有数据清除,再将RDB文件中的数据导入。

​ 当一个从实例被提升为主实例时,其他的从实例必须与新主实例重新同步。并且在Redis 4.0后,新主实例会记录旧主实例的master_replid和offset,因此可以接受其他从实例的部分重新同步请求。

复制机制的调优

​ 在Redis主实例失去与从实例的网络连接期间,主实例的一段内存(一个环形缓冲区)会跟踪最近所有的写入命令。这个缓冲区实际上是一个固定长度的列表。Redis使用这个缓冲区决定究竟是部分重新同步还是完全重新同步。更具体地说,在发出SLAVEOF命令后,从实例使用最后一个offset和最后一个主实例的ID(master_replid)向主实例发送一个部分重新同步请求。当主实例与从实例的连接建立后,主实例首先会检查请求中的offset能否从backlog缓冲区中获取,如果offset位于backlog的范围内,那么就可以从中获得连接断开期间的所有写入命令,这也意味着能进行部分重新同步。否则,如果主实例在连接断开期间收到的写入命令的数量超过了backlog缓冲区的容量,部分重新同步请求会被拒绝。此时,完全重新同步将被启动。

​ backlog的容量可以通过repl-backlog-size参数设置。默认情况下,backlog的大小是1MB,这个容量不足以应对高写入流量的情况。多数情况下,需要把这个值调高以满足需求。

​ 另一个有关backlog的参数是repl-backlog-ttl。该参数的含义是:如果所有的从实例与主实例的连接全部断开,主实例等待多久释放backlog占用的内存。默认值为3600s。通常不会有问题,因为与Redis实例占用的总内存相比backlog缓存区是很小的。

​ 从网络传输的角度看,可以通过将参数repl-disable-tcp-nodelay设为yes来减少带宽的使用。如果设置为yes,Redis会尝试将几个小包合并成一个包。在主实例和从实例位置较远时有作用,但是会造成约40ms的复制延迟。

持久化

​ Redis是在内存中存储数据的,所以当服务器重新启动时,所有的数据都将丢失。为了保证数据的安全,Redis还提供了将数据持久到磁盘中的机制。Redis共有两种数据持久化类型:RDB和AOF。RDB可以看作是Redis在某一个时间点上的快照,非常适合于备份和灾难恢复。AOF则是一个写入操作的日志,将在服务器启动时被重放。

使用RDB

​ RDB的工作方式是不断地对存储数据的内存进行快照。

​ 使用RDB的步骤如下:

​ 1、调用CONFIG SET命令,启用RDB持久化

127.0.0.1:6379> CONFIG SET save "900 1"
OK
127.0.0.1:6379> CONFIG SET save "900 1 300 10 60 10000"
OK

​ 2、如果想永久性地启用RDB持久化,可以在Redis配置文件中设置save参数

save 900 1
save 300 10
save 60 10000

​ 3、要永久禁用RDB持久化,去掉上面地三行参数即可,要在一个正在运行的 Redis实例上禁用RDB持久化,可以使用redis-cli把save参数设为空字符串:

./bin/redis-cli config set save ""
OK

​ 4、通过redis-cli获取save选项的值可以判断是否启用了RDB功能。如果结果是空字符串。则表示RDB功能是被禁用的,否则返回RDB快照的触发策略:

./bin/redis-cli CONFIG GET save
1) "save"
2) "900 1 300 10 60 10000"

​ 5、也可以在Redis的数据目录中检查是否生成了RDB文件:

ls -l dump.rdb

​ 6、如果要手动指定RDB快照,在redis-cli中调用SAVE命令即可。redis-cli将会阻塞一段时间,然会返回执行时间,或者使用BGSAVE命令执行非阻塞的RDB存储

127.0.0.1:6379> SAVE
OK 
(23.25s)
127.0.0.1:6379>

save参数的含义:

​ 在前面的示例中,900 1表示如果在900秒内有超过1个键发生了改变,则会进行一次RDB快照。在save选项中,可以使用多个策略控制RDB快照执行的频率。

​ save和bgsave的区别在于,前者使用Redis的主线程进行同步存储,而后者则在后台进行存储。

与RDB持久化相关的配置:

  1. stop-write-on-bgsave-error yes:设为yes的话,作为保护机制,当bgsave失败时Redis服务器将停止接受写入操作
  2. rdbcompression yes:压缩可以显著减小转储文件的大小,但会消耗更多的CPU
  3. rdbchecksum yes:设为yes将在保存和加载快照文件时额外消耗约10%的性能。设为no可以获得最大性能,但也会降低对数据损坏的抵抗力。

没有特殊情况,默认全部使用yes就可以了。

使用AOF

​ 使用RDB进行数据持久化并不能提供非常强的一致性。但Redis进程崩溃或者出现硬件故障时,两次RDB转储之间的数据将永久丢失。AOF是一种只记录Redis写入命令的追加式日志文件。

​ 1、调用CONFIG SET命令,在一个正在运行的Redis实例上启用AOF实例化

127.0.0.1:6379> CONFIG SET appendonly yes
OK

​ 2、永久启用AOF持久化,可以在Redis配置文件中添加appendonly yes,然后重启服务器。永久禁用AOF持久化设为no重启即可

appendonly yes

​ 3、在一个正在运行的Redis实例上禁用AOF实例化,可以通过redis-cli把appendonly参数设为no

./bin/redis-cli config set appendonly no
OK

​ 4、使用INFO PERSISTENCE命令检查AOF相关的内容可以判断是否启用了AOF功能

127.0.0.1:6379> INFO PERSISTENCE
#Persistence
loading:0
...
aof_enabled:1
aof_rewrite_in_progress:0

​ AOF文件的默认文件名是appendonly.aof,并可以通过配置文件的appendfilename参数进行修改。

​ 每当Redis服务器接收到写入命令时,Redis会将该命令追加到AOF文件中。但是操作系统实际上维护了一个缓冲区,redis的命令首先会被写入到这个缓冲区中。缓冲区的数据必须被刷新到磁盘中才能被永久保存。这个过程是通过调用fsync()完成的,这是一个阻塞调用,只有磁盘设备报告缓冲区中的数据写入完成后才会返回。

​ 可以通过Redis的配置参数appendfsync来调整频率,该参数共有三个选项:

  1. always:对每个写入命令都调用fsync。这个选项可以确保服务崩溃时只丢失一个命令。但是设置为always是不明智的,因为Redis服务器性能会显著降低
  2. everysec:每秒调用一次fsync。服务崩溃时只有一秒钟的数据会丢失。建议使用此选项进行平衡。
  3. no:永远不调用fsync。采用这个选项时,在大多数操作系统中,每30s将缓冲区中的数据写入磁盘。

当Redis服务器关闭时,fsync会被显式的调用,确保所有数据都会写入磁盘。

配置高可用和集群

​ 对于生产环境,使用Redis的主从机制和持久化可以解决数据冗余备份的问题。但是,如果没有人工干预,当主实例宕机时,整个Redis服务将无法恢复。2.6版本以后Redis原生支持的Sentinel是使用最广泛的高可用架构。

​ 随着Redis数据集大小的增长,在进行持久化或主从复制时,也会越来越多地出现诸如延迟等问题。对于这种问题,水平扩展,或通过向Redis服务中增加更多节点来实现伸缩就成为了一种迫切的需要。从3.0版本开始支持的Redis Cluster可以将数据集通过分区的方式分布到多个Redis主从实例中。

配置Sentinel

​ Sentinel(哨兵)充当了Redis主实例和从实例的守护者。因为单个哨兵也可能失效,所以一个哨兵显然不足以保证高可用。对主实例进行故障迁移的决策是基于仲裁系统的,所以至少需要三个哨兵进程才能构成一个健壮的分布式系统来持续地监控Redis主实例的状态。如果有多个哨兵进程检测到主实例下线,其中的一个哨兵进程会被选举出来负责推选一个从实例替代原有的主实例。如果配置得当,上述的整个过程都将是自动化的,无需人工干预。

​ 演示配置一个由三个哨兵监控的一主两从环境。

​ 各机器的角色、IP地址和端口号如下:

角色IP地址端口号
Master192.168.31.2486379
Slave-1192.168.31.1056379
Salve-2192.168.31.2346379
Sentinel-1192.168.31.24826379
Sentinel-2192.168.31.10526379
Sentinel-3192.168.31.23426379

需要在配置文件中通过设置bind参数允许其他机器访问自己,且Sentinel进程必须对配置文件有写入权限。

配置Sentinel的步骤:

1、在每台主机上准备一个配置文件sentinel.conf,可以直接从Redis解压文件夹里找到。复制到/usr/local/redis/etc目录,并进行以下的配置:

指定哨兵节点端口为26379
port 26379

下面的ip分别为要监控的Master服务器的ip,在三台主机上都设置一次
sentinel monitor mymaster 192.168.31.248 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 180000

2、启动一主二从三哨兵实例

​ 首先是主实例,进入192.168.31.248服务器命令行

cd /usr/local/redis
./bin/redis-server etc/redis.conf

​ 然后启动同一台服务器上的哨兵节点

./bin/redis-server etc/sentinel.conf --sentinel

​ 然后是第二台服务器,进入192.168.31.105服务器命令行,首先启动从实例

cd /usr/local/redis
./bin/redis-server etc/redis-slave.conf

​ 然后启动这台服务器上的哨兵节点

./bin/redis-server etc/sentinel.conf --sentinel

​ 最后启动第三台服务器192.168.31.234,首先启动从实例

cd /usr/local/redis
./bin/redis-server etc/redis-slave.conf

​ 然后启动这台服务器上的哨兵节点

./bin/redis-server etc/sentinel.conf --sentinel

3、使用redis-cli连接到Sentinel-1(即192.168.31.248服务器)服务器并执行INFO SENTINEL命令,需要指定端口26379

cd /usr/local/redis
./bin/redis-cli -p 26379
127.0.0.1:26379> INFO SENTINEL
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.31.248:6379,slaves=2,sentinels=3

​ 最后一句可以看出名称为mymaster、地址为192.168.31.248:6379的Master节点已经有了2个从实例,三个哨兵节点。

​ 在Sentinel-1上查看sentinel.conf的内容,可以在最后看到:

...
# Generated by CONFIG REWRITE
protected-mode no
sentinel known-replica mymaster 192.168.31.234 6380
sentinel known-replica mymaster 192.168.31.105 6380
sentinel known-sentinel mymaster 192.168.31.234 26379 a53d6fccca4608e75a5afb1ed514757c77fb4d9d
sentinel known-sentinel mymaster 192.168.31.105 26379 dc1691989bdc979cff3d505e779fccfd275791e9
sentinel current-epoch 0

​ 可以看出,从实例分别为192.168.31.234服务器上端口为6380的实例以及192.168.31.105服务器上端口为6380的实例。哨兵节点即为之前配置的26379的两个实例。

​ 如果要将一个新的主实例添加到Sentinel中进行监控,可以按如下形式在配置文件中增加一行:

sentinel monitor <master-name> <ip> <port> <quorum>

​ 本例中,sentinel monitor mymaster 192.168.31.248 6379 2表示Sentinel将监控192.168.31.248:6379的主实例,该实例名为mymaster。指在采取故障迁移操作前,发现并同意主实例不可达的最少哨兵数。down-after-milliseconds选项指在标记实例下线前不可达的最长毫秒数。Sentinel每秒钟都向实例发送PING命令来检查其是否可达。

​ 在本例中,如果某个实例超过30s仍未响应PING命令,那么它将被视作下线。当主实例发生故障迁移时,其中的一个从实例将被选为新的主实例,而其他的从实例则将需要从新的主实例那里进行主从复制。parallel-syncs参数表示有几个从实例可以同时从新的主实例进行数据同步。

​ 虽然只在sentinel配置文件中指定了主实例的信息。但是sentinel依然可以检测到从服务器和其他哨兵。每个sentinel进程每10秒钟会向其监控的所有Redis实例(包括主实例和被检测到的从实例)发送INFO REPLICATION命令,来获取整个主从复制拓扑结构的最新信息。

​ 为了探测其他哨兵与其他哨兵进行通信,每个sentinel进程每两秒钟会向一个名为_sentinel_:hello的频道发布一条消息,报告其自身及所监控的主实例的状态。因此,只要订阅该频道就能发现其他哨兵的消息了。

​ 连接到任何一个Redis实例并订阅该频道即可查看该频道中的消息。

配置Redis Cluster

​ 当Redis中的数据量急剧增长时,必须对其进行分区。

​ 配置Redis集群所需的三台虚拟机IP:

ip
192.168.31.248
192.168.31.105
192.168.31.234

正常的下载安装Redis,如同第一章所描述的。

创建节点

1、首先在 192.168.252.101机器上 /opt/redis-4.0.1目录下创建 redis-cluster 目录

mkdir /usr/local/redis/redis-cluster

2、在 redis-cluster 目录下,创建名为7000、7001、7002的目录

cd /usr/local/redis/redis-cluster
mkdir 7000 7001 7002

3、分别拷贝一份redis.conf文件到三个目录下

cd ../etc/redis.conf 7000/
cd ../etc/redis.conf 7001/
cd ../etc/redis.conf 7002/

​ 并修改为以下的配置:

​ 7000/redis.conf

port 7000
bind 192.168.31.248
daemonize yes
pidfile /var/run/redis_7000.pid
cluster-enabled yes
cluster-config-file nodes_7000.conf
appendonly yes

​ 7001/redis.conf

port 7001
bind 192.168.31.248
daemonize yes
pidfile /var/run/redis_7001.pid
cluster-enabled yes
cluster-config-file nodes_7001.conf
appendonly yes

​ 7002/redis.conf

port 7002
bind 192.168.31.248
daemonize yes
pidfile /var/run/redis_7002.pid
cluster-enabled yes
cluster-config-file nodes_7002.conf
appendonly yes

​ 配置说明:

#端口7000,7001,7002
port 7000

#默认ip为127.0.0.1,需要改为其他节点机器可访问的ip,否则创建集群时无法访问对应的端口,无法创建集群
bind 192.168.252.101

#redis后台运行
daemonize yes

#pidfile文件对应7000,7001,7002
pidfile /var/run/redis_7000.pid

#开启集群,把注释#去掉
cluster-enabled yes

#集群的配置,配置文件首次启动自动生成 7000,7001,7002          
cluster-config-file nodes_7000.conf 
        
#aof日志开启,有需要就开启,它会每次写操作都记录一条日志
appendonly yes

​ 接着在另外两台机器上(192.168.31.105和192.168.31.234)重复以上三步,只是把目录改为7003、7004、7005、7006、7007、7008。对应的配置文件也按照这个规则修改即可。

启动集群

#第一台机器上执行 3个节点
$ for((i=0;i<=2;i++)); do /usr/local/redis/bin/redis-server /usr/local/redis/redis-cluster/700$i/redis.conf; done

#第二台机器上执行 3个节点
$ for((i=3;i<=5;i++)); do /usr/local/redis/bin/redis-server /usr/local/redis/redis-cluster/700$i/redis.conf; done
                     
#第三台机器上执行 3个节点 
$ for((i=6;i<=8;i++)); do /usr/local/redis/bin/redis-server /usr/local/redis/redis-cluster/700$i/redis.conf; done

检查服务

$ ps -ef | grep redis           //redis是否启动成功
$ netstat -tnlp | grep redis    //监听redis端口

创建集群

​ 因为最新的版本已经支持通过redis-cli创建,所以使用以下命令创建即可:

cd /usr/local/redis
./bin/redis-cli --cluster create 192.168.31.248:7000 192.168.31.248:7001 192.168.31.248:7002 192.168.31.105:7003 192.168.31.105:7004 192.168.31.105:7005 192.168.31.234:7006 192.168.31.234:7007 192.168.31.234:7008 --replicas 1

​ 执行过程中会询问是否允许设置,输入yes即可。注意,必须是yes,不能输入y,y的话亲测会报错。最后会出现如下提示,说明创建成功:

[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

关闭集群

​ 批量关闭

pkill redis

​ 或者使用循环节点逐个关闭:

for((i=0;i<=2;i++)); do /opt/redis-4.0.1/src/redis-cli -c -h 192.168.252.101 -p 700$i shutdown; done

for((i=3;i<=5;i++)); do /opt/redis-4.0.1/src/redis-cli -c -h 192.168.252.102 -p 700$i shutdown; done

for((i=6;i<=8;i++)); do /opt/redis-4.0.1/src/redis-cli -c -h 192.168.252.103 -p 700$i shutdown; done

集群验证

​ 参数 -C 可连接到集群,因为 redis.conf 将 bind 改为了ip地址,所以 -h 参数不可以省略,-p 参数为端口号。

​ 在创建集群的192.168.31.248服务器上通过端口为7000的节点设置一个key:

/usr/local/redis/bin/redis-cli -h 192.168.31.248 -c -p 7000
192.168.31.248:7000> set key1 value1
-> Redirected to slot [9189] located at 192.168.31.234:7006
OK
192.168.31.234:7006> get key1
"value1"
192.168.31.234:7006>

发现redis set key1 之后重定向到192.168.31.234机器 redis 7006 这个节点。

然后切换到192.168.31.105的端口为7003的服务器节点查找刚才设置的key1的值:

/usr/local/redis/bin/redis-cli -h 192.168.31.105 -c -p 7003
192.168.31.105:7003> get key1
-> Redirected to slot [9189] located at 192.168.31.234:7006
"value1"
192.168.31.234:7006>

发现查询时重定向到192.168.31.234机器redis 7006这个节点。

说明集群已经是可用的了。

列出集群节点

/usr/local/redis/bin/redis-cli -h 192.168.31.248 -c -p 7000 
192.168.31.248:7000> cluster nodes
010c30553166ecf8fd45a7f6f5cdccc38aa2924b 192.168.31.248:7000@17000 myself,master - 0 1575306003000 1 connected 0-4095
346299b826cc92cc06ddad1586da947721564c89 192.168.31.105:7004@17004 slave 010c30553166ecf8fd45a7f6f5cdccc38aa2924b 0 1575306007763 5 connected
88da5bf3740fd3e057ef8baa8b55ed8975728b14 192.168.31.234:7008@17008 slave 952457106e1276c95359046176149e1ba07ffe9a 0 1575306006873 9 connected
9dabbae4083f2ecb865689a8f2a23ff985edbea6 192.168.31.105:7005@17005 slave 0538278479f346062570dfed838e366fa7cdb0da 0 1575306006757 7 connected
7ae490f9cd650c9b1b337d2a0d8433ea166b63a4 192.168.31.234:7007@17007 slave 010c30553166ecf8fd45a7f6f5cdccc38aa2924b 0 1575306008000 8 connected
952457106e1276c95359046176149e1ba07ffe9a 192.168.31.248:7001@17001 master - 0 1575306005000 2 connected 12288-16383
aef9c3724cfdf7ed1d684edf47ee7d23696ecbd5 192.168.31.105:7003@17003 master - 0 1575306005000 4 connected 4096-8191
0538278479f346062570dfed838e366fa7cdb0da 192.168.31.234:7006@17006 master - 0 1575306007000 7 connected 8192-12287
ad75c85d795524a15889790e6d9fc201515faef6 192.168.31.248:7002@17002 slave aef9c3724cfdf7ed1d684edf47ee7d23696ecbd5 0 1575306007000 4 connected
192.168.31.248:7000>

打印集群信息

/usr/local/redis/bin/redis-cli -h 192.168.31.248 -c -p 7000 
192.168.31.248:7000> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:9
cluster_size:4
cluster_current_epoch:9
cluster_my_epoch:1
cluster_stats_messages_ping_sent:2334
cluster_stats_messages_pong_sent:2423
cluster_stats_messages_sent:4757
cluster_stats_messages_ping_received:2415
cluster_stats_messages_pong_received:2334
cluster_stats_messages_meet_received:8
cluster_stats_messages_received:4757
192.168.31.248:7000>

节点

cluster meet <ip> <port> :将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。
cluster forget <node_id> :从集群中移除 node_id 指定的节点。
cluster replicate <node_id> :将当前节点设置为 node_id 指定的节点的从节点。
cluster saveconfig :将节点的配置文件保存到硬盘里面。

槽(slot)

cluster addslots <slot> [slot ...] :将一个或多个槽( slot)指派( assign)给当前节点。
cluster delslots <slot> [slot ...] :移除一个或多个槽对当前节点的指派。
cluster flushslots :移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
cluster setslot <slot> node <node_id> :将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽>,然后再进行指派。
cluster setslot <slot> migrating <node_id> :将本节点的槽 slot 迁移到 node_id 指定的节点中。
cluster setslot <slot> importing <node_id> :从 node_id 指定的节点中导入槽 slot 到本节点。
cluster setslot <slot> stable :取消对槽 slot 的导入( import)或者迁移( migrate)。

cluster keyslot <key> :计算键 key 应该被放置在哪个槽上。
cluster countkeysinslot <slot> :返回槽 slot 目前包含的键值对数量。
cluster getkeysinslot <slot> <count> :返回 count 个 slot 槽中的键 。

生产环境部署

​ 从操作系统级的优化开始,配置一个能够用于实际生产环境的Redis服务器。以及服务器端有关客户端连接的参数,以及如何在生产环境中安全加固Redis、如何配置内存策略。以及Redis的日志选项和基准测试工具。

在Linux上部署Redis

​ 1、设置与内存相关的内核参数

sudo sysctl -w vm.overcommit_memmory=1
sudo sysctl -w vm.swappiness=0

​ 使用如下的命令来持久化地保存这些参数:

echo vm.overcommit_memory=1" >> /etc/sysctl.confecho "vm.swappiness=0" >> /etc/sysctl.conf

​ 使用如下命令检查这些参数是否已被设置:

sudo sysctl vm.overcommit_memory vm.swappiness
vm.overcommit_memory = 1
vm.swappiness = 0

​ 2、禁用透明大页功能(transparent huge page)功能

sudo su - 
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

​ 使用如下的命令来持久化地保存这些设置:

cat >> /etc/rc.local << EOF
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
EOF

​ 对于RedHat linux,可以使用echo never > /sys/kernel/mm/redhat_transparent_hugepage/enabled修改/etc/rc.local

​ 使用如下的命令检查这些参数是否已被设置:

cat /sys/kernel/mm/transparent_hugepage/enabled
always madvise [never]
cat /sys/kernel/mm/transparent_hugepage/defrag
always madvise [never]

3、设置与网络相关的内核参数

sudo sysctl -w net.core.somaxconn=65535
sudo sysctl -w net.ipv4.tcp_max_syn_backlog=65535

​ 使用如下命令来持久化保存这些参数

echo "net.core.somaxconn=65535" >> /etc/sysctl.conf
echo "net.ipv4.tcp_max_syn_backlog=65535" /etc/sysctl.conf

​ 使用如下的命令检查这些参数:

sudo sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

4、要将进程能够打开的文件数设置为更高的值,先切换到启动Redis进程的用户,然后执行ulimit命令:

ulimit -n 288000

​ 必须将nofile设为一个小于/proc/sys/fs/file-max的值。因此在设置之前,需要使用cat命令检查/proc/sys/fs/file-max的值。

​ 要持久化地保存这个参数,可以将下面两行添加到/etc/security/limits.conf中

redis soft nofile 288000
redis hard nofile 288000

​ 使用如下的命令来检查这些参数是否已被设置:

ulimit -Hn -Sn
open files (-n) 288000
open files (-n) 288000

Redis安全相关设置

​ 1、修改配置文件中的bind和port来更新绑定的IP地址和端口

bind 127.0.0.1 192.168.31.248
port 36379

​ 2、不要忘记更新主从复制和sentinel配置中的相应参数

# Replica's configuration(redis-slave.conf)
slaveof 192.168.31.248 36379
# Sentinel's configuration
sentinel monitor mymaster 192.168.31.248 36379

​ 3、通过密码验证来保护Redis,可以给整个Redis服务器设置一个密码

​ 在配置文件的requirepass部分中添加明文密码即可

requirepass foobared

​ 要访问一个启用了身份验证的Redis服务器,需要使用AUTH命令:

./bin/redis-cli
127.0.0.1:6379> SET test 123
(error) NOAUTH Authentication required
127.0.0.1:6379> AUTH foobared
OK
127.0.0.1:6379> set test 123
OK

​ 如果Redis服务启用了主从复制,则需要将主实例的密码添加到所有从实例的配置文件中

masterauth foobared

配置客户端连接选项

​ 1、如果要配置客户端的网络参数,将以下的内容添加到Redis配置文件redis.conf中即可

# Redis客户端将在空闲timeout秒后关闭连接
timeout 0
# 设置挂起套接字请求队列的大小
tcp-backlog 511

​ 2、如果要配置Redis实例的最大客户端数量和tcp-keepalive时间,将以下的内容添加到Redis和Sentinel配置文件中即可:

# maxclients和tcp-keepalive对Redis实例和Sentinel都有效
# 客户端最大连接数
maxclients 10000
# 服务器按照指定的时间间隔发送TCP ACK来通知网络设备在此连接期间仍然活跃
tcp-keepalive 300

​ 3、配置客户端的缓冲区参数,在Redis配置文件redis.conf中添加:

client-output-buffer-limit normal 0 0 0 
client-output-buffer-limit slave 512mb 256mb 60
client-output-buffer-limit pubsub 32mb 8mb 60

​ Redis不会直接将命令的输出发送给客户端,而是首先将结果填充到每个连接的输出缓冲区中,然后再将其内容一次性地发送出去。这三个参数就是设置普通连接、pub/sub连接、从实例连接的输出缓冲区大小。

配置内存策略

​ 1、使用INFO MEMORY命令获取当前内存的使用情况

127.0.0.1:6379> INFO MEMORY

​ 2、设置MAXMEMORY,即Redis可用内存的最大值

127.0.0.1:6379> CONFIG SET MAXMEMORY 836900

​ 3、通过maxmemory-policy参数调整到达内存空间限制时的淘汰策略,默认是noeviction,即不淘汰键。maxmemory-policy参数可用列表:

选项动作
noeviction不淘汰,只在写入操作时返回错误
allkeys-lru使用最近最少使用(LRU)算法淘汰任意键
volitile-lru用近似的LRU算法淘汰设置了超时时间的键。如果没有这样的键则使用noeviction策略
allkeys-lfu使用最不常用(LFU)算法淘汰任意键
volitile-lfu使用近似的LFU算法淘汰设置了超时时间的键。如果没有这样的键则使用noeviction策略
allkeys-random随机淘汰键
volitile-random随机淘汰设置了超时时间的键。如果没有这样的键则使用noeviction策略
volitile-ttl淘汰马上就要过期的键(TTL最小)。如果没有这样的键则使用noeviction策略

如果想把Redis作为缓存服务器使用,那么要务必将maxmemory-policy设为除noeviction外的值,不过也应该避免让Redis频繁地淘汰键,因为键的淘汰过程会严重影响服务器的性能。