NoSQL

概述

NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,泛指非关系型的数据库

NoSQL 不依赖业务逻辑方式存储,而以简单的 key-value 模式存储。因此大大的增加了数据库的扩展能力。

  • 不遵循SQL标准。

  • 不支持ACID。

  • 远超于SQL的性能。

特点

1、易扩展

NoSQL 数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。

数据之间无关系,这样就非常容易扩展,也无形之间,在架构的层面上带来了可扩展的能力。

2、大数据量高性能

NoSQL数据库都具有非常高的读写性能,尤其是在大数据量下,同样表现优秀。这得益于它的非关系性,数据库的结构简单。

一般 MySQL使用 Query Cache,每次表的更新Cache就失效,是一种大力度的Cache,在针对Web2.0 的 交互频繁应用,Cache性能不高,而NoSQL的Cache是记录级的,是一种细粒度的Cache,所以NoSQL 在这个层面上来说就要性能高很多了。

官方记录:Redis 一秒可以写8万次,读11万次!

3、多样灵活的数据模型

NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式,而在关系数据库里,增删字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是噩梦。

4、对比

传统的关系型数据库 RDBMS 
- 高度组织化结构化数据 
- 结构化查询语言(SQL) 
- 数据和关系都存储在单独的表中 
- 数据操纵语言,数据定义语言 
- 严格的一致性 - 基础事务

NoSQL 
- 代表着不仅仅是SQL 
- 没有声明性查询语言 
- 没有预定义的模式 
- 键值对存储,列存储,文档存储,图形数据库 
- 最终一致性,而非ACID属性 
- 非结构化和不可预知的数据
- CAP定理 
- 高性能,高可用性 和 可伸缩性

适用场景

  • 对数据高并发的读写

  • 海量数据的读写

  • 对数据高可扩展性的

不适用场景

  • 需要事务支持

  • 基于 sql 的结构化查询存储,处理复杂的关系,需要即席查询。

  • (用不着 sql 的和用了 sql 也不行的情况,请考虑用NoSql)

NoSQL四大分类

KV键值

  • 新浪:BerkeleyDB+redis

  • 美团:redis+tair

  • 阿里、百度:memcache+redis

文档型数据库(bson格式比较多)

  • CouchDB

  • MongoDB

    • MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。旨在为 WEB 应用提供可 扩展的高性能数据存储解决方案。

    • MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰
      富,最像关系数据库的。

列存储数据库:

  • Cassandra, HBase

  • 分布式文件系统

图关系数据库

  • 它不是放图形的,放的是关系比如:朋友圈社交网络、广告推荐系统

  • 社交网络,推荐系统等。专注于构建关系图谱

  • Neo4J, InfoGrid

1649813263339

Memcache、Redis、MongoDB区别

Memcache:

  • 很早出现的NoSql数据库

  • 数据都在内存中,一般不持久化

  • 支持简单的key-value模式,支持类型单一

  • 一般是作为缓存数据库辅助持久化的数据库

Redis:

  • 几乎覆盖了Memcached的绝大部分功能

  • 数据都在内存中,支持持久化,主要用作备份恢复

  • 除了支持简单的key-value模式,还支持多种数据结构的存储,比如 list、set、hash、zset等。

  • 一般是作为缓存数据库辅助持久化的数据库

MongoDB:

  • 高性能、开源、模式自由(schema free)的文档型数据库

  • 数据都在内存中, 如果内存不足,把不常用的数据保存到硬盘

  • 虽然是key-value模式,但是对value(尤其是json)提供了丰富的查询功能

  • 支持二进制数据及大型对象

  • 可以根据数据的特点替代RDBMS ,成为独立的数据库。或者配合RDBMS,存储特定的数据。

CAP + BASE

传统的ACID

关系型数据库遵循ACID规则,事务在英文中是transaction,和现实世界中的交易很类似,它有如下四个

特性:

  • A (Atomicity) 原子性

    原子性很容易理解,也就是说事务里的所有操作要么全部做完,要么都不做,事务成功的条件是事务

    里的所有操作都成功,只要有一个操作失败,整个事务就失败,需要回滚。

  • C (Consistency) 一致性

    事务前后数据的完整性必须保持一致。

  • I (Isolation) 隔离性

    所谓的独立性是指并发的事务之间不会互相影响,如果一个事务要访问的数据正在被另外一个事务修

    改,只要另外一个事务未提交,它所访问的数据就不受未提交事务的影响。

  • D (Durability) 持久性

​ 持久性是指一旦事务提交后,它所做的修改将会永久的保存在数据库上,即使出现宕机也不会丢失。

CAP(三进二)

  • C : Consistency(强一致性)

  • A : Availability(可用性)

  • P : Partition tolerance(分区容错性)

CAP理论就是说在分布式存储系统中,最多只能实现上面的两点

而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容错性是我们必须需要实现的。

所以我们只能在一致性和可用性之间进行权衡,没有NoSQL系统能同时保证这三点。
注意:分布式架构的时候必须做出取舍。

一致性和可用性之间取一个平衡。多余大多数web应用,其实并不需要强一致性。
因此牺牲C换取P,这是目前分布式数据库产品的方向 。

一致性与可用性的决择

对于web2.0网站来说,关系数据库的很多主要特性却往往无用武之地 。

数据库事务一致性需求

很多 web 实时系统并不要求严格的数据库事务,对读一致性的要求很低, 有些场合对写一致性要求并不高。允许实现最终一致性。

