Redis入门案例

Rdis的基本概念

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。

Redis环境搭建

单机搭建
linux编译安装redis
wget https://robinliu.3322.org:8888/download/Redis-x64-3.2.100.zip
tar -zxf redis-3.0.0.tar.gz
cd redis-3.0.0
mkdir /usr/local/redis-3.0.0
make PREFIX=/usr/local/redis-3.0.0 install
cp redis.conf /usr/local/redis-3.0.0
vim ~/.bash_profile               # .bash_profile是隐藏文件, 在该文件中自定义环境变量
以下两行为.bash_profile最后两行内容:
PATH=$PATH:$HOME/bin:/opt/redis-3.2.9/src   # 添加src目录路径到这里
export PATH
:wq
source ~/.bash_profile //使配置的环境立即生效
echo $PATH
which redis-server
redis-server & //后台运行
ps -ef|grep redis //查看启动
kill -9 进程id //关闭服务
单机搭建方式2:

Linux环境下安装Redis-3.2.9 (centos7)_MrLee的博客-CSDN博客

伪分布式搭建
单节点的主从复制创建
首先进入redis安装目录,找到utils,下面会有一个初始化脚本redis_init_script,将其复制为redis+端口号
cp /home/redis-3.2.11/utils/redis_init_script redis-6380 //复制一份新的文件
vim redis-6380 //编辑文件
修改端口
:wq //保存并退出
cp /home/redis-3.2.11/redis.conf /usr/local/redis-3.2.11 //复制文件到指定位置
mv redis.conf redis-6380.conf //给文件重命名
vim redis-6380.conf //编辑文件
i //修改模式
自定义端口
:wq //保存并退回
修改端口为port:6380
redis-server /usr/local/redis-3.2.11/redis-6380.conf //启动测试
ps -ef|grep reids //检查是否启动
kill 进程名称 //自定义是否杀死
redis主从复制测试
以上修改端口重复两次,配置完毕,登录redis-cli -p 6379/redis-cli -p 6380/redis-cli -p 6381,
发现每个服务点都是主机点我们可以使用slaveof 127.0.0.1 6380/slaveof 127.0.0.1 6381,增加多个节点之间的联系,实现了一主二仆实现了数据同步

 

Redis的五大基本类型

String
5.判断当前key是否存在:exists key

6.移除指定数据库内的key:move key 1

7.设置key的过期时间,单位是秒:expire 查看当前key的剩余时间 :ttl key

8.查看当前key的类型:type key

9.set key 0 incr key #自增1

• decr key # 自减1

• incrby key 10 #加指定数量

• decrby key 10 #减指定数量

10.截取字符串指定地址:getrange key 0 3(-1,全部) 替换指定位置开始的字符串:setrange key 1 xx

11.设置key带有过期时间:setex(set with expire) setex key 30 “HELLO”

12.不存在再设置:setnx(set if not exist) setnx key “redis” —> 1 setnx key “MongoDB” —> 0

13.批量set与get mset k1 v1 k2 v2 k3 v3 mget k1 k2 k3 msetnx k1 v1 k2 v4 #原子性,要么全部成功,不然全部失败

14.存在值则获取原来的值,并设置新的值 getset key “redis”

<img src="C:\Users\liaoy\AppData\Local\Temp\Image.png" alt="Image" style="zoom: 67%;" />

redis数据结构string常用命令

賦值的命令:

set company imooc

获取值

get company

查询所有值

keys *

数值自增/自减

incr company    +1
decr compny -1
incrby company 5 +n
decrby company 5 -n

删除

del company

字符串追加

append company xxx

redis数据结构hash常用命令

赋值

hset myhash username jack
hset myhash age 18

赋值键值对

hmset myhash2 username rose age 21

获取单个属性/多个属性/所有属性

/单个属性
hget myhash username
/多个属性
hmget myhash2 username age
/所有属性
hgetall myhash/myhash2

删除某个key/values

hdel myhash2 username age

增加数字

hincrby myhash age 5

判断某个key属性是否存在

hexists myhash username/password

获取所有的key/vlues,以及长度

