NOSQL

大数据时代的3V+3高

3V

1、海量Volume

2、多样Variety

3、实时Velocity

3高

1、高并发

2、高可扩

3、高性能

  • NoSQL (非关系型数据库,(不依赖业务逻辑存储,而是按照键值对存储,大大的增加的数据库的扩展能力))
    不遵循sql标准,nosql不支持ACID

关系型数据库(存储与业务逻辑相关的数据)

nosql,缓存数据库,可以减少cpu和IO的压力

不同的数据用不同的方式存储来缓解IO压力

Redis能干什么

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

2、效率高,可以用于高速存储

3、发布订阅系统

gcc更新/切换版本

// gcc更新/切换版本

Redis安装

linux

运行

linux

在redis的安装根目录下 执行

redis-server 拷贝到其他文件夹的redis配置文件
redis-server /usr/local/bin/myredisconf/redis.conf

redis-cli 用客户端访问

redis命令

  • flushdb (清空当前库)
  • flushall (清掉所有库)

五大数据类型

String , list , set , hash , zset

  • select 3 —— (切换库,redis默认有16个库,初始下标0开始,初始默认使用0号库)
  • dbsize ——(查看当前库key的数量)
  • keys * ——(查看当前库所有key(匹配:key*1exists key 判断某个key是否存在)
  • type key ——(查看你的key是什么类型)
  • del key —— (删除指定的key数据)
  • unlink key ——(根据value选择非阻塞删除,(并不会立刻删除,而是选择异步删除)
  • expire key 10 ——(10秒钟,为给定的key设置过期时间)
  • setex key 过期时间 value ——(设置键值的同时,设置过期时间,单位秒 )
  • ttl key ——(查看还有多少秒过期,-1表示永不过期,-2表示已经过期)
  • set key value ——(设置key value)
  • get key ——(查询对应的键值)
  • append key value ——(将给定的value追加到原值的末尾,append 存在的key 想要追加的value )

append key1 append1

  • strlen key ——(获的值的长度)
  • setnx key value ——(只要当key不存在时,设置key的值,这个也是分布式锁,可以用del删除key来解锁)
  • incr key ——(将key中存储存的数字值增加1,只能对数字值操作,如果为空,新增值为1)
  • decr key ——(将key中存储存的数字值减1)
  • incrby key 步长 ——((步长(增加的值想要多少)自定义增加的值) )

incrby k1 10

  • decrby key 步长 ——(自定义减少的值)
  • mset key value key value key value ——(可以同时设置一个或多个键值对)
  • mget key value key value ——( 可以同时获取一个或多个value )
  • msetnx key value key value ——( 可以设置一个或多个键值对,仅仅当所有给定的key不存在时 )
  • getrange key 起始位置 结束位置 ——( 获得值的范围,类似于java的substring,前包,后包,下标从0开始)

现有kv值: key1 helloworld

getrange 0 4

结果 :“hello"

  • setrange key 起始位置 value ——(用value覆写key所储存的字符串值,从起始位置开始(索引从0开始))

现有kv值:key1 helloworld

setrange 0 test (从下标0开始覆盖原有的value)

结果:testoworld

  • getset ——(设置新值的同时,获取旧值)

现有kv值:k1 v1

getset k1 v2

此时获取到的值还是v1,但是k1的值已经被改成v2了

list

redis列表:简单的字符串列表,单键多值,有序可重复,底层是双向链表,对两端的操作性能很高,对操作中间的节点性能较差

list:底层是快速链表quicklist,元素较少时会使用,ziplist压缩列表,将所有的元素紧挨着一起存储,分配日的是一块连续的内存,数据量较多会使用quicklist

Redis将链表和ziplist结合起来组成了quicklist,也就是将多个ziplist使用双向指针串起来使用,这样既满足了快速的插入删除性能,又不会出现太大的空间冗余

ziplist <–> ziplist <–> ziplist <–> ziplist <–> ziplist 组成quicklist

  • lpush/rpush key value1 value2 value3 ——( 从左边/右边插入一个或多个值 ,此命令插入的值类型为list)

从左插入值-- 从头部插值

插入 v1 v2 v3 三个值,从左插入依次顺序是

v1

v2 v1

v3 v2 v1

从右侧插入则相反

v1

v1 v2

v1 v2 v3

  • lrange key 起始位置 结束位置 ——(按照索引下标获取元素 0 -1 获取全部 (0:左边第一个,-1 右边第一个))
  • lpop/rpop key (从左边/右边吐出一个值,这个命令不是获取元素,而是拿掉元素,每吐出一个值,key里就少一个值)
  • rpoplpush key1 key2 —— (从key1列表右边吐出一个值,插到key2列表左边)

现有: key1:v1 v2 v3 | key2 : v11 v22 v33

rpoplpush key1 key2

结果: key1=v1 v2 | key2 = v3 v11 v22 v3

  • lindex key index ——( 按照索引下标获取元素 (从左到右)下标从0开始 )

现有: key1:v1 v2 v3

lindex k1 2

结果:v3

  • llen key ——(获取列表的长度)
  • linsert key before/after value newvalue ——(在value的之前/之后 插入newvalue,插入值)

现有kv值:k1 = v1 v2 v3

linsert k1 before v1 new11

结果 new11 v1 v2 v3

  • lrem key n value ——(从左边删除n个value(从做到右))

现有kv值: k1 = v1 v1 v2 v1 v2 v3 v2 v3 v4

lrem k1 2 v11

结果: v2 v1 v2 v3 v2 v3 v4 ( 从左开始删除,删除两个v11 )

  • lset key index value ——(将列表key下标为index的值替换成value)

现有kv值: k1 = v1 v2 v3

lset k1 2 v99

结果: v1 v99 v3

Redis 集合 set

redis set 对外提供的功能与list类似,是一个列表的功能,特使指出在于set是可以自动排重的

Redis 的set是String类型的无序集合。底层是一个value为null的hash表,所以添加,删除,查找的复杂都是0(1)

set数据结构是dict字典,字典是用哈希表实现的

  • sadd key value1 value2… ——(将一个或多个member元素加入到集合key中,已经存在的member‘’元素将被忽略)
  • smembers key ——(取出该集合的所有值)
  • sismenmber ——(判断集合key是否为含有该value值)
  • scard key ——( 返回该集合的元素个数 )
  • srem key value1 value2… ——(删除集合中的某个元素)
  • spop key ——(随机从该集合中吐出一个值)
  • srandmenmber key n ——(随机从该集合中取出n个值。不会从集合中删除)
  • smove key1 key2 value ——(把集合中的一个值从一个集合移动到另一个集合)
  • sinter key1 key2 ——(返回两个集合的交集(相同的)元素)
  • sunion key1 key2 ——(返回两个集合的并集元素,(两个集合的所有元素))
  • sdiff key1 key2 ——(返回两个集合的差集元素 (key1中有,key2中没有的元素))

Redis Hash

Redis hash 是一个键值对集合

Redis hash是一个String类型的field和value的映射表,hash特别适合于用于存储对象

类似java里面的Map<String,Object>

数据结构:
hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable

**field(领域)可以理解为java中的对象,键就是对象,值就是参数 **

Reids hash的field 就是一个套娃,把值外面又套了一层东西,叫做field,field就是参数的名字,value就是参数的值

key

hash

filed

(value)

user:101

=

id

1

name

zhangsan

age

18

  • hset key field value ——(给key集合中的 field键赋值value)

hset user:101 id 1 (user:101 是key , id 1 是field (就是value))

hset user:101 name zhangsan

hset user:101 age 18

  • hget key1 field ——(从key1集合field取出 vlue)

hget user:101 name

结果: zhangsan

  • hmset key1 field value field2 value2… ——(批量设置hash的值)

hmset user:102 id 2 name lisi age 19

  • hexists key1 field ——(查看哈希表key中,给定域field是否存在)
  • hkeys key ——(列出该哈市几个的所有field)
  • hvals key ——(列出该hash集合的所有value)
  • hincrby key field increment ——(为哈希表key中的域field的值加上增量 1 -1)

现有 user:103 age 20

hincrby user:103 age 5

结果 25

  • hsetnx key field value ——(将哈希表key中的域field的值设置为value,当且仅当域field不存在)

现有 user:104 id 4 name lisi age 20

hsetnx user:104 age 18 错误:原key中有age

hsetnx user:104 gender 1 成功

Zset

Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合

不同支持是有序集合的每个成员都关联了一个评分(score),这个评分(score)被永磊按照从最低分到最高分分的方式排序集合中的成员,集合的成员是唯一的,但是评分可以是重复的

因为元素是有序的,所以你也可以很快的根据评分(score) 或者次序(position)来获取一个范围的元素

访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没哟重复成员的智能列表

  • zadd key score1 value1 score2 value2 ——(将一个或多个member元素及其score值加入到有序级key当中)

zadd top 200 java 300 python 400 c++ 500 php

  • zrange key start stop ——(返回有序集key中,下标在start stop之间的元素,0 -1 返回所有元素,(按下标取值,从0开始))
  • 结尾加上 withscores 可以让分数一起和值返回到结果集

zrange top 0 -1

结果:

  1. “java”
  2. “python”
  3. “c++”
  4. “php”

zrange top 0 -1 withscores

结果:

  1. “java”
  2. “200”
  3. “python”
  4. “300”
  5. “c++”
  6. “400”
  7. “php”
  8. “500”
  • zrangebyscore key minmax ——(返回有序集key中,所有score值介于min和max之间(包括min或max)的成员。有序集成员按score值递增(从小到大次序排列),(按分数取值))
  • 结尾加上 withscores 可以让分数一起和值返回到结果集

zrangebyscore top 200 400

结果:

  1. “java”
  2. “python”
  3. “c++”
  • zrevrangebyscore key maxmin ——((rev reverse相反,反转) , 同上改为从大到小)
  • 结尾加上 withscores 可以让分数一起和值返回到结果集
  • zincrby key increment value ——(为元素的score加上增量)

zincrby top 10 java

结果:

210 (java的分数(score)变成了210)

  • zrem key value ——(删除该集合下,指定值的元素)
  • zcount key min max ——(统计该集合,分数区间内的元素个数)

zcount top 200 300

结果:2 (200-300之间有两个值)

  • zrank key value ——(返回该值在集合中的排名,从0开始 )

三种特殊数据类型

Geospatial , Hyperloglog , Bitmap

Geospatial (地理空间)

  • geoadd 标签 经度 纬度 地理位置名称 ——(添加地理位置)
  • 有效的经度从-180度到180度
  • 有效的纬度从-85.05112878度到85.05112878度 (超出范围会报错)

geoadd China:city 121.49 31.223 shanghai (同一个标签可多次使用)

geoadd China:city 116.40 39.90 beijing

  • geopos 标签 地理位置名 ——(获取指定位置的经纬度)

geopos China:city beijng

geopos China:city beijing shanghai (可以连续查询)

  • geodist 标签 地理位置名1 地理位置名2 ——(返回两个给定位置之间的距离)

单位 m 米 km 千米

  • georadius 标签 经度纬度 半径距离(带单位 m/km) ——(以给定的经纬度为中心,找出某一半径内的元素)(附近的人可以用此功能实现)

georadius China:city 110 30 1000 km (找出在距离经纬度110 30 1000km的位置,这些位置需要保存在China:city集合中 )

  • 后面可以携带参数
  • withdist (显示到中间距离的位置 单位km)
  • withcoord (显示他人的定位信息)
  • count 2 (限定查询给出的数量)
  • georadiusbymember 标签 地理位置名 范围距离(指定单位 m/km) ——(找出位于指定范围内的元素,中心点是由给定的位置元素决定)

georadiusbymember China:city beijing 1000 km (找出距离beijing1000km内的城市)

  • geohash 标签 地理位置名1 地理位置名2 ——(返回一个或多个位置元素的geohash表示)

该命令将返回11个字符的geohash字符串

  • 移除城市可以用 Zset的命令 zrem 来移除 , 因为geo底层就是Zset,geo可以使用Zset的命令

Hyperloglog (超级日志)

可以用来统计网站的UV

  • pfadd key 元素… ——( 添加元素 )

pfadd uvkey a b c d

  • pfcount key ——(返回给定 HyperLogLog 的基数估算值,(统计数据))
  • pfmerge 合并到key 合并的key1 合并的key2 ——(将另个key合并都一个key中,合并之后是并集,自动去除掉重复的元素,( 可以合并更多key))

现有 uvkey1="a b c d " uvkey2 = " c d e f "

pfmerge uvkey3 uvkey1 uvkey2

结果:a b c d e f ( 重复的 c d 被去重 )

Bitmap

可以用来,记录是否打卡,是否在线 , 疫情是否感染… 两个状态的

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

  • setbit key 位 位 ——(添加,(位就是元素,一种状态))

记录上班是否打卡

setbit sign 1 1 ( 周一到周五,1:打卡 , 0:未打卡 )

setbit sign 2 0

setbit sign 3 1

setbit sign 4 1

setbit sign 5 0

  • getbit key 位 ——(获取指定元素)

geibit sign 3

结果 1

  • bitcount key ——(统计)

bitcount sign (统计打卡天数)

结果:3


订阅与发布

Reids发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

Redis客户端可以订阅任意数量的频道

  • subscribe 频道名字 ——(订阅频道)

sunscribe channel99

  • publish 频道名字 消息 ——(发布消息)

publish channel99 helloRedis


事务

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

一次性、顺序性、排他性 执行一系列的命令

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

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

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

Reids事务执行流程 三步

1、multi ——(开启事务)

2、命令入列 (底层自动实现)

3、exec ——(执行事务)

127.0.0.1:6379> MULTI		#开启事务
OK
127.0.0.1:6379> set k1 v1    
QUEUED						#进入队列
127.0.0.1:6379> set k2 v1
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec		#执行事务
1) OK						#统一执行
2) OK
3) "v1"
4) OK

每次执行完之后事务就消失了,想要使用需要再次开启

  • discard ——(放弃事务)

在执行事务的过程中,想要放弃事务可以使用该命令

放弃之后,在事务过程中的操作会被撤销

  • 编译时异常,事务队列中的命令都不会被执行
  • 运行时异常,如果事务队列中存在语法型错误,name执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常

监控

悲观锁:

很悲观,认为什么时候都会出现问题,无论做什么都会加锁 ——(效率较低

乐观锁:

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

  • 获取version
  • 更新的时候比较version
  • watch key ——(监控)
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> set money 100
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
########  连接一   ###########

127.0.0.1:6379> watch money			# 监控money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 40		# 在本连接中正常修改
QUEUED
127.0.0.1:6379> exec				
(nil)								# 执行事务后发现在监控期间数据被其他连接修改,返回nil
127.0.0.1:6379>
########  连接二   ###########

127.0.0.1:6379> set money 100
OK
  • unwatch ——( 解锁,解除监控 )

如果修改失败,就重新监控数据获取最新的值就好

  • 以上都是乐观锁的实现

Jedis

Jedis是Redis官方推荐的java连接开发工具,使用java操作Redis的中间件。

使用Jedis需要导入Jedis的依赖

Jedis的api与命令行中的命令一致

import redis.clients.jedis.Jedis;

public class jedisTest {
    public static void main(String[] args) {

        Jedis jedis = new Jedis("127.0.0.1",6379);
        System.out.println(jedis.ping());//测试连接

    }
}
Jedis操作事务
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class JedisTransaction {

    public static void main(String[] args) {

        Jedis jedis = new Jedis();

        jedis.flushDB();

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("name", "zhangsan");
        jsonObject.put("age", "18");
        String s = jsonObject.toString();
        Transaction multi = jedis.multi();

        try {
            multi.set("user1", s);
            multi.set("user2", s);
//            int i = 10/0;       //除0异常
            multi.exec();
        } catch (Exception e) {
            multi.discard();   // 出现异常放弃事务
            e.printStackTrace();
        } finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();   //关闭连接
        }


    }

}

