后端杂七杂八系列篇二

后端杂七杂八系列篇二

  • [① Redis--消息队列](#① Redis--消息队列)
    • [① 发布-订阅模式](#① 发布-订阅模式)
    • [② 发布-订阅常见的模型](#② 发布-订阅常见的模型)
    • [③ 发布-订阅redis命令](#③ 发布-订阅redis命令)
    • [④ 发布-订阅模式redis实战](#④ 发布-订阅模式redis实战)
  • [② Redis+Lua](#② Redis+Lua)
    • [① 什么是Lua?](#① 什么是Lua?)
    • [② Lua常用的命令](#② Lua常用的命令)
      • [① eval命令](#① eval命令)
      • [② SCRIPT LOAD命令 和 EVALSHA命令](#② SCRIPT LOAD命令 和 EVALSHA命令)
      • [② SCRIPT EXISTS 和 SCRIPT FLUSH](#② SCRIPT EXISTS 和 SCRIPT FLUSH)
      • [③ SCRIPT KILL](#③ SCRIPT KILL)
    • [③ Lua 写法的Demo](#③ Lua 写法的Demo)
      • [Demo 1](#Demo 1)
      • [Demo 2](#Demo 2)
      • [Demo 3](#Demo 3)
  • [③ 常见的限流算法](#③ 常见的限流算法)
    • [① 固定窗口算法](#① 固定窗口算法)
    • [② 滑动窗口算法](#② 滑动窗口算法)
    • [③ 令牌桶限流算法(控制令牌生成速度,取的速度不控制)](#③ 令牌桶限流算法(控制令牌生成速度,取的速度不控制))
    • [④ 漏桶限流算法(控制水滴流出速度,不控制水滴产生速度)](#④ 漏桶限流算法(控制水滴流出速度,不控制水滴产生速度))
  • [④ Spring Retry](#④ Spring Retry)

① Redis--消息队列

① 发布-订阅模式

② 发布-订阅常见的模型

① 一个发布者多个订阅者模型

② 多个发布者一个订阅者模型

③ 多个发布者多个订阅者模型

③ 发布-订阅redis命令

④ 发布-订阅模式redis实战

一个发布者多个订阅者模型

java 复制代码
# 先订阅到一个频道
127.0.0.1:6379> SUBSCRIBE FM888
1) "subscribe"
2) "FM888"
3) (integer) 1
 
# 发布者发布消息
127.0.0.1:6379> PUBLISH FM888 test1
(integer) 1
127.0.0.1:6379> PUBLISH FM888 test2
(integer) 2 (代表订阅个数)
 
# 订阅者们接收消息
127.0.0.1:6379(subscribed mode)> SUBSCRIBE FM888
1) "subscribe"
2) "FM888"
3) (integer) 1
1) "message"
2) "FM888"
3) "test1"
1) "message"
2) "FM888"
3) "test2"

多个发布者个一个订阅者模型

java 复制代码
# 订阅者订阅频道
127.0.0.1:6379(subscribed mode)> SUBSCRIBE FM888
 
# 发布者1
127.0.0.1:6379> PUBLISH FM888 test2
(integer) 1
 
# 发布者2
127.0.0.1:6379> PUBLISH FM888 test3
(integer) 1
 
# 订阅者
127.0.0.1:6379(subscribed mode)> SUBSCRIBE FM888
1) "subscribe"
2) "FM888"
3) (integer) 1
1) "message"
2) "FM888"
3) "test2"
1) "message"
2) "FM888"
3) "test3"

多个发布者个多个订阅者模型

java 复制代码
# 订阅者们订阅频道
127.0.0.1:6379> PSUBSCRIBE *
 
# 发布者1
127.0.0.1:6379> PUBLISH FM3 test10
(integer) 1
 
# 发布者2
127.0.0.1:6379> PUBLISH FM888 test10
(integer) 1
 
# 发布者3
127.0.0.1:6379> PUBLISH FM888 test1
(integer) 1
 
# 发布者4
127.0.0.1:6379> PUBLISH FM12 test12
(integer) 1
 
# 订阅者们收到的消息
127.0.0.1:6379> PSUBSCRIBE *
1) "psubscribe"
2) "*"
3) (integer) 1
1) "pmessage"
2) "*"
3) "FM3"
4) "test10"
1) "pmessage"
2) "*"
3) "FM888"
4) "test10"
1) "pmessage"
2) "*"
3) "FM888"
4) "test1"
1) "pmessage"
2) "*"
3) "FM12"
4) "test12"

