• DBA·王的Redis优化历程
    【摘要】 华为云分布式缓存服务Redis提供即开即用、安全可靠、弹性扩容、便捷管理的在线分布式缓存能力,且实例类型丰富,可满足用户高并发及快速数据访问的业务诉求。新一代分布式缓存服务Redis可实现实例8秒创建、单节点性能提升300%、可以G单位进行弹性扩展、提供可视化WebCli访问Redis。
  • DBA·王的Redis优化历程
    【摘要】 华为云分布式缓存服务Redis提供即开即用、安全可靠、弹性扩容、便捷管理的在线分布式缓存能力,且实例类型丰富,可满足用户高并发及快速数据访问的业务诉求。新一代分布式缓存服务Redis可实现实例8秒创建、单节点性能提升300%、可以G单位进行弹性扩展、提供可视化WebCli访问Redis。
  • Redis缓存数据库安全加固指导(二)
    背景在众多开源缓存技术中,Redis无疑是目前功能最为强大,应用最多的缓存技术之一,参考2018年国外数据库技术权威网站DB-Engines关于key-value数据库流行度排名,Redis暂列第一位,但是原生Redis版本在安全方面非常薄弱,很多地方不满足安全要求,如果暴露在公网上,极易受到恶意攻击,导致数据泄露和丢失。本文主要是在原生开源软件Redis3.0基础上,系统的在安全特性方面进行的增强,很多增强点涉及了开源代码的修改,后续章节阐述的Redis缓存数据库的安全规范, 理论上适用于所有应用Redis的产品。本系列共连载三篇,分九个章节,本文从敏感数据与加密保护,口令安全,日志审计三个章节阐述了Redis缓存数据库加固措施。1      敏感数据与加密保护1.1      密码保存(重要)安全问题:原生Redis服务端密码requirepass和masterauth是明文保存到redis.conf。解决方案: 服务端密码采用PBKDF2加密后保存到redis.conf。考虑到性能问题,每次认证都用PBKDF2会比较耗时,经过评审,采用在首次认证成功后,内存采用SHA256缓存,后续的请求优先使用SHA256校验。1.2      支持秘钥替换(重要)安全问题:涉及加解密的秘钥不能写死到代码中。解决方案: 秘钥支持定期替换。·   redis服务端redis-server:配置文件增加配置项:cipher-dir配置为redis_shared.key和root.key所在的文件夹的全路径,例如:cipher-dir /opt/redis/etc/cipher·    redis客户端:redis-cli添加参数-cipherdir,指向redis_shared.key和root.key所在的文件夹的全路径例如:redis-cli -h 127.0.0.1 -cipherdir /opt/redis/etc/cipher -a sessionrdb@dbuser@Changeme_123 -p 32091·    redis客户端SDK:jedis*.jar同一个进程内,Jedis接口为string, dbname@user@pwd,因为第三方接口(类似Jdbc),无法加密。1.3      密码传输(重要)安全问题:原生Redis通过config get命令可能获取到服务端敏感信息。解决方案:禁止将口令等敏感信息传送到客户端,因此需要禁掉config get requirepas/masterauth/requireuserpass等功能。1.4      密码修改(重要)安全问题:修改密码明文传输:config set masterauth pwd解决方案:Redis内存保存明文密码问题: masterauth 使用AES128加密,密码采用AES128保存2      口令安全2.1      产品缺省启用数据库口令复杂度检查功能安全问题:Redis修改密码没有复杂度检查。解决方案:提供单独的Redis修改工具来修改密码,特别注意以下几点。1.  进行口令复杂度检查。2.  在输入错误的用户名或密码时,不能出现类似于“密码错误”、“用户名不存在”之类的过于明确的原因提示信息,以防止攻击者用于猜解系统用户名/口令。3.  修改密码要校验老密码。4.  修改数据库密码不能和用户名一样。5.  交互式密码修改时要隐藏密码。6.  在文档中建议通过交互式修改密码。 2.2      防暴力破解,配置账户登录失败尝试次数安全问题:Redis原生版本存在暴力破解情况。解决方案:最大失败次数:maxauthfailtimes(单位 次,有效范围(0,10万],默认值1万)说明: 该配置项只支持在启动时redis.conf配置,不支持动态修改,屏蔽掉对应config set。不支持设置为0:表示不锁定任何IP。2.3      配置账户锁定后自动解锁时间鉴权失败锁定时间:authfaillocktime(单位分钟,有效范围[0~999],默认值10)设置为0时,表示永久锁定。说明: 该配置项只支持在启动时redis.conf配置,不支持动态修改,屏蔽掉对应config set。2.4      查看锁定IP问题:IP锁定后需要查看被锁定IP。解决方案:只有管理员可以查看已经锁定的IP列表,分隔符为英文冒号(:)示例1:config get lockedips返回:10.67.147.111;10.67.147.112;示例2:config get lockedips返回:10.67.147.111;说明:不支持config set lockedips,如果强制执行,返回错误:ERR Unsupported CONFIG parameter: lockedips2.5      手工锁定IP的解除只有管理员可以执行命令解锁锁定的IP,只支持解锁单个IP或者解锁全部IP解决方案:示例1,解锁单个IP:config set unlockips 10.67.147.111示例2,解锁所有IP:config set unlockips “all”说明:不支持config get unlockips,如果强制执行,返回空,redis-cli提示:(empty list or set)如果参数中的IP没有出现过异常,会返回解锁失败,例如:(error) ERR Invalid argument '10.67.147.111' for CONFIG SET 'unlockips'执行手动解锁,记录trace,例如:例如:26 Dec 03:15:19.958 * 10.67.147.113 unlocked by 10.67.147.111:59417日志审计2.6      安全审计1.         Redis自身支持日志记录到系统日志,如/var/log/localmessage。但需要通过在redis.conf进行如下配置:syslog-enabled yessyslog-ident redissyslog-facility local02.         客户端登录,记录客户端IP,账号等信息。3.         相关维护操作必须有详细的日志记录。示例: 29118:S 26 Nov 11:19:29.100 * The readdbuser logged in successfully;10.145.93.119:52817;2.7      操作日志转储安全问题:官方版本Redis日志不会转储,长时间运行可能会把磁盘占满。解决方案:单独运行tracemonitor进程(python版),定期管理Redis日志文件大小,主要是日志压缩和定期删除,避免占用过多磁盘。说明:目前平台默认60秒检测一次,日志达到20M压缩,日志个数最大50个。目前,华为云DCS Redis有免费使用活动,小伙伴们可以来试试:欢迎扫码查看更多精彩:
  • [热门活动] 【免费赠书】华为云发布新一代缓存Redis,留言评论领福利!
    9月14日,华为EBG中国区行业云拓展部部长胡维琦在大会现场进行了“做有信仰的云,在创新的道路上加速奔跑”的主题演讲,发布了华为云全新产品:新一代分布式缓存Redis。华为云结合自身优势以及多年的实践经验,在缓存技术领域不断探索和追求,为用户提供高可靠、高性能、企业级的云缓存服务,帮助企业应用快速上云,从容面对数字时代的转型和挑战。现在,只要点击《华为云发布新一代缓存Redis》,免费体验分布式缓存服务Redis,在文章中留言说出你的使用心得,(截至9月21日0点)点赞数超过10的前三名可获得《Redis开发与运维》技术书籍一本呦!扫一扫,在微信中打开文章:
  • 【免费赠书】华为云发布新一代缓存Redis,留言评论领福利!
    9月14日,华为EBG中国区行业云拓展部部长胡维琦在大会现场进行了“做有信仰的云,在创新的道路上加速奔跑”的主题演讲,发布了华为云全新产品:新一代分布式缓存Redis。华为云结合自身优势以及多年的实践经验,在缓存技术领域不断探索和追求,为用户提供高可靠、高性能、企业级的云缓存服务,帮助企业应用快速上云,从容面对数字时代的转型和挑战。现在,只要点击《华为云发布新一代缓存Redis》,免费体验分布式缓存服务Redis,在文章中留言说出你的使用心得,(截至9月21日0点)点赞数超过10的前三名可获得《Redis开发与运维》技术书籍一本呦!扫一扫,在微信中打开文章:
  • 如何通过外网连接DCS Redis
    目前,DCS Redis实例推出了新功能,可通过公网地址连接华为云上的DCS Redis实例。DCS实例大部分情况下只能仅能通过华为云内网连接,即只能通过同一VPC内ECS上的客户端连接DCS实例。使用场景公网连接Redis缓存实例,主要是为了方便开发人员在本地搭建开发或测试环境,提高开发效率。生产环境(正式环境)的使用,请通过VPC内连接方式访问缓存实例,保障访问效率。环境准备已购买密码模式的DCS Redis实例,并记录实例的ID,连接密码等信息。(为确保通信安全,免密模式的实例不支持公网访问。如果是免密模式,需要通过重置密码将实例变为密码模式。重置密码参考重置实例密码)Redis实例安全组配置了正确的规则(允许36379端口被外部地址访问)。这样客户端才能正常连接Redis实例。本地机器上已安装Redis-Cli客户端。开启公网访问开关登录DCS的管理控制台。进入Redis实例的详情页面。单击“公网访问”右侧的开关。选择一个弹性IP,并记录弹性IP。Stunnel客户端安装配置以下操作均在Linux系统进行,Windows系统的操作步骤可参考DCS Redis公网连接参考文档。安装Stunnel客户端。apt-get的方式:apt install stunnel 或apt-get install stunnelyum方式:yum install stunnel其他方式,请参考DCS Redis公网连接参考文档中的详细步骤。设置Stunnel相关参数。编辑/etc/default/stunnel4,将参数ENABLED设置为1。说明:不同的Linux系统版本,配置文件名称可能有差异,可在/etc/default/路径下查找该文件。配置实例连接的相关参数。在配置文件stunnel.conf中配置以下参数:debug = 4 output = /var/log/stunnel.log sslVersion = all [redis-client] client = yes accept = 8000 connect = {弹性IP地址}:{端口} CAfile = /etc/stunnel/dcs-ca.cer以下参数需要根据说明修改,其他参数不用修改:client值固定填yes,表示为Stunnel客户端。CAfile为CA证书,可选。如果需要,可进入到实例详情页面下载该证书;如果不需要,此参数请删除。accept为Stunnel监听端口,可以自定义。Redis客户端访问缓存实例时填写此端口。connect为Stunnel转发地址与端口,此处填Redis实例的IP地址与端口,即分别替换为步骤1获取的弹性IP地址和端口。启动stunnel服务。stunnel 或 stunnel /{customdir}/stunnel.conf 或 service stunnel4 start 或 systemctl start stunnel4连接实例以Redis-Cli客户端在Linux上的操作为例:./redis-cli -p 8000auth ****************替换为实例的连接密码。目前,华为云DCS Redis有免费使用活动,小伙伴们可以来试试:进一步了解Redis相关内容,请联系中间件小哥。
  • 【转载】深入了解一下Redis的内存模型!
    本文转载自:https://zhuanlan.zhihu.com/p/37487213前言Redis是目前最火爆的内存数据库之一,通过在内存中读写数据,大大提高了读写速度,可以说Redis是实现网站高并发不可或缺的一部分。我们使用Redis时,会接触Redis的5种对象类型(字符串、哈希、列表、集合、有序集合),丰富的类型是Redis相对于Memcached等的一大优势。在了解Redis的5种对象类型的用法和特点的基础上,进一步了解Redis的内存模型,对Redis的使用有很大帮助,例如:1、估算Redis内存使用量。目前为止,内存的使用成本仍然相对较高,使用内存不能无所顾忌;根据需求合理的评估Redis的内存使用量,选择合适的机器配置,可以在满足需求的情况下节约成本。2、优化内存占用。了解Redis内存模型可以选择更合适的数据类型和编码,更好的利用Redis内存。3、分析解决问题。当Redis出现阻塞、内存占用等问题时,尽快发现导致问题的原因,便于分析解决问题。这篇文章主要介绍Redis的内存模型(以3.0为例),包括Redis占用内存的情况及如何查询、不同的对象类型在内存中的编码方式、内存分配器(jemalloc)、简单动态字符串(SDS)、RedisObject等;然后在此基础上介绍几个Redis内存模型的应用。在后面的文章中,会陆续介绍关于Redis高可用的内容,包括主从复制、哨兵、集群等等,欢迎关注。目录一、Redis内存统计二、Redis内存划分1、数据2、进程本身运行需要的内存3、缓冲内存4、内存碎片三、Redis数据存储的细节1、概述2、jemalloc3、redisObject4、SDS四、Redis的对象类型与内部编码1、字符串2、列表3、哈希4、集合5、有序集合五、应用举例1、估算Redis内存使用量2、优化内存占用3、关注内存碎片率一、Redis内存统计工欲善其事必先利其器,在说明Redis内存之前首先说明如何统计Redis使用内存的情况。在客户端通过redis-cli连接服务器后(后面如无特殊说明,客户端一律使用redis-cli),通过info命令可以查看内存使用情况:1info memory其中,info命令可以显示redis服务器的许多信息,包括服务器基本信息、CPU、内存、持久化、客户端连接信息等等;memory是参数,表示只显示内存相关的信息。返回结果中比较重要的几个说明如下:(1)used_memory:Redis分配器分配的内存总量(单位是字节),包括使用的虚拟内存(即swap);Redis分配器后面会介绍。used_memory_human只是显示更友好。(2)used_memory_rss:Redis进程占据操作系统的内存(单位是字节),与top及ps命令看到的值是一致的;除了分配器分配的内存之外,used_memory_rss还包括进程运行本身需要的内存、内存碎片等,但是不包括虚拟内存。因此,used_memory和used_memory_rss,前者是从Redis角度得到的量,后者是从操作系统角度得到的量。二者之所以有所不同,一方面是因为内存碎片和Redis进程运行需要占用内存,使得前者可能比后者小,另一方面虚拟内存的存在,使得前者可能比后者大。由于在实际应用中,Redis的数据量会比较大,此时进程运行占用的内存与Redis数据量和内存碎片相比,都会小得多;因此used_memory_rss和used_memory的比例,便成了衡量Redis内存碎片率的参数;这个参数就是mem_fragmentation_ratio。(3)mem_fragmentation_ratio:内存碎片比率,该值是used_memory_rss / used_memory的比值。mem_fragmentation_ratio一般大于1,且该值越大,内存碎片比例越大。mem_fragmentation_ratio<1,说明Redis使用了虚拟内存,由于虚拟内存的媒介是磁盘,比内存速度要慢很多,当这种情况出现时,应该及时排查,如果内存不足应该及时处理,如增加Redis节点、增加Redis服务器的内存、优化应用等。一般来说,mem_fragmentation_ratio在1.03左右是比较健康的状态(对于jemalloc来说);上面截图中的mem_fragmentation_ratio值很大,是因为还没有向Redis中存入数据,Redis进程本身运行的内存使得used_memory_rss 比used_memory大得多。(4)mem_allocator:Redis使用的内存分配器,在编译时指定;可以是 libc 、jemalloc或者tcmalloc,默认是jemalloc;截图中使用的便是默认的jemalloc。二、Redis内存划分Redis作为内存数据库,在内存中存储的内容主要是数据(键值对);通过前面的叙述可以知道,除了数据以外,Redis的其他部分也会占用内存。Redis的内存占用主要可以划分为以下几个部分:1、数据作为数据库,数据是最主要的部分;这部分占用的内存会统计在used_memory中。Redis使用键值对存储数据,其中的值(对象)包括5种类型,即字符串、哈希、列表、集合、有序集合。这5种类型是Redis对外提供的,实际上,在Redis内部,每种类型可能有2种或更多的内部编码实现;此外,Redis在存储对象时,并不是直接将数据扔进内存,而是会对对象进行各种包装:如redisObject、SDS等;这篇文章后面将重点介绍Redis中数据存储的细节。2、进程本身运行需要的内存Redis主进程本身运行肯定需要占用内存,如代码、常量池等等;这部分内存大约几兆,在大多数生产环境中与Redis数据占用的内存相比可以忽略。这部分内存不是由jemalloc分配,因此不会统计在used_memory中。补充说明:除了主进程外,Redis创建的子进程运行也会占用内存,如Redis执行AOF、RDB重写时创建的子进程。当然,这部分内存不属于Redis进程,也不会统计在used_memory和used_memory_rss中。3、缓冲内存缓冲内存包括客户端缓冲区、复制积压缓冲区、AOF缓冲区等;其中,客户端缓冲存储客户端连接的输入输出缓冲;复制积压缓冲用于部分复制功能;AOF缓冲区用于在进行AOF重写时,保存最近的写入命令。在了解相应功能之前,不需要知道这些缓冲的细节;这部分内存由jemalloc分配,因此会统计在used_memory中。4、内存碎片内存碎片是Redis在分配、回收物理内存过程中产生的。例如,如果对数据的更改频繁,而且数据之间的大小相差很大,可能导致redis释放的空间在物理内存中并没有释放,但redis又无法有效利用,这就形成了内存碎片。内存碎片不会统计在used_memory中。内存碎片的产生与对数据进行的操作、数据的特点等都有关;此外,与使用的内存分配器也有关系:如果内存分配器设计合理,可以尽可能的减少内存碎片的产生。后面将要说到的jemalloc便在控制内存碎片方面做的很好。如果Redis服务器中的内存碎片已经很大,可以通过安全重启的方式减小内存碎片:因为重启之后,Redis重新从备份文件中读取数据,在内存中进行重排,为每个数据重新选择合适的内存单元,减小内存碎片。三、Redis数据存储的细节1、概述关于Redis数据存储的细节,涉及到内存分配器(如jemalloc)、简单动态字符串(SDS)、5种对象类型及内部编码、redisObject。在讲述具体内容之前,先说明一下这几个概念之间的关系。下图是执行set hello world时,所涉及到的数据模型。图片来源:https://searchdatabase.techtarget.com.cn/7-20218/(1)dictEntry:Redis是Key-Value数据库,因此对每个键值对都会有一个dictEntry,里面存储了指向Key和Value的指针;next指向下一个dictEntry,与本Key-Value无关。(2)Key:图中右上角可见,Key(”hello”)并不是直接以字符串存储,而是存储在SDS结构中。(3)redisObject:Value(“world”)既不是直接以字符串存储,也不是像Key一样直接存储在SDS中,而是存储在redisObject中。实际上,不论Value是5种类型的哪一种,都是通过redisObject来存储的;而redisObject中的type字段指明了Value对象的类型,ptr字段则指向对象所在的地址。不过可以看出,字符串对象虽然经过了redisObject的包装,但仍然需要通过SDS存储。实际上,redisObject除了type和ptr字段以外,还有其他字段图中没有给出,如用于指定对象内部编码的字段;后面会详细介绍。(4)jemalloc:无论是DictEntry对象,还是redisObject、SDS对象,都需要内存分配器(如jemalloc)分配内存进行存储。以DictEntry对象为例,有3个指针组成,在64位机器下占24个字节,jemalloc会为它分配32字节大小的内存单元。下面来分别介绍jemalloc、redisObject、SDS、对象类型及内部编码。2、jemallocRedis在编译时便会指定内存分配器;内存分配器可以是 libc 、jemalloc或者tcmalloc,默认是jemalloc。jemalloc作为Redis的默认内存分配器,在减小内存碎片方面做的相对比较好。jemalloc在64位系统中,将内存空间划分为小、大、巨大三个范围;每个范围内又划分了许多小的内存块单位;当Redis存储数据时,会选择大小最合适的内存块进行存储。jemalloc划分的内存单元如下图所示:图片来源:http://blog.csdn.net/zhengpeitao/article/details/76573053例如,如果需要存储大小为130字节的对象,jemalloc会将其放入160字节的内存单元中。3、redisObject前面说到,Redis对象有5种类型;无论是哪种类型,Redis都不会直接存储,而是通过redisObject对象进行存储。redisObject对象非常重要,Redis对象的类型、内部编码、内存回收、共享对象等功能,都需要redisObject支持,下面将通过redisObject的结构来说明它是如何起作用的。redisObject的定义如下(不同版本的Redis可能稍稍有所不同):1234567typedef struct redisObject {  unsigned type:4;  unsigned encoding:4;unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */  int refcount;  void *ptr;} robj;redisObject的每个字段的含义和作用如下:(1)typetype字段表示对象的类型,占4个比特;目前包括REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。当我们执行type命令时,便是通过读取RedisObject的type字段获得对象的类型;如下图所示:(2)encodingencoding表示对象的内部编码,占4个比特。对于Redis支持的每种类型,都有至少两种内部编码,例如对于字符串,有int、embstr、raw三种编码。通过encoding属性,Redis可以根据不同的使用场景来为对象设置不同的编码,大大提高了Redis的灵活性和效率。以列表对象为例,有压缩列表和双端链表两种编码方式;如果列表中的元素较少,Redis倾向于使用压缩列表进行存储,因为压缩列表占用内存更少,而且比双端链表可以更快载入;当列表对象元素较多时,压缩列表就会转化为更适合存储大量元素的双端链表。通过object encoding命令,可以查看对象采用的编码方式,如下图所示:5种对象类型对应的编码方式以及使用条件,将在后面介绍。(3)lrulru记录的是对象最后一次被命令程序访问的时间,占据的比特数不同的版本有所不同(如4.0版本占24比特,2.6版本占22比特)。通过对比lru时间与当前时间,可以计算某个对象的空转时间;object idletime命令可以显示该空转时间(单位是秒)。object idletime命令的一个特殊之处在于它不改变对象的lru值。lru值除了通过object idletime命令打印之外,还与Redis的内存回收有关系:如果Redis打开了maxmemory选项,且内存回收算法选择的是volatile-lru或allkeys—lru,那么当Redis内存占用超过maxmemory指定的值时,Redis会优先选择空转时间最长的对象进行释放。(4)refcountrefcount与共享对象refcount记录的是该对象被引用的次数,类型为整型。refcount的作用,主要在于对象的引用计数和内存回收。当创建新对象时,refcount初始化为1;当有新程序使用该对象时,refcount加1;当对象不再被一个新程序使用时,refcount减1;当refcount变为0时,对象占用的内存会被释放。Redis中被多次使用的对象(refcount>1),称为共享对象。Redis为了节省内存,当有一些对象重复出现时,新的程序不会创建新的对象,而是仍然使用原来的对象。这个被重复使用的对象,就是共享对象。目前共享对象仅支持整数值的字符串对象。共享对象的具体实现Redis的共享对象目前只支持整数值的字符串对象。之所以如此,实际上是对内存和CPU(时间)的平衡:共享对象虽然会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。对于整数值,判断操作复杂度为O(1);对于普通字符串,判断复杂度为O(n);而对于哈希、列表、集合和有序集合,判断的复杂度为O(n^2)。虽然共享对象只能是整数值的字符串对象,但是5种类型都可能使用共享对象(如哈希、列表等的元素可以使用)。就目前的实现来说,Redis服务器在初始化时,会创建10000个字符串对象,值分别是0~9999的整数值;当Redis需要使用值为0~9999的字符串对象时,可以直接使用这些共享对象。10000这个数字可以通过调整参数REDIS_SHARED_INTEGERS(4.0中是OBJ_SHARED_INTEGERS)的值进行改变。共享对象的引用次数可以通过object refcount命令查看,如下图所示。命令执行的结果页佐证了只有0~9999之间的整数会作为共享对象。(5)ptrptr指针指向具体的数据,如前面的例子中,set hello world,ptr指向包含字符串world的SDS。(6)总结综上所述,redisObject的结构与对象类型、编码、内存回收、共享对象都有关系;一个redisObject对象的大小为16字节:4bit+4bit+24bit+4Byte+8Byte=16Byte。4、SDSRedis没有直接使用C字符串(即以空字符’\0’结尾的字符数组)作为默认的字符串表示,而是使用了SDS。SDS是简单动态字符串(Simple Dynamic String)的缩写。(1)SDS结构sds的结构如下:12345struct sdshdr {int len;int free;char buf[];};其中,buf表示字节数组,用来存储字符串;len表示buf已使用的长度,free表示buf未使用的长度。下面是两个例子。图片来源:《Redis设计与实现》通过SDS的结构可以看出,buf数组的长度=free+len+1(其中1表示字符串结尾的空字符);所以,一个SDS结构占据的空间为:free所占长度+len所占长度+ buf数组的长度=4+4+free+len+1=free+len+9。(2)SDS与C字符串的比较SDS在C字符串的基础上加入了free和len字段,带来了很多好处:获取字符串长度:SDS是O(1),C字符串是O(n)缓冲区溢出:使用C字符串的API时,如果字符串长度增加(如strcat操作)而忘记重新分配内存,很容易造成缓冲区的溢出;而SDS由于记录了长度,相应的API在可能造成缓冲区溢出时会自动重新分配内存,杜绝了缓冲区溢出。修改字符串时内存的重分配:对于C字符串,如果要修改字符串,必须要重新分配内存(先释放再申请),因为如果没有重新分配,字符串长度增大时会造成内存缓冲区溢出,字符串长度减小时会造成内存泄露。而对于SDS,由于可以记录len和free,因此解除了字符串长度和空间数组长度之间的关联,可以在此基础上进行优化:空间预分配策略(即分配内存时比实际需要的多)使得字符串长度增大时重新分配内存的概率大大减小;惰性空间释放策略使得字符串长度减小时重新分配内存的概率大大减小。存取二进制数据:SDS可以,C字符串不可以。因为C字符串以空字符作为字符串结束的标识,而对于一些二进制文件(如图片等),内容可能包括空字符串,因此C字符串无法正确存取;而SDS以字符串长度len来作为字符串结束标识,因此没有这个问题。此外,由于SDS中的buf仍然使用了C字符串(即以’\0’结尾),因此SDS可以使用C字符串库中的部分函数;但是需要注意的是,只有当SDS用来存储文本数据时才可以这样使用,在存储二进制数据时则不行(’\0’不一定是结尾)。(3)SDS与C字符串的应用Redis在存储对象时,一律使用SDS代替C字符串。例如set hello world命令,hello和world都是以SDS的形式存储的。而sadd myset member1 member2 member3命令,不论是键(”myset”),还是集合中的元素(”member1”、 ”member2”和”member3”),都是以SDS的形式存储。除了存储对象,SDS还用于存储各种缓冲区。只有在字符串不会改变的情况下,如打印日志时,才会使用C字符串。四、Redis的对象类型与内部编码前面已经说过,Redis支持5种对象类型,而每种结构都有至少两种编码;这样做的好处在于:一方面接口与实现分离,当需要增加或改变内部编码时,用户使用不受影响,另一方面可以根据不同的应用场景切换内部编码,提高效率。Redis各种对象类型支持的内部编码如下图所示(图中版本是Redis3.0,Redis后面版本中又增加了内部编码,略过不提;本章所介绍的内部编码都是基于3.0的):图片来源:《Redis设计与实现》关于Redis内部编码的转换,都符合以下规律:编码转换在Redis写入数据时完成,且转换过程不可逆,只能从小内存编码向大内存编码转换。1、字符串(1)概况字符串是最基础的类型,因为所有的键都是字符串类型,且字符串之外的其他几种复杂类型的元素也是字符串。字符串长度不能超过512MB。(2)内部编码字符串类型的内部编码有3种,它们的应用场景如下:int:8个字节的长整型。字符串值是整型时,这个值使用long整型表示。embstr:<=39字节的字符串。embstr与raw都使用redisObject和sds保存数据,区别在于,embstr的使用只分配一次内存空间(因此redisObject和sds是连续的),而raw需要分配两次内存空间(分别为redisObject和sds分配空间)。因此与raw相比,embstr的好处在于创建时少分配一次空间,删除时少释放一次空间,以及对象的所有数据连在一起,寻找方便。而embstr的坏处也很明显,如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,因此redis中的embstr实现为只读。raw:大于39个字节的字符串示例如下图所示:embstr和raw进行区分的长度,是39;是因为redisObject的长度是16字节,sds的长度是9+字符串长度;因此当字符串长度是39时,embstr的长度正好是16+9+39=64,jemalloc正好可以分配64字节的内存单元。(3)编码转换当int数据不再是整数,或大小超过了long的范围时,自动转化为raw。而对于embstr,由于其实现是只读的,因此在对embstr对象进行修改时,都会先转化为raw再进行修改,因此,只要是修改embstr对象,修改后的对象一定是raw的,无论是否达到了39个字节。示例如下图所示:2、列表(1)概况列表(list)用来存储多个有序的字符串,每个字符串称为元素;一个列表可以存储2^32-1个元素。Redis中的列表支持两端**和弹出,并可以获得指定位置(或范围)的元素,可以充当数组、队列、栈等。(2)内部编码列表的内部编码可以是压缩列表(ziplist)或双端链表(linkedlist)。双端链表:由一个list结构和多个l**ode结构组成;典型结构如下图所示:图片来源:《Redis设计与实现》通过图中可以看出,双端链表同时保存了表头指针和表尾指针,并且每个节点都有指向前和指向后的指针;链表中保存了列表的长度;dup、free和match为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。而链表中每个节点指向的是type为字符串的redisObject。压缩列表:压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块(而不是像双端链表一样每个节点是指针)组成的顺序型数据结构;具体结构相对比较复杂,略。与双端链表相比,压缩列表可以节省内存空间,但是进行修改或增删操作时,复杂度较高;因此当节点数量较少时,可以使用压缩列表;但是节点数量多时,还是使用双端链表划算。压缩列表不仅用于实现列表,也用于实现哈希、有序列表;使用非常广泛。(3)编码转换只有同时满足下面两个条件时,才会使用压缩列表:列表中元素数量小于512个;列表中所有字符串对象都不足64字节。如果有一个条件不满足,则使用双端列表;且编码只可能由压缩列表转化为双端链表,反方向则不可能。下图展示了列表编码转换的特点:其中,单个字符串不能超过64字节,是为了便于统一分配每个节点的长度;这里的64字节是指字符串的长度,不包括SDS结构,因为压缩列表使用连续、定长内存块存储字符串,不需要SDS结构指明长度。后面提到压缩列表,也会强调长度不超过64字节,原理与这里类似。3、哈希(1)概况哈希(作为一种数据结构),不仅是redis对外提供的5种对象类型的一种(与字符串、列表、集合、有序结合并列),也是Redis作为Key-Value数据库所使用的数据结构。为了说明的方便,在本文后面当使用“内层的哈希”时,代表的是redis对外提供的5种对象类型的一种;使用“外层的哈希”代指Redis作为Key-Value数据库所使用的数据结构。(2)内部编码内层的哈希使用的内部编码可以是压缩列表(ziplist)和哈希表(hashtable)两种;Redis的外层的哈希则只使用了hashtable。压缩列表前面已介绍。与哈希表相比,压缩列表用于元素个数少、元素长度小的场景;其优势在于集中存储,节省空间;同时,虽然对于元素的操作复杂度也由O(n)变为了O(1),但由于哈希中元素数量较少,因此操作的时间并没有明显劣势。hashtable:一个hashtable由1个dict结构、2个dictht结构、1个dictEntry指针数组(称为bucket)和多个dictEntry结构组成。正常情况下(即hashtable没有进行rehash时)各部分关系如下图所示:图片改编自:《Redis设计与实现》下面从底层向上依次介绍各个部分:dictEntrydictEntry结构用于保存键值对,结构定义如下:123456789typedef struct dictEntry{void *key;union{void *val;uint64_tu64;int64_ts64;}v;struct dictEntry *next;}dictEntry;其中,各个属性的功能如下:key:键值对中的键;val:键值对中的值,使用union(即共用体)实现,存储的内容既可能是一个指向值的指针,也可能是64位整型,或无符号64位整型;next:指向下一个dictEntry,用于解决哈希冲突问题在64位系统中,一个dictEntry对象占24字节(key/val/next各占8字节)。bucketbucket是一个数组,数组的每个元素都是指向dictEntry结构的指针。redis中bucket数组的大小计算规则如下:大于dictEntry的、最小的2^n;例如,如果有1000个dictEntry,那么bucket大小为1024;如果有1500个dictEntry,则bucket大小为2048。dicthtdictht结构如下:123456typedef struct dictht{dictEntry **table;unsigned long size;unsigned long sizemask;unsigned long used;}dictht;其中,各个属性的功能说明如下:table属性是一个指针,指向bucket;size属性记录了哈希表的大小,即bucket的大小;used记录了已使用的dictEntry的数量;sizemask属性的值总是为size-1,这个属性和哈希值一起决定一个键在table中存储的位置。dict一般来说,通过使用dictht和dictEntry结构,便可以实现普通哈希表的功能;但是Redis的实现中,在dictht结构的上层,还有一个dict结构。下面说明dict结构的定义及作用。dict结构如下:123456typedef struct dict{dictType *type;void *privdata;dictht ht[2];int trehashidx;} dict;其中,type属性和privdata属性是为了适应不同类型的键值对,用于创建多态字典。ht属性和trehashidx属性则用于rehash,即当哈希表需要扩展或收缩时使用。ht是一个包含两个项的数组,每项都指向一个dictht结构,这也是Redis的哈希会有1个dict、2个dictht结构的原因。通常情况下,所有的数据都是存在放dict的ht[0]中,ht[1]只在rehash的时候使用。dict进行rehash操作的时候,将ht[0]中的所有数据rehash到ht[1]中。然后将ht[1]赋值给ht[0],并清空ht[1]。因此,Redis中的哈希之所以在dictht和dictEntry结构之外还有一个dict结构,一方面是为了适应不同类型的键值对,另一方面是为了rehash。(3)编码转换如前所述,Redis中内层的哈希既可能使用哈希表,也可能使用压缩列表。只有同时满足下面两个条件时,才会使用压缩列表:哈希中元素数量小于512个;哈希中所有键值对的键和值字符串长度都小于64字节。如果有一个条件不满足,则使用哈希表;且编码只可能由压缩列表转化为哈希表,反方向则不可能。下图展示了Redis内层的哈希编码转换的特点:4、集合(1)概况集合(set)与列表类似,都是用来保存多个字符串,但集合与列表有两点不同:集合中的元素是无序的,因此不能通过索引来操作元素;集合中的元素不能有重复。一个集合中最多可以存储2^32-1个元素;除了支持常规的增删改查,Redis还支持多个集合取交集、并集、差集。(2)内部编码集合的内部编码可以是整数集合(intset)或哈希表(hashtable)。哈希表前面已经讲过,这里略过不提;需要注意的是,集合在使用哈希表时,值全部被置为null。整数集合的结构定义如下:12345typedef struct intset{uint32_t encoding;uint32_t length;int8_t contents[];} intset;其中,encoding代表contents中存储内容的类型,虽然contents(存储集合中的元素)是int8_t类型,但实际上其存储的值是int16_t、int32_t或int64_t,具体的类型便是由encoding决定的;length表示元素个数。整数集合适用于集合所有元素都是整数且集合元素数量较小的时候,与哈希表相比,整数集合的优势在于集中存储,节省空间;同时,虽然对于元素的操作复杂度也由O(n)变为了O(1),但由于集合数量较少,因此操作的时间并没有明显劣势。(3)编码转换只有同时满足下面两个条件时,集合才会使用整数集合:集合中元素数量小于512个;集合中所有元素都是整数值。如果有一个条件不满足,则使用哈希表;且编码只可能由整数集合转化为哈希表,反方向则不可能。下图展示了集合编码转换的特点:5、有序集合(1)概况有序集合与集合一样,元素都不能重复;但与集合不同的是,有序集合中的元素是有顺序的。与列表使用索引下标作为排序依据不同,有序集合为每个元素设置一个分数(score)作为排序依据。(2)内部编码有序集合的内部编码可以是压缩列表(ziplist)或跳跃表(skiplist)。ziplist在列表和哈希中都有使用,前面已经讲过,这里略过不提。跳跃表是一种有序数据结构,通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。除了跳跃表,实现有序数据结构的另一种典型实现是平衡树;大多数情况下,跳跃表的效率可以和平衡树媲美,且跳跃表实现比平衡树简单很多,因此redis中选用跳跃表代替平衡树。跳跃表支持平均O(logN)、最坏O(N)的复杂点进行节点查找,并支持顺序操作。Redis的跳跃表实现由zskiplist和zskipl**ode两个结构组成:前者用于保存跳跃表信息(如头结点、尾节点、长度等),后者用于表示跳跃表节点。具体结构相对比较复杂,略。(3)编码转换只有同时满足下面两个条件时,才会使用压缩列表:有序集合中元素数量小于128个;有序集合中所有成员长度都不足64字节。如果有一个条件不满足,则使用跳跃表;且编码只可能由压缩列表转化为跳跃表,反方向则不可能。下图展示了有序集合编码转换的特点:五、应用举例了解Redis的内存模型之后,下面通过几个例子说明其应用。1、估算Redis内存使用量要估算redis中的数据占据的内存大小,需要对redis的内存模型有比较全面的了解,包括前面介绍的hashtable、sds、redisobject、各种对象类型的编码方式等。下面以最简单的字符串类型来进行说明。假设有90000个键值对,每个key的长度是7个字节,每个value的长度也是7个字节(且key和value都不是整数);下面来估算这90000个键值对所占用的空间。在估算占据空间之前,首先可以判定字符串类型使用的编码方式:embstr。90000个键值对占据的内存空间主要可以分为两部分:一部分是90000个dictEntry占据的空间;一部分是键值对所需要的bucket空间。每个dictEntry占据的空间包括:1) 一个dictEntry,24字节,jemalloc会分配32字节的内存块2) 一个key,7字节,所以SDS(key)需要7+9=16个字节,jemalloc会分配16字节的内存块3) 一个redisObject,16字节,jemalloc会分配16字节的内存块4) 一个value,7字节,所以SDS(value)需要7+9=16个字节,jemalloc会分配16字节的内存块5) 综上,一个dictEntry需要32+16+16+16=80个字节。bucket空间:bucket数组的大小为大于90000的最小的2^n,是131072;每个bucket元素为8字节(因为64位系统中指针大小为8字节)。因此,可以估算出这90000个键值对占据的内存大小为:90000*80 + 131072*8 = 8248576。下面写个程序在redis中验证一下:123456789101112131415161718192021222324public class RedisTest {  public static Jedis jedis = new Jedis("localhost", 6379);  public static void main(String[] args) throws Exception{    Long m1 = Long.valueOf(getMemory());    insertData();    Long m2 = Long.valueOf(getMemory());    System.out.println(m2 - m1);  }  public static void insertData(){    for(int i = 10000; i < 100000; i++){      jedis.set("aa" + i, "aa" + i); //key和value长度都是7字节,且不是整数    }  }  public static String getMemory(){    String memoryAllLine = http://jedis.info("memory");    String usedMemoryLine = memoryAllLine.split("\r\n")[1];    String memory = usedMemoryLine.substring(usedMemoryLine.indexOf(':') + 1);    return memory;  }}运行结果:8247552理论值与结果值误差在万分之1.2,对于计算需要多少内存来说,这个精度已经足够了。之所以会存在误差,是因为在我们**90000条数据之前redis已分配了一定的bucket空间,而这些bucket空间尚未使用。作为对比将key和value的长度由7字节增加到8字节,则对应的SDS变为17个字节,jemalloc会分配32个字节,因此每个dictEntry占用的字节数也由80字节变为112字节。此时估算这90000个键值对占据内存大小为:90000*112 + 131072*8 = 11128576。在redis中验证代码如下(只修改**数据的代码):12345public static void insertData(){  for(int i = 10000; i < 100000; i++){    jedis.set("aaa" + i, "aaa" + i); //key和value长度都是8字节,且不是整数  }}运行结果:11128576;估算准确。对于字符串类型之外的其他类型,对内存占用的估算方法是类似的,需要结合具体类型的编码方式来确定。2、优化内存占用了解redis的内存模型,对优化redis内存占用有很大帮助。下面介绍几种优化场景。(1)利用jemalloc特性进行优化上一小节所讲述的90000个键值便是一个例子。由于jemalloc分配内存时数值是不连续的,因此key/value字符串变化一个字节,可能会引起占用内存很大的变动;在设计时可以利用这一点。例如,如果key的长度如果是8个字节,则SDS为17字节,jemalloc分配32字节;此时将key长度缩减为7个字节,则SDS为16字节,jemalloc分配16字节;则每个key所占用的空间都可以缩小一半。(2)使用整型/长整型如果是整型/长整型,Redis会使用int类型(8字节)存储来代替字符串,可以节省更多空间。因此在可以使用长整型/整型代替字符串的场景下,尽量使用长整型/整型。(3)共享对象利用共享对象,可以减少对象的创建(同时减少了redisObject的创建),节省内存空间。目前redis中的共享对象只包括10000个整数(0-9999);可以通过调整REDIS_SHARED_INTEGERS参数提高共享对象的个数;例如将REDIS_SHARED_INTEGERS调整到20000,则0-19999之间的对象都可以共享。考虑这样一种场景:论坛网站在redis中存储了每个帖子的浏览数,而这些浏览数绝大多数分布在0-20000之间,这时候通过适当增大REDIS_SHARED_INTEGERS参数,便可以利用共享对象节省内存空间。(4)避免过度设计然而需要注意的是,不论是哪种优化场景,都要考虑内存空间与设计复杂度的权衡;而设计复杂度会影响到代码的复杂度、可维护性。如果数据量较小,那么为了节省内存而使得代码的开发、维护变得更加困难并不划算;还是以前面讲到的90000个键值对为例,实际上节省的内存空间只有几MB。但是如果数据量有几千万甚至上亿,考虑内存的优化就比较必要了。3、关注内存碎片率内存碎片率是一个重要的参数,对redis 内存的优化有重要意义。如果内存碎片率过高(jemalloc在1.03左右比较正常),说明内存碎片多,内存浪费严重;这时便可以考虑重启redis服务,在内存中对数据进行重排,减少内存碎片。如果内存碎片率小于1,说明redis内存不足,部分数据使用了虚拟内存(即swap);由于虚拟内存的存取速度比物理内存差很多(2-3个数量级),此时redis的访问速度可能会变得很慢。因此必须设法增大物理内存(可以增加服务器节点数量,或提高单机内存),或减少redis中的数据。要减少redis中的数据,除了选用合适的数据类型、利用共享对象等,还有一点是要设置合理的数据回收策略(maxmemory-policy),当内存达到一定量后,根据不同的优先级对内存进行回收。
  • 【转载】Redis应用场景
    本文内容为转载,原文章地址为:https://www.scienjus.com/redis-use-case/自从第一次在项目中使用 Redis 后便深深地喜欢上了这个方便快速的 NoSQL 数据库,所以在很多需求中都会下意识的去寻找基于 Redis 的解决方案,在尝试后将这些使用场景记录下来希望能给大家带来帮助。缓存作为Key-Value形态的内存数据库,Redis 最先会被想到的应用场景便是作为数据缓存。而使用 Redis 缓存数据非常简单,只需要通过string类型将序列化后的对象存起来即可,不过也有一些需要注意的地方:必须保证不同对象的 key 不会重复,并且使 key 尽量短,一般使用类名(表名)加主键拼接而成。选择一个优秀的序列化方式也很重要,目的是提高序列化的效率和减少内存占用。缓存内容与数据库的一致性,这里一般有两种做法:只在数据库查询后将对象放入缓存,如果对象发生了修改或删除操作,直接清除对应缓存(或设为过期)。在数据库新增和查询后将对象放入缓存,修改后更新缓存,删除后清除对应缓存(或设为过期)。消息队列Redis 中list的数据结构实现是双向链表,所以可以非常便捷的应用于消息队列(生产者 / 消费者模型)。消息的生产者只需要通过lpush将消息放入 list,消费者便可以通过rpop取出该消息,并且可以保证消息的有序性。如果需要实现带有优先级的消息队列也可以选择sorted set。而pub/sub功能也可以用作发布者 / 订阅者模型的消息。无论使用何种方式,由于 Redis 拥有持久化功能,也不需要担心由于服务器故障导致消息丢失的情况。时间轴(Timeline)list作为双向链表,不光可以作为队列使用。如果将它用作栈便可以成为一个公用的时间轴。当用户发完微博后,都通过lpush将它存放在一个 key 为LATEST_WEIBO的list中,之后便可以通过lrange取出当前最新的微博。排行榜使用sorted set和一个计算热度的算法便可以轻松打造一个热度排行榜,zrevrangebyscore可以得到以分数倒序排列的序列,zrank可以得到一个成员在该排行榜的位置(是分数正序排列时的位置,如果要获取倒序排列时的位置需要用zcard-zrank)。计数器计数功能应该是最适合 Redis 的使用场景之一了,因为它高频率读写的特征可以完全发挥 Redis 作为内存数据库的高效。在 Redis 的数据结构中,string、hash和sorted set都提供了incr方法用于原子性的自增操作,下面举例说明一下它们各自的使用场景:如果应用需要显示每天的注册用户数,便可以使用string作为计数器,设定一个名为REGISTERED_COUNT_TODAY的 key,并在初始化时给它设置一个到凌晨 0 点的过期时间,每当用户注册成功后便使用incr命令使该 key 增长 1,同时当每天凌晨 0 点后,这个计数器都会因为 key 过期使值清零。每条微博都有点赞数、评论数、转发数和浏览数四条属性,这时用hash进行计数会更好,将该计数器的 key 设为weibo:weibo_id,hash的 field 为like_number、comment_number、forward_number和view_number,在对应操作后通过hincrby使hash 中的 field 自增。如果应用有一个发帖排行榜的功能,便选择sorted set吧,将集合的 key 设为POST_RANK。当用户发帖后,使用zincrby将该用户 id 的 score 增长 1。sorted set会重新进行排序,用户所在排行榜的位置也就会得到实时的更新。好友关系这个场景最开始是是一篇介绍微博 Redis 应用的 PPT 中看到的,其中提到微博的 Redis 主要是用在在计数和好友关系两方面上,当时对好友关系方面的用法不太了解,后来看到《Redis 设计与实现》中介绍到作者最开始去使用 Redis 便是希望能通过set解决传统数据库无法快速计算集合中交集这个功能。后来联想到微博当前的业务场景,确实能够以这种方式实现,所以姑且猜测一下:对于一个用户 A,将它的关注和粉丝的用户 id 都存放在两个 set 中:A:follow:存放 A 所有关注的用户 idA:follower:存放 A 所有粉丝的用户 id那么通过sinter命令便可以根据A:follow和A:follower的交集得到与 A 互相关注的用户。当 A 进入另一个用户 B 的主页后,A:follow和B:follow的交集便是 A 和 B 的共同专注,A:follow和B:follower的交集便是 A 关注的人也关注了 B。分布式锁在 Redis 2.6.12 版本开始,string的set命令增加了三个参数:EX:设置键的过期时间(单位为秒)PX:设置键的过期时间(单位为毫秒)NX | XX:当设置为NX时,仅当 key 存在时才进行操作,设置为XX时,仅当 key 不存在才会进行操作由于这个操作是原子性的,可以简单地以此实现一个分布式的锁,例如:set key "lock" EX 1 XX如果这个操作返回false,说明 key 的添加不成功,也就是当前有人在占用这把锁。而如果返回true,则说明得了锁,便可以继续进行操作,并且在操作后通过del命令释放掉锁。并且即使程序因为某些原因并没有释放锁,由于设置了过期时间,该锁也会在 1 秒后自动释放,不会影响到其他程序的运行。倒排索引倒排索引是构造搜索功能的最常见方式,在 Redis 中也可以通过set进行建立倒排索引,这里以简单的拼音 + 前缀搜索城市功能举例:假设一个城市北京,通过拼音词库将北京转为beijing,再通过前缀分词将这两个词分为若干个前缀索引,有:北、北京、b、be…beijin和beijing。将这些索引分别作为set的 key(例如:index:北)并存储北京的 id,倒排索引便建立好了。接下来只需要在搜索时通过关键词取出对应的set并得到其中的 id 即可。一些建议Redis 速度快是建立在内存数据库基础上的,但是一台服务器的内存要比磁盘金贵许多,所以在项目初期不要想什么都往 Redis 里放,这样当数据量上来后很快内存就会不够用,反而得不偿失。合理的利用有限的内存,将读(写)频繁的热数据放在 Redis 中才能更好感受到它带来的性能提升。Redis 虽然提供了RDB和AOF两种持久化方式,但是普遍还是认为 Redis 的持久化并不是很靠谱。这也是我一直不敢尝试彻底的用 Redis 去实现第五点(好友关系)的原因。虽然 Redis 在 3.0 之后才推出官方的集群方案,但是也有很多不错的开源方案,比如Codis。
  • 【Redis最佳实践】使用DCS Redis实现排行榜功能
    本节基于华为云DCS Redis实践所编写,用于指导您在以下场景使用DCS Redis实现排行榜功能。本帖主要内容包括以下几方面:场景介绍代码示例实践指导运行结果场景介绍在网页和APP中常常需要用到榜单的功能,对某个key-value的列表进行降序显示。当操作和查询并发大的时候,使用传统数据库就会遇到性能瓶颈,造成较大的时延。使用分布式缓存服务(DCS)的Redis版本,可以实现一个商品热销排行榜的功能。它的优势在于:数据保存在缓存中,读写速度非常快。提供字符串(String)、链表(List)、集合(Set)、哈希(Hash)等多种数据结构类型的存储。代码示例package dcsDemo02; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.UUID; import redis.clients.jedis.Jedis; import redis.clients.jedis.Tuple; public class productSalesRankDemo {     static final int PRODUCT_KINDS = 30;     public static void main(String[] args) {         //实例连接地址,从控制台获取         String host = "192.168.0.246";         //Redis端口         int port = 6379;         Jedis jedisClient = new Jedis(host, port);         try {             //实例密码             String authMsg = jedisClient.auth("Demo@123");             if (!authMsg.equals("OK")) {                 System.out.println("AUTH FAILED: " + authMsg);             }             //键             String key = "商品热销排行榜";             jedisClient.del(key);             //随机生成产品数据             List<String> productList = new ArrayList<>();             for(int i = 0; i < PRODUCT_KINDS; i ++) {                 productList.add("product-" + UUID.randomUUID().toString());             }             //随机生成销量             for(int i = 0; i < productList.size(); i ++) {                 int sales = (int)(Math.random() * 20000);                 String product = productList.get(i);                 //**Redis的SortedSet中                 jedisClient.zadd(key, sales, product);             }             System.out.println();             System.out.println("                   "+key);             //获取所有列表并按销量顺序输出             Set<Tuple> sortedProductList = jedisClient.zrevrangeWithScores(key, 0, -1);             for(Tuple product : sortedProductList) {                 System.out.println("产品ID: " + product.getElement() + ", 销量: "                          + Double.valueOf(product.getScore()).intValue());             }             System.out.println();             System.out.println("                   "+key);             System.out.println("                   前五大热销产品");             //获取销量前五列表并输出             Set<Tuple> sortedTopList = jedisClient.zrevrangeWithScores(key, 0, 4);             for(Tuple product : sortedTopList) {                 System.out.println("产品ID: " + product.getElement() + ", 销量: "                          + Double.valueOf(product.getScore()).intValue());             }         }         catch (Exception e) {             e.printStackTrace();         }         finally {             jedisClient.quit();             jedisClient.close();         }     } }实践指导准备一台弹性云服务器(ECS),选择Windows系统类型。在ECS上安装JDK1.8以上版本和Eclipse,下载jedis客户端(点此处直接下载jar包)。在华为云控制台购买DCS Redis缓存实例。注意和ECS选择相同虚拟私有云、子网以及安全组。在ECS上运行Eclipse,创建一个java工程,为示例代码创建一个productSalesRankDemo.java文件,并将jedis客户端作为library引用到工程中。将DCS缓存实例的连接地址、端口以及连接密码配置到示例代码文件中。编译并运行得到结果。运行结果编译并运行以上Demo程序,结果如下: 商品热销排行榜 产品ID: product-b290c0d4-e919-4266-8eb5-7ab84b19862d, 销量: 18433 产品ID: product-e61a0642-d34f-46f4-a720-ee35940a5e7f, 销量: 18334 产品ID: product-ceeab7c3-69a7-4994-afc6-41b7bc463d44, 销量: 18196 产品ID: product-f2bdc549-8b3e-4db1-8cd4-a2ddef4f5d97, 销量: 17870 产品ID: product-f50ca2de-7fa4-45a3-bf32-23d34ac15a41, 销量: 17842 产品ID: product-d0c364e0-66ec-48a8-9ac9-4fb58adfd033, 销量: 17782 产品ID: product-5e406bbf-47c7-44a9-965e-e1e9b62ed1cc, 销量: 17093 产品ID: product-0c4d31ee-bb15-4c88-b319-a69f74e3c493, 销量: 16432 产品ID: product-a986e3a4-4023-4e00-8104-db97e459f958, 销量: 16380 产品ID: product-a3ac9738-bed2-4a9c-b96a-d8511ae7f03a, 销量: 15305 产品ID: product-6b8ad4b7-e134-480f-b3ae-3d35d242cb53, 销量: 14534 产品ID: product-26a9b41b-96b1-4de0-932b-f78d95d55b2d, 销量: 11417 产品ID: product-1f043255-a1f9-40a0-b48b-f40a81d07e0e, 销量: 10875 产品ID: product-c8fee24c-d601-4e0e-9d18-046a65e59835, 销量: 10521 产品ID: product-5869622b-1894-4702-b750-d76ff4b29163, 销量: 10271 产品ID: product-ff0317d2-d7be-4021-9d25-1f997d622768, 销量: 9909 产品ID: product-da254e81-6dec-4c76-928d-9a879a11ed8d, 销量: 9504 产品ID: product-fa976c02-b175-4e82-b53a-8c0df96fe877, 销量: 8630 产品ID: product-0624a180-4914-46b9-84d0-9dfbbdaa0da2, 销量: 8405 产品ID: product-d0079955-eaea-47b2-845f-5ff05a110a70, 销量: 7930 产品ID: product-a53145ef-1db9-4c4d-a029-9324e7f728fe, 销量: 7429 产品ID: product-9b1a1fd1-7c3b-4ae8-9fd3-ab6a0bf71cae, 销量: 5944 产品ID: product-cf894aee-c1cb-425e-a644-87ff06485eb7, 销量: 5252 产品ID: product-8bd78ba8-f2c4-4e5e-b393-60aa738eceae, 销量: 4903 产品ID: product-89b64402-c624-4cf1-8532-ae1b4ec4cabc, 销量: 4527 产品ID: product-98b85168-9226-43d9-b3cf-ef84e1c3d75f, 销量: 3095 产品ID: product-0dda314f-22a7-464b-ab8c-2f8f00823a39, 销量: 2425 产品ID: product-de7eb085-9435-4924-b6fa-9e9fe552d5a7, 销量: 1694 产品ID: product-9beadc07-aab0-438c-ac5e-bcc72b9d9c36, 销量: 1135 产品ID: product-43834316-4aca-4fb2-8d2d-c768513015c5, 销量: 256             商品热销排行榜             前五大热销产品 产品ID: product-b290c0d4-e919-4266-8eb5-7ab84b19862d, 销量: 18433 产品ID: product-e61a0642-d34f-46f4-a720-ee35940a5e7f, 销量: 18334 产品ID: product-ceeab7c3-69a7-4994-afc6-41b7bc463d44, 销量: 18196 产品ID: product-f2bdc549-8b3e-4db1-8cd4-a2ddef4f5d97, 销量: 17870 产品ID: product-f50ca2de-7fa4-45a3-bf32-23d34ac15a41, 销量: 17842目前,华为云DCS Redis有免费使用活动,小伙伴们可以来试试:进一步了解Redis相关内容,请联系中间件小哥。
  • 【Redis最佳实践】使用DCS Redis实现分布式锁
    本节基于华为云DCS Redis实践所编写,用于指导您在以下场景使用DCS Redis实现分布式锁。本帖主要内容包括以下几方面:场景介绍加锁实现测试代码实践指导运行结果不加锁场景场景介绍很多互联网场景(如商品秒杀,论坛回帖盖楼等),需要用加锁的方式,以对某种资源进行顺序访问控制。如果应用服务集群部署,则涉及到对分布式应用加锁。当前分布式加锁主要有三种方式:(磁盘)数据库、缓存数据库、Zookeeper。使用DCS Redis实例实现分布式加锁,有几大优势:加锁操作简单,使用SET、GET、DEL等几条简单命令即可实现锁的获取和释放。性能优越,缓存数据的读写优于磁盘数据库与Zookeeper。可靠性强,DCS有主备和集群实例类型,避免单点故障。加锁实现 注意:以下代码实现仅展示使用DCS Redis进行加锁访问的便捷性。具体技术实现需要考虑死锁、锁的检查等情况,这里不做详细说明。package dcsDemo01; import java.util.UUID; import redis.clients.jedis.Jedis; public class DistributedLock {     private final String host = "192.168.0.220";     private final int port = 6379;     private static final String SUCCESS = "OK";     private static final String SET_IF_NOT_EXIST = "NX";     private static final String EXPIRE_TIME = "PX";     public  DistributedLock(){}     /*      * @param lockName      锁名      * @param timeout       获取锁的超时时间      * @param lockTimeout   锁的有效时间      * @return              锁的标识      */     public String getLockWithTimeout(String lockName, long timeout, long lockTimeout) {         String ret = null;         Jedis jedisClient = new Jedis(host, port);         try {             String authMsg = jedisClient.auth("Demo@123");             if (!SUCCESS.equals(authMsg)) {                 System.out.println("AUTH FAILED: " + authMsg);             }             String identifier = UUID.randomUUID().toString();             String lockKey = "DLock:" + lockName;             long end = System.currentTimeMillis() + timeout;             while(System.currentTimeMillis() < end) {                 String result = jedisClient.set(lockKey, identifier, SET_IF_NOT_EXIST, EXPIRE_TIME, lockTimeout);                 if(SUCCESS.equals(result)) {                     ret = identifier;                     break;                 }                 try {                     Thread.sleep(2);                 } catch (InterruptedException e) {                     Thread.currentThread().interrupt();                 }             }         }         catch (Exception e) {             e.printStackTrace();         }finally {             jedisClient.quit();             jedisClient.close();         }         return ret;     }     /*      * @param lockName        锁名      * @param identifier    锁的标识      */     public void releaseLock(String lockName, String identifier) {         Jedis jedisClient = new Jedis(host, port);         try {             String authMsg = jedisClient.auth("Demo@123");             if (!SUCCESS.equals(authMsg)) {                 System.out.println("AUTH FAILED: " + authMsg);             }             String lockKey = "DLock:" + lockName;             if(identifier.equals(jedisClient.get(lockKey))) {                 jedisClient.del(lockKey);             }         }         catch (Exception e) {             e.printStackTrace();         }finally {             jedisClient.quit();             jedisClient.close();         }     } }测试代码假设20个线程对10台mate10手机进行抢购:package dcsDemo01; import java.util.UUID; public class CaseTest {     public static void main(String[] args) {         ServiceOrder service = new ServiceOrder();         for (int i = 0; i < 20; i++) {             ThreadBuy client = new ThreadBuy(service);             client.start();         }     } } class ServiceOrder {     private final int MAX = 10;     DistributedLock DLock = new DistributedLock();     int n = 10;     public void handleOder() {         String userName = UUID.randomUUID().toString().substring(0,8) + Thread.currentThread().getName();         String identifier = DLock.getLockWithTimeout("Huawei Mate 10", 10000, 2000);         System.out.println("正在为用户:" + userName + " 处理订单");         if(n > 0) {             int num = MAX - n + 1;             System.out.println("用户:"+ userName + "购买第" + num + "台,剩余" + (--n) + "台");         }else {             System.out.println("用户:"+ userName + "无法购买,已售罄!");         }         DLock.releaseLock("Huawei Mate 10", identifier);     } } class ThreadBuy extends Thread {     private ServiceOrder service;     public ThreadBuy(ServiceOrder service) {         this.service = service;     }     @Override     public void run() {         service.handleOder();     } }实践指导准备一台弹性云服务器(ECS),选择Windows系统类型。在ECS上安装JDK1.8以上版本和Eclipse,下载jedis客户端(点此处直接下载jar包)。在华为云控制台购买DCS Redis缓存实例。注意和ECS选择相同虚拟私有云、子网以及安全组。在ECS上运行Eclipse,创建一个java工程,为示例代码分别创建一个分布式锁实现类DistributedLock.java和测试类:CaseTest.java,并将jedis客户端作为libs引用到工程中。将DCS缓存实例的连接地址、端口以及连接密码配置到示例代码文件中。注意有两处需要配置密码信息,分别在getLockWithTimeout和releaseLock两个方法中。编译并运行得到结果。运行结果配置好实际的缓存实例连接地址、端口与连接密码,运行代码,得到以下结果:正在为用户:eee56fb7Thread-16 处理订单 用户:eee56fb7Thread-16购买第1台,剩余9台 正在为用户:d6521816Thread-2 处理订单 用户:d6521816Thread-2购买第2台,剩余8台 正在为用户:d7b3b983Thread-19 处理订单 用户:d7b3b983Thread-19购买第3台,剩余7台 正在为用户:36a6b97aThread-15 处理订单 用户:36a6b97aThread-15购买第4台,剩余6台 正在为用户:9a973456Thread-1 处理订单 用户:9a973456Thread-1购买第5台,剩余5台 正在为用户:03f1de9aThread-14 处理订单 用户:03f1de9aThread-14购买第6台,剩余4台 正在为用户:2c315ee6Thread-11 处理订单 用户:2c315ee6Thread-11购买第7台,剩余3台 正在为用户:2b03b7c0Thread-12 处理订单 用户:2b03b7c0Thread-12购买第8台,剩余2台 正在为用户:75f25749Thread-0 处理订单 用户:75f25749Thread-0购买第9台,剩余1台 正在为用户:26c71db5Thread-18 处理订单 用户:26c71db5Thread-18购买第10台,剩余0台 正在为用户:c32654dbThread-17 处理订单 用户:c32654dbThread-17无法购买,已售罄! 正在为用户:df94370aThread-7 处理订单 用户:df94370aThread-7无法购买,已售罄! 正在为用户:0af94cddThread-5 处理订单 用户:0af94cddThread-5无法购买,已售罄! 正在为用户:e52428a4Thread-13 处理订单 用户:e52428a4Thread-13无法购买,已售罄! 正在为用户:46f91208Thread-10 处理订单 用户:46f91208Thread-10无法购买,已售罄! 正在为用户:e0ca87bbThread-9 处理订单 用户:e0ca87bbThread-9无法购买,已售罄! 正在为用户:f385af9aThread-8 处理订单 用户:f385af9aThread-8无法购买,已售罄! 正在为用户:46c5f498Thread-6 处理订单 用户:46c5f498Thread-6无法购买,已售罄! 正在为用户:935e0f50Thread-3 处理订单 用户:935e0f50Thread-3无法购买,已售罄! 正在为用户:d3eaae29Thread-4 处理订单 用户:d3eaae29Thread-4无法购买,已售罄!不加锁场景如果注释掉加锁代码,变成无锁情况,则抢购无序。//测试类中注释两行用于加锁的代码: public void handleOder() {     String userName = UUID.randomUUID().toString().substring(0,8) + Thread.currentThread().getName();     //加锁代码     //String identifier = DLock.getLockWithTimeout("Huawei Mate 10", 10000, 2000);     System.out.println("正在为用户:" + userName + " 处理订单");     if(n > 0) {         int num = MAX - n + 1;         System.out.println("用户:"+ userName + "够买第" + num + "台,剩余" + (--n) + "台");     }else {         System.out.println("用户:"+ userName + "无法够买,已售罄!");     }     //加锁代码     //DLock.releaseLock("Huawei Mate 10", identifier); }注释加锁代码后的运行结果,可以看出处理过程是无序的:正在为用户:e04934ddThread-5 处理订单 正在为用户:a4554180Thread-0 处理订单 用户:a4554180Thread-0购买第2台,剩余8台 正在为用户:b58eb811Thread-10 处理订单 用户:b58eb811Thread-10购买第3台,剩余7台 正在为用户:e8391c0eThread-19 处理订单 正在为用户:21fd133aThread-13 处理订单 正在为用户:1dd04ff4Thread-6 处理订单 用户:1dd04ff4Thread-6购买第6台,剩余4台 正在为用户:e5977112Thread-3 处理订单 正在为用户:4d7a8a2bThread-4 处理订单 用户:e5977112Thread-3购买第7台,剩余3台 正在为用户:18967410Thread-15 处理订单 用户:18967410Thread-15购买第9台,剩余1台 正在为用户:e4f51568Thread-14 处理订单 用户:21fd133aThread-13购买第5台,剩余5台 用户:e8391c0eThread-19购买第4台,剩余6台 正在为用户:d895d3f1Thread-12 处理订单 用户:d895d3f1Thread-12无法购买,已售罄! 正在为用户:7b8d2526Thread-11 处理订单 用户:7b8d2526Thread-11无法购买,已售罄! 正在为用户:d7ca1779Thread-8 处理订单 用户:d7ca1779Thread-8无法购买,已售罄! 正在为用户:74fca0ecThread-1 处理订单 用户:74fca0ecThread-1无法购买,已售罄! 用户:e04934ddThread-5购买第1台,剩余9台 用户:e4f51568Thread-14购买第10台,剩余0台 正在为用户:aae76a83Thread-7 处理订单 用户:aae76a83Thread-7无法购买,已售罄! 正在为用户:c638d2cfThread-2 处理订单 用户:c638d2cfThread-2无法购买,已售罄! 正在为用户:2de29a4eThread-17 处理订单 用户:2de29a4eThread-17无法购买,已售罄! 正在为用户:40a46ba0Thread-18 处理订单 用户:40a46ba0Thread-18无法购买,已售罄! 正在为用户:211fd9c7Thread-9 处理订单 用户:211fd9c7Thread-9无法购买,已售罄! 正在为用户:911b83fcThread-16 处理订单 用户:911b83fcThread-16无法购买,已售罄! 用户:4d7a8a2bThread-4购买第8台,剩余2台目前,华为云DCS Redis有免费使用活动,小伙伴们可以来试试:进一步了解Redis相关内容,请联系中间件小哥。
  • 关于华为云redis,你想要的这里都有
    华为云redis官网资料太多,不知如何入手? 这里提供一份最完整的华为云redis资料导图,让你不需要再寻寻觅觅,一次就可以get到关键。了解华为云Redis产品介绍什么是redis?redis有哪些使用场景?有什么使用限制?有什么新特性?从这里进入了解:产品介绍产品术语了解相关术语:产品术语产品最新活动从这里进入了解:产品最新活动产品最新报价从这里进入了解:产品最新报价购买并快速入门如何购买关于购买、付费方式、续费与退订等相关的问题,从这里进入了解:购买指南申请实例从这里进入了解:申请实例连接实例从这里进入:连接实例Redis运维管理日常操作开启、关闭、重启、删除实例,从这里进入:日常操作配置管理配置运行参数、维护时间窗、安全组、实例后台任务、公网访问开关,从这里进入:配置管理扩容实例从这里进入:实例扩容备份恢复备份恢复原理说明、配置备份策略、执行手动备份、恢复实例,从这里进入:备份恢复密码管理修改/重置密码、配置免密访问开关,从这里进入:密码管理监控查看华为云redis的监控指标,配置监控阈值等,从这里进入:redis监控审计查看华为云redis的可审计操作列表,从这里进入:redis审计FAQ合集Redis运维过程中遇到的常见问题合集,从这里进入:FAQ合集二次开发API接口从这里进入:API接口新特性使用指导通过公网访问Redis实例配置安全组规则,从这里进入:配置安全组规则打开公网访问开关,从这里进入:开启公网访问功能客户端配置,公网连接,从这里进入:通过公网访问Redis实例通过Web控制台访问DCS2.0 Redis实例从这里进入:通过WebCli访问DCS2.0 Redis实例目前,华为云DCS Redis有免费使用活动,小伙伴们可以来试试:进一步了解Redis相关内容,请联系中间件小哥。
  • 【福利贴】脱单秘笈震撼上线,错过再等一年!
    不知不觉夏天已经过去一半了,一个人吹着空调就着西瓜抱着电脑的单身程序猿们,到底如何才能摆脱母胎solo的宿命呢? 为了拯救广大闷骚内秀的IT宅男们,小柴特地出炉了一份“脱单秘笈”。内含福利,一定要认真阅读哦~ Step 1男生可以不帅,但是绝对不能没有气质和个性!所以,请先把你的拖鞋、大裤衩、紧身背心扔掉,然后去理发店剪掉多余的头发。简单的白衬衫+牛仔裤+帆布鞋,看起来干净、阳光,瞬间秒杀条纹衫有木有? Step 2吸引了女神的注意之后,交流就显得非常重要。如果你不想当一开口就输了的话,下面这些土味情话了解一下?——能问下路吗?——去哪里?——去你心里的路。 ——你为什么要害我!——我害你?——害我那么喜欢你。 ——啊,我手上划了一道口子,不如你也划一道吧。——为什么呀?——这样我们就是两口子了呀。 ——你能不能别说话了。——我没有啊。——那为什么我满脑子都是你的声音。 Step 3如无意外,此时妹子已被你哄得心花怒放了。然后,妹子向你抛出了一个绝杀问题——要不,下班陪我一起去逛街吧? 这时你感到的开心,不是高兴,而是惊慌、失措:怎么办,难道我要告诉她,每次公司有新业务特别是新游戏上线,我都要加班到凌晨紧盯着运行数据,以防业务异常影响用户体验。所以不能陪她去逛逛逛、吃吃吃了?啊,怎么办,难道……难道我就不能摆脱母胎solo的宿命吗,嘤嘤嘤…… 女神似乎看出了你的小心思,说:噗,你该不是要加班吧?等等,你该不会还在自建Redis缓存吧?哎,是时候放大招了——华为云分布式缓存服务Redis提供单机、主备、集群等实例类型,完美兼容原生开源协议,30+项监控指标自主运维,能满足用户高性能读写的业务诉求。一键扩容,立等可用,哎,你还在等什么,快点击查看详情:https://activity.huaweicloud.com/dcs_midyear/index.html?forum  “选华为云分布式缓存服务Redis,数据库运维更省心,你就有时间陪我去逛街了,走吧~”
  • 【干货】Redis客户端连接合集
    Redis客户端支持多种语言。这里收集Redis各种客户端安装、配置、连接的示例。后续会继续收集,请持续关注。华为云Redis(点击这里进入)客户端连接示例:Redis-cli客户端Jedis客户端phpredis客户端hiredis客户端redis-py客户端NodeJs客户端(New)C#客户端(New)华为云Web控制台Webcli特别说明:安装客户端的服务器与Redis实例之间必须保持网络畅通。这里需要保证两点:(1).两者在同一个虚拟私有云VPC内。(2).两者的安全组规则相同。跨VPC访问的配置可参考Redis实例是否支持跨VPC访问?安全组规则配置可参考安全组配置。目前,华为云DCS Redis有免费使用活动,小伙伴们可以来试试:进一步了解Redis相关内容,请联系中间件小哥。
  • 【Redis客户端连接系列】之NodeJs客户端
    这里以Ubuntu(debian系列) 进行连接示例。前提安装NodeJs的服务器与Redis实例网络畅通。这里需要保证两点:(1).两者在同一个虚拟私有云VPC内。(2).两者的安全组规则相同。步骤登录到安装NodeJs的服务器。安装NodeJs。apt install nodejs-legacynode --version安装js包管理工具npm。apt install npm安装redis客户端ioredis。npm install ioredis编辑连接示例脚本ioredisdemo.js。var Redis = require('ioredis'); var redis = new Redis({   port: 6379,          // Redis连接端口   host: '192.168.0.196',   // Redis连接地址   family: 4,           // 4 (IPv4) or 6 (IPv6)   password: '******',  // Redis连接密码  db: 0 });  redis.set('foo', 'bar'); redis.get('foo', function (err, result) {   console.log(result); });  // Or using a promise if the last argument isn't a function redis.get('foo').then(function (result) {   console.log(result); }); // Arguments to commands are flattened, so the following are the same: redis.sadd('set', 1, 3, 5, 7); redis.sadd('set', [1, 3, 5, 7]); // All arguments are passed directly to the redis server: redis.set('key', 100, 'EX', 10);运行脚本,连接Redis实例。node ioredisdemo.js
  • 【Redis客户端连接系列】之Redis-py客户端
    Redis-py是python语言客户端。这里以python命令行模式进行连接示例。前提安装Redis-py的服务器与Redis实例网络畅通。这里需要保证两点:(1).两者在同一个虚拟私有云VPC内。(2).两者的安全组规则相同。步骤登录到安装Redis-py的服务器。如果系统没有自带python,先安装python。yum install python下载并安装redis-py。wget https://github.com/andymccurdy/redis-py/archive/master.zip;python setup.py install执行python命令,进入命令模式。连接Redis实例。r = redis.StrictRedis(host='192.168.0.171', port=6379, password='******');说明:host、port和password分别为Redis实例的连接地址、端口和密码。