hkeys myhash
hvals myhash
hlen myhash

redis数据结构list常用命令

向头部添加元素/尾部添加元素

lpush mylist a b c d
rpush mylist 1 2 3 

查询列表

lrange key start end:获取链表中从start到end的元素的值,start、end从0开始计数;也可以为负数,若为-1则表示链表尾部的元素,-2表示倒数第二个,依次类推....

lrange mylist 0 -1

两端弹出,类似于出栈

头部弹出,默认第一个元素
lpop mylist
尾部弹出,默认第一个元素
rpop mylsit

获取长度

llen mylist

lrem key count value:删除count个值为value的元素,如果count大于0,从头到尾遍历并删除count个值为value的元素,如果count小于0,则从尾到头遍历并删除,如果count等于0,则删除链表中所有等于value的元素。

从头删除两个b
lrem mylist 2 b
从尾部删除两个b
lrem mylist -2 a
删除所有a
lrem mylist 0 a

lset key index value:设置链表中的index的脚标的元素值,0代表的头元素,-1代表链表的微元素,操作链表的脚标不存在则抛异常。

lset mylist 0 ee

指定位置插入元素

linsert mylist after b a

redis数据结构set常用命令

set不允许出现重复元素

sadd:往set中添加值,无序,不可重复(只存储数字时,数字会升序存储)

sadd set01 2 2 3 3 4 4 1 1 5 5 

查询结果/查询总数量

smembers set01
smembers set01 1	..'1'
查询数量
scard set01		..'5'

srem key value:删除集合中的元素

srem set01 5

srandmember key n:随机从key中查询n个数

srabdmember set01 2

spop key:随机出栈

spop set01			..3
spop set01			..1

smove source_set objective_set value:将源集合中的value存入目标集合中:

smove set01 ste02 1

数学集合类: sdiff / sinter / sunion sdiff:取差集,存在第一个set而不存在于后面的任何 一个set sinter:取交集 sunion:取并集

sadd set01 1 2 3 4 5
sadd set02 1 2 3 x y
sdiff set01 set02		'4','5'
sinter set01 set02		'1,','2','3'
sunion set01 set02		'x','2','y','1','3','5','4'

redis数据结构sorted-set常用命令

 如果key已经存在,再次添加一个key相同的,但是分部不同,会将分数改成最新的分数,然后返回的数字是0;如果是添加一个新的key值,就是返回添加进去的个数。

zadd mysort 60 zs 70 ls 80 ww

查看filed个数/倒序

zcard mysort		个数
zrange mysort 0 -1	结果

删除

zrem mysort ww

根据分数排序

  一直是从小到大,一种是从大到小

zrange mysort 0 -1 withscores	结果+分数
zrevrange mysort 0 -1 withscores

范围删除

zremrangebyrank mysort 0 1		全部删除
zremrnagebyrankscore mysort 50 60	条件删除

分数排序后查看

zrangebyscore mysort 100 withscores limit 0 3

常用命令

连续删除

del a1 a2 a3

查询是否存在

exists a1

修改名字

rename a1 newa1

Spring整合Redis

  1. maven依赖配置项

    <dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
  2. properties配置项

    spring:
      redis:
        host: 127.0.0.1
        port: 6379
        password:
        jedis:
          pool:
            max-active: 8
            max-wait: -1
            max-idle: 500
            min-idle: 0
        lettuce:
          shutdown-timeout: 0
  3. Spring配置项

    @Configuration
    public class RedisConfig {
    
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(factory);
    
            // 设置key的序列化方式
            template.setKeySerializer(RedisSerializer.string());
            // 设置value的序列化方式
            template.setValueSerializer(RedisSerializer.json());
            // 设置hash的key的序列化方式
            template.setHashKeySerializer(RedisSerializer.string());
            // 设置hash的value的序列化方式
            template.setHashValueSerializer(RedisSerializer.json());
    
            template.afterPropertiesSet();
            return template;
        }
    
    }
  4. 测试单元

    @RunWith(SpringRunner.class)
    @SpringBootTest
    @ContextConfiguration(classes = CommunityApplication.class)
    public class RedisTest {
        @Autowired
        private RedisTemplate redisTemplate;
        @Autowired
        private LikeService likeService;
        @Test
        public void redisTest(){
            String key = "age";
            redisTemplate.opsForValue().set(key,20);
            redisTemplate.opsForValue().get(key);
            System.out.println(redisTemplate.opsForValue().get(key));
        }
    }

