redis笔记

学习redis数据库操作

今日内容

1. redis
	1. 概念
	2. 下载安装
	3. 命令操作
		1. 数据结构
	4. 持久化操作
	5. 使用Java客户端操作redis

关于Redis

1. 概念: redis是一款高性能的NOSQL系列的非关系型数据库


	1.1.什么是NOSQL
		NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,是一项全新的数据库理念,泛指非关系型的数据库。
		随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。

		1.1.1.	NOSQL和关系型数据库比较
			优点:
				1)成本:nosql数据库简单易部署,基本都是开源软件,不需要像使用oracle那样花费大量成本购买使用,相比关系型数据库价格便宜。
				2)查询速度:nosql数据库将数据存储于缓存之中,关系型数据库将数据存储在硬盘中,自然查询速度远不及nosql数据库。
				3)存储数据的格式:nosql的存储格式是key,value形式、文档形式、图片形式等等,所以可以存储基础类型以及对象或者是集合等各种格式,而数据库则只支持基础类型。
				4)扩展性:关系型数据库有类似join这样的多表查询机制的限制导致扩展很艰难。

			缺点:
				1)维护的工具和资料有限,因为nosql是属于新的技术,不能和关系型数据库10几年的技术同日而语。
				2)不提供对sql的支持,如果不支持sql这样的工业标准,将产生一定用户的学习和使用成本。
				3)不提供关系型数据库对事务的处理。

		1.1.2.	非关系型数据库的优势:
			1)性能NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。
			2)可扩展性同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。

		1.1.3.	关系型数据库的优势:
			1)复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。
			2)事务支持使得对于安全性能很高的数据访问要求得以实现。对于这两类数据库,对方的优势就是自己的弱势,反之亦然。

		1.1.4.	总结
			关系型数据库与NoSQL数据库并非对立而是互补的关系,即通常情况下使用关系型数据库,在适合使用NoSQL的时候使用NoSQL数据库,
			让NoSQL数据库对关系型数据库的不足进行弥补。
			一般会将数据存储在关系型数据库中,在nosql数据库中备份存储关系型数据库的数据

	1.2.主流的NOSQL产品
		•	键值(Key-Value)存储数据库
				相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
				典型应用: 内容缓存,主要用于处理大量数据的高访问负载。 
				数据模型: 一系列键值对
				优势: 快速查询
				劣势: 存储的数据缺少结构化
		•	列存储数据库
				相关产品:Cassandra, HBase, Riak
				典型应用:分布式的文件系统
				数据模型:以列簇式存储,将同一列数据存在一起
				优势:查找速度快,可扩展性强,更容易进行分布式扩展
				劣势:功能相对局限
		•	文档型数据库
				相关产品:CouchDB、MongoDB
				典型应用:Web应用(与Key-Value类似,Value是结构化的)
				数据模型: 一系列键值对
				优势:数据结构要求不严格
				劣势: 查询性能不高,而且缺乏统一的查询语法
		•	图形(Graph)数据库
				相关数据库:Neo4J、InfoGrid、Infinite Graph
				典型应用:社交网络
				数据模型:图结构
				优势:利用图结构相关算法。
				劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。
	1.3 什么是Redis
		Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s ,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的键值数据类型如下:
			1) 字符串类型 string
			2) 哈希类型 hash
			3) 列表类型 list
			4) 集合类型 set
			5) 有序集合类型 sortedset
		1.3.1 redis的应用场景
			•	缓存(数据查询、短连接、新闻内容、商品内容等等)
			•	聊天室的在线好友列表
			•	任务队列。(秒杀、抢购、12306等等)
			•	应用排行榜
			•	网站访问统计
			•	数据过期处理(可以精确到毫秒
			•	分布式集群架构中的session分离
2. 下载安装
	1. 官网:https://redis.io
	2. 中文网:http://www.redis.net.cn/
	3. windows解压直接可以使用:
		* 配置文件 : redis.windows.conf
		* redis的客户端 :redis-cli.exe
		* redis服务器端 :redis-server.exe
         * 卸载服务:redis-server --service-uninstall
         * 开启服务:redis-server --service-start
         * 停止服务:redis-server --service-stop
         * 重命名服务:redis-server --service-name name
	4.redis要在Linux上运行才稳定,官方也只做Linux的相关操作
	5.Linux下载安装redis
		* 下载到usr/local/src 目录下
		* 解压文件 sudo tar -zxvf src/redis-4.0.11.tar.gz
		* 切换到redis目录下编译  make
		* 安装到指定目录make install PREFIX=/usr/local/redis-4.0.11/
		* 安装完成默认端口号:6379
		* 为redis设置登录密码更改配置文件 vim redis.conf  
			requirepass 1234   #登录密码
			bind 127.0.0.1     #本机的IP地址
		* 启动redis服务器  切换到安装目录的bin下  
			cd /usr/local/redis-4.0.11/bin
			./redis-server redis.conf
		* 连接redis 
			./redis-cli -h 192.168.153.160 -p 6379

Redis常用操作

1.keys pattern  查找所有符合给定模式pattern的key
	* keys * 匹配数据库中的所有key
	* keys h?llo 匹配hllo和heeeello等
	* keys h[ae] 匹配hello和hallo,但不能匹配hillo
    示例:
    	a.查询所有
        192.168.153.160:6379> keys *
        1) "name"
        2) "age"
        3) "node"
        4) "native"
        5) "email"
        6) "nambe"
        b.模糊匹配:
        192.168.153.160:6379> keys n*
        1) "name"
        2) "node"
        3) "native"
        4) "nambe"
2.del 删除 key 
    示例:
        192.168.153.160:6379> del name
        (integer) 1
        192.168.153.160:6379> del age
        (integer) 1
3.flushdb 清除当前数据库,flushall 清除所有数据库
	示例:
		192.168.153.160:6379> flushdb
		OK
		192.168.153.160:6379> flushall
		OK
3.TTL key 以秒为单位,返回值给定key的剩余生存时间,返回值为-1表示永久时间
	示例:
		192.168.153.160:6379> set native 783
         OK
         192.168.153.160:6379> ttl native
         (integer) -1
4.exists 检查key是否存在  存在返回1,不存在返回0
	示例:
		192.168.153.160:6379> exists native
        (integer) 1
        192.168.153.160:6379> exists name
        (integer) 0
5.expire key seconds 为给定key设置生存时间,当key过期时(生存时间为0,它会自动删除,返回-1位永久有效,-2位过期
	示例:
        192.168.153.160:6379> set name zhangsan
        OK
        192.168.153.160:6379> ttl name
        (integer) -1
        192.168.153.160:6379> expire name 30
        (integer) 1
        192.168.153.160:6379> ttl name
        (integer) 25
        192.168.153.160:6379> ttl name
        (integer) 4
        192.168.153.160:6379> ttl name
        (integer) -2
6.pexpire key milliseconds 这个命令和expire命令类似,但它是以毫秒为单位设置key的生存时间
 	示例:
        192.168.153.160:6379> set name zhangsan
        OK
        192.168.153.160:6379> ttl name
        (integer) -1
        192.168.153.160:6379> pexpire name 9000
        (integer) 1
        192.168.153.160:6379> ttl name
        (integer) 3
        192.168.153.160:6379> ttl name
        (integer) -2
7.select  选择数据库,redis提供了14个数据库,排序为0-14select 1 选择第2个数据库
8.move key db 将当前数据库的key移动到给定的数据库db当中
	示例:
        192.168.153.160:6379> set name zhangsan
        OK
        192.168.153.160:6379> move name 2
        (integer) 1
        192.168.153.160:6379> keys *
        (empty list or set)
        192.168.153.160:6379> select 2
        OK
        192.168.153.160:6379[2]> keys *
        1) "name"

Redis数据类型 之 string

字符串:java代码
String str = "abc";
1.set key value
192.168.153.160:6379> set name zhangyong
OK
2.get key 返回key所关联的字符串的字符值,不存在返回nil
192.168.153.160:6379> get name
"zhangyong"
192.168.153.160:6379> get age
(nil)
3.mset key value 同时设置一个或者多个key-value对,如果存在将会覆盖
192.168.153.160:6379> mset name zhangsan age 18 email zhangsan@163.com number 123
OK

4.mget key 返回所有给定key的值,key不存在返回nil

192.168.153.160:6379> mget name age email number
1) "zhangsan"
2) "18"
3) "zhangsan@163.com"
4) "123"
5.append key 如果key已经存在并且是一个字符串,append命令将value追加到之前的value值末尾
192.168.153.160:6379> set k aaa
OK
192.168.153.160:6379> append k bbb
(integer) 6
192.168.153.160:6379> get k
"aaabbb"
6.strlen key 返回key所储存的字符串值得长度。当储存的不是字符串时,返回的时错误
192.168.153.160:6379> set str sfskjdhfdjhf
OK
192.168.153.160:6379> strlen str
(integer) 12
7.setex key seconds value 将值value关联到key的生存时间设置为seconds(以秒为单位)
192.168.153.160:6379> setex name 20 zhangsan
OK
192.168.153.160:6379> get name
"zhangsan"
192.168.153.160:6379> ttl name
(integer) 7
192.168.153.160:6379> ttl name
(integer) -2
8.setnx key value 将key的值设为value,当且仅当key不存在。存在不做任何个动作
192.168.153.160:6379> setnx name zhangsan
(integer) 1
192.168.153.160:6379> get name
"zhangsan"
192.168.153.160:6379> setnx name lisi
(integer) 0
9.incr key 将key中储存的数字值增1
192.168.153.160:6379> set k 1
OK
192.168.153.160:6379> get k
"1"
192.168.153.160:6379> incr k
(integer) 2
192.168.153.160:6379> get k
"2"
10.incrby key increment将key所储存的值加上增量increment
192.168.153.160:6379> set k 1
OK
192.168.153.160:6379> INCRBY k 10
(integer) 11
192.168.153.160:6379> get k
"11"
192.168.153.160:6379> incrby k 20
(integer) 31
192.168.153.160:6379> get k
"31"
11.incrbyfloat key increment 为key中储存的值加上浮点数增量increment
192.168.153.160:6379> set k 123
OK
192.168.153.160:6379> incrbyfloat k 3.1415
"126.1415"
192.168.153.160:6379> get k
"126.1415"
192.168.153.160:6379> incrbyfloat k 3.1415159
"129.2830159"
12.decr key 将key中储存的数字值减一
192.168.153.160:6379> set k 10
OK
192.168.153.160:6379> decr k
(integer) 9
192.168.153.160:6379> get k
"9"
13.decrby key decrement 将key所储存值减去减量decrement
192.168.153.160:6379> set k 123
OK
192.168.153.160:6379> decrby k 100
(integer) 23
192.168.153.160:6379> get k
"23"
14.getset key value 将给定key的值设为value,并返回key的旧值
192.168.153.160:6379> set name zhangsan
OK
192.168.153.160:6379> getset name lisi
"zhangsan"
192.168.153.160:6379> get name
"lisi"
15.getrange key start end返回key中字符串值的字符串,字符串的截取范围有start和end两个偏移量决定
192.168.153.160:6379> set k hgfsjklak
OK
192.168.153.160:6379> getrange k 1 5
"gfsjk"

Redis数据类型 之 set

set特点:无序、不能重复

java代码:
Set set={null,"aa","cc","bb"};
1.sadd key member 将一个或多个member元素加入到集合key当中,已经存在于集合的member元素将被忽略。假如key不存在,则创建一个包含member元素作成员的集合
192.168.153.160:6379> sadd data aa 22 bb 88 ba 33
(integer) 6
192.168.153.160:6379> smembers data
1) "88"
2) "bb"
3) "aa"
4) "22"
5) "ba"
6) "33"
2.scard key 返回集合key的基数(集合中元素的数量)
192.168.153.160:6379> sadd set aa bb
(integer) 2
192.168.153.160:6379> scard set
(integer) 2
3.sismember keymember 判断member元素是否集合key的成员,存在返回1,不存在返回0
192.168.153.160:6379> sadd data aa bb cc dd
(integer) 4
192.168.153.160:6379> sismember data bb
(integer) 1
192.168.153.160:6379> sismember data ee
(integer) 0
4.smove source destination member 将member元素从source移动到destination集合
192.168.153.160:6379> sadd d1 aa bb
(integer) 2
192.168.153.160:6379> sadd d2 11 22
(integer) 2
192.168.153.160:6379> smove d1 d2 bb
(integer) 1
192.168.153.160:6379> smembers d1
1) "aa"
192.168.153.160:6379> smembers d2
1) "11"
2) "22"
3) "bb"
5.spop key 移除并返回集合中的一个随机元素
192.168.153.160:6379> sadd data a b c d e f
(integer) 6
192.168.153.160:6379> spop data
"c"
192.168.153.160:6379> smembers data
1) "a"
2) "d"
3) "e"
4) "f"
5) "b"
192.168.153.160:6379> spop data
"b"
192.168.153.160:6379> smembers data
1) "a"
2) "d"
3) "e"
4) "f"
6.srandmember key 如果命令执行时,只提供了key参数,那么返回集合中的一个随机数
192.168.153.160:6379> sadd data a b c d e f
(integer) 6
192.168.153.160:6379> srandmember data
"d"
192.168.153.160:6379> smembers data
1) "a"
2) "d"
3) "e"
4) "f"
5) "c"
6) "b"
192.168.153.160:6379> srandmember data 3
1) "a"
2) "e"
3) "b"

7.srem key member 移除集合key中的一个或多个member元素,不存在member 元素会被忽略

192.168.153.160:6379> sadd data a b c d e f
(integer) 6
192.168.153.160:6379> srem data b c e
(integer) 3
192.168.153.160:6379> smembers data
1) "a"
2) "d"
3) "f"
8.sunion key返回一个集合的全部成员,该集合是所有给定集合的并集。不存在的 key 被视为空集。
192.168.153.160:6379> sadd data a b c d e f
(integer) 6
192.168.153.160:6379> sadd data2 j s g d a s
(integer) 5
192.168.153.160:6379> sunion data data2
1) "e"
2) "f"
3) "c"
4) "b"
5) "j"
6) "d"
7) "a"
8) "s"
9) "g"
9.sdiff key [key …]返回一个集合的全部成员,该集合是所有给定集合之间的差集。
192.168.153.160:6379> sadd set1 aa bb cc dd
(integer) 4
192.168.153.160:6379> sadd set2 aa 11 bb 22
(integer) 4
192.168.153.160:6379> sdiff set1 set2
1) "dd"
2) "cc"
10.sdiffstore destination key [key …]这个命令的作用和 sdiff 类似,但它将结果保存到 destination 集合,而不是简单地返回结果集。
192.168.153.160:6379> sadd set1 aa bb cc dd
(integer) 4
192.168.153.160:6379> sadd set2 aa 11 bb 22 cc
(integer) 5
192.168.153.160:6379> sdiffstore dest set1 set2
(integer) 1
192.168.153.160:6379> smembers dest
1) "dd"
11.sinter key [key …]返回一个集合的全部成员,该集合是所有给定集合的交集。不存在的 key 被视为空集。
192.168.153.160:6379> sadd set1 aa bb cc dd
(integer) 4
192.168.153.160:6379> sadd set2 aa 11 bb 22
(integer) 4
192.168.153.160:6379> sinter set1 set2
1) "aa"
2) "bb"
192.168.153.160:6379> sinter set2 set1
1) "aa"
2) "bb"
12.sinterstore destination key [key …]这个命令类似于 SINTER 命令,但它将结果保存到 destination 集合,而不是简单地返回结果集。如果 destination 集合已经存在,则将其覆盖。
192.168.153.160:6379> sadd set1 a b c d
(integer) 4
192.168.153.160:6379> sadd set2 a 1 b c 2
(integer) 5
192.168.153.160:6379> sinterstore dest set1 set2
(integer) 3
192.168.153.160:6379> smembers dest
1) "a"
2) "c"
3) "b"

Redis数据类型 之 list

特点:list列表:有序,能重复

java代码:
list list = {"aa","bb","cc"}
1.lpushx key value 将值value插入到列表,当且仅当key存在并且是一个列表
192.168.153.160:6379> lpush list aa bb cc dd
(integer) 4
192.168.153.160:6379> lrange list 0 -1
1) "dd"
2) "cc"
3) "bb"
4) "aa"
2.rpush key value [value …]将一个或多个值 value 插入到列表 key 的表尾(最右边)。
192.168.153.160:6379> rpush list a b c d
(integer) 4
192.168.153.160:6379> lrange list 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
3.lrange key start stop返回列表 key 中指定区间内的元素,区间以偏移量 start 和 stop 指定
192.168.153.160:6379> rpush list a b c d e f
(integer) 6
192.168.153.160:6379> lrange list 0 3
1) "a"
2) "b"
3) "c"
4) "d"
192.168.153.160:6379> lrange list 2 4
1) "c"
2) "d"
3) "e"
192.168.153.160:6379> lrange list 3 -1
1) "d"
2) "e"
3) "f"
4.lset key index value将列表 key 下标为 index 的元素的值设置为 value 。
192.168.153.160:6379> rpush list aa bb cc dd
(integer) 4
192.168.153.160:6379> lset list 2 1234
OK
192.168.153.160:6379> lrange list 0 -1
1) "aa"
2) "bb"
3) "1234"
4) "dd"
5.lindex key index返回列表 key 中,下标为 index 的元素。
192.168.153.160:6379> rpush list 1 2 3 4 5
(integer) 5
192.168.153.160:6379> lindex list 3
"4"
6.llen key返回列表key的长度
192.168.153.160:6379> rpush list a b c d e f
(integer) 6
192.168.153.160:6379> llen list
(integer) 6
7.linsert key before|after pivot value将值 value 插入到列表 key 当中,位于值 pivot 之前或之后。
  • 当 pivot 不存在于列表 key 时,不执行任何操作。

  • 当 key 不存在时, key 被视为空列表,不执行任何操作。

    192.168.153.160:6379> rpush list aa bb cc dd
    (integer) 4
    192.168.153.160:6379> linsert list before cc 11
    (integer) 5
    192.168.153.160:6379> lrange list 0 -1
    1) "aa"
    2) "bb"
    3) "11"
    4) "cc"
    5) "dd"
    192.168.153.160:6379> linsert list after bb 00
    (integer) 6
    192.168.153.160:6379> lrange list 0 -1
    1) "aa"
    2) "bb"
    3) "00"
    4) "11"
    5) "cc"
    6) "dd"
    
8.ltrim key start stop对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
192.168.153.160:6379> rpush list a b c d e f g
(integer) 7
192.168.153.160:6379> ltrim list 3 5
OK
192.168.153.160:6379> lrange list 0 -1
1) "d"
2) "e"
3) "f"
9.lpushx key value将值 value 插入到列表 key 的表头,当且仅当 key 存在并且是一个列表。
192.168.153.160:6379> rpush list a b c d
(integer) 4
192.168.153.160:6379> lpush list 0
(integer) 5
192.168.153.160:6379> lpushx list 1
(integer) 6
192.168.153.160:6379> lrange list 0 -1
1) "1"
2) "0"
3) "a"
4) "b"
5) "c"
6) "d"
10.rpushx key value将值 value 插入到列表 key 的表尾,当且仅当 key 存在并且是一个列表。
192.168.153.160:6379> lpush list a b c
(integer) 3
192.168.153.160:6379> rpushx list 99
(integer) 4
192.168.153.160:6379> lrange list 0 -1
1) "c"
2) "b"
3) "a"
4) "99"
11.lpop key移除并返回列表 key 的头元素。
192.168.153.160:6379> rpush list a b c d
(integer) 4
192.168.153.160:6379> lpop list
"a"
192.168.153.160:6379> lrange list 0 -1
1) "b"
2) "c"
3) "d"
12.rpop key移除并返回列表 key 的尾元素。
192.168.153.160:6379> lpush list a b c d
(integer) 4
192.168.153.160:6379> rpop list
"a"
192.168.153.160:6379> lrange list 0 -1
1) "d"
2) "c"
3) "b"
13.rpoplpush source destination命令 rpoplpush 在一个原子时间内,执行以下两个动作:

​ 将列表 source 中的最后一个元素(尾元素)弹出,并返回给客户端。
​ 将 source 弹出的元素插入到列表 destination ,作为 destination 列表的的头元素。

192.168.153.160:6379> rpush list1 aa bb cc
(integer) 3
192.168.153.160:6379> rpush list2 11 22 33
(integer) 3
192.168.153.160:6379> rpoplpush list1 list2
"cc"
192.168.153.160:6379> lrange list1 0 -1
1) "aa"
2) "bb"
192.168.153.160:6379> lrange list2 0 -1
1) "cc"
2) "11"
3) "22"
4) "33"

Redis数据类型 之 zset

zset特点:不能重复但有序

1.zadd key score member [[score member] [score member] …]将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
192.168.153.160:6379> zadd zset 8 zhangsan 2 lisi 9 wanger 12 mazi
(integer) 4
192.168.153.160:6379> zrange zset 0 -1
1) "lisi"
2) "zhangsan"
3) "wanger"
4) "mazi"
192.168.153.160:6379> zadd data 22 zhangsan 22 mazi 22 lisi 8 mazi 9 qianwu
(integer) 4
192.168.153.160:6379> zrange data 0 -1 withscores
1) "mazi"
2) "8"
3) "qianwu"
4) "9"
5) "lisi"
6) "22"
7) "zhangsan"
8) "22"
2.zcard key返回有序集 key 的基数。
192.168.153.160:6379> zadd zset 3 aa 2 bb -1 cc -8 dd
(integer) 4
192.168.153.160:6379> zcard zset
(integer) 4
3.zcount key min max返回有序集 key 中, score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max )的成员的数量。
192.168.153.160:6379> zadd data 12 aa 6 bb 18 cc -2 dd 9 ee
(integer) 5
192.168.153.160:6379> zcount data 5 10
(integer) 2
192.168.153.160:6379> zcount data -2 12
(integer) 4
4.zincrby key increment member为有序集 key 的成员 member 的 score 值加上增量 increment 。
192.168.153.160:6379> zadd data 8 a 9 b
(integer) 2
192.168.153.160:6379> zincrby data 12 b
"21"
192.168.153.160:6379> zrange data 0 -1
1) "a"
2) "b"
192.168.153.160:6379> zrange data 0 -1 withscores
1) "a"
2) "8"
3) "b"
4) "21"
5.zrange key start stop [withscores]返回有序集 key 中,指定区间内的成员。

其中成员的位置按 score 值递增(从小到大)来排序。

192.168.153.160:6379> zadd zset 8 zhangsan 2 lisi 9 wanger
(integer) 3
192.168.153.160:6379> zrange zset 0 -1
1) "lisi"
2) "zhangsan"
3) "wanger"
192.168.153.160:6379> zrange zset 0 -1 withscores
1) "lisi"
2) "2"
3) "zhangsan"
4) "8"
5) "wanger"
6) "9"
6.zrangebyscore key min max [withscores] [limit offset count]返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。LIMIT 参数指定返回结果的数量及区间(就像SQL中的 SELECT LIMIT offset, count )
192.168.153.160:6379> zadd data 1.1 a 8 b 3 c 12 d 15 e 3.14 f 2.2 g
(integer) 7
192.168.153.160:6379> zrangebyscore data 3 9
1) "c"
2) "f"
3) "b"
192.168.153.160:6379> zrangebyscore data 2 14 withscores
 1) "g"
 2) "2.2000000000000002"
 3) "c"
 4) "3"
 5) "f"
 6) "3.1400000000000001"
 7) "b"
 8) "8"
 9) "d"
10) "12"
7.zrem key member [member …]移除有序集 key 中的一个或多个成员,不存在的成员将被忽略。当 key 存在但不是有序集类型时,返回一个错误。
192.168.153.160:6379> zadd data 23 aa 12 bb 31 cc
(integer) 3
192.168.153.160:6379> zrem data bb
(integer) 1
192.168.153.160:6379> zrange data 0 -1
1) "aa"
2) "cc"
8.zremrangebyrank key start stop移除有序集 key 中,指定排名(rank)区间内的所有成员。区间分别以下标参数 start 和 stop 指出,包含 start 和 stop 在内。

下标参数 start 和 stop 都以 0 为底,也就是说,以 0 表示有序集第一个成员,以 1 表示有序集第二个成员,以此类推。
你也可以使用负数下标,以 -1 表示最后一个成员, -2 表示倒数第二个成员,以此类推。

192.168.153.160:6379> zadd data 3 a 2 b 8 c 9 d 5 e 6 f 1 g
(integer) 7
192.168.153.160:6379> zrange data 0 -1
1) "g"
2) "b"
3) "a"
4) "e"
5) "f"
6) "c"
7) "d"
192.168.153.160:6379> zremrangebyrank data 6 8
(integer) 1
192.168.153.160:6379> zrange data 0 -1
1) "g"
2) "b"
3) "a"
4) "e"
5) "f"
6) "c"
192.168.153.160:6379> zremrangebyrank data 2 -1
(integer) 4
192.168.153.160:6379> zrange data 0 -1
1) "g"
2) "b"
9.zremrangebyscore key min max移除有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。
192.168.153.160:6379> zadd data 23 a 8 b 3 c 6 e 2 f
(integer) 5
192.168.153.160:6379> zremrangebyscore data 2 7
(integer) 3
192.168.153.160:6379> zrange data 0 -1
1) "b"
2) "a"
10.zrevrange key start stop [withscores]返回有序集 key 中,指定区间内的成员。

其中成员的位置按 score 值递减(从大到小)来排列。
具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列。

192.168.153.160:6379> zadd data 22 zz 22 ee 22 bb 12 cc 28 ff 10 gg
(integer) 6
192.168.153.160:6379> zrange data 0 -1 withscores
 1) "gg"
 2) "10"
 3) "cc"
 4) "12"
 5) "bb"
 6) "22"
 7) "ee"
 8) "22"
 9) "zz"
10) "22"
11) "ff"
12) "28"
192.168.153.160:6379> zrevrange data 0 -1 withscores
 1) "ff"
 2) "28"
 3) "zz"
 4) "22"
 5) "ee"
 6) "22"
 7) "bb"
 8) "22"
 9) "cc"
10) "12"
11) "gg"
12) "10"
11.zrevrangebyscore key max min [withscores] [limit offset count]返回有序集 key 中, score 值介于 max 和 min 之间(默认包括等于 max 或 min )的所有的成员。有序集成员按 score 值递减(从大到小)的次序排列。
192.168.153.160:6379> zadd data 33 a 23 b 28 c 12 d 14 e 15 f 1 g
(integer) 7
192.168.153.160:6379> zrevrangebyscore data 33 1 withscores
 1) "a"
 2) "33"
 3) "c"
 4) "28"
 5) "b"
 6) "23"
 7) "f"
 8) "15"
 9) "e"
10) "14"
11) "d"
12) "12"
13) "g"
14) "1"
192.168.153.160:6379> zrevrangebyscore data 23 11 limit 2 2
1) "e"
2) "d"
12.zrevrank key member返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递减(从大到小)排序。

排名以 0 为底,也就是说, score 值最大的成员排名为 0 。

192.168.153.160:6379> zadd data 11 a 3 b 4 c 22 d
(integer) 4
192.168.153.160:6379> zrevrange data 0 -1
1) "d"
2) "a"
3) "c"
4) "b"
192.168.153.160:6379> zrevrank data c
(integer) 2
13.zscore key member返回有序集 key 中,成员 member 的 score 值。如果 member 元素不是有序集 key 的成员,或 key 不存在,返回 nil 。
192.168.153.160:6379> zadd data 22 aa
(integer) 1
192.168.153.160:6379> zscore data aa
"22"
192.168.153.160:6379> zscore data bb
(nil)
14.zunionstore destination numkeys key [key …] [weights weight [weight …]] [aggregate sum|min|max]计算给定的一个或多个有序集的并集,其中给定 key 的数量必须以 numkeys 参数指定,并将该并集(结果集)储存到 destination 。
192.168.153.160:6379> zadd data1 11 aa 11 bb 11 cc
(integer) 3
192.168.153.160:6379> zadd data2 22 bb 22 cc 22 dd
(integer) 3
192.168.153.160:6379> zunionstore dest 2 data1 data2
(integer) 4
192.168.153.160:6379> zrange dest 0 -1 withscores
1) "aa"
2) "11"
3) "dd"
4) "22"
5) "bb"
6) "33"
7) "cc"
8) "33"
192.168.153.160:6379> zunionstore dest2 2 data1 data2 weights 2 3
(integer) 4
192.168.153.160:6379> zrange dest2 0 -1 withscores
1) "aa"
2) "22"
3) "dd"
4) "66"
5) "bb"
6) "88"
7) "cc"
8) "88"
192.168.153.160:6379> zunionstore dest3 2 data1 data2 aggregate max
(integer) 4
192.168.153.160:6379> zrange dest3 0 -1 withscores
1) "aa"
2) "11"
3) "bb"
4) "22"
5) "cc"
6) "22"
7) "dd"
8) "22"
15.zinterstore destination numkeys key [key …] [weights weight [weight …]] [aggregate sum|min|max]计算给定的一个或多个有序集的交集,其中给定 key 的数量必须以 numkeys 参数指定,并将该交集(结果集)储存到 destination 。默认情况下,结果集中某个成员的 score 值是所有给定集下该成员 score 值之和
192.168.153.160:6379> zadd data1 11 aa 11 bb 11 cc
(integer) 3
192.168.153.160:6379> zadd data2 22 bb 22 cc 22 dd
(integer) 3
192.168.153.160:6379> zinterstore dest 2 data1 data2
(integer) 2
192.168.153.160:6379> zrange dest 0 -1
1) "bb"
2) "cc"
192.168.153.160:6379> zrange dest 0 -1 withscores
1) "bb"
2) "33"
3) "cc"
4) "33"

Redis数据类型之hash

java代码
Map map = {"k1","v1","k2","v2","k3","v3"};
1.HSET key field value将哈希表 key 中的域 field 的值设为 value 。如果 key 不存在,一个新的哈希表被创建并进行 HSET 操作。如果域 field 已经存在于哈希表中,旧值将被覆盖。
192.168.153.160:6379> hset map k1 v1
(integer) 1
192.168.153.160:6379> hget map k1
"v1"
192.168.153.160:6379> hset map k1 vv
(integer) 0
192.168.153.160:6379> hget map k1
"vv"
2.HSETNX key field value将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在。若域 field 已经存在,该操作无效。
192.168.153.160:6379> hset map k1 v1
(integer) 1
192.168.153.160:6379> hget map k1
"v1"
192.168.153.160:6379> hsetnx map k1 vv
(integer) 0
192.168.153.160:6379> hget map k1
"v1"
3.HGET key field返回哈希表 key 中给定域 field 的值。
192.168.153.160:6379> hset map k1 v1
(integer) 1
192.168.153.160:6379> hget map k1
"v1"
4.HMGET key field [field …]返回哈希表 key 中,一个或多个给定域的值。如果给定的域不存在于哈希表,那么返回一个 nil 值。
192.168.153.160:6379> hmset map k1 v1 k2 v2
OK
192.168.153.160:6379> hmget map k1 k2 k3
1) "v1"
2) "v2"
3) (nil)
5.HGETALL key返回哈希表 key 中,所有的域和值。在返回值里,紧跟每个域名(field name)之后是域的值(value),所以返回值的长度是哈希表大小的两倍。
192.168.153.160:6379> hmset map k1 v1 k2 v2 k3 v3
OK
192.168.153.160:6379> hgetall map
1) "k1"
2) "v1"
3) "k2"
4) "v2"
5) "k3"
6) "v3"
6.HKEYS key返回哈希表 key 中的所有域。
192.168.153.160:6379> hmset map k1 v1 k2 v2 k3 v3
OK
192.168.153.160:6379> hkeys map
1) "k1"
2) "k2"
3) "k3"
7.HVALS key返回哈希表 key 中所有域的值。
192.168.153.160:6379> hmset map k1 v1 k2 v2 k3 v3
OK
192.168.153.160:6379> hvals map
1) "v1"
2) "v2"
3) "v3"
8.HEXISTS key field查看哈希表 key 中,给定域 field 是否存在。
192.168.153.160:6379> hmset map k1 v1 k2 v2 k3 v3
OK
192.168.153.160:6379> hexists map k1
(integer) 1
192.168.153.160:6379> hexists map k2
(integer) 1
192.168.153.160:6379> hexists map k4
(integer) 0
9.HLEN key返回哈希表 key 中域的数量。
192.168.153.160:6379> hmset map k1 v1 k2 v2 k3 v3
OK
192.168.153.160:6379> hlen map
(integer) 3
10.HDEL key field [field …]删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
192.168.153.160:6379> hmset map k1 v1 k2 v2 k3 v3
OK
192.168.153.160:6379> hdel map k3
(integer) 1
192.168.153.160:6379> hgetall map
1) "k1"
2) "v1"
3) "k2"
4) "v2"
11.HINCRBY key field increment为哈希表 key 中的域 field 的值加上增量 increment 。

增量也可以为负数,相当于对给定域进行减法操作。
如果 key 不存在,一个新的哈希表被创建并执行 HINCRBY 命令。
如果域 field 不存在,那么在执行命令前,域的值被初始化为 0 。

192.168.153.160:6379> hset map k1 10
(integer) 1
192.168.153.160:6379> hincrby map k1 100
(integer) 110
192.168.153.160:6379> hget map k1
"110"
192.168.153.160:6379> hincrby map1 k1 100
(integer) 100
192.168.153.160:6379> hget map1 k1
"100"
12.HINCRBYFLOAT key field increment为哈希表 key 中的域 field 加上浮点数增量 increment 。

如果哈希表中没有域 field ,那么 HINCRBYFLOAT 会先将域 field 的值设为 0 ,然后再执行加法操作。
如果键 key 不存在,那么 HINCRBYFLOAT 会先创建一个哈希表,再创建域 field ,最后再执行加法操作。

192.168.153.160:6379> hset map k1 3.14
(integer) 1
192.168.153.160:6379> hincrbyfloat map k1 2.17
"5.31"
192.168.153.160:6379> hget map k1
"5.31"

Redis数据类型 之 HyperLogLog

基数统计(HyperLogLog)

基数是一种算法。比如一本英文著作由几百万个单词组成,但英文单词本身是有限的,在这几百万个单词中有许多重复的单词,去掉重复的,内存就足够存储了。比如数字集合{1,2,5,7,9,1,5,9}的基数集合为{1,2,5,7,9},那么基数为5。基数的作用是评估大约需要准备多少个存储单元去存储数据。基数不能存储元素。
HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。但是HyperLogLog也存在缺点,就是它是估计基数的算法,所以会有一定误差,而且无法获取具体的元素值。因此应用在对准确性不是很重要的场景,例如:QQ同时在线人数,网站IP访问数量等。
HyperLogLog是用来做基数统计的,它的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总量是固定很小的。在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。因为HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身。

HyperLogLog常用命令:
1.PFADD将指定的元素添加到指定的HyperLogLog 结构中。如果一个HyperLogLog的估计的近似基数在执行命令过程中发了变化, PFADD 返回1,否则返回0。
192.168.153.160:6379> pfadd data1 aa bb cc
(integer) 1     成功返回1
192.168.153.160:6379> pfadd data1 aa
(integer) 0     失败返回0
192.168.153.160:6379> pfadd data1 aa dd
(integer) 1     部分成功返回1
192.168.153.160:6379> pfcount data1
(integer) 4
2.PFCOUNT参数为一个key时,返回存储在HyperLogLog结构体的该变量的近似基数;如果该变量不存在,则返回0。

当参数为多个key时,返回这些HyperLogLog的并集的近似基数,这个值是将所有给定key的HyperLoglog结构合并到一个临时的HyperLogLog结构中计算而得到的。
返回的可见集合基数并不是精确值, 而是一个带有 0.81% 标准错误(standard error)的近似值。

192.168.153.160:6379> pfadd data1 a b  c d
(integer) 1
192.168.153.160:6379> pfcount data1
(integer) 4
192.168.153.160:6379> pfadd data2 c d e f
(integer) 1
192.168.153.160:6379> pfcount data2
(integer) 4
192.168.153.160:6379> pfcount data1 data2     //data1和data2的集合
(integer) 6
3.PFMERGE将多个 HyperLogLog 合并(merge)为一个 HyperLogLog,合并后的 HyperLogLog 的基数接近于所有输入 HyperLogLog 的可见集合(observed set)的并集。

