2024-09-09

在Redis中,使用SCAN命令可以进行百万级的键模糊查找。SCAN命令是以非阻塞的方式迭代当前数据库中的数据库键。

SCAN命令的基本语法如下:




SCAN cursor [MATCH pattern] [COUNT count]
  • cursor:迭代的游标,初始时以0开始,迭代过程中需要使用上一次返回的游标继续进行迭代。
  • MATCH pattern:可选参数,用于指定匹配的模式。
  • COUNT count:可选参数,提供每次迭代返回的近似键数,默认值通常为10。

SCAN命令返回两个元素,第一个是新的游标,第二个是匹配的键列表。当返回的游标为0时,表示迭代结束。

KEYS命令相比,SCAN命令的优势在于它不会阻塞服务器,使用SCAN命令可以逐步遍历大数据集合。

KEYS命令在处理大数据集时应谨慎使用,因为它可能会导致服务器阻塞。

下面是一个使用SCAN命令进行模糊查找的例子:




# 假设我们要查找所有以"user:"开头的键

# 初始化游标为0
scan 0 MATCH user:* COUNT 100

# 假设返回的游标为"42",则继续迭代
scan 42 MATCH user:* COUNT 100

# 继续,直到游标返回0,表示遍历完成

请注意,COUNT选项只是一个提示,返回的键数可能会超过或少于指定的COUNT值。使用SCAN命令时,应该保持一个循环,使用新的游标值继续迭代,直至游标值为0。

2024-09-09



import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
@Component
public class RepeatSubmitInterceptor implements HandlerInterceptor {
 
    @Autowired
    private StringRedisTemplate redisTemplate;
 
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String token = request.getHeader("token");
        if (redisTemplate.opsForValue().get(token) != null) {
            // Token存在,表示重复提交
            return false;
        } else {
            // Token不存在,表示第一次提交,存入Redis并设置过期时间
            redisTemplate.opsForValue().set(token, "1", 10, TimeUnit.MINUTES);
            return true;
        }
    }
}

这段代码实现了一个简单的拦截器,用于检测请求是否为重复提交。如果请求的header中包含token,并且Redis中已经存在这个token,就认为是重复提交,返回false拦截请求。如果Redis中不存在这个token,就将它添加到Redis中,并设置合适的过期时间,从而允许请求通过,并认为这是第一次提交。这样可以有效地防止用户因为网络问题而导致的重复提交,增加了用户体验。

2024-09-09

以下是一个简化的Spring Boot 3 + Spring Security 6 + JWT + Redis的项目框架示例。

  1. 引入依赖(pom.xml):



<dependencies>
    <!-- Spring Boot 3 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring Security 6 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- JWT -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
    <!-- Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>
  1. 配置Security和JWT(SecurityConfig.java):



@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Autowired
    private UserDetailsService userDetailsService;
 
    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
 
    @Autowired
    private JwtRequestFilter jwtRequestFilter;
 
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }
 
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .cors()
            .and()
            .csrf().disable()
            .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
            .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            .antMatchers("/authenticate").permitAll()
            .anyRequest().authenticated();
 
        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
}
  1. JWT认证和授权过滤器(JwtTokenUtil.java):



public class JwtTokenUtil {
 
    // ... JWT工具类的实现 ...
 
    public String generateToken(UserDetails userDetails) {
        return Jwts.builder()
                .setSubject(userDetails.getUsername())
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                .compact();
    }
 
    public boolean va
2024-09-09



import redis
 
# 假设Redis服务器的地址和端口是localhost和6379
redis_host = 'localhost'
redis_port = 6379
 
# 连接Redis
r = redis.StrictRedis(host=redis_host, port=redis_port, decode_responses=True)
 
# 获取所有key的超时配置
keys_with_timeout = r.keys("*")
 
# 定义一个函数来检查key的剩余超时时间
def check_key_timeout(key):
    ttl = r.ttl(key)
    if ttl == -2:
        print(f"Key {key} 存在于数据库,但没有设置超时时间。")
    elif ttl == -1:
        print(f"Key {key} 存在,但不会超时。")
    elif ttl == 0:
        print(f"Key {key} 已经超时,需要处理。")
    else:
        print(f"Key {key} 的剩余超时时间为 {ttl} 秒。")
 
# 对每个key应用超时检查
for key in keys_with_timeout:
    check_key_timeout(key)

这段代码使用了redis-py库来连接Redis服务器,并获取所有key的超时配置。然后定义了一个函数check_key_timeout来检查每个key的剩余超时时间,并根据不同的返回值给出相应的处理建议。最后,它遍历所有key并调用该函数来检查它们的状态。

2024-09-09

RedisJSON 是一个为了提高 Redis 处理 JSON 数据的能力,而开发的 Redis 模块。它允许用户直接在 Redis 中存储、更新和查询 JSON 文档,而不需要将 JSON 序列化为字符串或二进制格式。

以下是使用 RedisJSON 的一些基本命令:

