2024-09-04

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

Redis的数据类型有:String、Hash、List、Set、Sorted Set、bitmaps和HyperLogLog。

  1. String:字符串是最基本的 key-value 结构。



# 设置键值
redis.set('key', 'value')
# 获取键值
redis.get('key')
  1. Hash:在 Redis 中,Hash 是一个键值对(key-value)的无序映射。



# 设置键值对
redis.hset('hash_key', 'field1', 'value1')
# 获取键的所有字段
redis.hgetall('hash_key')
  1. List:列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。



# 在列表左侧插入元素
redis.lpush('list_key', 'value1')
# 在列表右侧插入元素
redis.rpush('list_key', 'value2')
# 获取列表所有元素
redis.lrange('list_key', 0, -1)
  1. Set:集合是无序的字符串集合。



# 添加一个或多个成员到集合
redis.sadd('set_key', 'value1')
# 返回集合中的所有成员
redis.smembers('set_key')
  1. Sorted Set:有序集合是字符串的集合,且不能重复。每个元素都关联着一个浮点数的分数。



# 添加成员及其分数到有序集
redis.zadd('zset_key', {'value1': 1, 'value2': 2})
# 通过索引范围返回有序集中的成员
redis.zrange('zset_key', 0, -1)
  1. bitmaps:Redis 从 2.2.0 版本开始支持对 String 类型进行 bit 级别的操作。



# 对 key 所储存的字符串值,设置或清除指定位上的位
redis.setbit('bitmap_key', 1, 1)
# 对 key 所储存的字符串值,获取指定位上的位值
redis.getbit('bitmap_key', 1)
  1. HyperLogLog:Redis HyperLogLog 是用来做基数统计的算法,适用于注册用户的 UV 统计。



# 添加一个或多个元素到 HyperLogLog 中
redis.pfadd('hll_key', 'value1')
# 返回给定 HyperLogLog 的基数估算值
redis.pfcount('hll_key')

以上就是 Redis 的常用数据类型和对应的 Python 代码示例。

2024-09-04

以下是在Linux环境中安装和配置JDK、Git、Maven、MySQL和Redis的简要步骤和示例代码:

  1. JDK安装



# 使用apt-get安装OpenJDK
sudo apt-get update
sudo apt-get install openjdk-11-jdk -y
 
# 验证安装
java -version
  1. Git安装



# 使用apt-get安装Git
sudo apt-get update
sudo apt-get install git -y
 
# 验证安装
git --version
  1. Maven安装



# 下载Maven
wget https://apache.osuosl.org/maven/maven-3/3.8.4/binaries/apache-maven-3.8.4-bin.tar.gz
 
# 解压Maven
sudo tar -xvzf apache-maven-3.8.4-bin.tar.gz -C /opt
 
# 设置环境变量
echo "export M2_HOME=/opt/apache-maven-3.8.4" | sudo tee -a /etc/profile
echo "export PATH=\$PATH:\$M2_HOME/bin" | sudo tee -a /etc/profile
 
# 应用环境变量
source /etc/profile
 
# 验证安装
mvn -version
  1. MySQL安装



# 下载MySQL APT Repository
wget https://repo.mysql.com//mysql-apt-config_0.8.16-1_all.deb
 
# 安装MySQL APT Repository
sudo dpkg -i mysql-apt-config_0.8.16-1_all.deb
 
# 在安装过程中选择MySQL版本和默认的编码
 
# 更新包列表
sudo apt-get update
 
# 安装MySQL
sudo apt-get install mysql-server -y
 
# 启动MySQL服务
sudo systemctl start mysql.service
 
# 验证安装
mysql -u root -p
  1. Redis安装



# 使用apt-get安装Redis
sudo apt-get update
sudo apt-get install redis-server -y
 
# 启动Redis服务
sudo systemctl start redis-server
 
# 验证安装
redis-cli ping

请根据你的Linux发行版(如Ubuntu、CentOS等)和版本,适当调整上述命令。对于MySQL,可能需要在安装期间设置root密码和确认服务器的配置选项。对于Redis,默认配置通常就可以了。

2024-09-04

ThreadLocal的主要作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,线程结束后,变量也随之销毁。ThreadLocal为解决多线程程序的数据共享问题提供了一种新的思路。

ThreadLocal的主要方法有:

  1. public T get() :返回此线程局部变量的当前线程副本中的值。
  2. public void set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。
  3. public void remove():移除此线程局部变量的当前线程副本中的值。
  4. protected T initialValue():返回此线程局部变量的当前线程副本的初始值。这个方法是一个protected的方法,显然是为了让子类覆写而设计的。