整合springBoot

简单的get,set使用

@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
    //操作不同的数据类型,ops...就是操作一些数据的
    //    redisTemplate.opsForList();
    //    redisTemplate.opsForHyperLogLog();

    // 获取Redis连接对象,较少使用
    //        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
    //        connection.flushDb();

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

}

}

对象序列化

@Test
    void UserTest() throws JsonProcessingException {
        User user = new User("zhangsan",18);

        //1、 需要把对象序列化,传递json字符串才可以,否则会报错(DefaultSerializer requires a Serializable payload but received an object of type)
        String userJson = new ObjectMapper().writeValueAsString(user);
//        redisTemplate.opsForValue().set("user",userJson);
        
        
        //2、 也可以直接在实体类中实现 "Serializable" 接口来实现序列化
        redisTemplate.opsForValue().set("user",user);
        System.out.println(redisTemplate.opsForValue().get("user"));
        
    }

编写自己的RedisTemplate

原生的ReidsTemplate可以存储对象,但可读性差

StringReidsTemplate可读性强,但不能存储对象

所有要编写自己的ReidsTemplate

目的是使用RedisTemplate既可以存储对象,可读性又强

序列化可以让在客户端查看key也有可读性

@Bean(name = "redisTemplate")
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> getRedisTemplate(RedisConnectionFactory factory) {
        // 为了开发方便一般使用<String,Object>
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(factory);
        //Json序列化的配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        //String的序列化配置
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key的序列化方式,以及hash的key
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        //value的序列化方式,以及hash的value
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

redisTemplate和stringRedisTemplate对比、redisTemplate几种序列化方式比较

security

Redis的密码设置
  • config set requirepass “123123” (设置密码 )
  • auth (验证密码)

Redis持久化

Redis 运行时数据保存在内存中, 一旦重启则数据将全部丢失.

Redis 提供了两种持久化方式:

  1. RDB 持久化: 生成某个时间点的快照文件
  2. AOF 持久化(append only file): 日志追加模式(Redis协议格式保存)

Redis可以同时使用以上两种持久化

RDB

执行 rdb 持久化时, Redis 会fork出一个子进程, 子进程将内存中数据写入到一个紧凑的文件中, 因此它保存的是某个时间点的完整数据。

Redis 启动时会从 rdb 文件中恢复数据到内存, 因此恢复数据时只需将redis关闭后,将备份的rdb文件替换当前的rdb文件,再启动Redis即可。

################################ SNAPSHOTTING  ################################
# 快照配置
# 注释掉“save”这一行配置项就可以让保存数据库功能失效
# 设置sedis进行数据库镜像的频率。
# 900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化) 
# 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化) 
# 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化)
save 900 1
save 300 10
save 60 10000

#当RDB持久化出现错误后,是否依然进行继续进行工作,yes:不能进行工作,no:可以继续进行工作,可以通过info中的rdb_last_bgsave_status了解RDB持久化是否有错误
stop-writes-on-bgsave-error yes

#使用压缩rdb文件,rdb文件压缩使用LZF压缩算法,yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间
rdbcompression yes

#是否校验rdb文件。从rdb格式的第五个版本开始,在rdb文件的末尾会带上CRC64的校验和。这跟有利于文件的容错性,但是在保存rdb文件的时候,会有大概10%的性能损耗,所以如果你追求高性能,可以关闭该配置。
rdbchecksum yes

#rdb文件的名称
dbfilename dump.rdb

#数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录
dir /var/lib/redis

触发机制

1、save的规则满足的情况下,会自动触发rbd规则

2、执行flushdb命令,会触发rdb规则

3、退出redis,会触发rdb规则

rbd就是备份文件

如何恢复rdb文件

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

2、查看需要存放的位置

127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" 		# 如果这个目录下存在dump.rdb 文件,启动就会自动恢复其中的数据

优点

  • rdb文件体积比较小, 适合备份及传输
  • 性能会比 aof 好(aof 需要写入日志到文件中)
  • rdb 恢复比 aof 要更快

缺点

  • 服务器故障时会丢失最后一次备份之后的数据
  • Redis 保存rdb时, fork子进程的这个操作期间, Redis服务会停止响应(一般是毫秒级),但如果数据量大且cpu时间紧张,则停止响应的时间可能长达1秒

AOF

AOF 其实就是将客户端每一次操作记录追加到指定的aof(日志)文件中,在aof文件体积多大时可以自动在后台重写aof文件(期间不影响正常服务,中途磁盘写满或停机等导致失败也不会丢失数据)

aof 默认是关闭的,需要在配置文件中手动开启 , 将appendonly no 改成 appendonly yes

############################## APPEND ONLY MODE ###############################
#默认redis使用的是rdb方式持久化,这种方式在许多应用中已经足够用了。但是redis如果中途宕机,会导致可能有几分钟的数据丢失,根据save来策略进行持久化,Append Only File是另一种持久化方式,可以提供更好的持久化特性。Redis会把每次写入的数据在接收后都写入 appendonly.aof 文件,每次启动时Redis都会先把这个文件的数据读入内存里,先忽略RDB文件。
appendonly yes

#aof文件名, 保存目录由 dir 参数决定
appendfilename "appendonly.aof"

#aof持久化策略的配置
#no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
#always表示每次写入都执行fsync,以保证数据同步到磁盘。
#everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。
appendfsync everysec

# 在aof重写或者写入rdb文件的时候,会执行大量IO,此时对于everysec和always的aof模式来说,执行fsync会造成阻塞过长时间,no-appendfsync-on-rewrite字段设置为默认设置为no。如果对延迟要求很高的应用,这个字段可以设置为yes,否则还是设置为no,这样对持久化特性来说这是更安全的选择。设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes。Linux的默认fsync策略是30秒。可能丢失30秒数据。
no-appendfsync-on-rewrite no

#aof自动重写配置。当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写,即当aof文件增长到一定大小的时候Redis能够调用bgrewriteaof对日志文件进行重写。当前AOF文件大小是上次日志重写得到AOF文件大小的二倍(设置为100)时,自动启动新的日志重写过程。
auto-aof-rewrite-percentage 100
#设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写
auto-aof-rewrite-min-size 64mb

#aof文件可能在尾部是不完整的,当redis启动的时候,aof文件的数据被载入内存。重启可能发生在redis所在的主机操作系统宕机后,尤其在ext4文件系统没有加上data=ordered选项(redis宕机或者异常终止不会造成尾部不完整现象。)出现这种现象,可以选择让redis退出,或者导入尽可能多的数据。如果选择的是yes,当截断的aof文件被导入的时候,会自动发布一个log给客户端然后load。如果是no,用户必须手动redis-check-aof修复AOF文件才可以。
aof-load-truncated yes

aof 文件是一个只追加的文件, 若写入了不完整的命令(磁盘满, 停机…)时, 可用自带的 redis-check-aof 工具轻易修复问题:执行命令redis-check-aof --fix

优点

  • 充分保证数据的持久化,正确的配置一般最多丢失1秒的数据
  • aof 文件内容是以Redis协议格式保存, 易读

缺点

  • aof 文件通常大于 rdb 文件
  • 速度会慢于rdb, 具体得看具体fsyn策略
  • 重新启动redis时会极低的概率会导致无法将数据集恢复成保存时的原样( 概率极低, 但确实出现过 )

Redis主从复制

环境配置

只配从库不用配库

  • info replication ——(查看当前库的信息)
  • slaveof host ip ——(从机认主)

主从复制不用配置主机,在从机配置认那台服务器为主机即可

没有配置的情况下,每台服务器都是主机,主机下可以有很多从机,但从机上只能有一个主机

如果redis设有密码的话,需要在配置文件中 REPLICATION下的 masterauth 密码设置密码

使用命令行配置的主从机是临时的,配置永久的需要在配置文件中配置

在配置文件中REPLICATION下的 repliceof 主机ip 端口 配置完之后就是该ip的主机的从机了

主机负责写,从机负责读(也只有读的权限),这样就算主机崩了,从机也可以读到数据

如果是用命令行配置的主从机,如果断开了,从机就会变回主机,需要重新认主,在断开期间,主机设置的值如果该从机没有再次认主,是读取不到这些数据的,但是可以读到之前有主从关系期间设置的值。如果再次认主,那么断开期间主机设置的值也可以再次读到(这个叫做 全量复制

主从复制有两种配置方案 (这两种方案并不会去使用,通常会使用哨兵模式 )

1、一台主机有很多从机,一些从机连接一台主机

2、让一些从机不在是去连接主机,而是去连接从机,让一台或多台从机既当从机又当主机,

  • slaveof no one ——(如果主机断开了连接,从机运行这条命令,让自己当主机,其他从机就可以 手动 连接这个最新的主节点)

哨兵模式

自动选举主机

哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。

其原理是:哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例

一般会设置多个哨兵,主机宕机,当哨兵1 检测到这个结果,并不会立即failover(故障转移) ,因为这仅仅是哨兵1 认为这个主机不可用,这个现象叫做 ‘ 主观下观 ’ 当其他哨兵也检测到这个主服务器不可用,那么哨兵之间就会进行一次投票,进行failover 故障转移,切换成功后,就会通过发布订阅模式让各个哨兵把自己监控的从服务器实现切换主机,这个过程叫做客观下线

配置哨兵

1、在bin目录下新建配置文件 sentinel.conf

2、在配置文件中加上 sentinel monitor 被监控的名称随便起个名字 被监控的主机ip 被监控的端口号 1 1 表示有几个哨兵认为master主机宕机了才会投票选举

哨兵启动

在bin目录下 redis-sentinel /myconfig/sentinel.conf 运行redis-sentinel 文件 并在后面加上sentinel.conf 哨兵的配置文件

主机宕机后哨兵会选举一个从机当新的主机,这时就算老主机重新上线,也会变成光杆司令,这时哨兵检测到老主机重新上线,它会让这台老主机变成新主机的从机

集群搭建

  • cluster nodes ——(查看集群信息)
  • 搭建一个节点
    新建一个redis的配置文件 例:redis6379.conf , 在里面加上一些配置
    cluster-enabled yes 打开集群模式
    cluster-config-file nodes-6379.conf 设置节点配置文件名 (nodes-6379.conf 是自定义的名字)
    cluster-node-timeout 15000 设定节点失联时间,超过改时间(毫秒),集群自动进行主从切换
  • 将所有节点合成一个集群
    cd /opt/redis-6.0.6/src (打开redis安装目录下的src)
redis-cli --cluster create --cluster-replicas 1 192.168.177.139:6379 192....


redis-cli --cluster				#表示集群操作
create --cluster-replicas 1		#创建集群的方式,1 表示用最简单的方式配置集群,1个主机1个从机  
192.168.177.138:6379 			#这里需要用真实的ip地址,不可以用127.0.0.1
192.........所有的节点ip以及端口号

这些是命令,需要在src目录下执行

连接集群
  • -c ——( 采用集群策略连接,设置数据会自动切换到相应的写主机)(c:cluster)

redis-cli -c -p 6380

用集群中的任何节点都可以连接

slot插槽

redis集群中有包含16384个插槽(hash slot) 0-16383 ,数据库中的每个键都属于这16387个插槽的其中一个

集群使用公式 CRC16(key)%16384 来计算键key属于哪个槽,集群中的每个节点负责处理一部分插槽。

  • 例:

节点A负责处理0-5460号插槽

节点B负责处理5461-10922号插槽

节点C负责处理10923-16383号插槽

连接集群后,往redis集群中写入值,当计算出key在其他的节点插槽范围,就会自动切换到哪个节点去存储

  • 插入多个值

集群中不可以使用mset , mget 等多键操作

如果想使用,需要把键添加一个组

  • 例:
    mset name{user} zhangsan age{user} 18
    其中name和age都属于user组
    这是集群计算的就不是name和age的插槽了,而是user的插槽
  • 查询集群中的值
  • cluster keyslot key ——(返回key在插槽中插槽值)
  • cluster countkeysinslot 插槽值 ——(计算插槽值中有几个key,若插槽值超出当前节点的插槽值范围,则无法查看,需要切换到相应的节点)
  • cluster getkeysinslot 插槽值 数量 ——(返回多少个插槽中的key , 就是在插槽中拿key)

cluster getkeysinslot 4839 10

缓存

缓存穿透

恶意查询缓存和数据库中都没有的数据,并查询量极大,超出了服务器的负荷,导致宕机

解决方案

1、 对空值缓存:

如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟

2、设置可访问的名单(白名单):

使用bitmaps类型定义一个可以访问的名单,名单id最为bitnaps的偏移量,每次访问和bitmap里面的di进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问

3、采用布隆过滤器:

(布隆过滤器(Bloom Filter))时候1970年有布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)

4、进行实时监控:

当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务

缓存击穿

数据库访问压力瞬时增加

redis里面没有出现大量key过期

redis正常运行

造成缓存击穿的原因:

redis中的某个key过期,而这个key在过期期间有大量的访问使用这个key,从而压力都转到数据库,导致数据库压力增大

  • 解决方案

1、预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的市场

2、实时调整:现场监控哪些数据热门,实时调整key的过期市场

3、使用锁:

3.1 、就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db

3.2、先使用缓存工具的某些带成功操作返回值的操作(比如redis的setnx)去set一个mutex key

3.3、当操作返回成功是,在进行load db的操作,并回设缓存,最后删除,mutex key

3.4、当操作返回失败,证明有线程咋load db , 当前线程睡眠一段时间再充实整个get缓存的方法

缓存雪崩

数据库压力变大,响应变慢,应用访问就会变慢,造成redis中有大量的访问等待,导致服务器崩溃

造成缓存雪崩的原因:

在极少的时间段,查询大量key的集中过期情况

  • 解决方案
    1、缓存存储高可用。比如 Redis 集群,这样就能防止某台 Redis 挂掉之后所有缓存丢失导致的雪崩问题。
    2、缓存失效时间要设计好。不同的数据有不同的有效期,尽量保证不要在同一时间失效,统一去规划有效期,让失效时间分布均匀即可。
    3、对于一些热门数据的持续读取,这种缓存数据也可以采取定时更新的方式来刷新缓存,避免自动失效。
    4、服务限流和接口限流。如果服务和接口都有限流机制,就算缓存全部失效了,但是请求的总量是有限制的,可以在承受范围之内,这样短时间内系统响应慢点,但不至于挂掉,影响整个系统。
    5、从数据库获取缓存需要的数据时加锁控制,本地锁或者分布式锁都可以。当所有请求都不能命中缓存,这就是我们之前讲的缓存穿透,这时候要去数据库中查询,如果同时并发的量大,也是会导致雪崩的发生,我们可以在对数据库查询的地方进行加锁控制,不要让所有请求都过去,这样可以保证存储服务不挂掉。

分布式锁

随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!

  • setnx key 上锁
  • del 删除key释放锁
  • setex key 时间 设置过期时间
  • ttl key 查看过期时间

有三种加锁方式

1、直接加锁 setnx key 用del释放

2、为了防止忘记释放锁的情况,可以用第二种:

加锁之后,在设置key的过期时间,key过期了锁就释放了

3、为了防止,加锁之后出现了以外,无法加过期时间,可以送第三种方法:

上锁的同时设置时间 set k1 abc nx ex 10 nx ex 分开用 加锁和过期时间

Redis 6.0 新功能

  • acl setuser 用户名 ——(添加用户)
  • 设置用户权限 …
  • acl whoami ——(查看当前用户)
  • auth 用户名 密码 ——(切换用户)
  • acl list ——(展现用户权限列表)
  • acl cat ——(操作命令有哪些)
  • all cat String ——(后面加上参数可以查看类型下具体的命令)