  1. 设置 JSON 值:



JSON.SET user:1 $ '{"name":"John", "age":30}'
  1. 获取 JSON 值:



JSON.GET user:1
  1. 更新 JSON 值:



JSON.SET user:1 $ '{"name":"Jane", "age":25}'
JSON.NUMINCRBY user:1 $ '{"age":1}'
  1. 查询 JSON 路径:



JSON.GET user:1 .name
  1. 删除 JSON 值:



DEL user:1

RedisJSON 模块提供了一种高效的方式来处理和查询 Redis 中的 JSON 数据。它可以用于存储和管理复杂的数据结构,并提供了一些强大的查询功能,比如 JSONPath 查询。

注意:RedisJSON 是一个第三方模块,需要独立安装。安装方法可能会根据 Redis 版本和操作系统的不同而有所差异,请参照官方文档进行安装。

2024-09-09

Redis是一个开源的使用C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

以下是Redis的一些重要知识点:

  1. 数据类型:Redis支持字符串、列表、集合、有序集合、哈希表等数据类型。
  2. 持久化:Redis支持两种持久化方式,RDB(默认方式)和AOF。
  3. 内存管理:Redis使用了高效的内存管理机制,如在内存不足时,会使用虚拟内存(swap)。
  4. 性能优化:Redis包含多种性能优化策略,如内存优化、连接优化等。
  5. 事务:Redis的事务可以一次性按顺序执行多个命令,与传统事务不同,Redis事务不保证原子性。
  6. 发布/订阅:Redis提供发布/订阅功能,可以用于消息订阅和发布。
  7. 分布式锁:Redis可以作为分布式锁使用,解决多线程、多进程中的同步问题。
  8. 集群:Redis支持集群模式,可以通过Redis Sentinel和Redis Cluster实现高可用性。

示例代码(Python使用redis-py库):




import redis
 
# 连接Redis
r = redis.Redis(host='localhost', port=6379, db=0)
 
# 设置键值对
r.set('key', 'value')
 
# 获取键对应的值
value = r.get('key')
print(value)
 
# 列表操作
r.lpush('mylist', 'value1')
r.lpush('mylist', 'value2')
print(r.lrange('mylist', 0, -1))  # 获取列表所有元素
 
# 集合操作
r.sadd('myset', 'value1')
r.sadd('myset', 'value2')
print(r.smembers('myset'))  # 获取集合所有元素
 
# 哈希操作
r.hset('myhash', 'field1', 'value1')
print(r.hgetall('myhash'))  # 获取哈希所有字段和值
 
# 使用事务
pipeline = r.pipeline()
pipeline.set('key1', 'value1')
pipeline.set('key2', 'value2')
pipeline.execute()
 
# 发布订阅
pubsub = r.pubsub()
pubsub.subscribe('channel1')
pubsub.publish('channel1', 'message1')

以上代码展示了如何使用Python的redis-py库进行Redis的基本操作,包括字符串、列表、集合、哈希表的增删改查,以及事务处理和发布/订阅。

2024-09-09

在计算两点之间的距离时,首先需要确保你的数据是经度和纬度。然后,你可以使用Haversine公式来计算两点之间的大圆距离。

以下是使用Python和Redis的示例代码:




import redis
from math import radians, sin, cos, sqrt, atan2
 
# 连接到Redis
r = redis.Redis(host='localhost', port=6379, db=0)
 
# 两点经纬度
point1 = (45.759724, 21.229727)  # 格拉巴斯塔
point2 = (46.063789, 23.562254)  # 布拉格
 
# 将经纬度转换为弧度
lat1, lon1 = map(radians, point1)
lat2, lon2 = map(radians, point2)
 
# Haversine公式
dlon = lon2 - lon1
dlat = lat2 - lat1
a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
c = 2 * atan2(sqrt(a), sqrt(1-a))
r = 6371  # 地球平均半径,单位为公里
 
# 两点距离
distance = r * c
 
# 将结果存储到Redis
r.set('distance', distance)
 
# 从Redis获取结果
stored_distance = float(r.get('distance'))
 
print(f"The distance between the two points is approximately: {stored_distance} km")

在这个例子中,我们首先导入了必要的模块,连接到了Redis,定义了两个点的经纬度,并使用Haversine公式计算了它们之间的距离。然后,我们将距离存储到Redis,并从Redis获取了存储的距离,打印出来。

请注意,这个例子假设Redis服务器运行在本地主机的默认端口6379上,并且没有密码保护。如果你的Redis设置不同,你需要在redis.Redis()调用中提供适当的参数。

2024-09-09

在使用Redisson分布式锁解决库存超卖问题时,可以通过加锁保证库存的原子性操作。以下是一个简单的示例代码:




import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;
 
public class StockService {
 
