1. 程式人生 > >Redis專案實戰入門

Redis專案實戰入門

Redis專案實戰Demo

資料快取伺服器

分散式鎖setNX

搭建一個簡單的SpringBoot-MyBatis

資料庫依賴引入
	<!--資料庫依賴-->
   <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
       <version>5.1.38</version>
    </dependency>
    <dependency>
       <groupId>com.mchange</groupId>
       <artifactId>c3p0</artifactId>
	   <version>0.9.5.2</version>
    </dependency>
mybatis逆向生成外掛
<dependency>
   <groupId>org.mybatis.generator</groupId>
   <artifactId>mybatis-generator-maven-plugin</artifactId>
   <version>1.3.6</version>
</dependency>

 <!-- mybatis 逆向工程maven工具 -->
 <plugin>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-maven-plugin</artifactId>
    <version>1.3.2</version>
    <dependencies>
       <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
       <version>5.1.38</version>
       </dependency>
	</dependencies>
	<configuration>
	 <!--配置檔案的路徑 -->
		<configurationFile>
			${basedir}/src/main/resources/generatorConfig.xml
		</configurationFile>
		<overwrite>true</overwrite>
	</configuration>
</plugin>
設定Mapper檔案過濾
<resources>
	<resource>
		<directory>src/main/java</directory>
	<includes>
		<include>
			com/qf/redis_demo_lock/dao/mapper/*.xml
		</include>
    </includes>
</resource>
MapperScan
@MapperScan("com.qf.redis_demo_lock.dao")
public class RedisDemoLockApplication {}
application.yml核心配置檔案
server:
  port: 8080
spring:
  datasource:
    url: jdbc:mysql:///1806_shop
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
    type: com.mchange.v2.c3p0.ComboPooledDataSource
  redis:
    host: 192.168.184.130
mybatis:
  mapper-locations: classpath*:com/qf/redis_demo_lock/dao/mapper/*.xml
  type-aliases-package: com.qf.redis_demo_lock.entity

SpringBoot整合redis使用分散式鎖並抽取工具類

@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private UserMapper userMapper;
//
//    private String lockLua = "local c = redis.call(\"setNX\",KEYS[1],ARGV[1])\n" +
//            "if c == 1 then\n" +
//            "    redis.call(\"expire\",KEYS[1],ARGV[2])\n" +
//            "    return 1\n" +
//            "end\n" +
//            "return 0";
//    private String token = UUID.randomUUID().toString();
//
//    private String unlockLua = "local token = redis.call(\"get\",KEYS[1])\n"+
//            "local token2 = ARGV[1]\n"+
//            "if token == token2  then\n"+
//            "    redis.call(\"del\",KEYS[1])\n"+
//            "    return 1\n"+
//            "end\n"+
//            "return 0";
    @Autowired
    private LockUtil lockUtil;
    /**
     *
     * 快取失效的問題
     * 對於一個快取來說,通常都需要給一個超時時間,因為這樣可以有效的過濾掉快取伺服器中的冷門資料。
     * 加入某個時刻,一個熱門資料達到了快取失效時間,這個時候就會有一個快取重建的問題。
     * 假設快取失效的一瞬間,大量請求請求這個資料,
     * 那麼有可能很多請求同時去進行快取重建,這個時候資料庫的壓力瞬間暴漲,就可能導致資料庫崩潰
     *
     * 解決:通過分散式鎖,保證只有一個執行緒在進行快取重建
     *
     * @return
     */
    @Override
    public List<User> queryAll() {


        //簡單的實現了redis快取
        List<User> users = (List<User>) redisTemplate.opsForValue().get("users");
        //通過分散式鎖來實現快取
        //RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        //設定分散式鎖
        //Boolean aBoolean = connection.setNX("lock".getBytes(), token.getBytes());
        //通過指令碼設定鎖

        if(users == null){
            //設定失效時間 一分鐘
            //問題:如果該鎖在設定之後,就宕機了也會導致死鎖,所有隻有通過lua指令碼保證一個原子性的操作
            //connection.expire("lock".getBytes(),60);
            //上鎖
            boolean flag = lockUtil.lock("lock",60000);
            if(flag){
                //當前執行緒已經設定了分散式鎖
                System.out.println("查詢了資料庫");
                users = userMapper.queryAll();
                redisTemplate.opsForValue().set("users",users);
                //設定快取超時時間
                redisTemplate.expire("users",5, TimeUnit.MINUTES);
                //釋放鎖
                lockUtil.unlock("lock");
            }else{
                //沒有獲得分散式鎖
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return queryAll();
            }

            //釋放鎖
            //問題,如果在設定鎖和釋放鎖之間,程式宕機,會導致該死鎖,所以我們一般會通過設定失效時間解決
            //釋放鎖的時候,需要保證是同一條執行緒,不然所有的人都可以釋放該鎖,鎖機制將無意義,通過value值uuid控制
            //問題,這個時候lock失效了,然後被其他的請求修改了值,該鎖也會死鎖.所以也需要通過指令碼進行控制
//            byte[] bytes = connection.get("lock".getBytes());
//            if(bytes == token.getBytes()){
//                connection.del("lock".getBytes());
//            }

        }
        return users;
    }

    @Override
    public User queryOne(Integer id) {
        //簡單的實現了redis快取
        User user = (User) redisTemplate.opsForValue().get("user" + id);
        //通過分散式鎖來實現快取
        //RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        //設定分散式鎖
        //Boolean aBoolean = connection.setNX("lock".getBytes(), token.getBytes());
        //通過指令碼設定鎖

        if(user == null){
            //設定失效時間 一分鐘
            //問題:如果該鎖在設定之後,就宕機了也會導致死鎖,所有隻有通過lua指令碼保證一個原子性的操作
            //connection.expire("lock".getBytes(),60);
            //上鎖
            boolean flag = lockUtil.lock("userLock" + id,60000);
            if(flag){
                //當前執行緒已經設定了分散式鎖
                System.out.println("查詢了資料庫");
                user = userMapper.selectByPrimaryKey(id);
                redisTemplate.opsForValue().set("user" + id,user);
                //設定快取超時時間
                redisTemplate.expire("user" + id,5, TimeUnit.MINUTES);
                //釋放鎖
                lockUtil.unlock("userLock" + id);
            }else{
                //沒有獲得分散式鎖
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return queryOne(id);
            }

            //釋放鎖
            //問題,如果在設定鎖和釋放鎖之間,程式宕機,會導致該死鎖,所以我們一般會通過設定失效時間解決
            //釋放鎖的時候,需要保證是同一條執行緒,不然所有的人都可以釋放該鎖,鎖機制將無意義,通過value值uuid控制
            //問題,這個時候lock失效了,然後被其他的請求修改了值,該鎖也會死鎖.所以也需要通過指令碼進行控制
//            byte[] bytes = connection.get("lock".getBytes());
//            if(bytes == token.getBytes()){
//                connection.del("lock".getBytes());
//            }

        }
        return user;
    }

    @Override
    public int deleteUser(Integer id) {
        redisTemplate.delete("users");
        return userMapper.deleteByPrimaryKey(id);
    }

    @Override
    public int insertUser(User user) {
        redisTemplate.delete("users");
        User user1 = new User();
        user.setUsername("username");
        user.setPassword("password");
        return userMapper.insertSelective(user);
    }
}

