Redis学习笔记

Redis基础

NOSQL

非关系型数据库的统称,Redis就是其中最常用的一种NOSQL型数据库

NOSQL数据库和SQL数据库的区别
  1. 不遵循sql标准
  2. 不支持ACID
  3. 具有远超关系型数据库的性能
什么时候使用NOSQL
  1. 对数据高并发的读写
  2. 海量数据的读写
  3. 需要高扩展性的数据
什么时候不使用NOSQL
  1. 当需要使用事务的情况下
  2. 当我们需要结构化查询数据的时候
常见的NOSQL数据库
  1. Redis
  2. MongoDB
  3. Hbase

Redis支持非常高速的读写,但是它是key-value型数据库,只能根据key来获取value,不能实现条件查询操作,所以Redis无法代替mysql,但是由于它的性能极强,所以可以用来当作关系型数据库的缓存层使用、分布式管理事务、分布式注册中心、消息队列


安装、配置Redis
#解压安装包
tar -zxvf redis-5.0.3.tar.gz
#安装Redis需要的依赖库
yum install gcc
#跳转到redis的目录
cd redis-5.0.3
#编译安装redis
make MALLOC=libc
#执行命令
cd src && make install

Redis启动的三种方式
  1. 前台启动:进入到src下,直接输入命令 ./redis-server,当启动之后需要一支开启窗口,不能执行其他操作,非常不方便
  2. 后台进程启动:
    1. 后台启动默认关闭,所以需要修改配置文件redis.conf,将daemonize no 改成yes
    2. 使用配置文件启动redissrc/redis-server redis.conf
  3. 设置开机自动启动
    1. 到etc下新建redis文件夹,将原来的配置文件复制一份过来cp /usr/local/redis-5.0.3/redis.conf /etc/redis/redis.conf
    2. /etc/init.d下创建自启动脚本redis,文件内容如下程序块所示
    3. 给脚本添加权限chmod 755 /etc/init.d/redis
    4. 将脚本添加到服务列表chkconfig --add /etc/init.d/redis
    5. 设置开机自启chkcinfig redis on
#!/bin/sh
# chkconfig:   2345 90 10
# description:  Redis is a persistent key-value database
PATH=/usr/local/bin:/sbin:/usr/bin:/bin


REDISPORT=6379
EXEC=/usr/local/bin/redis-server
REDIS_CLI=/usr/local/bin/redis-cli


PIDFILE=/var/run/redis.pid


CONF="/usr/local/redis-5.0.3/redis.conf"


case "$1" in
    start)
        if [ -f $PIDFILE ]
        then
                echo "$PIDFILE exists, process is already running or crashed"
        else
                echo "Starting Redis server..."
                $EXEC $CONF
        fi
                if [ "$?"="0" ]
        then
              echo "Redis is running..."
        fi
        ;;
    stop)
        if [ ! -f $PIDFILE ]
        then
                echo "$PIDFILE does not exist, process is not running"
        else
                PID=$(cat $PIDFILE)
                echo "Stopping ..."
                $REDIS_CLI -p $REDISPORT SHUTDOWN
                while [ -x ${PIDFILE} ]
               do
                    echo "Waiting for Redis to shutdown ..."
                    sleep 1
                done
                echo "Redis stopped"
        fi
        ;;
   restart|force-reload)
        ${0} stop
        ${0} start
        ;;
  *)
   echo "Usage: /etc/init.d/redis {start|stop|restart|force-reload}" >&2
        exit 1
esac

因为Redis默认不允许远程连接,需要使用远程连接通过Redis Desktop Manager看的话需要修改redis.conf配置文件,其中:

  1. bind 127.0.0.1改成0.0.0.0
  2. protected-mode yes改成protected-mode no
  3. 设置密码requirepass 123456(可以不设置)
  4. 关闭防火墙systemctl stop firewalld

使用Redis客户端
  1. 进入到Redis目录下的src中
  2. 输入./redis-cli —raw -a 123456进入

如何关闭redis
#查看redis的进程id
ps -aux | grep redis
#杀掉redis进程
kill 进程ID

Redis中常用的命令(key与五大数据类型)