数据库的写实时性和读实时性需求

对关系数据库来说,插入一条数据之后立刻查询,是肯定可以读出来这条数据的,但是对于很多web应用来说,并不要求这么高的实时性,比方说发一条消息之 后,过几秒乃至十几秒之后,我的订阅者才看到这条动态是完全可以接受的。

对复杂的SQL查询,特别是多表关联查询的需求

任何大数据量的web系统,都非常忌讳多个大表的关联查询,以及复杂的数据分析类型的报表查询,特别是SNS类型的网站,从需求以及产品设计角度,就避免了这种情况的产生。往往更多的只是单表的主键查询,以及单表的简单条件分页查询,SQL的功能被极大的弱化了。

CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大类:

  • CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
  • CP - 满足一致性,分区容错性的系统,通常性能不是特别高。
  • AP - 满足可用性,分区容错性的系统,通常可能对一致性要求低一些。

1649814052502

BASE 理论

BASE 理论是由 eBay 架构师提出的。BASE 是对 CAP 中一致性和可用性权衡的结果,其来源于对大规模互联网分布式系统实践的总结,是基于 CAP 定律逐步演化而来。其核心思想是即使无法做到强一致性,但 每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。

BASE就是为了解决关系数据库强一致性引起的问题而引起的可用性降低而提出的解决方案。

BASE其实是下面三个术语的缩写:

  • 基本可用(Basically Available): 基本可用是指分布式系统在出现故障的时候,允许损失部分可用
    性,即保证核心可用。电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服
    务层也可能只提供降级服务。这就是损失部分可用性的体现。

  • 软状态(Soft State): 软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用
    性。分布式存储中一般一份数据至少会有三个副本,允许不同节点间副本同步的延时就是软状态的
    体现。MySQL Replication 的异步复制也是一种体现。

  • 最终一致性(Eventual Consistency): 最终一致性是指系统中的所有数据副本经过一定时间后,最
    终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。
    它的思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观。为什么这么说呢,缘由就在于大型系统往往由于地域分布和极高性能的要求,不可能采用分布式事务来完成这些指标,要想获得这些指标,我们必须采用另外一种方式来完成,这里BASE就是解决这个问题的办法!

解释:

1、分布式:不同的多台服务器上面部署不同的服务模块(工程),他们之间通过 Rpc 通信和调用,对外提供服务和组内协作。

2、集群:不同的多台服务器上面部署相同的服务模块,通过分布式调度软件进行统一的调度,对外提供服务和访问。

Redis入门

概述

Redis:REmote DIctionary Server(远程字典服务器)

是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(Key/Value)分布式内存数据库,基于内存运行,并支持持久化的NoSQL数据库,是当前最热门的NoSQL数据库之一,也被人们称为 数据结构服务器

Redis与其他 key-value 缓存产品有以下三个特点

  • Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使
    用。

  • Redis不仅仅支持简单的 key-value 类型的数据,同时还提供list、set、zset、hash等数据结构的存储。

  • Redis支持数据的备份,即master-slave模式的数据备份。

Windows

redis-server.exe

redis-server.exe redis.windows.conf

# 用密码登陆认证
auth 123456

redis-cli.exe -h 127.0.0.1 -p 6379

Linux

位置:

1637408104799

自定义配置文件:

1637408190701

配置自动启动:

1637408350317

启动并连接

使用指定的配置文件

1637408561192

查看进程

1637408724349

关闭服务

1637408819956

性能测试

redis-benchmark

# 测试 100个并发,100000条请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

1637413501625

常用命令

默认有16 个数据库

1637413756836

切换数据库,查看db大小

1637412740999

# 查看当前数据库所有的键
keys *

# 清空当前数据库
flushdb

# 清空所有数据库
flushall
# 检查给定 key 是否存在
EXISTS key

# 将当前数据库的 key 移动到给定的数据库 db 当中
move key db

# 设置key的过期时间
expire key seconds 

# 查看当前key的剩余时间
ttl key

# 查看key类型
TYPE key

五大基本数据类型

String

# 追加字符串(v2,加不加引号一样),如果key不存在,相当于set key1 v2
APPEND key1 v2
# 获取长度
STRLEN key1

# 自增1
incr num
# 自减1
decr num
# 设置步长
incrby/decrby num step

字符串截取

# 截取区间[0,5]
GETRANGE key1 0 5

# 截取所有
GETRANGE key1 0 -1

# 替换指定位置开始的字符串 (从下标1开始替换成xyz)
SETRANGE key2 1 xyz

**setex(set with expire)/setnx(set if not exist) **

#设置过期时间 (keys的值为hello,30s后过期)
setex key3 seconds "hello"

# 不存在才设置,存在则失败
setnx mykey "redis"

mset/mget

127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3	# 同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k3"
3) "k2"
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> mget k1 k2 k3			# 同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4		# 是一个原子性操作,要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> get k4
(nil)

对象

# key巧妙设计:	user:{id}:{field} 
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379> mget user:1
1) (nil)
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "2"

getset

# 先获取原来的值,再设置新的值
127.0.0.1:6379> GETSET db redis
(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"

List

############################################################	lpush rpush lrange
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	#查看所有
1) "three"
2) "two"
3) "one"

127.0.0.1:6379> RPUSH list yy  # 插入到列表尾部(右边)
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "yy"