RedisAPI使用

Jedis

导入依赖

<!--redis的客户端依赖jedis-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

代码测试

package cn.tedu.redis;

 

import org.junit.Test;

import redis.clients.jedis.Jedis;

 

/**

 * 1 单个对象连接redis

 * 2 数据分片计算逻辑

 *  hash取余算法

 *  数据分片对象ShardedJedis--一致性hash

 * 3 jedis的连接池pool的使用

 */

public class JedisTest {

    //创建jedis对象,提供参数ip port 连接到10.9.160.137:9000的redis服务端

    @Test

    public void test01(){

        Jedis jedis=new Jedis("10.9.160.137",9000);

        jedis.set("name","王老师");//name=wanglaoshi 写入到redis

        System.out.println(jedis.get("name"));//读取redis 的key为name的数据

        //hash类型

        jedis.hset("user","name","王翠花");

        jedis.hset("user","age","18");

        jedis.hget("user","nage");

        //list类型

        jedis.lpush("students","张三","李四","张三","王五");

        jedis.rpop("students");

        //set类型

        jedis.sadd("favor","math","english");

        jedis.sismember("favor","math");

        //zset类型

        jedis.zadd("results",100,"朴乾");

        jedis.zadd("results",99,"陈哲");

    }

}
package cn.tedu.redis;

 

import org.junit.Test;

import redis.clients.jedis.Jedis;

 

/**

 * 1 单个对象连接redis

 * 2 数据分片计算逻辑

 *  hash取余算法

 *  数据分片对象ShardedJedis--一致性hash

 * 3 jedis的连接池pool的使用

 */

public class JedisTest {

    //创建jedis对象,提供参数ip port 连接到10.9.160.137:9000的redis服务端

    @Test

    public void test01(){

        Jedis jedis=new Jedis("10.9.160.137",9000);

        jedis.set("name","王老师");//name=wanglaoshi 写入到redis

        System.out.println(jedis.get("name"));//读取redis 的key为name的数据

        //hash类型

        jedis.hset("user","name","王翠花");

        jedis.hset("user","age","18");

        jedis.hget("user","nage");

        //list类型

        jedis.lpush("students","张三","李四","张三","王五");

        jedis.rpop("students");

        //set类型

        jedis.sadd("favor","math","english");

        jedis.sismember("favor","math");

        //zset类型

        jedis.zadd("results",100,"朴乾");

        jedis.zadd("results",99,"陈哲");

    }

}
JedisPool连接池

代码实现

 /*
    JedisPool
     */
    @Test
    public void test05(){
        JedisPool pool=new JedisPool("10.9.118.11",6380);
        //获取连接资源
        Jedis jedis1 = pool.getResource();
        jedis1.set("name","王翠花");
        pool.returnResource(jedis1);
        Jedis jedis2 = pool.getResource();
        System.out.println(jedis2.get("name"));
        pool.returnResource(jedis2);
    }

 

Redis分布式

分布式概念

单机节点存在意外故障导致宕机现象,在redis中我们可以引用redis分布式来保证可用性,分布式通俗的讲就是将一个人干的活交给多个人做

分布式需要考虑的问题
    • 数据的分配--数据分片计算

  • 单节点时,无需考虑数据的分配问题,因为所有数据都必须在这个节点实现读写,但是一旦引入分布式的集群,必须考虑数据整体怎么样分配给不同的节点,而且保证---单调性.整体一批数据被切分成了若干分,称之为数据分片

  • 单调性:哪存的,在哪取.

 

