Redis

 

目录

 

【*】什么是redis

【*】Redis应用场景

【*】Linux安装Redis

【*】Redis五种数据类型

【*】Redis存放对象

【*】Redis客户端连接工具

【*】SpringBoot整合Redis

【*】jedis

【*】Redis库

【*】消息中间件Redis可以实现消息中间件(不推荐使用,不是专业的)

【*】Redis发布订阅

【*】Redis实现主从复制

【*】心跳检测

【*】哨兵机制

【*】Redis持久化

【*】RDB

【*】AOF

【*】AOF和RDB的区别

【*】断电和停止redis服务器的区别

【*】Redis事务

【*】实例

【*】Redis的集群方案

【*】Redis为什么要使用二级缓存

【*】缓存雪崩产生原因

【*】SpringBoot+Redis+Ehcache实现二级缓存

【*】一级缓存和二级缓存时间的问题

【*】redis和Ehcache缓存值不同步的解决方案

【*】Redis和数据库的区别

【*】为什么做读写分离

【*】创建伪集群

【*】Redis集群事务

【*】Redis攻击的手段:雪崩和穿透

【*】缓存穿透

【*】解决缓存穿透的方案

【*】Redis解决Session一致性问题原理


【*】什么是redis

是一种非非关系型数据库(NoSQL),内存数据库,以key-value方式进行存储的,是单线程(可以保证线程安全问题),可以设置有效期,能够使用持久化机制保证数据高可用

【*】Redis应用场景

1.令牌的生成(有有效期)

2.短信验证码(有有效期)

3.热点数据(访问量比较大,但是不经常访问的数据,减轻数据库的压力)

4.使用Redis实现消息中间件,能够实现发布订阅功能,但是不推荐

5.分布式锁(ZK,Redis)

6.网站的计数器,因为是单线程的,在高并发的情况下,可以保证数据全局的唯一性

【*】Linux安装Redis

  • 注意实现外网环境安装redis注意事项,默认不支持外网的访问

解决办法:

1.首先关闭防火墙

2.配置Redis为后台启动,在redis.conf将daemonize no 改成daemonize yes

3.注释掉#bind 127.0.0.1 开启外网访问,因为默认bind的是内网访问

安装过程

  • 安装过程

下载Redis安装包

wget http://download.redis.io/releases/redis-3.2.9.tar.gz

解压Redis安装包

tar -zxvf redis-3.2.9.tar.gz

安装

cd redis-3.2.9

Make

Cd src

make install PREFIX=/usr/local/redis

移动配置文件到安装目录下

cd ../

mkdir /usr/local/redis/etc

mv redis.conf /usr/local/redis/etc

 

配置redis为后台启动

vi /usr/local/redis/etc/redis.conf //将daemonize no 改成daemonize yes

vi /usr/local/redis/etc/redis.conf // requirepass 123

开启redis

/usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf

 

连接Redis客户端

 

./redis-cli -h 127.0.0.1 -p 6379 -a "123456"

PING 结果表示成功

 

关闭防火墙

//临时关闭

systemctl stop firewalld

//禁止开机启动

systemctl disable firewalld

Removed symlink /etc/systemd/system/multi-user.target.wants/firewalld.service.

Removed symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.

 

 

停止Redis服务

./redis-cli -h 127.0.0.1 -p 6379 -a "123456" 

修改redis.conf

注释掉

#bind 127.0.0.1 开启外网访问

 

 

 

 

【*】Redis五种数据类型

  • String(字符串)
redis 127.0.0.1:6379> SET mykey "lby"
OK

redis 127.0.0.1:6379> GET mykey
"lby"
  • List(列表)
redis 127.0.0.1:6379> LPUSH runoobkey redis
(integer) 1
redis 127.0.0.1:6379> LPUSH runoobkey mongodb
(integer) 2
redis 127.0.0.1:6379> LPUSH runoobkey mysql
(integer) 3
redis 127.0.0.1:6379> LRANGE runoobkey 0 10

1) "mysql"
2) "mongodb"
3) "redis"
  • Hash(字典)
127.0.0.1:6379>  HMSET runoobkey name "redis tutorial" 
127.0.0.1:6379>  HGETALL runoobkey
1) "name"
2) "redis tutorial"
3) "description"
4) "redis basic commands for caching"
5) "likes"
6) "20"
7) "visitors"
8) "23000"
  • Set(集合)