############################################################	lpop rpop	
127.0.0.1:6379> LRANGE list 0 -1
1) "cc"
2) "bb"
3) "aa"
127.0.0.1:6379> LPOP list	# 移除左边第一个,可跟一个数字,代表要移除的数量
"cc"
127.0.0.1:6379> RPOP list	# 移除右边第一个,可跟一个数字,代表要移除的数量
"aa"
127.0.0.1:6379> LRANGE list 0 -1
1) "bb"

############################################################	lindex llen
127.0.0.1:6379> LINDEX list 1	# 获取指定下标的元素,下标从0开始
(nil)
127.0.0.1:6379> LINDEX list 0
"bb"
127.0.0.1:6379> LLEN list	# 返回list长度
(integer) 1

############################################################	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	# 移除list中指定个数的value
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> LREM list 2 three
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "two"

############################################################	ltrim
127.0.0.1:6379> RPUSH list "hello"
(integer) 1
127.0.0.1:6379> RPUSH list "hello2"
(integer) 2
127.0.0.1:6379> RPUSH list "hello3"
(integer) 3
127.0.0.1:6379> RPUSH list "hello4"
(integer) 4
127.0.0.1:6379> LTRIM list 1 2	# 通过下标1截取指定的长度2,会改变list
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "hello2"
2) "hello3"

############################################################	rpoplpush
127.0.0.1:6379> RPUSH mylist hello
(integer) 1
127.0.0.1:6379> RPUSH mylist hello1
(integer) 2
127.0.0.1:6379> RPUSH mylist hello2
(integer) 3
127.0.0.1:6379> RPOPLPUSH mylist mylist2	# 移除mylist最后一个放到mylist2头部
"hello2"
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "hello1"
127.0.0.1:6379> LRANGE mylist2 0 -1
1) "hello2"

############################################################	lset
127.0.0.1:6379> EXISTS list
(integer) 0
127.0.0.1:6379> LSET list 0 item	#更新list中指定下标的值,key不存在则报错
(error) ERR no such key
127.0.0.1:6379> LPUSH list v
(integer) 1
127.0.0.1:6379> LRANGE list 0 0
1) "v"
127.0.0.1:6379> LSET list 0 item
OK
127.0.0.1:6379> LRANGE list 0 0
1) "item"
127.0.0.1:6379> LSET list 1 other	# 越界
(error) ERR index out of range

############################################################	linsert 
127.0.0.1:6379> RPUSH list hello
(integer) 1
127.0.0.1:6379> RPUSH list world
(integer) 2
127.0.0.1:6379> LINSERT list before world other # 将给定值other插入到某个元素world的前面
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> LINSERT list after world new # 将给定值new插入到某个元素world的后面
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"

小结

  • 实际上是一个链表
  • 如果移除了所有值,空链表,则代表key不存在
  • 在两边操作(插入,移除,修改)效率高
  • 可以当成队列(Lpush Rpop),栈(Lpush,Lpop)

Set

####################################################### 	sadd smembers sismember	
127.0.0.1:6379> SADD myset hello	# myset中添加元素
(integer) 1
127.0.0.1:6379> SADD myset jiutian
(integer) 1
127.0.0.1:6379> SMEMBERS myset	# 查看myset所有元素
1) "hello"
2) "jiutian"
127.0.0.1:6379> SISMEMBER myset hello	# 判断某个元素是否在myset中
(integer) 1
127.0.0.1:6379> SISMEMBER myset world
(integer) 0

############################################################	scard
127.0.0.1:6379> SCARD myset	# 获取myset中元素个数
(integer) 2
127.0.0.1:6379> SADD myset jiutian
(integer) 0
127.0.0.1:6379> SADD myset jiutian1
(integer) 1
127.0.0.1:6379> SCARD myset
(integer) 3

############################################################	srem
127.0.0.1:6379> SREM myset hello	#移除指定元素
(integer) 1
127.0.0.1:6379> SCARD myset
(integer) 2
127.0.0.1:6379> SMEMBERS myset
1) "jiutian"
2) "jiutian1"

############################################################	srandmember
127.0.0.1:6379> SMEMBERS myset
1) "jiutian"
2) "jiutian1"
127.0.0.1:6379> SRANDMEMBER myset	# 随机抽选出一个元素
"jiutian1"
127.0.0.1:6379> SRANDMEMBER myset
"jiutian1"
127.0.0.1:6379> SRANDMEMBER myset 2	# 随机抽选出2个元素
1) "jiutian"
2) "jiutian1"
127.0.0.1:6379> SRANDMEMBER myset
"jiutian"

############################################################	spop
127.0.0.1:6379> SMEMBERS myset	
1) "jiutian"
2) "jiutian1"
127.0.0.1:6379> SPOP myset	# 随机移除元素
"jiutian"
127.0.0.1:6379> SMEMBERS myset
1) "jiutian1"

############################################################	smove
127.0.0.1:6379> SADD myset hello
(integer) 1
127.0.0.1:6379> SADD myset world
(integer) 1
127.0.0.1:6379> SADD myset jiutian
(integer) 1
127.0.0.1:6379> SADD myset2 aaa
(integer) 1
127.0.0.1:6379> SMOVE myset myset2 jiutian	# 将一个指定的元素移动到另一个集合
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "world"
127.0.0.1:6379> SMEMBERS myset2
1) "jiutian"
2) "aaa"