hash取余
  • 一旦涉及到单调性,最先考虑到的计算逻辑就是hash散列.hash取余是一种分布式结构中非常经典的计算方法.

  • hash取余对数据key-value的key值做hash取余计算,得到结果只要key值不变(字符串相等) 取余结果在[0,1,2,3,…,n-1] n=分片个数(节点个数),计算公式

  • (key.hashCode()&Integer.MAX_VALUE)%N 其中**N为分片(节点)个数**

  • key.hashCode(): 对key做hash散列计算,只要key值不变,得到一个不变可正可负的整数.只要散列计算,能够做到key不变,整数结果不变,不一定非得使用hashCode 最终任意一个key值都会对应[-21亿,21亿]区间的一个整数

  • key.hashCode()&Integer.MAX_VALUE: 31**位二进制保真运算**,目的是将前面的整数保真后31位二进制,保证他是一个正整数.& 位的与运算.目的是取得一个正整数

  •  

  • 结论**1**:当key值不变时,可以得到一个不变的正整数

  • (key.hashCode()&Integer.MAX_VALUE)%N

  • N=5,取余结果 [0,1,2,3,4]

  • N=4,取余结果 [0,1,2,3]

  • N=3,取余结果 [0,1,2]

  • 对N取余 结果[0,1,2,3,4,5…,N-1]

  •  

  • 结论**2**:当key值不变时,可以通过hash取余得到 [0,1,2,..,N-1] 一个不变的取余结果

  • hash取余就可以应用在redis分布式数据分片计算逻辑中

  • 当有key-value出现时,先对key做hash取余 n是节点个数(现在是3)

  • 所有节点jedis排序(list) 0 1 2 … n-1 使用到取余结果对应到一个固定的jedis对象,最终连接固定的redis节点

  • 代码实现

  • //hash取余数据分片计算
    
        @Test
    
        public void test02(){
    
            //先来封装3个redis的节点jedis对象的list
    
            List<Jedis> jedisList=new ArrayList<>();
    
            jedisList.add(new Jedis("10.9.160.137",6379));
    
            jedisList.add(new Jedis("10.9.160.137",6380));
    
            jedisList.add(new Jedis("10.9.160.137",6381));
    
            //系统生成了海量数据
    
            int rand=55;
    
            String keyS="";
    
            for(int i=0;i<100;i++){
    
                String key= UUID.randomUUID().toString();
    
                String value="value_"+i;
    
                //写入redis的节点 6379 6380 6381--hash取余计算方法
    
                //得到取余结果
    
                int index = (key.hashCode() & Integer.MAX_VALUE) % jedisList.size();
    
                //根据取余结果 从jedisList获取对应节点
    
                Jedis jedis = jedisList.get(index);
    
                jedis.set(key,value);
    
                if(i==rand){
    
                    keyS=key;
    
                }
    
            }
    
            //hash取余,获取keyS的对应节点,读数据如果读到了,说hash散列取余计算能保证单调性的
    
            int index = (keyS.hashCode() & Integer.MAX_VALUE) % jedisList.size();
    
            //根据取余结果 从jedisList获取对应节点
    
            Jedis jedis = jedisList.get(index);
    
            System.out.println(jedis.get(keyS));
    
        }
    
    • hash取余不适用于redis的分布式集群

      • 只要是hash必定数据倾斜; 可以通过key值的设计解决数据倾斜,key越规律(hashCode),数据倾斜越小.

      • 由于redis集群有动态线性扩展的特点.3个变5个,5个变12个,很频繁发生.hash取余无法保证扩容,缩容时的单调性.(单调性造成严重破坏)

一致性hash

原理

    • 基础:麻省理工大学97年创建的一个数学模型,后被应用到分布式领域.实现了一个hash映射计算,可以将任意的内存数据对象,通过hash散列映射到[0,2^32-1]

      由于结果是32位二进制,0减一变成2^32-1 最大数加一变成0,这个取值区间称谓hash环

redis如何被使用(redis结构变化)

不是增加服务器个数就一定能解决问题的。需要根据结构的变化处理新的问题

一旦引入分布式集群,需要解决 数据分片计算逻辑:数据切分,保证key-value单调性

