腾讯云大神亲码“Redis 深度笔记”,不讲一句废话,全是精华

163 阅读10分钟

Hello,今天给各位童鞋们分享Redis,赶紧拿出小本子记下来吧!

image.png

Redis概述

redis是典型的nosql数据库,nosql就是 非关系型数据库,和Java里的 Map<String,Object>相似,所以我们可以使用redis来做缓存

nosql的特点:

  1. 方便扩展,数据之间没有关系,很好扩展

  2. 大数据量高性能,Nosql的缓存记录是一种细粒度的缓存,性能高

  3. 数据类型多样,不需要事先设计数据库

  4. 传统的RDBMS(关系型数据库) 和Nosql

redis: Remote Dictionary Server

内存存储,持久化(使用RDB和AOF(append only file)两种方式)

效率高,可以用于高速缓存

redis有五大数据类型和三大特殊数据类型(geospatial,hyperloglog,bitmaps)

redis是单线程的,使用了reactor模式的IO

为何redis是单线程?

redis是基于内存操作的,速度很快,所以redis的瓶颈不是CPU ,而是机器的内存和网络带宽,所以能使用单线程就使用单线程

redis使用单线程也很快?

运行速度是 CPU>内存>硬盘

多线程需要有CPU的上下文切换,不一定有单线程效率高

redis将所有数据都放在内存中,所以使用单线程去操作,效率会是最高,用多线程时,CPU会有上下文切换,耗时较长。对于内存系统来说,如果没有上下文切换,效率就是最高的,多次读写都是在一个CPU上,所以redis使用单线程

命令:

启动: 去点击redis目录下的 redis-server 启动redis服务器,点击 redis-cli启动redis客户端来连接服务器

redis默认有16个数据库(0-15)默认使用0号数据库 ,我们可以使用 select index来切换数据库

  • select index 切换数据库

  • dbsize :获取当前数据库的 键值对 的个数

  • keys * 得到当前数据库的所有键值对

  • flushdb 清空当前数据库

  • flushall 清空所有数据库

  • move key index 把某个 键值对 移动到 index数据库中去,那么当前数据库的键值对就会消失

  • expire key seconds 设置过期时间

  • pexpire key milliseconds 设置过期时间,以毫秒为单位(后面设置的过期时间会覆盖前面设置的)

  • ttl key 查看还有多久过期,返回值是 秒

  • pttl key 查看还有多久过期 ,返回值是 毫秒

  • expireat key timestamp 设置过期时间,前面是设置的是剩余的存活时间,这个设置的是存活到某个时间

  • pexpireat key timestamp

  • time获取当前时间戳,可用于与上面 配置

  • type key 获取类型

五大数据类型:

String

set key value

append key value 当key为 null的时候相当于 set,不然就是追加在 key的value 后面

strlen key 获取字符串长度,这是在String的对象里有记录字符串长度的属性,所以时间复杂度为 O(1)

setnx key value set if not exist 不存在才设置,设置失败的话返回 0

mset key1 value1 key2 value2 ... multiple set ,批量设置

mget key1 key2 key3

List

list链表里的value值是可以重复的

lpush key value1 value2 value3 插入数据

lrange key start stop 取数据,下标从0开始,到stop结束(包括stop),然后stop为-1的时候是取出start 后面所有

rpop key 弹出链表尾值,链表中就会少一个值

lpop key ,弹出链表头的值

llen key 得到链表的长度

lrem key count value 在链表里面删除值为 value ,的元素,只删除count个,从链表头开始删 (为何是从链表头开始查,因为redis对链表的掌控都是使用 链表头的,所以遍历自然也是从链表头开始遍历,那么查到了就删,所以可以说是从链表头开始删)

ltrim key start end 把当前的链表切割成 list[start]–>list[end] 这部分

rpoplpush src dst 从src链表中使用 rpop取出一个值,使用lpush来插入到dst链表中去

lset key index value 把链表中的index这个位置的元素改成 value,也就是根据索引修改值

linsert key before|after pivot value(pivot是中心,也就是要查找的值),(list的值可以重复,所以就是找到离链表头最近的那个进行操作,原理上面有说过)

Set

list是有序的,set是无序的,所以前面list能使用索引进行操作,但是set就不可以

set是无序不重复的集合

sadd key value 插入

smembers key 得到所有的成员

sismember key value 判断set是否存在 value这个值

scard key 查询set的数量

srem key value删除set里的value

spop key 随机删除

smove src dst value 将src里的value移动到dst里去,dst可以不存在,移动的时候创建,src这个set就会丢失value

srandmember key count随机取count个元素

sdiff key1 key2取两个set的差集

sinter key1 key2取两个set的交集

sunion key1 key2 取两个set的并集

Hash

类似于Java里的Map

hset key field value ,只能一对一对地赋值

hmset key field1 value1 field2 value2 多对赋值

hget key field 取出hash集合里的field对应的value值

hmget key field1 field2 field3 一次性取多个值

hgetall key取出全部的 键值对

hkeys key取出所有键值对的 key

hvals key取出所有键值对的 value

Zset

按照score进行排序的set,可以叫做有序不重复集合

zadd key score1 member1 score2 member2 插入值

zrange key start stop 查看值,stop可以是 -1

zrevrange key start stop 倒序查看

zrangebyscore key min max按照分值查看值,这里min和max的值可以是 -int(表示最小整数), +int(表示最大整数)