############################################################	sdiff sinter sunion
127.0.0.1:6379> SADD key1 a
(integer) 1
127.0.0.1:6379> SADD key1 b
(integer) 1
127.0.0.1:6379> SADD key1 c
(integer) 1
127.0.0.1:6379> SADD key2 a
(integer) 1
127.0.0.1:6379> SADD key2 c
(integer) 1
127.0.0.1:6379> SADD key2 d
(integer) 1
127.0.0.1:6379> SDIFF key1 key2			# 差集 (key1 - key2)
1) "b"
127.0.0.1:6379> SINTER key1 key2		# 交集	
1) "a"
2) "c"
127.0.0.1:6379> SUNION key1 key2		# 并集
1) "a"
2) "b"
3) "d"
4) "c"

Hash(散列表)

kv模式不变,但V是一个键值对

##############################################	hset hget hmset hmget hgetall hdel
127.0.0.1:6379> HSET myhash field1 jiutian	# set一个key-value
(integer) 1
127.0.0.1:6379> hget myhash field1	# 获取一个字段值
"jiutian" 
127.0.0.1:6379> HMSET myhash field1 hello field2 world	# set多个key-value
OK
127.0.0.1:6379> HMGET myhash field1 field2	# 获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> HGETALL myhash	# 获取全部数据,键-值
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> HDEL myhash field1	# 删除myhash中指定的字段field1,对应的值也删除了
(integer) 1
127.0.0.1:6379> HGETALL myhash
1) "field2"
2) "world"

############################################################	hlen hexists
127.0.0.1:6379> HMSET myhash field1 hello field2 world
OK
127.0.0.1:6379> HGETALL myhash
1) "field2"
2) "world"
3) "field1"
4) "hello"
127.0.0.1:6379> HLEN myhash	# 获取hash表的字段数量
(integer) 2
127.0.0.1:6379> HEXISTS myhash field1	# 判断指定字段是否存在
(integer) 1
127.0.0.1:6379> HEXISTS myhash field3
(integer) 0

############################################################	hkeys hvals
127.0.0.1:6379> hkeys myhash	# 只获取所有的字段
1) "field2"
2) "field1"
127.0.0.1:6379> HVALS myhash	# 只获取所有的值
1) "world"
2) "hello"

############################################################	hincrby hsetnx
127.0.0.1:6379> HSET myhash field3 5	
(integer) 1
127.0.0.1:6379> HINCRBY myhash field3 2	# 自增2
(integer) 7
127.0.0.1:6379> HINCRBY myhash field3 -1	# 自减1 
(integer) 6
127.0.0.1:6379> HSETNX myhash field4 hello	# 不存在才设置
(integer) 1
127.0.0.1:6379> HSETNX myhash field4 world
(integer) 0

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

存储部分变更的数据,如用户信息等。

Zset(有序集合)

在set基础上,加一个score值

############################################################	zadd zrange
127.0.0.1:6379> ZADD myset 1 one	# 添加1个值
(integer) 1
127.0.0.1:6379> ZADD myset 2 two 3 three	# 添加多个值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1	
1) "one"
2) "two"
3) "three"

#####################################################	zrangebyscore zrevrange
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 jiutian	# 加进去之后会自动升序
(integer) 1
# +inf:正无穷,-inf:负无穷。
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 指定分数区,升序,只显示元素
1) "jiutian"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores	# 升序,带上score
1) "jiutian"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores # 显示2500以下,升序
1) "jiutian"
2) "500"
3) "xiaohong"
4) "2500"
127.0.0.1:6379> ZREVRANGE salary 0 -1 withscores	# 降序
1) "zhangsan"
2) "5000"
3) "xiaohong"
4) "2500"
5) "jiutian"
6) "500"
############################################################	zrem
127.0.0.1:6379> ZRANGE salary 0 -1
1) "jiutian"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZREM salary xiaohong	# 移除指定元素
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1
1) "jiutian"
2) "zhangsan"

############################################################	zcard zcount
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 jiutian
(integer) 2
127.0.0.1:6379> ZCOUNT myset 1 2	# 统计指定区间元素个数
(integer) 2
127.0.0.1:6379> ZCOUNT myset 1 3
(integer) 3

############################################################ zrank zrevrank
127.0.0.1:6379> ZADD salary 1000 aa
(integer) 1
127.0.0.1:6379> ZADD salary 2000 bb 500 cc
(integer) 2
127.0.0.1:6379> ZRANGE salary 0 -1 withscores
1) "cc"
2) "500"
3) "aa"
4) "1000"
5) "bb"
6) "2000"
127.0.0.1:6379> ZRANK salary aa	# 显示指定元素的位置(索引)(升序)
(integer) 1
127.0.0.1:6379> ZRANK salary cc
(integer) 0
127.0.0.1:6379> ZREVRANK salary cc	# 显示指定元素的位置(索引)(降序)
(integer) 2

三种特殊数据类型

geospatial(地理位置)

geoadd

# 语法 geoadd key longitude latitude member ...
# 将给定的空间元素(纬度、经度、名字)添加到指定的键里面。

# 有效的经度介于-180-180度之间,有效的纬度介于-85.05112878 度至 85.05112878 度之间。当用户尝试输入一个超出范围的经度或者纬度时,geoadd命令将返回一个错误。
127.0.0.1:6379> GEOADD china:city 116.405285 39.904989 beijing 121.472644 31.231706 shanghai
(integer) 2
127.0.0.1:6379> GEOADD china:city 114.085947 22.547 shenzhen 120.153576 30.287459 hangzhou
(integer) 2
127.0.0.1:6379> GEOADD china:city 115.892151 28.676493 jiangxi
(integer) 1