使用select index来切换数据库,例如:select 1

  • String (字符串)
    在redis中,字符串的类型是安全的二进制并且二进制的value最大值为512M,那就意味着String类型并不是只能存储一个简单的字符串,也可以存储图片或者文件的序列化对象

    • set key value,存储一个字符串到redis中
    • get key,从redis中取出一个字符串类型的数据
    • append key value,将内容追加到之前的key上,若没有这个key则会执行set操作,也就是说有key追加,没key创建一个再存储
    • strlen name ,获得value占用的字节数(一个中文三个字节)
    • incr key,key存在则value自增,否则创建keyvalue为1(key对应的value必须是数字,否则会报错)
    • incrby key increment,指定自增多少,increment可正可负
    • decr key,与incr相反,为自减操作
    • decrby key decrement,指定自减多少,decrement可正可负
    • mset key value [key value …],一次存多个key-value
      一次存储多个key-value与多次存储key-value有什么区别?
      一次存储多个key-value,命令不会被打断,而多条命令则可能被打断
    • mget key [key ...],一次取出多个value
    • msetnx key value [key value …],作用和mset一致,区别是msetnx若命令中有一个成功,就都成功;有一个失败,则全部失败(推荐使用)
      Redis的原子性
      原子是不可分割的组织,在Redis中单条命令能完成的操作都是原子性的,因为Redis是单线程的,所以Redis在执行一条命令的途中不会被其他线程打断,也不会被切换到其他线程。
    • getrange key start end,截取字符串,从开始下标到结束下标,将值返回,没有改变原本的值。
    • setrange key offset value,字符串替换,从开始下标开始进行value的替换操作,value有多少就替换多少,例:setrange name 0 gggg,从下标为0开始,替换成gggg,比如说原字符串是abcdefghigk,替换之后就成了ggggefghigk
    • setex key seconds value,在设置key的同时设置过期时间
    • getset key value,先获取原本的值,然后再设置新的值(value)
    • psetex key milliseconds value,设置key的时候设置过期时间(毫秒级)
    • exists key [key ...],判断key是否存在,1存在,0不存在
    • setnx key value,判断key是否存在,若存在则返回0并且不做任何操作;若key不存在则返回1并同时新增key-value
  • List(列表)
    底层是双向链表数据可重复,对两端的数据操作效率高,如果根据索引操控中间的数据效率低。
    如果链表中存在上下引用,并且只存储数字类型的数据,会造成空间的浪费

    • lpush/rpush key value [value ...],从左/右侧插入值,key不存在会创建,并返回列表中数据个数
    • lpop/rpop key,从List左/右侧取出一个值(拿出后list中就没有这个值了,当值被取空后,list会被删除
    • rpoplpush source destination,从source的右侧拿出一个值,放入到destination的左侧,例:rpoplpush list1 list2
    • lrange key start stop,从左边根据下标取值,从开始下标到结束下标,值还是在key中,不会被删除,若需要取出全部值,则结束下标写-1,-1就代表最后一个下标,例:lrange key 0 -1
    • lindex key index,根据下标取值
    • llen key,获取key的长度
    • linsert key BEFORE|AFTER pivot value,根据值插入,把value插入到pivot的前/后面
    • lrem key count value,从左侧开始删除count个value,并返回删除的个数
    • lset key index value,修改下标为index的值为value
  • set(集合)
    自动排重的功能,也就是指set中的值不能有重复。set底层是一个key-value形式的hash表

    • sadd key member [member ...],向集合中存放数据(member)无序,如果重复则会直接忽略
    • smembers key根据key取出所有的member(数据)无序,不会删除
    • scard key,获取元素个数
    • sismember key member,查找member,找到了返回1,找不到返回0
    • spop key [count]随机从key中取值,可设置取值个数,取出之后key中删除
    • srandmember key [count],从key中随机取值,可设置取值个数,取出后key中不删除
    • smove source destination member, 将source中的member移动到destination
    • sinter key [key ...],取出key的交集
    • sunion key [key ...],取出key的并集
    • sdiff key [key ...],取出key的差集
    • srem key member [member ...],删除key中的member,可删除多个,并返回删除的个数
  • zset(有序)
    zset有自己的排序规则,这个顺序并不是我们存储的顺序,而是按照存储数据时给的score排序

    • zadd key [NX|XX] [CH] [INCR] score member [score member …],向key中添加数据,score是排序的时候用的,member为存放的数据
    • zrange key start stop [WITHSCORES],取出key中start和stop之间的数据,需要score一起显示的话可以加上withscores
    • zrangebyscore key min max [WITHSCORES] [LIMIT offset count],可以==按照score的顺序(默认升序)==取出范围内的值
    • zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count],可以==按照score的顺序(降序)==取出范围内的值
    • zincrby key increment member,将key中member的score增加increment
      例:zincrby zset 100 zhangsan,意思就是给zset中的zhangsan的score值加上100
    • zrank key member,查询member在key中排第几,从零开始计算
    • zcount key min max,查询min到max之间有多少数据
    • zrem key member [member ...],删除key中的member
  • hash
    是一个键值对集合,类似于java中的Map。hash结构适合存储单个对象
    为什么不用String存储对象

    ​ 如果用String来存储对象,则需要将对象序列化然后存入,如果有修改对象属性的需求,则需要将整个对象反序列化,修改后再次序列化然后存入,开销大

    • hset key field value,根据键值对的形式向key中添加值,如果key已经存在则将其覆盖,field是属性名,value是属性值
    • hget key field,根据key以及field取出对应的value
    • hexists key field,判断key中是否存在field,存在返回1,否则返回0
    • hkeys key,取出key中所有的属性名(field)
    • hvals key,取出key中所有的属性值(value)
    • hincrby key field increment,对integer类型的属性值进行加/减操作,increment可正可负,返回修改后的值
    • hsetnx key field value,当属性不存在的时候添加属性以及值,如果属性存在则不进行任何操作,成功返回1,否则返回0
  • key
    redis中最关键的东西,所有的操作基本上都是通过key实现的

    • keys patternpattern为*的时候,可以获得所有的key
    • exists key [key ...],返回查询的key有多少个,存在返回存在的个数,否则返回0,不建议查多个
    • type key,查询key的类型是什么
    • del key [key ...],删除key,可以删除多个,如果key中有特殊字符,需要将key写在双引号之内,否则不识别,返回成功删除的个数
    • unlink key [key ...]异步删除key
    • expire key seconds,给key设置过期时间
    • ttl key,查询key的过期时间,-2代表已经删除,-1代表永不过期
    • dbsize -,查询当前数据库中key的数量
    • flushdb [ASYNC],清空当前数据库
    • flushall [ASYNC],清空所有数据库