redis 127.0.0.1:6379> SADD runoobkey redis
(integer) 1
redis 127.0.0.1:6379> SADD runoobkey mongodb
(integer) 1
redis 127.0.0.1:6379> SADD runoobkey mysql
(integer) 1
redis 127.0.0.1:6379> SADD runoobkey mysql
(integer) 0
redis 127.0.0.1:6379> SMEMBERS runoobkey

1) "mysql"
2) "mongodb"
3) "redis"
  • Sorted Set(有序集合)
redis 127.0.0.1:6379> ZADD runoobkey 1 redis
(integer) 1
redis 127.0.0.1:6379> ZADD runoobkey 2 mongodb
(integer) 1
redis 127.0.0.1:6379> ZADD runoobkey 3 mysql
(integer) 1
redis 127.0.0.1:6379> ZADD runoobkey 3 mysql
(integer) 0
redis 127.0.0.1:6379> ZADD runoobkey 4 mysql
(integer) 0
redis 127.0.0.1:6379> ZRANGE runoobkey 0 10 WITHSCORES

1) "redis"
2) "1"
3) "mongodb"
4) "2"
5) "mysql"
6) "4"

下面就分别介绍这五种数据类型及其相应的操作命令。

【*】Redis存放对象

一般都是存储json,json转换成字符串,可以存成String类型,可以做序列化和反序列化

【*】Redis客户端连接工具

redisclient-win32.x86.1.5

【*】SpringBoot整合Redis

1.添加的依赖

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

2.配置文件application.yml

spring:
  redis:
    database: 0
    host: 192.168.43.218
    port: 6379
    password: 123456
    jedis:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 8
        min-idle: 0
    timeout: 10000

2.代码

①注入StringRedisTemplate类

②set方法的第一个参数是key,第二个是value,第三个是超时时间

@Component
public class RedisService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public void set(String key, Object object, Long time) {
        // 存放String 类型
        if (object instanceof String) {
            setString(key, object);
        }
        // 存放 set类型
        if (object instanceof Set) {
            setSet(key, object);
        }
        // 设置有效期 以秒为单位
        if (time != null) {
            stringRedisTemplate.expire(key, time, TimeUnit.SECONDS);
        }
    }

    public void setString(String key, Object object) {
        // 如果是String 类型
        String value = (String) object;
        stringRedisTemplate.opsForValue().set(key, value);
    }

    public void setSet(String key, Object object) {
        Set<String> value = (Set<String>) object;
        for (String oj : value) {
            stringRedisTemplate.opsForSet().add(key, oj);
        }
    }

    public String getString(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

}

3.Controller

@RestController
public class IndexControler {

	@Autowired
	private RedisService redisService;

	@RequestMapping("/setString")
	public String setString(String key, String value) {
		redisService.set(key, value, 60l);
		return "success";
	}

	@RequestMapping("/getString")
	public String getString(String key) {
		return redisService.getString(key);
	}

	@RequestMapping("/setSet")
	public String setSet() {
		Set<String> set = new HashSet<String>();
		set.add("yushengjun");
		set.add("lisi");
		redisService.setSet("setTest", set);
		return "success";
	}
}

【*】jedis

jedis就是集成了redis的一些命令操作,封装了redis的java客户端。提供了连接池管理。一般不直接使用jedis,而是在其上在封装一层,作为业务的使用。如果用spring的话,可以看看spring 封装的 redis Spring Data Redis

【*】Redis库

Redis有16个库,默认连接第一个,在reids.conf中找到databases的配置,后面默认的值是16,可以修改,同一个库中的内容是不允许重复的,但是不同的库中的key可以重复

【*】消息中间件Redis可以实现消息中间件(不推荐使用,不是专业的)

消息中间件本身就是异步通讯

举例:

实现发布订阅功能,如果在今日头条订阅了某个频道,当记者(消息生产者)向频道中发布了新闻,你(消费者)就会收到频道的新闻

【*】Redis发布订阅

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

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

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

在我们实例中我们创建了订阅频道名为 redisChat:

redis 127.0.0.1:6379> SUBSCRIBE redisChat

1) "subscribe"

2) "redisChat"

3) (integer) 1

现在,我们先重新开启个 redis 客户端,然后在同一个频道 redisChat 发布两次消息,订阅者就能接收到消息。

