一.概述
Redis是什么?
Redis是远程服务字典服务,是一个开源的使用ANSI C语言编写,支持网络,可基于内存亦可持久化的日志型,Key-Value数据库,并提供多种语言的API。
redis会周期性把更新的数据写入磁盘或把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。免费开源,是NoSQL技术之一,也称为结构化数据库。
Redis能干嘛?
- 内存存储,持久化,内存中是断电即失,所以说持久化很重要
- 效率高,可用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器,计数器
…
二.Redis的基本了解
官方文档:
英文官方文档:https://redis.io/
中文文档:http://www.redis.cn/
Redis-Key常用命令:
http://www.redis.cn/commands.html
127.0.0.1:6379> ping #查看当前连接是否正常,正常返回PONG
PONG
127.0.0.1:6379> clear #清楚当前控制台(为了更好的看到下面输入的命令)
127.0.0.1:6379> keys * #查看当前库里所有的key
1) "db"
127.0.0.1:6379> FLUSHALL #清空所有库的内容
OK
127.0.0.1:6379> set name dingdada #添加一个key为‘name’ value为‘dingdada’的数据
OK
127.0.0.1:6379> get name #查询key为‘name’的value值
"dingdada"
127.0.0.1:6379> EXISTS name #判断当前key是否存在
127.0.0.1:6379> move name 1 #移除当前库1的key为‘name‘的数据
三.五大数据类型
1. Redis通用命令
2. Redis数据类型
2.1 String - 字符串类型
字符串命令:
String表面上是字符串,但其实他可以灵活的表示字符串,整数,浮点数3种值。Redis自动识别这3种值
127.0.0.1:6379> set key1 "hello world!" #注意点:插入的数据中如果有空格的数据,请用“”双引号,否则会报错!
OK
127.0.0.1:6379> set key1 hello world! #报错,因为在Redis中空格就是分隔符,相当于该参数已结束
(error) ERR syntax error
127.0.0.1:6379> set key1 hello,world! #逗号是可以的
OK
127.0.0.1:6379> incr num #指定key为‘num’的数据自增1,返回结果 相当于java中 i++
(integer) 1
127.0.0.1:6379> get num #一般用来做文章浏览量、点赞数、收藏数等功能
"1"
127.0.0.1:6379> decr num #可以一直减为负数~
(integer) -1
127.0.0.1:6379> decr num #一般用来做文章取消点赞、取消收藏等功能
#截取
127.0.0.1:6379> GETRANGE key1 0 4 #截取字符串,相当于java中的subString,下标从0开始,不会改变原有数据
"hello"
127.0.0.1:6379> get key1
"hello world!"
127.0.0.1:6379> GETRANGE key1 0 -1 #0至-1相当于 get key1,效果一致,获取整条数据
#替换
"hello world!"
127.0.0.1:6379> SETRANGE key2 5 888 #此语句跟java中replace有点类似,下标也是从0开始,但是有区别:java中是指定替换字符,Redis中是从指定位置开始替换,替换的数据根据你所需替换的长度一致,返回值是替换后的长度
(integer) 14
127.0.0.1:6379> get key2
"hello888world!"
127.0.0.1:6379> SETRANGE key2 5 67 #该处只替换了两位
(integer) 14
127.0.0.1:6379> get key2
"hello678world!"
#设置过期时间,跟Expire的区别是前者设置已存在的key的过期时间,而setex是在创建的时候设置过期时间
127.0.0.1:6379> setex name1 15 dingdada #新建一个key为‘name1’,值为‘dingdada’,过期时间为15秒的字符串数据
OK
127.0.0.1:6379> ttl name1 #查看key为‘name1’的key的过期时间
(integer) 6
#不存在设置
127.0.0.1:6379> setnx name2 dingdada2 #如果key为‘name2’不存在,新增数据,返回值1证明成功
(integer) 1
127.0.0.1:6379> get name2
"dingdada2"
127.0.0.1:6379> keys *
1) "name2"
127.0.0.1:6379> setnx name2 "dingdada3" #如果key为‘name2’的已存在,设置失败,返回值0,也就是说这个跟set的区别是:set会替换原有的值,而setnx不会,存在即不设置,确保了数据误操作~
(integer) 0
127.0.0.1:6379> get name2
"dingdada2"
2.2 Hash - Hash键值类型
Hash类型用于存储结构化数据
键-键-值
Hash 命令:
2.3 List - 列表类型
List列表就是一系列字符串的“数组”,按插入顺序排序
List列表最大长度为2的32次方-1,可以包含40亿个元素
List 命令
小结:
实际上是一个链表,before Node after , left,right 都可以插入值
如果key 不存在,创建新的链表
如果key存在,新增内容
如果移除了所有值,空链表,也代表不存在!
在两边插入或者改动值,效率最高! 中间元素,相对来说效率会低一点
消息排队!消息队列 (Lpush Rpop), 栈( Lpush Lpop)!
2.4 Set - 集合类型
Set集合是字符串的无序集合,集合成员是唯一的
2.5 Zset - 有序集合类型
Zset集合是字符串的有序集合,集合成员是唯一的
它的实质是每个数据对应了分数
总结:成绩表排序,工资表排序,年龄排序需求可以用zset来实现
四.三大特殊数据类型的学习和理解
1.Geospati:地理位置
城市维度查询:https://jingweidu.bmcx.com/
注意:
两极无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入
有效的经度从-180度到180度。
有效的纬度从-85.05112878度到85.05112878度。
m 为米。km 为千米。mi 为英里。ft 为英尺。
实际需求中,我们可以用来查询附近的人,计算两人之间的距离等。当然,那些所需的维度我们肯定要结合java代码来一次导入,手动查询和录入太过浪费时间。
2.Hyperloglog:基数
什么是基数?
数学层面上指两个数集中不重复的元素
但在redis中,可能会有一定误差,官方给的误差率为0.81%
Hyperloglog优点:占用的内存是固定的,2^64个元素,相等于只需要12kb的内存即可。效率高。
实际需求中,运行一定的误差值,我们可以使用技术统计来计算,效率非常高,如网站访问量,就可以利用Hyperloglog来进行计算统计
3.Bitmap:位存储
Bitmap位图,数据结构,都是操作二进制位来进行记录,只有0和1状态
实际需求中,可能需要我们统计用户的登录信息,员工的打开信息等。只要是事物的只有两种状态,我们都可以使用Bitmap来进行操作。
五.Redis中的事物和乐观锁如何实现?
1. 前言
事务的ACID:MySQL事务
在Redis中没有隔离级别的概念
在Redis单条命令式保证原子性,但是事物不保证原子性
乐观锁:
①当程序中可能出现并发的情况时,就需要保证在并发情况下数据的准确性,以此确保当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的。
②没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题。
在Redis是可以实现乐观锁的!
2. Redis如何实现事务?
正常执行事务
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set name dingyongjun #添加数据
QUEUED
127.0.0.1:6379> set age 26 #添加数据
QUEUED
127.0.0.1:6379> set high 172 #添加数据
QUEUED
127.0.0.1:6379> exec 执行事务
1) OK
2) OK
3) OK
127.0.0.1:6379> get name #获取数据成功,证明事务执行成功
"dingyongjun"
127.0.0.1:6379> get age
"26"
放弃事务
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set name dingyongjun #添加数据
QUEUED
127.0.0.1:6379> set age 26 #添加数据
QUEUED
127.0.0.1:6379> discard #放弃事务
OK
127.0.0.1:6379> get name #不会执行事务里面的添加操作
(nil)
③编译时异常,代码有问题,或者命令有问题,所有的命令都不会被执行
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set name dingyongjun #添加数据
QUEUED
127.0.0.1:6379> set age 23 #添加数据
QUEUED
127.0.0.1:6379> getset name #输入一个错误的命令,这时候已经报错了,但是这个还是进入了事务的队列当中
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set high 173 #添加数据
QUEUED
127.0.0.1:6379> exec #执行事务,报错,并且所有的命令都不会执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get name #获取数据为空,证明没有执行
(nil)
运行时异常,除了语法错误不会被执行且抛出异常后,其他的正确命令可以正常执行
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set name dingyongjun #添加字符串数据
QUEUED
127.0.0.1:6379> incr name #对字符串数据进行自增操作
QUEUED
127.0.0.1:6379> set age 23 #添加数据
QUEUED
127.0.0.1:6379> get age #获取数据
QUEUED
127.0.0.1:6379> exec #执行事务。虽然对字符串数据进行自增操作报错了,但是其他的命令还是可以正常执行的
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) "23"
127.0.0.1:6379> get age #获取数据成功
"23"
总结:由以上可以得出结论,redis是支持单条命令事务的,但是事务并不能保证原子性
3. Redis如何实现乐观锁
watch(‘监听’)
127.0.0.1:6379> set money 100 #添加金钱100
OK
127.0.0.1:6379> set cost 0 #添加花费0
OK
127.0.0.1:6379> watch money #监控金钱
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> DECRBY money 30 #金钱-30
QUEUED
127.0.0.1:6379> incrby cost 30 #花费+30
QUEUED
127.0.0.1:6379> exec #执行事务,成功!这时候数据没有发生变动才可以成功
1) (integer) 70
2) (integer) 30
多线程测试watch
线程一
#线程1
127.0.0.1:6379> set money 100 #添加金钱100
OK
127.0.0.1:6379> set cost 0 #添加花费0
OK
127.0.0.1:6379> watch money #开启监视(乐观锁)
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> DECRBY money 20 #金钱-20
QUEUED
127.0.0.1:6379> INCRBY cost 20 #花费+20
QUEUED
#这里先不要执行,先执行线程2来修改被监视的值
127.0.0.1:6379> exec #执行报错,因为我们监视了money这个值,如果事务要对这个值进行操作前
#监视器会判断这个值是否正常,如果发生改变,事务执行失败!
(nil)
线程二:
#线程2,这个在事务执行前操作执行
127.0.0.1:6379> INCRBY money 20 #金钱+20
(integer) 120
总结:
悲观锁: 什么时候都会出问题,所以一直监视着,没有执行当前步骤完成前,不让任何线程执行,十分浪费性能!一般不使用!
乐观锁: 只有更新数据的时候去判断一下,在此期间是否有人修改过被监视的这个数据,没有的话正常执行事务,反之执行失败!
六.Redis线程相关知识
Redis 是单线程的,这意味着它在任何给定时刻只会执行一个操作。虽然 Redis 是单线程的,但它通过使用非阻塞 I/O 和基于事件驱动的设计来实现高性能和并发处理。
Redis 的单线程架构使其非常快速,因为它无需处理线程之间的竞争条件和锁定。相反,Redis 依靠高效的内存数据存储和快速的非阻塞 I/O 操作来提供高吞吐量和低延迟。此外,Redis 还使用了一些技术来确保数据的一致性和持久性,如写日志和定期的数据快照。
虽然 Redis 主进程是单线程的,但它可以在后台启动多个附加线程来执行不同的任务,如数据持久化和复制。这些附加线程不会影响主线程的性能,因为它们通常处于低优先级状态,并且它们的工作不会阻塞主线程的执行。这使得 Redis 能够在单个线程下实现高性能和并发处理,成为一个非常受欢迎的内存数据库和缓存服务器。