前言
今天是正式学习redis是第一天,在此整理我redis的学习内容。
首先我们要知道当对Redis中的数据进行操作的时候,是在内存里面,最后才会把内存中的数据存在硬盘里面。但是在mysql数据库中数据是直接储存在硬盘之中。那么这两个都是数据库,有什么区别,我们在使用时应该怎样去选择呢?
在解答这个问题前,我们先要了解
计算机中内存和硬盘的区别
1.内存具有易失性,只有在我们当通电系统启动的时候,内存中数据才会存在,当断电之后,内存中数据就不会再存在了。而数据存在硬盘中则不会这样,在硬盘中无论通电与否,数据都可以长期存放在硬盘中。
2.容量不同,内存的容量一般比较小,硬盘的容量一般比较大。
3.速度不同,数据在内存中的读写速度是要比硬盘中快的,在内存中处理数据性能更好。
为什么要使用redis
那么在了解内存和硬盘的区别后我们就可以大致体会到redis和mysql的不同了和使用redis的目的了,redis一般用来做缓存,可以缓解数据库的压力,但是前提是当前的数据库有压力。比如说表里面有5000万条数据,并且这个表访问量还非常大。那么增加redis缓存就是一个非常适合的场景,这样可以防止大批量的大数据量查询将mysql压垮。
那我们说为什么MySQL在处理大批量数据查询时会不行呢,除了它的数据存在硬盘中访问速度慢外,还跟mysql的底层查询逻辑有关。
mysql在查询时会查用索引查询,它的数据会以B+树的形式存放,索引查询就像一个目录一样,通过选择目录进行快速查询,这样的查询模式虽然速度已经加快了,但是面对大量数据进行查询时还是太慢。
而redis则不然,它数据的储存方式便意味着它的查询速度会很快,redis中的数据采用key-value的形式储存,就像java中HashMap一样,每一个键对应一个值,这样在查询时就可以直接查询到想要的数据,不用像在mysql中需要遍历B+树去查询,再加上数据在内存中本来速度就会跟快,因此redis在处理大数据量高查询频率的情况会比mysql快很多。
redis的数据结构
在redis的实际使用处理业务时,该如何处理使用数据库key,value值取决我们的业务本身,根据利用redis本身的特点,再采用数据结构加算法的形式就基本可以我们的业务。
使用数据结构加算法可以实现很多的业务。redis的存储是以key-value的键值对的形式存储的,其中key都是String类型,value常见的可以是String,Hash,List,Set,Geospatial。
value是String类型
String类型是字符串类型,可以包含任何数据,最大可以是512MB,内部的实现结构和ArrayList类似,采用内分配冗余的形式,来减少内存的频繁分配(降低CPU压力)。
所谓内分配冗余是不在容量快要用尽时才进行扩容,会提前进行扩容。比如创建字符串的时候,len 的长度就是capacity,当需要修改时,如果存储容量不够的话,就会进行扩容,当字符串的容量小于1mb时,就会执行加倍扩容,即扩容到2*capacity,当容量大于1MB时,每次多增加1MB。
其使用场景如下:
1,不需要持久化的数据或者频繁更新的数据,比如验证码,因为验证码要求具有时间限制,没有必要长期储存。
2,对象缓存
可以通过序列化工具类,来缓存java对象,比如将某个对象序列化为json,需要用的时候再取出来,反序列化。常见的使用方式有mybatis二级缓存,接口级别缓存等等。
3,使用setnx来实现分布式锁,(使用分布式锁时一定要设置过期时间,防止不能释放锁,造成死锁)
4,可以用incr,decr来实现点赞数,因为其有原子性,在多线程下也是安全的。
5,分布式全局id
在一个大型的系统下,如果涉及到分库分表后,mysql 的自增id,肯定满足不了需要,如果用户量不大,可以每次从redis 这里通过自增获取id,但是如果用户量大,每次都拿肯定会给redis造成压力,可以一次取1000个,放本地缓存里,等用完了再去取。
常用的指令
set name zhencong --存放字符串键值对
mset name zhencong age 18 --批量存放键值对
SETNX name zhencong --如果不存在key为name,那么就设置value(分布式锁的原理)
get name -- 获取key
mget name age --批量获取key
DEL key -- 删除key
expire key 60 --设置过期时间,单位为秒
INCR key -- 将key中存储的数字加1
DECR key -- 将key中存储的数字减1
INCRBY key 2 --将key中存储的值都加上2
DECRBY key 2 --将key中存储的值都减去2
需要注意的是,尽量避免同时操作大批量的key,比如给所有的key设置过期时间,因为redis是单线程的,如果操作耗费太多时间,会造成redis的假死
Hash
value值是一个key-value的键值对,和java里的hashMap相似,当数据量较小是采用的是ziphash(默认),当数据量较大时采用hashtable。至于什么转换可以在配置文件进行配置。
应用场景
1,可以用于存储系统中对象的数据。
2,也可以用于做缓存,来解决数据一致性的问题(不推荐)。
常用的命令
hset hash name zhencong --设置值,
hget hash name -- 获取值
hmset hash name zhencong age 18 --批量设置
hmget hash name age --批量获取
hgetall hash 获取key的所有值
hkeys hash 获取hashmap中所有的key
hvals hash 获取hashmap中所有的value
List
redis的list为quickList(快速链表)即多个ziplist(压缩链表)组合起来的。如图所示:
ziplist:当数组容量较小的时候,会开辟一个连续的内存空间,只有当数组容量过多的时候,才会改为quickList,这样做的好处就是,如果采用普通的链表,当我们节点只存int类型的数据,还需要开辟两个指针,连接节点的上一个元素和下一个元素,会比较浪费空间。所以采用了quickList的方式,既能满足快速插入删除性能,又不会出现太大的空间浪费。
这么做也有缺点,就是当我们的list要变动时,肯定会涉及到内存重新分配和数据拷贝,这个是很影响性能的,list越大,修改元素的代价越大,所以一般我们不会存储过多元素。
redis的list是按插入顺序排序的,可以添加的一个节点到链表的头部(头插)或者尾部(尾插),是一个双向链表,对两端的操作性能会比较高,对中间节点的操作性能相对来说较差(因为得通过指针对遍历对应的节点)。
常用指令
rpush myList valu5e1 --向 list 的头部(右边)添加元素
rpush myList value2 value3 --向list的头部(最右边)添加多个元素
lpop myList # 将 list的尾部(最左边)元素取出
rpop myList2 value1 --从右边出
使用场景
可以实现栈和队列,需要注意的是,push和pop的操作是原子性的,所以操作redis的时候,直接用就行了,不要把list读出来,通过java修改,再放回去,这样不能保证数据一致性。(先读先写或先读后写)
比如想要实现栈,在使用rpush命令连续添加了多个数据后,可以使用rpop命令连续将数据从右边取出,这样就实现了数据的先进后出。
想要实现队列,在连续rpush命令添加了多个数据后,可以使用lpop命令连续将数据从左边取出,这样就实现了数据的先进先出。
今天就学习了这些东西,后面我将会继续整理。