redis 127.0.0.1:6379> PUBLISH redisChat "嘿嘿"

(integer) 1

redis 127.0.0.1:6379> PUBLISH redisChat "lby"

(integer) 1

# 订阅者的客户端会显示如下消息

1) "message"

2) "redisChat"

3) "嘿嘿"

1) "message"

2) "redisChat"

3) "lby"

【*】Redis实现主从复制

  • 概念

在redis中只能有一个主(Master)服务器,可以有多个从服务器,主的服务器读写的操作都可以,从的服务器只允许做的操作

  • 主从复制的应用场景

集群,读写分离,日志备份,高可用

买的服务器就给分配一个外网的IP和一个内网的IP

集群这几件相互通讯肯定要使用内网,内网传输速度快,外网速度慢且不是很安全

  • 主从服务器数据同步过程

(1)从服务器启动的时候,回向主服务器发送sync命令,
(2)主服务器收到这个命令,一旦主服务器有任何写的操作,就会开始在后台保存快照(执行rdb操作)文件(将写的操作记录下来)    
(3)一旦快照文件有任何的更新,都会通知给下面所有的从服务器(主服务器主动向快照发推给从服务器)
(4)但是同步的过程会产生延迟的问题(只要做通讯就会有延迟,很正常的),一定要使用内网进行通讯,因为内网的速度是非常快的,这个延迟可以忽略不计,但是还是有可能出现延迟(概率比较小),但是就算概率小发生怎么办呢后续会有说明

  • 配置过程

1.关闭防火墙

2.分别修改主服务器和从Redis从服务器配置文件

搜索配置文件时可以使用/slaveof,就可以找到配置文件中slaveof的位置

①修改slave从redis中的 redis.conf文件slaveof <masteri> <master port>

<masteri>:主服务器内网的IP

<master port>:主服务器内网的端口

例如: slaveof 127.27.0.1 6379  

②masterauth 123456--- 主redis服务器配置了密码,则需要配置

配置完成

③一主多从,其他的从服务器也是这样的配置,多台服务器已经做成了集群

④redis只能有一个主服务器,从服务器的作用说白了就是在做数据的备份

主服务器的masterauth 123456一定要开启,否则主服务器挂掉,在重启变为从服务器的时候,不能实现数据的同步

【*】心跳检测

为了检测某些服务的有效性,可以用一个心跳检测的程序每隔一段时间向需要检测的服务器节点发送固定的信息,并且服务器需要返回响应的信息,心跳检测的程序长时间没有收到服务器回复的消息,代表这个服务器节点已经失效,就会断开连接

【*】哨兵机制

作用:管理多个Redis服务器,是Redis 的高可用性解决方案

  • 监控:不断的检测Master(主服务器),Slave(从服务器)是否运行正常
  • 提醒:当某个redis节点出现问题,哨兵可以根据API向管理员或者其他的应用程序发送通知
  • 故障转移:当一个主服务器(Master)发生故障时,就会在从服务器(Slaver)中选举出一个新的主服务器(Maseter),让失效的Master和其他的从服务器复制新Master(新的主服务器)的内容,并且集群会向客户端返回新的Master地址,使得集群可以使用新的Mater代替失效的Master,实现了高可用
  • 哨兵模式的配置

(1)一个redis配置一个哨兵

(2)Sentinel 节点不应该部署在一台物理“机器”上,它们虽然有不同的 IP 地址,但实际上它们都是同一台物理机,意味着如果这台机器有什么硬件故障,所有的虚拟机都会受到影响,那么就不能实现高可用了

哨兵模式修改配置
实现步骤:
1.拷贝到etc目录
cp sentinel.conf  /usr/local/redis/etc
2.修改sentinel.conf配置文件
sentinel monitor mymast  192.168.43.218 6379 1  #主节点 名称 IP 端口号 有几台sentinel认为主节点不可达就会发生故障转移,通常时sentinel半数加1
sentinel auth-pass mymaster 123456  #如果主节点设置了密码需要加上这个配置
3. 修改心跳检测 5000毫秒
sentinel down-after-milliseconds mymaster 5000
4.sentinel parallel-syncs mymaster 2 #最多有多少个从节点
5. 启动哨兵模式
./redis-server /usr/local/redis/etc/sentinel.conf --sentinel &
6. 停止哨兵模式
systemctl stop redis-sentinel

