Redis는 새로운 데이터 저장 아이디어를 만들었습니다. Redis를 사용하면 단조로운 기능이 있는 데이터베이스를 직면할 때 코끼리를 냉장고에 넣는 방법에 집중할 필요가 없습니다. 대신 Redis를 사용하면 유연해질 수 있습니다. 변경 가능한 데이터 구조와 데이터 작업은 코끼리마다 다른 냉장고를 구축합니다. 이 비유가 마음에 드셨으면 좋겠습니다.
1. Redis에서 일반적으로 사용되는 데이터 유형(권장: redis 동영상 튜토리얼)
Redis에서 가장 일반적으로 사용되는 데이터 유형은 주로
String, Hash, List, Set, Sorted set
5가지 유형이 있습니다. 자세히 설명 데이터 유형을 분류하기 전에 먼저 다이어그램을 통해 Redis의 내부 메모리 관리에서 이러한 다양한 데이터 유형이 어떻게 설명되는지 이해하겠습니다.
먼저 Redis는 내부적으로 redisObject 개체를 사용하여 모든 키와 값을 나타냅니다. redisObject가 가장 중요합니다. 정보는 위 그림에 나와 있습니다. 유형은 값 객체의 특정 데이터 유형을 나타내고 인코딩은 다양한 데이터 유형이 redis 내에 저장되는 방식입니다.
예: type=string은 값이 일반 문자열로 저장됨을 의미하며 해당 인코딩은 raw 또는 int일 수 있습니다. int인 경우 실제 redis가 내부적으로 문자열을 숫자에 따라 저장하고 표현한다는 의미입니다. 물론 전제는 문자열 자체가 "123" "456"과 같은 숫자 값으로 표현될 수 있다는 것입니다.
여기서 vm 필드에 대해 특별히 주의해야 할 점은 Redis의 가상 메모리 기능이 활성화된 경우에만 이 필드가 실제로 메모리를 할당할 수 있다는 것입니다. 이 기능은 기본적으로 비활성화되어 있습니다.
위 그림을 통해 Redis는 redisObject를 사용하여 모든 키/값 데이터를 표현하는데 이는 메모리 낭비임을 알 수 있습니다. 물론 이러한 메모리 관리 비용은 주로 Redis의 다양한 데이터 유형에 대한 통합 관리 인터페이스를 제공하기 위한 것입니다. 실제 저작자도 메모리 사용량을 최대한 절약할 수 있도록 다양한 방법을 제시하고 있는데 이에 대해서는 나중에 자세히 다루도록 하겠다.
2. 다양한 데이터 유형의 적용 및 구현
먼저 이 5가지 데이터 유형의 사용 및 내부 구현을 하나씩 분석해 보겠습니다.
1. 문자열
문자열 데이터 구조는 간단한 키-값 유형입니다. 문자열뿐만 아니라 숫자도 가능합니다.
일반적으로 사용되는 명령: get, set, incr, decr, mget 등
응용 시나리오: 문자열은 가장 일반적으로 사용되는 데이터 유형이며 일반 키/값 저장소가 이 범주로 분류될 수 있습니다. 이는 Memcached의 현재 기능을 완전히 구현하고 더 효율적으로 사용할 수 있음을 의미합니다. Redis의 예약 지속성, 작업 로그 및 복제 기능도 즐길 수 있습니다. Memcached와 동일한 get, set, incr, decr 및 기타 작업을 제공하는 것 외에도 Redis는 다음 작업도 제공합니다.
문자열 길이 가져오기
문자열에 콘텐츠 추가
Set and get 문자 문자열의 특정 섹션
문자열의 특정 비트를 설정하고 가져옵니다
일련의 문자열 내용을 일괄 설정
사용 시나리오: 일반 키-값 캐시 애플리케이션. 정기 집계: 웨이보 게시물 수, 팬 수.
구현 방법: 문자열은 기본적으로 redisObject에 의해 참조되는 문자열로 저장됩니다. incr, decr 및 기타 작업이 발생하면 계산을 위해 숫자 유형으로 변환됩니다. redisObject는 int입니다.
2. Hash
일반적인 명령: hget, hset, hgetall 등
응용 시나리오:
Hash의 응용 시나리오를 설명하기 위해 간단한 예를 들어 보겠습니다. 예를 들어 다음 정보를 포함하는 사용자 정보 개체 데이터를 저장하려고 합니다.
사용자 ID는 검색할 키입니다. 저장된 값 사용자 개체에는 이름, 나이, 생일 및 기타 정보가 포함됩니다. 공통 키/값 구조를 사용하여 저장된 경우 두 가지 주요 저장 방법이 있습니다.
첫 번째 방법은 사용자 ID를 검색으로 사용합니다. 이 방법의 단점은 직렬화/역직렬화의 오버헤드가 증가하고 정보 중 하나를 수정해야 할 경우 전체 객체를 검색해야 하며 수정 작업을 수행해야 한다는 것입니다. 동시성을 보호합니다. CAS와 같은 복잡한 문제를 소개합니다.
두 번째 방법은 사용자 정보 객체에 멤버 수만큼 키-값 쌍을 저장하고, 사용자 ID + 해당 속성의 이름을 고유 식별자로 사용하여 해당 값을 얻는 것입니다. 속성을 사용하여 시퀀스를 생략하더라도 오버헤드와 동시성 문제를 최소화하지만, 사용자 ID가 반복적으로 저장되는 경우에는 여전히 메모리 낭비가 매우 큽니다.
그러면 Redis에서 제공하는 Hash는 이 문제를 매우 잘 해결합니다. Redis의 Hash는 실제로 내부적으로 HashMap으로 값을 저장하고 아래와 같이 이 Map의 구성원에 직접 액세스할 수 있는 인터페이스를 제공합니다.
즉, 키는 여전히 사용자 ID이고, 값은 Map입니다. 이 Map의 키는 회원의 속성 이름이고, 값은 속성 값입니다. 데이터는 내부 Map의 Key(Redis 내부 Map의 Key를 field라고 함)를 통해 직접 처리할 수 있다. 즉, 해당 속성 데이터를 key(사용자 ID) + field(속성 라벨)을 통해 조작할 수 있다. 데이터를 반복적으로 저장할 필요가 없으며 직렬화 및 동시 수정 제어 문제가 발생하지 않습니다. 문제를 아주 잘 해결했습니다.
여기서 주의할 점은 Redis는 모든 속성 데이터를 직접 얻을 수 있는 인터페이스(hgetall)를 제공한다는 점입니다. 그러나 내부 맵의 구성원이 많은 경우 Redis 단일 스레드 모델로 인해 전체 내부 맵을 순회해야 합니다. , 이 순회 작업은 시간이 많이 걸릴 수 있으며 다른 클라이언트 요청이 전혀 응답하지 않을 수 있으므로 특별한 주의가 필요합니다.
사용 시나리오: 사용자 정보 등 일부 변경 데이터를 저장합니다.
구현 방법:
위에서 언급한 것처럼 값에 해당하는 Redis 해시는 실제로 HashMap입니다. 여기에는 실제로 두 가지 다른 구현이 있습니다. 해시에 멤버 수가 적을 경우 Redis는 1차원 배열을 사용하여 이를 압축합니다. 메모리를 절약하기 위해 실제 HashMap 구조를 사용하는 대신 해당 값 redisObject의 인코딩은 zipmap이며 멤버 수가 증가하면 자동으로 실제 HashMap으로 변환되며 인코딩은 ht입니다.
3. 목록
일반적인 명령: lpush, rpush, lpop, rpop, lrange 등
응용 시나리오:
Redis 목록에는 많은 응용 시나리오가 있으며 Redis의 가장 중요한 데이터 구조 중 하나입니다. 예를 들어 트위터의 팔로우 목록, 팬 목록 등은 Redis의 목록 구조를 사용하여 구현할 수 있습니다.
List는 연결 목록입니다. 데이터 구조에 대한 지식이 있는 사람이라면 누구나 그 구조를 이해할 수 있어야 한다고 생각합니다. List 구조를 이용하면 최신 뉴스 순위 등의 기능을 쉽게 구현할 수 있습니다. List의 또 다른 응용 프로그램은 List의 PUSH 작업을 사용하여 작업을 List에 저장한 다음 작업자 스레드가 POP 작업을 사용하여 실행할 작업을 꺼낼 수 있는 것입니다. Redis는 List의 특정 세그먼트를 운영하기 위한 API도 제공하며, List의 특정 세그먼트에 대한 요소를 직접 쿼리하고 삭제할 수 있습니다.
//把当前登录人添加到链表里 ret = r.lpush("login:last_login_times", uid) //保持链表只有N位 ret = redis.ltrim("login:last_login_times", 0, N-1) //获得前N个最新登陆的用户Id列表 last_login_list = r.lrange("login:last_login_times", 0, N-1)예: sina Weibo: Redis에서 최신 Weibo ID는 항상 업데이트되는 상주 캐시를 사용합니다. 하지만 ID를 5000개 이하로 제한했기 때문에 ID 가져오기 기능은 항상 Redis에게 묻습니다. 시작/개수 매개변수가 이 범위를 초과하는 경우에만 데이터베이스에 액세스해야 합니다. 저희 시스템은 기존 방식처럼 캐시를 "새로 고침"하지 않으며 Redis 인스턴스의 정보는 항상 일관됩니다. SQL 데이터베이스(또는 하드 디스크에 있는 다른 유형의 데이터베이스)는 사용자가 "원거리" 데이터를 얻어야 할 때만 트리거되며 홈페이지나 첫 번째 댓글 페이지는 하드 디스크에 있는 데이터베이스를 방해하지 않습니다. 4. Set일반적인 명령: sadd, spop, smembers, sunion 등 응용 시나리오: Redis 세트에서 제공하는 외부 기능은 목록과 유사합니다. 특별한 점은 세트가 자동으로 데이터 중복을 제거해야 하고 중복 데이터가 표시되지 않도록 할 수 있다는 것입니다. 좋은 선택이며, 집합은 목록이 제공할 수 없는 집합 컬렉션에 멤버가 있는지 여부를 결정하는 중요한 인터페이스를 제공합니다. 세트는 세트이며, 세트의 개념은 여러 가지 고유한 값의 조합입니다. 일부 집합 데이터는 Redis에서 제공하는 Set 데이터 구조를 사용하여 저장할 수 있습니다. 사례: Weibo 애플리케이션에서는 사용자의 모든 팔로어를 컬렉션에 저장할 수 있으며, 모든 팬을 컬렉션에 저장할 수 있습니다. Redis는 또한 컬렉션에 대한 교집합, 합집합, 차이 등의 작업을 제공하므로 공통 관심, 공통 선호도 및 2급 친구와 같은 기능을 구현하는 데 매우 편리할 수 있습니다. 위의 모든 컬렉션 작업에 대해 다른 명령을 사용할 수도 있습니다. 결과를 클라이언트에 반환하거나 새 컬렉션에 저장합니다. Set은 문자열 형식의 순서가 지정되지 않은 집합입니다. Set은 해시테이블을 통해 구현됩니다. 개념은 기본적으로 수학의 집합과 유사합니다. 집합의 요소는 순서가 없습니다. . 구현 방법:
set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。
使用场景:
交集,并集,差集:(Set)
//book表存储book名称 set book:1:name ”The Ruby Programming Language” set book:2:name ”Ruby on rail” set book:3:name ”Programming Erlang” //tag表使用集合来存储数据,因为集合擅长求交集、并集 sadd tag:ruby 1 sadd tag:ruby 2 sadd tag:web 2 sadd tag:erlang 3 //即属于ruby又属于web的书? inter_list = redis.sinter("tag.web", "tag:ruby") //即属于ruby,但不属于web的书? inter_list = redis.sdiff("tag.ruby", "tag:web") //属于ruby和属于web的书的合集? inter_list = redis.sunion("tag.ruby", "tag:web")
获取某段时间所有数据去重值
这个使用Redis的set数据结构最合适了,只需要不断地将数据往set中扔就行了,set意为集合,所以会自动排重。
5、Sorted Set
常用命令:
zadd,zrange,zrem,zcard等
使用场景:
Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。
和Set相比,Sorted Set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列,比如一个存储全班同学成绩的Sorted Set,其集合value可以是同学的学号,而score就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。
另外还可以用Sorted Set来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
实现方式:
Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
三、Redis实际应用场景
1、显示最新的项目列表
下面这个语句常用来显示最新项目,随着数据多了,查询毫无疑问会越来越慢。
SELECT * FROM foo WHERE ... ORDER BY time DESC LIMIT 10
在Web应用中,“列出最新的回复”之类的查询非常普遍,这通常会带来可扩展性问题。这令人沮丧,因为项目本来就是按这个顺序被创建的,但要输出这个顺序却不得不进行排序操作。类似的问题就可以用Redis来解决。比如说,我们的一个Web应用想要列出用户贴出的最新20条评论。
在最新的评论边上我们有一个“显示全部”的链接,点击后就可以获得更多的评论。我们假设数据库中的每条评论都有一个唯一的递增的ID字段。我们可以使用分页来制作主页和评论页,使用Redis的模板,每次新评论发表时,我们会将它的ID添加到一个Redis列表:
LPUSH latest.comments <id></id>
我们将列表裁剪为指定长度,因此Redis只需要保存最新的5000条评论:
LTRIM latest.comments 0 5000
每次我们需要获取最新评论的项目范围时,我们调用一个函数来完成(使用伪代码):
FUNCTION get_latest_comments(start, num_items): id_list = redis.lrange("latest.comments",start,start+num_items - 1) IF id_list.length <p>这里我们做的很简单。在Redis中我们的最新ID使用了常驻缓存,这是一直更新的。但是我们做了限制不能超过5000个ID,因此我们的获取ID函数会一直询问Redis。只有在start/count参数超出了这个范围的时候,才需要去访问数据库。</p><p>我们的系统不会像传统方式那样“刷新”缓存,Redis实例中的信息永远是一致的。SQL数据库(或是硬盘上的其他类型数据库)只是在用户需要获取“很远”的数据时才会被触发,而主页或第一个评论页是不会麻烦到硬盘上的数据库了。</p><p>2、排行榜应用,取TOP N操作</p><p>这个需求与上面需求的不同之处在于,取最新N个数据的操作以时间为权重,这个是以某个条件为权重,比如按顶的次数排序,这时候就需要我们的sorted set出马了,将你要排序的值设置成sorted set的score,将具体的数据设置成相应的value,每次只需要执行一条ZADD命令即可。</p><p>热门,排行榜应用:</p><pre class="brush:php;toolbar:false">//将登录次数和用户统一存储在一个sorted set里 zadd login:login_times 5 1 zadd login:login_times 1 2 zadd login:login_times 2 3 //当用户登录时,对该用户的登录次数自增1 ret = r.zincrby("login:login_times", 1, uid) //那么如何获得登录次数最多的用户呢,逆序排列取得排名前N的用户 ret = r.zrevrange("login:login_times", 0, N-1)
另一个很普遍的需求是各种数据库的数据并非存储在内存中,因此在按得分排序以及实时更新这些几乎每秒钟都需要更新的功能上数据库的性能不够理想。典型的比如那些在线游戏的排行榜,比如一个Facebook的游戏,根据得分你通常想要:
- 列出前100名高分选手
- 列出某用户当前的全球排名
这些操作对于Redis来说小菜一碟,即使你有几百万个用户,每分钟都会有几百万个新的得分。模式是这样的,每次获得新得分时,我们用这样的代码:
ZADD leaderboard
你可能用userID来取代username,这取决于你是怎么设计的。得到前100名高分用户很简单:
ZREVRANGE leaderboard 0 99
用户的全球排名也相似,只需要:
ZRANK leaderboard
3、删除与过滤
我们可以使用LREM来删除评论。如果删除操作非常少,另一个选择是直接跳过评论条目的入口,报告说该评论已经不存在。 有些时候你想要给不同的列表附加上不同的过滤器。如果过滤器的数量受到限制,你可以简单的为每个不同的过滤器使用不同的Redis列表。毕竟每个列表只有5000条项目,但Redis却能够使用非常少的内存来处理几百万条项目。
4、按照用户投票和时间排序
排行榜的一种常见变体模式就像Reddit或Hacker News用的那样,新闻按照类似下面的公式根据得分来排序:score = points / time^alpha 因此用户的投票会相应的把新闻挖出来,但时间会按照一定的指数将新闻埋下去。下面是我们的模式,当然算法由你决定。
模式是这样的,开始时先观察那些可能是最新的项目,例如首页上的1000条新闻都是候选者,因此我们先忽视掉其他的,这实现起来很简单。每次新的新闻贴上来后,我们将ID添加到列表中,使用LPUSH + LTRIM,确保只取出最新的1000条项目。
有一项后台任务获取这个列表,并且持续的计算这1000条新闻中每条新闻的最终得分。计算结果由ZADD命令按照新的顺序填充生成列表,老新闻则被清除。这里的关键思路是排序工作是由后台任务来完成的。
5、处理过期项目
另一种常用的项目排序是按照时间排序。我们使用unix时间作为得分即可。 模式如下:
- 每次有新项目添加到我们的非Redis数据库时,我们把它加入到排序集合中。这时我们用的是时间属性,current_time和time_to_live。
- 另一项后台任务使用ZRANGE…SCORES查询排序集合,取出最新的10个项目。如果发现unix时间已经过期,则在数据库中删除条目。
6、计数
Redis是一个很好的计数器,这要感谢INCRBY和其他相似命令。我相信你曾许多次想要给数据库加上新的计数器,用来获取统计或显示新信息,但是最后却由于写入敏感而不得不放弃它们。
好了,现在使用Redis就不需要再担心了。有了原子递增(atomic increment),你可以放心的加上各种计数,用GETSET重置,或者是让它们过期。例如这样操作:
INCR user:<id> EXPIRE</id>
你可以计算出最近用户在页面间停顿不超过60秒的页面浏览量,当计数达到比如20时,就可以显示出某些条幅提示,或是其它你想显示的东西。
7、特定时间内的特定项目
另一项对于其他数据库很难,但Redis做起来却轻而易举的事就是统计在某段特点时间里有多少特定用户访问了某个特定资源。比如我想要知道某些特定的注册用户或IP地址,他们到底有多少访问了某篇文章。每次我获得一次新的页面浏览时我只需要这样做:
SADD page:day1:<page_id> <user_id></user_id></page_id>
当然你可能想用unix时间替换day1,比如time()-(time()%3600*24)等等。 想知道特定用户的数量吗?只需要使用
SCARD page:day1:<page_id></page_id>
需要测试某个特定用户是否访问了这个页面?
SISMEMBER page:day1:<page_id></page_id>
8、查找某个值所在的区间(区间无重合) :(Sorted Set)
例如有下面两个范围,10-20和30-40
A_start 10, A_end 20
B_start 30, B_end 40
我们将这两个范围的起始位置存在Redis的Sorted Sets数据结构中,基本范围起始值作为score,范围名加start和end为其value值:
redis 127.0.0.1:6379> zadd ranges 10 A_start (integer) 1 redis 127.0.0.1:6379> zadd ranges 20 A_end (integer) 1 redis 127.0.0.1:6379> zadd ranges 30 B_start (integer) 1 redis 127.0.0.1:6379> zadd ranges 40 B_end (integer) 1
这样数据在插入Sorted Sets后,相当于是将这些起始位置按顺序排列好了。现在我需要查找15这个值在哪一个范围中,只需要进行如下的zrangbyscore查找:
redis 127.0.0.1:6379> zrangebyscore ranges (15 +inf LIMIT 0 1 1) "A_end"
这个命令的意思是在Sorted Sets中查找大于15的第一个值。(+inf在Redis中表示正无穷大,15前面的括号表示>15而非>=15)查找的结果是A_end,由于所有值是按顺序排列的,所以可以判定15是在A_start到A_end区间上,也就是说15是在A这个范围里。至此大功告成。
9、交集,并集,差集:(Set)
//book表存储book名称 set book:1:name ”The Ruby Programming Language” set book:2:name ”Ruby on rail” set book:3:name ”Programming Erlang” //tag表使用集合来存储数据,因为集合擅长求交集、并集 sadd tag:ruby 1 sadd tag:ruby 2 sadd tag:web 2 sadd tag:erlang 3 //即属于ruby又属于web的书? inter_list = redis.sinter("tag.web", "tag:ruby") //即属于ruby,但不属于web的书? inter_list = redis.sdiff("tag.ruby", "tag:web") //属于ruby和属于web的书的合集? inter_list = redis.sunion("tag.ruby", "tag:web")
更多redis相关文章请关注redis数据库教程栏目。
위 내용은 Redis 애플리케이션 시나리오에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!