redis的学习

 

 

一、NoSql

 

1 什么是NoSql?

 

NoSQL = Not Only SQL(不仅仅是 SQL) ,也解释为 non-relational(非关系型数据库)。在 NoSQL 数据库中数据之间是无联系的,无关系的。数据的结构是松散的,可变的。

 

它不能替代关系型数据库,只能作为关系型数据库的一个良好补充。

 

为了解决高并发、高可扩展、高可用、大数据存储问题而产生的数据库解决方案,就是NoSql数据库。

 

MySQL 和 NoSQL 都有各自的特点和使用的应用场景,两者结合使用。让关系数据库

关注在关系上,NoSQL 关注在存储上。

 

2 NoSql分类

 

1. 键值(Key-Value)存储数据库

相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB

典型应用: 内容缓存,主要用于处理大量数据的高访问负载。

数据模型: 一系列键值对

优势: 快速查询

劣势: 存储的数据缺少结构化

 

2. 列存储数据库

相关产品:Cassandra, HBase, Riak

典型应用:分布式的文件系统

数据模型:以列簇式存储,将同一列数据存在一起

优势:查找速度快,可扩展性强,更容易进行分布式扩展

劣势:功能相对局限

 

3. 文档型数据库

相关产品:CouchDB、MongoDB

典型应用:Web应用(与Key-Value类似,Value是结构化的)

数据模型: 一系列键值对

优势:数据结构要求不严格

劣势:查询性能不高,而且缺乏统一的查询语法

 

4. 图形(Graph)数据库

相关数据库:Neo4J、InfoGrid、Infinite Graph

典型应用:社交网络

数据模型:图结构

优势:利用图结构相关算法。

劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。

 

3 NoSQL的劣势

 

(1) 无关系,数据之间是无联系的。

(2) 不支持标准的 SQL, 没有公认的 NoSQL  标准

(3) 没有关系型数据库的约束,大多数也没有索引的概念(MongoDB有)

(4) 没有事务,不能依靠事务实现 ACID.

(5) 没有丰富的数据类型(数值,日期,字符,二进制,大文本等)

 

二、redis 简介

 

英文: https://redis.io/

中文: http://www.redis.cn/

 

1 什么是redis?

 

Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.

 

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

 

2 redis的背景

 

国内如新浪微博、街旁网、知乎网,国外如GitHub、Stack Overflow、Flickr等都是Redis的用户。

Redis 的作者是 Salvatore Sanfilippo,来自意大利的西西里岛,现在居住在卡塔尼亚。目前供职于 Pivotal 公司(Pivotal 是 Spring 框架的开发团队),Salvatore Sanfilippo 被称为 Redis之父。

                                      redis的学习_第1张图片

 

2.1 redis的应用场景

 

① 缓存(数据查询、短连接、新闻内容、商品内容等等)。(最多使用

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

③ 聊天室的在线好友列表。

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

⑤ 应用排行榜(最近、最热、点击率最高、活跃度最高等等条件的top list)。

 

⑥ 网站访问统计(用户最近访问):

用户最近访问记录也是redis list的很好应用场景,

 

lpush lpop自动过期老的登陆记录,对于开发来说还是非常友好的

 

⑦ 数据过期处理(可以精确到毫秒): 手机验证码有效时间、用户错误登录次数。

⑧ 分布式锁:

https://redisson.org/

 

思考:红锁算法(zookeeper)。

3 redis安装及使用(linux)

 

CentOS: https://www.centos.org/

 

Redis下载: https://redis.io/download

 

3.1 redis的安装

 

需要先编译,redis是c语言开发的,编译依赖gcc环境,如果没有gcc环境,需要安装gcc。

 

安装:yum install gcc-c++。

解压:tar –zxvf redis-5.0.0.tar.gz –C /usr/local/

启动:./redis-server &

 

3.2 远程连接redis(linux)

 

远程连接redis需要修改redis主目录下的redis.conf配置文件:

 

①bind ip 绑定ip,此行注释

②protected-mode yes 保护模式改为 no

③以配置文件启动:./redis-server ../redis.conf &

 

有时可能需要关闭防火墙:sevice iptables stop/ systemctl stop firewalld (centos)

如果是修改防火墙规则,可以修改:/etc/sysconfig/iptables文件

查看防火墙状态:systemctl status firewalld

 

安装iptables 服务:yum -y install iptables-services

 

3.3 启动客户端:

 

# ./redis-cli -h 127.0.0.1 -p 6379

127.0.0.1:6379> exit 【退出】

 

-h:redis服务器的ip地址

-p:redis实例的端口号

 

不指定主机和端口也可以:

 

# ./redis-cli

 

默认端口是6379

 

三 、redis 数据类型

 

声明字段:

大致分三种,数值、日期/时间和字符串(字符)类型

 

MySql: int,varchar,char,datetime.....

 

参见: https://www.runoob.com/mysql/mysql-data-types.html

 

Oracle: CHAR, VARCHAR2, NUMBER, DATE, BLOB

 

参见: https://blog.csdn.net/weixin_41278231/article/details/78716417

 

PS:

在redis中的命令语句中,命令是忽略大小写的,而key是不忽略大小写的。

 

3.1 String 字符串

 

① 赋值与取值SET key value GET key

② 取值并赋值GETSET key value

③ 设置/获取多个键值:

MSET key value [key value …]

MGET key [key …]

 

127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3

OK

127.0.0.1:6379> get k1

"v1"

127.0.0.1:6379> mget k1 k3

1) "v1"