详情访问 https://www.cnblogs.com/kevingrace/p/9004460.html

【*】Redis持久化

  • 什么是Redis持久化,就是将内存数据保存到硬盘。
  • Redis 持久化存储 (AOF 与 RDB 两种模式)
  • 默认开启的RDB进行存储

【*】RDB

RDB 是以二进制文件进行存储,每隔一段时间将数据存储在dump.rdb临时文件中,从而实现持久化功能

  • 优点:使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
  • 缺点:RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候
  • RDB 默认开启,redis.conf 中的具体配置参数如下;

#dbfilename:持久化数据存储在本地的文件

dbfilename dump.rdb

#dir:持久化数据存储在本地的路径,如果是在/redis/redis-3.0.6/src下启动的redis-cli,则数据会存储在当前src目录下

dir ./

##snapshot触发的时机,save   

##如下为900秒后,至少有一个变更操作,才会snapshot 

##对于此值的设置,需要谨慎,评估系统的变更操作密集程度 

##可以通过“save “””来关闭snapshot功能 

#save时间,以下分别表示更改了1个key时间隔900s进行持久化存储;更改了10个key300s进行存储;更改10000个key60s进行存储。

save 900 1

save 300 10

save 60 10000

##当snapshot时出现错误无法继续时,是否阻塞客户端“变更操作”,“错误”可能因为磁盘已满/磁盘故障/OS级别异常等 

stop-writes-on-bgsave-error yes 

##是否启用rdb文件压缩,默认为“yes”,压缩往往意味着“额外的cpu消耗”,同时也意味这较小的文件尺寸以及较短的网络传输时间 

rdbcompression yes  

【*】AOF

AOF可以理解为日志文件,当有写的操作时AOF文件就会进行更新,因为AOF是实时的所以使用AOF更加的安全,但是也会因此产生较多的IO操作,但是AOF文件要比RDB文件大,所以数据恢复的速度比较慢

修改配置文件 reds.conf:appendonly yes

##此选项为aof功能的开关,默认为“no”,可以通过“yes”来开启aof功能  
##只有在“yes”下,aof重写/文件同步等特性才会生效  
appendonly yes  

##指定aof文件名称  
appendfilename appendonly.aof  

##指定aof操作中文件同步策略,有三个合法值:always everysec no,默认为everysec  
appendfsync everysec  
##在aof-rewrite期间,appendfsync是否暂缓文件同步,"no"表示“不暂缓”,“yes”表示“暂缓”,默认为“no”  
no-appendfsync-on-rewrite no  

##aof文件rewrite触发的最小文件尺寸(mb,gb),只有大于此aof文件大于此尺寸是才会触发rewrite,默认“64mb”,建议“512mb”  
auto-aof-rewrite-min-size 64mb  

##相对于“上一次”rewrite,本次rewrite触发时aof文件应该增长的百分比。  
##每一次rewrite之后,redis都会记录下此时“新aof”文件的大小(例如A),那么当aof文件增长到A*(1 + p)之后  
##触发下一次rewrite,每一次aof记录的添加,都会检测当前aof文件的尺寸。  
auto-aof-rewrite-percentage 100  

【*】AOF和RDB的区别

RDB 
优点:使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能 ,效率高
缺点:RDB是间隔一段时间进行持久化,如果持久化之间redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候

AOF
优点:可以保持更高的数据完整性
缺点:AOF文件比RDB文件大,且恢复速度慢,效率低

【*】断电和停止redis服务器的区别

断电:类似于杀死进程,不会进行持久化的操作,是比较危险的

停止redis服务,会在停止的时候会进行持久化

【*】Redis事务

  • redis里面是有事务的,但是用的不多
  • 命令是开启事务的命令MULTI
  • 开启事务之后的写的操作都会被事务管理
  • 当执行提交命令之后,才会正式的执行,提交的命令是EXEC

例如在集群中的主服务器开启事务进行写的操作,但是没有提交(没有进行EXEC)的操作,此时在从服务器上面是查询不到的,之后执行了提交的命令之后,才真正的提交,被从服务器查到

【*】实例

以下是一个事务的例子, 它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令:

redis 127.0.0.1:6379> MULTI
OK

redis 127.0.0.1:6379> SET myName "lby"
QUEUED

