当前位置:   article > 正文

Redis-Docker(一)_docker exec -it 546b5c62bbf5 redis-cli

docker exec -it 546b5c62bbf5 redis-cli

1. 为什么使用缓存

缓存是改善性能的第一手段

2. 什么是NoSQL

no only sql : 泛指一切非关系型数据库

3.Redis

3.1 启动Redis

  1. 初识配置文件
内容意义
daemonize是否开启守护程序默认daemonize no
port端口号port 6379
databases开启多少个数据库databases 16
maxmemory配置内存大小maxmemory 200mb
requirepass设置请求密码requirepass 123456

2.docker启动redis

docker start redis #底层也是通过redis-server启动
  • 1

3.2 连接Redis

  1. 进入容器,连接Redis
# 进入容器
docker exec -it redis bash
# 正常连接redis
redis-cli
# 指定IP端口连接
redis-cli -h 127.0.0.1 -p 6379
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 常见命令
命令作用
exit退出
shutdown关闭
keys键(keys * 查看所有数据)
select更换数据库
exists判断一个键是否存在
del删除一个键或者多个
type获取键值的数据类型
flushall清空所有数据库
flushdb清除当前数据库

3.3 Redis数据类型

  • string 字符串(可以为整形、浮点型和字符串,统称为元素)
    • 常用命令:
      自加:incr
      自减:decr
      加: incrby
      减: decrby
  • list 列表(实现队列,元素不唯一,先入先出原则)
    • 常用命令:
      lpush:从左边推入
      lpop:从右边弹出
      rpush:从右变推入
      rpop:从右边弹出
      llen:查看某个list数据类型的长度
  • set 集合(各不相同的元素)
    • 常用命令
      sadd:添加数据
      scard:查看set数据中存在的元素个数
      sismember:判断set数据中是否存在某个元素
      srem:删除某个set数据中的元素
  • hash hash散列值(hash的key必须是唯一的)
    • 常用命令
      hash数据类型支持的常用命令:
      hset:添加hash数据
      hget:获取hash数据
      hmget:获取多个hash数据
  • sort set 有序集合
    • 常用命令
      sort set和hash很相似,也是映射形式的存储:
      zadd:添加
      zcard:查询
      zrange:数据排序

3.4 Redis数据持久化

两种数据持久化方案,分别为rdb和aof

  • rdb
    实现方式:
# 打开redis.conf文件,找到save 位置,去配置持久化
save 60 1000 # 每隔60,1000个数据发生改变就持久化
  • 1
  • 2
  1. fork一个进程,遍历hash table,利用copy on write,把整个db dump保存下来。
  2. save,bgsave,shutdown, slave 命令会触发这个操作。粒度比较大,如果save, shutdown, slave 之前crash了,则中间的操作没办法恢复。 把写操作指令,持续的写到一个类似日志文件里。(类似于从postgresql等数据库导出sql一样,只记录写操作)
    1. 粒度较小,crash(宕机)之后,只有crash之前没有来得及做日志的操作,这些数据是没办法恢复。
  • aof
    实现方式
# redis配置文件redis.conf
appendonly yes # redis每次接收到一条写命令,就会写入日志文件中
  • 1
  • 2
  1. 把写操作指令,持续的写到一个类似日志文件里。(类似于从postgresql等数据库导出sql一样,只记录写操作)
  2. 粒度较小,crash(宕机)之后,只有crash之前没有来得及做日志的操作,这些数据是没办法恢复。

区别:
3. 一个是持续的用日志记录写操作, crash(崩溃)后利用日志恢复, 愿意一些性能, 换取更高的缓存一致性(aof)
4. 一个是平时写操作的时候不触发写, 只有手动提交save命令, 或者是shutdown关闭命令时, 才触发备份操作, 操作频繁的时候, 不启用备份来换取更高的性能(rdb)

3.5 Redis事物管理

命令作用
multi开启事务
exec提交事务
discard取消事务
watch监控,如果监控的值发生变化,则提交事务时会失败
unwatch去掉监控

3.6 Redis有效时间

