Redis知识浓缩总结——基础篇

先膜拜大佬的知识框架总和:Redis学习汇总(已完结)_大鱼等于负的博客-CSDN博客_redis学习汇总(已完结)

一、安装:

Widows和Linux下如何安装Redis_大鱼等于负的博客-CSDN博客_linux 安装redis5

二、五大数据类型的基础和理解:

1、String(字符串)

添加 -set查询 - get追加  - append获取长度 - strlen判断是否存在 - exists的操作

自增 - incy(incyby)自减 - decr(decrby)操作

截取 - getrange替换 - setrange字符串操作

设置过期时间 - setex不存在设置 - setnx操作

多条添加mset多条查询mget操作

添加获取对象、先get再set,先获取key,如果没有,set值进去,返回的是get的值 - getset操作

总结
String是Redis中最常用的一种数据类型,也是Redis中最简单的一种数据类型。首先,表面上它是字符串,但其实他可以灵活的表示字符串、整数、浮点数3种值。Redis会自动的识别这3种值。

数据结构

String的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配.

如图中所示,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。

2、List(列表)

①lpush(左插入)、lrange(查询集合)、rpush(右插入)操作

②lpop(左移除)、rpop(右移除)操作

③lindex(查询指定下标元素)、llen(获取集合长度) 操作

④lrem(根据value移除指定的值)

⑥ltrim(截取元素)、rpoplpush(移除指定集合中最后一个元素到一个新的集合中)操作

⑦lset(更新)、linsert操作

⑧小结:

实际上是一个链表,before Node after , left,right 都可以插入值
如果key 不存在,创建新的链表
如果key存在,新增内容
如果移除了所有值,空链表,也代表不存在!
在两边插入或者改动值,效率最高! 中间元素,相对来说效率会低一点~
消息排队!消息队列 (Lpush Rpop), 栈( Lpush Lpop)!

⑨数据结构:

List的数据结构为快速链表quickList。

首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表。

它将所有的元素紧挨着一起存储,分配的是一块连续的内存。

当数据量比较多的时候才会改成quicklist。

因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next。

 Redis将链表和ziplist结合起来组成了quicklist。也就是将多个ziplist使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。


3、Set(集合)元素唯一不重复

①sadd(添加)、smembers(查看所有元素)、sismember(判断是否存在)、scard(查看长度)、srem(移除指定元素)操作

②srandmember(抽随机)操作

③spop(随机删除元素)、smove(移动指定元素到新的集合中)操作

④sdiff(差集)、sinter(交集)、sunion(并集)操作

⑤总结:可实现共同好友、共同关注等需求。

⑥数据结构:

Set数据结构是dict字典,字典是用哈希表实现的

Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。

4、Hash(哈希)

①hset(添加hash)、hget(查询)、hgetall(查询所有)、hdel(删除hash中指定的值)、hlen(获取hash的长度)、hexists(判断key是否存在)操作

②hkeys(获取所有key)、hvals(获取所有value)、hincrby(给值加增量)、hsetnx(存在不添加)操作

③总结:比String更加适合存对象~

④数据结构:

Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable。

5、zSet(有序集合)元素唯一不重复

①zadd(添加)、zrange(查询)、zrangebyscore(排序小-大)、zrevrange(排序大-小)、zrangebyscore withscores(查询所有值包含key)操作

②zrem(移除元素)、zcard(查看元素个数)、zcount(查询指定区间内的元素个数)操作

③总结:成绩表排序,工资表排序,年龄排序等需求可以用zset来实现!

④数据结构:

SortedSet(zset)是Redis提供的一个非常特别的数据结构,一方面它等价于Java的数据结构Map<String, Double>,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。

zset底层使用了两个数据结构

(1)hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。

(2)跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。

三、三大特殊数据类型的基础和理解:

1、Geospatial(地理位置)

城市经纬度查询: 经纬度查询
注意点1:两极无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入!
注意点2:有效的经度从-180度到180度。
注意点3:有效的纬度从-85.05112878度到85.05112878度。
注意点4:m 为米。km 为千米。mi 为英里。ft 为英尺。
①geoadd(添加)、geopos(查看)、geodist(计算距离)操作
②georadius(查询附近位置)操作