2) "v3"

④ 删除 DEL key

127.0.0.1:6379> del test

(integer) 1

 

⑤ 数值增减 INCR key

 

i 当存储的字符串是整数时,Redis提供了一个实用的命令INCR,其作用是让当前键值递增,并返回递增后的值。

 

127.0.0.1:6379> incr num

(integer) 1

127.0.0.1:6379> incr num

(integer) 2

127.0.0.1:6379> incr num

(integer) 3

      

ii  增加指定的整数:

 

语法:INCRBY key increment

127.0.0.1:6379> incrby num 2

(integer) 5

127.0.0.1:6379> incrby num 2

(integer) 7

127.0.0.1:6379> incrby num 2

(integer) 9

 

iii 递减数值:

 

语法:DECR key

127.0.0.1:6379> decr num

(integer) 9

127.0.0.1:6379> decr num

(integer) 8

 

Iv 减少指定的整数

 

语法:DECRBY key decrement

127.0.0.1:6379> decr num

(integer) 6

127.0.0.1:6379> decr num

(integer) 5

127.0.0.1:6379> decrby num 3

(integer) 2

127.0.0.1:6379> decrby num 3

(integer) -1

 

⑥ 其他命令

 

       i 向尾部追加值:

 

APPEND的作用是向键值的末尾追加value。如果键不存在则将该键的值设置为value,即相当于 SET key value。返回值是追加后字符串的总长度。

 

语法:APPEND key value

127.0.0.1:6379> set str hello

OK

127.0.0.1:6379> append str " world!"

(integer) 12

127.0.0.1:6379> get str

"hello world!"

 

       ii 获取字符串长度           

 

STRLEN命令返回键值的长度,如果键不存在则返回0。

语法:STRLEN key

127.0.0.1:6379> strlen str

(integer) 0

127.0.0.1:6379> set str hello

OK

127.0.0.1:6379> strlen str

(integer) 5

 

⑦ 自增主键:

 

商品编号、订单号采用string的递增数字特性生成。

 

定义商品编号key:items:id

192.168.209.128:6379> INCR items:id

(integer) 2

192.168.209.128:6379> INCR items:id

(integer) 3

 

 

3.2 Hash 散列类型

 

提供了字段和字段值的映射。字段值只能是字符串类型。

 

使用String 的问题:

 

假设有User对象以JSON序列化的形式存储到Redis中,User对象有id,username、password、age、name等属性,存储的过程如下:

保存、更新: User对象 à json(string) à redis

 

 

① 赋值:

HSET命令不区分插入和更新操作,当执行插入操作时HSET命令返回1,当执行更新操作时返回0。

 

       i 一次只能设置一个字段值

语法:HSET key field value

127.0.0.1:6379> hset user username zhangsan

(integer) 1

      

ii 一次可以设置多个字段值

语法:HMSET key field value [field value ...]           

127.0.0.1:6379> hmset user age 20 username lisi

OK

 

       iii 当字段不存在时赋值,类似HSET,区别在于如果字段存在,该命令不执行任何操作

语法:HSETNX key field value

 

127.0.0.1:6379> hsetnx user age 30   如果user中没有age字段则设置age值为30,否则不做任何操作

(integer) 0

 

② 取值:

 

       i 一次只能获取一个字段值

语法:HGET key field

语法:HGET key field                

127.0.0.1:6379> hget user username

"zhangsan“

 

       ii 一次可以获取多个字段值

语法:HMGET key field [field ...]                     

127.0.0.1:6379> hmget user age username

1) "20"

2) "lisi"

 

       iii  获取所有字段值

语法:HGETALL key

127.0.0.1:6379> hgetall user

1) "age"

2) "20"

3) "username"

4) "lisi"

 

       iv 增加数字

语法:HINCRBY key field increment

127.0.0.1:6379> hincrby user age 2   将用户的年龄加2

(integer) 22

127.0.0.1:6379> hget user age          获取用户的年龄

"22“

 

③ 删除字段

可以删除一个或多个字段,返回值是被删除的字段个数

 

语法:HDEL key field [field ...]

127.0.0.1:6379> hdel user age

(integer) 1

127.0.0.1:6379> hdel user age name

(integer) 0

127.0.0.1:6379> hdel user age username

(integer) 1

 

④ 其他命令

     

       i 判断字段是否存在

 

语法:HEXISTS key field

127.0.0.1:6379> hexists user age       查看user中是否有age字段

(integer) 1

127.0.0.1:6379> hexists user name    查看user中是否有name字段

(integer) 0

 

       ii 只获取字段名或字段值

语法:

HKEYS key

HVALS key

127.0.0.1:6379> hmset user age 20 name lisi

OK

127.0.0.1:6379> hkeys user

1) "age"

2) "name"

127.0.0.1:6379> hvals user

1) "20"

2) "lisi"

 

       iii 获取字段数量

 

语法:HLEN key

127.0.0.1:6379> hlen user

(integer) 2

 

应用示例:

 

存储商品信息

192.168.209.128:6793> HMSET items:1001 id 3 name apple price 999.9

OK

获取商品信息

192.168.209.128:6793> HGET items:1001 id

"3"

192.168.209.128:6793> HGETALL items:1001

1) "id"

2) "3"

3) "name"

4) "apple"

