Redis的使用

一、Redis的安装

1、安装C语言编译环境:yum install -y gcc-c++

2、解压reidstar -zxvf redis-4.0.2.tar.gz -C /opt

3、修改redis编译后的路径(修改解压目录src/Makefile):
PREFIX?=/usr/local/redis

4、编译-进去redis的解压目录:make install

5、定制配置项启动

​ 复制redis.confcp /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、redisKey的命名规范

由于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与数据库数据一致性问题

redismysql数据同步是先删redis后写mysql还是先写mysql后删redis?

​ 这两种方式均会出现数据不一致问题。因为写和读是并发的,没法保证顺序,如果删了缓存,还没来得及写库,另一个线程就来读取,发现缓存为空,则去数据库汇总读取数据写入缓存,此时缓存中为脏数据;如果先写库后删缓存,如果删缓存的线程宕机没有删除掉缓存,此时也会出现数据不一致的问题。如果是redis集群,主从复制模式在复制的过程中存在一定的时间延迟,也会导致数据不一致问题。

【解决方案】:优先考虑先写入数据库,在一定的情况下可以允许数据缓存有误差,但是比如在结算等重要步骤时不允许数据不一致,此时应该进行数据一致性校验,校验成功后才能进行操作。

原文地址:https://www.cnblogs.com/hucheng1997/p/11803859.html