三种特殊的数据类型
  • geospatial(地理位置)redis的Geo功能可以推算地理位置的信息,geo底层的原理就是Zset,我们可以使用Zset操作geo

    #添加城市地理位置数据
    #规则:两极无法添加,一般使用java程序等一次性导入
    
    #有效的经度从-180度到180度。
    #有效的纬度从-85.05112878度到85.05112878度。
    
    #将指定的地理空间位置(纬度、经度、名称)添加到指定的key中
    #参数: key value(纬度 精度 名称)
    127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
    (integer) 1
    
    ----------------------------------------------------------------
    
    #从key里返回所有给定位置元素的位置(经度和纬度)
    127.0.0.1:6379> GEOPOS china:city beijing zhengzhou
    1) 1) "116.39999896287918091"
       2) "39.90000009167092543"
    2) 1) "104.32000011205673218"
       2) "29.41000080988116139"
    
    ----------------------------------------------------------------
    
    #返回两个给定位置之间的距离。
    #如果两个位置之间的其中一个不存在, 那么命令返回空值。
    #指定单位的参数 unit 必须是以下单位的其中一个:
    #m 表示单位为米。
    #km 表示单位为千米。
    #mi 表示单位为英里。
    #ft 表示单位为英尺。
    #如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。
    #GEODIST 命令在计算距离时会假设地球为完美的球形, 在极限情况下, 这一假设最大会造成 0.5% 的误差。
    
    #北京到郑州的直线距离
    127.0.0.1:6379> geodist china:city beijing zhengzhou
    "1604092.3762"
    127.0.0.1:6379> geodist china:city beijing zhengzhou km
    "1604.0924"
    
    #找附近的人?定位?
    
    #以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素
    #参数 key value(经度 纬度 半径 单位)
    127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km
    1) "zhengzhou"
    
    #withdist  显示到中间
    #withcoord 显示他人的定位信息
    #count 1 先是返回结果为一个
    127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withdist withcoord count 1
    1) 1) "zhengzhou"
       2) "552.5944"
       3) 1) "104.32000011205673218"
          2) "29.41000080988116139"
    
    #这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 
    
    #而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点
    
    #指定成员的位置被用作查询的中心。
    
    127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
    
    1. “beijing”

      #查看key中所有的value

      127.0.0.1:6379> ZRANGE city 0 -1

    2. “gg”

      #删除 value

      127.0.0.1:6379> ZREM city gg
      (integer) 1

  • hyperloglog:这个数据结构用作基数统计的算法,比如说网页的UV(一个人访问一个网站多次,但是需要只算做一个人)

    传统的方式:使用set保存用户id,然后就可以统计set中的元素数量作为标准判断。

    基数:**数据集里面不重复的元素个数**
    
    hyperloglog的优点:占用的内存是固定的,只需要12kb的内存,但是**有0.81%的错误率**,具体使用需要看当时的需求
    
    #创建一组元素
    127.0.0.1:6379> pfadd key a b c d e f g h i j
    (integer) 1
    
    #统计出key中的基数
    127.0.0.1:6379> pfcount key
    (integer) 10
    
    #合并两组数据
    127.0.0.1:6379> PFMERGE key3 key key2
    OK
    
  • bitmap:统计用户信息、用户是否活跃、是有登陆、打卡(两个状态的都可以使用bitmap)

    传统使用一张表、一个字段保存

    位图,是一种数据结构,都是操作二进制位来进行记录,只有0和1两种状态

    #例如使用bitmap记录打卡: sign这个位图               前边的参数表示天数 0开始,表示周一   后边的参数表示是否打卡,1打卡 0未打卡
    7.0.0.1:6379> setbit sign 0 1
    (integer) 0
    127.0.0.1:6379> setbit sign 1 1
    (integer) 0
    127.0.0.1:6379> setbit sign 2 1
    (integer) 0
    127.0.0.1:6379> setbit sign 3 0
    
    #统计 为1的数据有多少
    
    127.0.0.1:6379> BITCOUNT sign
    (integer) 3
    
    
    