② Redis+Lua

① 什么是Lua?

Lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放。Redis 2.6 版本通过内嵌支持 Lua 环境。也就是说一般的运用,是不需要单独安装Lua。

使用 Lua 脚本最大的好处是 Redis 会将整个脚本作为一个整体执行,不会被其他请求打断,可以保持原子性且减少了网络开销。

② Lua常用的命令

常用的命令不多,就下面这几个

  1. EVAL
  2. EVALSHA
  3. SCRIPT LOAD
  4. SCRIPT EXISTS
  5. SCRIPT FLUSH
  6. SCRIPT KILL

① eval命令

eval script numkeys key [key ...] arg [arg ...]

script 参数: 是一段 Lua 脚本程序,它可以是一段程序也可以是一个文件。
numkeys参数: 指定后续参数有几个key,即:key [key ...]中key的个数。如没有key,则为0
key [key ...]: 从 EVAL 的第三个参数开始算起,表示在脚本中所用到的那些 Redis 键(key)。在Lua脚本中通过KEYS[1], KEYS[2]获取。
arg [arg ...]: 附加参数,通过ARGV[1],ARGV[2]获取

java 复制代码
// 例1:numkeys=1,keys数组只有1个元素key1,arg数组无元素
127.0.0.1:6379> EVAL "return KEYS[1]" 1 key1
"key1"
java 复制代码
// 例2:numkeys=0,keys数组无元素,arg数组元素中有1个元素value1
127.0.0.1:6379> EVAL "return ARGV[1]" 0 value1
"value1"
java 复制代码
// 例3:numkeys=2,keys数组有两个元素key1和key2,arg数组元素中有两个元素first和second 
127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second 
1) "key1"
2) "key2"
3) "first"
4) "second"

Lua 脚本中执行 Redis 命令

redis.call(command, key [key ...] argv [argv...])

command:Redis 中的命令,如 set、get 等。
key:操作 Redis 中的 key 值,相当于我们调用方法时的形参。
param:代表参数,相当于我们调用方法时的实参。

② SCRIPT LOAD命令 和 EVALSHA命令

SCRIPT LOAD命令格式:SCRIPT LOAD script
EVALSHA命令格式:EVALSHA sha1 numkeys key [key ...] arg [arg ...]

SCRIPT LOAD 将脚本 script 添加到Redis服务器的脚本缓存中,并不立即执行这个脚本,而是会立即对输入的脚本进行求值。并返回给定脚本的 SHA1 校验和。如果给定的脚本已经在缓存里面了,那么不执行任何操作。

在脚本被加入到缓存之后,在任何客户端通过EVALSHA命令,可以使用脚本的 SHA1 校验和来调用这个脚本。脚本可以在缓存中保留无限长的时间,直到执行SCRIPT FLUSH为止。

简单来说,就是执行SCRIPT LOAD命令会存到缓存,执行EVALSHA会校验缓存中是否有,有就执行缓存的。

java 复制代码
## SCRIPT LOAD加载脚本,并得到sha1值
127.0.0.1:6379> SCRIPT LOAD "redis.call('SET', KEYS[1], ARGV[1]);redis.call('EXPIRE', KEYS[1], ARGV[2]); return 1;"
"6aeea4b3e96171ef835a78178fceadf1a5dbe345"

## EVALSHA使用sha1值,并拼装和EVAL类似的numkeys和key数组、arg数组,调用脚本。
127.0.0.1:6379> EVALSHA 6aeea4b3e96171ef835a78178fceadf1a5dbe345 1 userAge 10 60
(integer) 1
127.0.0.1:6379> get userAge
"10"

② SCRIPT EXISTS 和 SCRIPT FLUSH

SCRIPT EXISTS sha1 [sha1 ...]

判断脚本是否在缓存中。
SCRIPT FLUSH sha

清除Redis服务端所有 Lua 脚本缓存

③ SCRIPT KILL

SCRIPT FLUSH

杀死当前正在运行的 Lua 脚本

③ Lua 写法的Demo

Demo 1