ThreadLocal的实现原理:每个Thread内部都维护了一个ThreadLocalMap,这个Map的key是ThreadLocal实例本身,value是我们希望在线程内部共享的数据。

ThreadLocal的内存泄漏问题:由于ThreadLocal的生命周期跟Thread一样长,如果ThreadLocal没有正确的remove,那么如果Thread的生命周期非常长,比如服务器的一个工作线程,那么就会导致内存泄漏。

解决方案:

  1. 每次使用完ThreadLocal后,都调用它的remove()方法,这样最保险的避免了内存泄漏的问题。
  2. 如果ThreadLocal存储的是一个对象,那么这个对象应该实现Closeable接口,在try-with-resources语句块中使用,这样可以保证即使发生异常也能正确关闭资源。

解决方案:

在分布式系统中,我们通常需要在多个服务间共享登录状态,这时可以使用Redis来替代ThreadLocal。

  1. 用户登录后,将用户的session信息保存到Redis中,并将session的key存储在用户的cookie中。
  2. 当用户访问其他服务时,从请求中取出session的key,然后到Redis中查询session信息。

代码示例:




// 用户登录
public String login(String username, String password) {
    // 验证用户名密码
    // ...
    // 生成session信息
    String sessionId = UUID.randomUUID().toString();
    // 将session信息保存到Redis中
    redisTemplate.opsForValue().set(sessionId, new UserInfo(username), 3600, TimeUnit.SECONDS);
    // 返回sessionId
    return sessionId;
}
 
// 获取用户信息
public UserInfo getUserInfo(String sessionId) {
    return (UserInfo) redisTemplate.opsForValue().get(sessionId);
}

在这个例子中,我们使用了Redis来存储用户的session信息,而不是使用ThreadLocal。这样,无论用户访问哪个服务,只要服务能访问到Redis,就能获取到正确的用户信息,实现了共享session登录。

2024-09-04

问题:Redis面试常问问题

解决方案:

  1. 为什么使用Redis?

    Redis是一个开源的使用C语言编写的、支持网络交互的、可基于内存亦可持久化的Key-Value数据库。

    常见的使用Redis的情景包括:缓存系统、排行榜、消息队列系统、分布式会话存储、分布式锁等。

  2. Redis的数据类型?

    Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set有序集合)。

  3. Redis的持久化方式?

    Redis提供了两种持久化方式:RDB(默认方式)和AOF。

    RDB:在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是Snapshotting。

    AOF:每执行一个改变数据库状态的命令,就将该命令写入磁盘。

  4. Redis的过期策略和内存淘汰机制?

    过期策略:定期检查和惰性检查。

    内存淘汰机制:noeviction(不进行淘汰),allkeys-lru(当内存不足以容纳更多数据时,在键空间中,移除最近最少使用的键),allkeys-random(在键空间中,随机移除键),volatile-lru(在设置了过期时间的键空间中,移除最近最少使用的键),volatile-random(在设置了过期时间的键空间中,随机移除键),volatile-ttl(在设置了过期时间的键空间中,移除即将过期的键)。

  5. Redis的事务?

    事务可以一次执行多个命令,本身是序列化的,但不支持回滚。

  6. Redis的分布式锁?

    使用Redis的SETNX命令实现分布式锁,可以有效地防止分布式环境下的竞争条件。

  7. Redis的Lua脚本?

    Redis可以执行Lua脚本,可以用来解决原子操作的问题。

  8. Redis的性能问题?

    使用Redis时,应注意内存的使用,避免内存溢出,并考虑使用pipeline减少网络延迟。

  9. Redis的集群方式?

    Redis提供了集群的支持,可以通过Redis Sentinel来进行自动的故障转移,以及通过Redis Cluster来实现数据的分布式存储。

  10. Redis的性能测试?

    使用Redis-benchmark工具进行性能测试,可以评估Redis在特定条件下的性能表现。

  11. Redis的配置文件?

    Redis的配置文件是redis.conf,可以在启动Redis服务时指定。

  12. Redis的主从同步?

    主从同步可以确保Redis的数据备份,主机会将数据同步到从机。

  13. Redis的监控工具?

    可以使用Redis自带的monitor命令,或者第三方工具Redis-live等。

  14. Redis的优化方法?

    优化方法包括:使用合适的数据类型,设置适当的键的过期时间,合理使用Redis的内存淘汰策略,使用pipeline,优化数据结构,使用Lua脚本等。

  15. Redis的雪崩问题?

    雪崩是指