基本知识

#选择数据库
127.0.0.1:6379> select 3
OK
#数据库内容
127.0.0.1:6379[3]> dbsize
(integer) 0
127.0.0.1:6379[3]>

#查看数据库所有的key
127.0.0.1:6379[3]> keys *
(empty array)

#清除当前数据度
127.0.0.1:6379[3]> flushdb
OK

#清除所有数据库
127.0.0.1:6379[3]> flushall
OK
redis是单线程的!

明白:redis是很快的。它是基于内存的,它的性能瓶颈是根据机器的内存大小和网络带宽,既然可以使用单线程来实现,就直接使用单线程了。

redus为什么是单线程还那么快?
  1. 误区1:高性能的服务器一定是多线程的
  2. 误区2: 多线程一定比单线程高

核心:redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的(多线程:cpu上下文切换,耗时),对于内存系统来说,如果没有上下文切换,那么效率就是最高的。


Redis基本事务

redus单条命令保证原子性,但是事务不保证原子性

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

redis事务没有隔离级别的概念

所有的命令在事务中并没有直接被执行,只有发起执行命令的时候才会执行 (Exec)

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

  • 开启事务(MULTI)
  • 命令入队(其他)
  • 执行事务(exec)
正常执行事务
127.0.0.1:6379> MULTI              #开启事务
OK

#命令入队
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)>  get k2
QUEUED
127.0.0.1:6379(TX)>  set k3 v3
QUEUED

127.0.0.1:6379(TX)> EXEC         #执行事务
1) OK
2) OK
3) "v2"
4) OK
放弃事务
127.0.0.1:6379> MULTI              #开启事务
OK

#事务队列中的命令都不会执行
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> DISCARD        #取消事务
OK
127.0.0.1:6379> get k4
(nil)

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

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED

#错误的命令
127.0.0.1:6379(TX)>  getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> EXEC

#所有的命令都不执行
(error) EXECABORT Transaction discarded because of previous errors.

