Redis

一、基础知识

  1. redis默认有16个数据库,默认使用第0个,使用select 1切换为第一个数据库。
  2. 使用dbsize,查看数据库容量
  3. 使用key * 查看所有的key
  4. 清空当前数据库 flushdb
  5. 清空全部数据库flushall
  6. redis是单线程的,redis是基于内存操作,cpu并不能成为redis的瓶颈。redis的瓶颈是根据机器的内存和网络带宽。

Redis 为什么单线程还这么快?

1 、误区 1 :高性能的服务器一定是多线程的?

2 、误区 2 :多线程(CPU上下文会切换!)一定比单线程效率高!

先去CPU>内存>硬盘的速度要有所了解!

核心:redis 是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程
(CPU上下文会切换:耗时的操作!!!),对于内存系统来说,如果没有上下文切换效率就是最高
的!多次读写都是在一个CPU上的,在内存情况下,这个就是最佳的方案!

二、五大数据类型

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间
件MQ。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合
(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间
(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU
驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过
Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

Redis-Key

127 .0.0.1:6379> keys *  # 查看所有的key
(empty list or set)
127 .0.0.1:6379> set name kuangshen  # set key
OK
127 .0.0.1:6379> EXISTS name  # 判断当前的key是否存在
(integer) 1
127 .0.0.1:6379> move name 1 # 移除当前的key
(integer) 1
127 .0.0.1:6379> get name
"qinjiang"
127 .0.0.1:6379> EXPIRE name 10 # 设置key的过期时间,单位是秒
(integer) 1
127 .0.0.1:6379> ttl name  # 查看当前key的剩余时间
(integer) 4
127 .0.0.1:6379> type name  # 查看当前key的一个类型!
string

1、String(字符串)

设置获得值

127 .0.0.1:6379> set key1 v1  # 设置值
OK
127 .0.0.1:6379> get key1 # 获得值
"v1"
127 .0.0.1:6379> keys * # 获得所有的key
1 ) "key1"

判断存在EXISTS ,追加APPEND ,长度STRLEN

127 .0.0.1:6379> EXISTS key1  # 判断某一个key是否存在
(integer) 1
127 .0.0.1:6379> APPEND key1 "hello" # 追加字符串,如果当前key不存在,就相当于setkey
(integer) 7
127 .0.0.1:6379> STRLEN key1  # 获取字符串的长度!
(integer) 7

自增incr 自减decr 步长INCRBY、DECRBY

# i++
# 步长 i+=
127 .0.0.1:6379> set views 0 # 初始浏览量为 0
OK
127 .0.0.1:6379> get views
"0"
127 .0.0.1:6379> incr views  # 自增 1 浏览量变为 1
(integer) 1
127 .0.0.1:6379> incr views
(integer) 2
127 .0.0.1:6379> get views
"2"
127 .0.0.1:6379> decr views  # 自减 1 浏览量-1
(integer) 1
127 .0.0.1:6379> decr views
(integer) 0
127 .0.0.1:6379> decr views
(integer) -1
127 .0.0.1:6379> get views
"-1"
127 .0.0.1:6379> INCRBY views 10 # 可以设置步长,指定增量!
(integer) 9
127 .0.0.1:6379> INCRBY views 10
(integer) 19
127 .0.0.1:6379> DECRBY views 5

获取字符串范围 GETRANGE

127 .0.0.1:6379> set key1 "hello,kuangshen" # 设置 key1 的值
OK
127 .0.0.1:6379> get key1
"hello,kuangshen"
127 .0.0.1:6379> GETRANGE key1 0 3 # 截取字符串 [0,3]
"hell"
127 .0.0.1:6379> GETRANGE key1 0 -1 # 获取全部的字符串 和 get key是一样的
"hello,kuangshen"

替换SETRANGE

127 .0.0.1:6379> set key2 abcdefg
OK
127 .0.0.1:6379> get key2
"abcdefg"
127 .0.0.1:6379> SETRANGE key2 1 xx # 替换指定位置开始的字符串!
(integer) 7
127 .0.0.1:6379> get key2
"axxdefg"

设置过期时间setex ,不存在在设置setnx

127 .0.0.1:6379> setex key3 30 "hello" # 设置key3 的值为 hello,30秒后过期
OK
127 .0.0.1:6379> ttl key3
(integer) 26
127 .0.0.1:6379> get key3
"hello"
127 .0.0.1:6379> setnx mykey "redis" # 如果mykey 不存在,创建mykey
(integer) 1
127 .0.0.1:6379> keys *
1 ) "key2"
2 ) "mykey"
3 ) "key1"
127 .0.0.1:6379> ttl key3
(integer) -2
127 .0.0.1:6379> setnx mykey "MongoDB" # 如果mykey存在,创建失败!
(integer) 0
127 .0.0.1:6379> get mykey
"redis"