5) "price"

6) "999.9"

 

3.3 List 列表

 

① Arraylist和linkedlist的区别?

 

ArrayList使用数组方式存储数据,所以根据索引查询数据速度快,而新增或者删除元素时需要涉及到位移操作,所以比较慢。

LinkedList使用双向链表方式存储数据,每个元素都记录前后元素的指针,所以插入、删除数据时只是更改前后元素的指针指向即可,速度非常快。然后通过下标查询元素时需要从头开始索引,所以比较慢,但是如果查询前几个元素或后几个元素速度比较快

 

列表类型(list)可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的某一个片段。

       列表类型内部是使用双向链表(double linked list)实现的,所以向列表两端添加元素的时间复杂度为0(1),获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的。

 

② 向列表两端增加元素

 

       i向列表左边增加元素

语法:LPUSH key value [value ...]

127.0.0.1:6379> lpush list:1 1 2 3

(integer) 3

 

       ii 向列表右边增加元素

语法:RPUSH key value [value ...]

127.0.0.1:6379> rpush list:1 4 5 6

(integer) 3

 

③ 查看列表

LRANGE命令是列表类型最常用的命令之一,获取列表中的某一片段,将返回start、stop之间的所有元素(包含两端的元素),索引从0开始。索引可以是负数,如:“-1”代表最后边的一个元素

语法:LRANGE key start stop

 

127.0.0.1:6379> lrange list:1 0 2

1) "2"

2) "1"

3) "4"

 

127.0.0.1:6379> lrange list1 0 -1

 

④ 从列表两端弹出元素

 

LPOP命令从列表左边弹出一个元素,会分两步完成:

第一步是将列表左边的元素从列表中移除

第二步是返回被移除的元素值。

语法:

LPOP key

RPOP key

127.0.0.1:6379> lpop list:1

"3“

127.0.0.1:6379> rpop list:1

"6“

 

⑤ 获取列表中元素的个数

 

语法:LLEN key

127.0.0.1:6379> llen list:1

(integer) 2

 

⑥ 其他常用命令

 

       i LREM命令会删除列表中前count个值为value的元素,返回实际删除的元素个数。根据count值的不同,该命令的执行方式会有所不同:

  1. 当count>0时, LREM会从列表左边开始删除。
  2. 当count<0时, LREM会从列表后边开始删除。
  3. 当count=0时, LREM删除所有值为value的元素。

语法:LREM key count value

  • Eg: 略(思考)

 

       ii 获得/设置指定索引的元素值

   a 获得指定索引的元素值

语法:LINDEX key index

127.0.0.1:6379> lindex l:list 2

"1"

 

       b 设置指定索引的元素值

语法:LSET key index value

127.0.0.1:6379> lset l:list 2 2

OK

127.0.0.1:6379> lrange l:list 0 -1

1) "6"

2) "5"

3) "2"

4) "2"

 

       iii 只保留列表指定片段,指定范围和LRANGE一致

语法:LTRIM key start stop

127.0.0.1:6379> lrange l:list 0 -1

1) "6"

2) "5"

3) "0"

4) "2"

127.0.0.1:6379> ltrim l:list 0 2

OK

127.0.0.1:6379> lrange l:list 0 -1

1) "6"

2) "5"

3) "0"

 

       iv 向列表中插入元素

该命令首先会在列表中从左到右查找值为pivot的元素,然后根据第二个参数是BEFORE还是AFTER来决定将value插入到该元素的前面还是后面。

 

语法:LINSERT key BEFORE|AFTER pivot value

 

127.0.0.1:6379> lrange list 0 -1

1) "3"

2) "2"

3) "1"

127.0.0.1:6379> linsert list after 3 4

(integer) 4

127.0.0.1:6379> lrange list 0 -1

1) "3"

2) "4"

3) "2"

4) "1"

 

       v 将元素从一个列表转移到另一个列表中

 

语法:RPOPLPUSH source destination

127.0.0.1:6379> rpoplpush list newlist

"1"

127.0.0.1:6379> lrange newlist 0 -1

1) "1"

127.0.0.1:6379> lrange list 0 -1

1) "3"

2) "4"

3) "2"

 

应用

Eg: 评论列表。

思路:

在Redis中创建商品评论列表

用户发布商品评论,将评论信息转成json存储到list中。

用户在页面查询评论列表,从redis中取出json数据展示到页面。

 

定义商品评论列表key:

商品编号为1001的商品评论key【items: comment:1001】

192.168.209.128:7001> LPUSH items:comment:1001 '{"id":1,"name":"商品不错,很好!!","date":1430295077289}'

 

3.4 Set 集合

 

集合类型:无序、不可重复

列表类型:有序、可重复

 

集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等,由于集合类型的Redis内部是使用值为空的散列表实现,所有这些操作的时间复杂度都为0(1)。

 

① 增加/删除元素

 

语法:SADD key member [member ...]

127.0.0.1:6379> sadd set a b c

(integer) 3

127.0.0.1:6379> sadd set a

(integer) 0

 

语法:SREM key member [member ...]

127.0.0.1:6379> srem set c d

(integer) 1

 

② 获得集合中的所有元素

语法:SMEMBERS key

127.0.0.1:6379> smembers set

1) "b"

2) "a”

 

③ 判断元素是否在集合中

语法:SISMEMBER key member

127.0.0.1:6379> sismember set a

(integer) 1

