深入Redis
Redis前言
REmote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统,是跨平台的非关系型数据库。
()Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。
Redis 通常被称为数据结构服务器,因为值(value)可以是字符串(String)、哈希(Hash)、列表(list)、**集合(sets)和有序集合(sorted sets)**等类型。
()Redis 简介
Redis 是完全开源的,遵守 BSD 协议,是一个高性能的 key-value 数据库。
Redis 与其他 key - value 缓存产品有以下三个特点:
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
- Redis支持数据的备份,即master-slave模式的数据备份。
Redis是单线程,用作数据库、缓存、消息中间件、端口号为6379,redis拥有16个数据库(可通过select 命令切换),底层实现是通过C语言
Redis 优势
- 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
- 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
- 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
- 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
Redis与其他key-value存储有什么不同?
- Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
- Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。
Redis效率
我们知道Redis效率性能是非常高,那为什么会优于其他数据库呢
- redis是基于内存的,内存的读写速度非常快;
- redis是单线程的,省去了很多上下文切换线程的时间;
- redis使用多路复用技术,可以处理并发的连接;
上下文切换
上下文切换就是cpu在多线程之间进行轮流执行(枪战cpu资源),而redis单线程的,因此避免了繁琐的多线程上下文切换。
多路复用
- 多路:指的是多个socket连接,
- 复用:指的是复用一个线程。
目前,多路复用主要有三种技术:select,poll,epoll。它们出现的顺序是按后的,越排后的技术改正了之前技术的缺点。epoll是最新的也是目前最好的多路复用技术。
举个例子:一个酒吧服务员,前面有很多醉汉,epoll这种方式相当于一个醉汉吼了一声要酒,服务员听见之后就去给他倒酒,而在这些醉汉没有要求的时候可以玩玩手机等。但是select和poll技术是这样的场景:服务员轮流着问各个醉汉要不要倒酒,没有空闲的时间。io多路复用的意思就是做个醉汉公用一个服务员。
- select:
1.会修改传入的参数,对于多个调用的函数来说非常不友好;
2.要是sock(io流出现了数据),select只能轮询这去找数据,对于大量的sock来说开销很大;
3.不是线程安全的,很恐怖;
4.只能监视1024个连接;
- poll:
1.还不是线程安全的…
2.去掉了1024个连接的限制;
3.不修改传入的参数了;
- epoll:
1.线程安全了;
2.epoll不仅能告诉你sock有数据,还能告诉你哪个sock有数据,不用轮询了;
3.however,只支持linux系统;
select, poll, epoll 都是I/O多路复用的具体的实现,之所以有这三个存在,其实是他们出现是有先后顺序的。
I/O多路复用这个概念被提出来以后, select是第一个实现 (1983 左右在BSD里面实现的)。
一、select 被实现以后,很快就暴露出了很多问题。
- select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的。
- select 如果任何一个sock(I/O stream)出现了数据,select 仅仅会返回,但是并不会告诉你是那个sock上有数据,于是你只能自己一个一个的找,10几个sock可能还好,要是几万的sock每次都找一遍,这个无谓的开销就颇有海天盛筵的豪气了。
- select 只能监视1024个链接
- select 不是线程安全的,如果你把一个sock加入到select, 然后突然另外一个线程发现,这个sock不用,要收回。对不起,这个select 不支持的,如果你关掉这个sock, select的标准行为不可预测的,
二、于是14年以后(1997年)一帮人又实现了poll, poll 修复了select的很多问题,比如
- poll 去掉了1024个链接的限制,于是要多少链接呢, 主人你开心就好。
- poll 从设计上来说,不再修改传入数组,不过这个要看你的平台了,所以行走江湖,还是小心为妙。
其实拖14年那么久也不是效率问题, 而是那个时代的硬件实在太弱,一台服务器处理1千多个链接简直就是神一样的存在了,select很长段时间已经满足需求。
但是poll仍然不是线程安全的, 这就意味着,不管服务器有多强悍,你也只能在一个线程里面处理一组I/O流。你当然可以那多进程来配合了,不过然后你就有了多进程的各种问题。
于是5年以后, 在2002, 大神 Davide Libenzi 实现了epoll.
三、epoll 可以说是I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题, 比如:
- epoll 现在是线程安全的。
- epoll 现在不仅告诉你sock组里面数据,还会告诉你具体哪个sock有数据,你不用自己去找了。
可是epoll 有个致命的缺点,只有linux支持。比如BSD上面对应的实现是kqueue。
其实有些国内知名厂商把epoll从安卓里面裁掉这种脑残的事情我会主动告诉你嘛。什么,你说没人用安卓做服务器,尼玛你是看不起p2p软件了啦。
而ngnix 的设计原则里面, 它会使用目标平台上面最高效的I/O多路复用模型咯,所以才会有这个设置。一般情况下,如果可能的话,尽量都用epoll/kqueue吧。
详细的在这里:
Connection processing methods
五大数据类型
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
String(字符串)
string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。
string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。
string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。
实例
redis 127.0.0.1:6379> SET name "wangping" OK redis 127.0.0.1:6379> GET name "wangping"
在以上实例中我们使用了 Redis 的 SET 和 GET 命令。键为 name,对应的值为 wangping。
**注意:**一个键最大能存储 512MB。
Hash(哈希)
Redis hash 是一个键值(key=>value)对集合。
Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
实例
DEL name 用于删除前面测试用过的 key,不然会报错:(error) WRONGTYPE Operation against a key holding the wrong kind of value
redis 127.0.0.1:6379> DEL runoob redis 127.0.0.1:6379> HMSET runoob field1 "Hello" field2 "World" "OK" redis 127.0.0.1:6379> HGET runoob field1 "Hello" redis 127.0.0.1:6379> HGET runoob field2 "World"
实例中我们使用了 Redis HMSET, HGET 命令,HMSET 设置了两个 field=>value 对, HGET 获取对应 field 对应的 value。
每个 hash 可以存储 2^32-1 键值对(40多亿)。
List(列表)
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
实例
redis 127.0.0.1:6379> DEL runoob redis 127.0.0.1:6379> lpush runoob redis (integer) 1 redis 127.0.0.1:6379> lpush runoob mongodb (integer) 2 redis 127.0.0.1:6379> lpush runoob rabbitmq (integer) 3 redis 127.0.0.1:6379> lrange runoob 0 10 1) "rabbitmq" 2) "mongodb" 3) "redis" redis 127.0.0.1:6379>
列表最多可存储 232 - 1 元素 (4294967295, 每个列表可存储40多亿)。
Set(集合)
Redis 的 Set 是 string 类型的无序集合。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
sadd 命令
添加一个 string 元素到 key 对应的 set 集合中,成功返回 1,如果元素已经在集合中返回 0。
sadd key member
实例
redis 127.0.0.1:6379> DEL runoob redis 127.0.0.1:6379> sadd runoob redis (integer) 1 redis 127.0.0.1:6379> sadd runoob mongodb (integer) 1 redis 127.0.0.1:6379> sadd runoob rabbitmq (integer) 1 redis 127.0.0.1:6379> sadd runoob rabbitmq (integer) 0 redis 127.0.0.1:6379> smembers runoob 1) "redis" 2) "rabbitmq" 3) "mongodb"
**注意:**以上实例中 rabbitmq 添加了两次,但根据集合内元素的唯一性,第二次插入的元素将被忽略。
集合中最大的成员数为 232 - 1(4294967295, 每个集合可存储40多亿个成员)。
zset(sorted set:有序集合)
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。
zadd 命令
添加元素到集合,元素在集合中存在则更新对应score
zadd key score member
实例
redis 127.0.0.1:6379> DEL runoob redis 127.0.0.1:6379> zadd runoob 0 redis (integer) 1 redis 127.0.0.1:6379> zadd runoob 0 mongodb (integer) 1 redis 127.0.0.1:6379> zadd runoob 0 rabbitmq (integer) 1 redis 127.0.0.1:6379> zadd runoob 0 rabbitmq (integer) 0 redis 127.0.0.1:6379> ZRANGEBYSCORE runoob 0 1000 1) "mongodb" 2) "rabbitmq" 3) "redis"
各个数据类型应用场景:
类型 简介 特性 场景 String(字符串) 二进制安全 可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M — Hash(字典) 键值对集合,即编程语言中的Map类型 适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去) 存储、读取、修改用户属性 List(列表) 链表(双向链表) 增删快,提供了操作某一段元素的API 1,最新消息排行等功能(比如朋友圈的时间线) 2,消息队列 Set(集合) 哈希表实现,元素不重复 1、添加、删除,查找的复杂度都是O(1) 2、为集合提供了求交集、并集、差集等操作 1、共同好友 2、利用唯一性,统计访问网站的所有独立ip 3、好友推荐时,根据tag求交集,大于某个阈值就可以推荐 Sorted Set(有序集合) 将Set中的元素增加一个权重参数score,元素按score有序排列 数据插入集合时,已经进行天然排序 1、排行榜 2、带权重的消息队列 注意:Redis支持多个数据库,并且每个数据库的数据是隔离的不能共享,并且基于单机才有,如果是集群就没有数据库的概念。
Redis是一个字典结构的存储服务器,而实际上一个Redis实例提供了多个用来存储数据的字典,客户端可以指定将数据存储在哪个字典中。这与我们熟知的在一个关系数据库实例中可以创建多个数据库类似,所以可以将其中的每个字典都理解成一个独立的数据库。
每个数据库对外都是一个从0开始的递增数字命名,Redis默认支持16个数据库(可以通过配置文件支持更多,无上限),可以通过配置databases来修改这一数字。客户端与Redis建立连接后会自动选择0号数据库,不过可以随时使用SELECT命令更换数据库,如要选择1号数据库:
redis> SELECT 1 OK redis [1] > GET foo (nil)
然而这些以数字命名的数据库又与我们理解的数据库有所区别。首先Redis不支持自定义数据库的名字,每个数据库都以编号命名,开发者必须自己记录哪些数据库存储了哪些数据。另外Redis也不支持为每个数据库设置不同的访问密码,所以一个客户端要么可以访问全部数据库,要么连一个数据库也没有权限访问。最重要的一点是多个数据库之间并不是完全隔离的,比如FLUSHALL命令可以清空一个Redis实例中所有数据库中的数据。综上所述,这些数据库更像是一种命名空间,而不适宜存储不同应用程序的数据。比如可以使用0号数据库存储某个应用生产环境中的数据,使用1号数据库存储测试环境中的数据,但不适宜使用0号数据库存储A应用的数据而使用1号数据库B应用的数据,不同的应用应该使用不同的Redis实例存储数据。由于Redis非常轻量级,一个空Redis实例占用的内存只有1M左右,所以不用担心多个Redis实例会额外占用很多内存。
Redis命令
官网搜索命令http://www.redis.cn/commands.html
Redis 命令用于在 redis 服务上执行操作。
要在 redis 服务上执行命令需要一个 redis 客户端。Redis 客户端在我们之前下载的的 redis 的安装包中。
语法
Redis 客户端的基本语法为:
$ redis-cli
实例
以下实例讲解了如何启动 redis 客户端:
启动 redis 服务器,打开终端并输入命令 redis-cli,该命令会连接本地的 redis 服务。
$ redis-cli redis 127.0.0.1:6379> redis 127.0.0.1:6379> PING PONG
在以上实例中我们连接到本地的 redis 服务并执行 PING 命令,该命令用于检测 redis 服务是否启动。
Redis 连接
Redis 连接命令主要是用于连接 redis 服务。
实例
以下实例演示了客户端如何通过密码验证连接到 redis 服务,并检测服务是否在运行:
redis 127.0.0.1:6379> AUTH "password" OK redis 127.0.0.1:6379> PING PONG
Redis 连接命令
下表列出了 redis 连接的基本命令:
序号 命令及描述 1 AUTH password 验证密码是否正确 2 ECHO message 打印字符串 3 PING 查看服务是否运行 4 QUIT 关闭当前连接 5 SELECT index 在远程服务上执行命令
如果需要在远程 redis 服务上执行命令,同样我们使用的也是 redis-cli 命令。
语法
$ redis-cli -h host -p port -a password
实例
以下实例演示了如何连接到主机为 127.0.0.1,端口为 6379 ,密码为 mypass 的 redis 服务上。
$redis-cli -h 127.0.0.1 -p 6379 -a "mypass" redis 127.0.0.1:6379> redis 127.0.0.1:6379> PING PONG
返回的是PONG即已启动Redis
切换数据库
选择一个数据库,下标值从0开始,一个新连接默认连接的数据库是DB0。
返回值
simple-string-reply
redis 127.0.0.1:6379> select 1 #切换一号数据库 redis 127.0.0.1:6379> select 2 #切换二号数据库 redis 127.0.0.1:6379> select 3 #切换三号数据库 ...
清空数据库
flushdb #清除当前的数据库的所有值 flushall #清除所有数据库的所有数据
删除数据
删除指定的一批keys(一个或则多个),如果删除中的某些key不存在,则直接忽略。
返回值
integer-reply: 被删除的keys的数量
例子
redis> SET key1 "Hello" OK redis> SET key2 "World" OK redis> DEL key1 key2 key3 #删除指定key1 key2 key3的数据 (integer) 2 redis>
移动数据
move key db
将当前数据库的 key 移动到给定的数据库 db 当中。
如果当前数据库(源数据库)和给定数据库(目标数据库)有相同名字的给定 key ,或者 key 不存在于当前数据库,那么 MOVE 没有任何效果。
因此,也可以利用这一特性,将 MOVE 当作锁(locking)原语(primitive)。
返回值
integer-reply:
- 移动成功返回 1
- 失败则返回 0
move key1 1 #将数据key1移动到一号数据库中(数据库有0-15号数据库,共16个数据库)
判断数据是否存在
key是否存在
EXISTS key [key …]
**时间复杂度:**O(1)
返回key是否存在。
返回值
integer-reply,如下的整数结果
- 1 如果key存在
- 0 如果key不存在
例子
redis> SET key1 "Hello" OK redis> EXISTS key1 (integer) 1 redis> EXISTS key2 (integer) 0 redis>
hash里面field是否存在
HEXISTS key field
**时间复杂度:**O(1)
返回hash里面field是否存在
返回值
integer-reply, 含义如下:
- 1 hash里面包含该field。
- 0 hash里面不包含该field或者key不存在。
例子
redis> HSET myhash field1 "foo" (integer) 1 redis> HEXISTS myhash field1 (integer) 1 redis> HEXISTS myhash field2 (integer) 0 redis>
Redis 键(key)
Redis 键命令用于管理 redis 的键。
语法
Redis 键命令的基本语法如下:
redis 127.0.0.1:6379> COMMAND KEY_NAME
实例
redis 127.0.0.1:6379> SET runoobkey redis OK redis 127.0.0.1:6379> DEL runoobkey (integer) 1
在以上实例中 DEL 是一个命令, runoobkey 是一个键。 如果键被删除成功,命令执行后输出 (integer) 1,否则将输出 (integer) 0
Redis keys 命令
下表给出了与 Redis 键相关的基本命令:
序号 命令及描述 1 DEL key 该命令用于在 key 存在时删除 key。 2 DUMP key 序列化给定 key ,并返回被序列化的值。 3 EXISTS key 检查给定 key 是否存在。 4 EXPIRE key seconds 为给定 key 设置过期时间,以秒计。 5 EXPIREAT key timestamp EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。 6 PEXPIRE key milliseconds 设置 key 的过期时间以毫秒计。 7 PEXPIREAT key milliseconds-timestamp 设置 key 过期时间的时间戳(unix timestamp) 以毫秒计 8 KEYS pattern 查找所有符合给定模式( pattern)的 key 。 9 MOVE key db 将当前数据库的 key 移动到给定的数据库 db 当中。 10 PERSIST key 移除 key 的过期时间,key 将持久保持。 11 PTTL key 以毫秒为单位返回 key 的剩余的过期时间。 12 TTL key 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。 13 RANDOMKEY 从当前数据库中随机返回一个 key 。 14 RENAME key newkey 修改 key 的名称 15 RENAMENX key newkey 仅当 newkey 不存在时,将 key 改名为 newkey 。 16 [SCAN cursor MATCH pattern] [COUNT count] 迭代数据库中的数据库键。 17 TYPE key 返回 key 所储存的值的类型。 String数据类型
基本操作
127.0.0.1:6379> set key1 v1 #设置值 oK 127.0.0.1:6379> get key1 #获得值 "v1" 127.0.0.1:6379> keys * #获得所有的key 1) "key1" 127.0.0.1:6379> EXISTs key1 #判断某一个key是否存在 (integer) 1 127.0.0.1:6379>APPEND key1 "he11o" #追加字符串,如果当前key不存在,就相当于setkeyl (integer) 7 127.0.0.1:6379> get key1 "v1he11o" 127.0.0.1:6379>STRLEN key1 #获取字符串长度 (integer) 7 127.0.0.1:6379>APPEND key1 ", readis" (integer) 17 127.0.0.1:6379>STRLEN key1 (integer) 17 127.0.0.1:6379> get key1 "v1he11o,readis" ##获取字符串指定范围 127.0.0.1:6379> getrange key1 0 7 #获取字符串指定0-7字符 "v1he11o" 127.0.0.1:6379> getrange key1 0 -1 #获取字符串全部字符 "v1he11o,readis" #替换! 127.0.0.1:6379> set key2 abcdefg 127.0.0.1:6379> get key2 "abcdefg" 127.0.0.1:6379>SETRANGE key2 1 xx #替换指定位置开始的字符串! (integer) 7 127.0.0.1:6379> get key2"axxdefg"
自增自减
可实现游览量功能
127.0.0.1:6379> set views 0 #初始浏览量为0 OK 127.0.0.1:6379> get views "0" 127.0.0.1:6379> incr views #自增1 浏览量变为1 (integer) 1 127.0.0.1:6379> incr views (integer) 2 127.0.0.1:6379> get views "2" 127.0.0.1:6379> decr views # 自减1 浏览量变为1 (integer) 1 127.0.0.1:6379> decr views (integer) 0 127.0.0.1:6379> decr views (integer) -1 127.0.0.1:6379> get views "-1" 127.0.0.1:6379>INCRBY views 10 #每次自增10,即步长为10的自增 (integer) 9 127.0.0.1:6379> INCRBY views 10 (integer) 19 127.0.0.1:6379> DECRBY views 8 #每次自减8,即步长为8的自减 (integer) 11
setex/setnx
# setex (set with expire) 设置过期时间 # setnx (set if not exist) #不存在在设置(在分布式锁中会常常使用! ) 127.0.0.1:6379> setex key3 30 "he11o" #设置key3的值为he11o,30秒后过期 OK 127.0.0.1:6379> ttl key3 #查看该数据的过期时间 (integer) 26 127.0.0.1:6379> get key3 "he11o"" 127.0.0.1:6379> setnx mykey "redis" #如果mykey不存在,创建mykey (integer) 1 127.0.0.1:6379> keys * 1 "key2" 2 "mykey" 3 "key1" 127.0.0.1:6379> ttl key3 #该数据已经过期,返回-2 (integer) -2 127.0.0.1:6379> setnx mykey "MongoDB" # 如果mykey存在,创建失败! (integer 0) 127.0.0.1:6379s get mykey "redis"
mset/mget
127.0.0.1:6379> mset kl v1 k2 v2 k3 v3 #同时设置多个值oK 127.0.0.1:6379> keys * 1)"k1" 2)"k2" 3)"k3" 127.0.0.1:6379> mget kl k2 k3 #同时获取多个值 1)"v1" 2)"v2" 3)"v3" 127.0.0.1:6379> msetnx k1 v1 k4 v4 # mseynx是一个原子性的操作,要么一起失败,要么一起成功 (integer) 0 127.0.0.1:6379> get k4 (nil)
对象设置
#对象 set user:1 {name :zhangsan , age: 3} #设置一个user:1对象值为 json字符来保存一个对象! # 单这里的key是一个巧妙的设计:user:{id}:{filed},如此设计在Redis中是完全OK了! 127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2 oK 127.0.0.1:6379> mget user:1:name user:1:age 1) "zhangsan" 2) "2"
getset
# 先get然后在set 127.0.0.1:6379> getset db redis #如果不存在值,则返回nil (nil) 127.0.0.1:6379> get db "redis 127.0.0.1:6379> getset db mongodb #如果存在值,获取原来的值,并设置新的值 "redis" 127.0.0.1:6379> get db "mongodb"
String类似的使用场景:value除了是我们的字符串还可以是我们的数字!
- 计数器
- 统计多单位的数量
- 粉丝数
- 对象缓存存储!
List数据类型
在redis中,lsit可以完成栈,队列,阻塞队列的操作
注:list允许添加重复值
添加和移除操作
127.0.0.1:6379>LPuSH list one #将一个值或者多个值,插入到列表头部(左)(integer) 1 127.0.0.1:6379>LPUSH list two (integer) 2 127.0.0.1:6379> LPUSH list three (integer) 3 127.0.0.1:6379>LRANGE list 0 -1 #获取1ist中值! 1) "three" 2) "two" 3)"one" 127.0.0.1:6379> LRANGE list 0 1 #通过区间获取具体的值! 1) "three" 2) "two" 127.0.0.1:6379>Rpush list righr #将一个值或者多个值,插入到列表位部(右) (integer) 4 127.0.0.1:6379> LRANGE list o -1 1) "three" 2) "two" 3) "one" 4) "righr" 127.0.0.1:6379> Lpop list #移除1ist的第一个元素 "three " 127.o.0.1:6379> Rpop list #移除1ist的最后一个元素 "righr" 127.0.0.1:6379> LRANGE 1ist 0 -1 1) "two" 2) "one"
通过下标获取值Len
127.0.0.1:6379> Lpush 1ist one (integer) 1 127.0.0.1:6379> Lpush 1ist two (integer) 2 127.0.0.1:6379> Lpush 1ist three (integer) 3 127.0.0.1:6379> Llen list #返回列表的长度 (integer) 3
移除指定的值(Lrem)
#移除指定的值! 127.0.0.1:6379> LRANGE list 0 -1 1) "three" 2) "three" 3) "two" 4)"one" 127.0.0.1:6379> lrem list 1 one #移除1ist集合中指定个数的value,精确匹配 (integer) 1 127.0.0.1:6379> LRANGE list o -1 1) "three" 2) "three" 3) "two" 127.0.0.1:6379> lrem 1ist 1 three (integer) 1 127.0.0.1:6379> LRANGE list 0 -1 1) "three" 2) "two" 127.0.0.1:6379> Lpush list three (integer) 3 127.0.0.1:6379> lrem list 2 three (integer) 2 127.0.0.1:6379> LRANGE list 0 -1 1) "two""
截取list数据trim
127.o.0.1:6379> Rpush mylist "he11o" (integer) 1 127.0.0.1:6379> Rpush mylist "he11o1" (integer) 2 127.0.0.1:6379> Rpush mylist "he11o2" (integer) 3 127.0.0.1:6379> Rpush mylist "he11o3" (integer) 4 127.0.0.1:6379> 1trim my]ist 1 2 #通过下标截取指定的长度,这个1ist已经被改变了,截断了只剩下截取的元素! OK 127.0.0.1:6379> LRANGE mylist 0 -1 1) "he11o1" 2 "he11o2"
rpoplpush
移除列表的最后一个元素并将它移动到新的列表中
127.0.0.1:6379> rpush mylist "he11o" (integer) 1 127.0.0.1:6379> rpush mylist "he11o1" (integer) 2 127.0.0.1:6379>rpush mylist "he11o2"" (integer) 3 127.0.0.1:6379>rpoplpush mylist myotherlist #移除列表的最后一个元素,将他移动到新的列表中! "he11o2" 127.0.0.1:6379> lrange mylist 0 -1 #查看原来的列表 1)"he11o" 2)"he11o1" 127.0.0.1:6379> lrange myotherlist 0 -1 #查看目标列表中,确实存在改值! 1)"he11o2"
lset更新操作
#lset将列表中指定下标的值替换为另外一个值,更新操作 127.0.0.1:6379> EXISTS list #判断这个列表是否存在(integer)o 127.0.0.1:6379> lset list 0 item (error) ERR no such key 127.0.0.1:6379> 1push list value1 (integer) 1 127.0.0.1:6379> LRANGE 1ist 0 0 1) "value1" 127.0.0.1:6379> 1set list 0 item #如果存在 ,更新当前下标值 OK 127.0.0.1:6379>LRANGE 1ist 0 0 1) "item"" 127.0.0.1:6379>lset list 1 other #如果不存在,就会报错 (error)ERR index out of range
linsert插入操作
#linsert 将某个具体的value插入到列把你中某个元素的前面或者后面! 127.0.0.1:6379> Rpush mylist "he11o" (integer 1 127.0.0.1:6379> Rpush mylist "wor1d" (integer) 2 127.0.0.1:6379> LINSERT mylist before "wor1d" "other" (integer) 3 127.0.0.1:6379> LRANGE mylist 0 -1 1)"he11o" 2) "other" 3) "wor1d" 127.0.0.1:6379> LINSERT mylist after world new (integer) 4 127.0.0.1:6379> LRANGE mylist 0 -1 1)"he11o" 2)"other" 3)"wor1d" 4)"new"
Set数据类型
注:set中不允许重复值(无序不重复)
127.0.0.1:6379> sadd myset "hello" #set集合中添加元素 (integer) 1 127.0.0.1:6379> sadd myset "wangping" (integer) 1 127.0.0.1:6379> sadd myset "lovewangping" (integer) 1 127.0.0.1:6379>SMEMBERS myset #查看指定set的所有值 1) "hello" 2) "lovewangping" 3) "wangping" 127.0.0.1:6379>SISMEMBER myset hello #判断某一个值是不是在set集含中! (integer) 1 127.0.0.1:6379>SISMEMBER myset world (integer) 0
随机抽取数据功能
127.0.0.1:6379>SMEMBERS myset 1)"lovewangping2" 2)"lovewangping" 3 "wangping" 127.0.0.1:6379>SRANDMEMBER myset #随机抽选出一个元素 "wangping" 127.0.0.1:6379>SRANDMEMBER myset "wangping" 127.0.0.1:6379>SRANDMEMBER myset "wangping" 127.0.0.1:6379>SRANDMEMBER myset "wangping" 127.0.0.1:6379>SRANDMEMBER myset 2 #随机抽选出两个元素 1) "lovewangping" 2) "lovewangping2" 127.0.0.1:6379>SRANDMEMBERmyset 2 1) "lovewangping" 2) "lovewangping2" 127.0.0.1:6379>SRANDMEMBER myset #随机抽选出一个元素 "lovewangping2"
Hash数据类型
127.0.0.1:6379> hset myhash field1 wangping #set一个具体 key-vlaue (integer) 1 127.0.0.1:6379> hget myhash fie1d1 #获取一个字段值 "wangping" 127.0.0.1:6379> hmset myhash field1 hello field2 world # set多个key-vlaue OK 127.0.0.1:6379> hmget myhash fie1d1 fie1d2 #获取多个字段值 1) "hello" 2) "world"" 127.0.0.1:6379> hgeta11 myhash #获取全部的数据, 1)"fie1d1" 2) "he11o" 3) "fie1d2" 4) "wor1d" 127.0.0.1:6379> hde1 myhash field1 #删除hash指定key字段!对应的value值也就消失了! (integer) 1 127.0.0.1:6379> hgeta11 myhash 1) "fie1d2"" 2) "wor1d" #只获得所有field #只获得所有value 127.0.0.1:6379> hkeys myhash #只获得所有field 1) "field2" 2) "fie1d1" 127.0.0.1:6379> hvals myhash # 只获得所有ivalue 1) "world" 2) "he11o" 127.0.0.1:6379> hlen myhash # 获取hash表的字段数量! (integer) 2 127.0.0.1:6379> HEXISTS myhash fieldl # 判断hash中指定字段是否存在! (integer) 1 127.0.0.1:6379> HEXISTS myhash field3 (integer) 0 127.0.0.1:6379> hset myhash fie1d3 5 #指定增量! (integer) 1 127.0.0.1:6379> HINCRBY myhash field3 1 (integer) 6 127.0.0.1:6379> HINCRBY myhash fie1d3 -1 (integer) 5 127.0.0.1:6379> hsetnx myhash fie1d4 he11o #如果不存在则可以设置 (integer) 1 127.0.0.1:6379> hsetnx myhash fie1d4 world #如果存在则不能设置 (integer) 0
hash变更的数据user name age,尤其是是用户信息之类的,经常变动的信息! hash更适合于对象的存储,String更加适合字符串存储!
Zset数据类型
在set的基础上,增加了一个值,set k1 v1 zset k1 score1 v1
127.0.0.1:6379> zadd salary 2500 xiaohong # 添加三个用户 (integer) 1 127.0.0.1:6379> zadd salary 5000 zhangsan (integer) 1 127.0.0.1:6379> zadd salary 500 kaungshen (integer) 1 127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf #显示所有用户,按照从小到大排序 1) "wangping" 2) "xiaohong" 3) "zhangsan" 127.0.0.1:6379> ZREVRANGE salary 0 -1 #从大到进行排序! 1) "zhangsan 2) "xiaohong" 3) "kaungshen 127. .0.0. 1:6379> ZRANGEBYSCORE salary -inf +inf withscores #显示全部的用户并且附带成绩 1) "wangping" 2) "500" 3) "xiaohong" 4) "2500" 5) "zhangsan" 6) "5000" 127.0. 0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores #显示工资小于2500员工的升序排序!| 1) "wangping" 2) "500" 3) "xiaohong” 4) "2500" #移除rem中的元素 127.0.0.1:6379> zrange salary 0 -1 1) "wangping" 2) "xiaohong" 3) "zhangsan" 127.0.0.1:6379> zrem salary xi aohong (integer) 1 127.0.0.1:6379> zrange salary 0 -1 1) "wangping" 2) "zhangsan" 127.0.0.1:6379> zcard salary #获取有序集合中的个数 (integer) 2 127.0.0.1:6379> zadd myset 1 hello (integer) 1 127.0.0.1:6379> zadd myset 2 world 3 kuangshen (integer) 2 127.0.0.1:6379> zcount myset 1 3 # 获取指定区间的成员数量! (integer) 3 127.0.0.1:6379> zcount myset 1 2 (integer) 2
三种特殊数据类型
Geospatial 地理位置
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。
Redis GEO 操作方法有:
- geoadd:添加地理位置的坐标。
- geopos:获取地理位置的坐标。
- geodist:计算两个位置之间的距离。
- georadius:根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
- georadiusbymember:根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合。
- geohash:返回一个或多个位置对象的 geohash 值。
geoadd
geoadd 用于存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中。
geoadd 语法格式如下:
GEOADD key longitude latitude member [longitude latitude member ...]
以下实例中 key 为 Sicily,Palermo 和 Catania 为位置名称 :
实例
redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" (integer) 2 redis> GEODIST Sicily Palermo Catania "166274.1516" redis> GEORADIUS Sicily 15 37 100 km 1) "Catania" redis> GEORADIUS Sicily 15 37 200 km 1) "Palermo" 2) "Catania" redis>
geopos
geopos 用于从给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil。
geopos 语法格式如下:
GEOPOS key member [member ...]
实例
redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" (integer) 2 redis> GEOPOS Sicily Palermo Catania NonExisting 1) 1) "13.36138933897018433" 2) "38.11555639549629859" 2) 1) "15.08726745843887329" 2) "37.50266842333162032" 3) (nil) redis>
geodist
geodist 用于返回两个给定位置之间的距离。
geodist 语法格式如下:
GEODIST key member1 member2 [m|km|ft|mi]
member1 member2 为两个地理位置。
最后一个距离单位参数说明:
- m :米,默认单位。
- km :千米。
- mi :英里。
- ft :英尺。
- > 计算 Palermo 与 Catania 之间的距离:
实例
redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" (integer) 2 redis> GEODIST Sicily Palermo Catania "166274.1516" redis> GEODIST Sicily Palermo Catania km "166.2742" redis> GEODIST Sicily Palermo Catania mi "103.3182" redis> GEODIST Sicily Foo Bar (nil) redis>
georadius、georadiusbymember
georadius 以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
georadiusbymember 和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 georadiusbymember 的中心点是由给定的位置元素决定的, 而不是使用经度和纬度来决定中心点。
georadius 与 georadiusbymember 语法格式如下:
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key] GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
参数说明:
- m :米,默认单位。
- km :千米。
- mi :英里。
- ft :英尺。
- WITHDIST: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。
- WITHCOORD: 将位置元素的经度和维度也一并返回。
- WITHHASH: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。
- COUNT 限定返回的记录数。
- ASC: 查找结果根据距离从近到远排序。
- DESC: 查找结果根据从远到近排序。
georadius 实例:
redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" (integer) 2 redis> GEORADIUS Sicily 15 37 200 km WITHDIST 1) 1) "Palermo" 2)"190.4424" 2) 1) "Catania" 2) "56.4413" redis> GEORADIUS Sicily 15 37 200 km WITHCOORD 1) 1) "Palermo" 2) 1) "13.36138933897018433" 2) "38.11555639549629859" 2) 1) "Catania" 2) 1) "15.08726745843887329" 2) "37.50266842333162032" redis> GEORADIUS Sicily 15 37 200 km WITHDIST WITHCOORD 1) 1) "Palermo" 2) "190.4424" 3) 1) "13.36138933897018433" 2) "38.11555639549629859" 2) 1) "Catania" 2) "56.4413" 3) 1) "15.08726745843887329" 2) "37.50266842333162032" redis>
georadiusbymember 实例:
redis> GEOADD Sicily 13.583333 37.316667 "Agrigento" (integer) 1 redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" (integer) 2 redis> GEORADIUSBYMEMBER Sicily Agrigento 100 km 1) "Agrigento" 2) "Palermo" redis>
geohash
-
Redis GEO 使用 geohash 来保存地理位置的坐标。
-
geohash 用于获取一个或多个位置元素的 geohash 值。
-
geohash 语法格式如下:
GEOHASH key member [member ...]
实例:
redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania" (integer) 2 redis> GEOHASH Sicily Palermo Catania 1) "sqc8b49rny0" 2) "sqdtr74hyu0" redis>
Hyperloglog
Bitmaps
事务
Redis事务本质: 一组命令的集合! 一个事务中的所有命令都会被序列化,在事务执行过程的中,会按照顺序执行!
一次性、顺序性、排他性! 执行一些列的命令!
-----队列 set set set 执行-----
Redis事务没有没有隔离级别的概念!
所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行! Exec
Redis单条命令式保证原子性的,但是事务不保证原子性!
redis的事务:
- 开启事务(multi )
- 命令入队( )
- 执行事务( exec)
127 .0.0.1:6379> multi #开启事务 0K #命令入队 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> get k2 QUEUED 127.0.0.1:6379> set k3 V3 QUEUED 127.0.0.1:6379> exec #执行事务 1) OK 2) OK 3) "v2" 4) Ok
放弃事务
127.0.0.1:6379> multi # 开启事务 OK 127.0.0.1:6379> set k1 vl QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> set k4 v4 QUEUED 127.0.0.1:6379> DISCARD # 取消事务 OK 127.0.0.1:6379> get k4 #事务队列中命令都不会被执行! (nil)
编译型异常(代码有问题!命令有错! ), 事务中所有的命令都不会被执行!
127.0.0.1:6379> mu1ti OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> set k3 v3 QUEUED 127.0.0.1:6379> getset k3 #错误的命令 Cerror) ERR wrong number of arguments for 'getset' command 127.0.0.1:6379> set k4 v4 QUEUED 127.0.0.1:6379> set k5 v5 QUEUED 127.0.0.1:6379> exec # 执行事务报错! Cerror) EXECABORT Transaction discarded because of previous errors 。 127.0.0.1:6379> get k5 # 所有的命令都不会被执行! (nil)
运行时异常( 1/0),如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常!
127.0.0.1:6379> set k1 "v1" 0K 127.0.0.1:6379> multi 0K 127.0.0. 1:6379> inkr k1 QUEUED I 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> set k3 v3 QUEUED 127.0.0.1:6379> get k3 QUEUED 127.0.0.1:6379> exec 1) (error) ERR value is not an integer or out of range #虽然第.条命令报错了, 但是依旧正常执行成功了! 2) OK 3) OK 4) "v3" 127.0.0.1:6379> get k2 "v2" 127.0.0.1:6379> get k3 "V3"
Redis实现乐观锁
监控 ------- Watch
悲观锁:
- 很悲观,认为什么时候都会出问题,无论做什么都会加锁! 影响性能
乐观锁
- 很乐观,认为什么时候都不会出问题,所以不会上锁!更新数据的时候去判断-下,在此期间是否有人修改过这个数据,
- 获取version
- 更新的时候比较version
Redis测监视测试
正常执行
127.0.0.1:6379> set money 100 127.0.0.1:6379> set out 0 127.0.0. 1:6379> watch money #监视money对象 127.0.0.1:6379> multi #事务正常结束,数据期间没有发生变动,这个时候就正常执行成功! 127.0.0.1:6379> DECRBY money 20 QUEUED 127.0.0.1:6379> INCRBY out 20 QUEUED 127.0.0. 1:6379> exec 1) (integer) 80 2) (integer) 20
测试多线程修改值,使用watch 可以当做redis的乐观锁操作!
场景:
127.0.0.1:6379> watch money #监视money OK 127.0.0. 1:6379> multi OK 127.0.0.1:6379> DECRBY money 10 QUEUED 127.0.0.1:6379> INCRBY out 10 QUEUED 127.0.0.1:6379> exec #执行之前,另外一个线程,修改了我们的值,这个时候, 就会导致事务执行失败! (nil)
#另一个线程插队,修该money值 127.0.0.1:6379> get money 80 127.0.0.1:6379> set money 1000 OK 127.0.0.1: 6379>
1.如果发现事务执行失败,就先解锁
2.获取最新的值,再次监视,select version
3.比对监视的值是否发生变化,如果没有变化,那么可以执行成功,如果执行失败重复1、2步骤
实现:
127.0.0.1: 6379> UNWATCH #解锁 OK 127.0.0.1:6379> WATCH money #加锁 0K 127.0.0.1:6379> multi OK 127.0.0.1:6379> DECRBY money 1 QUEUED 127.0.0.1:6379> incrBY money 1 QUEUED 127.0.0.1:6379> exec 1) (integer) 999 2) (integer) 1000
这样就实现了乐观锁!
Jedis操作Redis数据类型
是官方推荐的java连接开发工具,使用Java操作redis中间件!如果要使用java操作redis,那么一定要对jedis十分熟悉
导入相关依赖
redis.clients jedis 3.2.0 com. alibaba fastjson 1.2.62
编码测试:
- 连接数据库
- 操作数据库
- 断开连接
eg:
package com.stu; import redis.clients.jedis.Jedis; import java.util.concurrent.TimeUnit; public class TestString { public static void main(String[] args){ Jedis jedis = new Jedis("127.0.0.1",6379); jedis.flushDB(); //清空当前数据库 System.out.println("=========增加数据========="); //jedis.方法() 该方法就是操作redis数据库的所有命令 System.out.println(jedis.set("key1","value1")); System.out.println(jedis.set("k@y2" , "value2")); System.out.println(jedis.set("key3", "value3")); System.out.println("删除键key2:"+jedis.del("key2")); System.out.println("获取键key2:"+jedis.get("key2")); System.out.println("修改key1:"+jedis.set("key1", "valuelChanged")); System.out.println("获取key1的值: "+jedis.get("key1")); System.out.println("在key3后面加入值: "+jedis.append("key3", "End")); System.out.println("key3的值:"+jedis.get("key3")); System.out.println("增加多个键值对:"+jedis.mset("key01" , "value01" , "key02" , "value02" , "key03" , "value03")); System.out.println("获取多个键值对: "+jedis.mget("keye1", "key02" , "key03")); System.out.println("获取多个键值对: "+jedis.mget("key01" , "key02" , "key03","key04")); System.out.println("删除多个键值对: "+jedis.del("keye1" , "key02")); System.out.println("获取多个键值对: "+jedis.mget("keye1" , "key02" , "key03")); jedis.flushDB(); System.out.println("=========新增键值对防止覆盖原先值=========="); System.out.println(jedis.setnx("key1", "value1")); System.out.println(jedis.setnx("key2", "value2")); System. out . println(jedis.setnx("key2","va1ue2-new")); System. out. println(jedis.get("key1")); System.out. println(jedis.get("key2")); System. out. println("-=========新增键值对并设置有效时间_========="); System . out. println(jedis.setex("key3",2, "value3")); System. out . println(jedis. get("key3")); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e. printStackTrace(); } System.out.print1n(jedis.get("key3")); System.out.print1n("==========获取原值,更新为新值========="); System.out.println(jedis.getSet("key2", "key2GetSet")); System.out.println(jedis.get("key2")); System.out.println("获得key2的值的字串: "+jedis . getrange("key2",2,4)); } } //其他数据类型类比学习
Jedis操作事务
public class TestTX { public static void main(String[] args) { Jedis jedis = new Jedis( host: "127.0.0.1",port: 6379); jedis.flushDB(); ISONObject jsonbject = new JSONObject(); jsonObject. put("he1lo", "world"); jsonObject. put("name" , "kuangshen"); //开启事务 Transaction multi = jedis.multi(); String result = jsonObject.toJSONString(); // jedis.watch(result); //数据监控 try { multi.set("user1", result); multi.set("user2", result); int i = 1/0 ; //代码抛出异常事务,执行失败! multi.exec(); //执行事务! } catch (Exception e) { multi.discard(); //放弃事务 e.printStackTrace(); } finally { System.out.println(jedis.get("user1")); System.out.println(jedis.get("user2")); jedis.close(); //关闭连接 } } }
Redis 数据备份与恢复
数据备份
Redis SAVE 命令用于创建当前数据库的备份。
语法
redis Save 命令基本语法如下:
redis 127.0.0.1:6379> SAVE
实例
redis 127.0.0.1:6379> SAVE OK
该命令将在 redis 安装目录中创建dump.rdb文件。
恢复数据
如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可。获取 redis 目录可以使用 CONFIG 命令,如下所示:
redis 127.0.0.1:6379> CONFIG GET dir 1) "dir" 2) "/usr/local/redis/bin"
以上命令 CONFIG GET dir 输出的 redis 安装目录为 /usr/local/redis/bin。
Bgsave
创建 redis 备份文件也可以使用命令 BGSAVE,该命令在后台执行。
实例
127.0.0.1:6379> BGSAVE Background saving started
Redis安全
我们可以通过 redis 的配置文件设置密码参数,这样客户端连接到 redis 服务就需要密码验证,这样可以让你的 redis 服务更安全。
实例
我们可以通过以下命令查看是否设置了密码验证:
127.0.0.1:6379> CONFIG get requirepass 1) "requirepass" 2) ""
默认情况下 requirepass 参数是空的,这就意味着你无需通过密码验证就可以连接到 redis 服务。
你可以通过以下命令来修改该参数:
127.0.0.1:6379> CONFIG set requirepass "runoob" OK 127.0.0.1:6379> CONFIG get requirepass 1) "requirepass" 2) "runoob"
设置密码后,客户端连接 redis 服务就需要密码验证,否则无法执行命令。
语法
AUTH 命令基本语法格式如下:
127.0.0.1:6379> AUTH password
实例
127.0.0.1:6379> AUTH "runoob" OK 127.0.0.1:6379> SET mykey "Test value" OK 127.0.0.1:6379> GET mykey "Test value"
整合SpringBoot
- 很悲观,认为什么时候都会出问题,无论做什么都会加锁! 影响性能
-
- select: