5、Redis的发布和订阅
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接受消息。 Redis客户端可以订阅任意数量的频道。
5.1:订阅发布消息图
下图展示了频道channel1,以及订阅这个频道的三个客户端–client2、client5、和client1之间的关系:
当有新消息通过PUBLISH命令发送给频道channel1时,这个消息会被发送给订阅它的三个客户端:
5.1:发布订阅命令行实现
1、打开一个客户端订阅一个频道channel1频道
SUBSCRIBE <频道名>
:
2、打开另一个客户端,给channel1发布消息hello
publish channell hello
:
注意:这里的1是返回订阅该频道的数量
3、然后我们再打开另外一个客户端可以看到发送的消息
6、Redsi新数据类型
6.1、Bitmaps简介
在开发中,可能会遇到这种情况:需要统计用户的某些信息,如活跃或不活跃,登录或者不登录;又如 需要记录用户一年的打卡情况,打卡了是1, 没有打卡是0,如果使用普通的 key/value存储,则要记录 365条记录,如果用户量很大,需要的空间也会很大,所以 Redis 提供了 Bitmap 位图这中数据结构, Bitmap 就是通过操作二进制位来进行记录,即为 0 和 1;如果要记录 365 天的打卡情况,使用 Bitmap 表示的形式大概如下:0101000111000111…,这样有什么好处呢?当然就是节约内存 了,365 天相当于 365 bit,又 1 字节 = 8 bit , 所以相当于使用 46 个字节即可。 BitMap 就是通过一个 bit 位来表示某个元素对应的值或者状态, 其中的 key 就是对应元素本身,实际上 底层也是通过对字符串的操作来实现。Redis 从 2.2 版本之后新增了setbit, getbit, bitcount 等几个 bitmap 相关命令。
6.1.1、命令
setbit key offset value
: 设置key的第offset位为value(1或0)
# 案例,记录一周打卡情况 # 周一:1,周二:0,周三:0,周四:1,周五:1,周六:0,周天:0 (1 为打卡,0 为不打卡) 127.0.0.1:6379> setbit 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
getbit key offset
:获取offset设置的值,未设置的默认返回0
127.0.0.1:6379> getbit sign 3 # 查看周四是否打卡 1 127.0.0.1:6379> getbit sign 6 # 查看周七是否打卡 0
bitcoun key[start,end]
:统计key上位为1的个数
# 统计这周打卡的记录,可以看到只有3天是打卡的状态: 127.0.0.1:6379> bitcount sign 3
6.2、HyperLogLog简介
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积 非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。 HyperLogLog则是一种算法,它提供了不精确的去重计数方案。
什么是基数
比如数据集{1,3,5,7,5,7,8},那么这个数据集的基数集为{1,3,5,7,8},基数(不重复元素为5),基数估计就是在误差可接受的范围内,快速计算基数。
6.2.1、命令
pfadd<key><element>[element...]
:添加指定元素到HyperLogLog中
pfcount <key>
[key…]:计算name的近似基数
pfmerge destkey sourcekey[sourcekey...]
:将多个HyperLogLog合并为一个HyperLogLog,并集计算
127.0.0.1:6379> pfadd name qi (integer) 1 127.0.0.1:6379> pfadd name wang (integer) 1 # 重复的加不进去 127.0.0.1:6379> pfadd name qi (integer) 0 127.0.0.1:6379> pfadd name a b c (integer) 1 127.0.0.1:6379> pfcount name (integer) 5 127.0.0.1:6379> pfadd name1 a b c d e e f (integer) 1 127.0.0.1:6379> pfmerge name_result name name1 OK 127.0.0.1:6379> pfcount name_result (integer) 8
6.3、Geospatial简介
Redis3.2中增加了多GEO类型的支持,GEO,Geographic,地理信息的缩写。该类型就是元素的二维坐标,在地图上就是经纬度。redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。
6.3.1、命令
geoadd
解析:
语法 geoadd key longitude latitude member ... # 将给定的空间元素(纬度、经度、名字)添加到指定的键里面。 # 这些数据会以有序集合的形式被储存在键里面,从而使得georadius和georadiusbymember这样的命令可以在之后通过位置查询取得这些元素。 # geoadd命令以标准的x,y格式接受参数,所以用户必须先输入经度,然后再输入纬度。 # geoadd能够记录的坐标是有限的:非常接近两极的区域无法被索引。 # 有效的经度介于-180-180度之间,有效的纬度介于-85.05112878 度至 85.05112878 度之间。, #当用户尝试输入一个超出范围的经度或者纬度时,geoadd命令将返回一个错误。
测试:百度搜索经纬度查询,模拟真实数据
127.0.0.1:6379> geoadd china:city 116.23 40.22 beijing (integer) 1 127.0.0.1:6379> geoadd china:city 121.48 31.40 shanghai 113.88 22.55 shenzhen 120.21 30.20 hangzhou (integer) 3 127.0.0.1:6379> geoadd china:city 106.54 29.40 chongqing 108.93 34.23 xian 114.02 30.58 wuhan (integer) 3
geopos
解析:
# 语法 geopos key member[member...] # 从key里返回所有给定位置元素的位置(经度和维度)
测试:
127.0.0.1:6379> geopos china:city beijing 1) 1) "116.23000055551528931" 2) "40.2200010338739844" 127.0.0.1:6379> geopos china:city shanghai chongqing 1) 1) "121.48000091314315796" 2) "31.40000025319353938" 2) 1) "106.54000014066696167" 2) "29.39999880018641676"
geodist
解析:
# 语法 geodist key member1 member2 [unit] # 返回两个给定位置之间的距离,如果两个位置之间的其中一个不存在,那么命令返回空值。 # 指定单位的参数unit必须是以下单位的其中一个: # m表示单位为米 # km表示单位为千米 # mi表示单位为英里 # ft表示单位为英尺 # 如果用户没有显式地指定单位参数,那么geodist默认使用米作为单位。 #geodist命令在计算距离时会假设地球为完美的球形,在极限情况下,这一假设最大会造成0.5%的误差。
127.0.0.1:6379> geodist china:city beijing shanghai km "1088.7854" 127.0.0.1:6379> geodist china:city wuhan chongqing "732286.2747"
georadius
解析
#语法 georadius key longitude latitude radius m|km|ft|mi [withcoord][withdist] [withhash][asc|desc][count count] # 以给定的经纬度为中心, 找出某一半径内的元素
测试:
# china:city 中寻找坐标 100 30 半径为 1000km 的城市 127.0.0.1:6379> georadius china:city 100 30 1000 km 1) "chongqing" 2) "xian" # withdist 返回位置名称和中心距离 127.0.0.1:6379> georadius china:city 100 30 1000 km withdist 1) 1) "chongqing" 2) "635.2850" 2) 1) "xian" 2) "963.3171" # withcoord 返回位置名称和经纬度 127.0.0.1:6379> georadius china:city 100 30 1000 km withcoord 1) 1) "chongqing" 2) 1) "106.54000014066696167" 2) "29.39999880018641676" 2) 1) "xian" 2) 1) "108.92999857664108276" 2) "34.23000121926852302" # withdist withcoord 返回位置名称 距离 和经纬度 count 限定寻找个数 127.0.0.1:6379> georadius china:city 100 30 1000 km withdist withcoord count 1 1) 1) "chongqing" 2) "635.2850" 3) 1) "106.54000014066696167" 2) "29.39999880018641676"
georadiusbymember
解析
#语法 georadiusbymember key member radius m|km|ft|mi [withcoord][withdist] [withhash][asc|desc][count count] # 找出位于指定范围内的元素,中心点是由给定的位置元素决定
测试:
127.0.0.1:6379> georadiusbymember china:city beijing 1000 km 1) "beijing" 2) "xian"
geohash
解析:
#语法 geohash key member [member...] # Redis使用geohash将二维经纬度转换为一维字符串,字符串越长表示位置更精确,两个字符串越相似,表示距离越近。
测试:
127.0.0.1:6379> geohash china:city beijing chongqing 1) "wx4sucu47r0" 2) "wm5z22h53v0" 127.0.0.1:6379> geohash china:city beijing shanghai 1) "wx4sucu47r0" 2) "wtw6sk5n300"
zrem
GEO没有提供删除成员的命令,但是因为GEO的底层实现是zset,所以可以借用zrem命令实现对地理位 置信息的删除
127.0.0.1:6379> zrange china:city 0 -1 #查看所有元素 1) "chongqing" 2) "xian" 3) "shenzhen" 4) "wuhan" 5) "hangzhou" 6) "shanghai" 7) "beijing" 127.0.0.1:6379> zrem china:city beijing # 移除一个元素 (integer) 1 127.0.0.1:6379> zrange china:city 0 -1 1) "chongqing" 2) "xian" 3) "shenzhen" 4) "wuhan" 5) "hangzhou" 6) "shanghai"
7、Redis_Jedis测试
7.1、步骤
1、创建一个普通的maven项目,引入项目所需要jar包
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.2.2</version> </dependency>
2、编写测试类进行测试
public class JedisDemo1 { public static void main(String[] args) { // 创建Jedis对象 Jedis jedis = new Jedis("101.34.254.161", 6379); // 验证密码,没有设置的话忽略即可。 jedis.auth("password"); // 测试 String ping = jedis.ping(); System.out.println(ping); } }
注意:链接不上的,需要将redis.conf文件这两个地方做个变动
#bind 127.0.0.1 -::1 这个需要注释掉 protocted-mode no 这个需要改为no
并且需要在防火墙上添加6379端口,或者彻底关闭防火墙。
并且需要在服务器安全组添加6379端口放行即可。
测试类结果:
PONG
7.2、常用API
基本操作
public class JedisDemo1 { public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1", 6379); //验证密码,如果没有设置密码这段代码省略 // jedis.auth("password"); jedis.connect(); //连接 jedis.disconnect(); //断开连接 jedis.flushAll(); //清空所有的key } }
对key操作的命令
public class JedisDemo1 { 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','qi'>的键值 对:" + jedis.set("username", "qi")); 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 JedisDemo1 { 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 JedisDemo1 { 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 JedisDemo1 { 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 JedisDemo1 { 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"));//returnSet<String> System.out.println("散列hash的所有值为:" + jedis.hvals("hash"));//returnList<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")); } }
对zset操作命令
public class JedisDemo1 { public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1","6379"); jedis.zadd("china",100d,"shanghai"); Set<String> zrange =jedis.zrange("china",0,-1); for(String e : zrange){ System.out.println(e); } } }