io多路复用技术解释:
上面文字描述中的进入一个地方是指:
安装C 语言的编译环境:
yum install centos-release-scl scl-utils-build
yum install -y devtoolset-8-toolchain
scl enable devtoolset-8 bash
测试 gcc版本
前提条件:
后台启动:
redis-server /myredis/redis.conf (这里的 redis.conf 是从安装目录下面复制到myredis中来的)
redis使用到的是:
单线程+多路io复用
特点:
支持多数据类型,支持持久化,单线程+多路IO复用
http://www.redis.cn/commands.html
1、String的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配.
2、 如图中所示,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。
1、Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
2、它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。
List的数据结构为快速链表quickList。
使用场景:
需要修改redis.conf,将bind 127.0.0.1注释掉(表示只能本机连接);将protected-mode 改成no(后台启动)
10.1.2.1 setbit设置值
10.1.2.2 getbit获取值
10.1.2.3 bitcount 统计字符串被设置为1的bit数。
10.1.2.4 bitop (bitop是一个复合操作, 它可以做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在destkey中。)
and的测试:
1、Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
2、Redis事务的主要作用就是串联多个命令防止别的命令插队。
总结:Redis的事务可以看做一个队列,将需要执行的命令放在队列中。
注意:redis是单线程的,但是他不能处理多线程的并发问题,还是需要通过乐观锁或者悲观锁来处理
在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里
Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的;
官方推荐两个都启用。
如果对数据不敏感,可以选单独用RDB。
不建议单独用 AOF,因为可能会出现Bug。
如果只是做纯内存缓存,可以都不用。
package com.bear.service;
import com.bear.config.JedisPoolUtil;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisSentinelPool;
import java.io.IOException;
/**
* <简述>
* <详细描述>
*
* @author LiuShanshan
* @version $Id$
*/
@Service
public class SewckillServiceTwo {
String secKillScript ="local userid=KEYS[1];\r\n" +
"local prodid=KEYS[2];\r\n" +
"local qtkey='sk:'..prodid..\":qt\";\r\n" +
"local usersKey='sk:'..prodid..\":usr\";\r\n" +
"local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" +
"if tonumber(userExists)==1 then \r\n" +
" return 2;\r\n" +
"end\r\n" +
"local num= redis.call(\"get\" ,qtkey);\r\n" +
"if tonumber(num)<=0 then \r\n" +
" return 0;\r\n" +
"else \r\n" +
" redis.call(\"decr\",qtkey);\r\n" +
" redis.call(\"sadd\",usersKey,userid);\r\n" +
"end\r\n" +
"return 1" ;
public boolean doSecKill(String prodid,String uid) throws IOException {
// 从redis线程池里面获取redis线程
JedisSentinelPool jedisPoolInstance = JedisPoolUtil.getJedisFromSentinel();
Jedis jedis = jedisPoolInstance.getResource();
// JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
// Jedis jedis = jedisPoolInstance.getResource();
//String sha1= .secKillScript;
String sha1= jedis.scriptLoad(secKillScript);
Object result= jedis.evalsha(sha1, 2, uid,prodid);
String reString=String.valueOf(result);
if ("0".equals( reString ) ) {
System.err.println("已抢空!!");
}else if("1".equals( reString ) ) {
System.out.println("抢购成功!!!!");
}else if("2".equals( reString ) ) {
System.err.println("该用户已抢过!!");
}else{
System.err.println("抢购异常!!");
}
jedis.close();
return true;
}
}
package com.bear.config;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisSentinelPool;
import java.util.HashSet;
import java.util.Set;
public class JedisPoolUtil {
private static volatile JedisPool jedisPool = null;
private JedisPoolUtil() {
}
public static JedisPool getJedisPoolInstance() {
if (null == jedisPool) {
synchronized (JedisPoolUtil.class) {
if (null == jedisPool) {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(200);
poolConfig.setMaxIdle(32);
poolConfig.setMaxWaitMillis(100*1000);
poolConfig.setBlockWhenExhausted(true);
poolConfig.setTestOnBorrow(true); // ping PONG
jedisPool = new JedisPool(poolConfig, "120.48.77.231", 6379, 60000 );
}
}
}
return jedisPool;
}
private static JedisSentinelPool jedisSentinelPool = null;
public static JedisSentinelPool getJedisFromSentinel() {
if (jedisSentinelPool == null) {
Set<String> sentinelSet = new HashSet<>();
sentinelSet.add("120.48.77.231:26379");
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(200); //最大可用连接数
jedisPoolConfig.setMaxIdle(32); //最大闲置连接数
jedisPoolConfig.setMinIdle(5); //最小闲置连接数
jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待
jedisPoolConfig.setMaxWaitMillis(100*1000); //等待时间
jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping pong
return new JedisSentinelPool("mymaster", sentinelSet, jedisPoolConfig, 60000);
}
return jedisSentinelPool;
}
}
todo:上面说的坑,其实细想,应该是启动1个master和多个slave的时候,就应该设置他们的ip是真实ip(不使用127.0.0.1),然后在sentinel.conf中设置master的ip为真实ip。如果master挂掉,选举其他的slave为master,也能不用再改sentinel.conf中的配置将新的master的ip变成真实ip了。
根据虚拟机里面的真实ip来:
redis-cli --cluster create --cluster-replicas 1 192.168.0.4:6379 192.168.0.4:6380 192.168.0.4:6381 192.168.0.4:6389 192.168.0.4:6390 192.168.0.4:6391
注意:如果主节点下线?从节点能否自动升为主节点?注意:15秒超时
public class JedisClusterTest {
public static void main(String[] args) {
Set<HostAndPort> set =new HashSet<HostAndPort>();
set.add(new HostAndPort("120.48.77.231",6379));
JedisCluster jedisCluster=new JedisCluster(set);
jedisCluster.set("k1", "v1");
System.out.println(jedisCluster.get("k1"));
}
}
第一种:加锁+解锁+uuid防止误删
第二种:加锁+解锁+uuid防止误删+lua脚本保证删除的原子性
package com.atguigu.redis_springboot.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
/**
*<简述> 优化之LUA脚本保证删除的原子性
*<详细描述>
* @author Liushanshan
* @param
* @return void
*/
@GetMapping("testLockLua")
public void testLockLua() {
//1 声明一个uuid ,将做为一个value 放入我们的key所对应的值中
String uuid = UUID.randomUUID().toString();
//2 定义一个锁:lua 脚本可以使用同一把锁,来实现删除!
String skuId = "25"; // 访问skuId 为25号的商品 100008348542
String locKey = "lock:" + skuId; // 锁住的是每个商品的数据
// 3 获取锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 3, TimeUnit.SECONDS);
// 第一种: lock 与过期时间中间不写任何的代码。
// redisTemplate.expire("lock",10, TimeUnit.SECONDS);//设置过期时间
// 如果true
if (lock) {
// 执行的业务逻辑开始
// 获取缓存中的num 数据
Object value = redisTemplate.opsForValue().get("num");
// 如果是空直接返回
if (StringUtils.isEmpty(value)) {
return;
}
// 不是空 如果说在这出现了异常! 那么delete 就删除失败! 也就是说锁永远存在!
int num = Integer.parseInt(value + "");
// 使num 每次+1 放入缓存
redisTemplate.opsForValue().set("num", String.valueOf(++num));
/*使用lua脚本来锁*/
// 定义lua 脚本
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// 使用redis执行lua执行
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
// 设置一下返回值类型 为Long
// 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
// 那么返回字符串与0 会有发生错误。
redisScript.setResultType(Long.class);
// 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
} else {
// 其他线程等待
try {
// 睡眠
Thread.sleep(1000);
// 睡醒了之后,调用方法。
testLockLua();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
*<简述> 加锁,释放锁;加入uuid防误删
*<详细描述>
* @author Liushanshan
* @param
* @return void
*/
@GetMapping("testLock")
public void testLock(){
String uuid = UUID.randomUUID().toString();
//1获取锁,setne
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,3, TimeUnit.SECONDS);
//2获取锁成功、查询num的值
if(lock){
Object value = redisTemplate.opsForValue().get("num");
//2.1判断num为空return
if(StringUtils.isEmpty(value)){
return;
}
//2.2有值就转成成int
int num = Integer.parseInt(value+"");
//2.3把redis的num加1
redisTemplate.opsForValue().set("num", ++num);
//2.4释放锁,del
//判断比较uuid值是否一样
String lockUuid = (String)redisTemplate.opsForValue().get("lock");
if(lockUuid.equals(uuid)) {
redisTemplate.delete("lock");
}
}else{
//3获取锁失败、每隔0.1秒再获取
try {
Thread.sleep(100);
testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@GetMapping
public String testRedis() {
//设置值到redis
redisTemplate.opsForValue().set("name","lucy");
//从redis获取值
String name = (String)redisTemplate.opsForValue().get("name");
return name;
}
}