LockUtil工具類

@Component
public class LockUtil {
    @Autowired
    private RedisTemplate redisTemplate;
    private RedisConnection connection;

    private String lockLua = "local c = redis.call(\"setNX\",KEYS[1],ARGV[1])\n" +
            "if c == 1 then\n" +
            "    redis.call(\"expire\",KEYS[1],ARGV[2])\n" +
            "    return 1\n" +
            "end\n" +
            "\n" +
            "return 0";
    //通過令牌與執行緒繫結實現鎖只有當前執行緒能釋放
    private ThreadLocal<String> threadLocal = new ThreadLocal<>();

    //private String token = UUID.randomUUID().toString();

    private String unlockLua = "local token = redis.call(\"get\",KEYS[1])\n"+
            "local token2 = ARGV[1]\n"+
            "if token == token2  then\n"+
            "    redis.call(\"del\",KEYS[1])\n"+
            "    return 1\n"+
            "end\n"+
            "\n" +
            "return 0";

    //將兩個指令碼存到redis上返回的字串唯一標識
    private String lockId;
    private String unlockId;

    @PostConstruct
    public void init() {
        connection = redisTemplate.getConnectionFactory().getConnection();
        //快取加鎖的lua指令碼
        lockId = connection.scriptLoad(lockLua.getBytes());
        //快取解鎖的lua指令碼
        unlockId = connection.scriptLoad(unlockLua.getBytes());
    }