zrem key value 删除

三大特殊类型

geospatial

这个就是地图

geoadd key longitude latitude member 添加值,longitude指的是经度,latitude是纬度

geopos key value 查看

geodist key member1 member2 [unit] 根据两个元素的经纬度计算两地的距离,unit可以修改单位,默认是m,可以改成 km

hyperloglog

用于基数统计

基数 :每个重复的元素只计算一次

pfadd key element 插入

pfmerge dst src 将src合并到dst中去

pfcount key 计算基数

BitMap

类似于Map ,但是bitmap的值只能设置 0/1 ,所有只存在两种状态的结构都可以使用bitmaps

setbit key offset value设置值

getbit key offset 查看值

事务

redis事务: 一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中会按照顺序执行

Redis会将一个事务中的所有命令序列化,然后按顺序执行。Redis不可能在一个Redis事务的执行过程中插入执行另一个客户端发出的请求。这样便能保证Redis将这些命令作为一个单独的隔离操作执行。

在一个Redis事务中,Redis要么执行其中的所有命令,要么什么都不执行。因此,Redis事务能够保证原子性。

redis事务的特性: 一次性、顺序性、排他性

使用事务:

标记事务块的开始 multi

命令入队,redis会将命令逐个放入队列中

执行事务 exec ,在一个事务中执行所有先前放入队列的命令,返回值是一个数组,分别是每一条命令的返回值

丢弃命令队列 discard ,清除所有放入队列的命令

事务错误:

事务执行时会产生两种错误:

一个命令可能会在被放入队列时失败。因此,事务有可能在调用EXEC命令之前就发生错误。例如,这个命令可能会有语法错误(参数的数量错误、命令名称错误,等等),或者可能会有某些临界条件(例如:如果使用maxmemory指令,为Redis服务器配置内存限制,那么就可能会有内存溢出条件)。

在调用EXEC命令之后,事务中的某个命令可能会执行失败。例如,我们对某个键执行了错误类型的操作(例如,对一个字符串(String)类型的键执行列表(List)类型的操作)。

对于第一种,服务器会记住事务积累命令期间发生的错误。然后,Redis会拒绝执行这个事务,在运行EXEC命令之后,便会返回一个错误消息。最后,Redis会自动丢弃这个事务。

对于第二种,也就是在调用EXEC命令之后发生的事务错误,Redis不会进行任何特殊处理:在事务运行期间,即使某个命令运行失败,所有其他的命令也将会继续执行。

redis为何不支持回滚

只有当被调用的Redis命令有语法错误时,这条命令才会执行失败(在将这个命令放入事务队列期间,Redis能够发现此类问题),或者对某个键执行不符合其数据类型的操作:实际上,这就意味着只有程序错误才会导致Redis命令执行失败,这种错误很有可能在程序开发期间发现,一般很少在生产环境发现。

事务回滚并不能解决任何程序错误。例如,如果某个查询会将一个键的值递增2,而不是1,或者递增错误的键,那么事务回滚机制是没有办法解决这些程序问题的。请注意,没有人能解决程序员自己的错误,这种错误可能会导致Redis命令执行失败。正因为这些程序错误不大可能会进入生产环境,所以发Redis时选用更加简单和快速的方法,没有实现错误回滚的功能。

锁、监视器

悲观锁 : 认为什么时候都会出问题,无论什么时候才会加锁

乐观锁:很乐观,认为什么时候都不会出问题,所以不会上锁,更新数值的时候才判断在此期间是否有人修改过此数据 ,方法是使用版本 获取version---->更新的时候比较version

redis可以使用 CAS(检查再设置,compare and swap)实现乐观锁,用的是 watch 命令:

开启两个客户端 cli1 和 cli2

在 cli2 使用watch key给某个变量上锁,然后开启事务

在cli1 使用 set key 修改这个变量

在cli2的事务里get key 查看变量和 set key修改变量

cli2的事务提交 exec,发现返回值为nil,也就是事务允许失败

我们在 watch key给这个key上锁,拿到一个version,我们上锁之后在事务执行期间在第二个客户端执行了set,修改了version,当我们事务去执行的时候就会发现version版本不同,执行失败。

要拿到最新的版本需要 unwatch–> watch

Java操作redis

jedis是redis官方推荐的Java连接开发工具

依赖:

        <groupId>redis.clients</groupId>

        <artifactId>jedis</artifactId>

但是我们在使用spring boot的时候,依赖则是:

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-data-redis</artifactId>

spring-boot-starter-data-redis使用的是 lettuce的方式连接redis

jedis: 采用的是直连,多个线程操作时不安全的,如果想要避免不安全的,使用jedis pool连接池!像BIO模式

lettuce: 采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况,可以减少线程数据了,更像NIO模式

使用:

导入spring-boot-starter-data-redis依赖

配置文件里配置 spring.redis.host,spring.redis.port

自动注入 RedisTemplate

查看里面的方法,可以发现几乎全部都是RedisSerializer类型,所以我们在向redis设置值的时候,所有对象都需要进行序列化,否则会报错

序列化方法:

使用ObjectMapper类的writeValueAsString(Object value)

对象实现 Serializable接口

对reids的操作方法:

使用redis的opsforXXXXX().set()来进行设置值的操作

使用opsForXXXXX().get() 来进行取值的操作

好啦,今天的文章就到这里了,希望能够帮助屏幕前迷茫的你们