两个计算方法目的:key-value找到对应节点写,并且key不变找到同一个节点读

hash取余:hash保证单调性,取余获取key与节点对应关系

hash取余算法,适用于n不变的环境,否则n经常变化,会破坏数据单调性

一致性hash:什么是一致性hash 描述一下。

基础:hash环,0-43亿整数区间做映射

数据映射:redis数据 节点数据,key值

对应关系: key整数顺时针寻找最近节点整数

平衡性:引入了计算的中间结果,虚拟节点,可以将数据映射关系做的更平均,160*weight,可以根据比例分配权重值。实现权重逻辑。

数据倾斜问题,靠调整key的取值结构,缓解 倾斜。key和节点是强耦合

Redis哨兵

介绍

1.redis中存在一个高可用结构的重要进程,sentinel哨兵,可以实现监听主从控制主从,完成主从复制基础之上的故障替换转移,主节点宕机,从节点顶替.哨兵也是redis-server进程,比较特殊,不处理数据增删查改,只负责监听主从

2.主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。

img

哨兵执行逻辑

  • 哨兵启动会连接主节点

  • 调用info replication 获取到主从集群的所有节点信息

  • 开启每秒钟的心跳检测(rpc远程通信协议),判断节点是否存活

    • 从节点宕机,哨兵进程只会做记录 down

    • 主节点宕机,哨兵开始发起投票选举,在多个从节点中选择其中一个顶替成为新的主节点,其他从节点哨兵重新挂接到新的主节点,宕机的主节点,哨兵会记录他为从节点(暂时无法到这个宕机执行命令) .当主节点恢复之后,哨兵到这个主节点执行挂接命令,挂接到新的主节点

  • 投票需要过半机制

    • 如果3个哨兵进程,2个以上选择同一个从节点,这个从节点才能顶替原有主节点提供功能

    • 不过半的投票结果是作废的,将会隔一段时间重新投票

搭建测试环境

redis哨兵创建
	cd /home/redis-3.2.11			//操作文件位置地址
	cp sentinel.conf sentinel-6380.conf		//复制
	vim sentinel-6380.conf			//编辑
	i
	修改端口
	:wq
	/home/redis-3.2.11/src/redis-sentinel /home/redis-3.2.11/sentinel-6380.conf	//启动哨兵
	ps -ef|grep redis				//查看是否启动
redis哨兵测试
	以下代码为添加三个哨兵
	修改redis根路径中的sentinel.conf为多个sentinel-6380.conf/sentinel-6381.conf,并修改里面的配置文件修改,
	sentinel monitor mymaster 127.0.0.1 6379 2
	sentinel monitor mymaster 127.0.0.1 6380 2
	sentinel monitor mymaster 127.0.0.1 6381 2
	,修改配置文件中的端口
	port 26379
	port 26380
	port 26381
	可以宕机主节点,查看哨兵配置日志文件发现执行流程

 

Redis持久化策略

持久化的目的

redis是一个内存数据库(非关系型数据库),当redis服务器重启,或电脑重启,数据会丢失,我们可以将redis内存中的数据持久化保存到硬盘的文件中。

持久化思想

redis操作时,首先操作的都是内存中的数据。redis根据自身的配置选择不同的持久化方式,定期将内存中的数据保存到本地磁盘中,当redis重启服务时,首先会根据配置文件中指定的持久化文件恢复内存数据。

RDB

img

工作方式

当 Redis 需要保存 dump.rdb 文件时, 服务器执行以下操作:

1.Redis 调用forks。同时拥有父进程和子进程。

2.子进程将数据集写入到一个临时 RDB 文件中。

3.子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文 件。这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益。

触发方式

自动触发:

触发条件可以通过redis.conf 配置文件中的 SNAPSHOTTING 下配置

手动触发:

save:阻塞命令,客户端无法进行命令操作 。bgsave:(非阻塞,子进程执行保存操作)

优势

1). 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比 如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的 备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。

2). 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到 其它存储介质上。

3). 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由 子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。

4). 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。

劣势

1). 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统 一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。

2). 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整 个服务器停止服务几百毫秒,甚至是1秒钟

