2024-09-05



import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
 
@Configuration
public class ReactiveRedisConfig {
 
    @Bean
    public ReactiveRedisTemplate<String, Object> reactiveRedisTemplate(ReactiveRedisConnectionFactory factory) {
        return new ReactiveRedisTemplate<>(factory, RedisSerializationContext.java().string().serializer(new GenericJackson2JsonRedisSerializer()));
    }
}

这段代码定义了一个配置类ReactiveRedisConfig,其中包含一个Bean方法reactiveRedisTemplate,用于创建一个ReactiveRedisTemplate实例,该实例使用字符串作为键的序列化方式,并且使用GenericJackson2JsonRedisSerializer作为值的序列化器。这样配置后,你可以直接在Spring Data Reactive Redis中使用这个模板来操作Redis,无需手动序列化和反序列化对象。

2024-09-05

解释:

  1. 缓存穿透:查询不存在的数据,缓存不命中,请求直接打到数据库,可能导致数据库压力剧增。
  2. 缓存雪崩:缓存失效时间集中,导致大量请求打到数据库。
  3. 淘汰策略:当内存不足时,根据一定策略移除缓存数据。

解决方案:

  1. 缓存穿透:使用布隆过滤器,维护一个全局集合,存储所有可能的查询key。访问前先经过布隆过滤器,如果不存在则直接拒绝请求。
  2. 缓存雪崩:

    • 设置缓存数据的过期时间随机化,避免大量数据同时失效。
    • 实现热点数据永不过期或使用二级缓存。
  3. 淘汰策略:

    • 使用LRU(最近最少使用)、LFU(最常使用)、FIFO(先进先出)等策略。
    • 当内存不足时,通过设置内存淘汰机制来保证业务的正常运行。

示例代码(假设使用Redis作为缓存):




# 缓存key的存在性检查
def is_key_exist(key):
    # 假设有一个布隆过滤器实例
    return bloom_filter.is_exist(key)
 
# 设置缓存,并设置随机过期时间
def set_cache(key, value, ttl):
    random_ttl = random.randint(ttl//2, ttl)
    redis.setex(key, random_ttl, value)
 
# 淘汰策略
redis.config_set("maxmemory-policy", "allkeys-lru") # 设置LRU淘汰策略

注意:具体实施时需要根据业务场景和需求进行调整。

2024-09-04

在使用Sa-Token进行短信登录、注册以及鉴权时,可以结合Redis来存储短信验证码以及用户会话信息,以下是一个简化的示例代码:

首先,需要添加Sa-Token和Redis的依赖:




<!-- Sa-Token 依赖 -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>最新版本</version>
</dependency>
<!-- Redis 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

然后配置Sa-Token与Redis的集成:




@Configuration
public class SaTokenConfig {
    @Bean
    public SaTokenAction saTokenAction() {
        return new SaTokenActionRedis(); // 使用Redis作为存储方式
    }
}

接下来,实现短信登录和注册的接口:




@RestController
public class MessageController {
 
    // 发送短信验证码接口
    @PostMapping("/send-message")
    public Response sendMessage(@RequestParam("phone") String phone) {
        // 生成验证码并保存至Redis
        String code = RandomUtil.randomNumbers(6);
        SaTokenRedisUtil.set(SaTokenRedisKeyBuild.smsCodeKey(phone), code, 1000 * 60 * 5); // 有效期5分钟
 
        // 模拟发送短信操作
        // sendSms(phone, code);
        return Response.success("短信发送成功");
    }
 
    // 短信登录接口
    @PostMapping("/sms-login")
    public Response smsLogin(@RequestParam("phone") String phone, @RequestParam("code") String code) {
        // 从Redis获取验证码并校验
        String cacheCode = SaTokenRedisUtil.get(SaTokenRedisKeyBuild.smsCodeKey(phone));
        if (!code.equals(cacheCode)) {
            return Response.error("验证码错误");
        }
 
        // 登录成功,创建会话
        StpUtil.login(phone); // 更多StpUtil方法参考Sa-Token官方文档
        return Response.success("登录成功");
    }
 
    // 短信注册接口
    @PostMapping("/sms-register")
    public Response smsRegister(@RequestParam("phone") String phone, @RequestParam("code") String code) {
        // 验证码校验同登录接口
        // ...
 
        // 注册用户
        int userId = StpUtil.getLoginIdAsInt(); // 假设用户ID是自增的,实际应用中需要根据业务逻辑生成
        // 保存用户信息至数据库等操作
        // saveUser(userId, phone);
 
        return Response.success("注册成功");
    }
}

以上代码提供了短信验证码的发送、登录和注册接口,并使用Redis来存储验证码和会话信息。在实际应用中,你需要根据自己的业务逻辑来扩展这些接口,比如集成短信服务提供商的API,处理用户信息的存储等。

2024-09-04



/* 示例代码:Redis过期键删除策略和内存淘汰策略 */
#include "redis.h"
 
void activeExpireCycle(redisDb *db, unsigned int max_loops) {
    // 迭代数据库,处理过期键
    dictEntry *de;
    int timelimit_exit = 0;
    long long start_cycle, now;
    unsigned int i, expired, num, visited;
 
    start_cycle = mstime();
    now = start_cycle;
    expired = 0;
    visited = 0;
    if (max_loops <= 0) max_loops = 1000000;
 
    for (i = 0; i < db->dict->size && max_loops--; i++) {
        // 遍历数据库中的所有键
        de = dictGetRandomKey(db->dict);
        if (de == NULL) break; /* 如果已经遍历完所有键,则退出 */
 
        // 检查键是否过期
        if (activeExpireCycleTryExpire(db,de,now)) expired++;
        if (i == 0 && mstime()-start_cycle > 1000) timelimit_exit = 1;
        visited++;
    }
 
    /* 如果达到时间限制,则下次继续从此处开始 */
    if (timelimit_exit) {
        db->active_expire_cycle = dictGetKey(de);
        return;
    }
    /* 如果没有可过期的键,则清除active_expire_cycle标志 */
    if (!expired) db->active_expire_cycle = NULL;
 
    /* 计算平均迭代次数 */
    if (visited) server.stat_expired_stale_perc = (float)expired/visited;
}
 
/* 尝试过期键 */
int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
    int retval;
    robj *key, *val;
 
    // 获取键值对
    key = dictGetKey(de);
    val = dictGetVal(de);
 
    // 检查键是否过期
    retval = 0;
    if (val->lru <= LRU_CLOCK()) {
        retval = expireIfNeeded(db,key,val);
    }
    return retval;
}
 
