第一部分
一、redis持久化策略
说明:
- redis作为内存数据库需要保持大量的用户的数据,但是redis中的数据保存到内存中断电,宕机,会导致内存数据丢失,所以为了防止数据丢失采用持久化方式,实现内存数据的备份。
策略:
- 当redis种内存有数据时,通过自身的策略有2种方式,一种RDB模式/AOF模式。根据各自模式的设定进行数据的持久化。RDB或者AOF都有各自的持久化文件。
- 当redis重启时,会根据配置文件中制定的持久化文件的名称,加载持久化文件,为了恢复内存数据。
RDB模式:
- 特点:RDB模式定期将内存中的数据持久化,如果用户允许丢失少量的数据,首选RDB模式。RDB模式定期为内存做快照,该方式的备份速度很快,
备份命令
dump.rdb–>查看加密
-
说明:执行redis备份命令需要在客户端中执行
命令:save 手动备份
命令:bgsave 后台备份 -
区别:save 会造成线程阻塞,只有当备份操作完成时,才允许直行redis更新操作
bgsave表示后台运行,不会造成线程阻塞,挑当前不忙的实践做数据备份。 -
备份方式: 看配合文件 redis.conf
配置文件说明
在900秒内redis做一个更新操作 备份一次
在300秒内redis做10次更新操作 备份一次
在60秒内,如果redis做一万次更新 备份一次
持久化文件名称及路径:
AOF模式:
说明:
AOF可以做到实时数据备份,该模式相当于通过配置文件记录了用户的全部操作过程
特点:
1、可以实时数据备份,安全性更好
2、持久化速度较RDB模式慢
3、AOF持久化文件的体积会很大
4、当数据恢复时,需要消耗很长的时间
RDB快(允许丢数据,快照形式保存,数据恢复快),AOF慢 (数据实时备份,安全性好,持久化文件体积大,数据恢复需要很长时间)
AOF模式持久化策略
1、开启AOF模式
开启AOF以后,RDB模式不生效
2、持久化策略
Redis内存策略:
**说明:**为了保证服务器正常运行,需要为redis设定最大内存空间,但是如果内存一直新增,内存很快就会占满,如果优化内存???
解决方案:采用LRU算法
可以将内存中最近最不常使用的数据清空,保存其他的数据
内存优化手段
1、设定内存空间 建议内存不要超过1G 256-512M 537行
2、内存策略
3、策略设定
※ 缓存使用的问题:
缓存穿透:
条件:访问一个不存在的数据
说明:当访问一个不存在的数据,因为缓存中没有这个key,导致缓存形同虚设,最终访问后台数据库,但是数据中没有该数据所以返回null
隐患:如果有人恶意频繁访问一个不存在的数据,可能会导致数据库服务负载高导致宕机。
总结:业务系统访问一个不存在的数据,称之为缓存穿透
防护:限定同一IP访问频率 【京东】
缓存击穿:
条件:当缓存key失效/过期/未命中时,高并发访问该key
说明:如果给一个key设定了失效时间,当key失效时有一万的并发请求访问这个key,这时缓存失效,所有的请求都会访问后台数据库,称之为缓存击穿。
场景:【微博】热点消息访问量很大,如果该缓存失效则会直接访问后台数据库,导致数据库负载过高
缓存雪崩:
前提:高并发访问,缓存命中较低或者失效时
说明:假设缓存都设定了失效时间,在同一时间内缓存大量失效。如果这时用户高并发访问,缓存命中率过低,导致全部的用户访问都会访问后台真实的数据库
场景:在高并发条件下,缓存动态更新时【flushall】
二、Redis分片技术
1、如果将全部数据都保存到一台redis中,如果该服务器损坏,则影响全部的服务
2、使用单台Redis内存设定一般不要超过1G,但是有些业务数据量很大,如果不修改内存则数据无法存储
改进方案:采用Redis分片技术实现
优点:
1、使用redis分片技术可以实现内存数据的动态扩容
2、使用分片每台redis节点中尽可能保存1/n的数据量,防止数据的丢失
3、对于用户而言,整个redis的分片就是一个服务
分片搭建:
1、复制配置文件:将redis.conf文件复制到shards中,启动几个就复制几个,63
79、6380、6381
2、修改端口号
说明:分别修改6380和6381的配置文件中的端口号
3、分别启动,查看redis
4、测试分片
pool.getResource()的类型是ShardedJedis
Hash一致性算法
数据如何保存?
2的32次方
node:IP地址+算法
顺时针寻找
==》
1、将节点的IP+算法确定唯一的哈希值,之后在内存中确定节点的位置
2、当保存数据时,根据key进行hash运算,确定唯一的一个位置【不保证绝对唯一】。
3、根据当前key位置顺时针查找最近的node节点进行挂载。
均衡性
说明:根据redis几点的个数要求尽可能的让数据均匀分片,每个节点中的数据尽可能保证1/n
提升:为了保证均衡性,hash一致性算法引入虚拟节点的概念,为了平衡数据。如果一个节点不够则声称多个节点。
单调性
说明:如果node节点新增,能够动态实现数据的迁移【原本属于nodeC的空间,分给了虚拟NodeB】不需要关心原先存储在C上但是分给虚拟NodeB上的数据,他们仍保存在原有位置,只不过所属位置区域的名称发生了改变
分散性
特点:由于分布式原因,导致用户使用内存时,不能获取全部的内存空间,导致一个key对应多个位置
负载
说明:从另一个角度考虑分散性
特点:一个位置对应多个key
如何解决分散性和负载的问题?
在进行架构设计时,要求使用全部的内存空间。
第二部分:Spring整合分片
分片:动态内存扩容,减少内存丢失。
一、编辑Properties文件
redis.host=192.168.126.166
redis.port.a=6379
redis.port.b=6380
redis.port.c=6381
redis.maxTotal=10000
二、编辑config-application-redis.xml
<!-- 实现spring整合分片 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="${redis.maxTotal}"/>
</bean>
<!-- 定义三个redis节点 -->
<!-- A -->
<bean id="shardInfoA" class="redis.clients.jedis.JedisShardInfo">
<constructor-arg name="host" value="${redis.host}"/>
<constructor-arg name="port" value="${redis.port.a}"/>
</bean>
<!-- B -->
<bean id="shardInfoB" class="redis.clients.jedis.JedisShardInfo">
<constructor-arg name="host" value="${redis.host}"/>
<constructor-arg name="port" value="${redis.port.b}"/>
</bean>
<!-- C -->
<bean id="shardInfoC" class="redis.clients.jedis.JedisShardInfo">
<constructor-arg name="host" value="${redis.host}"/>
<constructor-arg name="port" value="${redis.port.c}"/>
</bean>
<!-- 定义连接池 -->
<bean id="jedisPool" class="redis.clients.jedis.ShardedJedisPool">
<constructor-arg name="poolConfig" ref="poolConfig"/>
<constructor-arg name="shards">
<list>
<ref bean="shardInfoA"/>
<ref bean="shardInfoB"/>
<ref bean="shardInfoC"/>
</list>
</constructor-arg>
</bean>
三、编辑工具API
@Service
public class RedisService {
//有的工程需要,有的工程不需要。设置required=false,有就注入,没有就不注入。
@Autowired(required = false)
private ShardedJedisPool shardedJedisPool;
//传递redis的内容 key和value
private void set(String key,String value){
ShardedJedis jedis = shardedJedisPool.getResource();//获取连接
jedis.set(key,value);
shardedJedisPool.returnResource(jedis);;//返回连接
}
//设定超时时间
private void set(String key,String value,int seconds){
ShardedJedis jedis = shardedJedisPool.getResource();//获取连接
jedis.setex(key,seconds,value);
shardedJedisPool.returnResource(jedis);//返回连接
}
//传递key值
private String get(String key){
ShardedJedis jedis = shardedJedisPool.getResource();//获取连接
String result = jedis.get(key);//将获取到的内容存储到字符串中
shardedJedisPool.returnResource(jedis);//返回连接
return result;
}
}
四、将项目重新打包
五、修改ItemCatServiceImpl
@Service
public class ItemCatServiceImpl implements ItemCatService {
@Autowired
private ItemCatMapper itemCatMapper;
// 注入Redis
@Autowired
//private Jedis jedis;//不使用单台jedis 使用分片技术
private RedisService redisService;
private static ObjectMapper objectMapper = new ObjectMapper();
/**
* 1.根据parentId查询列表信息 2.将列表数据转化为返回voList集合
*/
public List<EasyUITree> findItemCatById(Long parentId) {
ItemCat itemCat = new ItemCat();
itemCat.setParentId(parentId);
List<ItemCat> itemCatList = itemCatMapper.select(itemCat);
List<EasyUITree> treeList = new ArrayList<>();
for (ItemCat itemCatTemp : itemCatList) {
EasyUITree easyUITree = new EasyUITree();
easyUITree.setId(itemCatTemp.getId());
easyUITree.setText(itemCatTemp.getName());
// 如果是父级则应该closed,如果不是父级 open
String state = itemCatTemp.getIsParent() ? "closed" : "open";
easyUITree.setState(state);
treeList.add(easyUITree);
}
return treeList;
}
// 通过redis缓存实现信息查询
public List<EasyUITree> findCacheItemCatById(Long parentId) {
// TODO Auto-generated method stub
List<EasyUITree> treeList = null;
// 1 查询缓存数据 要保证key不重复,操作的是item_cat对象 拼接parentId
String key = "ITEM_CAT_" + parentId;
String resultJson = redisService.get(key);
try {
// 2判断缓存数据是否为空
if (StringUtils.isEmpty(resultJson)) {
// 如果缓存内容为空,需要查询数据库中的内容 调用已经写好的方法
//找到数据后,将数据通过API转化为Json串
treeList = findItemCatById(parentId);
String jsonData = objectMapper.writeValueAsString(treeList);
//将数据库k-v写入缓存中
redisService.set(key, jsonData);
System.out.println("第一次查询数据库");
} else {
// 如果缓存中的数据不为空,则将Json串转化为Java对象,直接return返回
//将json串转为java对象【list
//先或得到数组 再转为集合
EasyUITree[] trees = objectMapper.readValue(resultJson, EasyUITree[].class);
treeList = Arrays.asList(trees);
System.out.println("查询缓存数据");
}
} catch (Exception e) {
// TODO: handle exception
}
return treeList;
}
}
第三部分:Redis哨兵
业务需求
采用redis分片技术可以实现内存扩容,但是如果其中一台机器宕机,则整个redis分片不能正常运行。
实现主从挂载【为redis高可用做准备】
角色划分:6379主 6380/6381从机
新建sentinel文件夹,其中有三台redis配置信息
检查节点状态
info replication
打开6380redis服务,将80挂载到79上
从机没有写的权限
切换到主机,可以修改
将6381挂载到6379上
查看一下主机状态
哨兵实现
原理:
1、哨兵监控主机状态,并且获取主机全部信息,包括从机信息。
2、当主机宕机【心跳检测】,三次连接没有响应,则断定主机宕机,哨兵会匆匆集中选举一台从机当主机。同事将另外的从机(旧的主机)修改挂在状态,挂载到新的主机上。
3、当旧的主机修复完成后,充当从机,等待下次选举。
编辑哨兵配置文件
1、同名配置文件sentinel.conf拷贝到sentinel文件中
2、编辑sentinel.conf配置文件
3、修改哨兵监听节点信息
只有一票,写1
4、修改推选时间10s
5、修改超时时间20s
哨兵的启动
测试,新建redis连接,关掉主机