1.Expire (设置生效时长-单位秒)
语法:EXPIRE key seconds
2.Persist(取消时长设置)
语法:PERSIST key
3.pexpire(单位毫秒)
语法:PEXPIRE key milliseconds
应用于秒杀场景

4. Java操作数据库

4.1 Jedis的应用

  1. 添加依赖
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.5.2</version>
</dependency>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 连接Redis实现Redis数据库操作
@Test
void testRedisStringOper(){
    Jedis jedis=new Jedis("192.168.126.130", 6379);
    //jedis.auth("123456");假如你的redis设置了密码
    jedis.set("id", "101");
    jedis.set("name", "tony");
    System.out.println("set ok");
    String id=jedis.get("id");
    String name=jedis.get("name");
    System.out.println("id="+id+";name="+name);
    jedis.incr("id");
    jedis.incrBy("id", 2);
    System.out.println(jedis.strlen("name"));
    //......
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 说明:当在测试之前,我们需要将redis.conf配置文件中的bind 127.0.0.1元素注释掉,
    并且将其保护模式(protected-mode)设置为no(redis3.0之后默认开启了这个策略) ,当修改了配置以后,一定要记得重启redis,然后再进行访问

4.2 连接池JedisPool

@Test
void testJedisPool(){
    // 构建连接池配置信息
    JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
    // 设置最大连接数
    jedisPoolConfig.setMaxTotal(200);
    // 构建连接池
    JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.174.130", 6379);
    // 从连接池中获取连接
    Jedis jedis = jedisPool.getResource();
    // 读取数据
    System.out.println(jedis.get("name"));
    // 释放连接池
    jedisPool.close();
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

4.3 RedisTemplate

  1. 添加依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  1. 在application.yml中添加redis配置文件
spring:
  redis:
    host: 192.168.126.130
    port: 6379
  • 1
  • 2
  • 3
  • 4
  1. 实现redis字符串操作
@SpringBootTest
public class StringRedisTemplateTests {
    /**此对象为spring提供的一个用于操作redis数据库中的字符串的一个对象*/
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

  @Test
  void testRedisStringOper()throws Exception{
    //获取用于操作字符串的值对象
   ValueOperations<String, String> valueOperations
            = stringRedisTemplate.opsForValue();
   //向redis中存储数据
   valueOperations.set("ip", "192.168.174.130");
   valueOperations.set("state","1",1, TimeUnit.SECONDS);
   valueOperations.decrement("state");
   // Thread.sleep(2000);
   //从redis中取数据
   String ip=valueOperations.get("ip");
   System.out.println("ip="+ip);
   String state=valueOperations.get("state");
   System.out.println("state="+state);
  }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  1. 实现redis复杂数据操作
@SpringBootTest
public class RedisTemplateTests {
    /**
     * 通过此对象操作redis中复杂数据类型的数据,例如hash结构
     */
    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void testSetData(){
        SetOperations setOperations=redisTemplate.opsForSet();
        setOperations.add("setKey1", "A","B","C","C");
        Object members=setOperations.members("setKey1");
        System.out.println("setKeys="+members);
        //........
    }

    @Test
    void testListData(){
      //向list集合放数据
        ListOperations listOperations = redisTemplate.opsForList();
        listOperations.leftPush("lstKey1", "100"); //lpush
        listOperations.leftPushAll("lstKey1", "200","300");
        listOperations.leftPush("lstKey1", "100", "105");
        listOperations.rightPush("lstKey1", "700");
        Object value= listOperations.range("lstKey1", 0, -1);
        System.out.println(value);
      //从list集合取数据
        Object v1=listOperations.leftPop("lstKey1");//lpop
        System.out.println("left.pop.0="+v1);
        value= listOperations.range("lstKey1", 0, -1);
        System.out.println(value);
    }

    /**通过此方法操作redis中的hash数据*/
    @Test
    void testHashData(){
        HashOperations hashOperations = redisTemplate.opsForHash();//hash
        Map<String,String> blog=new HashMap<>();
        blog.put("id", "1");
        blog.put("title", "hello redis");
        hashOperations.putAll("blog", blog);
        hashOperations.put("blog", "content", "redis is very good");
        Object hv=hashOperations.get("blog","id");
        System.out.println(hv);
        Object entries=hashOperations.entries("blog");
        System.out.println("entries="+entries);
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

5. Redis分片

5.1 概念

Redis中的分片思想就是把鸡蛋放到不同的篮子中进行存储。因为一个redis服务的存储能力是有限。分片就是实现redis扩容的一种有效方案。

5.2 启动多个服务

docker run -p 6379:6379 --name redis01 \
-v /usr/local/docker/redis02/data:/data \
-v /usr/local/docker/redis02/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf 

docker run -p 6380:6379 --name redis02 \
-v /usr/local/docker/redis03/data:/data \
-v /usr/local/docker/redis03/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf 

docker run -p 6381:6379 --name redis03 \
-v /usr/local/docker/redis03/data:/data \
-v /usr/local/docker/redis03/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf 

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

5.3 操作分片数据

@Test	//分片
	public void shard(){
		
		
		//构造各个节点链接信息,host和port
		List<JedisShardInfo> infoList = 
new ArrayList<JedisShardInfo>();
		JedisShardInfo info1 = 
new JedisShardInfo("192.168.126.130",6379);
		//info1.setPassword("123456");
		infoList.add(info1);
		JedisShardInfo info2 = new JedisShardInfo("192.168.126.130",6380);
		infoList.add(info2);
		JedisShardInfo info3 = new JedisShardInfo("192.168.126.130",6381);
		infoList.add(info3);
		
		//分片jedis
		
		JedisPoolConfig config = new JedisPoolConfig();
		config.setMaxTotal(500);	//最大链接数
		
		ShardedJedisPool pool = new ShardedJedisPool(config, infoList);
		//ShardedJedis jedis = new ShardedJedis(infoList);
		ShardedJedis jedis = pool.getResource();	//从pool中获取
		for(int i=0;i<10;i++){
			jedis.set("n"+i, "t"+i);
		}
		System.out.println(jedis.get("n9"));
		jedis.close();
	}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

6. Redis缓存概念

6.1 缓存穿透

  1. 缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。

  2. 解决:

    1. 利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试。
    2. 采用异步更新策略,无论 Key 是否取到值,都直接返回。Value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
    3. 提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的 Key。迅速判断出,请求所携带的 Key 是否合法有效。如果不合法,则直接返回。

6.2 缓存雪崩分析

  1. 缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。
  2. 缓存雪崩解决方案:
    1. 给缓存的失效时间,加上一个随机值,避免集体失效。
    2. 使用互斥锁,但是该方案吞吐量明显下降了。
    3. 双缓存。我们有两个缓存,缓存 A 和缓存 B。缓存 A 的失效时间为 20 分钟,缓存 B 不设失效时间。自己做缓存预热操作。

双缓存的实现过程:从缓存 A 读数据库,有则直接返回;A 没有数据,直接从 B 读数据,直接返回,并且异步启动一个更新线程,更新线程同时更新缓存 A 和缓存 B。

7. 如何保证Redis和数据库数据一致

最经典的操作是:Cache Aside Pattern旁路缓存方案

  • 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
  • 更新的时候,先更新数据库,然后再删除缓存。

第一种方案:采用延时双删策略
在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。

1.具体的步骤就是:

  • 先删除缓存
  • 再写数据库
  • 休眠500毫秒
  • 再次删除缓存

2.设置缓存过期时间

从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。所有的写操作以数据库为准,只要到达缓存过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。

3.该方案的弊端

结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求的耗时。

第二种方案:异步更新缓存(基于消息队列)
订阅binlog消息的同步机制
binlog: mysql 的二进制日志文件
实现思路: MySQL binlog增量订阅消费 --> 消息队列 --> 增量数据更新到redis
操作:

  • 读取binlog后分析 ,利用消息队列,推送更新各台的redis缓存数据。

  • 这样一旦MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。

  • 其实这种机制,很类似MySQL的主从备份机制,因为MySQL的主备也是通过binlog来实现的数据一致性。

  • 消息队列: rabbitmq rocketmq

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/537936
推荐阅读
相关标签
  

闽ICP备14008679号