Redis
一、Redis 的读并发量太大怎么办?
使用读写分离
10w/s
8w/s
二、单机版的Redis 挂掉怎么办?
三、需要写并发又要安全
之前我们只有一个主机,写的并发量有限,使用多个主机来做
在redis 3.0 后,官方发布了集群方案
3.1 中心化
3.2 去中心化
四、Redis集群的执行流程分析
16384个key
五、搭建集群
http://redis.cn/topics/cluster-tutorial.html
5.1 集群原理
去中心化
5.2 集群的规划
5.3 搭建过程
5.3.1 新建文件夹
5.3.2 准备一个服务端程序
5.3.3准备6个redis的配置文件
Redis-1
daemonize yes
bind 0.0.0.0
port 7000
# 开启集群
cluster-enabled yes
# 集群的配置文件,该文件自动生成
cluster-config-file nodes-1.conf
# 集群的超市时间
cluster-node-timeout 5000
# 打开aof 持久化
appendonly yes
Redis-2
daemonize yes
bind 0.0.0.0
port 7001
# 开启集群
cluster-enabled yes
# 集群的配置文件,该文件自动生成
cluster-config-file nodes-2.conf
# 集群的超市时间
cluster-node-timeout 5000
# 打开aof 持久化
appendonly yes
Redis-3
daemonize yes
bind 0.0.0.0
port 7002
# 开启集群
cluster-enabled yes
# 集群的配置文件,该文件自动生成
cluster-config-file nodes-3.conf
# 集群的超市时间
cluster-node-timeout 5000
# 打开aof 持久化
appendonly yes
Redis-4
daemonize yes
bind 0.0.0.0
port 7003
# 开启集群
cluster-enabled yes
# 集群的配置文件,该文件自动生成
cluster-config-file nodes-4.conf
# 集群的超市时间
cluster-node-timeout 5000
# 打开aof 持久化
appendonly yes
Redis-5
daemonize yes
bind 0.0.0.0
port 7004
# 开启集群
cluster-enabled yes
# 集群的配置文件,该文件自动生成
cluster-config-file nodes-5.conf
# 集群的超市时间
cluster-node-timeout 5000
# 打开aof 持久化
appendonly yes
Redis-6
daemonize yes
bind 0.0.0.0
port 7005
# 开启集群
cluster-enabled yes
# 集群的配置文件,该文件自动生成
cluster-config-file nodes-6.conf
# 集群的超市时间
cluster-node-timeout 5000
# 打开aof 持久化
appendonly yes
5.3.4 同时启动所有的redis
5.3.5 使用脚本创建集群(分配槽)
使用该脚本就可以创建
使用docker 下载redis-trib的镜像运行
A: 下载镜像
docker pull inem0o/redis-trib
B:创建集群
docker run -t -i inem0o/redis-trib create --replicas 1 192.168.231.143:7000 192.168.231.143:7001
192.168.231.143:7002 192.168.231.143:7003 192.168.231.143:7004 192.168.231.143:7005
-t(打开bash)
-i(能输入命令)
5.3.6 测试redis集群的路由效果
六、使用Redis 做缓存和位集合
Map->Redis
@annotation(注解)
6.1 配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="20"></property>
<property name="maxTotal" value="25"></property>
<property name="minIdle" value="10"></property>
</bean>
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg name="poolConfig" ref="poolConfig"></constructor-arg>
<constructor-arg name="host" value="192.168.231.143"></constructor-arg>
<constructor-arg name="port" value="6379"></constructor-arg>
</bean>
</beans>
6.2 改造代码
@Component
@Aspect
public class CacheAspect {
// private static Map<String,Object> caches = new HashMap<>();
@Autowired
private JedisPool pool;
private static final String ALL_MENU_LABEL = "alll-menu-data";
@Around("execution (* com.sxt.service.impl.MenuServiceImpl.loadAllMenu())")
public Object cache(ProceedingJoinPoint ponit) {
Jedis jedis = pool.getResource();
if(jedis.exists(ALL_MENU_LABEL)) {
String menuJson = jedis.get(ALL_MENU_LABEL);
List<Menu> menu = JSON.parseArray(menuJson, Menu.class);
return menu ;
}
// if(caches.containsKey(ALL_MENU_LABEL)) {
// System.out.println("缓存里面有");
// return caches.get(ALL_MENU_LABEL);
// }
Object result = null ;
try {
System.out.println("执行真实方法的调用");
result = ponit.proceed(ponit.getArgs()); // 在此实现了真实方法的调用
// 放入缓存
// caches.put(ALL_MENU_LABEL, result);
jedis.set(ALL_MENU_LABEL,JSON.toJSONString(result));
} catch (Throwable e) {
e.printStackTrace();
}
return result;
}
}
6.3 测试
开启redis(单机) mysql zk
七、在SpringBoot 里面使用Redis
7.1 新建项目
7.2 选择依赖
7.3 spring boot 是一个什么东西
Spring 项目创建的脚手架,里面充分利用习惯大于约束的原则来初始化一些常用对象,使得Spring的开发变得更简洁,可以做到不用写配置文件(或写很少的配置文件就可以实现功能)。
7.4 spring boot 如何加载默认的对象
为什么加上redis 的依赖就能创建redis的操作对象,不加就不创建?
7.5 Spring boot的核心
包扫描的使用(扫描文件夹)
7.6 修改配置文件
spring:
redis:
host: 192.168.231.143
port: 6379
pool:
max-active: 25
max-idle: 20
min-idle: 10
八、Spring boot 操作Redis
8.1 StringRedisTemplate(使用最多)
/**
* spring 和junit 整合的测试框架
* @author CodeLab
*
*/
/**
* 可以把是sping boot的项目启动起来
* @author CodeLab
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class StringRedisTemplateTest {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* redis数据类型为String的操作
*/
@Test
public void testString() {
// 操作String类型
ValueOperations<String, String> opsValue = redisTemplate.opsForValue();
// 给redis 里面set 一个key
opsValue.set("boot", "spring-boot"); // k -v 都是String
// 从redis 里面获取key
String value = opsValue.get("boot");
System.out.println(value);
// 从redis 里面或多个key
List<String> asList = Arrays.asList("boot","alll-menu-data");
List<String> mulitValues = opsValue.multiGet(asList);
System.out.println(mulitValues);
// redis的自动增长
Long increment = opsValue.increment("boot-incr", 2);// delta 可以+ 任意的数(步长)
System.out.println(increment);
}
@Test
public void testHash() {
HashOperations<String, Object, Object> opsForHash = redisTemplate.opsForHash();
// hset
opsForHash.put("object-1", "name", "ltd"); // 后面的2 个参数都是object,但是只支持String 类型
opsForHash.put("object-1", "age", "27"); // 后面的2 个参数都是object,但是只支持String 类型
opsForHash.put("object-1", "sex", "man"); // 后面的2 个参数都是object,但是只支持String 类型
Object value = opsForHash.get("object-1", "sex");
System.out.println(value);
// 取多个值
List<Object> multiGet = opsForHash.multiGet("object-1", Arrays.asList("name","sex"));
System.out.println(multiGet);
}
@Test
public void testZset() {
ZSetOperations<String, String> opsForZSet = redisTemplate.opsForZSet();
// 放到zset集合里面
opsForZSet.add("lol", "ltd", 2500);
opsForZSet.add("lol", "lz", 0);
opsForZSet.add("lol", "ln", 1400);
opsForZSet.add("lol", "ll", -10);
opsForZSet.add("lol", "lt", 2700);
Set<String> rangeAsc = opsForZSet.range("lol", 0, 2); // 通过排序取值 ll lz ln
System.out.println(rangeAsc);
Set<String> reverseRange = opsForZSet.reverseRange("lol", 0, 2);//lt ltd ln
System.out.println(reverseRange);
Set<TypedTuple<String>> tuples = new HashSet<ZSetOperations.TypedTuple<String>>();
tuples.add(new DefaultTypedTuple<String>("ltd", 1000.00) );
tuples.add(new DefaultTypedTuple<String>("lv", 1200.00) );
tuples.add(new DefaultTypedTuple<String>("lz", 2900.00) );
tuples.add(new DefaultTypedTuple<String>("lt", 100.00) );
// 若redis 存在该key ,则需要数据类型相同,不然报错
opsForZSet.add("dnf", tuples);
}
}
8.2 RedisTemplate(扩展String类型)
若不设置序列化规则,它将使用JDK自动的序列化将对象转换为字节,存到Redis 里面
可以设置序列化
/**
* spring 和junit 整合的测试框架
* @author CodeLab
*
*/
/**
* 可以把是sping boot的项目启动起来
* @author CodeLab
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTemplateTest {
@Autowired
private RedisTemplate<Object, Object> redisTemplate ; // 因为创建RedisTemplate 没有使用泛型信息来创建,泛型 本质还是Object,只不过泛型能自动推断并强转
@Test
public void testString() {
redisTemplate.setKeySerializer(new StringRedisSerializer()); // key的序列化使用String 类型来完成 因为key 很多时候都是一个字符串
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // 优先没有泛型的,因为有泛型后,它支持的数据类型就定了
ValueOperations<Object, Object> valueOperations = redisTemplate.opsForValue();
// valueOperations.set("boot-redis", "boot-value"); //对象->字符串 json
User user = new User(1, "ltd", "124", "liangtiandong@live.com", "xx.jpg");
// KEY : com.sxt.domain.User:1
// com.fasterxml.jackson.databind.JsonSerializer 没有依赖jackson 之前大家可能使用spring-boot-web,这里面会自动依赖
valueOperations.set(User.class.getName()+":"+user.getId(), user);
// 若该对象的强转转换,则redis 内部会使用JackSon 的工具将字符串-> 转换为java 对象 ,那jackson 转换为对象时,需要一个对象的类型 ,其实它已经自动对象的类型了"@class": "com.sxt.domain.User",
User object = (User)valueOperations.get(User.class.getName()+":"+user.getId());
System.out.println(object.getUsername()+":"+object.getIcon());
}
/**
* hash
*/
@Test
public void testHash() {
redisTemplate.setKeySerializer(new StringRedisSerializer()); // key的序列化使用String 类型来完成 因为key 很多时候都是一个字符串
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer()); // 若都是string 则和StringRedisTempalte一样了
HashOperations<Object, Object, Object> opsForHash = redisTemplate.opsForHash();
opsForHash.put("redis-hash", "prop1", "value");
}
}
8.3 RedisKeyValueTemplate(很少用)
数据库的存储引擎
InnoDb
不保存数据的id,我们的做法,只是将数据存入redis ,并没有set 集合
MyISAM,不仅仅保存数据,还把当前表里面存在id 也放在一个文件里面(set集合)
使用一个文件专门保存已经存入表里面的数据id
我想查询一个没有的数据
Id = 47 没有?MyISAM
我想统计当前表里面有多少个数据?
Innodb需要扫描全表
MyISAM:得到一个总条数
* spring 和junit 整合的测试框架
* @author CodeLab
*
*/
/**
* 可以把是sping boot的项目启动起来
* @author CodeLab
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class KeyValueRedisTemplateTest {
@Autowired
private RedisKeyValueTemplate redisKeyValue;
@Autowired
private StringRedisTemplate strRedis;
/**
* 别人写的有set 集合,有的好处就是查询一个不存在的对象,和统计数据的个数,已经获取前几条比较高效
*/
@Test // 和数据库的操作很像 可以根据对象的属性来查找数据
public void testCurd() {
User user = new User(1, "mjm", "1231", "4e12421", "r23r.jpg");
redisKeyValue.insert(user); //redis 如何存储一个对象?map - > hash 最合适 属性 属性值
// 对象->Map集合
// redisKeyValue.delete(objectToDelete);
// redisKeyValue.findById(id, type);
}
@Test
public void testFind() {
User findById = redisKeyValue.findById(3, User.class); // 1 先查询set(com.sxt.domain.User)集合里面是否有3 ,有:拼接key(com.sxt.domain.User:3) ,获取数据,没有,直接结束
System.out.println(findById);
}
@Test
public void testDelete() {
User delete = redisKeyValue.delete(3, User.class);
System.out.println(delete.getId());
}
@Test
public void testFindAll() {
Iterable<User> findInRange = redisKeyValue.findInRange(0, 2, User.class); // 从set 集合获取keys ,使用keys 查询
Iterator<User> iterator = findInRange.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next().getId());
}
}
/**
*自己写的存储对象。没有那个set 集合
*/
// @Test
public void testInsertObject() {
User user = new User(3, "mjm", "1231", "4e12421", "r23r.jpg");
HashOperations<String, Object, Object> opsForHash = strRedis.opsForHash();
String key = user.getClass().getName()+":"+user.getId();
Map<String, Object> map = new HashMap<String,Object>();
map = object2Map(user);
opsForHash.putAll(key, map);
}
private static Map<String, Object> object2MapXiaoWen(Object key) {
HashMap<String,Object> map = new HashMap<String,Object>();
String objectJson = JSON.toJSONString(key);
Map parseObject = JSON.parseObject(objectJson, Map.class);
return parseObject;
}
private static Map<String, Object> object2Map(Object object){
Class<?> clazz = object.getClass();
Map<String,Object> map = new HashMap<String,Object>();
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
String key = field.getName();
Object value = null ;
try {
value = String.valueOf(field.get(object));
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
map.put(key, value);
}
return map ;
}
public static void main(String[] args) {
User user = new User(2, "mjm", "1231", "4e12421", "r23r.jpg");
Map<String, Object> object2Map = object2Map(user);
object2Map.forEach((k,v)->{
System.out.println("k:"+k+"-----"+"v:"+v);
});
}
}
8.4 集群的额外操作
@Test
public void testCluster() {
ClusterOperations<String, String> opsForCluster = redisTemplate.opsForCluster();
opsForCluster.shutdown(new RedisClusterNode("192.168.231.143", 7000));
}
九、使用Redis 做缓存
9.1 在boot 里面使用Redis 做缓存
9.2 在你需要添加缓存的方法上面,添加注解
9.2.1 cacheable(使用最多)
就是一个完整的切面,先从缓存里面查询,若没有,查询数据库,把结果放入缓存里面
9.2.2 cacheput(解决脏读)
每次调用该方法,都会执行数据库查询,并且把缓存的值覆盖
9.2.3 cachEvict(解决脏读)
直接把缓存里面值删除
9.2.4 cacheconfig(全局的配置缓存)