Redis笔记

Redis

建议参考目录使用!!!
Redis(Remote Dictionary Server ),即远程字典服务

redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并在此基础上实现了主从同步

免费、开源,当下最热门的NoSQL技术之一,结构化数据库

Redis能干嘛

1、内存存储、持久化,内存中是断电即失,所以持久化很重要()

2、效率高,可用于高速缓存

3、发布订阅系统(消息队列)

4、地图信息分析

5、计时器、计数器(浏览量)

数据库、缓存、消息队列

特性

1、多样的数据类型

2、持久化

3、集群

4、事务

...

recommend using Linux for deployment.

注意:Redis推荐都是在Linux服务器上搭建的,在Linux系统上进行学习(Windows停更很久还存在兼容性问题)

安装

Windows

github上下载安装包,解压

1、双击redis-server.exe启动redis

2、使用redis-cli.exe客户端连接redis

输入ping命令,返回PONG说明连接成功

3、进行操作set [key] [value]get [key]

Linux安装

1、官网下载安装包.tar.gz, 用xftp将压缩包传入linux虚拟机上

2、将压缩包移到/opt目录下

mv redis-7.0.4.tar.gz  /opt 

3、解压

tar -zxvf redis-7.0.4.tar.gz

4、安装gcc

yum install gcc-c++  #Redis运行需要c++的环境  
gcc -v  #看是否安装成功

5、安装Redis

make  #安装后的文件放在/usr/local/bin目录下
make install  #分两步进行安装

6、进入查看

7、将配置文件拷贝至当前目录下的子目录

mkdir RedisConfig
cp /opt/redis-7.0.4/redis.conf RedisConfig/

8、修改配置文件,redis默认不是后台运行,需要手动开启

vim redis.conf  #进入conf修改
i  #对进行修改
daemonize yes  #将此处配置改为yes
esc + :wq  #保存并退出

9、启动redis服务

redis-server RedisConfig/redis.conf  #启动redis服务器,需要加上配置文件
redis-cli -p 6379  	#客户端连接服务器,也可以通过-h指定ip

10、测试连接

ping
set [key] [value]
get [key]
keys *  #查看所有的key

11、查看redis服务是否开启

ps -ef|grep redis  #在另一个linux控制台查看

12、关闭redis服务

shutdown #在redis中控制台执行shutdown命令

测试性能

redis-benchmark,是一个压力测试工具,官方自带的性能测试工具

参数说明,进行测试的设置

测试100个连接, 100000个请求

redis-benchmark -h localhost -p 6379 -c 100 -n 100000

基础知识

redis一共有16个数据库,可以使用select进行切换

切换数据库
select [index]  #index为指定的数据库,从0开始
DBSIZE  #查看当前数据库的大小
清除数据库
flushdb  #清除当前数据库
flushall  #清除所有数据库
redis是单线程的!!!

redis是基于内存操作,cpu不是redis的瓶颈,redis的瓶颈是机器的内存和网络带宽,因此使用单线程、

为什么redis单线程还比较快?

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

误区2:多线程的效率一定比单线程的效率高?

1.redis是基于内存的,内存的读写速度非常快(纯内存); 数据存在内存中,数据结构用HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)。

2.redis是单线程的,省去了很多上下文切换线程的时间(避免线程切换和竞态消耗)。

3.redis使用IO多路复用技术(IO multiplexing, 解决对多个I/O监听时,一个I/O阻塞影响其他I/O的问题),可以处理并发的连接(非阻塞IO)。

五大数据类型

Redis-Key

keys *  #查看当前所有的键

exists [key]  #是否存在这个key

move [key] 1  #移除当前数据库的指定key

expire [key] [second]  #为某个key设置过期时间

ttl [key]  #查看当前key的剩余时间

type [key]  #查看当前key的类型

append [key] [value]  #向key的value中继续追加value,如果key不存在就相当于set

strlen [key]  #获取字符串的长度

遇到不会的命令去官网查

String

自增、自减