java 复制代码
// Redis_CompareAndSet.lua 文件
local key = KEYS[1]
local val = redis.call("GET", key);

if val == ARGV[1]
then
        redis.call('SET', KEYS[1], ARGV[2])
        return 1
else
        return 0
end
java 复制代码
// 执行命令
如:redis-cli -a 123456 --eval ./Redis_CompareAndSet.lua userName , zhangsan lisi 
java 复制代码
## Redis客户端执行
127.0.0.1:6379> set userName zhangsan 
OK
127.0.0.1:6379> get userName
"zhangsan"

Demo 2

例如:同一IP在10秒内最多访问三次

java 复制代码
// Redis_LimitIpVisit.lua
local visitNum = redis.call('incr', KEYS[1])

if visitNum == 1 then
        redis.call('expire', KEYS[1], ARGV[1])
end

if visitNum > tonumber(ARGV[2]) then
        return 0
end

return 1;
java 复制代码
## LimitIP:127.0.0.1为key, 10 3表示:同一IP在10秒内最多访问三次
## 前三次返回1,代表未被限制;第四、五次返回0,代表127.0.0.1这个ip已被拦截
[root@vm01 learn_lua]# redis-cli -a 123456 --eval Redis_LimitIpVisit.lua LimitIP:127.0.0.1 , 10 3
 (integer) 1
[root@vm01 learn_lua]# redis-cli -a 123456 --eval Redis_LimitIpVisit.lua LimitIP:127.0.0.1 , 10 3
 (integer) 1
[root@vm01 learn_lua]# redis-cli -a 123456 --eval Redis_LimitIpVisit.lua LimitIP:127.0.0.1 , 10 3
 (integer) 1
[root@vm01 learn_lua]# redis-cli -a 123456 --eval Redis_LimitIpVisit.lua LimitIP:127.0.0.1 , 10 3
 (integer) 0
[root@vm01 learn_lua]# redis-cli -a 123456 --eval Redis_LimitIpVisit.lua LimitIP:127.0.0.1 , 10 3
 (integer) 0

Demo 3

Springboot 继承 Redis 使用 Lua

java 复制代码
# Redis数据库地址 
spring.redis.host=127.0.0.1 
# Redis端口 
spring.redis.port=6379 
# Redis密码(如果没有密码不用填写) 
spring.redis.password= 
java 复制代码
// RedisCRUD.lua脚本

-- set 
if KEYS[1] and ARGV[1] then 
redis.call('SET', KEYS[1], ARGV[1]) 
return 1 
end 
-- get 
if KEYS[1] and not ARGV[1] then 
return redis.call('GET', KEYS[1]) 
end 
-- delete 
if KEYS[1] and not ARGV[1] then 
redis.call('DEL', KEYS[1]) 
return 1 
end 
-- exists 
if KEYS[1] and not ARGV[1] then 
    if redis.call('EXISTS', KEYS[1]) == 1 then 
    return true 
    else 
    return false 
    end 
end 
-- hset 
if KEYS[1] and ARGV[1] and ARGV[2] and ARGV[3] then 
redis.call('HSET', KEYS[1], ARGV[1], ARGV[2]) 
redis.call('EXPIRE', KEYS[1], ARGV[3]) 
return 1 
end 
-- hget 
if KEYS[1] and ARGV[1] and not ARGV[2] then 
return redis.call('HGET', KEYS[1], ARGV[1]) 
end 
-- hdelete 
if KEYS[1] and ARGV[1] and not ARGV[2] then 
redis.call('HDEL', KEYS[1], ARGV[1]) 
return 1 
end 
-- hexists 
if KEYS[1] and ARGV[1] and not ARGV[2] then 
    if redis.call('HEXISTS', KEYS[1], ARGV[1]) == 1 then 
    return true 
    else 
    return false 
    end 
end
java 复制代码
// 在RedisTemplate的Bean中添加

 @Bean 
 public RedisScript<Long> redisScript() {
     RedisScript<Long> redisScript = new DefaultRedisScript<>(); 
     redisScript.setLocation(new ClassPathResource("lua/RedisCRUD.lua"));
     redisScript.setResultType(Long.class); 
     return redisScript; 
 } 
java 复制代码
// 实现RedisService