geopos

127.0.0.1:6379> GEOPOS china:city beijing shanghai	# 获取指定城市的经度和纬度
1) 1) "116.40528291463851929"
   2) "39.9049884229125027"
2) 1) "121.47264629602432251"
   2) "31.23170490709807012"

geodist

两地的距离

单位:

  • m表示单位为米
  • km表示单位为千米
  • mi表示单位为英里
  • ft表示单位为英尺
127.0.0.1:6379> GEODIST china:city beijing shanghai km	# 查看北京和上海的直线距离
"1067.5980"
127.0.0.1:6379> GEODIST china:city beijing jiangxi km	# 查看北京和江西的直线距离
"1249.7870"

georadius

根据对应的key,寻找指定中心(经纬度)周围(方圆)的目标

# 以110,30这个经纬度为中心寻找方圆1000km的城市
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km 
1) "shenzhen"
2) "jiangxi"
3) "hangzhou"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km
(empty array)
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withcoord
1) 1) "shenzhen"
   2) 1) "114.08594459295272827"
      2) "22.54699993773966327"
2) 1) "jiangxi"
   2) 1) "115.89214950799942017"
      2) "28.67649306190701708"
3) 1) "hangzhou"
   2) 1) "120.15357345342636108"
      2) "30.28745790721532671"
# withcoord:显示目标定位信息(经纬度),withdist:显示直线距离 ,count:限定数量
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withcoord withdist count 2
1) 1) "jiangxi"
   2) "589.8852"
   3) 1) "115.89214950799942017"
      2) "28.67649306190701708"
2) 1) "shenzhen"
   2) "923.4929"
   3) 1) "114.08594459295272827"
      2) "22.54699993773966327"

georadiusbymember

# 找出位于指定元素周围的元素(包括其自己)
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 400 km
1) "hangzhou"
2) "shanghai"

geohash

# 将二维的经纬度转换为一维的字符串
127.0.0.1:6379> GEOHASH china:city beijing shanghai
1) "wx4g0b7xrt0"
2) "wtw3sjt9vg0"

GEO底层实现原理是 Zset,所以可以使用 Zset的命令来操作。

127.0.0.1:6379> zrange china:city 0 -1	# 查看地图中全部元素
1) "shenzhen"
2) "jiangxi"
3) "hangzhou"
4) "shanghai"
5) "beijing"
127.0.0.1:6379> ZREM china:city beijing	# 移除指定元素
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "shenzhen"
2) "jiangxi"
3) "hangzhou"
4) "shanghai"

Hyperloglog

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常大时,计算基数所需的空间总是固定的、并且是很小的。HyperLogLog是一种算法,它提供了不精确的去重计数方案。尽管它大概有0.81%的错误率,但对于统计UV(网站流量统计中的“UV”所指的是访问网站的一个客户端)这种不需要很精确的数据是可以忽略不计的。

127.0.0.1:6379> PFADD mykey a b c d e f g h i j	# 创建第一组元素
(integer) 1
127.0.0.1:6379> PFCOUNT mykey	# 统计基数数量
(integer) 10
127.0.0.1:6379> PFADD mykey2 e h t e f g h k o b	# 创建第二组元素		
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2
(integer) 7
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2	# 合并两组 ,并集
OK
127.0.0.1:6379> PFCOUNT mykey3
(integer) 12

Bitmap

BitMap 就是通过一个 bit 位来表示某个元素对应的值或者状态, 其中的 key 就是对应元素本身,实际上底层也是通过对字符串的操作来实现。

位示图 只有 0 或 1

setbit key offet value
127.0.0.1:6379> SETBIT sign 0 1	# 设置key为sign 指定位置0的值为1
(integer) 0
127.0.0.1:6379> SETBIT sign 1 0
(integer) 0
127.0.0.1:6379> SETBIT sign 2 0
(integer) 0
127.0.0.1:6379> SETBIT sign 3 1
(integer) 0
127.0.0.1:6379> SETBIT sign 4 1
(integer) 0
127.0.0.1:6379> SETBIT sign 5 0
(integer) 0
127.0.0.1:6379> SETBIT sign 6 0
(integer) 0
127.0.0.1:6379> GETBIT sign 3	# 获取指定位置的值
(integer) 1
127.0.0.1:6379> GETBIT sign 6
(integer) 0
127.0.0.1:6379> BITCOUNT sign	# 统计 1 的数量
(integer) 3

事务

概念

Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的 。

顺序:

  • 开启事务(multi)
  • 命令入队(.......)
  • 执行事务(exec)

Redis 事务三特性

  • 单独的隔离操作

事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

  • 没有隔离级别的概念

队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行。

  • 不保证原子性

事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。

实践

127.0.0.1:6379> MULTI	# 开启事务
OK
# 命令入队
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED	
127.0.0.1:6379(TX)> EXEC	# 执行事务
1) OK
2) OK
3) "v2"
4) OK

放弃事务

127.0.0.1:6379> MULTI	# 开启事务
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> DISCARD	# 放弃事务,队列中的命令都不执行
OK
127.0.0.1:6379> EXEC
(error) ERR EXEC without MULTI
127.0.0.1:6379> get k4
(nil)

若在事务队列中存在命令错误(类似于java编译型错误),则执行EXEC命令时,所有命令都不会执行

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> getset k2	# 入队出错,命令错误
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> exec	# 执行事务报错,所有命令都不会执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k4
(nil)