2024-09-04

Redis 的 Set 是一种数据类型,它以无序的方式存储多个字符串元素,并保证每个元素都是唯一的。Redis 的 Set 是不允许有重复元素的。

以下是一些使用 Redis Set 的常见操作:

  1. 添加元素到 Set



import redis
 
r = redis.Redis(host='localhost', port=6379, db=0)
r.sadd('myset', 'hello')
r.sadd('myset', 'world')
r.sadd('myset', 'world')  # 再次添加 'world',由于 Set 中已经存在,所以不会添加
  1. 获取 Set 中的所有元素



print(r.smembers('myset'))  # 输出:{b'world', b'hello'}
  1. 检查元素是否在 Set 中



print(r.sismember('myset', 'hello'))  # 输出:True
print(r.sismember('myset', 'foo'))  # 输出:False
  1. 删除 Set 中的元素



r.srem('myset', 'hello')
  1. 获取 Set 的长度



print(r.scard('myset'))  # 输出:1
  1. 求两个 Set 的交集



r.sadd('myset2', 'hello')
print(r.sinter('myset', 'myset2'))  # 输出:{b'hello'}
  1. 求两个 Set 的并集



print(r.sunion('myset', 'myset2'))  # 输出:{b'world', b'hello'}
  1. 求两个 Set 的差集



print(r.sdiff('myset', 'myset2'))  # 输出:{b'world'}
  1. 随机从 Set 中取出元素



print(r.spop('myset'))  # 输出:b'world'
  1. 使用 Set 进行交集、并集、差集的操作并存储结果到一个新的 Set 中



r.sadd('myset2', 'foo')
r.sadd('myset2', 'bar')
 
# 求交集并存储到 newset
r.sinterstore('newset', 'myset', 'myset2')
print(r.smembers('newset'))  # 输出:{b'hello'}
 
# 求并集并存储到 newset
r.sunionstore('newset', 'myset', 'myset2')
print(r.smembers('newset'))  # 输出:{b'world', b'hello', b'foo', b'bar'}
 
# 求差集并存储到 newset
r.sdiffstore('newset', 'myset', 'myset2')
print(r.smembers('newset'))  # 输出:{b'world'}

以上就是 Redis Set 的一些基本操作,在 Python 中使用 Redis 库进行操作。

2024-09-04



import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.CanalEntry;
import redis.clients.jedis.Jedis;
 
public class CanalToRedis {
 
    public static void main(String args[]) {
        // 创建连接
        CanalConnector connector = CanalConnectors.newSingleConnector(
                new InetSocketAddress(AddressUtils.getHostIp(),
                11111), "example", "", "");
 
        // 启动连接
        connector.connect();
        connector.subscribe(".*\\..*");
        Jedis jedis = new Jedis("localhost");
 
        while (true) {
            // 获取数据
            Message message = connector.getWithoutAck(1024); // 获取指定数量的数据
            long batchId = message.getId();
            if (batchId == -1 || message.getEntries().isEmpty()) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            } else {
                dataHandle(message, jedis);
                connector.ack(batchId); // 确认消息消费成功
            }
        }
    }
 
    private static void dataHandle(Message message, Jedis jedis) {
        for (CanalEntry.Entry entry : message.getEntries()) {
            if (entry.getEntryType() == CanalEntry.EntryType.ROWDATA) {
                CanalEntry.RowChange rowChage = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
                for (CanalEntry.RowData rowData : rowChage.getRowDatasList()) {
                    if (rowData.getAction() == CanalEntry.EventType.INSERT) {
                        // 插入数据逻辑
                        insertData(jedis, entry, rowData);
                    } else if (rowData.getAction() == CanalEntry.EventType.UPDATE) {
                        // 更新数据逻辑
                        updateData(jedis, entry, rowData);
                    } else if (rowData.getAction() == CanalEntry.EventType.DELETE) {
                        // 删除数据逻辑
                        deleteData(jedis, entry, rowData);
                    }
                }
            }
        }
2024-09-04

Redis复制功能是Redis提供的一种方式,用于创建数据的副本。Redis复制可以有多个副本,但只有一个主副本。主副本可以进行读写操作,而从副本只能进行读操作。

在Redis中,复制可以手动设置,也可以通过配置文件设置。

解决方案1:手动设置复制




# 在主服务器上,运行命令 SLAVEOF 来指定从服务器的地址和端口
> SLAVEOF 127.0.0.1 6379
 
# 在从服务器上,运行命令 SLAVEOF 来指定主服务器的地址和端口
> SLAVEOF 127.0.0.1 6379

解决方案2:配置文件设置复制

在Redis的配置文件中,可以通过以下设置来指定主服务器:




# 在从服务器的配置文件中,添加以下行
slaveof 127.0.0.1 6379

然后,重新启动Redis服务器来应用这些更改。

注意:在生产环境中,应该使用更复杂的配置,例如哨兵模式,来管理复制和故障转移。

2024-09-04

在Spring Boot中整合Redis实现限流,可以使用spring-boot-starter-data-redis依赖,并结合RedisRateLimiter来实现。以下是一个简单的示例:

  1. 添加依赖到pom.xml



<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
  1. 配置Redis:



@Configuration
public class RedisConfig {
 
    @Bean
    public ReactiveRedisTemplate<String, String> reactiveRedisTemplate(ReactiveRedisConnectionFactory factory) {
        return new ReactiveRedisTemplate<>(factory, RedisSerializationContext.string());
    }
}
  1. 使用RedisRateLimiter实现限流:



@Configuration
public class RateLimiterConfig {
 
    @Bean
    public KeyResolver keyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getPath().value());
    }
 
    @Bean
    public ReactorRedisRateLimiter redisRateLimiter(ReactiveRedisTemplate<String, String> redisTemplate) {
        return new ReactorRedisRateLimiter(redisTemplate, "rate_limiter_", 1, 2);
    }
}
  1. 在路由中应用限流:



