集群的实操
1、在/usr/local目录下创建一个文件夹redis-cluster1
mkdir /usr/local/redis-cluster1
2、在redis-cluster1中创建6个文件夹
mkdir 7001
mkdir 7002
....
mkdir 7006
3、将redis解压目录中的 redis.conf文件复制到7001中
cp redis.conf /usr/local/redis-cluster1/7001
4、vim这个文件进行更改
daemonize yes
port 7001
dir /usr/local/redis-cluster1/7001
cluster-enabled yes
cluster-config-file nodes-7001.conf
cluster-node-timeout 5000
appendonly yes
5、将7001中的redis.conf文件 分别复制到 7002-7006中 更改里面所有是7001的地方更改成 对应的端口号就可以了
...
6、安装ruby的相关的工具(5.0的时候没有使用ruby去创建集群这个步骤可省略)
yum install ruby
yum install rubygems
gem install redis
安装最后一个工具就出错了....
(centos7 安装较高版本ruby2.2/2.3/2.4+)按照步骤玩一次就对了
7、开启每一个服务
./redis-server /usr/local/redis-cluster1/7001/redis.conf
./redis-server /usr/local/redis-cluster1/7002/redis.conf
./redis-server /usr/local/redis-cluster1/7003/redis.conf
./redis-server /usr/local/redis-cluster1/7004/redis.conf
./redis-server /usr/local/redis-cluster1/7005/redis.conf
./redis-server /usr/local/redis-cluster1/7006/redis.conf
8、创建集群
以前的版本:
./redis-trib.rb create --replicas 1 106.54.13.167:7001 106.54.13.167:7002 106.54.13.167:7003 106.54.13.167:7004 106.54.13.167:7005 106.54.13.167:7006
现在的版本
redis-cli --cluster create 106.54.13.167:7001 106.54.13.167:7002 106.54.13.167:7003 106.54.13.167:7004 106.54.13.167:7005 106.54.13.167:7006 --cluster-replicas 1
登陆集群某一个客户端的命令
./redis-cli -c -h 106.54.13.167 -p 7001
1、如果集群中一个主服务器死了 那么整个集群的数据是否是完整的?
肯定是、因为主服务器死了之后会选举他自己原来的从服务器来完成接班操作
2、当死了的服务器从新启动之后 主服务器会自动的将数据同步给从服务器
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.54version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
#配置redis
spring.redis.host=39.99.200.54
#设置端口
spring.redis.port=6379
#给数据库设置密码
#spring.redis.password=xxxx
#设置对大的连接数
spring.redis.jedis.pool.max-active=10
#设置线程池中最大的空闲的连接
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.min-idle=0
#连接池最大的阻塞时间 -1的话那么 表示没有限制
spring.redis.jedis.pool.max-wait=-1ms
@Component //先放入IOC的容器
public class RedisManager {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 向Redis中存放一个键值对
* @param key
* @param value
*/
public void addKeyAndValue(String key,String value){
/**
* stringRedisTemplate.opsForValue(); 这个就是用来操作 String类型的
* stringRedisTemplate.opsForList(); 这个主要就用来操作list的
* stringRedisTemplate.opsForZSet(); 这个主要用来操作sorted set
* stringRedisTemplate.opsForHash(); 用来操作hash结构的
* stringRedisTemplate.opsForSet(); 这个就是用来操作Set数据类型的
*/
stringRedisTemplate.opsForValue().set(key,value);
}
/**
* 通过key获取String类型中的值
* @param key
* @return
*/
public String getValueForKey(String key){
return stringRedisTemplate.opsForValue().get(key);
}
}
/**
* 常用的API的意思
* //设置key过期的API
* stringRedisTemplate.expire("NZ1904",60, TimeUnit.SECONDS);
* //ttl :获取一个键的过期时间
* stringRedisTemplate.getExpire()
* // exists :判断一个键是否存在
* stringRedisTemplate.hasKey("");
* //del :删除一个键值对
* stringRedisTemplate.delete("key");
*incrby 这个命令
* stringRedisTemplate.opsForValue().increment("key",1);
*decrby这个命令
* stringRedisTemplate.opsForValue().increment("key",-1);
*hmget这个命令
*stringRedisTemplate.opsForHash().entries("");
*hmset
*stringRedisTemplate.opsForHash().putAll("",null);
*hset这个命令
*stringRedisTemplate.opsForHash().put();
* hdel命令
*stringRedisTemplate.opsForHash().delete()
* 判断hash结构中这个键是否存在
* stringRedisTemplate.opsForHash().hasKey()
* Set集合中获取某一个值
* stringRedisTemplate.opsForSet().members()
* 判断set集合中是否存在某一个值
*stringRedisTemplate.opsForSet().isMember()
* set集合设置值
* stringRedisTemplate.opsForSet().add()
*/
参考图
最终分析的代码
public String produceStock(){
String lock="lock";
String value=UUID.randomUUID().toString();
//设置个字符串
try {
// setnx命令
//Boolean tag = stringRedisTemplate.opsForValue().setIfAbsent(lock, "");
//给这个key设置过期时间
//执行下面这一句话的时候突然死了? 是不是又出现死锁了
//stringRedisTemplate.expire(lock,30,TimeUnit.SECONDS);
//这个命令的底层实际上 也运行的是 咋们的命令 在底层他是怎样来实现原子性的呢?
// 充分的利用了 redis和lua脚本 在C的层面上来实现原子性的a
//setnx 只有这个可以不存在的时候 这个才会被删除
Boolean tag=stringRedisTemplate.opsForValue().setIfAbsent(lock,value,30,TimeUnit.SECONDS);
if (!tag) {
return "目前排队人数过多...请稍后重试";
}
//超过了15
//开一个守护线程
MyThread myThread = new MyThread(lock);
myThread.setDaemon(true);
myThread.start();
// 每隔设置时间的3分支1就进行线程的续命
//一会初始值的时候我将火车票 放到redis中去
//减去库存
//去Redis中将库存数据给取出来
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("traintickes"));
//首先要判断下 这个库存是否>0
if (stock > 0) {
//说明可以减去库存
int rStock = stock - 1;
//下一步:将真实的库存放到咋们的Redis中去
stringRedisTemplate.opsForValue().set("traintickes", String.valueOf(rStock));
logger.info("扣减库存成功....剩余库存:" + rStock);
} else {
//说明不能扣减库存
logger.info("库存扣减失败、库存是负数、不足...");
} //已经用了15秒钟
}finally {
if(value.equals(stringRedisTemplate.opsForValue().get(lock))){
stringRedisTemplate.delete(lock);
}
}
return "抢票成功....";
}
Redission这个框架就解决了 分布式锁的问题
Reddsion这个框架实际上也是咋们的Redis的客户端代码
<dependency>
<groupId>org.redissongroupId>
<artifactId>redissonartifactId>
<version>3.11.0version>
dependency>
@Bean
public RedissonClient redissonClient(){
RedissonClient redissonClient=null;
//获取config的实例
Config config = new Config();
//设置请求的URL地址
String url="redis://106.54.13.167:6379";
//设置config
config.useSingleServer().setAddress(url);
//通过Redisson来创建一个客户端对象
try{
redissonClient= Redisson.create(config);
logger.info("创建RedissonClient成功");
return redissonClient;
}catch (Exception err){
logger.info("创建RedissonClient失败:"+err.fillInStackTrace());
return null;
}
}
@Component
public class DistributeRedisLock {
private Logger logger= LoggerFactory.getLogger(getClass());
@Autowired
private RedissonClient redissonClient;
//一个方法用来加锁
/**
* 加锁成功....
* @param lockName
* @return
*/
public boolean lock(String lockName){
try {
if(null==redissonClient){
//如果对象没有注入进来那么说明是有问题的
logger.info("注入redissonClient对象失败....");
return false;
}
//获取这个锁
RLock lock = redissonClient.getLock(lockName);
//锁住了
lock.lock(30, TimeUnit.SECONDS);
logger.info("加锁成功.......");
return true;
} catch (Exception e) {
logger.info("不可预期的异常造成了加锁失败....");
return false;
}
}
/**
* 释放锁
* @param lockName
* @return
*/
public boolean unlock(String lockName){
try {
if(null==redissonClient){
//说明没法释放出问题了....
logger.info("释放锁失败----"+lockName);
}
//获取到这个锁对象
RLock lock = redissonClient.getLock(lockName);
if(null!=lock){
lock.unlock();
logger.info("释放锁成功....");
return true;
}
return false;
} catch (Exception e) {
logger.info("释放锁失败了....");
return false;
}
}
}
public String produceStockRedisson(){
String lock="lock";
try {
boolean lock1 = distributeRedisLock.lock(lock);
if(true==lock1){
//说明加锁成功
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("traintickes"));
//首先要判断下 这个库存是否>0
if (stock > 0) {
//说明可以减去库存
int rStock = stock - 1;
//下一步:将真实的库存放到咋们的Redis中去
stringRedisTemplate.opsForValue().set("traintickes", String.valueOf(rStock));
logger.info("扣减库存成功....剩余库存:" + rStock);
} else {
//说明不能扣减库存
logger.info("库存扣减失败、库存是负数、不足...");
} //已经用了15秒钟
}else{
return "当前的排队人数过多...";
}
}finally {
distributeRedisLock.unlock(lock);
}
return "抢票成功....";
}
为什么键值要序序列化呢?
不同平台之间的数据传输 深拷贝 浅拷贝
Redis的序列化到底是什么?
简单的是说 就是 key 和 value存储到redis中的形式 这个样子是可以自己定义的
public class BoboSerializer implements RedisSerializer {
private Class clazz;
public BoboSerializer(Class clazz){
this.clazz=clazz;
}
/**
* 就是序列化的方法
* 简单的说就是将对象转换成字符串的方法
* @param o
* @return
* @throws SerializationException
*/
@Override
public byte[] serialize(Object o) throws SerializationException {
if(null==o){
return null;
}
//我们要将这个值转换成json对象存储到Redis中
String jsonString = JSON.toJSONString(o);
return jsonString.getBytes(Charset.forName("UTF-8"));
}
/**
* 反序列化
* 简单的说就是将redis中的字符串转换成 java对象的
* @param bytes
* @return
* @throws SerializationException
*/
@Override
public Object deserialize(byte[] bytes) throws SerializationException {
if(null==bytes) {
return null;
}
String strResult = new String(bytes);
//将String类型的数据(JSON)转换成java对象
return JSON.parseObject(strResult,clazz);
}
}
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
//设置连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置的是值的序列化器
// redisTemplate.setValueSerializer(new BoboSerializer(Object.class));
//在Redis着有提供json格式的的序列化器
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
return redisTemplate;
}
redisTemplate.opsForValue().set("user111",new User(2,"xiaobobo2","1232"));
什么是缓存穿透 …
简单的说就是获取数据的时候后 先去redis找数据 结果没找到 又去MySQL中找数据 结果还是没有找到 这样的话 那么 每一个线程进来都要去访问数据库、这样的话数据库的压力就很大 数据库就会奔溃 这种现象就叫做 缓存穿透
脑裂问题