127.0.0.1:6379> sismember set h

(integer) 0

 

④ 运算命令

 

A 集合的差集运算 A-B

属于A并且不属于B的元素构成的集合。

 

语法:SDIFF key [key ...]

127.0.0.1:6379> sadd setA 1 2 3

(integer) 3

127.0.0.1:6379> sadd setB 2 3 4

(integer) 3

127.0.0.1:6379> sdiff setA setB

1) "1"

127.0.0.1:6379> sdiff setB setA

1) "4"

 

B 集合的交集运算 A ∩ B

属于A且属于B的元素构成的集合。

 

语法:SINTER key [key ...]

127.0.0.1:6379> sinter setA setB

1) "2"

2) "3"

 

C 集合的并集运算 A ∪ B

属于A或者属于B的元素构成的集合

 

语法:SUNION key [key ...]

127.0.0.1:6379> sunion setA setB

1) "1"

2) "2"

3) "3"

4) "4"

 

⑤ 其他常用命令

 

A 获得集合中元素的个数

 

语法:SCARD key

127.0.0.1:6379> smembers setA

1) "1"

2) "2"

3) "3"

127.0.0.1:6379> scard setA

(integer) 3

 

B 从集合中弹出一个元素

 

注意:由于集合是无序的,所有SPOP命令会从集合中随机选择一个元素弹出

 

语法:SPOP key

127.0.0.1:6379> spop setA

"1“

 

3.5 Sortedset (zset) 有序集合

 

Sortedset是有序集合,可排序的,但是唯一。

 

在集合类型的基础上,有序集合类型为集合中的每个元素都关联一个分数,这使得我们不仅可以完成插入、删除和判断元素是否存在在集合中,还能够获得分数最高或最低的前N个元素、获取指定分数范围内的元素等与分数有关的操作。

 

在某些方面有序集合和列表类型有些相似。

1、二者都是有序的。

2、二者都可以获得某一范围的元素。

但是,二者有着很大区别:

1、列表类型是通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问中间数据的速度会变慢。

2、有序集合类型使用散列表实现,所有即使读取位于中间部分的数据也很快。

3、列表中不能简单的调整某个元素的位置,但是有序集合可以(通过更改分数实现)

4、有序集合要比列表类型更耗内存。

 

① 增加元素

向有序集合中加入一个元素和该元素的分数,如果该元素已经存在则会用新的分数替换原有的分数。返回值是新加入到集合中的元素个数,不包含之前已经存在的元素。

 

语法:ZADD key score member [score member ...]

127.0.0.1:6379> zadd scoreboard 80 zhangsan 89 lisi 94 wangwu

(integer) 3

127.0.0.1:6379> zadd scoreboard 97 lisi

(integer) 0

 

 

② 获取元素的分数

 

语法:ZSCORE key member

127.0.0.1:6379> zscore scoreboard lisi

"97"

 

③ 删除元素:

移除有序集key中的一个或多个成员,不存在的成员将被忽略。

当key存在但不是有序集类型时,返回一个错误。

 

语法:ZREM key member [member ...]

127.0.0.1:6379> zrem scoreboard lisi

(integer) 1

 

④ 获得排名在某个范围的元素列表

 

A 获得排名在某个范围的元素列表:

   按照元素分数从小到大的顺序返回索引从start到stop之间的所有元素(包含两端的元素)

语法:ZRANGE key start stop [WITHSCORES]              

127.0.0.1:6379> zrange scoreboard 0 2

1) "zhangsan"

2) "wangwu"

3) "lisi“

 

B  按照元素分数从大到小的顺序返回索引从start到stop之间的所有元素(包含两端的元素)

语法:ZREVRANGE key start stop [WITHSCORES]         

127.0.0.1:6379> zrevrange scoreboard 0 2

1) " lisi "

2) "wangwu"

3) " zhangsan “

 

如果需要获得元素的分数的可以在命令尾部加上WITHSCORES参数

127.0.0.1:6379> zrange scoreboard 0 1 WITHSCORES

1) "zhangsan"

2) "80"

3) "wangwu"

4) "94"

 

⑤ 获取元素的排名

 

A 从小到大

语法:ZRANK key member

127.0.0.1:6379> ZRANK scoreboard lisi

(integer) 0

 

B 从大到小

语法:ZREVRANK key member

127.0.0.1:6379> ZREVRANK scoreboard zhangsan

(integer) 1

 

⑥ 其他常用命令

 

A 获得指定分数范围的元素

语法:ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

127.0.0.1:6379> ZRANGEBYSCORE scoreboard 90 97 WITHSCORES

1) "wangwu"

2) "94"

3) "lisi"

4) "97"

127.0.0.1:6379> ZRANGEBYSCORE scoreboard 70 100 limit 1 2

1) "wangwu"

2) "lisi"

 

B 增加某个元素的分数

返回值是更改后的分数

 

语法:ZINCRBY  key increment member

127.0.0.1:6379> ZINCRBY scoreboard 4 lisi

"101“

 

C 获得集合中元素的数量

 

语法:ZCARD key

127.0.0.1:6379> ZCARD scoreboard

(integer) 3

 

D 获得指定分数范围内的元素个数

语法:ZCOUNT key min max

127.0.0.1:6379> ZCOUNT scoreboard 80 90

(integer) 1

 

E 按照排名范围删除元素

 

语法:ZREMRANGEBYRANK key start stop