AOF

概念

快照功能(RDB)并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将 丢失最近写入、且仍未保存到快照中的那些数据。 从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方 式: AOF 持久化。

触发方式:

将 redis.conf 的 appendonly 配置改为 yes 即可。

  AOF 保存文件的位置和 RDB 保存文件的位置一样,都是通过 redis.conf 配置文件的 dir 配置

创建原理

创建AOF

恢复原理

AOF运行原理 - 恢复

重启 Redis 之后就会进行 AOF 文件的载入。

  异常修复命令:redis-check-aof --fix 进行修复

AOF三种持久化策略:

在配置文件中,aof提供了三种持久化策略。可以通过对appendfsync字段配置:aof持久化策略的配置

1.Always

服务器每次写入一个命令,就调用一次fdatasync,将缓冲区命令写入到磁盘中,这种模式下,服务器即使 遭遇危机意外停机,也不会丢失任何成功执行的命令数据

2.Everrysec

每秒调用用一次fdatasync,将缓冲区命令写入磁盘,在这种模式写,服务器即使遭遇意外停机时,最多只丢失 一秒钟内执行的命令数据

3.No

服务器不主动调用fdatasync,由操作系统决定任何将缓冲区里面的命令写入磁盘里面,在这种模式写,服务器 遭遇意外停机时,丢失命令的数据是不确定的

4.always的速度慢,everysec和no都很快,默认值:everysec

AOF重写

因为 AOF 的运作方式是不断地将命令追加到文件的末尾, 所以随着写入命令的不断增加, AOF 文件的体积 也会变得越来越大。举个例子, 如果你对一个计数器调用了 100 次 INCR , 那么仅仅是为了保存这个计数器 的当前值, AOF 文件就需要使用 100 条记录(entry)。然而在实际上, 只使用一条 SET 命令已经足以保存 计数器的当前值了, 其余 99 条记录实际上都是多余的。为了处理这种情况, Redis 支持一种有趣的特性: 可以在不打断服务客户端的情况下, 对 AOF 文件进行重建(rebuild)。执行 bgrewriteaof 命令, Redis 将 生成一个新的 AOF 文件, 这个文件包含重建当前数据集所需的最少命令。Redis 2.2 需要自己手动执行 grewriteaof 命令; Redis 2.4 则可以通过配置自动触发 AOF 重写。

二者的区别

1.RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

2.AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

Redis事务

Redis事务的概念:

  Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

  总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。  

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

  批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。

Redis不保证原子性:

  Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

Redis事务的三个阶段:

  • 开始事务

  • 命令入队

  • 执行事务

Redis事务相关命令:

  watch key1 key2 ... : 监视一或多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断 ( 类似乐观锁 )

  multi : 标记一个事务块的开始( queued )

  exec : 执行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 ) 

  discard : 取消事务,放弃事务块中的所有命令

  unwatch : 取消watch对所有key的监控

Redis-Cluster集群

槽道原理

Redis穿透、击穿、雪崩

缓存穿透

现象:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。由于缓存是不命中时被动写的, 并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存 储层去查询,失去了缓存的意义。

在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过 大。

解决方案:

1.从DB中查询出来数据为空,也进行空数据的缓存,避免DB数据为空也每次都进行数据库查询; 2.使用布隆过滤器,但是会增加一定的复杂度及存在一定的误判率;

缓存雪崩

现象:大量key同一时间点失效,同时又有大量请求打进来,导致流量直接打在DB上,造成DB不可用。

解决方案:

1.设置key永不失效(热点数据); 2.设置key缓存失效时候尽可能错开; 3.使用多级缓存机制,比如同时使用redsi和memcache缓存,请求->redis->memcache->db; 4.购买第三方可靠性高的Redis云服务器;

缓存击穿

现象: 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

解决方案:

1.设置热点数据永远不过期。

2.接口限流与熔断,降级。重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接 口中的某些 服务 不可用时候,进行熔断,失败快速返回机制。

3.布隆过滤器。bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应 用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算 法和容器大小,

4.加互斥锁