    public boolean lock(String key,Integer time){
        System.out.println("新增分散式鎖");
        String token = UUID.randomUUID().toString();
        //把令牌放到當前執行緒中
        threadLocal.set(token);
        long result = connection.evalSha(lockId,
                ReturnType.INTEGER,
                1,
                key.getBytes(),
                token.getBytes(),
                "60000".getBytes());
        return result == 1;
    }
    public boolean unlock(String key){
        System.out.println("釋放分散式鎖");
        String token = threadLocal.get();
        long result = connection.evalSha(unlockId,
                ReturnType.INTEGER, 1,
                key.getBytes(), token.getBytes());
        return result == 1;
    }

}

SpringBoot整合redis使用快取註解

預設快取註解自帶分散式鎖,springBoot幫我們封裝好了,和之前我們自己手動封裝的效果其實是一致的

引入快取依賴
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
配置快取
spring:
  cache:
    type: redis
    redis:
      time-to-live: 60000
使用快取
@EnableCaching//開啟自動配置快取
public class RedisDemoLockApplication {}

#####第一個註解@Cacheable

@Override
@Cacheable(cacheNames = "cache",key = "'users'")
public List<User> queryAll() {
	System.out.println("查詢了資料庫");
	return userMapper.queryAll();
}
redis中:
keys * ---> "cache::users"

作用:進入目標方法之前,先查詢快取伺服器
如果快取伺服器中有結果直接返回,不再呼叫目標方法
如果快取伺服器中沒有結果,就將目標方法的返回值重建進快取伺服器中
屬性有:
cacheNames:快取key的字首
key:快取的key值
condition:只有滿足condition中表達式的結果才會去快取中查詢資料
unless:與condition相反,滿足unless中的表示式就不會去快取中查詢資料

第二個註解@CacheEvict
@Override
@CacheEvict(cacheNames = "cache",key = "'user' + #id")
public int deleteUser(Integer id) {
	return userMapper.deleteByPrimaryKey(id);
}

作用:進入目標方法之後,刪除掉指定快取
使用場景:
寫操作的時候需要更新快取中的中的資料,將快取中queryAll的key刪除

第三個註解@CachePut
@Override
@CacheEvict(cacheNames = "cache", key = "'stus'")
@CachePut(cacheNames = "cache",key = "'user' + #user.id")//此處需要主鍵回填
public int insertUser(User user) {
	User user1 = new User();
	user.setUsername("usernameCache");
	return userMapper.insertSelective(user);
}

作用:
和@Cacheable大概意思差不多,唯一的區別在於@CachePut標記的方法一定會被執行,同時方法的返回值也被記錄到快取中。
使用場景:
當需要根據請求改變值的時候,利用@CachePut將值改變並寫入到快取中,而@Cacheable標籤除了第一次之外,一直是取的快取的值。

第四個註解@Caching(多個其他註解)
@Override
@Caching(evict = {
	@CacheEvict(cacheNames = "cache",key = "'users'"),
	@CacheEvict(cacheNames = "cache",key = "'user' + #id")
})
public int deleteUser(Integer id) {
	return userMapper.deleteByPrimaryKey(id);
}

作用:可以用來巢狀其他@Cachexx註解