set views 0  #设置浏览量
incr views  #自增
decr views  #自减
incrby views 10  #自己设置步长
decrby views 10  #自定义步长

字符串范围

#subString
set str "hello, world"
getrange str 0 -1  #获取str的全部value值,也可以定义begin和end,类似于subString
getrange [key] [begin] [end]

#replace
setrange [key] [index] xxx  #将value从index开始替换,直至将xxx完全放入value中去

条件设置

setex [key] [seconds] [value]  #(set with expire) 设置过期时间
setnx [key] [value]  #(set if not exist) 不存在时设置,在分布式锁中常使用

批量设置

mset k1 v1 k2 v2 k3 v3 ...
mget k1 k2 k3 ...

对象

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

#这里的key是一个巧妙的设计: user:{id}:{field} 
set user:1:name zhangsan user:1:age 3...

#可以通过此方法完成浏览量统计
set article:1001:views 0  #通过自增进行统计 id1001可传入

getset [key] [value]  #先获取key的value,再将value设置成新的value,可以对比CompareAndSet思想

List

在redis中,可以把list当做栈、队列;常用的操作如下:

#索引从左往右增大

#lpush、rpush、lrange
LPUSH list one  #创建一个List叫list,并从list最左边push进one
LPUSH list two  #在one的左边push进two
RPUSH list three  #从list的最右边push进three
LRANGE list 0 -1  #查看list中的所有的数,也可以是指定索引的范围

#lpop、rpop
LPOP list  #从最左边pop
RPOP list  #从最右边pop

#push和pop配合使用可以达到栈、队列的功能
LPUSH - LPOP、RPUSH - RPOP  #栈
LPUSH - RPOP、RPUSH - LPOP  #队列

#lindex通过下标获取list中的某一个值
lindex list [index]

#llen获取list的长度
llen list

#lrem移除指定的值,如果value在list中有重复,n可以指定移除的个数
lrem list [n] [value]

#ltrim修剪list
ltrim list [begin] [end]

#rpoplpush移除列表的最后一个元素并加入一个新的列表
rpoplpush [oldlist] [newlist]

#lset对指定下标进行修改
lset list [index] [value]  #前提必须保证list[index]存在

#linsert在指定位置前或者后进行插值
linsert list before|after [value] [newvalue]

Set

set中的值不能重复,常用操作:

#sadd向set中插入值
sadd myset [value] 

#smembers查看set中的元素
smembers myset  

#sismember判断元素是否在集合中
sismember myset [value]

#scard获取set中的元素个数
scard myset

#srem从集合中移除指定的某个元素
srem myset [value]

#srandmember从集合中随机的取n个元素
srandmember myset [n]

#spop从集合中随机的删除一些元素
spop myset

#smove将一个指定的值移到另一个集合中
smove [oldset] [newset] [value]

#进行一些集合运算,发现共同好友、共同关注等等
#将两个人的好友/关注放在同两个集合中运算

#SDIFF差集
sdiff set1 set2

#SINTER交集
sinter set1 set2

#SUNION并集
sunion set1 set2

Hash

想成一个map集合,此时的value是一个map,即key-map;常用操作

#hset创建一个hash并set值
hset myhash field1 value1

#hget获取指定的key对应的value
hget myhash field1

#hmset、hmget批量存储和批量获取
hmset myhash key1 value1 key2 value2 ....
hget key1 key2 ...

#hgetall获取所有的k-v对
hgetall myhash

#hdel删除指定的key
hdel myhash key

#hlen获取hash的k-v对个数
hlen myhash

#hexists判断hash中的某个字段是否存在
hexists myhash key

#hkeys、hvals获取hash中所有的key、values
hkeys myhash
hvals myhash

#hincr、hdecr、hincrby、hdecrby
hset myhash views 0  #添加views字段
hincrby myhash views 1  #步长为1自增

#hsetnx 判断是否存在

小结:hash常用于存储用户信息之内的经常变更的信息,更适合对象的存储,string更适合字符串的存储