若在事务队列中存在语法性错误(类似于java的1/0的运行时异常),则执行EXEC命令时,其他正确命令会被执行,错误命令抛出异常。

127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> incr k1	# 入队不出错,执行的时候会报错
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
127.0.0.1:6379(TX)> 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"

Watch实现乐观锁

watch 监测

正常情况

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> WATCH money	# 监视 money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 20
QUEUED
127.0.0.1:6379(TX)> INCRBY out 20
QUEUED
127.0.0.1:6379(TX)> EXEC
1) (integer) 80
2) (integer) 20

多线程

# 用 watch 当作乐观锁
127.0.0.1:6379> WATCH money	# 监视 money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 10
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10
QUEUED
## 在事务提交执行之前另一个线程作了修改
127.0.0.1:6379(TX)> exec	# 执行之前,另一个线程作了修改,则会导致事务执行失败
(nil)	
#  UNWATCH 取消 WATCH 命令对所有 key 的监视
127.0.0.1:6379> UNWATCH		# 可用 unwatch 先放弃监视(解锁),并再次监视
OK
127.0.0.1:6379> WATCH money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 10
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10
QUEUED
127.0.0.1:6379(TX)> EXEC
1) (integer) 80
2) (integer) 40

说明:

一但执行 EXEC 开启事务的执行后,无论事务是否执行成功, WATCH 对变量的监控都将被取消。故当事务执行失败后,需重新执行WATCH命令对变量进行监控,并开启新的事务进行操作。

小结

watch 指令类似于乐观锁,在事务提交时,如果watch监控的多个KEY中任何KEY的值已经被其他客户端更改,则使用EXEC执行事务时,事务队列将不会被执行,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。

Jedis连接

导包

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
</dependency>

测试

public class TestPing {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        //验证密码,如果没有设置密码这段代码省略
        //jedis.auth("123456");
        System.out.println(jedis.isConnected()); //false
        System.out.println(jedis.ping());
        System.out.println(jedis.isConnected()); //true
        jedis.connect(); //连接
        System.out.println(jedis.isConnected()); //true
        jedis.disconnect(); //断开连接
        System.out.println(jedis.flushAll());//清空所有的key
        jedis.close();  //关闭连接
    }
}

Key

public class TestKey {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        System.out.println("清空数据:" + jedis.flushDB());
        System.out.println("判断某个键是否存在:" + jedis.exists("username"));
        System.out.println("新增<'username','jiutian'>的键值 对:" + jedis.set("username", "jiutian"));
        System.out.println("新增<'password','password'>的键值 对:" + jedis.set("password", "password"));
        System.out.print("系统中所有的键如下:");
        Set<String> keys = jedis.keys("*");
        System.out.println(keys);

        System.out.println("删除键password:" + jedis.del("password"));
        System.out.println("判断键password是否存 在:" + jedis.exists("password"));
        System.out.println("查看键username所存储的值的类 型:" + jedis.type("username"));
        System.out.println("随机返回key空间的一个:" + jedis.randomKey());
        System.out.println("重命名key:" + jedis.rename("username", "name"));
        System.out.println("取出改后的name:" + jedis.get("name"));
        System.out.println("按索引查询:" + jedis.select(0));
        System.out.println("删除当前选择数据库中的所有key:" + jedis.flushDB());
        System.out.println("返回当前数据库中key的数目:" + jedis.dbSize());
        System.out.println("删除所有数据库中的所有key:" + jedis.flushAll());
    }
}

String

public class TestString {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        System.out.println("===========增加数据===========");
        System.out.println(jedis.set("key1", "value1"));
        System.out.println(jedis.set("key2", "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", "value1Changed"));
        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("key01", "key02", "key03"));
        System.out.println("获取多个键值 对:" + jedis.mget("key01", "key02", "key03", "key04"));
        System.out.println("删除多个键值对:" + jedis.del("key01", "key02"));
        System.out.println("获取多个键值 对:" + jedis.mget("key01", "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", "value2-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.println(jedis.get("key3"));
        System.out.println("===========获取原值,更新为新值==========");
        System.out.println(jedis.getSet("key2", "key2GetSet"));
        System.out.println(jedis.get("key2"));
        System.out.println("获得key2的值的字串:" + jedis.getrange("key2", 2, 4));
    }
}

List

public class TestList {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        System.out.println("===========添加一个list===========");
        jedis.lpush("collections", "ArrayList", "Vector", "Stack", "HashMap", "WeakHashMap", "LinkedHashMap");
        jedis.lpush("collections", "HashSet");
        jedis.lpush("collections", "TreeSet");
        jedis.lpush("collections", "TreeMap");
        System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));//-1代表倒数第一个元素,-2代表倒数第二个元素,end为-1表示查询全部
        System.out.println("collections区间0-3的元 素:" + jedis.lrange("collections", 0, 3));
        System.out.println("===============================");
        // 删除列表指定的值 ,第二个参数为删除的个数(有重复时),后add进去的值先被删,类似于出栈
        System.out.println("删除指定元素个数:" + jedis.lrem("collections", 2, "HashMap"));
        System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
        System.out.println("删除下表0-3区间之外的元素:" + jedis.ltrim("collections", 0, 3));
        System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
        System.out.println("collections列表出栈(左 端):" + jedis.lpop("collections"));
        System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
        System.out.println("collections添加元素,从列表右端,与lpush相对 应:" + jedis.rpush("collections", "EnumMap"));
        System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
        System.out.println("collections列表出栈(右 端):" + jedis.rpop("collections"));
        System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
        System.out.println("修改collections指定下标1的内 容:" + jedis.lset("collections", 1, "LinkedArrayList"));
        System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
        System.out.println("===============================");
        System.out.println("collections的长度:" + jedis.llen("collections"));
        System.out.println("获取collections下标为2的元 素:" + jedis.lindex("collections", 2));
        System.out.println("===============================");
        jedis.lpush("sortedList", "3", "6", "2", "0", "7", "4");
        System.out.println("sortedList排序前:" + jedis.lrange("sortedList", 0, -1));
        System.out.println(jedis.sort("sortedList"));
        System.out.println("sortedList排序后:" + jedis.lrange("sortedList", 0, -1));
    }
}