运行时异常(1/0 语法型错误)执行命令的时候没有错的命令是正常执行,错误命令会抛出异常

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k1 "v1"
QUEUED

#错误命令
127.0.0.1:6379(TX)> INCR k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) (error) ERR value is not an integer or out of range         #只有这一个命令不能执行
3) OK
4) OK
5) "v3"
监控

悲观锁

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

乐观锁

  • 认为什么时候都不会出问题,不会加锁。更新数据的时候判断一下,在此期间是否有人修改过这个数据(version)
    • 获取version
    • 跟新的时候比较version
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(TX)> DECRBY money 20
QUEUED
127.0.0.1:6379(TX)> INCRBY out 20
QUEUED
127.0.0.1:6379(TX)> EXEC
1) (integer) 80
2) (integer) 20

测试多线程修改值,使用watch当作redis的乐观锁

27.0.0.1:6379> watch money           #监视money
OK 
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 10
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10
QUEUED
127.0.0.1:6379(TX)> EXEC            #执行之前另外一个线程修改了monry,就会导致事务失败
(nil)

监视失败,重新监视(重新获取最新的值就好)

127.0.0.1:6379> UNWATCH                   #事务失败之后,取消监视这个对象
OK
127.0.0.1:6379> watch money                #再次监视这个对象
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY monry 100
QUEUED
127.0.0.1:6379(TX)> INCRBY out 100
QUEUED
127.0.0.1:6379(TX)> EXEC
1) (integer) -100
2) (integer) 120

Jedis控制redis
什么是jedis?官方推荐的java连接开发工具 使用java操作redis中间件

1.导入依赖

<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>3.2.0</version>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.62</version>
</dependency>

2.编码测试:

  • 操作数据库
  • 断开连接
测试连接redis
package com.yan;

import redis.clients.jedis.Jedis;

/**
 * @author zhangruiyan
 */
public class TestPing {
    public static void main(String[] args) {
        //new jedis对象即可
        Jedis jedis = new Jedis("127.0.0.1",6379);

        //所有的命令都在这个对象里
        System.out.println(jedis.ping());
    }

}
jedis处理事务
package com.yan;

import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

/**
 * @author zhangruiyan
 */
public class TestTX {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);

        jedis.flushDB();
        //开启事务
        Transaction multi = jedis.multi();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello","word");
        jsonObject.put("name","zhangsan");
        String s = jsonObject.toJSONString();

        //监视s这个对象
        jedis.watch(s);
        try {
            multi.set("user1",s);
            //代码有错,抛出异常,放弃事务
            int i = 1/0;
            multi.set("user2",s);
            //执行事务
            multi.exec();
        }catch (Exception e){
            //放弃事务
            multi.discard();
        }finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();
        }
    }
}

SpringBoot整合Redis

说明:在Springboot2.x之后,jedis被替换成了lettuce

jedis:采用的是直连,多个线程操作的话是不安全的,如果想避免不安全,就需要使用jedis pool连接池,更像BIO

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

源码分析:

 @Bean
     //这个注解的意思就是如果不设置redisTemplet的话就直接使用这个现成的
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

      //redisTemplete 没有过多的设置 , redis对象都需要序列化
      //两个泛型都是 object,object 的类型 我们使用需要转换成 string,object
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }


//spring是redis中最常用的类型,所以说单独有一个bean
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

1.导入依赖

    <!--操作redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

2.配置连接

#springboot所有的配置类 都有一个自动配置类   RedisAutoConfiguration
#自动配置类都会绑定一个properties配置文件    RedisProperties
spring:
  #配置redis
  redis:
    host: 127.0.0.1
    port: 6379

3.测试

package com.yan;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class Redis02SpringbootApplicationTests {


    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    void contextLoads() {
        /*
            redisTemplate 操作不同的数据类型 api和redis的命令一样
            redisTemplate.opsForValue(); 操作字符串 类似string
            redisTemplate.opsForList();  操作list集合
            除了基本的操作,常用的方法都可以直接通过redisTemplate操作,例如事务、基本的crud
         */

        redisTemplate.opsForValue().set("name","zhangSan");
        System.out.println(redisTemplate.opsForValue().get("name"));


    }

}

