目录
1.redis事务处理实践
1.1Redis事务简介
概述
Redis采用了乐观所方式进行事务控制,它使用watch命令监视给定的key,当exec(提交事务)时候如果监视的key从调用watch后发生过变化,则整个事务会失败。也可以调用watch多次监视多个key。注意watch的key是对整个连接有效的,如果连接断开,监视和事务都会被自动清除。当然exec,discard,unwatch命令都会清除连接中的所有监视。
基本指令
redis进行事务控制时,通常是基于如下指令进行实现,例如:
multi 开启事务
exec 提交事务
discard 取消事务
watch 监控,如果监控的值发生变化,则提交事务时会失败
unwatch 去掉监控
Redis保证一个事务中的所有命令要么都执行,要么都不执行(原子性)。如果在发送EXEC命令前客户端断线了,则Redis会清空事务队列,事务中的所有命令都不会执行。而一旦客户端发送了EXEC命令,所有的命令就都会被执行,即使此后客户端断线也没关系,因为Redis中已经记录了所有要执行的命令。
1.2 Redis事务控制实践
exec提交事务
例如:模拟转账,tony 500,jack 200,tony转给jack100。
discard取消事务
注意redis事务太简单,没有回滚,而只有取消。
秒杀抢票事务处理
基于一个秒杀,抢购案例,演示redis乐观锁方式
1.3Jedis 客户端事务操作
基于Jedis进行事务测试
Jedis 客户端秒杀操作实践
2. redis主从复制实践
2.1 简介
单个Redis支持的读写能力还是有限的,此时我们可以使用多个redis来提高redis的并发处理能力,这些redis如何协同,就需要有一定的架构设计,这里我们首先从主从(Master/Slave)架构进行分析和实现.具体的架构图如下:
其中,master负责读写,并将数据同步到salve,从节点负责读操作.
2.2 快速入门实践
基于redis,设计一主从架构,一个Master,两个Slave,其中Master负责Redis读写操作,并将数据同步到Slave,Slave只负责读.,其步骤如下:
1. 将redis01拷贝两份,命令如下:
cp -r redis01/ redis02
cp -r redis01/ redis03
2.假如已有redis服务,先将原先所有redis服务停止(docker rm -f redis容器名),并启动新的redis容器,例如:
docker run -p 6379:6379 --name redis6379 \
-v /usr/local/docker/redis01/data:/data \
-v /usr/local/docker/redis01/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf \
--appendonly yes
docker run -p 6380:6379 --name redis6380 \
-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 \
--appendonly yes
docker run -p 6381:6379 --name redis6381 \
-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 \
--appendonly yes
3.检测redis服务角色
启动三个客户端,分别登陆三台redis容器服务,通过info指令进行角色查看,默认新启动的三个redis服务角色都为master.
127.0.0.1:6379> info replication
4。检测redis6379的ip设置
docker inspect redis6379
5.设置Master/Slave架构
slaveof 172.17.0.2 6379 #host为master的ip地址
6.再次登陆redis6379,然后检测info
[root@centos7964 ~]# docker exec -it redis6379 redis-cli
127.0.0.1:6379> info replication
7.: 登陆redis6379测试,master读写都可以
8. 登陆redis6380测试,slave只能读。
2.3 主从同步原理分析
Redis的主从结构可以采用一主多从结构,Redis主从复制可以根据是否是全量分为全量同步和增量同步。
Redis全量同步
Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下:
1)从服务器连接主服务器,发送SYNC命令;
2)主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
3)主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
4)从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
5)主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
6)从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;
Redis增量同步
Redis增量复制是指Slave初始化后,开始正常工作时主服务器发生的写操作同步到从服务器的过程。 增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
2.4 小节面试分析
如果redis要支持10万+的的并发你会怎么做?
单机的redis几乎不太可能说QPS超过10万+,除非一些特殊情况,比如你的机器性能特别好,配置特别高,物理机,维护做的特别好,而且你的整体的操作不是太复杂,一般的单机也就在几万。真正实现redis的高并发,需要读写分离。对缓存而言,一般都是用来支撑读高并发的,写的请求是比较少的,可能写请求也就一秒钟几千。读的请求相对就会比较多,例如,一秒钟二十万次读。所以redis的高并发可以基于主从架构与读写分离机制进行实现。
Redis的replication机制是怎样的?
(1)redis采用异步方式复制数据到slave节点。
(2)一个master node是可以配置多个slave node的。
(3)slave node也可以连接其他的slave node。
(4)slave node做复制的时候,是不会block master node的正常工作的。
(5)slave node在做复制的时候,也不会block对自己的查询操作,它会用旧的数据集来提供服务; 但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了。
(6)slave node主要用来进行横向扩容,做读写分离,扩容的slave node可以提高读的吞吐量。
3.redis 构建集群
3.1 简述
Redis单机模式可靠性保证不是很好,容易出现单点故障,同时其性能也受限于CPU的处理能力,实际开发中Redis必然是高可用的,所以单机模式并不是我们的终点,我们需要对目前redis的架构模式进行升级。Sentinel模式做到了高可用,但是实质还是只有一个master在提供服务(读写分离的情况本质也是master在提供服务),当master节点所在的机器内存不足以支撑系统的数据时,就需要考虑集群了。
Redis集群架构实现了对redis的水平扩容,即启动N个redis节点,将整个数据分布存储在这N个redis节点中,每个节点存储总数据的1/N。redis集群通过分区提供一定程度的可用性,即使集群中有一部分节点失效或无法进行通讯,集群也可以继续处理命令请求。
对于redis集群(Cluster),一般最少设置为6个节点,3个master,3个slave,其简易架构如下:
说明:redis集群是在启动redis监控下执行的,可以视为多个Master管理了多个slave
3.2 创建集群
3.2.1. 步骤
1.准备网络环境
创建虚拟网卡,主要是用于redis-cluster能于外界进行网络通信,一般常用桥接模式。
docker network create redis-net
2. 准备redis配置模板
创建配置模版目录 mkdir -p /usr/local/docker/redis-cluster
创建配置文件 cd /usr/local/docker/redis-cluster
编辑配置文件 vim redis-cluster.tmpl
在redis-cluster.tmpl中输入以下内容:
port ${PORT}
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 192.168.227.131
cluster-announce-port ${PORT}
cluster-announce-bus-port 1${PORT}
appendonly yes
说明:复制到文件中,同时修改ip 为宿主机地址
3.创建节点配置文件
在redis-cluser中执行以下命令:
for port in $(seq 8010 8015); \
do \
mkdir -p ./${port}/conf \
&& PORT=${port} envsubst < ./redis-cluster.tmpl > ./${port}/conf/redis.conf \
&& mkdir -p ./${port}/data; \
done
’说明:该命令是linux系统中的循环语句,循环创建不同端口的配置文件
4. 创建集群中的redis节点容器
for port in $(seq 8010 8015); \
do \
docker run -it -d -p ${port}:${port} -p 1${port}:1${port} \
--privileged=true -v /usr/local/docker/redis-cluster/${port}/conf/redis.conf:/usr/local/etc/redis/redis.conf \
--privileged=true -v /usr/local/docker/redis-cluster/${port}/data:/data \
--restart always --name redis-${port} --net redis-net \
--sysctl net.core.somaxconn=1024 redis redis-server /usr/local/etc/redis/redis.conf; \
done
说明:该命令是创建对应的节点容器,并对其中的挂载信息等进行配置
5.创建redis-cluster集群配置
docker exec -it redis-8010 bash #进入8010容器 准备配置
redis-cli --cluster create 192.168.126.130:8010 192.168.126.130:8011 192.168.126.130:8012 192.168.126.130:8013 192.168.126.130:8014 192.168.126.130:8015 --cluster-replicas 1
6.连接redis-cluster,并添加数据到redis
redis-cli -c -h 192.168.126.130 -p 8010
说明:系统会根据不同的key值算出哈希值,并将其存储到不同的端口的redis容器中,从而实现了redis的分布式缓存.
3.2.2. 拓展
在搭建过程,可能在出现问题后,需要停止或直接删除docker容器,可以使用以下参考命令:
批量停止docker 容器:
docker ps -a | grep -i "redis-801*" | awk '{print $1}' | xargs docker stop
批量删除docker 容器:
docker ps -a | grep -i "redis-801*" | awk '{print $1}' | xargs docker rm -f
批量删除文件 rm -rf 801{0..5}/conf/redis.conf
说明:管道中的几部分分别是获取对应的容器,选取第一部分,以及对其进行停用或删除
具体命令差别如图:
3.3 Jedis读写数据测试
@SpringBootTest
public class MasterSlaveTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void testMasterReadWrite(){//配置文件端口为6379
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("role", "master6379");
Object role = valueOperations.get("role");
System.out.println(role);
}
@Test
void testSlaveRead(){//配置文件端口为6380
ValueOperations valueOperations = redisTemplate.opsForValue();
Object role = valueOperations.get("role");
System.out.println(role);
}
}
说明:
4.Redis哨兵模式
4.1 简介
哨兵(Sentinel)是Redis的主从架构模式下,实现高可用性(high availability)的一种机制。
由一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。
4.2 哨兵快速入门
1.分别进入3台redis容器内部进行配置,在容器指定目录/etc/redis创建sentinel.conf文件
sentinel monitor redis6379 172.17.0.2 6379 1
2.在每个redis容器内部的/etc/redis目录下执行如下指令(最好是在多个客户端窗口执行)
3.打开一个新的客户端连接窗口,关闭redis6379服务(这个服务是master服务)
4.登陆ip为172.17.0.4对应的服务进行info检测,例如:
4.3 Sentinel 配置进阶
对于sentinel.conf文件中的内容,我们还可以基于实际需求,进行增强配置,例如:
sentinel monitor redis6379 172.17.0.2 6379 1 daemonize yes #后台运行 logfile "/var/log/sentinel_log.log" #运行日志 sentinel down-after-milliseconds redis6379 30000 #默认30秒
其中:
1)daemonize yes表示后台运行(默认为no)
2)logfile 用于指定日志文件位置以及名字
3)sentinel down-after-milliseconds 表示master失效了多长时间才认为失效
4.4 哨兵工作原理分析
1):每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令。
2):如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值(这个配置项指定了需要多少失效时间,一个master才会被这个sentinel主观地认为是不可用的。 单位是毫秒,默认为30秒), 则这个实例会被 Sentinel 标记为主观下线。
3):如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。
4):当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线 。
5):在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令 。
6):当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次 。
7):若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除。
8): 若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除
5.redis高级运用cache
5.1 cache简介
为什么要Cache
- 降低数据库的访问压力.
- 提高查询的性能,改善用户体验.
你都了解哪些Cache?
- 浏览器缓存
- 数据持久层的Cache(MyBatis中的Cache设计)
- 逻辑处理层的Cache(Spring中的Cache)
- CPU的高速缓存
本地缓存的cache设计
- 数据的存储结构(散列存储)
- 数据的淘汰算法(FIFO,LRU)
- 任务调度策略(定时刷新缓存)
- 缓存日志的记录(命中率)
- GC时会缓存数据的影响
5.2 本地缓存的设计
1.接口设计
/**Cache 接口规范设计*/
public interface Cache {
/** 存储数据*/
void putObject(Object key,Object value);
/** 基于key获取数据*/
Object getObject(Object key);
/** 移除指定key的数据*/
Object removeObject(Object key);
/** 清空缓存*/
void clear();
/** 获取缓存中数据的个数 */
int size();
//...
}
2.默认存储设计
package com.jt.cache;
import java.util.HashMap;
import java.util.Map;
public class DefaultCache implements Cache{
private Map<Object,Object> cache=new HashMap<>();
@Override
public void putObject(Object key, Object value) {
cache.put(key,value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public int size() {
return cache.size();
}
@Override
public String toString() {
return "DefaultCache{" +
"cache=" + cache.toString() +
'}';
}
public static void main(String[] args) {
Cache cache=new DefaultCache();
cache.putObject("A",100);
cache.putObject("B",200);
cache.putObject("C",300);
System.out.println(cache);
}
}