127.0.0.1:6379> ZREMRANGEBYRANK scoreboard 0 1

(integer) 2

127.0.0.1:6379> ZRANGE scoreboard 0 -1

1) "lisi"

 

F 按照分数范围删除元素

语法:ZREMRANGEBYSCORE key min max

127.0.0.1:6379> zadd scoreboard 84 zhangsan      

(integer) 1

127.0.0.1:6379> ZREMRANGEBYSCORE scoreboard 80 100

(integer) 1

 

应用

 

商品销售排行榜

需求:根据商品销售量对商品进行排行显示

思路:定义商品销售排行榜(sorted set集合),Key为items:sellsort,分数为商品销售量。

 

写入商品销售量:

   商品编号1001的销量是9,商品编号1002的销量是10

192.168.209.128:6379> ZADD items:sellsort 9 1001 10 1002

 

商品编号1001的销量加1:

192.168.209.128:7001> ZINCRBY items:sellsort 1 1001

 

商品销量前10名:

192.168.209.128:7001> ZRANGE items:sellsort 0 9 withscores

 

3.6 Keys 命令

 

① keys : 返回满足给定pattern 的所有key

redis 127.0.0.1:6379> keys mylist*

1) "mylist"

2) "mylist5"

3) "mylist6"

4) "mylist7"

5) "mylist8"

 

② exists: 确认一个key 是否存在

示例:从结果来看,数据库中不存在HongWan 这个key,但是age 这个key 是存在的。

 

redis 127.0.0.1:6379> exists HongWan

(integer) 0

redis 127.0.0.1:6379> exists age

(integer) 1

redis 127.0.0.1:6379>

 

 

 

③ del : 删除一个 key

删除一个key

redis 127.0.0.1:6379> del age

(integer) 1

redis 127.0.0.1:6379> exists age

(integer) 0

 

④ rename : 重命名key

示例:age 成功的被我们改名为age_new 了

 

redis 127.0.0.1:6379[1]> keys *

1) "age"

redis 127.0.0.1:6379[1]> rename age age_new

OK

redis 127.0.0.1:6379[1]> keys *

1) "age_new"

redis 127.0.0.1:6379[1]>

 

⑤ type : 返回值的类型

 

返回值的类型

示例:这个方法可以非常简单的判断出值的类型

redis 127.0.0.1:6379> type addr

string

redis 127.0.0.1:6379> type myzset2

zset

redis 127.0.0.1:6379> type mylist

list

redis 127.0.0.1:6379>

 

⑥ 设置key的生存时间

 

Redis在实际使用过程中更多的用作缓存,然而缓存的数据一般都是需要设置生存时间的,即:到期后数据销毁。

 

EXPIRE key seconds         设置key的生存时间(单位:秒)key在多少秒后会自动删除

TTL key                              查看key剩余的生存时间

PERSIST key                        清除生存时间

PEXPIRE key milliseconds    生存时间设置单位为:毫秒

 

例子:

192.168.209.128:6379> set test 1             设置test的值为1

OK

192.168.209.128:6379> get test                获取test的值

"1"

192.168.209.128:6379> EXPIRE test 5 设置test的生存时间为5秒

(integer) 1

192.168.209.128:6379> TTL test               查看test的剩余生成时间还有1秒删除

(integer) 1

192.168.209.128:6379> TTL test

(integer) -2

192.168.209.128:6379> get test                获取test的值,已经删除

(nil)

 

四、 Redis 特征

 

4.1 事务

       事务是指一系列操作步骤,这一系列的操作步骤,要么完全地执行,要么完全地不执行。

Redis 中的事务(transaction)是一组命令的集合,至少是两个或两个以上的命令,redis

事务保证这些命令被执行时中间不会被任何其他操作打断。

 

4.1.1 事务操作的命令

①   multi

语法: multi

作用:标记一个事务的开始。事务内的多条命令会按照先后顺序被放进一个队列当中。

返回值:总是返回 ok

②   exec

语法:exec

作用:执行所有事务块内的命令

返回值:事务内的所有执行语句内容,事务被打断(影响)返回 nil

 

③ discard

语法:discard

作用:取消事务,放弃执行事务块内的所有命令

返回值:总是返回 ok

 

④ watch

语法:watch key [key ...]

作用:监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,

那么事务将被打断。

返回值:总是返回 ok

 

⑤ unwatch

语法:unwatch

作用:取消 WATCH 命令对所有 key 的监视。如果在执行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了

返回值:总是返回 ok

 

4.1.2 事务的实现

① 正常执行事务:

事务的执行步骤: 首先开启事务, 其次向事务队列中加入命令,最后执行事务提交

 

例 1:事务的执行:

1)multi : 用 multi 命令告诉 Redis,接下来要执行的命令你先不要执行,而是把它们暂

时存起来 (开启事务)

2)sadd works john 第一条命令进入等待队列(命令入队)

3)sadd works rose 第二条命令进入等待队列(命令入队)

4)exce 告知 redis 执行前面发送的两条命令(提交事务)

 

② 事务执行 exec之前,入队命令错误(语法 错误;严重错误导致服务器不能正常工作(例如) 内存不足)) ),放弃事务。

 

执行事务步骤:

1)MULTI 正常命令

2)SET key value 正常命令

3)INCR 命令语法错误

4)EXEC 无法执行事务,那么第一条正确的命令也不会执行,所以 key 的值不会设置成功

 

