1.三种特殊数据类型
1.1Geospatial
应用:附近的人,朋友定位,出租车距离
经纬度在线查询工具:http://www.jsons.cn/lngcode/
只有六个命令
-
geoadd:在地理位置添加坐标。
#geoadd:南北两级不能添加,我们通常通过java程序直接导入城市数据 #key value(经度 纬度 名称) #有效精度:-180 ~ 180 有效纬度:-85.05 ~85.05.当位置超过时,会报错 127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing 120.62 31.30 suzhou (integer) 2 127.0.0.1:6379> geoadd china:city 108.96 34.26 xian (integer) 1
-
geopos:获取地理位置坐标。
127.0.0.1:6379> geopos china:city xian 1) 1) "108.96000176668167114" 2) "34.25999964418929977"
-
geodist:计算两个位置之间的距离。
单位:m、km、mi 英里、ft 英尺。
127.0.0.1:6379> geodist china:city xian suzhou km "1138.2045"
-
georadius:以给定的经度纬度为中心,在一定半径内找出元素。
附近的人,定位
127.0.0.1:6379> georadius china:city 110 30 1000 km 1) "chongqing" 2) "xian" 127.0.0.1:6379> georadius china:city 110 30 500 km withcoord 1) 1) "chongqing" 2) 1) "106.49999767541885376" 2) "29.52999957900659211" 2) 1) "xian" 2) 1) "108.96000176668167114" 2) "34.25999964418929977" 127.0.0.1:6379> georadius china:city 110 30 500 km withdist #距离中心点
1 ) 1 ) "chongqing" 2 ) "341.9374" 2 ) 1 ) "xian" 2 ) "483.8340" 127.0.0.1:637 9> georadius china:city 110 30 500 km count 1 #获取指定数量的城市 1 ) "chongqing" 127.0.0.1:637 9> georadius china:city 110 30 500 km count 2 1 ) "chongqing" 2 ) "xian" -
georadiusbymember:以给定的城市为中心,找出某一半径内的元素。
127.0.0.1:6379> georadiusbymember china:city xian 1000 km 1) "xian" 2) "chongqing"
-
geohash:返回一个或多个位置对象的 geohash 值
#将二维的经纬度返回一维的11位字符串值,如果两个字符串越接近则表示两个城市越近 127.0.0.1:6379> geohash china:city xian 1) "wqj6zky6bn0"
GEO的底层实现其实是Zset,我们可以使用Zset命令来操作geo。
1.2Hyperloglog
-
基数:不重复的元素
-
网页的uv:统计访问网站的人数,一个人访问多次还是算做一次。
-
传统的方式:set保存用户的id,统计set中元素的数量即可,如果用户id很长还很多就会占用大量内存。
-
Redis Hyperloglog是基数统计的算法
- 优点:占用的内存是固定的,2^64不同元素只需要12kb内存,如果从内存角度来说统计基数Hyperloglog是首选。
- Hyperloglog会有0.18%的错误率,在统计uv任务上可以忽略不计!
127.0.0.1:6379> pfadd mykey q w e r t y u #添加元素
(integer) 1
127.0.0.1:6379> pfcount mykey #统计元素数量
(integer) 7
127.0.0.1:6379> pfadd mykey1 q o y p r
(integer) 1
127.0.0.1:6379> pfcount mykey1
(integer) 5
127.0.0.1:6379> pfmerge mykey2 mykey mykey1 #合并俩个key
OK
127.0.0.1:6379> pfcount mykey2
(integer) 9
1.3Bitmap
位存储:
- 统计疫情感染人数,14亿人口没感染的为0,感染的为1:0 1 0 0 0 1
- 登陆、未登录,活跃、不活跃,打卡、不打卡,凡是两个状态的都可以用Bitmaps
Bitmap位图,数据结构,都是操作二进制位来进行记录的,就只有0和1两个状态。
365天=365bit =46个字节左右。
2.Redis事务
redis单条命令是保证原子性的,但是redis事务不保证原子性的。
redis事务没有隔离级别的概念。
所有的命令在事务中不会直接执行,只有发起执行的时候才会执行(exec)。
一次性、顺序性、排他性!
redis事务本质:一组命令的集合!一个事务中所有的命令都会被序列化,在事务执行过程中会按照顺序执行。
每次执行完事务,事务就完事了,再次执行需要再次开启事务!
2.1事务开启与执行
redis事务:
- 开启事务—multi
- 命令入队
- 执行事务—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)> get k1
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec #执行事务
1) OK
2) OK
3) "v1"
4) OK
放弃事务—discard
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> discard #取消事务,事务队列中的所有命令都不会被执行
OK
127.0.0.1:6379> get k4 #set k4 命令没有执行
(nil)
异常
-
编译型异常:命令一旦写错,所有的事务都不会被执行
127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set k1 #错误命令 (error) ERR wrong number of arguments for 'set' command 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 k1 QUEUED 127.0.0.1:6379(TX)> exec #执行错误,没有执行 (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379> get k1 (nil)
-
运行时异常:语法性异常,比如1/0就会抛出异常,事务中其他命令正常执行
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)> get k2 QUEUED 127.0.0.1:6379(TX)> exec 1) (error) ERR value is not an integer or out of range 2) OK 3) "v2"
2.2乐观锁
- 悲观锁:认为什么时候都可能出现问题,做什么都会加锁
- 乐观锁:认为什么时候都不可能出现问题,做什么都不会加锁;更新的时候去判断一下,在此期间是否有人修改过数据(mysql里用的是version),只有version判断正确才会提交事务。
监视watch,一旦事务执行成功监控就会自动取消
- 更新watch
- 比较watch(下面例子watch由100变1000,所以事务失败)
正常执行:
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
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
再来一个客户端:
#先在一个客户端开启事务,此时不执行
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
#另一个客户端改变money
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000
OK
#再去执行事务
127.0.0.1:6379(TX)> exec
(nil) #返回nil执行失败,这就是加锁的好处,多线程体现的很明显
#解决,先放弃监视,再重新监视---解锁再加锁
127.0.0.1:6379> 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) 990
2) (integer) 30
3.Jedis
概念:Jedis是使用java操作redis的中间件,是redis官方推荐的java连接开发工具。
1.新建一个java空项目,加maven模块,导包
<!--导入jedis包-->
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.2.3</version>
</dependency>
<!--导入fastjson存数据-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
2.测试连接
public class TestPing {
public static void main(String[] args) {
//1.创建jedis对象
Jedis jedis = new Jedis("127.0.0.1", 6379);
//2.jedis能用的指令就是在虚拟机上的那些
System.out.println(jedis.ping());//PONG
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.append("user1","小猫");
jsonObject.append("user2","小狗");
String s = jsonObject.toString();
Transaction multi = jedis.multi();//开启事务
try {
multi.set("a",s);
multi.set("b",s);
multi.exec();//执行事务
} catch (Exception e) {
multi.discard();//放弃事务
e.printStackTrace();
} finally {
System.out.println(jedis.get("a"));
System.out.println(jedis.get("b"));
jedis.close();//关闭连接
}
}
}
4.整合SpringBoot
4.1基本连接
1.新建项目,导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
springboot 2.x后 ,原来使用的 Jedis 被 lettuce 替换,配置文件里也尽量使用lettuce,jedis有的会不生效。
jedis:采用的直连,多个线程操作,是不安全的。如果要避免不安全,使用jedis pool连接池!更像BIO模式
lettuce:采用netty,实例可在多个线程中共享,不存在线程不安全!可以减少线程数据了,更像NIO模式
RedisAutoConfiguratio只有两个简单的Bean:
- RedisTemplate
- StringRedisTemplate
当看到xxTemplate时可以对比RestTemplat、SqlSessionTemplate,通过使用这些Template来间接操作组件。那么这俩分别用于操作Redis和Redis中的String数据类型。
2.配置文件
所有的springboot配置类都会有一个自动配置类,并绑定一个properties配置文件。
注意使用时一定使用Lettuce的连接池。
#配置redis
spring.redis.host = 127.0.0.1
spring.redis.port=6379
3.测试
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
/*opsForValue 操作字符串 类似String opsForList 操作List 类似List redisTemplate.opsForList(); 除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务和基本的CRUD redisTemplate.exec(); 获取连接对象 RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); connection.flushAll();*/
redisTemplate.opsForValue().set("mykey","小猫");
System.out.println(redisTemplate.opsForValue().get("mykey"));
}
在idea中输出”小猫“,但是去redis中看乱码, 这关系到存储对象的序列化问题,在网络中传输的对象也是一样需要序列化,否者就全是乱码。
4.2自定义RedisTemplate
RedisTemplate源码分析:
默认的是JDK序列化形式
自定义RedisTemplate:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
// 将template 泛型设置为 <String, Object>
RedisTemplate<String, Object> template = new RedisTemplate();
// 连接工厂,不必修改
template.setConnectionFactory(redisConnectionFactory);
//序列化设置
// key、hash的key 采用String序列化方式
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// value、hash的value 采用 Jackson 序列化方式
template.setValueSerializer(RedisSerializer.json());
template.setHashValueSerializer(RedisSerializer.json());
template.afterPropertiesSet();
return template;
}
}
4.3自定义工具类
参考博客:https://www.cnblogs.com/zhzhlong/p/11434284.html、http://t.csdn.cn/r4Yy2
5.Redis.conf
进入配置文件:
解读配置文件:
单位
包含
网络,ip
通用(GENERAL),日志
daemonize yes #守护进程,默认是no,我们需要改成yes
pidfile /var/run/redis_6379.pid #如果以后台的方式运行我们就需要指定一个pid文件
#日志
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice
logfile "" #日志的文件位置名,为空就是默认的
databases 16 #数据库数量
always-show-logo no #是否总是显示logo
快照(SNAPSHOTTING),rdb
在规定的时间内,执行多少次操作,才会持久化到文件 .rdb .aof
redis是内存数据库,断电即失,所以需要持久化。
#3600秒内有一个key进行了修改,我们就进行持久化操作
#我们之后学习持久化会自己定义save
save 3600 1
save 300 100
save 60 10000
stop-writes-on-bgsave-error yes #持久化出错是否还继续操作
rdbcompression yes #是否压缩rdb文件,压缩的话就会消耗一些CPU资源
rdbchecksum yes #保存rdb文件的时候进行错误的校验
dir ./ #rdb文件保存目录--当前目录下
安全,设密码
但是一般我们通过命令来设置密码:
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456" #设置密码
OK
127.0.0.1:6379> auth 123456 #验证登录
OK
127.0.0.1:6379> config set requirepass "" #去掉密码
OK
客户端
内存
APPEND ONLY MODE:aof配置
appendonly no #默认是不开启aof的,默认使用的是rdb持久化,大部分情况下rdb完全够用
appendfilename "appendonly.aof" #持久化文件名字
# appendfsync always 每次修改都会同步,消耗性能
appendfsync everysec #每秒执行一次同步,可能会丢失一秒的数据
# appendfsync no 不执行同步,这时候操作系统自己执行同步,速度是最快的
6.持久化
6.1rdb
RDB概念:Redis Databases,在指定时间间隔后,将内存中的数据集快照写入数据库 ;在恢复时候,直接读取快照文件,进行数据的恢复。
Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中,文件名可以在配置文件中进行自定义。
工作原理
- 在进行 RDB 的时候,redis 的主线程是不会做 io 操作的,这确保了极高的性能,主线程会 fork() 一个子线程来完成该操作
- 子进程将数据集写入到一个临时 RDB 文件中
- 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件
- 这种工作方式使得 Redis 可以从写时复制机制(copy-on-write)中获益(因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求)
测试一下
127.0.0.1:6379> save
OK
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> set k3 v3
OK
127.0.0.1:6379> set k4 v4
OK
127.0.0.1:6379> set k5 v5
OK
127.0.0.1:6379> shutdown #关掉redis
not connected> exit
[root@haodoop100 bin]# redis-server mconfig/redis.conf
[root@haodoop100 bin]# redis-cli -p 6379 #再次连接redis
127.0.0.1:6379> get k1
"v1" #数据还在,持久化成功
触发机制
-
save的规则满足的情况下,会自动触发rdb原则
-
执行flushall命令,也会触发我们的rdb原则
-
退出redis,也会自动产生rdb文件
恢复rdb文件
只将rdb放在我们的启动目录里即可,启动的时候会自动检查rdb文件并恢复
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin"
优缺点
- 适合大规模的数据恢复
- 对数据的完整性要求不高
- 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了。
- fork进程的时候,会占用一定的内容空间。
6.2aof
概念:Append Only File,将我们所有的命令都记录下来,恢复的时候就把这个文件全部再执行一遍。
以日志的形式来记录每个写的操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据。换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
文件保存在appendonly.aof,appendonly no默认不开启aof,数据每秒一同步。
测试:
开启aof,重启redis,查看配置文件
如果aof文件有错,这时候redis是启动不起来的,我需要修改这个aof文件,redis给我们提供了一个工具
redis-check-aof
- 每一次修改都会同步,文件的完整性会更加好
- 没秒同步一次,可能会丢失一秒的数据
- 从不同步,效率最高
- 相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢!
- Aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化
aof默认是文件无线追加,文件会越来越大 如果aof文件大于64mb,fork一个新进程将我们的文件进行重写
6.3总结
1、RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
2、AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
4、同时开启两种持久化方式
- 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
- RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的Bug,留着作为一个万一的手段。
5、性能建议
- 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留 save 900 1 这条规则。
- 如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite 的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
- 如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。
7.发布订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
微信、微博、关注系统等。
Redis 客户端可以订阅任意数量的频道。
7.1命令
PUBLISH channel message #将信息发送到指定的频道。
SUBSCRIBE channel [channel …] #订阅给定的一个或多个频道的信息。
UNSUBSCRIBE [channel [channel …]] #退订给定的频道。
PSUBSCRIBE pattern [pattern …] #订阅一个或多个符合给定模式的频道。根据模式来订阅,可以订阅许多频道
PUNSUBSCRIBE [pattern [pattern …]] #退订所有给定模式的频道。
PUBSUB subcommand [argument [argument …]] #查看订阅与发布系统状态。
测试
开启两个端