Set

public class TestSet {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        System.out.println("============向集合中添加元素(不重复) ============");
        System.out.println(jedis.sadd("eleSet", "e1", "e2", "e4", "e3", "e0", "e8", "e7", "e5"));
        System.out.println(jedis.sadd("eleSet", "e6"));
        System.out.println(jedis.sadd("eleSet", "e6"));
        System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
        System.out.println("删除一个元素e0:" + jedis.srem("eleSet", "e0"));
        System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
        System.out.println("删除两个元素e7和e6:" + jedis.srem("eleSet", "e7", "e6"));
        System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
        System.out.println("随机的移除集合中的一个元素:" + jedis.spop("eleSet"));
        System.out.println("随机的移除集合中的一个元素:" + jedis.spop("eleSet"));
        System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
        System.out.println("eleSet中包含元素的个数:" + jedis.scard("eleSet"));
        System.out.println("e3是否在eleSet中:" + jedis.sismember("eleSet", "e3"));
        System.out.println("e1是否在eleSet中:" + jedis.sismember("eleSet", "e1"));
        System.out.println("e1是否在eleSet中:" + jedis.sismember("eleSet", "e5"));
        System.out.println("=================================");
        System.out.println(jedis.sadd("eleSet1", "e1", "e2", "e4", "e3", "e0", "e8", "e7", "e5"));
        System.out.println(jedis.sadd("eleSet2", "e1", "e2", "e4", "e3", "e0", "e8"));
        System.out.println("将eleSet1中删除e1并存入eleSet3 中:" + jedis.smove("eleSet1", "eleSet3", "e1"));
        //移到集合元素
        System.out.println("将eleSet1中删除e2并存入eleSet3 中:" + jedis.smove("eleSet1", "eleSet3", "e2"));
        System.out.println("eleSet1中的元素:" + jedis.smembers("eleSet1"));
        System.out.println("eleSet3中的元素:" + jedis.smembers("eleSet3"));
        System.out.println("============集合运算=================");
        System.out.println("eleSet1中的元素:" + jedis.smembers("eleSet1"));
        System.out.println("eleSet2中的元素:" + jedis.smembers("eleSet2"));
        System.out.println("eleSet1和eleSet2的交 集:" + jedis.sinter("eleSet1", "eleSet2"));
        System.out.println("eleSet1和eleSet2的并 集:" + jedis.sunion("eleSet1", "eleSet2"));
        System.out.println("eleSet1和eleSet2的差 集:" + jedis.sdiff("eleSet1", "eleSet2"));//eleSet1中有,eleSet2中没有
        jedis.sinterstore("eleSet4", "eleSet1", "eleSet2");//求交集并将交集保存到 dstkey的集合
        System.out.println("eleSet4中的元素:" + jedis.smembers("eleSet4"));
    }
}

Hash

public class TestHash {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        Map<String, String> map = new HashMap<>();
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");
        map.put("key4", "value4");
        //添加名称为hash(key)的hash元素
        jedis.hmset("hash",map);
        //向名称为hash的hash中添加key为key5,value为value5元素
        jedis.hset("hash", "key5", "value5");
        System.out.println("散列hash的所有键值对 为:"+jedis.hgetAll("hash"));//return Map<String,String>
        System.out.println("散列hash的所有键为:"+jedis.hkeys("hash"));//return Set<String>
        System.out.println("散列hash的所有值为:"+jedis.hvals("hash"));//return List<String>
        System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加 key6:"+jedis.hincrBy("hash", "key6", 6));
        System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
        System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加 key6:"+jedis.hincrBy("hash", "key6", 3));
        System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
        System.out.println("删除一个或者多个键值对:"+jedis.hdel("hash", "key2"));
        System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
        System.out.println("散列hash中键值对的个数:"+jedis.hlen("hash"));
        System.out.println("判断hash中是否存在 key2:"+jedis.hexists("hash","key2"));
        System.out.println("判断hash中是否存在 key3:"+jedis.hexists("hash","key3"));
        System.out.println("获取hash中的值:"+jedis.hmget("hash","key3"));
        System.out.println("获取hash中的 值:"+jedis.hmget("hash","key3","key4"));
    }
}

事务

public class testTX {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);

        jedis.flushDB();

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello", "world");
        jsonObject.put("name", "jiutian");

        Transaction multi = jedis.multi();
        String result = jsonObject.toString();

        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();  //关闭连接
        }
    }
}

SpringBoot 整合

导包

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

连接配置

# 默认配置了 localhost 6379
spring.redis.host=127.0.0.1
spring.redis.port=6379
# spring.redis.password=123456

配置

