Redis的使用
一、Redis的安装
1、安装C语言编译环境:yum install -y gcc-c++
2、解压reids
:tar -zxvf redis-4.0.2.tar.gz -C /opt
3、修改redis
编译后的路径(修改解压目录src/Makefile
):
PREFIX?=/usr/local/redis
4、编译-进去redis
的解压目录:make install
5、定制配置项启动
复制redis.conf
:cp /opt/redis-4.0.2/redis.conf /usr/local/redis/
;
修改配置项:
配置项名称 | 作用 | 取值 |
---|---|---|
daemonize |
控制是否以守护进程形式运行redis 服务器 |
yes |
logfile |
指定日志文件位置 | "/var/logs/redis.log" |
dir |
redis 工作目录 |
/usr/local/redis |
protected-mode |
关闭保护,这样才能远程访问 | no |
注释掉#bind 127.0.0.1
二、Redis的使用
1、启动redis
服务
./redis-server ../redis-conf
2、引入reids
客户端
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
3、在application.properties
配置redis
spring.redis.host=192.168.163.211
spring.redis.port=6379
spring.redis.database=0
3、编写ReidsUtil
工具类
public class RedisUtil {
private JedisPool jedisPool;
public void initPool(String host,int port ,int database){
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(200);
poolConfig.setMaxIdle(30);
poolConfig.setBlockWhenExhausted(true);
poolConfig.setMaxWaitMillis(10*1000);
poolConfig.setTestOnBorrow(true);
jedisPool=new JedisPool(poolConfig,host,port,20*1000);
}
public Jedis getJedis(){
Jedis jedis = jedisPool.getResource();
return jedis;
}
}
4、编写RedisConfig
配置类同时将RedisUtil
注入到容器中
@Configuration
public class RedisConfig {
//读取配置文件中的redis的ip地址
@Value("${spring.redis.host:disabled}")
private String host;
@Value("${spring.redis.port:0}")
private int port;
@Value("${spring.redis.database:0}")
private int database;
@Bean
public RedisUtil getRedisUtil(){
if(host.equals("disabled")){
return null;
}
RedisUtil redisUtil=new RedisUtil();
redisUtil.initPool(host,port,database);
return redisUtil;
}
}
5、redis
的使用
@Override
public SkuInfo item(String skuId) {
SkuInfo skuInfo = null;
//从缓存中取出sku的数据
Jedis jedis = redisUtil.getJedis();
String skuInfoStr = jedis.get("sku:" + skuId + ":info");
skuInfo = JSON.parseObject(skuInfoStr, SkuInfo.class);
//如果缓存中没有数据时,则去访问数据库
if (skuInfo == null) {
//设置分布式锁(在Redis缓存在高并发下宕机后,为了防止多请求访问数据库导致数据库宕机,则设置分布式锁)
String OK = jedis.set("sku:" + skuId + ":lock", "1", "nx");
//如果返回的OK为null的话则存在分布式锁,如果不为null,则设置分布式锁成功
if (StringUtils.isBlank(OK)){
//分布式锁被占用,设置一定时间线程睡眠时间,等时间过后再次请求
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//自旋(递归的访问过程,不会开启新的线程)
return item(skuId);
}else {
//拿到分布式锁,可以访问数据库
skuInfo = itemFromDB(skuId);
}
jedis.del("sku:" + skuId + ":lock");
//同步缓存到Redis
jedis.set("sku:" + skuId + ":info", JSON.toJSONString(skuInfo));
}
jedis.close();
return skuInfo;
}
6、redis
中Key
的命名规范
由于redis
不像数据库那样有结构,其所以的数据全靠key
进行索引,所以redis
数据的可读性全依靠key
.
企业中最常用的方式就是:object:id:field
比如:sku:1314:info
; user:1092:password
;
三、Redis做分布式锁
通过UUID
生成一个唯一值,然后通过setnx
将该值存入到redis
中(可以设置过期时间),这样就设置了分布式锁。如何解锁呢?通过UUID
的值和redis
中的值进行比较,如果相同,则删除redis
中的该值,就释放了锁。
public String func(Integer id,String str){
//px为毫秒 ex为秒
String OK = jedis.set("item:" + id + ":lock", UUID.randomUUID(), "nx", "px", 10000);
if(StringUtils.isBlank(OK)){
//自旋
TimeUnit.SECONDS.sleep(10);
//执行一次
return func(id,str);
}else{
//执行业务操作
......
}
jedis.del("item:" + id + ":lock")
}
四、Redis与数据库数据一致性问题
redis
和mysql
数据同步是先删redis
后写mysql
还是先写mysql
后删redis
?
这两种方式均会出现数据不一致问题。因为写和读是并发的,没法保证顺序,如果删了缓存,还没来得及写库,另一个线程就来读取,发现缓存为空,则去数据库汇总读取数据写入缓存,此时缓存中为脏数据;如果先写库后删缓存,如果删缓存的线程宕机没有删除掉缓存,此时也会出现数据不一致的问题。如果是redis
集群,主从复制模式在复制的过程中存在一定的时间延迟,也会导致数据不一致问题。
【解决方案】:优先考虑先写入数据库,在一定的情况下可以允许数据缓存有误差,但是比如在结算等重要步骤时不允许数据不一致,此时应该进行数据一致性校验,校验成功后才能进行操作。