设置或获取多个mset、mget

127 .0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 同时设置多个值
OK
127 .0.0.1:6379> keys *
1 ) "k1"
2 ) "k2"
3 ) "k3"
127 .0.0.1:6379> mget k1 k2 k3 # 同时获取多个值
1 ) "v1"
2 ) "v2"
3 ) "v3"

注意:msetnx 是一个原子性的操作,要么一起成功,要么一起失败!

127 .0.0.1:6379> msetnx k1 v1 k4 v4  # msetnx 是一个原子性的操作,要么一起成功,要么一起失败!
(integer) 0
127 .0.0.1:6379> get k4
(nil)

关于string的高级用法

# 对象
set user:1 {name:zhangsan,age:3}  # 设置一个user:1 对象 值为 json字符来保存一个对象!

127 .0.0.1:6379> mset user:1:name zhangsan user:1:age 2
OK
127 .0.0.1:6379> mget user:1:name user:1:age
1 ) "zhangsan"
2 ) "2"

getset

127 .0.0.1:6379> getset db redis # 如果不存在值,则返回 nil
(nil)
127 .0.0.1:6379> get db
"redis
127 .0.0.1:6379> getset db mongodb  # 如果存在值,获取原来的值,并设置新的值
"redis"
127 .0.0.1:6379> get db
"mongodb"

2、List(列表)

在redis里面,我们可以把list玩成 ,栈、队列、阻塞队列!

所有的list命令都是用l开头的,Redis不区分大小命令

左/右插入、左/右移除、获取

127 .0.0.1:6379> LPUSH list one  # 将一个值或者多个值,插入到列表头部 (左)
(integer) 1
127 .0.0.1:6379> LPUSH list two
(integer) 2
127 .0.0.1:6379> LPUSH list three
(integer) 3
127 .0.0.1:6379> LRANGE list 0 -1 # 获取list中值!
1 ) "three"
2 ) "two"
3 ) "one"
127 .0.0.1:6379> LRANGE list 0 1 # 通过区间获取具体的值!
1 ) "three"
2 ) "two"
127 .0.0.1:6379> Rpush list righr  # 将一个值或者多个值,插入到列表位部 (右)
(integer) 4
127 .0.0.1:6379> LRANGE list 0 -1
1 ) "three"
2 ) "two"
3 ) "one"
4 ) "righr"
127 .0.0.1:6379> LRANGE list 0 -1
1 ) "three"
2 ) "two"
3 ) "one"
4 ) "righr"
127 .0.0.1:6379> Lpop list  # 移除list的第一个元素
"three"
127 .0.0.1:6379> Rpop list  # 移除list的最后一个元素
"righr"
127 .0.0.1:6379> LRANGE list 0 -1
1 ) "two"
2 ) "one"

根据下标取值lindex rindex

127 .0.0.1:6379> LRANGE list 0 -1
1 ) "two"
2 ) "one"
127 .0.0.1:6379> lindex list 1 # 通过下标获得 list 中的某一个值!
"one"
127 .0.0.1:6379> lindex list 0
"two"

获取长度Llen rlen

127 .0.0.1:6379> Lpush list one
(integer) 1
127 .0.0.1:6379> Lpush list two
(integer) 2
127 .0.0.1:6379> Lpush list three
(integer) 3
127 .0.0.1:6379> Llen list # 返回列表的长度
(integer) 3

移除指定的值Lrem