③ 事务执行exec命令后,执行队列命令,命令执行错误,事务提交。

 

执行步骤:

1)MULTI 正常命令

2)SET username zhangsan 正常命令

3)lpop username 正常命令,语法没有错误,执行命令时才会有错误。

4)EXEC 正常执行 ,发现错误可以在事务提交前放弃事务,执行 discard.

 

结论:在exec执行后的所产生的错误, 即使事务中有某个/某些命令在执行时产生了错误,

事务中的其他命令仍然会继续执行。

 

Redis 在事务失败时不进行回滚,而是继续执行余下的命令。

Redis 这种设计原则是:Redis 命令只会因为错误的语法而失败(这些问题不能在入队时发现),或是命令用在了错误类型的键上面,失败的命令并不是 Redis 导致,而是由编程错误造成的,这样错误应该在开发的过程中被发现,生产环境中不应出现语法的错误。就是在程序的运行环境中不应该出现语法的错误。而 Redis 能够保证正确的命令一定会被执行。再者不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。

 

④ 放弃事务

 

执行步骤:

1) MULTI 开启事务

2) SET age 25 命令入队

3) SET age 30 命令入队

4) DISCARD 放弃事务,则命令队列不会被执行

 

⑤ Redis的watch机制

 

A WATCH 机制原理

WATCH 机制:使用 WATCH 监视一个或多个 key , 跟踪 key 的 value 修改情况,如果有key 的 value 值在事务 EXEC 执行之前被修改了, 整个事务被取消。EXEC 返回提示信息,表示事务已经失败。

WATCH 机制使的事务 EXEC 变的有条件,事务只有在被 WATCH 的 key 没有修改的前提下才能执行。不满足条件,事务被取消。使用 WATCH 监视了一个带过期时间的键, 那么即使这个键过期了,事务仍然可以正常执行。

大多数情况下,不同的客户端会访问不同的键,相互同时竞争同一 key 的情况一般都很少,乐观锁能够以很好的性能解决数据冲突的问题。

 

B 怎么取消key的监视?

① WATCH 命令可以被调用多次。 对键的监视从 WATCH 执行之后开始生效,直到调用 EXEC 为止。不管事务是否成功执行,对所有键的监视都会被取消。

② 当客户端断开连接时,该客户端对键的监视也会被取消。

③ UNWATCH 命令可以手动取消对所有键的监视。

 

4.2 持久化

4.2.1  概述

 

持久化可以理解为存储,就是将数据存储到一个不会丢失的地方,如果把数据放在内存中,电脑关闭或重启数据就会丢失,所以放在内存中的数据不是持久化的,而放在磁盘就算是一种持久化。

 

4.2.2  RDB

 

A RDB

Redis Database(RDB),就是在指定的时间间隔内将内存中的数据集快照写入磁盘,数据恢复时将快照文件直接再读到内存。

RDB 保存了在某个时间点的数据集(全部数据)。存储在一个二进制文件中,只有一个文件。默认是 dump.rdb。RDB 技术非常适合做备份,可以保存最近一个小时,一天,一个月的全部数据。保存数据是在单独的进程中写文件,不影响 Redis 的正常使用。RDB 恢复数据时比AOF 速度快。

 

B 如何配置

 

RDB 方式的数据持久化,仅需在 redis.conf 文件中配置即可,默认配置是启用的。

在配置文件 redis.conf 中搜索 SNAPSHOTTING, 查找在注释开始和结束之间的关于 RDB的配置说明。配 SNAPSHOTTING 置地方有三处。

 

①:配置执行 RDB 生成快照文件的时间策略。

对 Redis 进行设置,让它在“ N 秒内数据集至少有 M 个 key 改动”这一条件被满足时,自动保存一次数据集。

 

save 开头的一行就是持久化配置,可以配置多个条件(每行配置一个条件),每个条件之间是“或”的关系。

“save 900 1”表示15分钟(900秒钟)内至少1个键被更改则进行快照。

“save 300 10”表示5分钟(300秒)内至少10个键被更改则进行快照。

 

配置格式:save

save 900 1

save 300 10

save 60 10000

 

②:dbfilename:设置 RDB 的文件名,默认文件名为 dump.rdb

③:dir:指定 RDB 文件的存储位置,默认是 ./ 当前目录

 

Redis启动后会读取RDB快照文件,将数据从硬盘载入到内存。根据数据量大小与结构和服务器性能不同,这个时间也不同。通常将记录一千万个字符串类型键、大小为1GB的快照文件载入到内存中需要花费20~30秒钟。

 

C 缺陷

通过RDB方式实现持久化,一旦Redis异常退出,就会丢失最后一次快照以后更改的所有数据。这就需要开发者根据具体的应用场合,通过组合设置自动快照条件的方式来将可能发生的数据损失控制在能够接受的范围。

如果数据很重要以至于无法承受任何损失,则可以考虑使用AOF方式进行持久化

 

4.2.3  AOF

 

A AOF

Append-only File(AOF),Redis 每次接收到一条改变数据的命令时,它将把该命令写到一个 AOF 文件中(只记录写操作,读操作不记录),当 Redis 重启时,它通过执行 AOF 文件中所有的命令来恢复数据。

 

B 如何配置

 

AOF 方式的数据持久化,仅需在 redis.conf 文件中配置即可

配置项:

①:appendonly:默认是 no,改成 yes 即开启了 aof 持久化