③ georadiusbymember (查找指定元素指定范围内的元素)、geohash (返回经纬度的hash值)、zrange、zrem(使用zset命令操作geo)

④总结:实际需求中,我们可以用来查询附近的人、计算两人之间的距离等。当然,那些所需的经纬度我们肯定要结合java代码来一次导入,手动查询和录入太过于浪费时间!

2、Hyperloglog(基数)

首先得明白什么是基数?
再数学层面上可以说是:两个数据集中不重复的元素~
但是再Redis中,可能会有一定的误差性。 官方给出的误差率是0.81%。
Hyperloglog的优点: 占用的内存是固定的,2^64个元素,相当于只需要12kb的内存即可。效率极高!
①pfadd(添加数据集)、pfcount(统计数据集)、pfmegre(合并数据集-自动去重)
②总结:如果在实际业务中,允许一定的误差值,我们可以使用基数统计来计算~效率非常高!比如:网站的访问量,就可以利用Hyperloglog来进行计算统计!

3、Bitmap(位存储)

Bitmap 位图,数据结构! 都是操作二进制位来进行记录,就只有0 和 1 两个状态!
①setbit(添加)、getset(获取)、bitcount(统计)操作

②总结:实际需求中,可能需要我们统计用户的登陆信息,员工的打卡信息等等。只要是事务的只有两个状态的,我们都可以用Bitmap来进行操作!!!

四、Redis中的事务和乐观锁如何实现?


事务(ACID):
①原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
②一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
③隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
④持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
在Redis事务没有没有隔离级别的概念!
在Redis单条命令式保证原子性的,但是事务不保证原子性!

脏读、幻读、不可重复读的区别:

1、脏读:脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
例如:
张三的工资为5000,事务A中把他的工资改为8000,但事务A尚未提交。
与此同时,
事务B正在读取张三的工资,读取到张三的工资为8000。
随后,
事务A发生异常,而回滚了事务。张三的工资又回滚为5000。
最后,
事务B读取到的张三工资为8000的数据即为脏数据,事务B做了一次脏读。

2、不可重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
例如:
在事务A中,读取到张三的工资为5000,操作没有完成,事务还没提交。
与此同时,
事务B把张三的工资改为8000,并提交了事务。
随后,
在事务A中,再次读取张三的工资,此时工资变为8000。在一个事务中前后两次读取的结果并不致,导致了不可重复读。

3、幻读:是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
例如:
目前工资为5000的员工有10人,事务A读取所有工资为5000的人数为10人。
此时,
事务B插入一条工资也为5000的记录。
这是,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。
 

乐观锁
①当程序中可能出现并发的情况时,就需要保证在并发情况下数据的准确性,以此确保当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的。
②没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题。
在Redis是可以实现乐观锁的!

1、Redis如何实现事务?

① 开启事务: multi

② 执行事务:exec

③ 放弃事务:discard

④运行时异常,除了语法错误不会被执行且抛出异常后,其他的正确命令可以正常执行

127.0.0.1:6379> exec  #执行事务。虽然对字符串数据进行自增操作报错了,但是其他的命令还是可以正常执行的
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) "23"

⑤总结:由以上可以得出结论,Redis是支持单条命令事务的,但是事务并不能保证原子性!

2、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

#线程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

#线程2,这个在事务执行前操作执行
127.0.0.1:6379> INCRBY money 20  #金钱+20
(integer) 120

③总结:进一步解释乐观锁和悲观锁的区别:悲观锁与乐观锁的实现(详情图解)_牧小农的博客-CSDN博客_悲观锁的实现方式
悲观锁: 什么时候都会出问题,所以一直监视着,没有执行当前步骤完成前,不让任何线程执行,十分浪费性能!一般不使用!
乐观锁: 只有更新数据的时候去判断一下,在此期间是否有人修改过被监视的这个数据,没有的话正常执行事务,反之执行失败!

猜你喜欢

转载自blog.csdn.net/qq_39367410/article/details/128251680