redis 127.0.0.1:6379> GET myName
(nil)

redis 127.0.0.1:6379> EXEC
1) OK

redis 127.0.0.1:6379> GET myName
"lby"


  •  SpringBoot开启redis事务
	public void setString(String key, Object object) {
		stringRedisTemplate.setEnableTransactionSupport(true);
		// 开启事务
		stringRedisTemplate.multi();
		try {
			// 如果是String 类型
			String value = (String) object;
			stringRedisTemplate.opsForValue().set(key, value);
		} catch (Exception e) {
			// 回滚
			stringRedisTemplate.discard();
		} finally {
			// 提交
			stringRedisTemplate.exec();
		}

	}

 

【*】Redis的集群方案

  • 主从复制:缺点,每个服务器的数据都一样,数据冗余,浪费内存
  • 客户端分片技术:没有故障转移功能
  • 使用代理工具:和反向代理比较相似
  • 分片集群:RedisCluster(redis3.0之后自己的开发,强烈推荐),不用自己实现故障转移,在内部已经做好了

【*】Redis为什么要使用二级缓存

可以抵抗雪崩效应,减轻redis的访问压力,提高访问速度

【*】缓存雪崩产生原因

在高并发的情况下,当redis挂掉之后,大量的请求会直接访问数据库,产生雪崩效应

【*】SpringBoot+Redis+Ehcache实现二级缓存

1.使用redis作为一级缓存,使用Ehcache作为二级缓存:当redis挂掉,有Ehcache作为备胎(本地jvm)

2.使用Ehcache作为一级缓存,使用Redis作为二级缓存:可以首先查询本地(Ehcache),本地没有之后,才走网络(查询redis),效率更高一点

3.maven依赖

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.0.RELEASE</version>
	</parent>
	<dependencies>
		<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.47</version>
		</dependency>
		<dependency>
	<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
		</dependency>
		<!-- SpringBoot web 核心组件 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

		<!--开启 cache 缓存 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>
		<!-- ehcache缓存 -->
		<dependency>
			<groupId>net.sf.ehcache</groupId>
			<artifactId>ehcache</artifactId>
			<version>2.9.1</version><!--$NO-MVN-MAN-VER$ -->
		</dependency>

		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.1.1</version>
		</dependency>
		<!-- mysql 依赖 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
	</dependencies>


	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<maimClass>
						com.lby.controller.IndexController</maimClass>
				</configuration>
				<executions>
					<execution>
						<goals>
							<goal>repackage</goal>
						</goals>
					</execution>
				</executions>

			</plugin>
		</plugins>
	</build>

4.Ehcache工具类

@Component
public class EhCacheUtils {

	// @Autowired
	// private CacheManager cacheManager;
	@Autowired
	private EhCacheCacheManager ehCacheCacheManager;

	// 添加本地缓存 (相同的key 会直接覆盖)
	public void put(String cacheName, String key, Object value) {
		Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
		Element element = new Element(key, value);
		cache.put(element);
	}

	// 获取本地缓存
	public Object get(String cacheName, String key) {
		Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
		Element element = cache.get(key);
		return element == null ? null : element.getObjectValue();
	}

	public void remove(String cacheName, String key) {
		Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
		cache.remove(key);
	}

}

5.Redis工具类

@Component
public class RedisService {

	@Autowired
	private StringRedisTemplate stringRedisTemplate;

	// public void set(String key, Object object, Long time) {
	// stringRedisTemplate.opsForValue();
	// // 存放String 类型
	// if (object instanceof String) {
	// setString(key, object);
	// }
	// // 存放 set类型
	// if (object instanceof Set) {
	// setSet(key, object);
	// }
	// // 设置有效期 以秒为单位
	// stringRedisTemplate.expire(key, time, TimeUnit.SECONDS);
	// }
	//
	public void setString(String key, Object object) {
		// 开启事务权限
		stringRedisTemplate.setEnableTransactionSupport(true);
		try {
			// 开启事务 begin
			stringRedisTemplate.multi();
			String value = (String) object;
			stringRedisTemplate.opsForValue().set(key, value);
			System.out.println("存入完毕,马上开始提交redis事务");
			// 提交事务
			stringRedisTemplate.exec();
		} catch (Exception e) {
			// 需要回滚事务
			stringRedisTemplate.discard();
		}
	}

