Redis 基本概念
Redis是一个开源(BSD协议),内存数据结构存储,可以用作数据 库、缓存以及消息代理。
Redis是所谓的键值存储( key-value store),通常称为NoSQL数据库。
Redis不是普通的键值存储
,它实际上是一个data structures server,支持不同类型的值。
这意味着,在传统的键值存储中,您将字符串键与字符串值相关联,在Redis中,值不仅限于简单的字符串,还可以包含更复杂的数据结构
。
以下是Redis支持的所有数据结构的列表:
Binary-safe strings
Lists
: 根据插入顺序排序的字符串元素的集合。本质上是链表。
Sets
: 不同的,未排序的字符串元素的集合
Sorted sets
: 和Sets类似,但每个字符串元素都有与浮点数值相关联的score。我们可以根据score的排序进行取值操作.(例如,您可能会问:给我前10个,或者下10个)
Hashes
: 由与值相关联的字段组成的映射。字段和值都是字符串。这与Ruby或Python哈希非常相似。
Bit arrays (or simply bitmaps)
: 使用特殊命令可以像处理位数一样处理字符串值:您可以设置和清除各个位,将所有位设置为1,查找第一个或未设置位,等等。
HyperLogLogs
: 这是一种概率数据结构,用于估计集合的基数(cardinality).
Streams
: 追加一系列map-like
的项,以提供抽象日志数据类型 仅附加的类似于地图的条目集合,它们提供抽象日志数据类型(append-only collections of map-like entries that provide an abstract log data type)。它们在Redis Streams简介中有详细介绍。
Redis具有内置复制(built-in replication),Lua脚本,LRU驱逐(LRU eviction),事务和不同级别的磁盘持久性,并通过Redis哨兵(Redis Sentinel)提供高可用性,通过Redis Cluster自动分区。
> SET name 'cc'
OK
> GET name
"cc"
存取示例
键值存储提供的其他常见操作是DEL删除给定键和关联值,SET-if-not-exists(在Redis上称为SETNX)仅在键不存在时设置键,并且INCR以原子方式递增
存储在给定键上的数字:
常用的其他操作举例
DEL
可以删除给定的键和关键值
SETNX
即SET-if-not-exists
不存在的时候设置键
INCR
以原子的形式进行递增
> GET name
"cc"
> SET connections 10
OK
> INCR connections
(integer) 11
> INCR connections
(integer) 12
> INCR connections
(integer) 13
> SETNX connections 999
(integer) 0
> GET connections
"13"
INCR
的操作是原子的,它很有存在的必要。如下列操作,多个客户端同时进行不能保证线程安全。
x = GET count
x = x + 1
SET count x
过期设置
可以通过key来告知缓存失效的确切时间。这是通过EXPIRE
和TTL
命令完成的。
> SET resource:lock "Redis Demo"
OK
> GET resource:lock
"Redis Demo"
> TTL resource:lock
(integer) -1
> EXPIRE resource:lock 8
> TTL resource:lock
(integer) 8
> GET resource:lock
(nil)
> TTL resource:lock
(integer) -2
总结:
TTL <key>
- 返回值为 -1 表示永不过期
- 返回值为 -2 表示不存在。可能已经过期,也可能没存过
EXPIRE <key> <秒>
复杂的数据结构
Redis还支持几种更复杂的数据结构。
列表
我们要看的第一个是列表。列表是一系列有序值。
与列表交互的一些重要命令是
RPUSH
添加元素到数组后面(right)
LPUSH
添加元素到数组前面
LLEN
数组长度
LRANGE <A> <B>
相当于截取数组。取出[A,B]区间的数组。下标超过数组范围的不报错/不显示。 -1表示到数组最后一个。
LPOP
移除并返回第一个元素
RPOP
移除并返回最后一个元素
几个注意事项:
- 通过
Linked Lists
来实现。这意味着即使列表中有数百万个元素,在列表的头部或尾部添加新元素的时间也是常量。使用LPUSH
命令将新元素添加到具有十个元素的列表的头部的速度与将具有1000万个元素的元素添加到列表头部的速度相同。
- 缺点:根据下标访没有优势。操作需要的工作量与所访问元素的索引成比例。
- Redis列表使用
Linked Lists
列表实现,因为对于数据库系统,能够以非常快的方式将元素添加到很长的列表中是至关重要的。
- Redis允许我们设置
List的上限
,只保留最新的n个数据。旧的数据通过LTRIM
丢弃。
LTRIM
命令类似于LRANGE,但它不是显示指定范围的元素,而是将此范围设置为新列表值。超出范围内的元素移除掉。
您可以立即开始使用key存储列表,只要这个key现在没用于存储其他类型的数据。
> RPush friends 'alice'
(integer) 1
> RPush friends 'bill'
(integer) 2
> GET RPush
(nil)
> GET friends\
(nil)
> GET friends
(error) WRONGTYPE Operation against a key holding the wrong kind of value
> LLEN friends
(integer) 2
> LPUSH friends "Sam"
> LRange friends 0 2
1) "Sam"
2) "alice"
3) "bill"
> LRange friends 0 999
1) "Sam"
2) "alice"
3) "bill"
> LRange friends 0 -1
1) "Sam"
2) "alice"
3) "bill"
> LLEN friends
(integer) 3
> RPOP friends
"bill"
> LPOP friends
"Sam"
> LLEN friends
(integer) 1
> LLEN friends
(integer) 3
> RPOP friends
"bill"
> LPOP friends
"Sam"
> LLEN friends
(integer) 1
> rpush mylist 1 2 3 4 5
(integer) 5
> ltrim mylist 0 2
OK
> lrange mylist 0 -1
1) "1"
2) "2"
3) "3"
集合
集合类似于列表,但是没有特定的顺序,每个元素只能出现一次。
使用集合的一些重要命令是
SADD <k> <v1>
添加值
SREM <k> <v1>
移除特定的值
SISMEMBER <k> <v1>
是否包含特定值
SUNION
合并两个或多个集合并返回所有元素的列表
SMEMBERS
返回此集合中所有成员的列表
> SADD superpowers "flight"
(integer) 1
> SADD superpowers "x-ray vision"
(integer) 1
> SADD superpowers "reflexes"
(integer) 1
> SREM superpowers "reflexes"
(integer) 1
> SISMEMBER superpowers "flight"
(integer) 1
> SISMEMBER superpowers "reflexes"
(integer) 0
> SMEMBERS superpowers
1) "x-ray vision"
2) "flight"
sorted sets
集合是一种非常方便的数据类型,但由于它们未排序,因此无法解决许多问题。
这就是Redis 1.2引入Sorted Sets的原因。
有序集类似于常规集,但现在每个值都有一个关联的分数。此分数用于对集合中的元素进行排序。
> ZADD hackers 1940 "Alan Kay"
(integer) 1
> ZADD hackers 1940 "Alan Kay"
(integer) 0
> ZADD hackers 1906 "Grace Hopper"
> ZRANGE hackers 2 4
(empty list or set)
> ZRANGE hackers 1 0
(empty list or set)
> ZRANGE hackers 0 -1
1) "Grace Hopper"
2) "Alan Kay"
Hashes
哈希是字符串字段和字符串值之间的映射,因此它们是表示对象的完美数据类型(例如:具有多个字段的用户,如姓名,姓氏,年龄等).
HSET
设置单个值
HMSET
设置多个值
HGETALL
获取全部hash
数字类型的值的插入方法与字符串相同,数字类型的值提供了原子性的添加方法: HINCRBY
> HSET user:1000 name "John Smith"
(integer) 1
> HSET user:1000 email "john.smith@example.com"
(integer) 1
> HSET user:1000 password "s3cret"
(integer) 1
> HGETALL user:1000
1) "name"
2) "John Smith"
3) "email"
4) "john.smith@example.com"
5) "password"
6) "s3cret"
> HMSET user:1001 name "Mary Jones" password "hidden" email "mjones@example.com"
OK
> HGET user:1001 name
"Mary Jones"
> HSET user:1000 visits 10
(integer) 1
> HINCRBY user:1000 visits 1
(integer) 11
> HGETALL user:1000
1) "name"
2) "John Smith"
3) "email"
4) "john.smith@example.com"
5) "password"
6) "s3cret"
7) "visits"
8) "11"
> HDEL user:1000 visits
(integer) 1
> HGETALL user:1000
1) "name"
2) "John Smith"
3) "email"
4) "john.smith@example.com"
5) "password"
6) "s3cret"
> HINCRBY user:1000 visits 1
(integer) 1
> HGETALL user:1000
1) "name"
2) "John Smith"
3) "email"
4) "john.smith@example.com"
5) "password"
6) "s3cret"
7) "visits"
8) "1"
其他知识
Redis keys
Redis keys是二进制安全的,这意味着不管是字符串还是JPEG图片都可以作为key。
关于key的其他规则
- 不建议过长。过长更占用内存,而且根据key取值时做字符串比较也更耗时。即使当前的任务是匹配大值的存在(Even when the task at hand is to match the existence of a large value),散列它(例如使用SHA1)也是一个更好的主意,特别是从内存和带宽的角度来看。
- 不建议过端。例如
user:1000:followers
、u1000flw
后者不利于可读性。虽然短key消耗内存更少,但您的工作就是找到合适的平衡点。
- 坚持使用schema。例如,
object-type:id
是一个好主意,如user:1000
。.
或-
通常用于多字词字段,如comment:123:reply.to
或comment:123:reply-to
。
- 允许最大的key为512 MB
Redis Strings
Redis String类型是可以与Redis键关联的最简单的值类型。它是Memcached中唯一的数据类型,因此新手在Redis中使用它也很自然。
> SET mykey 123
OK
> GET mykey
"123"
> SET mykey 'zz'
OK
> GET mykey
"zz"
正如您所看到的,使用SET和GET命令是我们设置和检索字符串值的方式。
请注意,即使key与非字符串值相关联,SET也将替换已存在于key中的任何现有值(如果key已存在)
。所以SET执行一项分配。
SET
值可以是各种类型的string(包括二进制数据)
SET
值不能大于512 MB
SET
后面可以加参数nx xx
nx
: Only set the key if it does not already exist.
xx
: Only set the key if it already exist.
incr
原子方式递增
- 将字符串值解析为整数,将其递增1,最后将获取的值设置为新值。
- 其他类似的命令,如INCRBY,DECR和DECRBY。在内部,它始终是相同的命令,以稍微不同的方式起作用。
Strings相关的其他命令:
GETSET
设置旧值返回新值
MSET
、MGET
设置/取多个值
> set mykey newval nx
(nil)
> set mykey newval xx
OK
> set counter 100
OK
> incr counter
(integer) 101
> incr counter
(integer) 102
> incrby counter 50
(integer) 152
> set v1 100
OK
> getset v1 200
"100"
> getset v11 200
(nil)
> getset v11 2001
"200"
更改和查询key space
> set mykey hello
OK
> type mykey
string
> exists mykey
(integer) 1
> del mykey
(integer) 1
> exists mykey
(integer) 0
比较重要的概念
master/slave
主从(master/slave)
参考 https://lanjingling.github.io/2015/11/17/redis-mast-slaveof/
Redis中,用户可以使用slaveof命令或者slaveof配置项,让一个服务器去复制另一个服务器。进行复制中的主从服务器双方的数据库将保存相同的数据(一致性)。
- 主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库
- 数据库一般是只读的,并接受主数据库同步过来的数据
- 一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库
- 命令:
slaveof <主redis服务器>
- 使用
slaveof no one
进行清空
使用主从的两种方式(不同ip的访问请确保配好远程访问):
1)通过命令slaveof <主redis服务器>
127.0.0.1:6380> slaveof 127.0.0.2 6379
上面的命令表示:端口为6380的服务器 开始复制 端口为6379的服务器。 并且后续数据会同步到从节点。 从节点(slave)是可以只读的,不可以手动set值。
2)通过配置文件:
主服务器不用做任何修改,在从服务器的配置文件中作如下修改:
slaveof 192.168.0.100 6379 (映射到主服务器上)
如果master设置了验证密码,还需配置masterauth。楼主的master设置了验证密码为admin,所以配置masterauth admin。配置完之后启动slave的Redis服务,OK,主从配置完成。
原理
当从数据库启动时,会向主数据库发送sync命令,主数据库接收到sync后开始在后台报错快照rdb,在保存快照期间受到的命名缓存起来,当快照完成时,主数据库会将快照和缓存的命令一块发送给从。复制初始化结束。
之后,主每受到1个命令就同步发送给从。
当出现断开重连后,2.8之后的版本会将断线期间的命令传给重数据库。增量复制
主从复制是乐观复制,当客户端发送写执行给主,主执行完立即将结果返回客户端,并异步的把命令发送给从,从而不影响性能。也可以设置至少同步给多少个从主才可写。
无硬盘复制:如果硬盘效率低将会影响复制性能,2.8之后可以设置无硬盘复制,repl-diskless-sync yes
哨兵
哨兵(sentinel)用于自动化的系统监控和故障恢复功能。
sentinel是redis高可用的解决方案,sentinel系统可以监视一个或者多个redis master服务,以及这些master服务的所有从服务;当某个master服务下线时,自动将该master下的某个从服务升级为master服务替代已下线的master服务继续处理请求。一般建议sentinel采取奇数台,防止某一台sentinel无法连接到master导致误切换。
- 建议使用redis2.8以上
- Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自动切换
SUB/PUB
https://redis.io/topics/pubsub
订阅及发布
- 订阅
- 开启订阅,支持一个或多个:
SUBSCRIBE <channel1> <channel2>
- 模糊匹配:
PSUBSCRIBE f*
- 发布
- 发布信息到频道
PUBLISH <channel> <message>
使用两个终端调试
➜ ~ docker exec -it redis redis-cli
127.0.0.1:6379> SUBSCRIBE channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1
1) "message"
2) "channel1"
3) "hello"
1) "message"
2) "channel1"
3) "worl"
➜ ~ docker exec -it redis redis-cli
127.0.0.1:6379> PUBLISH channel1 'hello'
(integer) 1
127.0.0.1:6379> PUBLISH channel1 'worl'
(integer) 1
其他
数据持久化、cluster稍后进行补充
问题
远程访问
$ docker exec -it redis redis-cli -h 10.10.38.44 -p 6379
10.10.38.44:6379> set key 11
Error: Server closed the connection
(不限ip访问的设置)开启远程访问访问,修改配置文件redis.conf :
- 注释掉
# bind 127.0.0.1
- Redis默认不是以守护进程的方式运行
daemonize no
,这块不需要修改
- 保护模式关掉
protected-mode no
可以根据实际情况灵活配置。生产环境配置请慎重。
参考
连接docker redis超时问题
连接本地redis-server不报错,jedis连接docker返回报错。但是命令行是没问题的。先记录此问题。
2019-06-06 11:33:24.356 ERROR 37771 --- [ main] o.s.boot.SpringApplication : Application run failed
java.lang.IllegalStateException: Failed to execute ApplicationRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:807) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:794) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:324) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at com.example.jedisdemo.JedisDemoApplication.main(JedisDemoApplication.java:17) ~[classes/:na]
Caused by: redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out
at redis.clients.util.RedisInputStream.ensureFill(RedisInputStream.java:202) ~[jedis-2.9.3.jar:na]
at redis.clients.util.RedisInputStream.readByte(RedisInputStream.java:40) ~[jedis-2.9.3.jar:na]
at redis.clients.jedis.Protocol.process(Protocol.java:153) ~[jedis-2.9.3.jar:na]
at redis.clients.jedis.Protocol.read(Protocol.java:218) ~[jedis-2.9.3.jar:na]
at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:341) ~[jedis-2.9.3.jar:na]
at redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:240) ~[jedis-2.9.3.jar:na]
at redis.clients.jedis.Jedis.set(Jedis.java:122) ~[jedis-2.9.3.jar:na]
at com.example.jedisdemo.JedisDemoApplication.useJedis(JedisDemoApplication.java:63) ~[classes/:na]
at com.example.jedisdemo.JedisDemoApplication.run(JedisDemoApplication.java:22) ~[classes/:na]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:804) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
... 5 common frames omitted
Caused by: java.net.SocketTimeoutException: Read timed out
at java.base/java.net.SocketInputStream.socketRead0(Native Method) ~[na:na]
at java.base/java.net.SocketInputStream.socketRead(SocketInputStream.java:115) ~[na:na]
at java.base/java.net.SocketInputStream.read(SocketInputStream.java:168) ~[na:na]
at java.base/java.net.SocketInputStream.read(SocketInputStream.java:140) ~[na:na]
at java.base/java.net.SocketInputStream.read(SocketInputStream.java:126) ~[na:na]
at redis.clients.util.RedisInputStream.ensureFill(RedisInputStream.java:196) ~[jedis-2.9.3.jar:na]
... 14 common frames omitted
参考