Redis-session共享实战

概念

Redis最常见的用途是会话存储。与其他会话存储(如Memcache)不同,Redis可以保留数据,以便在缓存停止的情况下,在重新启动时,所有数据仍然存在。即便不是需要严格持续的任务,此功能仍可以为你的用户省去大量的麻烦。没有人会乐于见到他们的会话被无缘无故随机删掉。

代码测试

package cn.tedu.user.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jt.common.pojo.User;
import org.junit.jupiter.api.DynamicTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@RestController
public class RedisController {
    @Autowired
    private StringRedisTemplate redisTemplate;
    //请求到该节点 /cluster 传递参数 key value
    //方法中将key写入到redis集群中

    @RequestMapping("/cluster")
    public String setAndGet(String key, String value, HttpServletRequest request, HttpServletResponse response){
       /* Cookie cookie=new Cookie("name","王翠花");
        response.addCookie
                response.setPath
                        response.setMaxAge
       */ //redisTemplate封装底层client jedis lettuce
        //redisTemplate直接操作基础方法命令expire pexpire ttl pttl
        redisTemplate.expire(key,2, TimeUnit.HOURS);//设置超时时长
        Long ttl = redisTemplate.getExpire(key, TimeUnit.MINUTES);
        System.out.println("超时时间,还剩"+ttl+"分钟");
        Boolean exist= redisTemplate.hasKey(key);//exists
        System.out.println(key+(exist?"存在":"不存在"));
        redisTemplate.delete(key);
        //redisTemplate.expireAt("name",new Date("时间"));设置超时时间点
        //五种数据类型 封装了对应的操作对象
        //String 从template获取 opsForValue的对象
        ValueOperations<String, String> opsForString
                = redisTemplate.opsForValue();
        opsForString.increment("num",100);//incr incrby
        opsForString.set(key,value);
        //Hash 从template获取opsForHash
        HashOperations<String, Object, Object> opsForHash
                = redisTemplate.opsForHash();
        opsForHash.put("user","age","18");
        opsForHash.get("user","age");
        //List 从template获取opsForList
        ListOperations<String, String> opsForList
                = redisTemplate.opsForList();
        opsForList.leftPush("list01","100");
        opsForList.rightPop("list01");
        opsForList.size("list01");//llen
        opsForList.set("list01",0,"200");
        //SET opsForSet
        //ZSET opsForZSet
        return opsForString.get(key);

    }
    //执行续约逻辑
    /*
        判断 集群中,一个叫做"user" 的key值,剩余超时时间
        是否超过1小时,超过一小返回"剩余时间充足"
        如果不超过一小时,将其设置超时时间为2小时;返回"重置超时时间"
        getExpire TimeUnit
        expire
     */
    @RequestMapping("/lease")
    public String lease(){
        Long leftTime =
                redisTemplate.getExpire("user", TimeUnit.SECONDS);
        if(leftTime<60*60){
            //重置2小时超时
            redisTemplate.expire("user",2,TimeUnit.HOURS);
            return "当前user的数据重置2小时超时";
        }
        return "当前user数据剩余时间充足";
    }

   /* public static void main(String[] args) throws JsonProcessingException {
        User user=new User();
        user.setUserName("wangcuihua");
        user.setUserPassword("123456");
        //{"userName":"wangcuihua","passowrd":"123456",null,null}
        ObjectMapper objectMapper=new ObjectMapper();
        String userJson=objectMapper.writeValueAsString(user);
        System.out.println(userJson);
    }*/

}

Redis分布式锁与JDK锁实战

 

Rdis高级数据类型

HyperLogLog
  1. 采用一种基数算法,用于完成独立总数的统计。

  2. 占据空间小,无论统计多少个数据,只占12K的内存空间。-不精确的统计算法,标准误差为081%。

Bitmap
  1. 不是一种独立的数据结构,实际上就是字符串。-支持按位存取数据,可以将其看成是byte数组。-适合存储索大量的连续的数据的布尔值。

posted @ 2022-02-24 18:37  简讯_A  阅读(162)  评论(0)    收藏  举报