	public void setSet(String key, Object object) {
		Set<String> value = (Set<String>) object;
		for (String oj : value) {
			stringRedisTemplate.opsForSet().add(key, oj);
		}
	}

	public String getString(String key) {
		return stringRedisTemplate.opsForValue().get(key);
	}

}

6.整合一级二级缓存

@Service
public class UserService {
	@Autowired
	private EhCacheUtils ehCacheUtils;
	private static final String CACHENAME_USERCACHE = "userCache";
	@Autowired
	private RedisService redisService;
	@Autowired
	private UserMapper userMapper;

	public Users getUser(Long id) {
             //先查询一级缓存的key,key以当前的类名+方法名+传递的参数值
		String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName()
				+ "-id:" + id;
		// 1.先查找一级缓存(本地缓存),如果本地缓存有数据直接返回
		Users ehUser = (Users) ehCacheUtils.get(CACHENAME_USERCACHE, key);
		if (ehUser != null) {
			System.out.println("使用key:" + key + ",查询一级缓存 ehCache 获取到ehUser:" + JSONObject.toJSONString(ehUser));
			return ehUser;
		}
		// 2. 如果本地缓存没有该数据,直接查询二级缓存(redis)
		String redisUserJson = redisService.getString(key);
		if (!StringUtils.isEmpty(redisUserJson)) {
			// 将json 转换为对象(如果二级缓存redis中有数据直接返回二级缓存)
			JSONObject jsonObject = new JSONObject();
			Users user = jsonObject.parseObject(redisUserJson, Users.class);
			// 更新一级缓存
			ehCacheUtils.put(CACHENAME_USERCACHE, key, user);
			System.out.println("使用key:" + key + ",查询二级缓存 redis 获取到ehUser:" + JSONObject.toJSONString(user));
			return user;
		}
		// 3. 如果二级缓存redis中也没有数据,查询数据库
		Users user = userMapper.getUser(id);
		if (user == null) {
			return null;
		}
		// 更新一级缓存和二级缓存
		String userJson = JSONObject.toJSONString(user);
		redisService.setString(key, userJson);
		ehCacheUtils.put(CACHENAME_USERCACHE, key, user);
		System.out.println("使用key:" + key + ",一级缓存和二级都没有数据,直接查询db" + userJson);
		return user;
	}

}

存储在Ehcache里面是对象,存储在Redis里面是Json

【*】一级缓存和二级缓存时间的问题

需要设置一级缓存要比二级缓存过期时间短,相同也不行,因为执行代码是有延迟的,会发生Redis和Ehcache完全不同步

【*】redis和Ehcache缓存值不同步的解决方案

一般使用定时Job和Mq通知,在后台启动一个JOB比对redis和Ehcache中的缓存值,如果Ehcache的版本号小于Redis的版本号,可以直接修改,如果Ehcache的版本号大于Redis版本号,直接将Ehcache和Redis缓存全部清除,当redis值发生变化可以使用Mq主动通知Ehcache去主动修改,到那时这样的成本非常大,但确实效率比较高,因为不需要走网络。

【*】Redis和数据库的区别

相同点:都需要进行网络连接,Ehcache不需要,是直接从内存获取的,效率最高

不同点: 存放的介质不同,redis存储在内存,数据库存储在硬盘上,在效率上来讲,存储在硬盘上需要进行IO操作,比直接操作内存效率要低

【*】为什么做读写分离

读写分库连接,读一个库,写一个库,互不影响,增加整体的吞吐量,主服务器可以做写,从服务器可以做读,两者相互分开,但是会产生集群同步问题(Redis自带可以解决)

【*】创建伪集群

1.创建文件夹

我们计划集群中 Redis 节点的端口号为 9001-9006 ,端口号即集群下各实例文件夹。数据存放在 端口号/data 文件夹中。

-p:可以直接创建子文件

mkdir /usr/local/redis-cluster
cd redis-cluster/
mkdir -p 9001/data 9002/data 9003/data 9004/data 9005/data 9006/data

2.复制脚本

cd /usr/local/redis-cluster
mkdir bin

cd /usr/local/redis-3.2.9/src

 cp mkreleasehdr.sh redis-benchmark redis-check-aof  redis-cli redis-server redis-trib.rb /usr/local/redis-cluster/bin

3.复制redis实例