@Configuration
public class RoutesConfiguration {
 
    @Bean
    public SecurityWebFilterChain springSecurityWebFilterChain(ServerHttpSecurity http) {
        http
            .authorizeExchange()
            .pathMatchers("/limited").routeTo(route -> route.gatewayFilter(gatewayFilter).build())
            .and()
            // ... 其他配置
            ;
        return http.build();
    }
}

在上述配置中,ReactorRedisRateLimiter使用Redis作为存储,并对路径/limited的请求实施了每秒2个请求的限流。

确保你的Redis服务器正在运行并且配置信息(如主机名、端口和密码)在application.propertiesapplication.yml中正确设置。

2024-09-04

Redis 底层数据结构主要包括:

  1. 字符串(String)
  2. 字典(Hash)
  3. 链表(LinkedList)
  4. 跳跃表(SkipList)
  5. 哈希表(HashTable)
  6. 快速列表(QuickList)
  7. 整数集合(IntSet)
  8. 压缩列表(ZipList)

这些数据结构是 Redis 高效存储数据的基础。Redis 会根据数据的类型和场景选择合适的底层数据结构。

例如,当你使用 Redis 存储用户信息时,用户信息以键值对的形式存储,Redis 会使用哈希表(hash table)来存储键值对。

以下是一个简单的 Redis 命令示例,它使用哈希表来存储和检索用户信息:




# 设置用户信息
HSET user:1000 name "John Doe"
HSET user:1000 email "johndoe@example.com"
HSET user:1000 age 30

# 获取用户信息
HGETALL user:1000

在上述例子中,"user:1000" 是键,而每个键都关联着一个哈希表,哈希表存储了用户的属性。Redis 会使用哈希表作为底层数据结构来存储这些键值对。

2024-09-04

Redisson提供了分布式锁的功能,可以在分布式系统中对共享资源进行同步控制。锁可以重试和续命,以下是这两个特性的基本原理和示例代码:

  1. 重试锁:当尝试获取锁失败时,Redisson可以自动重试获取锁,直到成功或超时。



// 创建锁对象
RLock lock = redisson.getLock("anyLock");
 
// 尝试获取锁,最多等待100秒,上锁后10秒自动释放
// 如果在等待期间锁被释放,那么当前线程将获得这个锁
boolean isLocked = lock.tryLock(100, 10, TimeUnit.SECONDS);
 
if (isLocked) {
    try {
        // 处理业务逻辑
    } finally {
        // 释放锁
        lock.unlock();
    }
}
  1. 锁续命:Redisson的锁可以设置锁的持有时间(默认是30秒),当锁超过一半的时间会自动续期。



// 创建锁对象
RLock lock = redisson.getLock("anyLock");
 
// 上锁,锁定10秒
lock.lock(10, TimeUnit.SECONDS);
 
try {
    // 处理业务逻辑
} finally {
    // 释放锁
    lock.unlock();
}

以上代码展示了如何使用Redisson的锁特性。锁对象RLock提供了获取锁、释放锁的方法,以及可重试的获取锁方法tryLock。锁可以在构造时指定持有时间和锁的自动续命行为。