    private RedissonClient redissonClient;
 
    public StockService(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }
 
    public void decreaseStock() {
        RLock lock = redissonClient.getLock("stockLock");
        try {
            // 尝试获取锁,最多等待100秒,超过时间则失败
            if (lock.tryLock(100, 100, TimeUnit.SECONDS)) {
                // 获取锁成功后执行库存减少操作
                // 这里应该是对数据库的库存字段进行减法操作
                // int stockCount = ...; // 假设这是从数据库中获取的库存数量
                // if (stockCount > 0) {
                //     // 减少库存
                //     // update database set stock_count = stock_count - 1 where ...
                // } else {
                //     // 库存不足
                // }
                
                // 这里是模拟减库存的逻辑,实际应用中需要替换为数据库操作
                System.out.println("库存减少成功!");
            } else {
                // 获取锁失败,库存减少操作被延迟执行或者不执行
                System.out.println("获取锁失败,库存减少操作被延迟执行或者不执行!");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            // 处理异常情况
        } finally {
            // 释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

在这个示例中,RedissonClient 是用于获取锁的Redisson客户端实例。decreaseStock 方法尝试获取名为"stockLock"的锁,并在获取锁成功后执行库存减少的逻辑。如果尝试获取锁失败,则会打印相应的日志信息,并且不会执行减少库存的操作。这样可以防止在高并发情况下发生超卖现象。

2024-09-09

在Redis中,可以使用不同的数据类型和命令来实现计数功能,以下是几种常见的方法:

  1. 使用字符串(string)类型的 INCR 命令:



> SET mycounter "0"
OK
> INCR mycounter
(integer) 1
> INCR mycounter
(integer) 2
  1. 使用哈希表(hash)来存储多个计数器:



> HSET mycounters user_id 0
(integer) 1
> HINCRBY mycounters user_id 1
(integer) 1
> HINCRBY mycounters another_id 1
(integer) 1
  1. 使用列表(list)来进行计数,但不推荐这种方式,因为列表并非设计为计数工具:



> LPUSH mylist 0
(integer) 1
> INCR mylist
(error) ERR value is not an integer or out of range
  1. 使用集合(set)来进行计数,同样不推荐,因为集合不保证元素顺序且不允许重复:



> SADD myset 0
(integer) 1
> INCR myset
(error) ERR value is not an integer or out of range
  1. 使用有序集合(sorted set)来进行计数,也不推荐,因为有序集合是为了排序而设计的:



> ZADD myzset 0 member
(integer) 1
> INCR myzset
(error) ERR value is not an integer or out of range

在实际应用中,通常使用字符串类型的 INCR 命令或者哈希表的 HINCRBY 命令来进行简单的计数。如果需要更复杂的计数逻辑(例如分组计数或者是分布式计数),可能需要结合多种数据类型和命令,或者编写复杂的Lua脚本来实现。

2024-09-09

布隆过滤器(Bloom Filter)是一种空间效率高的元素存在性检查工具,可以用于检查元素是否可能在集合中,或者元素是否一定不在集合中。在Redis中,我们可以使用布隆过滤器来检查大量数据是否存在。

在这个实战中,我们将使用Redis的布隆过滤器功能来检查8亿个数据集中的元素是否存在。

首先,我们需要安装并启动Redis服务。

然后,我们可以使用Python的redis-py-cluster库和pybloom_live库来操作Redis布隆过滤器。

以下是一个简单的Python脚本,用于在Redis布隆过滤器中插入和查询元素:




from rediscluster import RedisCluster
from pybloom_live import BloomFilter
 
# 连接到Redis集群
startup_nodes = [{"host": "127.0.0.1", "port": "7000"}]
rc = RedisCluster(startup_nodes=startup_nodes, decode_responses=True)
 
# 创建一个布隆过滤器实例
bf = BloomFilter(capacity=800000000, error_rate=0.001, filename='bf.bloom', initial_capacity=None, no_single_point_failure=True)
 
# 插入元素到布隆过滤器
bf.add('element1')
bf.add('element2')
 
# 查询元素是否可能在集合中
print('element1' in bf)  # 应该返回True
print('element3' in bf)  # 应该返回False,因为element3可能并不在集合中

在实际应用中,你可能需要将数据集分批插入布隆过滤器,并在查询时使用布隆过滤器的特性来减少无效查询。记得在插入数据之前初始化布隆过滤器,并选择合适的容量和错误率。