cp -r /usr/local/redis    /usr/local/redis-cluster/9001

注意,修改 redis.conf 配置和单点唯一区别是下图部分,其余还是常规的这几项:

port 9001(每个节点的端口号)
daemonize yes
bind 192.168.119.131(绑定当前机器 IP)
dir /usr/local/redis-cluster/9001/data/(数据文件存放位置,在这个文件的最后一行加,shifit+G定位到最后一行)
pidfile /var/run/redis_9001.pid(pid 9001和port要对应)
cluster-enabled yes(启动集群模式)
cluster-config-file nodes9001.conf(9001和port要对应)
cluster-node-timeout 15000
appendonly yes

4.复制5个redis实例

我们已经完成了一个节点了,其实接下来就是机械化的再完成另外五个节点,其实可以这么做:把 9001 实例 复制到另外五个文件夹中,唯一要修改的就是 redis.conf 中的所有和端口的相关的信息即可,其实就那么四个位置。开始操作

\cp -rf /usr/local/redis-cluster/9001/*   /usr/local/redis-cluster/9002
\cp -rf /usr/local/redis-cluster/9001/*   /usr/local/redis-cluster/9003
\cp -rf /usr/local/redis-cluster/9001/*   /usr/local/redis-cluster/9004
\cp -rf /usr/local/redis-cluster/9001/*   /usr/local/redis-cluster/9005
\cp -rf /usr/local/redis-cluster/9001/*   /usr/local/redis-cluster/9006

\cp -rf 命令是不使用别名来复制,因为 cp 其实是别名 cp -i,操作时会有交互式确认

5.修改 9002-9006 的 redis.conf 文件

通过搜索会发现其实只有四个点需要修改,我们全局替换下吧,进入相应的节点文件夹,做替换就好了。


vi /usr/local/redis-cluster/9002/redis/etc/redis.conf

vi /usr/local/redis-cluster/9003/redis/etc/redis.conf
vi /usr/local/redis-cluster/9004/redis/etc/redis.conf
vi /usr/local/redis-cluster/9005/redis/etc/redis.conf
vi /usr/local/redis-cluster/9006/redis/etc/redis.conf


替换端口号
%s/9001/9002
%s/9001/9003
%s/9001/9004
%s/9001/9005
%s/9001/9006


其实我们也就是替换了下面这四行:


port 9002
dir /usr/local/redis-cluster/9002/data/
cluster-config-file nodes-9002.conf
pidfile /var/run/redis_9002.pid



6.启动9001-9006六个节点

/usr/local/redis/bin/redis-server  /usr/local/redis-cluster/9001/redis/etc/redis.conf
/usr/local/redis/bin/redis-server  /usr/local/redis-cluster/9002/redis/etc/redis.conf
/usr/local/redis/bin/redis-server  /usr/local/redis-cluster/9003/redis/etc/redis.conf
/usr/local/redis/bin/redis-server  /usr/local/redis-cluster/9004/redis/etc/redis.conf
/usr/local/redis/bin/redis-server  /usr/local/redis-cluster/9005/redis/etc/redis.conf
/usr/local/redis/bin/redis-server  /usr/local/redis-cluster/9006/redis/etc/redis.conf

7.找一个节点测试试

/usr/local/redis-cluster/9001/redis/bin/redis-cli -c -h 192.168.212.150 -p 9001

(error) CLUSTERDOWN Hash slot not served

这是因为虽然我们配置并启动了 Redis 集群服务,但是他们暂时还并不在一个集群中,互相直接发现不了,而且还没有可存储的位置,就是所谓的slot(槽)。

8.安装集群所需要的软件

由于 Redis 集群需要使用 ruby 命令,所以我们需要安装 ruby 和相关接口。

yum install ruby
yum install rubygems
gem install redis  使用本地上传方式

gem install -l redis-3.2.1.gem

9.application.yml配置文件(没有设置密码的方式)

spring:
  redis:
    database: 0
#    host: 132.232.44.194
#    port: 6379
#    password: 123456
    jedis:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 8
        min-idle: 0
    timeout: 10000
    cluster:
      nodes:
        - 192.168.212.149:9001
        - 192.168.212.149:9002
        - 192.168.212.149:9003
        - 192.168.212.149:9004
        - 192.168.212.149:9005
        - 192.168.212.149:9006

10.pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.lby</groupId>
	<artifactId>springboot2.0-redis</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.0.RELEASE</version>
	</parent>
	<dependencies>
		<!-- SpringBoot web 核心组件 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- SpringBoot对Redis支持 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
	</dependencies>


	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<maimClass>
						com.lby.controller.IndexController</maimClass>
				</configuration>
				<executions>
					<execution>
						<goals>
							<goal>repackage</goal>
						</goals>
					</execution>
				</executions>

			</plugin>
		</plugins>
	</build>

</project>

11.一般都是一主一备,最好是偶数

【*】Redis集群事务

redis集群对象JedisCluster不支持事务,但是,集群里边的单个节点支持事务,但是可以使用第三方的插件(使用Lua语言做第三方插件)做事务,使用JedisCluster的情况下,使用事务没有效

【*】Redis攻击的手段:雪崩和穿透

【解决雪崩的方案】

1.的分布式锁(本地锁)

当突然有大量的请求到数据库服务器的时候需要进行服务器请求的限制,可以使用锁的机制,保证只有一个线程(请求)对数据库进行访问,排队等待,如果是集群的服务器需要做分布式锁,单机模式可以使用本地锁(Ehcache),确实可以解决服务器的雪崩效用,但是会减少服务器的吞吐量(适合小项目)

@RequestMapping("/getUsers")
	public Users getByUsers(Long id) {
		// 1.先查询redis
		String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName()
				+ "-id:" + id;
		String userJson = redisService.getString(key);
		if (!StringUtils.isEmpty(userJson)) {
			Users users = JSONObject.parseObject(userJson, Users.class);
			return users;
		}
		Users user = null;
		try {
			lock.lock();
			// 查询db
			user = userMapper.getUser(id);
			redisService.setSet(key, JSONObject.toJSONString(user));
		} catch (Exception e) {

		} finally {
			lock.unlock(); // 释放锁
		}
		return user;
	}

注意:加锁排队只是为了减轻数据库的压力,并没有提高系统吞吐量。假设在高并发下,缓存重建期间key是锁着的,这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时,这是个治标不治本的方法。

2.使用消息中间件的方式(消息中间件可以解高并发)

当大量请求访问redis中没有值,把查询参数投放在消息队列,消费者接收到消息,查询数据库,得到结果使用同步方式,返回给生成者,消息中间件具有缓存消息的功能

3.使用以及和二级缓存(Eedis+Ehcache),缺陷集群不是很方便

4.均摊分配redis key的失效时间(不同的Key失效时间不同,需要根据业务划分,比较难管理),不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀

最常用3>4>2>1

缓存上一级还可以设置服务降级、隔离、容错、熔断等防止雪崩。----springcloud服务治理框架,服务容错机制hystrix

【*】缓存穿透

用户查询的数据在数据库中没有,缓存中自然也就没有,这样相当于绕过缓存直接查询数据库,当访问量超过数据库的承受范围,就会导致数据库出现问题,可能瞬间宕机,这就是缓存击穿

【*】解决缓存穿透的方案

1.使用网关,将一写不符合查询条件的key进行过滤掉,不然其访问

2.当用户查询的值在缓存和数据库中都不存在的情况下,就将对应的key,在缓存中存一个null,这样下次再查询这个Key的时候,直接在缓存中返回null,就不会查询数据库

3.但是上述方案会有一个问题,第一次查询的key在数据库中没有,在缓存中将其存为null,但是此时对应这个key被存储数据库了,此时,这个key在数据库中存在,但是在缓存中不存在;对于这样的解决方案是,插入缓存的key为null的key的时候,首先清除缓存,然后在插入数据库。

public String getByUsers2(Long id) {
		// 1.先查询redis
		String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName()
				+ "-id:" + id;
		String userName = redisService.getString(key);
		if (!StringUtils.isEmpty(userName)) {
			return userName;
		}
		System.out.println("######开始发送数据库DB请求########");
		Users user = userMapper.getUser(id);
		String value = null;
		if (user == null) {
			// 标识为null
			value = SIGN_KEY;
		} else {
			value = user.getName();
		}
		redisService.setString(key, value);
		return value;
	}

【*】Redis解决Session一致性问题原理

https://blog.csdn.net/grabungen/article/details/91950667

 

 

 

 



 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SuperLBY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值