Zset

有序集合,在set基础上中间增加了一个排序字段score,常用操作:

#zadd添加集合元素
zadd myset 1 one  #给one这个元素赋分为1,为后来排序的依据
zadd myset 2 two 3 three

#zrange获取zset中所有值
#zrangebyscore zset [min] [max]  从小到大进行排序   zrevrange zset [begin] [end]从小到大排序
zrangebyscore salary -inf +inf withscores  #所有用户薪水从小到大排序,排序结果带上score
zrevrange salary 0 -1  #从大到小排序


#zrem移除zset中的指定元素
zrem myzset [value]

#zcard获取有序集合中的个数
#zcount获得指定score区间中的元素的个数

可以实现排行榜、普通重要消息、班级表、工资表

三种特殊数据类型

geospatial

地理位置,朋友的定位,附近的人,打车距离计算,常用操作:

#geoadd添加地理位置
#规则:两级无法添加,格式:纬度 经度 名称,一般下载城市数据,通过java导入
#有效的经度:-180~180 纬度:-85.05112878~85.05112878
geoadd china:city 39.90 116.40 beijing
geoadd china:city 31.23 121.47 shanghai
geoadd china:city 29.53 106.50 chongqin 22.52 114.05 shenzhen

#geopos获得指定城市的经度和纬度
geopos china:city beijing chongqin

#geodist获取两点间的距离
geodist beijing shanghai km  #最后为指定单位

#georadius以某一点为中心,获取半径距离内的geo元素,withdist显示距离,withcrood显示他人经纬度
georadius china:city 110 30 1000km [withdist]/[withcrood] count [n]  #count n限定查找个数

#georadiusbymember也是以某一点为中心查找附近的点,无具体信息
georadiusbymember china:city beijing 1000km

#geohash将二维的经纬度转为一维的字符串,如果两个字符串越相近说明距离越近

geo底层的实现原理其实就是zset,可以通过zset的命令来进行操作

geo没有提供删除功能,可以用zrem china:city beijing来关闭地址信息,zrange查看地图中全部元素

hyperloglog

基数计算找出两个集合中的不同的元素,可接受误差

场景:网页一个用户多次访问,仍应该算作一个访问。

传统的使用set会大量保存用户id,而hyperloglog仅使用固定大小的空间计数,0.81%的错误率,可忽略

常用操作:

#pfadd 添加元素
pfadd mykey a b c d e f g h ...

#pfcount 统计元素个数
pfcount mykey

#pfmerge 合并两个集合
pfmerge mykey3 mykey1 mykey2  #将mykey1和mykey2合并去重后放至mykey3中

#再使用pfcount计数 

小结:

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素

可以理解成将较长的数据按照特定规则映射成很短的数据进行基数统计

bitmap

位存储,使用0-1记录状态,使用场景:统计用户信息是否活跃,上班打卡

常用操作:

#使用bitmap记录周一到周日的打卡
#setbit记录打卡状态
setbit sign 0 1
setbit sign 1 1
setbit sign 2 1
setbit sign 3 1
setbit sign 4 1
setbit sign 5 0
setbit sign 6 0

#getbit获取某一天是否打卡
getbit sign 3  #周四是否打卡

#bitcount自带统计操作
bitcount sign  #获取本周打卡天数,即状态为1的个数

事务

Redis单条命令是原子性的,但是事务并不保证原子性。一个事务中的所有命令都会被序列化,会顺序执行。

一次性、顺序性、排他性。redis事务没有隔离级别的概念。

redis事务:

  1. 开启事务(multi)
  2. 命令入队(......)
  3. 执行事务(exec)、事务回滚(discard)

注意:

​ 编译时异常,事务中所有命令都不会被执行

​ 运行时异常,如果事务中存在语法性错误,会跳过错误的语句继续执行

加锁,watch

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

乐观锁:认为什么时候都不会出问题,不会上锁,数据更新的时候通过变量的版本判断一下是否有人修改过数据

set money 100
set out 0

#线程1
watch money  #watch通过版本号监视money变量,如果money改变事务无法执行
multi   #开启事务
decrby money 10
incrby out 10
exec

#线程2
incr money 1000  #在线程1事务执行前对money进行修改
#在watch发现money发生改变,事务会执行失败

#线程1
unwatch money  #解除监视并重新设置,重新执行事务
watch money  
multi
decrby money 10
incrby out 10
exec

jedis

jedis是Redis官方推荐的java连接开发工具,使用java操作redis中间件,必须要熟悉jedis,jedis命令和之前的linux指令一样,只是通过Jedis对象来操作

Jedis jedis = new Jedis("127.0.0.1", 6379);  //设置ip、port

jedis.close();  //关闭连接

采用的直连,多个线程操作的话,是不安全的。

如果想要避免不安全,就要使用 Jedis pool 连接池解决。

这样是有一些弊端的,比如线程数量太多了,Redis 服务就比较庞大,而且它是阻塞的。

lettuce

底层采用 Netty,实例可以在多个线程中进行共享。

不存在线程不安全的情况,可以减少线程数量。

SpringBoot整合

直接自动创建,去看自动装配的源码,发现有这些东西可以进行配置

@Bean
@ConditionalOnMissingBean(name = "redisTemplate")  //使用redisTemplate来操作redis
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    //这里定义的是Object,Object,通常使用的是String,Object,可以自定义redisTemplate
    RedisTemplate<Object, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    return new StringRedisTemplate(redisConnectionFactory);
}

将redisTemplate注入后操作redis,对基本类型的操作

自定义配置类

package com.gw.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.context.annotation.Configuration;
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;
/**
 * 自定义 Redis 模板
 */
@Configuration
public class RedisConfig {
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        // 定义泛型为 <String, Object> 的 RedisTemplate
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        // 设置连接工厂
        template.setConnectionFactory(factory);
        // 定义 Json 序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        // Json 转换工具
        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;
    }
}

将RedisTemplate封装成工具类

package com.gw.utils;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
 * Redis 工具类
 *
 * @author LiaoHang
 * @date 2022-06-09 22:15
 */
@Component
public class RedisUtil {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    // =============================Common 基础============================
    /**
     * 指定缓存失效时间
     *
     * @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 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        // 如果返回值为 null,则返回 0L
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }
    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return Boolean.TRUE.equals(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));
            }
        }
    }
    // ============================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)
     */
    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)
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }
    // ===============================List 列表=================================
    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     */
    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 键
     */
    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倒数第二个元素,依次类推
     */
    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 值
     */
    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  时间(秒)
     */
    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 {
            return redisTemplate.opsForList().remove(key, count, value);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    // ============================Set 集合=============================
    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     */
    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 键
     */
    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 {
            return redisTemplate.opsForSet().remove(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    // ================================Hash 哈希=================================
    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     */
    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 对应多个键值
     */
    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)
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }
    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }
}

redis.conf说明

单位大小写不敏感

# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.

可以include其他的conf文件

# include .\path\to\local.conf
# include c:\path\to\other.conf

网络

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

通用

daemonize yes  #默认是no需要手动开启为yes,以守护进程的方式运行

pidfile /var/run/redis.pid  #如果以后台的方式运行需要指定一个pid文件

# 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  #设置log的级别

logfile ""  #日志的位置文件名

database 16  #设置数据库的个数

快照

持久化,在规定的时间执行了多少次操作,会持久化到.aof.rdb文件中;redis是内存数据库,没有持久化断电即失

#默认的持久化策略

#900秒内修改了一次进行持久化操作
save 900 1

#300秒内修改10次进行持久化操作
save 300 10

#60秒内修改10000次进行持久化操作,高并发
save 60 10000

#持久化失败后任然进行持久化操作
stop-writes-on-bgsave-error yes

#是否压缩rdb文件,压缩操作耗费cpu
rdbcompression yes

#保存rdb文件进行校验
rdbchecksum yes

#文件名
dbfilename dump.rdb

#目录
dir ./

安全

config set requirepass "123456"  #设置redis密码
config get requirepass  #获取密码
auth 123456  #进行登录,登录后才能使用redis

客户端、内存限制

maxclients 10000  #设置最大客户数
maxmemory <bytes>  #设置最大内存
maxmemory-policy noeviction  #设置内存使用达到最大后的策略
    #1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 
    #2、allkeys-lru : 删除lru算法的key   
    #3、volatile-random:随机删除即将过期key   
    #4、allkeys-random:随机删除   
    #5、volatile-ttl : 删除即将过期的   
    #6、noeviction : 永不过期,返回错误

aof配置

appendonly no  #默认使用rdb模式,aof模式默认不开启

appendfilename "appendonly.aof"  #aof持久化的文件名

# appendfsync always  #每次修改都会同步,消耗性能
appendfsync everysec  #每秒钟保存一次,可能丢失这一秒的数据
# appendfsync no  #不执行同步,操作系统自己同步数据,速度最快

#重写策略,由于aof是无限追加,在超过限制的情况下,需要扩大文件容量,fork一个新进程重写
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb  #可以将64mb设置的更大些,扩容耗费资源


Redis持久化

redis是内存数据库,没有持久化断电即失

RDB

一般默认的配置就够用了,保存的时dump.rdb文件

触发规则

  1. save规则满足时会自动触发rdb
  2. 执行flushall也会触发rdb
  3. 推出redis也会触发rdb

如何运行rdb文件

  1. 只需将rdb文件放在redis的启动目录下,redis启动时会自动检查dump.rdb文件,恢复其中的数据
  2. 查看需要存在的位置config get dir,将dump.rdb文件放在查询出的目录即可

优点:适合大规模的数据恢复;对数据完整性要求不高

缺点:需要一定的时间间隔进行操作,如果redis意外宕机了,最后一次修改的数据就没有了;

fork进程的时候会占用一定的内存

AOF

以日志的形式记录所有写的操作,将redis执行的所有操作记录下来(读操作不记录),只许追加文件但不允许改写文件,redis启动时会读取该文件重新构建数据,就是将所有写的指令再执行一遍,保存的时appendonly.aof文件

默认不开启,需要手动开启appendonly yes,详细配置见redis.conf

如果aof文件有错位,自己手动对aof文件进行了修改,可以使用redis-check-aof --fix来恢复aof文件,丢弃错误的数据

优点:

  1. 每一次修改都同步,文件的完整会更加好
  2. 每秒同步一次,可能会丢失1秒的数据
  3. 从不同步效率最高

缺点:

  1. 相对于数据文件来说,aof远远大于rdb,恢复的速度也比rdb慢
  2. aof运行效率也比rdb慢,因此redis默认使用rdb

aof配置

appendonly no  #默认使用rdb模式,aof模式默认不开启

appendfilename "appendonly.aof"  #aof持久化的文件名

# appendfsync always  #每次修改都会同步,消耗性能
appendfsync everysec  #每秒钟保存一次,可能丢失这一秒的数据
# appendfsync no  #不执行同步,操作系统自己同步数据,速度最快

#重写策略,由于aof是无限追加,在超过限制的情况下,需要扩大文件容量,fork一个新进程重写
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb  #可以将64mb设置的更大些,扩容耗费资源

小结:

可以二者都开启,如果 redis 重启的话,则会优先采用 AOF 方式来进行数据恢复,这是因为 AOF 方式的数据恢复完整度更高。

也可以二者都不开启,只希望数据在服务器运行时存在,只做缓存,不做持久化

Redis发布订阅

原理

客户订阅频道后,发布者一旦有新消息就会通知每个客户

使用场景

  1. 实时消息系统
  2. 实时聊天,把频道当成聊天室,把消息回显给所有人
  3. 订阅、关注系统
  4. 稍微复杂的场景使用消息中间件(MQ)来做

命令

Redis主从复制

一般来说不可能只使用一台redis,而是搭建redis集群,来实现高可用、高并发;集群中有一个主节点(master),多个从节点(slave)

默认情况下所有节点都是主节点,一个主节点可以有多个从节点,一个从节点只有一个主节点;数据的复制是单向的,只能从主节点复制到从节点,主节点负责写,从节点负责读;主从复制、读写分离

主从复制的作用:

读写分离:主节点写,从节点读,提高服务器的读写负载能力

数据冗余︰主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。

故障恢复︰当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复 ; 实际上是一种服务的冗余。

负载均衡︰在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载 ; 尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。

高可用(集群)基石︰除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

搭建redis集群

1、将配置文件统一放在/usr/local/bin/redis-config自定义文件夹下,分别拷贝3分redis.conf

  1. 修改端口号6379、6380、6381
  2. 修改pid
  3. 修改日志文件名
  4. 修改dump文件名

2、通过redis-server redis-config/redis79.conf指定服务器启动的配置文件开启三个服务

3、用三个客户端连接服务器redis-cli -p 6379,连接后查看节点信息info replication

4、设置主节点,只需要指明认哪个节点当老大slaveof

slaveof 127.0.0.1 6379  #认6379号端口的redis节点当老大自己为从节点

在从节点中查看

在主节点中查看

小结:

主机断开连接后,从机依旧连接到主机,但是没有写操作,这时候主机如果回来了,从机依旧可以从主机读取写的信息。即主机宕机重连后依旧是主机,但是在宕机是redis集群没有主节点;从机宕机后,重连后主节点会自动执行一次全量复制

如果需要保存设置,需要在redis.conf中配置replication的相关配置,主从关联等等

复制原理:

slave启动成功后连接到master后会发送一个sync同步命令;master收到命令后,启动后台的存盘进程,同时接收所有的修改数据集命令,在后台进程执行完毕后,master将传送整个数据文件到slave,完成一次完全同步。

全量复制:slave接收到数据库文件后,将其存盘并加载到内存

增量复制:master继续收集修改数据的命令,一次传给slave

主机宕机后手动配置,指定某个从节点为主节点

slaveof no one

哨兵模式sentinel

主机宕机后,redis集群再推选出一个从节点为主节点,这时如果原来主节点重连成功,则也会变成新主节点的从节点。为了增加哨兵模式的高可用性,也可以建立一个哨兵集群。

哨兵通过发送命令,等待redis的响应,从而监控运行的多个redis实例

哨兵的作用:

  • 通过发送命令获取主从服务器的运行状态,进行监控
  • 当哨兵检测到master宕机,将会自动的推选出一个slave成为新的master,然后通过发布订阅模式通知其他的服务器,修改配置文件,让他们切换主机

如果主服务器宕机,哨兵1检测到了这个结果,系统并不会立刻进行failover过程,仅仅是哨兵1主观认为主服务器宕机,称为主观下线;当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会发起投票,投票的结果由一个哨兵发起,进行failover操作,切换成功该哨兵就会发布订阅让各个哨兵进行切换,称为实际下线

开启哨兵

1、在redis-config/sentinel.conf进行配置

#sentinel monitor 哨兵名 ip port 1
sentinel monitor myredis 127.0.0.1 6379 1  #1表示从节点投票模式,主节点宕机后选出新的从节点

2、开启哨兵模式

redis-sentinel redis-config/sentinel.conf

主节点宕机后:

更多关于宕机的配置

# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379
port 26379

# 哨兵sentinel的工作目录
dir /tmp

# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2

# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd

# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000

# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1

# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。 
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000

# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。

#通知脚本
# shell编程
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh

# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh # 一般都是由运维来配置!

Redis雪崩、击穿、穿透

见文章https://zhuanlan.zhihu.com/p/346651831很详细

posted @ 2022-09-28 12:24  CDUT的一只小菜鸡  阅读(40)  评论(0编辑  收藏  举报