我们来编写自己的redisConfig

package com.yan.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author zhangruiyan
 */
@SpringBootConfiguration
public class RedisConfig {

    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {

        //为了开发方便一般直接使用 string object
        RedisTemplate<String, Object> template = new RedisTemplate<String,Object>();

        template.setConnectionFactory(redisConnectionFactory);

        //json配置具体的序列化方式
        Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        //转译
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

        //已经废弃了(暂时不用,用下边的试试)
        //om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        om.activateDefaultTyping(om.getPolymorphicTypeValidator());
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);

        objectJackson2JsonRedisSerializer.setObjectMapper(om);

        //string的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        //key采用string的序列化方式
        template.setKeySerializer(stringRedisSerializer);

        //hash的key也采用string的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);

        //value序列化方式采用jackson
        template.setValueSerializer(objectJackson2JsonRedisSerializer);

        //hash的value序列化采用jackson
        template.setHashValueSerializer(objectJackson2JsonRedisSerializer);
        template.afterPropertiesSet();


        return template;
    }

}

自己写的RedisUtil

package com.yan.config.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author zhangruiyan
 */
@Component
public class RedisUtil {
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    //-----------------------------------------------------------------

    /**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间(秒)
     */
    public boolean expire(String key,long time){
        try {
            if (time>0){
                redisTemplate.expire(key,time, TimeUnit.SECONDS);
            }
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key获得过期时间
     * @param key 键 不能为空
     * @return 时间(秒) 返回0 代表永久有效
     */
    public long getExpire(String key){
        return redisTemplate.getExpire(key,TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false 不存在
     */
    public boolean haskey(String key){
        try {
            return redisTemplate.hasKey(key);
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key){
        if(key != null && key.length > 0){
            if(key.length==1){
                redisTemplate.delete(key[0]);
            }else{
                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
            }
        }
    }
    /**
     * 通过前缀模糊删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void delVague(String key) {
        Set keys = redisTemplate.keys(key+"*");
        if (keys!=null && keys.size()>0) {
            for (Object o : keys) {
                String curKey = (String) o;
                redisTemplate.delete(curKey);
            }
        }
    }
    //============================String=============================
    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key){
        return key==null?null:redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time){
        try {
            if(time>0){
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            }else{
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 递增
     * @param key 键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta){
        if(delta<0){
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }
    /**
     * 递减
     * @param key 键
     * @param delta 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta){
        if(delta<0){
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }
    //================================Map=================================
    /**
     * HashGet
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key, String item){
        return redisTemplate.opsForHash().get(key, item);
    }
    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key){
        return redisTemplate.opsForHash().entries(key);
    }
    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map){
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * HashSet 并设置时间
     * @param key 键
     * @param map 对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time){
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if(time>0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @param time 时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if(time>0){
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 删除hash表中的值
     * @param key 键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item){
        redisTemplate.opsForHash().delete(key,item);
    }
    /**
     * 判断hash表中是否有该项的值
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item){
        return redisTemplate.opsForHash().hasKey(key, item);
    }
    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     * @param key 键
     * @param item 项
     * @param by 要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item, double by){
        return redisTemplate.opsForHash().increment(key, item, by);
    }
    /**
     * hash递减
     * @param key 键
     * @param item 项
     * @param by 要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by){
        return redisTemplate.opsForHash().increment(key, item,-by);
    }
    //============================set=============================
    /**
     * 根据key获取Set中的所有值
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key){
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 根据value从一个set中查询,是否存在
     * @param key 键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value){
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将数据放入set缓存
     * @param key 键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object...values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 将set数据放入缓存
     * @param key 键
     * @param time 时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object...values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if(time>0) {
                expire(key, time);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 获取set缓存的长度
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key){
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 移除值为value的
     * @param key 键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object...values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    //===============================list=================================
    /**
     * 获取list缓存的内容
     * @param key 键
     * @param start 开始
     * @param end 结束  0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end){
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 获取list缓存的长度
     * @param key 键
     * @return
     */
    public long lGetListSize(String key){
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 通过索引 获取list中的值
     * @param key 键
     * @param index 索引  index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key, long index){
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 根据索引修改list中的某条数据
     * @param key 键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 移除N个值为value
     * @param key 键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

}

redis.conf配置文件

单位

配置文件 unit单位对大小写不敏感

# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes

多个配置文件配置过来

################################## INCLUDES ###################################

# Include one or more other config files here.  This is useful if you
# have a standard template that goes to all Redis servers but also need
# to customize a few per-server settings.  Include files can include
# other files, so use this wisely.
#
# Note that option "include" won't be rewritten by command "CONFIG REWRITE"
# from admin or Redis Sentinel. Since Redis always uses the last processed
# line as value of a configuration directive, you'd better put includes
# at the beginning of this file to avoid overwriting config change at runtime.
#
# If instead you are interested in using includes to override configuration
# options, it is better to use include as the last line.
#
# include /path/to/local.conf
# include /path/to/other.conf
网络
bind 0.0.0.0     #绑定的ip

protected-mode no   #保护模式

port 6379            #端口号
通用GENERAL
daemonize yes    #以守护线程的方式运行,默认no 需要自己开启(就是后台启动)

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 ""


#数据库的数量 默认16个
databases 16

#是否显示redis的logo
always-show-logo no
快照SNAPSHOTTING

持久化,在规定的时间内执行了多少次操作,则会持久化到文件.rdb.aof

redis是内存数据库,如果没有持久化,那么断电的话数据就会丢失

#如果 3600s内 如果至少有1个key进行了操作,就会进行持久化操作
# save 3600 1
#如果 300s内 如果至少有100个key进行了操作,就会进行持久化操作
# save 300 100
#如果 60s内 如果至少有10000个key进行了操作,就会进行持久化操作
# save 60 10000


#持久化失败之后是否继续工作
stop-writes-on-bgsave-error yes

#是否压缩rdb文件 会消耗一些cpu资源
rdbcompression yes

#保存rdb文件的时候进行错误的检查校验
rdbchecksum yes

#rdb文件的保存位置
dir ./
安全 SECURITY
#设置密码
# requirepass foobared

redis持久化

RDB

在指定的时间间隔内将内存的数据集快照写进磁盘,即Snapshot快照,它恢复的时候是直接读到内存中

Redis会单独创建一个子进程(fork)来进行持久化,它会将数据写入一个临时rdb文件,等持久化过程结束之后,再用这个临时文件替换上次持久化好的文件。

整个过程中,主进程不进行任何IO操作,这保证了极高的性能,如果需要进行大规模数据的恢复,并且对于数据恢复的完整性不是非常敏感,那么RDB方式比AOF更加的高效

RBD的缺点就是最后一次持久化后的数据可能丢失

Redis默认RDB,一般不进行修改

RDB保存的文件是 dump.rdb,在配置文件中的快照这一块配置

dbfilename dump.rdb
触发机制
  1. 满足save设置的规则
  2. 执行flushall命令
  3. 退出redis

备份就会自动生成一个REB文件

如何恢复RDB文件

只需要将RDB文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb文件并恢复其中的数据

查看启动目录:

127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/redis-6.2.5"       #如果在这个目录下 redis启动就会自动恢复
RDB优点
  1. 适合大规模的数据恢复
  2. 如果对数据完整性要求不高
RDB缺点
  1. 需要一定的时间间隔进程操作
  2. fork进程的时候会占用内存空间

AOF(Append Only File)

将我们所有的命令都记录下来,恢复的时候就把这个文件全部执行一遍

以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只允许追加文件,不可改写文件,Redis启动的时候会读取该文件重新构建数据,即:Redis重启的话就根据日志文件的内容将写指令从前到后执行一次,以完成数据的恢复工作。

AOF保存的是appendonly.aof文件

将appendonly改成yes即可开启AOF

############################## APPEND ONLY MODE ###############################
appendonly no        #默认不开启

appendfilename "appendonly.aof"    #AOF产生的文件

重启Redis即可生效

如果这个aof文件有错误,这时候redis无法启动,可以使用redis-check-aof –fix命令来修复

优点
  1. 每次修改都同步,数据更完整

  2. 每秒同步一次,可能会丢失一秒的数据

缺点
  1. 相对于数据文件来说,AOF远远大于RDB,修复的速度慢
  2. AOF效率比较低
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值