②:appendfilename:指定 AOF 文件名,默认文件名为 appendonly.aof

③:dir : 指定 RDB 和 AOF 文件存放的目录,默认是 ./

④:appendfsync:配置向 aof 文件写命令数据的策略:

no:不主动进行同步操作,而是完全交由操作系统来做(即每 30 秒一次),比较快但不

是很安全。

always:每次执行写入都会执行同步,慢一些但是比较安全。

everysec:每秒执行一次同步操作,比较平衡,介于速度和安全之间。这是默认项。

⑤:auto-aof-rewrite-min-size:允许重写的最小 AOF 文件大小,默认是 64M 。当 aof 文件大于 64M 时,开始整理 aop 文件,去掉无用的操作命令。缩小 aop 文件。

 

总结

1)append-only 文件是另一个可以提供完全数据保障的方案;

2)AOF 文件会在操作过程中变得越来越大。比如,如果你做一百次加法计算,最后你只会在数据库里面得到最终的数值,但是在你的 AOF 里面会存在 100 次记录,其中 99 条记录对最终的结果是无用的;但 Redis 支持在不影响服务的前提下在后台重构 AOF 文件,让文件得以整理变小

3)可以同时使用这两种方式,redis 默认优先加载 aof 文件(aof 数据最完整);

 

4.3 主从复制-读写分离

 

持久化保证了即使redis服务重启也不会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据丢失,如果通过redis的主从复制机制就可以避免这种单点故障,如下图:

 

注意:

       主机一旦发生增删改操作,那么主机会将数据同步到从机中。

       从机不能执行写操作。

 

4.3.1 操作步骤

 

A 配置多端redis文件

 

如果 Redis 启动,先停止。

作为 Master 的 Redis 端口是 6380

作为 Slaver 的 Redis 端口分别是 6382 , 6384

从原有的 redis.conf 拷贝三份,分别命名为 redis6380.conf, redis6382.conf , redis6384.conf

 

B 配置Master配置文件

 

编辑 Master 的配置文件 redis6380.conf : 在空文件加入如下内容

include /usr/local/redis-3.2.9/redis.conf

daemonize yes

port 6380

pidfile /var/run/redis_6380.pid

logfile 6380.log

dbfilename dump6380.rdb

 

配置项说明:

include : 包含原来的配置文件内容。/usr/local/redis-3.2.9/redis.conf 按照自己的目录设置。

daemonize:yes 后台启动应用,相当于 ./redis-server & , &的作用。

port : 自定义的端口号

pidfile : 自定义的文件,表示当前程序的 pid ,进程 id。

logfile:日志文件名

dbfilename:持久化的 rdb 文件名

 

C 配置Slave配置文件

 

编辑 Slave 的配置文件 redis6382.conf 和 redis6384.conf: 在空文件加入如下内容

①:redis6382.conf:

include /usr/local/redis-3.2.9/redis.conf

daemonize yes

port 6382

pidfile /var/run/redis_6382.pid

logfile 6382.log

dbfilename dump6382.rdb

slaveof 127.0.0.1 6380

配置项说明:

slaveof : 表示当前 Redis 是谁的从。当前是 127.0.0.0 端口 6380 这个 Master 的从。

②:redis6384.conf:

include /usr/local/redis-3.2.9/redis.conf

daemonize yes

port 6384

pidfile /var/run/redis_6384.pid

logfile 6384.log

dbfilename dump6384.rdb

slaveof 127.0.0.1 6380

 

D 启动三个redis,查看相关信息

 

①: Redis 客户端使用指定端口连接 Redis 服务器

./redis-cli -p 端口

②:查看服务器信息

info replication

③ flushall 清除所有key值数据

 

4.3.2 容灾处理

 

当 Master 服务出现故障,需手动将 slave 中的一个提升为 master, 剩下的 slave 挂至新的master 上(冷处理:机器挂掉了,再处理)

命令:

①:slaveof no one,将一台 slave 服务器提升为 Master (提升某 slave 为 master)

②:slaveof 127.0.0.1 6381 (将 slave 挂至新的 master 上)

 

4.3.3 redis集群

 

4.4.1 Redis 集群化方案

(1)Replication(复制)+Sentinel / ˈsentn-əl / (社区高可用版本)

redis的学习_第2张图片

Sentinel的作用有三个: - 监控:Sentinel 会不断的检查主服务器和从服务器是否正常运行。 - 通知:当被监控的某个redis服务器出现问题,Sentinel通过API脚本向管理员或者其他的应用程序发送通知。 - 自动故障转移: 当主节点不能正常工作时,Sentinel会开始一次自动的故障转移操作,它会将与失效主节点是主从关系的其中一个从节点升级为新的主节点,并且将其他的从节点指向新的主节点。

缺陷: (1)主从切换的过程中会丢数据 (2)Redis只能单点写,不能水平扩容。

(2) Proxy+Replication+Sentinel

A:Twitter开发的twemproxy (2015年)

用proxy对后端redis server进行代理 但是由于代理层的消耗性能很高 而且通常涉及多个key的操作都是不支持的 而且本身不支持动态扩容和透明的数据迁移 而且也失去维护 Twitter内部已经不使用了。

redis的学习_第3张图片