合并得出的 HyperLogLog 会被储存在目标变量(第一个参数)里面,如果该键并不存在, 那么命令在执行之前, 会先为该键创建一个空的。

192.168.153.160:6379> pfadd data1 a b  c d
(integer) 1
192.168.153.160:6379> pfadd data2 c d e f
(integer) 1
192.168.153.160:6379> pfmerge data data1 data2    //把data1并到data2
OK
192.168.153.160:6379> pfcount data
(integer) 6

Redis高级 之 sort 排序

sort排序的语法:

SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] 
[ASC | DESC] [ALPHA] [STORE destination]
用来对给定列表、集合、有序集合、Map进行排序。
  • [ASC|DESC] [ALPHA]:选择按照顺序、逆序或者字符串排序,set 集合(本身没有索引值)排序操作必须指定 ALPHA
  • [LIMIT offset count]:排序之后返回元素的数量可以通过 LIMIT 修饰符进行限制,修饰符接受 offset (要跳过的元素数量,即起始位置)和 count (返回的元素数量)两个参数。
  • [STORE destination]:默认情况下, sort 操作只是简单地返回排序结果,并不进行任何保存操作。通过给 store 选项指定一个 key 参数,可以将排序结果保存到给定的键上。
  • [GET pattern [GET pattern …]]:get 可以根据排序的结果来取出相应的键值,“get #” 表示返回自身元素,“get pattern” 可以返回外部 key 的数据 。
  • [BY pattern]:sort 命令默认使用集合元素进行排序,可以通过 “BY pattern” 使用外部 key 的数据作为权重排序。
  • sort是Redis中最强大最复杂的命令之一,如果使用不好很容易成为性能瓶颈。SORT命令的时间复杂度是O(n+mlogm ),其中n 表示要排序的列表(集合或有序集合)中的元素个数,m表示要返回的元素个数。当n 较大的时候sort命令的性能相对较低,并且Redis在排序前会建立一个长度为n(有一个例外是当键类型为有序集合且参考键为常量键名时容器大小为m 而不是n)的容器来存储待排序的元素,虽然是一个临时的过程,但如果同时进行较多的大数据量排序操作则会严重影响性能。

开发中使用SORT命令时需要注意以下几点。
(1)尽可能减少待排序键中元素的数量(使n 尽可能小)。
(2)使用LIMIT参数只获取需要的数据(使m 尽可能小)。
(3)如果要排序的数据数量较大,尽可能使用STORE参数将结果缓存。

1.普通的升序降序排序

sort key 按照key进行升序排序

192.168.153.160:6379> rpush list 22 18 3 5 9 6 2 18
(integer) 8
192.168.153.160:6379> lrange list 0 -1
1) "22"
2) "18"
3) "3"
4) "5"
5) "9"
6) "6"
7) "2"
8) "18"
192.168.153.160:6379> sort list
1) "2"
2) "3"
3) "5"
4) "6"
5) "9"
6) "18"
7) "18"
8) "22"

sort key desc 按照key进行降序排序

192.168.153.160:6379> rpush list 22 18 3 5 9 6 2 18
(integer) 8
192.168.153.160:6379> lrange list 0 -1
1) "22"
2) "18"
3) "3"
4) "5"
5) "9"
6) "6"
7) "2"
8) "18"
192.168.153.160:6379> sort list desc
1) "22"
2) "18"
3) "18"
4) "9"
5) "6"
6) "5"
7) "3"
8) "2"
2.使用ALPHA修饰符对字符串进行排序

sort 默认使用数据进行排序,如果value是字符串,需要再key后面增加alpha参数,否则会报错。

192.168.153.160:6379> rpush list zhangsan lisi wanger mazi
(integer) 4
192.168.153.160:6379> sort list alpha
1) "lisi"
2) "mazi"
3) "wanger"
4) "zhangsan"
192.168.153.160:6379> sort list
(error) ERR One or more scores can't be converted into double
3.使用limit限制返回结果:limit可以接受两个参数:

offset 指定要跳过的元素数量;count 指定跳过 offset 个指定的元素之后,要返回多少个对象

192.168.153.160:6379> rpush list a z e t c b p
(integer) 7
192.168.153.160:6379> sort list alpha limit 3 2
1) "e"
2) "p"
192.168.153.160:6379> sort list alpha
1) "a"
2) "b"
3) "c"
4) "e"
5) "p"
6) "t"
7) "z"
4.使用外部key进行排序可以使用外部key 的数据作为权重,代替默认直接对比键值进行排序。
192.168.153.160:6379> rpush ids 2 4 1
(integer) 3
192.168.153.160:6379> mset user_name_1 zhangsan user_age_1 18
OK
192.168.153.160:6379> mset user_name_2 lisi user_age_2 17
OK
192.168.153.160:6379> mset user_name_3 wanger user_age_3 19
OK
192.168.153.160:6379> mset user_name_4 mai user_age_4 20
OK
5.使用BY参数

BY 参数的语法为“BY参考键”。其中参考键可以是字符串类型键或者是散列类型键的某个字段(表示为键名—>字段名)。如果提供了BY参数,SORT命令将不再依据元素自身的值进行排序,而是对每个元素使用元素的值替换参考键中的第一个“*”并获取其值,然后依据该值对元素排序。

192.168.153.160:6379> sort ids by user_name_* alpha
1) "2"
2) "4"
3) "1"
192.168.153.160:6379> sort ids by user_age_*
1) "2"
2) "1"
3) "4"
6.使用GET参数

GET参数不影响排序,它的作用是使SORT命令的返回结果不再是元素自身的值,而是GET参数中指定的键值。GET参数的规则和BY参数一样,GET参数也支持字符串类型和散列类型的键,并使用“*”作为占位符。
在一个SORT命令中可以有多个GET参数,而BY参数只能有一个,有N个GET参数,每个元素返回的结果就有N行。

192.168.153.160:6379> sort ids
1) "1"
2) "2"
3) "4"
192.168.153.160:6379> sort ids desc get uer_name_*
1) (nil)
2) (nil)
3) (nil)
192.168.153.160:6379> sort ids desc get user_name_*
1) "mai"
2) "lisi"
3) "zhangsan"
192.168.153.160:6379> sort ids asc get user_age_*
1) "18"
2) "17"
3) "20"
7.组合使用BY和GET参数,同时可以获取多个字段
192.168.153.160:6379> sort ids desc by user_name_* alpha
1) "1"
2) "4"
3) "2"
192.168.153.160:6379> sort ids desc by user_name_* alpha get user_name_* get user_age_*
1) "zhangsan"
2) "18"
3) "mai"
4) "20"
5) "lisi"
6) "17"
8.获取外部键,但不进行排序

将一个不存在的键作为参数传给 BY 选项, 可以让 SORT 跳过排序操作。通过将这种用法和 GET 选项配合, 可以在不排序的情况下, 获取多个外部键, 相当于执行一个整合的获取操作(类似于 SQL 数据库的 join 关键字)
当参考键名不包含“*”时(即常量键名,与元素值无关),SORT命令将不会执行排序操作,因为Redis认为这种情况是没有意义的(因为所有要比较的值都一样)

GET # 可以获取排序的外键

192.168.153.160:6379> sort ids by aa
1) "2"
2) "4"
3) "1"
192.168.153.160:6379> sort ids by aa get # get user_name_* get user_age_*
1) "2"
2) "lisi"
3) "17"
4) "4"
5) "mai"
6) "20"
7) "1"
8) "zhangsan"
9) "18"
9.将哈希表作为get或by的参数
192.168.153.160:6379> rpush ids 3 1 4
(integer) 3
192.168.153.160:6379> hmset user_info_1 name zhangsan age 18
OK
192.168.153.160:6379> hmset user_info_2 name lisi age 17
OK
192.168.153.160:6379> hmset user_info_3 name wanger age 19
OK
192.168.153.160:6379> hmset user_info_4 name mazi age 20
OK
10.按照name进行排序,并输出id,name,age

BY 和 GET 选项都可以用 key->field 的格式来获取哈希表中的域的值, 其中 key 表示哈希表键, 而 field 则表示哈希表的域
参考键虽然支持散列类型,但是“*”只能在“->”符号前面( 即键名部分)才有用,在“->”后( 即字段名部分) 会被当成字段名本身而不会作为占位符被元素的值替換,即常量键名。

192.168.153.160:6379> sort ids by user_info_*->name alpha get # get user_info_*->name get user_info_*->age
1) "4"
2) "mazi"
3) "20"
4) "3"
5) "wanger"
6) "19"
7) "1"
8) "zhangsan"
9) "18"
11.保存排序结果

默认sort仅用来排序,不会存储排序结果,可以使用STORE将结果存储到指定key中。保存后的键的类型为列表类型,如果键已经存在则会覆盖它。加上STORE参数后SORT命令的返回值为结果的个数。

192.168.153.160:6379> sort ids by user_info_*->name alpha get # get user_info_*->name get user_info_*->age store res
(integer) 9
192.168.153.160:6379> type res
list
192.168.153.160:6379> lrange res 0 -1
1) "4"
2) "mazi"
3) "20"
4) "3"
5) "wanger"
6) "19"
7) "1"
8) "zhangsan"
9) "18"

Jedis 基本语法

Jedis 基础