/* 主动过期键 */
int expireIfNeeded(redisDb *db, robj *key, robj *val) {
    mstime_t when = val->lru - LRU_CLOCK();
 
    // 如果键已经过期,执行过期删除
    if (when <= 0) {
        propagateExpire(db,key,val);
        return dbDelete(db,key);
    }
    return 0;
}
 
/* 传播过期 */
void propagateExpire(redisDb *db, robj *key, robj *val) {
    // 发布过期事件
    redisAssertWithInfo(NULL,key,db->id == 0 && val->type == REDIS_STRING);
    redisExpire(NULL,db,key->ptr);
}
 
/* 内存淘汰策略 */
int activeExpireCycleTryAOFAndRefresh(redisDb *db, dictEntry *de, long long now, int *expired, int *freq) {
    robj *key, *val;
 
    // 获取键值对
    key = dictGetKey(de);
    val = dictGetVal(de);
 
    // 尝试根据键值对执行AOF日志同步
    if (val->type == REDIS_STRING && val->refcount == 1 &&
        /* 假设条件 */
        (now - val->lru) > 120 * REDIS_AUTOSAVE_INTERVAL)
    {
        // 执行AOF日志同步
        sds aof_
2024-09-04



-- 假设我们有一个名为"SlowQueryLog"的表,用于记录慢查询日志
-- 以下SQL语句用于统计每个客户端IP的慢查询数量,并按慢查询数量降序排序
 
SELECT
  ClientIP,
  COUNT(*) AS NumSlowQueries
FROM
  SlowQueryLog
GROUP BY
  ClientIP
ORDER BY
  NumSlowQueries DESC;

这段SQL语句首先从SlowQueryLog表中选择客户端IP地址,然后计算每个IP的慢查询数量,最后按慢查询数量进行降序排序。这样可以帮助我们识别哪些客户端的查询对性能造成了负面影响,并且可以通过优化这些查询来提高服务器性能。

2024-09-04

Redis支持的数据类型包括字符串、列表、集合、有序集合、哈希表等,以及特殊的位图和流数据类型。

  1. 字符串(STRING)



# 设置值
redis.set('key', 'value')
 
# 获取值
redis.get('key')
 
# 增加数字
redis.incr('counter')
 
# 减少数字
redis.decr('counter')
  1. 列表(LIST)



# 添加元素
redis.rpush('list', 'element')
 
# 获取元素
redis.lrange('list', 0, -1)
  1. 集合(SET)



# 添加元素
redis.sadd('set', 'element')
 
# 获取元素
redis.smembers('set')
  1. 有序集合(SORTED SET)



# 添加元素
redis.zadd('sortedset', {'element': score})
 
# 获取元素
redis.zrange('sortedset', 0, -1)
  1. 哈希表(HASH)



# 设置值
redis.hset('hash', 'key', 'value')
 
# 获取值
redis.hgetall('hash')
  1. 位图(BITMAP)



# 设置位
redis.setbit('bitmap', offset, 1)
 
# 获取位
redis.getbit('bitmap', offset)
  1. 流(STREAM)



# 发布消息
redis.xadd('stream', {'key': 'value'})
 
# 读取消息
redis.xrange('stream', '-', '-', count=10)

Redis的发布订阅功能允许客户端订阅频道,接收消息。




# 订阅频道
redis.subscribe('channel')
 
# 发布消息
redis.publish('channel', 'message')

Redis的消息队列模式可以使用列表实现先进先出队列,集合实现无序队列,有序集合实现有序队列。




# RPUSH实现先进先出队列
redis.rpush('queue', 'element')
redis.lpop('queue')
 
# SADD实现无序队列
redis.sadd('queue', 'element')
redis.smembers('queue')
 
# ZADD实现有序队列
redis.zadd('queue', {'element': score})
redis.zrange('queue', 0, -1)

布隆过滤器(Bloom Filter)可以用于检查元素是否可能在集合中,适用于大数据处理,因其高效和节省空间而受欢迎。




# 添加元素
redis.bf.add('bloomfilter', 'element')
 
# 检查元素
redis.bf.exists('bloomfilter', 'element')

以上代码示例展示了Redis的基本数据类型操作和发布订阅、消息队列的使用方法,以及布隆过滤器的添加和检查操作。

2024-09-04



import redis.clients.jedis.JedisSentinelPool;
 
public class RedisSentinelExample {
    public static void main(String[] args) {
        String masterName = "mymaster"; // Redis主服务器名称
        Set<String> sentinels = new HashSet<String>(); // 哨兵服务器集合
        sentinels.add("sentinel1-host:26379");
        sentinels.add("sentinel2-host:26379");
        sentinels.add("sentinel3-host:26379");
 
        JedisSentinelPool sentinelPool = new JedisSentinelPool(masterName, sentinels);
 
        try (Jedis jedis = sentinelPool.getResource()) {
            // 使用jedis连接到Redis哨兵,进行操作
            jedis.set("key", "value");
            System.out.println(jedis.get("key"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这段代码使用了Jedis客户端库中的JedisSentinelPool来创建一个Redis哨兵连接池。通过哨兵池获取的Jedis实例可以用来执行Redis命令,无需手动维护主节点的IP和端口。当主节点发生变更时,哨兵模式会自动将从节点晋升为主节点,并通知客户端新的主节点地址。

2024-09-04

在这个例子中,我们将使用Redis和JWT来实现一账N地登录。

首先,我们需要安装Redis和相关的Go包:




go get -u github.com/go-redis/redis/v8
go get -u github.com/golang-jwt/jwt/v4

以下是核心函数的实现:




package main
 
import (
    "context"
    "crypto/rand"
    "crypto/rsa"
    "encoding/json"
    "fmt"
    "github.com/go-redis/redis/v8"
    "github.com/golang-jwt/jwt/v4"
    "log"
    "time"
)
 
var (
    redisClient *redis.Client
    privateKey  *rsa.PrivateKey
    publicKey   rsa.PublicKey
)
 
type UserInfo struct {
    Username string `json:"username"`
    Password string `json:"password"`
}
 
type Payload struct {
    jwt.Payload
    UserInfo
}
 
func init() {
    redisClient = redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
    })
 
    // 生成私钥和公钥
    var err error
    privateKey, err = rsa.GenerateKey(rand.Reader, 2048)
    if err != nil {
        log.Fatalf("failed to generate private key: %v", err)
    }
    publicKey = privateKey.PublicKey
}
 
func createToken(userInfo UserInfo) (string, error) {
    payload := Payload{
        Payload: jwt.NewWithConfig(jwt.GetSigningMethod("RS256"), jwt.StandardClaims{
            Issuer:    "example",
            Subject:   userInfo.Username,
            IssuedAt:  time.Now(),
            ExpiresAt: time.Now().Add(time.Hour * 24), // 设置token有效期1天
        }),
        UserInfo: userInfo,
    }
 
    token, err := jwt.Sign(&payload, jwt.NewRS256(privateKey))
    if err != nil {
        return "", err
    }
    return token, nil
}
 
func login(username, password string) (string, error) {
    ctx := context.Background()
    userKey := fmt.Sprintf("user:%s", username)
    userInfoBytes, err := redisClient.Get(ctx, userKey).Bytes()
    if err != nil {
        return "", err
    }
 
    var userInfo UserInfo
    if err := json.Unmarshal(userInfoBytes, &userInfo); err != nil {
        return "", err
    }
 
    if userInfo.Password != password {
        return "", fmt.Errorf("invalid password")
    }
 
    token, err := createToken(userInfo)
    if err != nil {
        return "", err
    }
 
    return token, nil
}
 
func main() {
    // 假设用户已经在其他地方登录,并且我们已经有了他们的凭证
    token, err := login("user1", "password123")
    if err != nil {
        log.Fatalf("login failed: %v", err)
    }
    fmt.Printf("Token: %s\n", token)
}

在这个例子中,我们首先初始化了Redis客户端,并生成了一对私钥和公钥,用于签发JWT。然后我们实现了createToken函数,它使用私钥来签发JWT。login函数会从Redis中获取用户信息,验证密码,如果验证通过,则为用户签发新的Tok

2024-09-04

以下是搭建所述环境的基本步骤,请根据自己的需求和操作系统的具体情况进行调整:

  1. 更新系统包列表和软件包:



sudo apt update
sudo apt upgrade
  1. 安装Java 8:



sudo apt install openjdk-8-jdk
  1. 安装Nginx:



sudo apt install nginx
  1. 安装Redis:



sudo apt install redis-server
  1. 安装MySQL 8:



sudo apt install mysql-server

在安装MySQL的过程中,会提示设置root用户的密码,请按提示操作。

确保所有服务启动并运行:




sudo systemctl start nginx
sudo systemctl enable nginx
sudo systemctl start redis-server
sudo systemctl enable redis-server
sudo systemctl start mysql
sudo systemctl enable mysql

配置Nginx和Java应用的反向代理,编辑Nginx配置文件:




sudo nano /etc/nginx/sites-available/default

在该文件中添加以下配置:




server {
    listen 80;
 
    location / {
        proxy_pass http://localhost:8080; # 假设Java应用运行在8080端口
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

保存并关闭文件,然后重启Nginx:




sudo systemctl restart nginx

这样,你就搭建了一个基本的Java 8, Nginx, Redis, MySQL 8环境。根据具体的Java应用,可能还需要进行额外的配置和调整。

2024-09-04

MySQL和Redis都是常用的数据库管理系统,但是它们有不同的应用场景和优点。

MySQL是关系型数据库,适合存储大量的结构化数据,具有强大的事务处理能力和数据一致性。

Redis是内存中的数据结构存储系统,提供了诸如字符串、哈希表、列表、集合等数据结构的存储,并且支持数据的持久化。Redis的操作是原子的,并支持发布/订阅机制、Lua脚本等功能。

两者结合运用可以提高应用的性能和可扩展性。例如,可以使用Redis作为MySQL的前端缓存,减少对MySQL的访问压力,提高应用的响应速度。

以下是一个简单的Python示例,展示了如何结合使用MySQL和Redis:




import redis
import pymysql
 
# 连接Redis
redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
 
# 连接MySQL
mysql_conn = pymysql.connect(host='localhost', user='your_user', password='your_password', db='your_db')
mysql_cursor = mysql_conn.cursor()
 
# 要查询的数据的唯一标识
key = 'some_unique_key'
 
# 尝试从Redis缓存中获取数据
data = redis_client.get(key)
 
if data is None:
    # Redis缓存未命中,查询MySQL数据库
    mysql_cursor.execute("SELECT data FROM your_table WHERE id = %s", (key,))
    data = mysql_cursor.fetchone()
    if data:
        # 将查询结果存储到Redis,设置过期时间为1小时
        redis_client.setex(key, 3600, data[0])
else:
    # 从Redis缓存中获取数据
    data = data.decode('utf-8')
 
# 后续处理data...
 
# 关闭MySQL和Redis连接
mysql_cursor.close()
mysql_conn.close()

在这个例子中,我们首先尝试从Redis缓存中获取数据。如果缓存未命中,我们会查询MySQL数据库。查询到的数据会存储回Redis,并设置一个过期时间,以便管理缓存。这样可以减少对MySQL的频繁访问,提高应用的响应速度。