工作原理如下 - 前端使用Twemproxy+KeepAlived(检测web服务器的状态)做代理,将其后端的多台Redis实例分片进行统一管理与分配 - 每一个分片节点的Slave都是Master的副本且只读 - Sentinel持续不断的监控每个分片节点的Master,当Master出现故障且不可用状态时,Sentinel会通知/启动自动故障转移等动作 - Sentinel 可以在发生故障转移动作后触发相应脚本(通过 client-reconfig-script 参数配置 ),脚本获取到最新的Master来修改Twemproxy配置。

缺陷: (1)部署结构超级复杂 (2)可扩展性差,进行扩缩容需要手动干预 (3)运维不方便。

B:豌豆荚开发的codis 17

  codis使用的也是proxy思路 但是做的比较好 是这两种之间的一个中间级 而且支持redis命令是最多的 有图形化GUI管理和监控工具 运维友好。

(3)redis官方的redis-cluster (4.0后推荐)—公元2019

 

4.4.2 redis-cluster /ˈkləstər/ 架构图

redis的学习_第4张图片

架构细节:

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

(2)节点的fail是通过集群中超过半数的节点检测失效时才生效.

(3)客户端与redis节点直连,不需要中间proxy.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可.

(4)redis-cluster把所有的物理节点映射到[0-16348]slot槽上,cluster 负责维护node<->slot<->value

Slot:插槽。

 

4.4.3  redis-cluster 原理

redis-cluster是三个里性能最强大的 因为他使用去中心化的思想 使用hash slot方式 将16384个hash slot 覆盖到所有节点上 对于存储的每个key值 使用特定算法得到他对应的hash slot ,并在访问key时就去找他的hash slot在哪一个节点上 然后由当前访问节点从实际被分配了这个hash slot的节点去取数据,自动实现负载均衡与高可用 自动实现failover  并且支持动态扩展 官方已经玩到可以1000个节点 实现的复杂度低。

 

Redis 集群使用数据分片(sharding)而非一致性哈希(consistency hashing)来实现: 一个 Redis 集群包含 16384 个哈希槽(hash slot), 数据库中的每个键都属于这 16384 个哈希槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。

 

拓展: 一致性哈希: https://zhuanlan.zhihu.com/p/24440059

有没有完善的运维工具?可以参照一下搜狐出的CacheCloud。

 

 

Redis Cluster的架构,是属于分片集群的架构。而redis本身在内存上操作,不会涉及IO吞吐,即使读写分离也不会提升太多性能,Redis在生产上的主要问题是考虑容量,单机最多10-20G,key太多降低redis性能.因此采用分片集群结构,已经能保证了我们的性能。其次,用上了读写分离后,还要考虑主从一致性,主从延迟等问题,徒增业务复杂度。

 

示例如下:

 

Server1

0-5000

Server2

5001-10000

Server3

10001-16383

 

4.4.4 redis-cluster投票:容错

(1)集群中所有master参与投票,如果半数以上master节点与其中一个master节点通信超时(cluster-node-timeout),认为该master节点挂掉.

(2):什么时候整个集群不可用(cluster_state:fail)? 

  1. 如果集群任意master挂掉,且当前master没有slave,则集群进入fail状态。也可以理解成集群的[0-16383]slot映射不完全时进入fail状态。
  2. 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态。

 

 

4.5 redis-cluster 的搭建

 

4.5.1 开始

redis集群管理工具redis-trib.rb依赖ruby环境,首先需要安装ruby环境。

 

安装ruby

# yum install ruby

# yum install rubygems

  • 安装ruby和redis的接口程序

# gem install /usr/local/redis-4.1.1.gem

https://rubygems.org/gems/redis/versions/4.1.1

 

Centos 默认 ruby 最高 2.0.0,redis需要2.2.2 以上

解决方案:

https://www.cnblogs.com/carryping/p/7447823.html

 

./redis-trib.rb create --replicas 1 192.168.209.128:7002 192.168.209.128:7003 192.168.209.128:7004 192.168.209.128:7005 192.168.209.128:7006

连接任意一个节点
redis01/redis-cli -p 7001 –c

授权脚本文件权限

chmod +x start-all.sh

ruby redis-trib.rb create --replicas 1 192.168.209.128:7002 192.168.209.128:7003 192.168.209.128:7004 192.168.209.128:7005 192.168.209.128:7006

开冲

./redis-cli --cluster create 192.168.209.128:7001 192.168.209.128:7002 192.168.209.128:7003 192.168.209.128:7004 192.168.209.128:7005 192.168.209.128:7006 --cluster-replicas 1

./redis-cli --cluster create 192.168.0.2:7001 192.168.0.2:7002 192.168.0.2:7003 192.168.0.2:7004 192.168.0.2:7005  192.168.0.2:7006 --cluster-replicas 1

/usr/local/redis-5.0.4/src/

./redis-server /usr/local/redis-cluster/redis01/bin/redis.conf

daemonize    yes                      //redis后台运行

pidfile  /var/run/redis_7000.pid       //pidfile文件对应7000,7002,7003,7004

port  7000                             //端口7000,7002,7003,7004

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

cluster-config-file  nodes_7000.conf   //集群的配置  配置文件首次启动自动生成 7000,7001,7002

cluster-node-timeout  5000         //请求超时  设置5秒够了

appendonly  yes                   //aof日志开启  有需要就开启,它会每次写操作

springCache

jetCache

jedis/lettuce/https://redisson.org/

布隆过滤器bloomfilter

 

 

你可能感兴趣的:(Redis,Redis,缓存,数据库)