1. 配置 Redis

确保Spring Boot 项目中已经包含了 Redis 和redisson的依赖。在 pom.xml 中添加以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.16.0</version>
</dependency>


2. 配置 Redisson

创建一个配置类来配置 Redisson

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        return Redisson.create(config);
    }
}


3. 创建访问量服务

在服务类中使用 Redisson 分布式锁来确保线程安全:

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class PageViewService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    private static final String PAGE_VIEW_KEY_PREFIX = "page:view:";
    private static final String LOCK_KEY_PREFIX = "lock:page:view:";

    public void incrementPageView(String pageId) {
        String redisKey = PAGE_VIEW_KEY_PREFIX + pageId;
        String lockKey = LOCK_KEY_PREFIX + pageId;
        RLock lock = redissonClient.getLock(lockKey);

        try {
            // 尝试获取锁,等待时间为2秒,锁的持有时间为10秒
            if (lock.tryLock(2, 10, TimeUnit.SECONDS)) {
                try {
                    // 如果Redis中没有该页面的访问量,则从数据库中读取并初始化
                    Object value = redisTemplate.opsForValue().get(redisKey);
                    if (value == null) {
                        // 假设从数据库中读取初始访问量数据
                        int initialViewCount = getInitialPageViewFromDatabase(pageId);
                        redisTemplate.opsForValue().set(redisKey, initialViewCount);
                    }
                    // 增加访问量
                    redisTemplate.opsForValue().increment(redisKey);
                } finally {
                    // 确保锁的释放
                    lock.unlock();
                }
            } else {
                // 锁等待超时的处理逻辑(可选)
                System.out.println("Failed to acquire lock for page: " + pageId);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            // 处理锁获取失败的情况
        }
    }

    public int getPageView(String pageId) {
        Object value = redisTemplate.opsForValue().get(PAGE_VIEW_KEY_PREFIX + pageId);
        return value != null ? Integer.parseInt(value.toString()) : 0;
    }

    private int getInitialPageViewFromDatabase(String pageId) {
        // 假设从数据库中读取初始访问量数据
        return 0; // 返回初始访问量
    }
}


调用incrementPageView(pageId)就能在redis里面自增
调用getPageView(pageId)获取redis中访问量

这里设置等待锁的时间为 2 秒,如果在 2 秒内无法获取锁,则返回 false,避免长时间等待。
但是这种固定10秒的锁可能会出现并发问题。比如:

  • 锁自动释放后新的请求获取锁:
    在锁自动释放后,其他等待的请求会有机会获取到锁。这可能会导致并发请求对同一个资源进行操作,可能出现数据一致性问题。

  • 未完成的业务逻辑被中断:
    原先持有锁的线程可能会在业务逻辑尚未完成的情况下失去锁,这样可能会导致未完成的操作影响系统的稳定性和数据的一致性。

可以使用 Redisson 看门狗机制,进行续锁,改进后:

public void incrementPageView(String pageId) {
    String redisKey = PAGE_VIEW_KEY_PREFIX + pageId;
    String lockKey = LOCK_KEY_PREFIX + pageId;
    RLock lock = redissonClient.getLock(lockKey);

    try {
        if (lock.tryLock(2, TimeUnit.SECONDS)) {
            // 锁定成功后启动看门狗
            try {
                boolean renewLock = true;
                while (renewLock) {
                    // 定期续期锁
                    renewLock = lock.renewLease(10, TimeUnit.SECONDS);
                    
                    // 执行业务逻辑
                    Object value = redisTemplate.opsForValue().get(redisKey);
                    if (value == null) {
                        int initialViewCount = getInitialPageViewFromDatabase(pageId);
                        redisTemplate.opsForValue().set(redisKey, initialViewCount);
                    }
                    redisTemplate.opsForValue().increment(redisKey);
                    
                    // 检查是否需要继续续期
                    if (/* 业务逻辑完成条件 */) {
                        renewLock = false;
                    }
                }
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println("Failed to acquire lock for page: " + pageId);
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}


大概逻辑就这样,具体看业务需求,俺只做个笔记!

目录