127 .0.0.1:6379> LRANGE list 0 -1
1 ) "three"
2 ) "three"
3 ) "two"
4 ) "one"
127 .0.0.1:6379> lrem list 1 one # 移除list集合中指定个数的value,精确匹配
(integer) 1
127 .0.0.1:6379> LRANGE list 0 -1
1 ) "three"
2 ) "three"
3 ) "two"
127 .0.0.1:6379> lrem list 1 three
(integer) 1
127 .0.0.1:6379> LRANGE list 0 -1
1 ) "three"
2 ) "two"
127 .0.0.1:6379> Lpush list three
(integer) 3
127 .0.0.1:6379> lrem list 2 three
(integer) 2
127 .0.0.1:6379> LRANGE list 0 -1
1 ) "two"

trim 修剪

127 .0.0.1:6379> keys *
(empty list or set)
127 .0.0.1:6379> Rpush mylist "hello"
(integer) 1
127 .0.0.1:6379> Rpush mylist "hello1"
(integer) 2
127 .0.0.1:6379> Rpush mylist "hello2"
(integer) 3
127 .0.0.1:6379> Rpush mylist "hello3"
(integer) 4
127 .0.0.1:6379> ltrim mylist 1 2 # 通过下标截取指定的长度,这个list已经被改变了,截断了
只剩下截取的元素!
OK
127 .0.0.1:6379> LRANGE mylist 0 -1
1 ) "hello1"
2 ) "hello2"

rpoplpush

移除列表的最后一个元素,将他移动到新的列表中

127 .0.0.1:6379> rpush mylist "hello"
(integer) 1
127 .0.0.1:6379> rpush mylist "hello1"
(integer) 2
127 .0.0.1:6379> rpush mylist "hello2"
(integer) 3
127 .0.0.1:6379> rpoplpush mylist myotherlist # 移除列表的最后一个元素,将他移动到新的
列表中!
"hello2"
127 .0.0.1:6379> lrange mylist 0 -1 # 查看原来的列表
1 ) "hello"
2 ) "hello1"
127 .0.0.1:6379> lrange myotherlist 0 -1 # 查看目标列表中,确实存在改值!
1 ) "hello2"

lset 替换指定下标

127 .0.0.1:6379> EXISTS list # 判断这个列表是否存在
(integer) 0
127 .0.0.1:6379> lset list 0 item # 如果不存在列表我们去更新就会报错
(error) ERR no such key
127 .0.0.1:6379> lpush list value1
(integer) 1
127 .0.0.1:6379> LRANGE list 0 0
1 ) "value1"
127 .0.0.1:6379> lset list 0 item # 如果存在,更新当前下标的值
OK
127 .0.0.1:6379> LRANGE list 0 0
1 ) "item"
127 .0.0.1:6379> lset list 1 other # 如果不存在,则会报错!
(error) ERR index out of range

linsert 插入带指定某个元素前后

127 .0.0.1:6379> Rpush mylist "hello"
(integer) 1
127 .0.0.1:6379> Rpush mylist "world"
(integer) 2
127 .0.0.1:6379> LINSERT mylist before "world" "other"
(integer) 3
127 .0.0.1:6379> LRANGE mylist 0 -1
1 ) "hello"
2 ) "other"
3 ) "world"
127 .0.0.1:6379> LINSERT mylist after world new
(integer) 4
127 .0.0.1:6379> LRANGE mylist 0 -1
1 ) "hello"
2 ) "other"
3 ) "world"
4 ) "new"

总结

如果移除了所有值,空链表,也代表不存在!

在两边插入或者改动值,效率最高! 中间元素,相对来说效率会低一点~

消息排队!消息队列 (Lpush Rpop), 栈( Lpush Lpop)

3、Set(集合)

  • sadd key member:添加值
  • smembers key:查看key中所有值
  • smember key value:判断value值是否在key集合中
  • scard key:获取key集合中的内容元素个数
  • srem key member:移除key集合中的指定元素
  • randmember key [count]:随机抽取key中count数量的元素
  • spop key [count]:随机删除key中count数量的元素
  • smove source destination member:将source中指定的值移动到另外一个set集合中
  • sdiff key [key…]:差集
  • sinter key [key…]:交集
  • sunion key [key…]:并集