@Service 
public class RedisServiceImpl implements RedisService { 
    @Autowired 
    private RedisTemplate<String, Object> redisTemplate; 
    @Autowired 
    private RedisScript<Long> redisScript;
    
    public void set(String key, Object value) { 
        redisTemplate.opsForValue().set(key, value); 
    } 
    public Object get(String key) { 
        return redisTemplate.opsForValue().get(key); 
    } 
    public void delete(String key) { 
        redisTemplate.delete(key); 
    } 
    public Boolean exists(String key) { 
        return redisTemplate.hasKey(key); 
    } 
    public Long hset(String key, String field, Object value) { 
        return redisTemplate.opsForHash().put(key, field, value); 
    } 
    public Object hget(String key, String field) { 
        return redisTemplate.opsForHash().get(key, field); 
    } 
    public void hdelete(String key, String... fields) { 
        redisTemplate.opsForHash().delete(key, fields); 
    } 
    public Boolean hexists(String key, String field) {
       return redisTemplate.opsForHash().hasKey(key, field); 
    } 
    public Long eval(String script, List<String> keys, List<Object> args) { 
        return redisTemplate.execute(RedisScript.of(script), keys, args.toArray()); 
    } 
    public Long eval(List<String> keys, List<Object> args) { 
        return redisTemplate.execute(redisScript, keys, args.toArray()); 
    } 
 } 
java 复制代码
//测试RedisService

@RunWith(SpringRunner.class) 
@SpringBootTest 
public class RedisServiceImplTest { 
    @Autowired 
    private RedisService redisService; 
    @Test 
    public void test() {
        //第一种方式:执行string的lua
        redisService.eval("redis.call('SET', KEYS[1], ARGV[1])",Collections.singletonList(hashKey), Collections.singletonList(hashValue));
        //第二种方式:执行lua脚本
        String key ="key";
        String value ="value";
        redisService.eval(Collections.singletonList(hashKey), Collections.singletonList(hashValue));
    }

③ 常见的限流算法

① 固定窗口算法

又称计数器算法:在指定周期内累加访问次数,当访问次数达到设定的阈值时,触发限流策略,当进入下一个时间周期时进行访问次数的清零。如图所示,我们要求3秒内的请求不要超过150次:

但是,貌似看似很"完美"的流量统计方式其实存在一个非常严重的临界问题,即:如果第2到3秒内产生了150次请求,而第3到4秒内产生了150次请求,那么其实在第2秒到第4秒这两秒内,就已经发生了300次请求了,远远大于我们要求的3秒内的请求不要超过150次这个限制。

② 滑动窗口算法

③ 令牌桶限流算法(控制令牌生成速度,取的速度不控制)

④ 漏桶限流算法(控制水滴流出速度,不控制水滴产生速度)

④ Spring Retry

重试框架,就是当我们方法失败后的重试机制

java 复制代码
@Service
@Slf4j
public class SpringRetryAnnotationService {
    @Autowired
    CommonService commonService;

    /**
     * 如果失败,定义重试3次,重试间隔为3s,指定恢复名称,指定监听器
     */
    @Retryable(value = RuntimeException.class, maxAttempts = 3, backoff = @Backoff(value = 3000L), recover = "testRecover", listeners = {"myRetryListener"})
    public void test() {
        commonService.test("注解式");
    }

    @Recover
    public void testRecover(RuntimeException runtimeException) {
        commonService.recover("注解式");
    }
}
相关推荐
一颗花生米。15 分钟前
深入理解JavaScript 的原型继承
java·开发语言·javascript·原型模式
问道飞鱼15 分钟前
Java基础-单例模式的实现
java·开发语言·单例模式
ok!ko4 小时前
设计模式之原型模式(通俗易懂--代码辅助理解【Java版】)
java·设计模式·原型模式
2401_857622664 小时前
SpringBoot框架下校园资料库的构建与优化
spring boot·后端·php
2402_857589364 小时前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
吾爱星辰5 小时前
Kotlin 处理字符串和正则表达式(二十一)
java·开发语言·jvm·正则表达式·kotlin
哎呦没5 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
编程、小哥哥6 小时前
netty之Netty与SpringBoot整合
java·spring boot·spring
IT学长编程7 小时前
计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·玩具租赁系统
莹雨潇潇7 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器