1、Redis是简介
redis官方网
Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助
工程基于基础架构 Kotlin+SpringBoot+MyBatisPlus搭建最简洁的前后端分离框架美搭建最简洁最酷的前后端分离框架进行完善
2、Redis开发者
3、Redis安装
Redis安装与其他知识点请参考几年前我编写文档 Redis Detailed operating instruction.pdf,这里不做太多的描述,主要讲解在kotlin+SpringBoot然后搭建Redis与遇到的问题
Redis详细使用说明书.pdf
https://github.com/jilongliang/kotlin/blob/dev-redis/src/main/resource/Redis%20Detailed%20operating%20instruction.pdf
4、Redis应该学习哪些?
列举一些常见的内容
5、Redis有哪些命令
Redis官方命令清单 https://redis.io/commands
Redis常用命令
Redis常用命令
6、 Redis常见应用场景
应用场景
7、 Redis常见的几种特征
Redis的哨兵机制
Redis的原子性
Redis持久化有RDB与AOF方式
8、工程结构
工程结构
9、Kotlin与Redis+Lua的代码实现
Redis 依赖的Jar配置
<!-- Spring Boot Redis 依赖 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId></dependency>
LuaConfiguration
设置加载限流lua脚本
@Configurationclass LuaConfiguration { @Bean fun redisScript(): DefaultRedisScript<Number> { val redisScript = DefaultRedisScript<Number>() //设置限流lua脚本 redisScript.setScriptSource(ResourceScriptSource(ClassPathResource("limitrate.lua"))) //第1种写法反射转换Number类型 //redisScript.setResultType(Number::class.java) //第2种写法反射转换Number类型 redisScript.resultType = Number::class.java return redisScript }}
Lua限流脚本
local key = "request:limit:rate:" .. KEYS[1] --限流KEYlocal limitCount = tonumber(ARGV[1]) --限流大小local limitTime = tonumber(ARGV[2]) --限流时间local current = tonumber(redis.call('get', key) or "0")if current + 1 > limitCount then --如果超出限流大小 return 0else --请求数+1,并设置1秒过期 redis.call("INCRBY", key,"1") redis.call("expire", key,limitTime) return current + 1end
RateLimiter自定义注解
1、Java自定义注解Target使用@Target(ElementType.TYPE, ElementType.METHOD)
2、Java自定义注解Retention使用@Retention(RetentionPolicy.RUNTIME)
3、Kotlin自定义注解Target使用@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
4、Kotlin自定义注解Target使用@Retention(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)@Retention(AnnotationRetention.RUNTIME)annotation class RateLimiter( /** * 限流唯一标识 * @return */ val key: String = "", /** * 限流时间 * @return */ val time: Int, /** * 限流次数 * @return */ val count: Int )
限流AOP的Aspect切面实现
import com.flong.kotlin.core.annotation.RateLimiterimport com.flong.kotlin.core.exception.BaseExceptionimport com.flong.kotlin.core.exception.CommMsgCodeimport com.flong.kotlin.core.vo.ErrorRespimport com.flong.kotlin.utils.WebUtilsimport org.aspectj.lang.ProceedingJoinPointimport org.aspectj.lang.annotation.Aroundimport org.aspectj.lang.annotation.Aspectimport org.aspectj.lang.reflect.MethodSignatureimport org.slf4j.Loggerimport org.slf4j.LoggerFactoryimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.context.annotation.Configurationimport org.springframework.data.redis.core.RedisTemplateimport org.springframework.data.redis.core.script.DefaultRedisScriptimport org.springframework.web.context.request.RequestContextHolderimport org.springframework.web.context.request.ServletRequestAttributesimport java.util.*@Suppress("SpringKotlinAutowiring")@Aspect@Configurationclass RateLimiterAspect { @Autowired lateinit var redisTemplate: RedisTemplate<String, Any> @Autowired var redisScript: DefaultRedisScript<Number>? = null /** * 半生对象 */ companion object { private val log: Logger = LoggerFactory.getLogger(RateLimiterAspect::class.java) } @Around("execution(* com.flong.kotlin.modules.controller ..*(..) )") @Throws(Throwable::class) fun interceptor(joinPoint: ProceedingJoinPoint): Any { val signature = joinPoint.signature as MethodSignature val method = signature.method val targetClass = method.declaringClass val rateLimit = method.getAnnotation(RateLimiter::class.java) if (rateLimit != null) { val request = (RequestContextHolder.getRequestAttributes() as ServletRequestAttributes).request val ipAddress = WebUtils.getIpAddr(request = request) val stringBuffer = StringBuffer() stringBuffer.append(ipAddress).append("-") .append(targetClass.name).append("- ") .append(method.name).append("-") .append(rateLimit!!.key) val keys = Collections.singletonList(stringBuffer.toString()) print(keys + rateLimit!!.count + rateLimit!!.time) val number = redisTemplate!!.execute<Number>(redisScript, keys, rateLimit!!.count, rateLimit!!.time) if (number != null && number!!.toInt() != 0 && number!!.toInt() <= rateLimit!!.count) { log.info("限流时间段内访问第:{} 次", number!!.toString()) return joinPoint.proceed() } } else { var proceed: Any? = joinPoint.proceed() ?: return ErrorResp(CommMsgCode.SUCCESS.code!!, CommMsgCode.SUCCESS.message!!) return joinPoint.proceed() } throw BaseException(CommMsgCode.RATE_LIMIT.code!!, CommMsgCode.RATE_LIMIT.message!!) }}
WebUtils代码
import javax.servlet.http.HttpServletRequest/** * User: liangjl * Date: 2020/7/28 * Time: 10:01 */object WebUtils { fun getIpAddr(request: HttpServletRequest): String? { var ipAddress: String? = null try { ipAddress = request.getHeader("x-forwarded-for") if (ipAddress == null || ipAddress!!.length == 0 || "unknown".equals(ipAddress!!, ignoreCase = true)) { ipAddress = request.getHeader("Proxy-Client-IP") } if (ipAddress == null || ipAddress!!.length == 0 || "unknown".equals(ipAddress!!, ignoreCase = true)) { ipAddress = request.getHeader("WL-Proxy-Client-IP") } if (ipAddress == null || ipAddress!!.length == 0 || "unknown".equals(ipAddress!!, ignoreCase = true)) { ipAddress = request.getRemoteAddr() } // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 if (ipAddress != null && ipAddress!!.length > 15) { // "***.***.***.***".length()= 15 if (ipAddress!!.indexOf(",") > 0) { ipAddress = ipAddress!!.substring(0, ipAddress.indexOf(",")) } } } catch (e: Exception) { ipAddress = "" } return ipAddress }}
Controller代码
@RestController @RequestMapping("rest") class RateLimiterController { companion object { private val log: Logger = LoggerFactory.getLogger(RateLimiterController::class.java) } @Autowired private val redisTemplate: RedisTemplate<*, *>? = null @GetMapping(value = "/limit") @RateLimiter(key = "limit", time = 10, count = 1) fun limit(): ResponseEntity<Any> { val date = DateFormatUtils.format(Date(), "yyyy-MM-dd HH:mm:ss.SSS") val limitCounter = RedisAtomicInteger("limit:rate:counter", redisTemplate!!.connectionFactory!!) val str = date + " 累计访问次数:" + limitCounter.andIncrement log.info(str) return ResponseEntity.ok<Any>(str) } }
注意:RedisTemplateK, V>这个类由于有K与V,下面的做法是必须要指定Key-Value 2 type arguments expected for class RedisTemplate
运行结果
访问地址:http://localhost:8080/rest/limit
成功
失败
10、参考文章
参考分布式限流之Redis+Lua实现参考springboot + aop + Lua分布式限流的最佳实践
11、工程架构源代码
Kotlin+SpringBoot+Redis+Lua实现限流访问控制详解工程源代码
https://github.com/jilongliang/kotlin/tree/dev-lua
12 、总结与建议
2、 在学习过程中也遇到很多困难和疑点,如有问题或误点,望各位老司机多多指出或者提出建议。本人会采纳各种好建议和正确方式不断完善现况,人在成长过程中的需要优质的养料。
用户评论
这个游戏的开发技术听起来相当专业,感觉可以学到很多东西。
有17位网友表示赞同!
Kotlin和SpringBoot的结合太酷了,加上Redis+Lua实现限流访问控制,真是太实用了!
有19位网友表示赞同!
我之前就对Kotlin很感兴趣,这下有机会实际操作一下了。
有16位网友表示赞同!
SpringBoot的快速发展让我惊叹,不知道这游戏能否用到最新的框架更新。
有16位网友表示赞同!
Redis作为缓存工具,用在游戏中可以减轻服务器压力,很期待看到这方面的实现。
有19位网友表示赞同!
Lua语言在游戏开发中的应用也让人好奇,它能带来哪些新特性呢?
有13位网友表示赞同!
限流访问控制对游戏来说非常重要,希望这个游戏能给出详细的方案解析。
有17位网友表示赞同!
学完这个游戏后,我对搭建高性能的服务器更有信心了。
有14位网友表示赞同!
Kotlin的函数式编程在游戏中应用得怎么样?很期待看到实际案例。
有11位网友表示赞同!
SpringBoot的模块化设计可以减少重复代码,不知道在这个游戏中表现如何?
有20位网友表示赞同!
Redis的数据结构是否适合用于游戏数据存储和交互呢?想了解一下。
有11位网友表示赞同!
Lua的性能强大,不知它在游戏中能达到怎样的效果。
有7位网友表示赞同!
这款游戏的实现代码公开的话,一定会成为编程爱好者的学习素材。
有9位网友表示赞同!
限流访问控制是防止服务器崩溃的关键,希望作者能详细讲解其实现过程。
有15位网友表示赞同!
Kotlin语言的学习曲线如何?对于新手来说是否容易上手?
有13位网友表示赞同!
SpringBoot+Redis的组合在分布式系统中应用广泛,不知道这个游戏怎么发挥它的优势。
有20位网友表示赞同!
Lua在游戏开发中的使用案例较少,这款游戏能否给出一些实践的经验分享?
有7位网友表示赞同!
学完这个游戏,我对游戏服务器架构的认知将会更上一层楼。
有11位网友表示赞同!
期待看到这个游戏在实际项目中的应用效果,看看它能在游戏中带来什么新玩法。
有13位网友表示赞同!
希望作者在后续的文章中,能进一步探讨游戏开发中的优化策略和技巧。
有13位网友表示赞同!