4、Hash(哈希)

  • hset key field value:set一个具体的值,值是一个map集合,field-value
  • hget key field:获取一个字段值
  • hmset key field [key field …]:同时set多个值
  • hmget key field [field…]:同时获取多个值
  • hgetall key:获取所有的值
  • hdel key field [field…]:删除hash指定的field字段,对应的value也会删除
  • hlen key:获取hash表的字段数量
  • hexists key field:判断key中field字段是否存在
  • hkeys key:只获得所有的field
  • hvals key:只获得所有的value
  • hincrby key field increment:指定增量
  • hsetnx key field value:如果存在则设置值,如果存在则不能设置

5、Zset(有序集合)

  • zdd key score value:增加值
  • zrangebyscore key min max [withscores]:显示全部的用户,从小到大 [附带成绩]
  • zrevrangebyscore key key max min [withscores]:显示全部的用户,从大到小 [附带成绩]
  • zrem key member [member…]:移除有序集合中的指定元素
  • zcard key:获取有序集合中的个数
  • zcount key min max:获取指定区间的成员数量

三、三种特殊数据类型

geospatial(地理位置)

  • geoadd key 经度 纬度 member:添加数据
  • geopos key member:获得指定城市的经度和纬度
  • geodist key member1 member2 [unit]:查看member1到member2的距离[单位]
  • georadius key 经度 纬度 半径 单位:以这个经度纬度为中性寻找半径以内的位置
  • georadiusbymember key member radius 单位:找出member周围的元素
  • geohash key member:将二维的经纬度转换成一维的字符串

Hyperloglog(基数)

  • pfadd key element:创建一组元素
  • pfcount key:统计key元素的基数数量
  • pfmerge destkey sourcekey [sourcekey…]:合并两组->destkey

Bitmap(位存储)

  • setbit key offset value:设置值
  • getbit key offset:获取值
  • bitcount key [start end]:统计数量

四、事务

  • Redis事务本质:一组命令的集合,一个事务中所有的命令都会被序列化,在事务执行的过程中,会按照顺序执行。
  • Redis事务没有隔离级别的概念
  • Redis单条命令是保存原子性的,但是事务不保证原子性

redis的事务

  • 开启事务(multi)
  • 命令入队
  • 执行事务(exec) | 放弃事务(discard)

编译型异常(代码有问题,命令有错),事务中所有的命令都不会被执行

运行时异常,如果事务队列中存在语法性,那么执行命令的时候,其他命令式可以正常执行的,错误命令抛出异常

watch监控

悲观锁:

认为什么时候都会出问题,无论做什么都会加锁!

乐观锁:

认为什么时候都不会出问题,所以不会上锁! 更新数据的时候去判断一下,在此期间是否有人修改过这个数据,

获取version
更新的时候比较 version

测试多线程修改值 , 使用watch 可以当做redis的乐观锁操作!

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 # 监视 money 对象
OK
127 .0.0.1:6379> multi # 事务正常结束,数据期间没有发生变动,这个时候就正常执行成功!
OK
127 .0.0.1:6379> DECRBY money 20
QUEUED
127 .0.0.1:6379> INCRBY out 20
QUEUED
127 .0.0.1:6379> exec
1 ) (integer) 80
2 ) (integer) 20

如果修改失败,解锁(unwatch)获取最新(watch)的值就好

五、Jedis

导入对应的依赖

<!--导入jedis的包-->
<dependencies>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>

创建对象,命令与redis相同

六、SpringBoot整合

@Configuration
    public class RedisConfig {
        @Bean
        @SuppressWarnings("all")
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
            // 我们为了自己开发方便,一般直接使用 <String, Object>      
            RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
            template.setConnectionFactory(factory);
            // Json序列化配置      
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
            ObjectMapper om = new ObjectMapper();
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jackson2JsonRedisSerializer.setObjectMapper(om);
            // String 的序列化      
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
            // key采用String的序列化方式    
            template.setKeySerializer(stringRedisSerializer);
            // hash的key也采用String的序列化方式    
            template.setHashKeySerializer(stringRedisSerializer);
            // value序列化方式采用jackson  
            template.setValueSerializer(jackson2JsonRedisSerializer);
            // hash的value序列化方式采用jackson      
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            template.afterPropertiesSet();
            return template;
        }
    }