查看源码

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

   @Bean
   @ConditionalOnMissingBean(name = "redisTemplate")
   @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
   public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
      RedisTemplate<Object, Object> template = new RedisTemplate<>();
      template.setConnectionFactory(redisConnectionFactory);
      return template;
   }

   @Bean
   @ConditionalOnMissingBean
   @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
   public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
      return new StringRedisTemplate(redisConnectionFactory);
   }

}

通过源码可以看出,SpringBoot自动在容器中生成了一个RedisTemplate和一个 StringRedisTemplate。 但是,这个RedisTemplate的泛型是<Object,Object>,写代码不方便,需要写好多类型转换的代码;我们需要一个泛型为<String,Object>形式的RedisTemplate。 并且,这个RedisTemplate没有设置数据存在Redis时,key及value的序列化方式。 看到这个@ConditionalOnMissingBean注解后,就知道如果Spring容器中RedisTemplate对象了, 这个自动配置的RedisTemplate不会实例化。因此我们可以直接自己写个配置类,配置 RedisTemplate。

自定义 RedisTemplate

@Configuration
public class RedisConfig {

    // 自定义 RedisTemplate
    @Bean
    public RedisTemplate<String, Object> customerRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        // Json序列化配置
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);

        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(mapper);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);

        template.afterPropertiesSet();
        return template;
    }
}

封装工具类

详细可见

Github: https://github.com/jiutian777/redis-study/blob/main/redis-springboot-common/src/main/java/com/jiutian/utils/RedisUtil.java

Git: https://gitee.com/jiutian777/redis-study/blob/main/redis-springboot-common/src/main/java/com/jiutian/utils/RedisUtil.java

测试

@SpringBootTest
class Redis02SpringbootApplicationTests {

    @Autowired
    @Qualifier("customerRedisTemplate")
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisUtil redisUtil;

    @Test
    void test1(){
        redisUtil.set("name","jiutian");
        System.out.println(redisUtil.get("name"));
    }

    @Test
    void contextLoads() {
        //opsForValue  String
        //opsForList    List
        //opsForSet     Set
        //opsForHash    Hash
        //opsForZSet    ZSet

        // RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        // connection.flushDb();
        redisTemplate.opsForValue().set("mykey", "jiutian");
        System.out.println(redisTemplate.opsForValue().get("mykey"));
    }

    @Test
    void test() throws JsonProcessingException {
        User user = new User("九天", 3);
        //String jsonUser = new ObjectMapper().writeValueAsString(user);
        redisTemplate.opsForValue().set("user", user);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }
}

配置

默认的配置文件:

cd /opt/redis-6.2.6/

1648522742615

自定义的配置文件:

1648522809769

NETWORK 网络配置

bind 127.0.0.1 # 绑定的ip 
protected-mode yes # 保护模式 
port 6379 # 默认端口

GENERAL 通用

daemonize yes # 默认情况下,Redis不作为守护进程运行。需要开启的话,改为 yes 
supervised no # 可通过upstart和systemd管理Redis守护进程 
pidfile /var/run/redis_6379.pid # 以后台进程方式运行redis,则需要指定pid 文件 
loglevel notice # 日志级别。可选项有: 
# debug(记录大量日志信息,适用于开发、测试阶段); 
# verbose(较多日志信息);
# notice(适量日志信息,使用于生产环境); 
# warning(仅有部分重要、关键信息才会被记录)。
logfile "" # 日志文件的位置,当指定为空字符串时,为标准输出 
databases 16 # 设置数据库的数目。默认的数据库是DB 0 
always-show-logo yes # 是否总是显示logo

SNAPSHOPTING 快照

# 900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化) 
save 900 1 
# 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化) 
save 300 10 
# 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化) 
save 60 10000 

stop-writes-on-bgsave-error yes # 持久化出现错误后,是否依然进行继续进行工作 

rdbcompression yes # 使用压缩rdb文件 yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间 

rdbchecksum yes # 是否校验rdb文件,更有利于文件的容错性,但是在保存rdb文件的时候,会有大概10%的性能损耗 

dbfilename dump.rdb # dbfilenamerdb文件名称 

dir ./ # dir 数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录

SECURITY安全

# 启动redis 
# 连接客户端 

# 获得和设置密码 
config get requirepass
# 命令方式设置密码
config set requirepass "123456" 

限制

maxclients 10000 # 设置能连上redis的最大客户端连接数量 
maxmemory <bytes> # redis配置的最大内存容量 
maxmemory-policy noeviction #内存达到上限的处理策略 
#volatile-lru:利用LRU算法移除设置过过期时间的key。 
#volatile-random:随机移除设置过过期时间的key。 
#volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL) 
#allkeys-lru:利用LRU算法移除任何key。
#allkeys-random:随机移除任何key。 
#noeviction:不移除任何key,只是返回一个写错误。

append only模式

appendonly no # 是否以append only模式作为持久化方式,默认使用的是rdb方式持久化,这种方式在许多应用中已经足够用了 

appendfilename "appendonly.aof" # appendfilename AOF 文件名称 

appendfsync everysec # appendfsync aof持久化策略的配置 
# no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。 
# always表示每次写入都执行fsync,以保证数据同步到磁盘。 
# everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。

基础命令


127.0.0.1:6379> config set requirepass "123456"  # 设置密码
OK

127.0.0.1:6379> config set requirepass ""   # 取消密码
OK

[root@VM-16-4-centos bin]# redis-cli -p 6379  
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456	   # 登录认证密码
OK

Q.E.D.


以无限为有限,以无法为有法