1.JedisPool 基本案例,用来测试环境是否正常
public class DemoTest {
    private static String host = "192.168.153.160";
    private static int port = 6379;
    private static String password = "1234";
    private static int db = 0;
    private static int timeout = 30000;

    public static void main(String[] args) {
        JedisPoolConfig cfg = new JedisPoolConfig();
        cfg.setMaxTotal(20);//连接池中最多连接数
        cfg.setMaxIdle(5);//连接池中连接最大空闲数

        //创建连接池对象
        JedisPool pool = new JedisPool(cfg, host, port, timeout, password, db);
        Jedis jedis = pool.getResource(); //获取Jedis对象

        jedis.set("k1", "hello world");

        String val = jedis.get("k1");
        System.out.println(val);
        //jedis.del("jedis");
        
        jedis.close();
        pool.close();
        System.out.println("finish");
    }
}
2.JedisPool连接池,数据库连接池工具类:懒汉单例设计模式保证JedisPool对象是唯一的
public class JedisPoolUtil {
    private static String host = "192.168.153.160";
    private static int port = 6379;
    private static String password = "1234";
    private static int db = 0;
    private static int timeout = 30000;
    private static int maxWaitMillis = 20000;

    private volatile static JedisPool pool = null; //懒汉式单例

    private JedisPoolUtil() {
    }
    private static JedisPool getJedisPool() {
        if (pool == null) {
            synchronized (JedisPoolUtil.class) {
                JedisPoolConfig cfg = new JedisPoolConfig();
                cfg.setMaxTotal(20);//连接池中最多连接数
                cfg.setMaxIdle(5);//连接池中连接最大空闲数
                cfg.setMaxWaitMillis(maxWaitMillis);
                //创建连接池对象
                if (pool == null) {
                    pool = new JedisPool(cfg, host, port, timeout, password, db);
                }
            }
        }
        return pool;
    }
    public static Jedis getJedis() {
        return getJedisPool().getResource(); //从连接池中取一个Jedis对象
    }
    public static void release(Jedis jedis) {
        if (jedis != null) {
            jedis.close();// 将jedis放回连接池
        }
    }
}

//测试代码
public static void main(String[] args) {
        System.out.println(getJedis());
 }
3.代码框架,下面的一系列示例都是这个框架中的测试方法:
public class UtilTest {

    //运行下面每个测试类都要加载JedisPoolUtil连接池(before,after)
    private static Jedis jedis;
    @Before
    public  void before(){
        jedis = JedisPoolUtil.getJedis();
    }
    @After
    public void after(){
        JedisPoolUtil.release(jedis);
    }

    /**
     * 测试key - value 的数据
     * @throws InterruptedException
     */
    @Test
    public void testKey() throws InterruptedException {
        System.out.println("清空数据:"+jedis.flushDB());
        System.out.println("判断某个键是否存在:"+jedis.exists("username"));
        System.out.println("新增<'username','wukong'>的键值对:"+jedis.set("username", "wukong"));
        System.out.println("是否存在:"+jedis.exists("name"));
        System.out.println("新增<'password','password'>的键值对:"+jedis.set("password", "password"));
        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的过期时间为5s:"+jedis.expire("username", 5));
        TimeUnit.SECONDS.sleep(2);
        System.out.println("查看键username的剩余生存时间:"+jedis.ttl("username"));
        System.out.println("移除键username的生存时间:"+jedis.persist("username"));
        System.out.println("查看键username的剩余生存时间:"+jedis.ttl("username"));
        System.out.println("查看键username所存储的值的类型:"+jedis.type("username"));
    }

    /***
     * 字符串操作
     * memcached和redis同样有append的操作,但是memcached有prepend的操作,redis中并没有。
     * @throws InterruptedException
     */
    @Test
    public void testString() throws InterruptedException {
        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(new String[]{"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"));
        TimeUnit.SECONDS.sleep(3);
        System.out.println(jedis.get("key3"));

        System.out.println("===========获取原值,更新为新值==========");//GETSET is an atomic set this value and return the old value command.
        System.out.println(jedis.getSet("key2", "key2GetSet"));
        System.out.println(jedis.get("key2"));

        System.out.println("获得key2的值的字串:"+jedis.getrange("key2", 2, 4));
    }

    /***
     * 整数和浮点数
     */
    @Test
    public void testNumber() {
        jedis.flushDB();
        jedis.set("key1", "1");
        jedis.set("key2", "2");
        jedis.set("key3", "2.3");
        System.out.println("key1的值:"+jedis.get("key1"));
        System.out.println("key2的值:"+jedis.get("key2"));
        System.out.println("key1的值加1:"+jedis.incr("key1"));
        System.out.println("获取key1的值:"+jedis.get("key1"));
        System.out.println("key2的值减1:"+jedis.decr("key2"));
        System.out.println("获取key2的值:"+jedis.get("key2"));
        System.out.println("将key1的值加上整数5:"+jedis.incrBy("key1", 5));
        System.out.println("获取key1的值:"+jedis.get("key1"));
        System.out.println("将key2的值减去整数5:"+jedis.decrBy("key2", 5));
        System.out.println("获取key2的值:"+jedis.get("key2"));
    }

    /***
     * 列表
     */
    @Test
    public void testList() {
        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代表倒数第二个元素
        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集合
     */
    @Test
    public void testSet() {
        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中没有
    }

    /***
     * 散列
     */
    @Test
    public void testHash() {
        jedis.flushDB();
        Map<String,String> map = new HashMap<String,String>();
        map.put("key1","value1");
        map.put("key2","value2");
        map.put("key3","value3");
        map.put("key4","value4");
        jedis.hmset("hash",map);
        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"));
    }

    /**
     * 有序集合
     */
    @Test
    public void testSortedSet() {
        jedis.flushDB();
        Map<String,Double> map = new HashMap<String,Double>();
        map.put("key2",1.2);
        map.put("key3",4.0);
        map.put("key4",5.0);
        map.put("key5",0.2);
        System.out.println(jedis.zadd("zset", 3,"key1"));
        System.out.println(jedis.zadd("zset",map));
        System.out.println("zset中的所有元素:"+jedis.zrange("zset", 0, -1));
        System.out.println("zset中的所有元素:"+jedis.zrangeWithScores("zset", 0, -1));
        System.out.println("zset中的所有元素:"+jedis.zrangeByScore("zset", 0,100));
        System.out.println("zset中的所有元素:"+jedis.zrangeByScoreWithScores("zset", 0,100));
        System.out.println("zset中key2的分值:"+jedis.zscore("zset", "key2"));
        System.out.println("zset中key2的排名:"+jedis.zrank("zset", "key2"));
        System.out.println("删除zset中的元素key3:"+jedis.zrem("zset", "key3"));
        System.out.println("zset中的所有元素:"+jedis.zrange("zset", 0, -1));
        System.out.println("zset中元素的个数:"+jedis.zcard("zset"));
        System.out.println("zset中分值在1-4之间的元素的个数:"+jedis.zcount("zset", 1, 4));
        System.out.println("key2的分值加上5:"+jedis.zincrby("zset", 5, "key2"));
        System.out.println("key3的分值加上4:"+jedis.zincrby("zset", 4, "key3"));
        System.out.println("zset中的所有元素:"+jedis.zrange("zset", 0, -1));
    }

    /**
     * 排序
     */
    @Test
    public void testSort()
    {
        jedis.flushDB();
        jedis.lpush("collections", "ArrayList", "Vector", "Stack", "HashMap", "WeakHashMap", "LinkedHashMap");
        System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));
        SortingParams sortingParameters = new SortingParams();
        System.out.println(jedis.sort("collections",sortingParameters.alpha()));
        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", sortingParameters.asc()));
        System.out.println("升序:"+jedis.sort("sortedList", sortingParameters.desc()));
        System.out.println("===============================");
        jedis.lpush("userlist", "33");
        jedis.lpush("userlist", "55");
        jedis.lpush("userlist", "11");
        jedis.hset("user:66", "name", "66");
        jedis.hset("user:55", "name", "55");
        jedis.hset("user:33", "name", "33");
        jedis.hset("user:22", "name", "79");
        jedis.hset("user:11", "name", "24");
        jedis.hset("user:11", "add", "beijing");
        jedis.hset("user:22", "add", "shanghai");
        jedis.hset("user:33", "add", "guangzhou");
        jedis.hset("user:55", "add", "chongqing");
        jedis.hset("user:66", "add", "xi'an");
        sortingParameters = new SortingParams();
        sortingParameters.get("user:*->name");
        sortingParameters.get("user:*->add");
        System.out.println(jedis.sort("userlist",sortingParameters));
    }
}
4.Jedis工具类

添加lombok依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
</dependency>
@Slf4j
public class JedisUtil {
    /**
     * 字符串设置Key-value-expire
     * @param key
     * @param value
     * @param expire 过期时间,值为-1时永不过期
     * @return 成功返回OK
     */
    public static String strSet(String key, String value, int expire) {
        Jedis jedis = null;
        String res = null;
        try {
            jedis = JedisPoolUtil.getJedis();
            res = jedis.set(key, value);
            if (expire != -1) {
                jedis.expire(key, expire);
            }
        } catch (Exception e) {
            log.error("strSet{}error:{}", key, e);
        } finally {
            JedisPoolUtil.release(jedis);
        }
        return res;
    }