七、Redis.conf详解

网络

    bind 127.0.0.1 # 绑定的ip
    protected-mode yes # 保护模式
    port 6379 # 端口设置

通用 GENERAL

    daemonize yes  # 以守护进程的方式运行,默认是no,我们需要自己开启为yes
    pidfile /var/run/redis_6379.pid # 如果以后台的方式运行,我们就需要指定一个pid文件
    # 日志
    # Specify the server verbosity level.
    # This can be one of:
    # 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 # 数据库的数量,默认是16个
    always-show-logo yes # 是否总是显示logo

快照

  • 持久化,在规定的时间内,执行了多少次操作,则会持久化到文件.rdb.aof
  • Redis是内存数据库。,如果没有持久化,那么数据会断点缺失
    save 900 1 # 如果900s内,至少有1个key进行了修改,我们就进行持久化操作
    save 300 10 # 如果300s内,至少有10个key进行了修改,我们就进行持久化操作
    save 60 10000 # 如果60s内,至少有10000个key进行了修改,我们就进行持久化操作
    stop-writes-on-bgsave-error yes # 持久化如果出错,是否还要继续工作
    rdbcompression yes # 是否压缩rdb文件,需要消耗一些cpu资源
    rdbchecksum yes # 保存rdb文件的时候,进行错误的检查校验
    dir ./ # rdb文件保存的目录

SECURITY 安全

  • 可以设置redis的密码,默认是没有密码的
    config get requirepass # 获取redis的密码
    config set requirepass password # 设置redis的密码
    auth password # 使用密码进行登录

限制 CLIENTS

    maxclients 10000 # 设置能连接上redis的最大客户端数量
    maxmemory <bytes> # redis配置最大的内存容量
    maxmemory-policy noeviction # 内存达到上限之后的处理策略

APPEND ONLY MODE AOF配置

    appendonly no # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分的情况下,rdb完全够用
    appendfilename "appendonly.aof" # 持久化的文件名名字
    # appendfsync always # 每次修改都会sync,消耗性能
    appendfsync everysec # 每秒执行一次sync,可能会丢失中这1s的数据
    # appendfsync no # 不执行sync,这个时候操作系统自动同步数据,速度最快

八、Redis持久化

Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。

RDB(Redis DataBase)

  • Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写到一个临时文件中,等待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是很敏感,那么RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化的数据,可能丢失。
  • RDB保存的文件是dump.rdb,在配置文件中配置的。
触发机制
  • save的规则满足的情况下,会自动触发rdb规则
  • 执行flushall命令,也会触发rdb规则
  • 退出redis,也会产生rdb文件
恢复rdb文件
  • 只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb,恢复其中的数据
  • 查看需要存在的位置:config get dir
优点:
  • 适合大规模的数据恢复
  • 对数据的完整性要求不高
缺点:
  • 需要一定的时间间隔进程操作,如果redis意外崩溃了,最后一次修改的数据就不会生效
  • fork进程的时候,会占用一定的内存空间

AOF(Append Only File)

将我们所有的命令都记录下来。

  • 以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
  • AOF保存的文件是appendonly.aof文件
  • redis-check-aof --fix aof文件:用来修复这个aof文件

优点:

  • 每一次修改都同步,文件的完整性会更好
  • 每秒同步一次,可能会丢失一秒的数据
  • 从不同步,效率是最高的

缺点:

  • 相对于数据文件来说,aof远远大于rdb,修复的数据也比rdb慢
  • aof运行效率也要比rdb慢

扩展:

  • RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
  • AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始 的数据,AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重 写,使得AOF文件的体积不至于过大。
  • 只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
  • 同时开启两种持久化方式 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF 文件保存的数据集要比RDB文件保存的数据集要完整。 RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者 建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有 AOF可能潜在的Bug,留着作为一个万一的手段。

性能建议:

  • 因为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文件,载入较新的那个,微博就是这种架构。
原文地址:https://www.cnblogs.com/xiaolaodi1999/p/13508466.html