    /**
     * 字符串设置key-value
     * @param key
     * @param value
     * @return 成功返回ok
     */
    public static String strSet(String key, String value) {
        return strSet(key, value, -1);
    }

    /**
     * 根据key获取字符串的值
     * @param key
     * @return
     */
    public static String strGet(String key) {
        Jedis jedis = null;
        String res = null;
        try {
            jedis = JedisPoolUtil.getJedis();
            res = jedis.get(key);
        } catch (Exception e) {
            log.error("strSet{}error:{}", key, e);
        } finally {
            JedisPoolUtil.release(jedis);
        }
        return res;
    }

    /**
     * 从右边开始往List中添加数据
     * @param key
     * @param value
     * @return 返回值为添加list的个数
     */

    public static long listRPush(String key, String... value) {
        Jedis jedis = null;
        long res = -1;
        try {
            jedis = JedisPoolUtil.getJedis();
            res = jedis.rpush(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("listRPush():{},error:{}", key, value);
        } finally {
            JedisPoolUtil.release(jedis);
        }
        return res;
    }

    /**
     * 取出存入list中的值
     * @param key
     * @param start
     * @param end
     * @return 返回值为数组list的值
     */
    public static List<String> listLrange(String key, int start, int end) {
        List<String> res = null;
        Jedis jedis = null;
        try {
            jedis = JedisPoolUtil.getJedis();
            res = jedis.lrange(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("listRPush():{} {} {},error:{}", key, start, end);
        } finally {
            JedisPoolUtil.release(jedis);
        }
        return res;
    }

    /**
     * 刪除Key
     * @param key
     * @return 成功返回1
     */
    public static long del(String key) {
        long res = 1;
        Jedis jedis = null;
        try {
            jedis = JedisPoolUtil.getJedis();
            res = jedis.del(key);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("del():{},error:{}", key);
        } finally {
            JedisPoolUtil.release(jedis);
        }
        return res;
    }
    hash操作
    /**
     * mapHset 操作
     * @param key
     * @param field
     * @param value
     * @return 返回值为1
     */

    public static long mapHset(String key, String field, String value) {
        long res = -1;
        Jedis jedis = null;
        try {
            jedis = JedisPoolUtil.getJedis();
            res = jedis.hset(key, field, value);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("mapHset(){}{}{},errors{}", key, field, value, e);
        } finally {
            JedisPoolUtil.release(jedis);
        }
        return res;
    }

    /**
     * mapHget 获取值
     * @param key
     * @param field
     * @return
     */
    public static String mapHget(String key, String field) {
        String res = null;
        Jedis jedis = null;
        try {
            jedis = JedisPoolUtil.getJedis();
            res = jedis.hget(key, field);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("mapHget() {} {} , errors {}", key, field, e);
        } finally {
            JedisPoolUtil.release(jedis);
        }
        return res;
    }

    /**
     * 设置map值
     * @param key
     * @param map
     * @return 返回值 OK
     */
    public static String mapHmset(String key, Map<String, String> map) {
        String res = null;
        Jedis jedis = null;
        try {
            jedis = JedisPoolUtil.getJedis();
            res = jedis.hmset(key, map);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("mapHmset() {} {} , errors {}", key, map, e);
        } finally {
            JedisPoolUtil.release(jedis);
        }
        return res;
    }

    /**
     * mapHmget獲取值
     * @param key
     * @param fields
     * @return
     */
    public static List<String> mapHmget(String key, String... fields) {
        List<String> res = null;
        Jedis jedis = null;
        try {
            jedis = JedisPoolUtil.getJedis();
            res = jedis.hmget(key, fields);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("mapHmget(){}{},errors{}", key, fields, e);
        } finally {
            JedisPoolUtil.release(jedis);
        }
        return res;
    }
}

测试代码:

public class JedisUtilTest {
    @Test
    public void mapHset() {
        long res = JedisUtil.mapHset("map", "name", "zhangsan");
        System.out.println(res);
    }

    @Test
    public void mapHget() {
        String res = JedisUtil.mapHget("map", "name");
        System.out.println(res);
    }

    @Test
    public void mapHmset() {
        Map<String, String> map = new HashMap<>();
        map.put("name", "zhangsan");
        map.put("age", "13");
        map.put("birth", "1999-09-09");
        String res = JedisUtil.mapHmset("map", map);
        System.out.println(res);
    }

    @Test
    public void mapHmget() {
        List<String> res = JedisUtil.mapHmget("map", "name", "age", "birth");
        System.out.println(res);
    }

    @Test
    public void del() {
        long list = JedisUtil.del("add");
        System.out.println(list);
    }

    @Test
    public void listLrange() {
        List<String> list = JedisUtil.listLrange("list", 0, -1);
        System.out.println(list);
    }

    @Test
    public void listRPush() {
        long res = JedisUtil.listRPush("list", "aa", "bb", "cc", "bb");
        System.out.println(res);
    }

    @Test
    public void strSet() {
        String res = JedisUtil.strSet("k2", "v2", 60);
        System.out.println(res);
    }

    @Test
    public void strSet1() {
        String res = JedisUtil.strSet("k3", "v3");
        System.out.println(res);
    }

    @Test
    public void strGet() {
        String k2 = JedisUtil.strGet("k2");
        System.out.println(k2);
    }
}

Redis 发布订阅

Pub/SubPublish, Subscribe),即发布及订阅功能。基于事件的系统中,Pub/Sub是目前广泛使用的通信模型,它采用事件作为基本的通信机制,提供大规模系统所要求的松散耦合的交互模式:订阅者(如客户端)以事件订阅的方式表达出它有兴趣接收的一个事件或一类事件;发布者(如服务器)可将订阅者感兴趣的事件随时通知相关订阅者。

redis可以作为一个pub/sub 服务器,在发布者和订阅者之间起到消息路由的功能。

发布者通过 publish 命令向 redis 服务器 的 channel 发送消息,订阅该 channel 的全部 client 都会收到此消息。
订阅者可以通过 subscribe 和 psubscribe 命令向 redis server 订阅自己感兴趣的channel ;
Client即可以作为消息发送方,也可以作为消息接收方。
一个 client 可以订阅多个 channel,一个client可以向多个 channel 发送消息。

Redis 客户端可以订阅任意数量的频道。
下图展示了频道 channel1,以及订阅这个频道的client2、client5和 client1三个客户端之间的关系:

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

语法:
PSUBSCRIBE pattern [pattern …]
订阅一个或多个符合给定模式的频道。
每个模式以 * 作为匹配符,比如 it* 匹配所有以 it 开头的频道( it.news 、 it.blog 、 it.tweets 等等), news.* 匹配所有以 news. 开头的频道( news.it 、 news.global.today 等等),诸如此类。

PUBLISH channel message
将信息 message 发送到指定的频道 channel 。

PUNSUBSCRIBE [pattern [pattern …]]
指示客户端退订所有给定模式。
如果没有模式被指定,也即是,一个无参数的 PUNSUBSCRIBE 调用被执行,那么客户端使用 PSUBSCRIBE 命令订阅的所有模式都会被退订。在这种情况下,命令会返回一个信息,告知客户端所有被退订的模式。

SUBSCRIBE channel [channel …]
订阅给定的一个或多个频道的信息。

UNSUBSCRIBE [channel [channel …]]
指示客户端退订给定的频道。
如果没有频道被指定,也即是,一个无参数的 UNSUBSCRIBE 调用被执行,那么客户端使用 SUBSCRIBE 命令订阅的所有频道都会被退订。在这种情况下,命令会返回一个信息,告知客户端所有被退订的频道。

1.Redis模拟

示例1:

消息发送方 
192.168.153.160:6379> publish ch1 aa
(integer) 1
192.168.153.160:6379> publish ch1 bb
(integer) 1

消息接收方
192.168.153.160:6379> subscribe ch1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "ch1"
3) (integer) 1
1) "message"
2) "ch1"
3) "aa"
1) "message"
2) "ch1"
3) "bb"

示例2:

消息发送方 
192.168.153.160:6379> publish ch1 aa
(integer) 1
192.168.153.160:6379> publish ch2 bb
(integer) 1
192.168.153.160:6379> publish ch3 cc   
(integer) 0   //ch3没有接受方,返回值为0

消息接收方
192.168.153.160:6379> subscribe ch1 ch2
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "ch1"
3) (integer) 1
1) "subscribe"
2) "ch2"
3) (integer) 2
1) "message"
2) "ch1"
3) "aa"
1) "message"
2) "ch2"
3) "bb"

Redis 的发布与订阅实现也支持模式匹配(pattern matching): 客户端可以订阅一个带 * 号的模式, 如果某个/某些频道的名字和这个模式匹配, 那么当有信息发送给这个/这些频道的时候, 客户端也会收到这个/这些频道的信息。需要注意的是:通过pattern模式而接收到的信息的类型为 pmessage。

客户端将收到来自 news.zhengzhi 、 news.yule 等频道的信息。

注意:

因为所有接收到的信息都会包含一个信息来源:当信息来自频道时,来源是某个频道;当信息来自模式时,来源是某个模式。因此, 客户端可以用一个哈希表,将特定来源和处理该来源的回调函数关联起来。 当有新信息到达时, 程序就可以根据信息的来源, 在 O(1) 复杂度内, 将信息交给正确的回调函数来处理。比如
SUBSCRIBE foo
PSUBSCRIBE f*
  那么当有信息发送到频道 foo 时, 客户端将收到两条信息: 一条来自频道 foo ,信息类型为 message ; 另一条来自模式 f* ,信息类型为 pmessage 。
3、要在单独的线程中订阅,因为subscribe会阻塞当前线程的执行。你可以使用一个PubSub实例来订阅多个Channel。
4、一旦客户端进入订阅状态,客户端就只可接受订阅相关的命令SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE和PUNSUBSCRIBE除了这些命令,其他命令一律失效。
5、使用PUNSUBSCRIBE命令只能退订通过PSUBSCRIBE命令订阅的规则,不会影响SUBSCRIBE订阅的频道。

使用Jedis实现发布订阅需要继承抽象类JedisPubSub,在该类中定义了如下六个方法。分别表示

public void onMessage(String channel, String message)
监听到订阅模式接受到消息时的回调
public void onPMessage(String pattern, String channel, String message)
监听到订阅频道接受到消息时的回调
public void onSubscribe(String channel, int subscribedChannels)
订阅频道时的回调
public void onPSubscribe(String pattern, int subscribedChannels)
订阅频道模式时的回调
public void onUnsubscribe(String channel, int subscribedChannels)
取消订阅频道时的回调
public void onPUnsubscribe(String pattern, int subscribedChannels)
取消订阅模式时的回调

Jedis模拟-发送方不停地发送消息,接收方不断地接收消息。

1.准备工作:加入Maven依赖

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

第一步:

public class MyJedisPubSub extends JedisPubSub {
    @Override
    public void onMessage(String channel, String message) {
        System.out.println(String.format(
                "线程'%s':收到来自频道的'%s'消息,具体消息为:'%s'",
                Thread.currentThread().getName(), channel, message
        ));
    }

    @Override
    public void onSubscribe(String channel, int subscribedChannels) {
        System.out.println(String.format(
                "线程'%s':成功订阅频道'%s',目前该线程所订阅频道总数为:'%s'",
                Thread.currentThread().getName(), channel, subscribedChannels
        ));
    }

    @Override
    public void onUnsubscribe(String channel, int subscribedChannels) {
        System.out.println(String.format(
                "线程'%s':取消频道订阅频道'%s',目前该线程所订阅频道总数为:'%s'",
                Thread.currentThread().getName(), channel, subscribedChannels
        ));
    }
}

第二步:测试代码

1.先启动消息接收方

public class Subscriber {
    public static void main(String[] args) throws InterruptedException {
        MyJedisPubSub sub1 = new MyJedisPubSub();
        MyJedisPubSub sub2 = new MyJedisPubSub();

        //1号订阅频道线程
        new Thread(() -> {
            Jedis jedis = JedisPoolUtil.getJedis();
            jedis.subscribe(sub1, "ch");
            JedisPoolUtil.release(jedis);
        }).start();

        //2号订阅频道线程
        new Thread(() -> {
            Jedis jedis = JedisPoolUtil.getJedis();
            jedis.subscribe(sub2, "ch");
            JedisPoolUtil.release(jedis);
        }).start();

        Thread.sleep(10000);
    }
}

2.再启动消息发送方

public class Publisher {
    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        Jedis jedis = JedisPoolUtil.getJedis();
        while (i < 10000) {
            jedis.publish("ch", UUID.randomUUID().toString());
            JedisPoolUtil.release(jedis);
            Thread.sleep(1000);
            i+=1000;
        }
        JedisPoolUtil.destroyJedisPool();        //销毁连接池
    }
}

rides高级之事务

Redis事务的本质是一组命令的集合。

Redis的事务分为以下三个阶段:
  • 开启:以MULTI开始一个事务
  • 入队:将多个命令入队到事务中,这些命令并不会立即执行,而是放到等待执行的事务队列里面,最后一起执行
  • 执行:由EXEC命令触发事务
Redis事务相关命令:
  • multi:标记一个事务块的开始。
  • exec:开始执行事务块内的所有命令。
  • discard:取消事务,放弃执行事务块内的所有命令。
  • watch key [key …]:监视一个(多个) key ,如果在事务执行之前这些key中的任意一个被其他命令所改变,那么事务将被打断,这种情况通常可以在程序中重新尝试一次。
  • unwatch:取消 watch 命令对所有 key 的监视。
1.multi:标记一个事务块的开始
192.168.153.160:6379> multi
OK
192.168.153.160:6379> set k1 aa
QUEUED
192.168.153.160:6379> set k2 bb
QUEUED
192.168.153.160:6379> get k2
QUEUED
192.168.153.160:6379> set k3 cc
QUEUED
192.168.153.160:6379> exec
1) OK
2) OK
3) "bb"
4) OK
2.discard:取消事务,放弃执行事务块内的所有命令
192.168.153.160:6379> set k1 aa
OK
192.168.153.160:6379> multi
OK
192.168.153.160:6379> set k1 11
QUEUED
192.168.153.160:6379> get k1
QUEUED
192.168.153.160:6379> set k2 22
QUEUED
192.168.153.160:6379> discard
OK
192.168.153.160:6379> get k1
"aa"
192.168.153.160:6379> get k2
(nil)
3.exec:执行事务块内的所有命令

3.1 全体连坐(语法有问题,编译时就出错)

192.168.153.160:6379> multi
OK
192.168.153.160:6379> set k1 aa
QUEUED
192.168.153.160:6379> set k2 bb
QUEUED
192.168.153.160:6379> set k2 bb cc
QUEUED
192.168.153.160:6379> getset k2
(error) ERR wrong number of arguments for 'getset' command
192.168.153.160:6379> set k3 cc
QUEUED
192.168.153.160:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
192.168.153.160:6379> get k1
(nil)
192.168.153.160:6379> get k3
(nil)

3.2 冤头债主(语法没有问题,运行时才出错)

192.168.153.160:6379> set k1 aa
OK
192.168.153.160:6379> multi
OK
192.168.153.160:6379> set k2 bb
QUEUED
192.168.153.160:6379> incr k1
QUEUED
192.168.153.160:6379> set k3 cc
QUEUED
192.168.153.160:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
192.168.153.160:6379> get k1
"aa"
192.168.153.160:6379> get k2
"bb"
192.168.153.160:6379> get k3
"cc"
4.模拟信用卡可用余额及支出。用balance表示余额,用debt表示支出,两者数据应该一致

第一步:打开客户端A,在其中初始化余额和支出,在事务中间完成一次25元的消费,采用先监控再开启的方式,同时保证两笔金额变动在同一个事务内,如下图所示:

192.168.153.160:6379> set balance 100
OK
192.168.153.160:6379> set debt 0
OK
192.168.153.160:6379> watch balance
OK
192.168.153.160:6379> multi
OK
192.168.153.160:6379> decrby balance 25
QUEUED
192.168.153.160:6379> incrby debt 25
QUEUED
192.168.153.160:6379> exec
1) (integer) 75
2) (integer) 25

第二步:在客户端A中,再次监视余额balance,如下图所示:

192.168.153.160:6379> watch balance
OK

第三步:再打开一个客户端B,在其中给余额增加500元,如下图所示:

192.168.153.160:6379> incrby balance 500
(integer) 575

第三步:在客户端A中再做一次25元的消费,如下图所示:

192.168.153.160:6379> multi
OK
192.168.153.160:6379> decrby balance 25
QUEUED
192.168.153.160:6379> incrby debt 25
QUEUED
192.168.153.160:6379> exec
(nil)

上面代码说明,监控了key的情况下,如果key在别的进程中被被修改了,后面一个事务的执行失效。

第三步若采用unwatch,则会正确远行,如下图所示:

192.168.153.160:6379> unwatch
OK
192.168.153.160:6379> watch balance
OK
192.168.153.160:6379> multi
OK
192.168.153.160:6379> decrby balance 25
QUEUED
192.168.153.160:6379> incrby debt 25
QUEUED
192.168.153.160:6379> exec
1) (integer) 550
2) (integer) 50

Redis高级 之 pipeline

利用pipeline的方式可以从client端打包多条命令一起发出,不需要等待单条命令的响应返回,而redis服务端会处理完多条命令后会将多条命令的处理结果打包到一起返回给客户端。

示例:

public class PipelineTest {
    public static void main(String[] args) {
        int count = 5000;
        long start = System.currentTimeMillis();
        withoutPipeline(count);
        long end = System.currentTimeMillis();
        System.out.println("withoutPipeline: " + (end - start));

        start = System.currentTimeMillis();
        usePipeline(count);
        end = System.currentTimeMillis();
        System.out.println("withPipeline: " + (end - start));
    }
    private static void withoutPipeline(int count) {
        Jedis jedis = null;
        try {
            jedis = JedisPoolUtil.getJedis();
            for (int i = 0; i < count; i++) {
                jedis.incr("key1");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
           JedisPoolUtil.release(jedis);
        }
    }
    private static void usePipeline(int count) {
        Jedis jedis = null;
        try {
            jedis = JedisPoolUtil.getJedis();
            Pipeline pl = jedis.pipelined(); //使用管道
            for (int i = 0; i < count; i++) {
                pl.incr("key2");
            }
            pl.sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JedisPoolUtil.release(jedis);
        }
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值