Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27fc6199ca | ||
|
|
0b01578d2e | ||
|
|
920c45b818 | ||
|
|
cf6c3f4b04 | ||
|
|
fb90934c47 | ||
|
|
207ca3cedd | ||
|
|
97d3b7dc8d | ||
|
|
abdbfc14c0 | ||
|
|
de5d4be994 | ||
|
|
69bfdcf7d4 | ||
|
|
699465c31e | ||
|
|
45332cc47b | ||
|
|
2d2c17b075 | ||
|
|
048c0f0f37 | ||
|
|
3eb3cc9da4 | ||
|
|
74f6ee8dd5 | ||
|
|
7a55d72f28 | ||
|
|
4e4b3b943c | ||
|
|
61e2547675 | ||
|
|
595b5974f8 | ||
|
|
58418d7c3e | ||
|
|
c02bda324f | ||
|
|
1afef16d88 | ||
|
|
7377aacd84 | ||
|
|
eece2d528c | ||
|
|
eb5aeaf138 | ||
|
|
1a6281f52f | ||
|
|
c2571b314a | ||
|
|
97aeda9828 | ||
|
|
ca9d961073 | ||
|
|
2446fbb3bb | ||
|
|
970e813be2 | ||
|
|
a36d524562 | ||
|
|
bbaf76ea2f | ||
|
|
669f302e16 | ||
|
|
71791e7a8e | ||
|
|
2f91d7ada4 | ||
|
|
5ef8f9531d | ||
|
|
3ce30a6906 | ||
|
|
65e79a8272 | ||
|
|
ef8bc13d0d | ||
|
|
0ffeadd290 | ||
|
|
db0e263b42 | ||
|
|
d526d09e41 | ||
|
|
9738e587da | ||
|
|
da67b5f59a | ||
|
|
825d2904d1 | ||
|
|
3becef9ee6 | ||
|
|
2ccf6e81a6 | ||
|
|
e18b59ae7e | ||
|
|
8ff1353766 | ||
|
|
99677e89d0 | ||
|
|
f43502fb0e | ||
|
|
ebb07fb4bb | ||
|
|
c13c4080e5 | ||
|
|
360664c9c8 | ||
|
|
7ac1b364fe | ||
|
|
1b2b8cbbde | ||
|
|
f487bbbfe2 | ||
|
|
4b2499cf2f | ||
|
|
01a0b09886 | ||
|
|
90419b5681 | ||
|
|
ae7db3fe83 | ||
|
|
454f4bfd28 | ||
|
|
1408d147e4 | ||
|
|
78055bd4ca | ||
|
|
736d447380 | ||
|
|
71b6f64f2a | ||
|
|
2380388974 | ||
|
|
3c49070b35 | ||
|
|
d69a483556 | ||
|
|
f2f2424e00 | ||
|
|
c4e93cadfb | ||
|
|
0540df22ba | ||
|
|
9ac5be2ebe | ||
|
|
b902c1413e | ||
|
|
54e9f28922 | ||
|
|
dd889d884f | ||
|
|
1443a814d0 | ||
|
|
f474a5bd4e | ||
|
|
1cd3c1e08c | ||
|
|
174df6fe49 | ||
|
|
53f1d81712 | ||
|
|
021321e0ef | ||
|
|
076ff11c06 | ||
|
|
6418b4c790 | ||
|
|
59aee5513d | ||
|
|
9fecc4057a | ||
|
|
51716cbdb8 | ||
|
|
9a7a07a466 | ||
|
|
0898aa462e | ||
|
|
4c4dec7202 | ||
|
|
401c3e213c | ||
|
|
beb1aab3fd | ||
|
|
28c07c7bf8 | ||
|
|
6674e3b910 | ||
|
|
65add0a311 | ||
|
|
b9f7e9e63c | ||
|
|
2136a880a7 | ||
|
|
f18e059e82 | ||
|
|
c392edf531 | ||
|
|
c8061392e1 | ||
|
|
53f0efe28c | ||
|
|
7d5f5712d9 | ||
|
|
ad1b4f4f59 | ||
|
|
1333f98dd2 | ||
|
|
8f8eeffec1 | ||
|
|
e983cf34be | ||
|
|
180e07b8c0 | ||
|
|
7982173fef | ||
|
|
eae9cce166 | ||
|
|
30407e1f4f | ||
|
|
6a246b1e7e | ||
|
|
cc7c4158bc | ||
|
|
8e33831b2b | ||
|
|
648e965460 | ||
|
|
603e616bf4 | ||
|
|
57997664ea | ||
|
|
2b2eca1f56 | ||
|
|
96b5d05fde | ||
|
|
f858c11d7d | ||
|
|
d8f160a848 | ||
|
|
ef11bcccca | ||
|
|
9f9e1ceaa0 | ||
|
|
8c304be359 | ||
|
|
7ecd4644e7 | ||
|
|
5ce3a24dba | ||
|
|
afd438dfff | ||
|
|
d6107fd6f1 | ||
|
|
a15742a41b | ||
|
|
b215a496a4 | ||
|
|
6f8a32d5c7 | ||
|
|
dd48de748c | ||
|
|
4b918769a1 | ||
|
|
e9bc56dbed | ||
|
|
3d24304ff9 | ||
|
|
cc20906390 | ||
|
|
586500c0ef | ||
|
|
eae33c1c81 | ||
|
|
1b508da7ca | ||
|
|
8b5db0a8dc | ||
|
|
076f88d657 | ||
|
|
e1a586ee69 | ||
|
|
3c1bf4957e | ||
|
|
4769dc7826 | ||
|
|
a5be65f71c | ||
|
|
039357e471 | ||
|
|
d51ebef509 | ||
|
|
a4ce758155 | ||
|
|
ecf9401415 | ||
|
|
8a88c368ed | ||
|
|
ac06fc011d | ||
|
|
5fa95ad763 | ||
|
|
c8a0070a61 | ||
|
|
bc8ffafeb9 | ||
|
|
bbac56c2f8 | ||
|
|
ce260f736e | ||
|
|
baa14ef913 | ||
|
|
8987bf23bf | ||
|
|
e3c51c4b1b | ||
|
|
59bd44d1c8 | ||
|
|
7c25a43adc | ||
|
|
ba3b474111 | ||
|
|
357a841714 | ||
|
|
b2a7fd0cf7 | ||
|
|
8a979f0390 | ||
|
|
c54afb6d0d | ||
|
|
b2cc45bfbc | ||
|
|
41945ba6ae | ||
|
|
ff4058183b | ||
|
|
d8d528e992 | ||
|
|
efcf948c1a | ||
|
|
5b761a3387 | ||
|
|
a2a69d5803 | ||
|
|
50d0e82d54 | ||
|
|
2612e0521f | ||
|
|
a4e48b417d | ||
|
|
bd70a5f588 | ||
|
|
9a68cf91f0 | ||
|
|
8a623a98c3 | ||
|
|
5397f2b596 | ||
|
|
c086b85afb | ||
|
|
9fd01051bf | ||
|
|
0c2f75c6d8 | ||
|
|
11fd0c422b | ||
|
|
d9d8ccab93 | ||
|
|
5402c4262e | ||
|
|
240f8dbf3f | ||
|
|
be98a33b51 | ||
|
|
97e7f8aec3 | ||
|
|
57c9babd81 | ||
|
|
abc3ff4d90 | ||
|
|
645e9962cb | ||
|
|
3b5e72d402 | ||
|
|
ecc72ca17f | ||
|
|
56fce7ce7d | ||
|
|
feecb608ed | ||
|
|
4ebfc45528 | ||
|
|
12ebe2ac17 | ||
|
|
f6433915fe | ||
|
|
26b3366993 | ||
|
|
62ec599c36 | ||
|
|
f4aa600b99 | ||
|
|
8146e31677 | ||
|
|
ec8f06675a | ||
|
|
afc156c2d8 | ||
|
|
4d7e125519 | ||
|
|
a9b18e54d4 | ||
|
|
8df3dcada5 | ||
|
|
e902b579b4 | ||
|
|
339b9dc2d2 | ||
|
|
cfcd5d6d43 | ||
|
|
7fc4ce13ed | ||
|
|
24f753a8b9 | ||
|
|
8d3e063a0a | ||
|
|
1b1f47c915 | ||
|
|
297e77c6ab | ||
|
|
0a546fc017 | ||
|
|
539fb43820 | ||
|
|
7d0966a6b7 | ||
|
|
3ce014c766 | ||
|
|
b5b22da8e6 | ||
|
|
ca734d17ad | ||
|
|
a3e60027e7 | ||
|
|
7d47ecd543 | ||
|
|
10c12171cc | ||
|
|
92e282288f | ||
|
|
21dbc6499a | ||
|
|
4794d88f15 | ||
|
|
a3a323e0e5 | ||
|
|
5e78edb350 | ||
|
|
73abd0a9d2 | ||
|
|
1de98301f6 | ||
|
|
e584d82fec | ||
|
|
244201f6ba | ||
|
|
d08fac3eb9 | ||
|
|
19e61097c5 | ||
|
|
d94ac406ba | ||
|
|
7cdc98b630 | ||
|
|
da47440d44 | ||
|
|
d8a717fb1a | ||
|
|
fc41345116 | ||
|
|
1a587ff843 | ||
|
|
13b3715925 | ||
|
|
0a802bd7a0 | ||
|
|
75fcab8c23 | ||
|
|
a375b077cc | ||
|
|
19408d83a4 | ||
|
|
33aba595b0 | ||
|
|
75b41de8ca | ||
|
|
b19c33d48a | ||
|
|
ea5b70924d | ||
|
|
13a49af44b | ||
|
|
1aa608fc68 | ||
|
|
9da6caac4e | ||
|
|
dc11daf3b5 | ||
|
|
5b12b47df9 | ||
|
|
00cf82c0bd | ||
|
|
cd8788f26d | ||
|
|
5a4f9f27e7 | ||
|
|
9f8ded8ced | ||
|
|
95506e4611 | ||
|
|
53eeeaff08 | ||
|
|
a36879293d | ||
|
|
670bf2fd36 | ||
|
|
b33ef40105 | ||
|
|
165346ca29 | ||
|
|
306c6a02e3 | ||
|
|
ef59a8bc9e | ||
|
|
7236fdb22f | ||
|
|
91504b6cbe | ||
|
|
26f3388d27 | ||
|
|
b04ce2a35c | ||
|
|
4fe83b554a | ||
|
|
ab17b909fe | ||
|
|
893819801d | ||
|
|
5d10923f7b | ||
|
|
704bd093be | ||
|
|
941c9fa285 | ||
|
|
9f1ae9abee | ||
|
|
b4f2e412d0 | ||
|
|
1dd10ca233 | ||
|
|
144a5e72f2 | ||
|
|
fdc0bde935 | ||
|
|
4610b0332c | ||
|
|
ef27ba988b | ||
|
|
1a06bf93c4 | ||
|
|
e43505d6e9 | ||
|
|
b0d605c1d6 | ||
|
|
30d31cc8bb | ||
|
|
963238f713 | ||
|
|
136cf53f22 | ||
|
|
56e52b69fe | ||
|
|
50a9fad5d5 | ||
|
|
beb7756dcb | ||
|
|
b882056c93 | ||
|
|
5ca2f0c498 | ||
|
|
5171777bf1 | ||
|
|
a4f3f93b90 | ||
|
|
34a719d250 | ||
|
|
d433ebc681 | ||
|
|
25bb8a4452 | ||
|
|
192fc3376a | ||
|
|
2b00385d51 | ||
|
|
4c2e506a39 | ||
|
|
83f39c7ab2 | ||
|
|
f335779240 | ||
|
|
89f9f83769 | ||
|
|
3856f14759 | ||
|
|
412e457c27 | ||
|
|
1d18f50458 | ||
|
|
0997b4119d | ||
|
|
e984050fb9 | ||
|
|
e13865033d | ||
|
|
3c23ee1ba2 | ||
|
|
bc63407be6 | ||
|
|
7f9a4db3c0 | ||
|
|
da14590bd9 | ||
|
|
155fb4b45e | ||
|
|
7f00cd2264 | ||
|
|
efc5d4cc0d | ||
|
|
106bd87a3c | ||
|
|
84403fe7c1 | ||
|
|
169d2ef1e0 | ||
|
|
abe18d0e00 | ||
|
|
9e83ac06ef | ||
|
|
556bdfbab9 | ||
|
|
b435f64510 | ||
|
|
49128f0b9d | ||
|
|
3ab203762f | ||
|
|
60361e5aac | ||
|
|
36c19d03e0 | ||
|
|
4a7893ca9c | ||
|
|
b70d355521 | ||
|
|
cd76bb651d | ||
|
|
2403fc9fde | ||
|
|
0537e7bf80 | ||
|
|
73db2acc37 | ||
|
|
eddb388ef9 | ||
|
|
a047bf52a4 | ||
|
|
dbebd395eb | ||
|
|
ec96ef47a6 | ||
|
|
f85202c3dc | ||
|
|
d320764706 | ||
|
|
f7f12a606c | ||
|
|
fc09b9f449 | ||
|
|
08f55b786b | ||
|
|
7b30cc3a7b | ||
|
|
1eb13e4913 | ||
|
|
57b0738011 | ||
|
|
b301c1fc2b | ||
|
|
834ef78e27 | ||
|
|
fb92ecece7 | ||
|
|
8079656a8e | ||
|
|
93b2a7718e | ||
|
|
e0e1c19520 | ||
|
|
8fedd04dcc | ||
|
|
ed0dd55402 | ||
|
|
36babc1e31 | ||
|
|
2f6b31c3bb | ||
|
|
e5f257c2b2 | ||
|
|
c1ae36aea8 | ||
|
|
09252fc4f3 | ||
|
|
357d36733d | ||
|
|
2df84b7269 | ||
|
|
ec7e138926 | ||
|
|
23c64fe50d | ||
|
|
588cd980e9 | ||
|
|
452ccf7a41 | ||
|
|
e4ecc93119 | ||
|
|
4e0d34089a | ||
|
|
94364d53b4 | ||
|
|
e59a64b8d3 | ||
|
|
2929ca9786 | ||
|
|
bad7d097e9 | ||
|
|
5f19e8a4a5 | ||
|
|
740eee1cc6 | ||
|
|
674492bceb | ||
|
|
acc75bfd4f | ||
|
|
f9d5c4e33c | ||
|
|
76864d5626 | ||
|
|
b4b62c34db | ||
|
|
f791d66e20 | ||
|
|
f9b252613b | ||
|
|
ae77016e57 | ||
|
|
5d15b5207d | ||
|
|
4b93e5e267 | ||
|
|
c0b3d42372 | ||
|
|
b37ca6edb1 | ||
|
|
695fe87456 | ||
|
|
b91d605a35 | ||
|
|
778b2210a9 | ||
|
|
e452436a07 | ||
|
|
01daeecee7 | ||
|
|
a679185aa5 | ||
|
|
e193873025 | ||
|
|
c91abdcd07 | ||
|
|
cb72d0f155 | ||
|
|
029e5577ff | ||
|
|
2b9a59471f | ||
|
|
ced6709cb9 | ||
|
|
5d4f3a8c85 | ||
|
|
a53ebb4c8e | ||
|
|
87c74dfaa8 | ||
|
|
aaada3f962 | ||
|
|
c470538142 | ||
|
|
cbce517145 | ||
|
|
abb731e5b8 | ||
|
|
cf0c6b78f1 | ||
|
|
3a51bff035 | ||
|
|
123a10f7a5 | ||
|
|
07242c0ccf | ||
|
|
0439d792c4 | ||
|
|
f2dd4769dd | ||
|
|
69ef89f2cf | ||
|
|
2159782b51 | ||
|
|
1fb4e8def7 | ||
|
|
a539d29ac5 | ||
|
|
6146329f1f | ||
|
|
a5639e7dd9 | ||
|
|
23d7298b15 | ||
|
|
2c572622fb | ||
|
|
0cf5b7b57c | ||
|
|
c25a5d3b10 | ||
|
|
bcf2995c98 | ||
|
|
7e91f971f7 | ||
|
|
c61e69257a | ||
|
|
6825491928 | ||
|
|
0c7a9dec65 | ||
|
|
673e1fb7e4 | ||
|
|
d9e28bcf00 | ||
|
|
715c801a07 | ||
|
|
86d392498b | ||
|
|
cbf7e1070a | ||
|
|
8c1420ff2a | ||
|
|
a0573260b0 | ||
|
|
6171250871 | ||
|
|
db0c43a70c | ||
|
|
dd3f505ff5 | ||
|
|
f99e660b44 | ||
|
|
80091bbaac | ||
|
|
e0be2289e9 | ||
|
|
b3aa6d712e | ||
|
|
399f2f401c | ||
|
|
d9dd352b36 | ||
|
|
b1e0bd4b9b | ||
|
|
e39c8b5047 | ||
|
|
230729617d | ||
|
|
c8a10631d1 | ||
|
|
2f996f0217 | ||
|
|
e002ec6801 | ||
|
|
cdbea20afb | ||
|
|
0e5441d816 | ||
|
|
1a71fb9669 | ||
|
|
2cffe2993b | ||
|
|
5b4bff9c17 | ||
|
|
e51a74aa40 | ||
|
|
99628c1af8 | ||
|
|
185cabda45 | ||
|
|
443d1e9efe | ||
|
|
acc0185493 | ||
|
|
f26dde8ca9 | ||
|
|
b056ca39f2 | ||
|
|
8b654e54c4 | ||
|
|
d0a4e24e32 | ||
|
|
70a214c46d | ||
|
|
d06a5b23c8 | ||
|
|
b67d234563 | ||
|
|
b7a8daef60 | ||
|
|
2767f1c0c6 | ||
|
|
96ffb2fe97 | ||
|
|
3688d7f308 | ||
|
|
d3b958c3fc | ||
|
|
5bd09cd4c5 | ||
|
|
daf2049d0d | ||
|
|
af4e866dbb | ||
|
|
0f49d6b049 | ||
|
|
bb8716b6dc | ||
|
|
2c24c22039 | ||
|
|
24110a4d7d | ||
|
|
fb829ec6a2 | ||
|
|
e2641e09cc | ||
|
|
b8b8501d70 | ||
|
|
f483ce5ffe | ||
|
|
c2ff0e90b8 | ||
|
|
4589a823fd | ||
|
|
676740a960 | ||
|
|
5eedc9c65e | ||
|
|
4774a53b24 | ||
|
|
184d74abc6 | ||
|
|
d5096a28d7 | ||
|
|
d52e588869 | ||
|
|
7e79de541a | ||
|
|
7d04fc7563 | ||
|
|
08b5920750 | ||
|
|
3042fb0537 | ||
|
|
7c4fc71c15 | ||
|
|
400aea2b13 | ||
|
|
273f616930 | ||
|
|
ab37269c38 | ||
|
|
bb039e853d | ||
|
|
70ff3511bc | ||
|
|
a8dca69bb3 | ||
|
|
b978abbf02 | ||
|
|
d0b58d5300 | ||
|
|
f6475c7250 | ||
|
|
ac9b8cfe57 | ||
|
|
e24d93762f | ||
|
|
35cabcb505 | ||
|
|
7d288d6547 | ||
|
|
23d3a5feef | ||
|
|
3ab98cef4e | ||
|
|
144b0094c3 | ||
|
|
70b4b320ae | ||
|
|
244b873b0c | ||
|
|
bcfb387694 | ||
|
|
1240552da9 | ||
|
|
279d7e67cf | ||
|
|
0e1684bcd0 | ||
|
|
dedff272f6 | ||
|
|
306974f5d7 | ||
|
|
4e16d8b312 | ||
|
|
829137b9a0 | ||
|
|
056c19c6f2 | ||
|
|
b785b2bf66 | ||
|
|
65cc766581 | ||
|
|
cd627d4e78 | ||
|
|
6b0253fabd | ||
|
|
a26452263f | ||
|
|
ab193fe452 | ||
|
|
d4507ec615 | ||
|
|
d1578a33ee | ||
|
|
422cf21f15 | ||
|
|
4dc1218c58 | ||
|
|
dda20542ab | ||
|
|
003f0840ff | ||
|
|
d0686e070d | ||
|
|
846d8b3ea5 | ||
|
|
22194a7ffe | ||
|
|
4c8f23700b | ||
|
|
44262c58a4 | ||
|
|
0924181364 | ||
|
|
178d690372 | ||
|
|
74e0f445a8 | ||
|
|
9fcfd6b651 | ||
|
|
a7159fe817 | ||
|
|
5a9fcb87ca | ||
|
|
afbf59145a | ||
|
|
f6fa411d6c | ||
|
|
6b6f101c27 | ||
|
|
5713f06b33 | ||
|
|
7a6ae0a2b2 | ||
|
|
7f7499eeac | ||
|
|
73bd6c583b | ||
|
|
6e0e5bedd9 | ||
|
|
9e5d2e8bd6 | ||
|
|
38273a9ed6 | ||
|
|
436f18b618 | ||
|
|
1edbae8678 | ||
|
|
a89b7013ff | ||
|
|
7e02fe32d3 | ||
|
|
169dd6b7d3 | ||
|
|
e1f93d4b2c | ||
|
|
ffc1585267 | ||
|
|
b84186ff2d | ||
|
|
612e4de8ce | ||
|
|
356f923913 | ||
|
|
dbc289aed1 | ||
|
|
4e538759c4 | ||
|
|
a4798f733d | ||
|
|
e4ed181d40 | ||
|
|
6ddc908ab6 | ||
|
|
9eaef89fbc | ||
|
|
dc845730a9 | ||
|
|
d71b98650f | ||
|
|
a03611e133 | ||
|
|
1cd92e7f04 | ||
|
|
7dd8e7cfdf | ||
|
|
23f964946b | ||
|
|
b6eb970394 | ||
|
|
2796f6da7b | ||
|
|
0f62e1775d | ||
|
|
be02a7c0d6 | ||
|
|
6a8e35ad92 | ||
|
|
8632fb3040 | ||
|
|
560db612e1 | ||
|
|
752203d86d | ||
|
|
3fbf9001ce | ||
|
|
bd8db0ada8 | ||
|
|
dbaa41c618 | ||
|
|
d2ee16abec | ||
|
|
9ae6b0be4a | ||
|
|
a6dd455b09 | ||
|
|
697bd5673f | ||
|
|
d72562f7ba | ||
|
|
c7d9d662a4 | ||
|
|
0f3dfa87bc | ||
|
|
033fb554be | ||
|
|
177a0a0b0d | ||
|
|
c03206fdf1 | ||
|
|
a24ba809d1 | ||
|
|
7b1f85c0a2 | ||
|
|
1ce81fa59b | ||
|
|
6435c76772 | ||
|
|
0c0d056412 | ||
|
|
bb57b965c4 | ||
|
|
03e52931dd | ||
|
|
c8d9e7f4c0 | ||
|
|
d593c48869 | ||
|
|
a5456b2cb6 | ||
|
|
dcb9cf4e82 | ||
|
|
fc2c0f7a6c | ||
|
|
4812cf2873 | ||
|
|
c4aace9003 | ||
|
|
6205b46387 | ||
|
|
aa549962a4 | ||
|
|
c7019f7019 | ||
|
|
c09c2c3b04 | ||
|
|
75d8978e7a | ||
|
|
37fff074a2 | ||
|
|
af5f66fb98 | ||
|
|
29b14d5fac | ||
|
|
f6eb17477a | ||
|
|
0f10458c4f | ||
|
|
924727d905 | ||
|
|
ba5b4bde21 | ||
|
|
779deb60f9 | ||
|
|
33c1269efc | ||
|
|
335d16bc0f | ||
|
|
08253bf42b | ||
|
|
11ac6ff613 | ||
|
|
3d04d29e7f | ||
|
|
d55d5c5dd3 | ||
|
|
1a132bbc9c | ||
|
|
3caf15e6da | ||
|
|
5fc9229c34 | ||
|
|
d5d23dabd7 | ||
|
|
b0bd87f60c | ||
|
|
38db91713c | ||
|
|
8a3b0d2d9a | ||
|
|
ea2c08e922 | ||
|
|
ca3f830b32 | ||
|
|
4ea93ad3ac | ||
|
|
a2f4f8711a | ||
|
|
37dc9e5abb | ||
|
|
90fdc82697 | ||
|
|
1ad4d31631 | ||
|
|
6531c94d28 | ||
|
|
bc000c1db0 | ||
|
|
9005896c0a | ||
|
|
b167f87705 | ||
|
|
cef34df02a | ||
|
|
9b30e1a207 | ||
|
|
c20c189db5 | ||
|
|
87c68815c3 | ||
|
|
37ab76c99d | ||
|
|
75a190ca72 | ||
|
|
fda32cb601 | ||
|
|
329cdba00a | ||
|
|
7fc9853339 | ||
|
|
be00fa244d | ||
|
|
89328c9244 | ||
|
|
c22b2ec863 | ||
|
|
2f4dd7e0fd | ||
|
|
dcb518fd16 | ||
|
|
c4669d257f | ||
|
|
4993adbaaf | ||
|
|
fd80abda08 | ||
|
|
10dea8dc1b | ||
|
|
834c72fa57 | ||
|
|
cbae1d34f6 | ||
|
|
6185fd3532 | ||
|
|
f166bb1d50 | ||
|
|
8513a757ab | ||
|
|
fab43727b4 | ||
|
|
c14753cea1 | ||
|
|
9b8834158b | ||
|
|
d6d3f92fb0 | ||
|
|
b36d1c3091 | ||
|
|
469c4e45c3 | ||
|
|
53cbf66caf | ||
|
|
46b614edf7 | ||
|
|
30dd89b6b7 | ||
|
|
2e5eb04e50 | ||
|
|
c7dadd1513 | ||
|
|
274e45e365 | ||
|
|
a42453a593 | ||
|
|
9caf1a2043 | ||
|
|
aed57a31af | ||
|
|
73287b2b57 | ||
|
|
5436146c8d | ||
|
|
26ef09a835 | ||
|
|
9e08e1b9ed | ||
|
|
482b672df1 | ||
|
|
d6f4c262a1 | ||
|
|
fdfb02e7ff | ||
|
|
0f3010ab0d | ||
|
|
76a224700d | ||
|
|
b78fd80f1e | ||
|
|
3863d8ea81 | ||
|
|
3be2c9d732 | ||
|
|
85ecc65edc | ||
|
|
1c4114be40 | ||
|
|
9cf9e6f197 | ||
|
|
4d16bb253c | ||
|
|
8ba4004934 | ||
|
|
5d373da96a | ||
|
|
ab72b4833d | ||
|
|
4786851152 | ||
|
|
1488645e15 | ||
|
|
628e1c6910 | ||
|
|
239515bcce | ||
|
|
4fb6d00c97 | ||
|
|
47bebf15fa | ||
|
|
35453d992f | ||
|
|
98578b5704 | ||
|
|
758b6d4c27 | ||
|
|
36e5db6d24 | ||
|
|
a57d9cc437 | ||
|
|
ba798261ce | ||
|
|
b58ba10526 | ||
|
|
7a121e60f2 | ||
|
|
9682a58fca | ||
|
|
9376e434f0 | ||
|
|
32a6651355 | ||
|
|
fb82e75cd1 | ||
|
|
88e8d89f1e | ||
|
|
fe24458996 | ||
|
|
5107436cdf | ||
|
|
128e89dde2 | ||
|
|
8c096b16a1 | ||
|
|
418807d2d5 | ||
|
|
abce5627d7 | ||
|
|
6485f29382 | ||
|
|
ad30aa6069 | ||
|
|
bd59ecfb92 | ||
|
|
ee14da56e6 | ||
|
|
e5b7a215e8 | ||
|
|
bf02809825 | ||
|
|
dc05abde89 | ||
|
|
1b67773229 | ||
|
|
a34e0a2574 | ||
|
|
89e689c592 | ||
|
|
45ae650129 | ||
|
|
585af7e21c | ||
|
|
242a64f3d6 | ||
|
|
3350558346 | ||
|
|
59305dc7a9 | ||
|
|
3317c67971 | ||
|
|
0a6f3f0f8a | ||
|
|
3805e04f78 | ||
|
|
739ba0d211 | ||
|
|
ca1788b560 | ||
|
|
6f07874621 | ||
|
|
f3b52411db | ||
|
|
f4f06efccc | ||
|
|
a0e7e5f516 | ||
|
|
8b5bb414f1 | ||
|
|
f424d5f398 | ||
|
|
4132ad8d49 | ||
|
|
8063b99da6 | ||
|
|
98d2e23be2 | ||
|
|
946342c190 | ||
|
|
28ed1f33ee | ||
|
|
029245feac | ||
|
|
81330149f8 | ||
|
|
e51fa063db | ||
|
|
57ca68acc1 | ||
|
|
e795c75888 | ||
|
|
cb8ae3c889 | ||
|
|
b4bd05241d | ||
|
|
2cf5e5136b | ||
|
|
af090ffa52 | ||
|
|
48d729a7d9 | ||
|
|
10ce12761a | ||
|
|
3c5caedc69 | ||
|
|
a737ada1e5 | ||
|
|
7aaaad5070 | ||
|
|
dd142b9cd7 | ||
|
|
f40b035d7b | ||
|
|
0ce7679849 | ||
|
|
5bdb384ff0 | ||
|
|
8ff6a48b99 | ||
|
|
8f63ddca00 | ||
|
|
cbb87f7f8a | ||
|
|
249c3a7dd1 | ||
|
|
095e805e89 | ||
|
|
21cdc9f064 | ||
|
|
a88a2af6c5 | ||
|
|
526d00a572 | ||
|
|
c8d0ea0ef1 | ||
|
|
7accafbbe2 | ||
|
|
ffcc5608aa | ||
|
|
34b8a559a8 | ||
|
|
4e5240af64 | ||
|
|
723240057a | ||
|
|
2b61932933 | ||
|
|
dc4be23ec8 | ||
|
|
24df76987e | ||
|
|
3bb225d66b | ||
|
|
d6fb4d5396 | ||
|
|
0040fa253f | ||
|
|
a08bb01d67 | ||
|
|
188714211a | ||
|
|
122c049834 | ||
|
|
566c3c7a22 | ||
|
|
d3277ecd92 | ||
|
|
66c8853f9c | ||
|
|
7ccd2d0a5f | ||
|
|
f1a930bbb7 | ||
|
|
08ee9b570f | ||
|
|
177727542c | ||
|
|
682c73e87e | ||
|
|
bd79a6bda0 | ||
|
|
4e8dbaf463 | ||
|
|
f83c6cb5eb | ||
|
|
3f973463a7 | ||
|
|
edc2f63ad9 | ||
|
|
97224de7f9 | ||
|
|
c651fd9ee3 | ||
|
|
55017f9da0 | ||
|
|
4bfcbab8b2 | ||
|
|
c4e8313231 | ||
|
|
b4eb9ccdcb | ||
|
|
b6f07345c9 | ||
|
|
705dad386d | ||
|
|
a3f3af860b | ||
|
|
c44d3b56df | ||
|
|
c811bb381d | ||
|
|
feb8d7e6a6 | ||
|
|
58e1c9c143 | ||
|
|
d4dd6556c3 | ||
|
|
16fa22f12b | ||
|
|
6d7d1370c3 | ||
|
|
6e9e463f80 | ||
|
|
0c390abc69 | ||
|
|
1f1c769518 | ||
|
|
7fb16bacae | ||
|
|
a71f072f73 | ||
|
|
bc55456975 | ||
|
|
fb765a0ae3 | ||
|
|
8ca3e9d10b | ||
|
|
5413c40da7 | ||
|
|
e6cca5dba6 | ||
|
|
9a22de8270 | ||
|
|
b60bace9f9 | ||
|
|
bbe025e04b | ||
|
|
e0a62c7fdb | ||
|
|
4e27f26863 | ||
|
|
09aeb5790f | ||
|
|
d33278d160 | ||
|
|
05df762175 | ||
|
|
dae121d9aa | ||
|
|
10d91d7fb3 | ||
|
|
bdcb92f273 | ||
|
|
78ebe4c88f | ||
|
|
d9eaa43a02 | ||
|
|
884d4b39d4 | ||
|
|
5727b9aa94 | ||
|
|
10c2baa50a | ||
|
|
6fcb180039 | ||
|
|
454eea7c44 | ||
|
|
686bc0ac5c | ||
|
|
aa7c29340f | ||
|
|
5e26ae88b8 | ||
|
|
4005fef124 | ||
|
|
8bca8773b4 | ||
|
|
14ae26d89d | ||
|
|
e9484a8502 | ||
|
|
da2cfe8a3b | ||
|
|
bfded2aa51 | ||
|
|
8c6700720d | ||
|
|
ffc6b7f864 | ||
|
|
06278a6758 | ||
|
|
958cd5f394 | ||
|
|
d6cc8867b7 | ||
|
|
ff767a7580 | ||
|
|
201037f562 | ||
|
|
de450ee9e5 | ||
|
|
fc46bb71ae | ||
|
|
befec3cd91 | ||
|
|
cac154c580 | ||
|
|
3819207933 | ||
|
|
9e071b4bf4 | ||
|
|
43078ff844 | ||
|
|
570e43c828 | ||
|
|
53476b82a1 | ||
|
|
01426b054c | ||
|
|
168ac5c6e3 | ||
|
|
248ea31003 | ||
|
|
b61a28fe35 | ||
|
|
500ece7c17 | ||
|
|
bc86d88e05 | ||
|
|
e940927314 | ||
|
|
2a6a2ed102 | ||
|
|
1763929f23 | ||
|
|
cf87ebf22d | ||
|
|
aab055ae1f | ||
|
|
d36c4e97e5 | ||
|
|
3ea27d37d1 | ||
|
|
44efe66ef2 | ||
|
|
7bf90179a3 | ||
|
|
5e2022bb48 | ||
|
|
42ab0172ab | ||
|
|
723fb69b9e | ||
|
|
6bccf64a5e |
+9
-1
@@ -1,4 +1,3 @@
|
||||
00-RELEASENOTES
|
||||
*.o
|
||||
*.rdb
|
||||
*.log
|
||||
@@ -6,8 +5,17 @@ redis-cli
|
||||
redis-server
|
||||
redis-benchmark
|
||||
redis-check-dump
|
||||
redis-check-aof
|
||||
doc-tools
|
||||
mkrelease.sh
|
||||
release
|
||||
myredis.conf
|
||||
misc/*
|
||||
src/release.h
|
||||
appendonly.aof
|
||||
SHORT_TERM_TODO
|
||||
redis.conf.*
|
||||
release.h
|
||||
src/transfer.sh
|
||||
src/configs
|
||||
src/redis-server.dSYM
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
Redis 2.2 release notes
|
||||
|
||||
Migrating from 2.0 to 2.2
|
||||
=========================
|
||||
|
||||
Redis 2.0 is mostly a strict subset of 2.2. Some return value changed in edge
|
||||
cases, basicaly it is very unlikely that you will experience any problem
|
||||
upgrading your 2.0 instances to 2.2, as 2.2 can work as a drop in replacement
|
||||
for 2.0.
|
||||
|
||||
---------
|
||||
CHANGELOG
|
||||
---------
|
||||
|
||||
What's new in Redis 2.2.3
|
||||
=========================
|
||||
|
||||
* Fixed issue #503. MONITOR + QUIT (and other combinations) could crash
|
||||
the server.
|
||||
* OBJECT command implemented. See http://redis.io/commands/object
|
||||
* Fixed a problem in redis-cli related to escapes in the form "\x..".
|
||||
* Fixed a minor memory leak in redis-cli
|
||||
* Saved RDB on SIGTERM on archs where it was not working properly.
|
||||
|
||||
What's new in Redis 2.2.2
|
||||
=========================
|
||||
|
||||
Redis 2.2.2 is a bugfix release. Changelog:
|
||||
|
||||
* AOF file descriptor leak after the first rewrite fixed.
|
||||
* Return value of GETRANGE / SUBSTR is now an empty string if the interval
|
||||
specified is not valid, or the key does not exist. Was a null bulk reply
|
||||
before, but this is against the Redis way.
|
||||
|
||||
What's new in Redis 2.2.1
|
||||
=========================
|
||||
|
||||
Redis 2.2.1 is a bugfix release. Changelog:
|
||||
|
||||
* Fixed an SPOP crash. When using SPOP in a MULTI/EXEC block there was a problem
|
||||
introduced in the latest release when fixing an SPOP replication/AOF related
|
||||
bug.
|
||||
|
||||
What's new in Redis 2.2.0 final
|
||||
===============================
|
||||
|
||||
Redis 2.2.0 final is both a bug fix and minor enhancement release:
|
||||
|
||||
* SPOP is now replicated correctly in AOF and across slaves.
|
||||
* CONFIG SET/GET for all the special encoding parameters of sets, lists, hashes.
|
||||
* Now BRPOPLPUSH will reply with single null bulk on timeout.
|
||||
* Specifying port 0 in redis.conf will tell Redis to don't listen on TCP socket.
|
||||
* Propagate key eviction to slaves and AOF, similarly to expires.
|
||||
|
||||
What's new in Redis 2.1.10 (2.2 Release Candidate 4)
|
||||
====================================================
|
||||
|
||||
Redis 2.2 RC4 is a bug fix and minor enhancement release:
|
||||
|
||||
* Fixed timeout error in replication where master took a big time to BGSAVE.
|
||||
* Introduced explicit PING between master and slave, to reliably detect when
|
||||
the link is down, even if the socket remains apparently connected.
|
||||
* Fixed compilation on FreeBSD.
|
||||
* Removed a small portability issue in redis-benchmark.
|
||||
|
||||
What's new in Redis 2.1.10 (2.2 Release Candidate 3)
|
||||
====================================================
|
||||
|
||||
Redis 2.2 RC3 is a bug fix and minor enhancements release:
|
||||
|
||||
* Solaris fixes
|
||||
* Fixes and improvements for redis-benchmark
|
||||
* New INFO field with memory allocation details
|
||||
* New INFO fields with info about clients max input/output buffer
|
||||
* Replication: KEYS fixed in slaves
|
||||
* Different default thresholds for Hash type memory saving encodings
|
||||
|
||||
What's new in Redis 2.1.10 (2.2 Release Candidate 2)
|
||||
====================================================
|
||||
|
||||
Redis 2.2 RC2 is exactly like RC1 with the following minor changes:
|
||||
|
||||
* Added evicted keys counter separated from expired keys.
|
||||
* Overflow detection in INCR family functions.
|
||||
|
||||
Enjoy,
|
||||
Salvatore
|
||||
|
||||
What's new in Redis 2.1.9 (2.2 Release Candidate 1)
|
||||
===================================================
|
||||
|
||||
This is the first Release Candidate of Redis 2.2, in our experience the
|
||||
server is very stable, but in the latest weeks we rewrote part of the internals
|
||||
in order to use a lot less memory while the saving child process is performing
|
||||
a BGREWRITEAOF or a BGSAVE, so handle with care for a couple of weeks.
|
||||
|
||||
Oh, and I've some very good news: the majority of apps can work if you simply replace 2.2 in your old 2.0 environment. I can't think of any breakage.
|
||||
|
||||
WHAT'S NEW IN REDIS 2.2 compared to the 2.0 version?
|
||||
====================================================
|
||||
|
||||
* Specially encoded data types, small lists and sets can now use up to an order of magnitude less memory.
|
||||
* VM partial rewrite for code cleaness and memory usage.
|
||||
* Change to the implementation of the top level dictionary for better memory efficienty.
|
||||
* redis-cli is hugely improved: tab completion, inline help (Thanks to TJ Holowaychuk), raw output, rewritten using the new hiredis C library.
|
||||
* Networking internals rewritten for efficiency. You can expect LRANGE and similar commands to be at least 10 times faster.
|
||||
* Most read only commands are now copy-on-write friendly, this means that Redis will use little memory when a saving child is active and the parent process is mostly stressed by read queries.
|
||||
* Non blocking replication even from the point of view of the slave, with configurable behavior about what to do when the link is disconnected. You can select if serving old data or replying with an error.
|
||||
* Check-and-set (CAS) transactions with the new WATCH command.
|
||||
* Now write operations work against keys with an EXPIRE set! Imagine the possibilities.
|
||||
* New maxmemory eviction policies. It is possible to select among LRU, farest TTL expire, and other algorithms, and if when the memory limit is reached only keys with an expire set or all the keys should be expired.
|
||||
* SETBIT / GETBIT / SETRANGE / GETRANGE / STRLEN. Now your strings are your arrays!
|
||||
* Syslog support (Thanks to Jonah H. Harris)
|
||||
* Unix domain socket support.
|
||||
* New List related functions LINSERT, LPUSHX, RPUSHX (Thanks to Robey Pointer)
|
||||
* BRPOPLPUSH (Thanks to Michel Martens and Damian Janowski)
|
||||
* Much more interesting informations in the INFO output.
|
||||
* Sorted sets are now less memory hungry.
|
||||
* Non blocking loading of .rdb / AOF file on startup, with progress information in the INFO output.
|
||||
* Now Redis has a clean, powerful, supported C library: hiredis.
|
||||
* Code layout completely new, the 2.0.x huge redis.c file is now splitted in many parts.
|
||||
* Redis-benchmark rewritten to be faster and in order to use hiredis as well.
|
||||
* Ability to rename or disable commands from the config file.
|
||||
* Endless other CPU optimizations and bugs fixed.
|
||||
|
||||
Credits: Where not specified the implementation and design are done by Salvatore Sanfilippo and Pieter Noordhuis. Thanks to VMware for making all this possible. Also many thanks to all the other contributors and the amazing community we have.
|
||||
|
||||
Cheers,
|
||||
Salvatore
|
||||
@@ -1,9 +0,0 @@
|
||||
This is a stable release, for beta testing make sure to download the latest source code from Git:
|
||||
|
||||
git clone git://github.com/antirez/redis.git
|
||||
|
||||
It's also possibe to download the latest source code as a tarball:
|
||||
|
||||
http://github.com/antirez/redis/tree/master
|
||||
|
||||
(use the download button)
|
||||
@@ -0,0 +1,13 @@
|
||||
1. Enter irc.freenode.org #redis and start talking with 'antirez' and/or 'pietern' to check if there is interest for such a feature and to understand the probability of it being merged. We'll try hard to keep Redis simple... so you'll likely encounter an high resistence.
|
||||
|
||||
2. Drop a message to the Redis Google Group with a proposal of semantics/API.
|
||||
|
||||
3. If steps 1 and 2 are ok, use the following procedure to submit a patch:
|
||||
|
||||
a. Fork Redis on github
|
||||
b. Create a topic branch (git checkout -b my_branch)
|
||||
c. Push to your branch (git push origin my_branch)
|
||||
d. Create an issue in the Redis google code site with a link to your patch
|
||||
e. Done :)
|
||||
|
||||
Thanks!
|
||||
@@ -0,0 +1,30 @@
|
||||
To compile Redis, do the following:
|
||||
|
||||
cd src; make
|
||||
|
||||
The compilation will produce a redis-server binary.
|
||||
|
||||
To install Redis, use
|
||||
|
||||
make install
|
||||
|
||||
and all the binaries will be installed on /usr/local/bin.
|
||||
|
||||
Alternatively:
|
||||
|
||||
make PREFIX=/some/other/directory
|
||||
|
||||
to have the binaries in /some/other/directory/bin.
|
||||
|
||||
Run the server using the following command line:
|
||||
|
||||
/path/to/redis-server
|
||||
|
||||
This will start a Redis server with the default configuration.
|
||||
|
||||
Otherwise if you want to provide your configuration use:
|
||||
|
||||
/path/to/redis-server /path/to/redis.conf
|
||||
|
||||
You can find an example redis.conf file in the root directory
|
||||
of this source distribution.
|
||||
@@ -1,103 +1,22 @@
|
||||
# Redis Makefile
|
||||
# Copyright (C) 2009 Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
# This file is released under the BSD license, see the COPYING file
|
||||
# Top level makefile, the real shit is at src/Makefile
|
||||
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
OPTIMIZATION?=-O2
|
||||
ifeq ($(uname_S),SunOS)
|
||||
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -Wall -W -D__EXTENSIONS__ -D_XPG6
|
||||
CCLINK?= -ldl -lnsl -lsocket -lm -lpthread
|
||||
else
|
||||
CFLAGS?= -std=c99 -pedantic $(OPTIMIZATION) -Wall -W $(ARCH) $(PROF)
|
||||
CCLINK?= -lm -pthread
|
||||
endif
|
||||
CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
|
||||
DEBUG?= -g -rdynamic -ggdb
|
||||
TARGETS=32bit noopt test
|
||||
|
||||
OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o
|
||||
BENCHOBJ = ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o
|
||||
CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o
|
||||
CHECKDUMPOBJ = redis-check-dump.o lzf_c.o lzf_d.o
|
||||
all:
|
||||
cd src && $(MAKE) $@
|
||||
|
||||
PRGNAME = redis-server
|
||||
BENCHPRGNAME = redis-benchmark
|
||||
CLIPRGNAME = redis-cli
|
||||
CHECKDUMPPRGNAME = redis-check-dump
|
||||
|
||||
all: redis-server redis-benchmark redis-cli redis-check-dump
|
||||
|
||||
# Deps (use make dep to generate this)
|
||||
adlist.o: adlist.c adlist.h zmalloc.h
|
||||
ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c
|
||||
ae_epoll.o: ae_epoll.c
|
||||
ae_kqueue.o: ae_kqueue.c
|
||||
ae_select.o: ae_select.c
|
||||
anet.o: anet.c fmacros.h anet.h
|
||||
dict.o: dict.c fmacros.h dict.h zmalloc.h
|
||||
lzf_c.o: lzf_c.c lzfP.h
|
||||
lzf_d.o: lzf_d.c lzfP.h
|
||||
pqsort.o: pqsort.c
|
||||
redis-benchmark.o: redis-benchmark.c fmacros.h ae.h anet.h sds.h adlist.h \
|
||||
zmalloc.h
|
||||
redis-cli.o: redis-cli.c fmacros.h anet.h sds.h adlist.h zmalloc.h
|
||||
redis.o: redis.c fmacros.h config.h redis.h ae.h sds.h anet.h dict.h \
|
||||
adlist.h zmalloc.h lzf.h pqsort.h zipmap.h staticsymbols.h
|
||||
sds.o: sds.c sds.h zmalloc.h
|
||||
zipmap.o: zipmap.c zmalloc.h
|
||||
zmalloc.o: zmalloc.c config.h
|
||||
|
||||
redis-server: $(OBJ)
|
||||
$(CC) -o $(PRGNAME) $(CCOPT) $(DEBUG) $(OBJ)
|
||||
@echo ""
|
||||
@echo "Hint: To run the test-redis.tcl script is a good idea."
|
||||
@echo "Launch the redis server with ./redis-server, then in another"
|
||||
@echo "terminal window enter this directory and run 'make test'."
|
||||
@echo ""
|
||||
|
||||
redis-benchmark: $(BENCHOBJ)
|
||||
$(CC) -o $(BENCHPRGNAME) $(CCOPT) $(DEBUG) $(BENCHOBJ)
|
||||
|
||||
redis-cli: $(CLIOBJ)
|
||||
$(CC) -o $(CLIPRGNAME) $(CCOPT) $(DEBUG) $(CLIOBJ)
|
||||
|
||||
redis-check-dump: $(CHECKDUMPOBJ)
|
||||
$(CC) -o $(CHECKDUMPPRGNAME) $(CCOPT) $(DEBUG) $(CHECKDUMPOBJ)
|
||||
|
||||
.c.o:
|
||||
$(CC) -c $(CFLAGS) $(DEBUG) $(COMPILE_TIME) $<
|
||||
install: dummy
|
||||
cd src && $(MAKE) $@
|
||||
|
||||
clean:
|
||||
rm -rf $(PRGNAME) $(BENCHPRGNAME) $(CLIPRGNAME) $(CHECKDUMPPRGNAME) *.o *.gcda *.gcno *.gcov
|
||||
cd src && $(MAKE) $@
|
||||
cd deps/hiredis && $(MAKE) $@
|
||||
cd deps/linenoise && $(MAKE) $@
|
||||
|
||||
dep:
|
||||
$(CC) -MM *.c
|
||||
$(TARGETS):
|
||||
cd src && $(MAKE) $@
|
||||
|
||||
staticsymbols:
|
||||
tclsh utils/build-static-symbols.tcl > staticsymbols.h
|
||||
src/help.h:
|
||||
@./utils/generate-command-help.rb > $@
|
||||
|
||||
test:
|
||||
tclsh test-redis.tcl
|
||||
|
||||
bench:
|
||||
./redis-benchmark
|
||||
|
||||
log:
|
||||
git log '--pretty=format:%ad %s' --date=short > Changelog
|
||||
|
||||
32bit:
|
||||
@echo ""
|
||||
@echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386"
|
||||
@echo ""
|
||||
make ARCH="-m32"
|
||||
|
||||
gprof:
|
||||
make PROF="-pg"
|
||||
|
||||
gcov:
|
||||
make PROF="-fprofile-arcs -ftest-coverage"
|
||||
|
||||
noopt:
|
||||
make OPTIMIZATION=""
|
||||
|
||||
32bitgprof:
|
||||
make PROF="-pg" ARCH="-arch i386"
|
||||
dummy:
|
||||
|
||||
@@ -1 +1,83 @@
|
||||
Check the 'doc' directory. doc/README.html is a good starting point :)
|
||||
Where to find complete Redis documentation?
|
||||
-------------------------------------------
|
||||
|
||||
This README is just a fast "quick start" document. You can find more detailed
|
||||
documentation at http://redis.io
|
||||
|
||||
Building Redis
|
||||
--------------
|
||||
|
||||
It is as simple as:
|
||||
|
||||
% make
|
||||
|
||||
Redis is just a single binary, but if you want to install it you can use
|
||||
the "make install" target that will copy the binary in /usr/local/bin
|
||||
for default. You can also use "make PREFIX=/some/other/directory install"
|
||||
if you wish to use a different destination.
|
||||
|
||||
You can run a 32 bit Redis binary using:
|
||||
|
||||
% make 32bit
|
||||
|
||||
After building Redis is a good idea to test it, using:
|
||||
|
||||
% make test
|
||||
|
||||
Buliding using tcmalloc
|
||||
-----------------------
|
||||
|
||||
tcmalloc is a fast and space efficient implementation (for little objects)
|
||||
of malloc(). Compiling Redis with it can improve performances and memeory
|
||||
usage. You can read more about it here:
|
||||
|
||||
http://goog-perftools.sourceforge.net/doc/tcmalloc.html
|
||||
|
||||
In order to compile Redis with tcmalloc support install tcmalloc on your system
|
||||
and then use:
|
||||
|
||||
% make USE_TCMALLOC=yes
|
||||
|
||||
Note that you can pass any other target to make, as long as you append
|
||||
USE_TCMALLOC=yes at the end.
|
||||
|
||||
Running Redis
|
||||
-------------
|
||||
|
||||
To run Redis with the default configuration just type:
|
||||
|
||||
% cd src
|
||||
% ./redis-server
|
||||
|
||||
If you want to provide your redis.conf, you have to run it using an additional
|
||||
parameter (the path of the configuration file):
|
||||
|
||||
% cd src
|
||||
% ./redis-server /path/to/redis.conf
|
||||
|
||||
Playing with Redis
|
||||
------------------
|
||||
|
||||
You can use redis-cli to play with Redis. Start a redis-server instance,
|
||||
then in another terminal try the following:
|
||||
|
||||
% cd src
|
||||
% ./redis-cli
|
||||
redis> ping
|
||||
PONG
|
||||
redis> set foo bar
|
||||
OK
|
||||
redis> get foo
|
||||
"bar"
|
||||
redis> incr mycounter
|
||||
(integer) 1
|
||||
redis> incr mycounter
|
||||
(integer) 2
|
||||
redis>
|
||||
|
||||
You can find the list of all the available commands here:
|
||||
|
||||
http://redis.io/commands
|
||||
|
||||
Enjoy!
|
||||
|
||||
|
||||
@@ -1,88 +1,4 @@
|
||||
Redis TODO and Roadmap
|
||||
This is a stable release! No TODO file here.
|
||||
Please check the TODO file in the master branch on github.
|
||||
|
||||
VERSION 2.0 TODO
|
||||
================
|
||||
|
||||
* BRPOPLPUSH
|
||||
* List ops like L/RPUSH L/RPOP should return the new list length.
|
||||
* Save dataset / fsync() on SIGTERM
|
||||
* MULTI/EXEC should support the "EXEC FSYNC" form?
|
||||
* BLPOP & C. tests (write a non blocking Tcl client as first step)
|
||||
* Once ZRANK is implemented, change the implementation of ZCOUNT to use the augmented skiplist in order to be much faster.
|
||||
* Write doc for ZCOUNT, and for open / closed intervals of sorted sets range operations.
|
||||
|
||||
Virtual Memory sub-TODO:
|
||||
* Check if the page selection algorithm is working well
|
||||
* Divide swappability of objects by refcount
|
||||
* Use multiple open FDs against the VM file, one for thread.
|
||||
* it should be possible to give the vm-max-memory option in megabyte, gigabyte, ..., just using 2GB, 100MB, and so forth.
|
||||
* Try to understand what can be moved into I/O threads that currently is instead handled by the main thread. For instance swapping file table scannig to find contiguous page could be a potential candidate (but I'm not convinced it's a good idea, better to improve the algorithm, for instance double the fast forward at every step?).
|
||||
* Possibly decrRefCount() against swapped objects can be moved into I/O threads, as it's a slow operation against million elements list, and in general consumes CPU time that can be consumed by other threads (and cores).
|
||||
* EXISTS should avoid loading the object if possible without too make the code too specialized.
|
||||
* vm-min-age <seconds> option
|
||||
* Make sure objects loaded from the VM are specially encoded when possible.
|
||||
* Check what happens performance-wise if instead to create threads again and again the same threads are reused forever. Note: this requires a way to disable this clients in the child, but waiting for empty new jobs queue can be enough.
|
||||
* Sets of integers are slow to load, for a number of reasons. Fix it. (use slow_sets.rdb file for debugging). (p.s. this was now partially fixed).
|
||||
* On EXEC try to block the client until relevant keys are loaded.
|
||||
|
||||
* Hashes (GET/SET/DEL/INCRBY/EXISTS/FIELDS/LEN/MSET/MGET). Special encoding for hashes with less than N elements.
|
||||
* Write documentation for APPEND
|
||||
* Implement LEN, PEEK, POKE, SETBIT, GETBIT
|
||||
|
||||
VERSION 2.2 TODO (Fault tolerant sharding)
|
||||
===========================================
|
||||
|
||||
* Redis-cluster, a fast intermediate layer (proxy) that implements consistent hashing and fault tollerant nodes handling.
|
||||
|
||||
Interesting readings about this:
|
||||
|
||||
- http://ayende.com/Blog/archive/2009/04/06/designing-rhino-dht-a-fault-tolerant-dynamically-distributed-hash.aspx
|
||||
|
||||
VERSION 2.4 TODO (Optimizations and latency)
|
||||
============================================
|
||||
|
||||
* Lower the CPU usage.
|
||||
* Lower the RAM usage everywhere possible.
|
||||
* Use epool and alike to rewrite ae.c for Linux and other platforms suppporting fater-than-select() mutiplexing APIs.
|
||||
* Implement an UDP interface for low-latency GET/SET operations.
|
||||
|
||||
OTHER IMPORTANT THINGS THAT WILL BE ADDED BUT I'M NOT SURE WHEN
|
||||
===============================================================
|
||||
|
||||
BIG ONES:
|
||||
|
||||
* Specially encoded memory-saving integer sets.
|
||||
* A command to export a JSON dump (there should be mostly working patch needing major reworking).
|
||||
* Specially encoded sets of integers (this includes a big refactoring providing an higher level layer for Sets manipulation)
|
||||
|
||||
SMALL ONES:
|
||||
|
||||
* If sizeof(double) == sizeof(void*) we could store the double value of sorted sets directly in place of the pointer instead of allocating it in the heap.
|
||||
* Delete on writes against expire policy should only happen after argument parsing for commands doing their own arg parsing stuff.
|
||||
* Give errors when incrementing a key that does not look like an integer, when providing as a sorted set score something can't be parsed as a double, and so forth.
|
||||
* MSADD (n keys) (n values). See this thread in the Redis google group: http://groups.google.com/group/redis-db/browse_thread/thread/e766d84eb375cd41
|
||||
* Don't save empty lists / sets / zsets on disk with snapshotting.
|
||||
* Remove keys when a list / set / zset reaches length of 0.
|
||||
* An option to exec a command slave-side if the master connection is lost: even cooler: if the script returns "0" the slave elects itself as master, otherwise continue trying to reconnect.
|
||||
|
||||
THE "MAYBE" TODO LIST: things that may or may not get implemented
|
||||
=================================================================
|
||||
|
||||
Most of this can be seen just as proposals, the fact they are in this list
|
||||
it's not a guarantee they'll ever get implemented ;)
|
||||
|
||||
* Move dict.c from hash table to skip list, in order to avoid the blocking resize operation needed for the hash table.
|
||||
* FORK command (fork()s executing the commands received by the current
|
||||
client in the new process). Hint: large SORTs can use more cores,
|
||||
copy-on-write will avoid memory problems.
|
||||
* DUP command? DUP srckey dstkey, creates an exact clone of srckey value in dstkey.
|
||||
* SORT: Don't copy the list into a vector when BY argument is constant.
|
||||
* Write the hash table size of every db in the dump, so that Redis can resize the hash table just one time when loading a big DB.
|
||||
* LOCK / TRYLOCK / UNLOCK as described many times in the google group
|
||||
* Replication automated tests
|
||||
* Byte Array type (BA prefixed commands): BASETBIT BAGETBIT BASETU8 U16 U32 U64 S8 S16 S32 S64, ability to atomically INCRBY all the base types. BARANGE to get a range of bytes as a bulk value, BASETRANGE to set a range of bytes.
|
||||
* zmalloc() should avoid to add a private header for archs where there is some other kind of libc-specific way to get the size of a malloced block. Already done for Mac OS X.
|
||||
* Read-only mode.
|
||||
* Pattern-matching replication.
|
||||
* Add an option to relax the delete-expiring-keys-on-write semantic *denying* replication and AOF when this is on? Can be handy sometimes, when using Redis for non persistent state, but can create problems. For instance should rename and move also "move" the timeouts? How does this affect other commands?
|
||||
* Multiple BY in SORT.
|
||||
https://github.com/antirez/redis/raw/master/TODO
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
An updated list of client libraries for Redis can be found here:
|
||||
|
||||
http://code.google.com/p/redis
|
||||
http://redis.io/clients
|
||||
|
||||
All the links are in the front page.
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
#ifndef __CONFIG_H
|
||||
#define __CONFIG_H
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <AvailabilityMacros.h>
|
||||
#endif
|
||||
|
||||
/* test for malloc_size() */
|
||||
#ifdef __APPLE__
|
||||
#include <malloc/malloc.h>
|
||||
#define HAVE_MALLOC_SIZE 1
|
||||
#define redis_malloc_size(p) malloc_size(p)
|
||||
#endif
|
||||
|
||||
/* define redis_fstat to fstat or fstat64() */
|
||||
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
|
||||
#define redis_fstat fstat64
|
||||
#define redis_stat stat64
|
||||
#else
|
||||
#define redis_fstat fstat
|
||||
#define redis_stat stat
|
||||
#endif
|
||||
|
||||
/* test for backtrace() */
|
||||
#if defined(__APPLE__) || defined(__linux__)
|
||||
#define HAVE_BACKTRACE 1
|
||||
#endif
|
||||
|
||||
/* test for polling API */
|
||||
#ifdef __linux__
|
||||
#define HAVE_EPOLL 1
|
||||
#endif
|
||||
|
||||
#if (defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__NetBSD__)
|
||||
#define HAVE_KQUEUE 1
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
/hiredis-test
|
||||
/hiredis-example*
|
||||
/*.o
|
||||
/*.so
|
||||
/*.dylib
|
||||
/*.a
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
Copyright (c) 2006-2009, Salvatore Sanfilippo
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Redis nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
Vendored
+112
@@ -0,0 +1,112 @@
|
||||
# Hiredis Makefile
|
||||
# Copyright (C) 2010 Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
# This file is released under the BSD license, see the COPYING file
|
||||
|
||||
OBJ = net.o hiredis.o sds.o async.o
|
||||
BINS = hiredis-example hiredis-test
|
||||
|
||||
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
|
||||
OPTIMIZATION?=-O3
|
||||
ifeq ($(uname_S),SunOS)
|
||||
CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -D__EXTENSIONS__ -D_XPG6 $(ARCH) $(PROF)
|
||||
CCLINK?=-ldl -lnsl -lsocket -lm -lpthread
|
||||
LDFLAGS?=-L. -Wl,-R,.
|
||||
DYLIBNAME?=libhiredis.so
|
||||
DYLIB_MAKE_CMD?=$(CC) -G -o ${DYLIBNAME} ${OBJ}
|
||||
STLIBNAME?=libhiredis.a
|
||||
STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
|
||||
else ifeq ($(uname_S),Darwin)
|
||||
CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
|
||||
CCLINK?=-lm -pthread
|
||||
LDFLAGS?=-L. -Wl,-rpath,.
|
||||
OBJARCH?=-arch i386 -arch x86_64
|
||||
DYLIBNAME?=libhiredis.dylib
|
||||
DYLIB_MAKE_CMD?=libtool -dynamic -o ${DYLIBNAME} -lm ${DEBUG} - ${OBJ}
|
||||
STLIBNAME?=libhiredis.a
|
||||
STLIB_MAKE_CMD?=libtool -static -o ${STLIBNAME} - ${OBJ}
|
||||
else
|
||||
CFLAGS?=-std=c99 -pedantic $(OPTIMIZATION) -fPIC -Wall -W -Wwrite-strings $(ARCH) $(PROF)
|
||||
CCLINK?=-lm -pthread
|
||||
LDFLAGS?=-L. -Wl,-rpath,.
|
||||
DYLIBNAME?=libhiredis.so
|
||||
DYLIB_MAKE_CMD?=gcc -shared -Wl,-soname,${DYLIBNAME} -o ${DYLIBNAME} ${OBJ}
|
||||
STLIBNAME?=libhiredis.a
|
||||
STLIB_MAKE_CMD?=ar rcs ${STLIBNAME} ${OBJ}
|
||||
endif
|
||||
CCOPT= $(CFLAGS) $(CCLINK)
|
||||
DEBUG?= -g -ggdb
|
||||
|
||||
PREFIX?= /usr/local
|
||||
INSTALL_INC= $(PREFIX)/include/hiredis
|
||||
INSTALL_LIB= $(PREFIX)/lib
|
||||
INSTALL= cp -a
|
||||
|
||||
all: ${DYLIBNAME} ${BINS}
|
||||
|
||||
# Deps (use make dep to generate this)
|
||||
net.o: net.c fmacros.h net.h
|
||||
async.o: async.c async.h hiredis.h sds.h util.h
|
||||
example.o: example.c hiredis.h
|
||||
hiredis.o: hiredis.c hiredis.h net.h sds.h util.h
|
||||
sds.o: sds.c sds.h
|
||||
test.o: test.c hiredis.h
|
||||
|
||||
${DYLIBNAME}: ${OBJ}
|
||||
${DYLIB_MAKE_CMD}
|
||||
|
||||
${STLIBNAME}: ${OBJ}
|
||||
${STLIB_MAKE_CMD}
|
||||
|
||||
dynamic: ${DYLIBNAME}
|
||||
static: ${STLIBNAME}
|
||||
|
||||
# Binaries:
|
||||
hiredis-example-libevent: example-libevent.c adapters/libevent.h ${DYLIBNAME}
|
||||
$(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis -levent example-libevent.c
|
||||
|
||||
hiredis-example-libev: example-libev.c adapters/libev.h ${DYLIBNAME}
|
||||
$(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis -lev example-libev.c
|
||||
|
||||
ifndef AE_DIR
|
||||
hiredis-example-ae:
|
||||
@echo "Please specify AE_DIR (e.g. <redis repository>/src)"
|
||||
@false
|
||||
else
|
||||
hiredis-example-ae: example-ae.c adapters/ae.h ${DYLIBNAME}
|
||||
$(CC) -o $@ $(CCOPT) $(DEBUG) -I$(AE_DIR) $(LDFLAGS) -lhiredis example-ae.c $(AE_DIR)/ae.o $(AE_DIR)/zmalloc.o
|
||||
endif
|
||||
|
||||
hiredis-%: %.o ${DYLIBNAME}
|
||||
$(CC) -o $@ $(CCOPT) $(DEBUG) $(LDFLAGS) -lhiredis $<
|
||||
|
||||
test: hiredis-test
|
||||
./hiredis-test
|
||||
|
||||
.c.o:
|
||||
$(CC) -c $(CFLAGS) $(OBJARCH) $(DEBUG) $(COMPILE_TIME) $<
|
||||
|
||||
clean:
|
||||
rm -rf ${DYLIBNAME} ${STLIBNAME} $(BINS) hiredis-example* *.o *.gcda *.gcno *.gcov
|
||||
|
||||
dep:
|
||||
$(CC) -MM *.c
|
||||
|
||||
install: ${DYLIBNAME} ${STLIBNAME}
|
||||
mkdir -p $(INSTALL_INC) $(INSTALL_LIB)
|
||||
$(INSTALL) hiredis.h async.h adapters $(INSTALL_INC)
|
||||
$(INSTALL) ${DYLIBNAME} ${STLIBNAME} $(INSTALL_LIB)
|
||||
|
||||
32bit:
|
||||
@echo ""
|
||||
@echo "WARNING: if it fails under Linux you probably need to install libc6-dev-i386"
|
||||
@echo ""
|
||||
$(MAKE) ARCH="-m32"
|
||||
|
||||
gprof:
|
||||
$(MAKE) PROF="-pg"
|
||||
|
||||
gcov:
|
||||
$(MAKE) PROF="-fprofile-arcs -ftest-coverage"
|
||||
|
||||
noopt:
|
||||
$(MAKE) OPTIMIZATION=""
|
||||
Vendored
+311
@@ -0,0 +1,311 @@
|
||||
# HIREDIS
|
||||
|
||||
Hiredis is a minimalistic C client library for the [Redis](http://redis.io/) database.
|
||||
|
||||
It is minimalistic because it just adds minimal support for the protocol, but
|
||||
at the same time it uses an high level printf-alike API in order to make it
|
||||
much higher level than otherwise suggested by its minimal code base and the
|
||||
lack of explicit bindings for every Redis command.
|
||||
|
||||
Apart from supporting sending commands and receiving replies, it comes with
|
||||
a reply parser that is decoupled from the I/O layer. It
|
||||
is a stream parser designed for easy reusability, which can for instance be used
|
||||
in higher level language bindings for efficient reply parsing.
|
||||
|
||||
Hiredis only supports the binary-safe Redis protocol, so you can use it with any
|
||||
Redis version >= 1.2.0.
|
||||
|
||||
The library comes with multiple APIs. There is the
|
||||
*synchronous API*, the *asynchronous API* and the *reply parsing API*.
|
||||
|
||||
## UPGRADING
|
||||
|
||||
Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing
|
||||
code using hiredis should not be a big pain. The key thing to keep in mind when
|
||||
upgrading is that hiredis >= 0.9.0 uses a `redisContext*` to keep state, in contrast to
|
||||
the stateless 0.0.1 that only has a file descriptor to work with.
|
||||
|
||||
## Synchronous API
|
||||
|
||||
To consume the synchronous API, there are only a few function calls that need to be introduced:
|
||||
|
||||
redisContext *redisConnect(const char *ip, int port);
|
||||
void *redisCommand(redisContext *c, const char *format, ...);
|
||||
void freeReplyObject(void *reply);
|
||||
|
||||
### Connecting
|
||||
|
||||
The function `redisConnect` is used to create a so-called `redisContext`. The
|
||||
context is where Hiredis holds state for a connection. The `redisContext`
|
||||
struct has an integer `err` field that is non-zero when an the connection is in
|
||||
an error state. The field `errstr` will contain a string with a description of
|
||||
the error. More information on errors can be found in the **Errors** section.
|
||||
After trying to connect to Redis using `redisConnect` you should
|
||||
check the `err` field to see if establishing the connection was successful:
|
||||
|
||||
redisContext *c = redisConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
// handle error
|
||||
}
|
||||
|
||||
### Sending commands
|
||||
|
||||
There are several ways to issue commands to Redis. The first that will be introduced is
|
||||
`redisCommand`. This function takes a format similar to printf. In the simplest form,
|
||||
it is used like this:
|
||||
|
||||
reply = redisCommand(context, "SET foo bar");
|
||||
|
||||
The specifier `%s` interpolates a string in the command, and uses `strlen` to
|
||||
determine the length of the string:
|
||||
|
||||
reply = redisCommand(context, "SET foo %s", value);
|
||||
|
||||
When you need to pass binary safe strings in a command, the `%b` specifier can be
|
||||
used. Together with a pointer to the string, it requires a `size_t` length argument
|
||||
of the string:
|
||||
|
||||
reply = redisCommand(context, "SET foo %b", value, valuelen);
|
||||
|
||||
Internally, Hiredis splits the command in different arguments and will
|
||||
convert it to the protocol used to communicate with Redis.
|
||||
One or more spaces separates arguments, so you can use the specifiers
|
||||
anywhere in an argument:
|
||||
|
||||
reply = redisCommand("SET key:%s %s", myid, value);
|
||||
|
||||
### Using replies
|
||||
|
||||
The return value of `redisCommand` holds a reply when the command was
|
||||
successfully executed. When an error occurs, the return value is `NULL` and
|
||||
the `err` field in the context will be set (see section on **Errors**).
|
||||
Once an error is returned the context cannot be reused and you should set up
|
||||
a new connection.
|
||||
|
||||
The standard replies that `redisCommand` are of the type `redisReply`. The
|
||||
`type` field in the `redisReply` should be used to test what kind of reply
|
||||
was received:
|
||||
|
||||
* **`REDIS_REPLY_STATUS`**:
|
||||
* The command replied with a status reply. The status string can be accessed using `reply->str`.
|
||||
The length of this string can be accessed using `reply->len`.
|
||||
|
||||
* **`REDIS_REPLY_ERROR`**:
|
||||
* The command replied with an error. The error string can be accessed identical to `REDIS_REPLY_STATUS`.
|
||||
|
||||
* **`REDIS_REPLY_INTEGER`**:
|
||||
* The command replied with an integer. The integer value can be accessed using the
|
||||
`reply->integer` field of type `long long`.
|
||||
|
||||
* **`REDIS_REPLY_NIL`**:
|
||||
* The command replied with a **nil** object. There is no data to access.
|
||||
|
||||
* **`REDIS_REPLY_STRING`**:
|
||||
* A bulk (string) reply. The value of the reply can be accessed using `reply->str`.
|
||||
The length of this string can be accessed using `reply->len`.
|
||||
|
||||
* **`REDIS_REPLY_ARRAY`**:
|
||||
* A multi bulk reply. The number of elements in the multi bulk reply is stored in
|
||||
`reply->elements`. Every element in the multi bulk reply is a `redisReply` object as well
|
||||
and can be accessed via `reply->elements[..index..]`.
|
||||
Redis may reply with nested arrays but this is fully supported.
|
||||
|
||||
Replies should be freed using the `freeReplyObject()` function.
|
||||
Note that this function will take care of freeing sub-replies objects
|
||||
contained in arrays and nested arrays, so there is no need for the user to
|
||||
free the sub replies (it is actually harmful and will corrupt the memory).
|
||||
|
||||
### Cleaning up
|
||||
|
||||
To disconnect and free the context the following function can be used:
|
||||
|
||||
void redisFree(redisContext *c);
|
||||
|
||||
This function immediately closes the socket and then free's the allocations done in
|
||||
creating the context.
|
||||
|
||||
### Sending commands (cont'd)
|
||||
|
||||
Together with `redisCommand`, the function `redisCommandArgv` can be used to issue commands.
|
||||
It has the following prototype:
|
||||
|
||||
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
It takes the number of arguments `argc`, an array of strings `argv` and the lengths of the
|
||||
arguments `argvlen`. For convenience, `argvlen` may be set to `NULL` and the function will
|
||||
use `strlen(3)` on every argument to determine its length. Obviously, when any of the arguments
|
||||
need to be binary safe, the entire array of lengths `argvlen` should be provided.
|
||||
|
||||
The return value has the same semantic as `redisCommand`.
|
||||
|
||||
### Pipelining
|
||||
|
||||
To explain how Hiredis supports pipelining in a blocking connection, there needs to be
|
||||
understanding of the internal execution flow.
|
||||
|
||||
When any of the functions in the `redisCommand` family is called, Hiredis first formats the
|
||||
command according to the Redis protocol. The formatted command is then put in the output buffer
|
||||
of the context. This output buffer is dynamic, so it can hold any number of commands.
|
||||
After the command is put in the output buffer, `redisGetReply` is called. This function has the
|
||||
following two execution paths:
|
||||
|
||||
1. The input buffer is non-empty:
|
||||
* Try to parse a single reply from the input buffer and return it
|
||||
* If no reply could be parsed, continue at *2*
|
||||
2. The input buffer is empty:
|
||||
* Write the **entire** output buffer to the socket
|
||||
* Read from the socket until a single reply could be parsed
|
||||
|
||||
The function `redisGetReply` is exported as part of the Hiredis API and can be used when a reply
|
||||
is expected on the socket. To pipeline commands, the only things that needs to be done is
|
||||
filling up the output buffer. For this cause, two commands can be used that are identical
|
||||
to the `redisCommand` family, apart from not returning a reply:
|
||||
|
||||
void redisAppendCommand(redisContext *c, const char *format, ...);
|
||||
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
After calling either function one or more times, `redisGetReply` can be used to receive the
|
||||
subsequent replies. The return value for this function is either `REDIS_OK` or `REDIS_ERR`, where
|
||||
the latter means an error occurred while reading a reply. Just as with the other commands,
|
||||
the `err` field in the context can be used to find out what the cause of this error is.
|
||||
|
||||
The following examples shows a simple pipeline (resulting in only a single call to `write(2)` and
|
||||
a single call to `write(2)`):
|
||||
|
||||
redisReply *reply;
|
||||
redisAppendCommand(context,"SET foo bar");
|
||||
redisAppendCommand(context,"GET foo");
|
||||
redisGetReply(context,&reply); // reply for SET
|
||||
freeReplyObject(reply);
|
||||
redisGetReply(context,&reply); // reply for GET
|
||||
freeReplyObject(reply);
|
||||
|
||||
This API can also be used to implement a blocking subscriber:
|
||||
|
||||
reply = redisCommand(context,"SUBSCRIBE foo");
|
||||
freeReplyObject(reply);
|
||||
while(redisGetReply(context,&reply) == REDIS_OK) {
|
||||
// consume message
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
|
||||
### Errors
|
||||
|
||||
When a function call is not successful, depending on the function either `NULL` or `REDIS_ERR` is
|
||||
returned. The `err` field inside the context will be non-zero and set to one of the
|
||||
following constants:
|
||||
|
||||
* **`REDIS_ERR_IO`**:
|
||||
There was an I/O error while creating the connection, trying to write
|
||||
to the socket or read from the socket. If you included `errno.h` in your
|
||||
application, you can use the global `errno` variable to find out what is
|
||||
wrong.
|
||||
|
||||
* **`REDIS_ERR_EOF`**:
|
||||
The server closed the connection which resulted in an empty read.
|
||||
|
||||
* **`REDIS_ERR_PROTOCOL`**:
|
||||
There was an error while parsing the protocol.
|
||||
|
||||
* **`REDIS_ERR_OTHER`**:
|
||||
Any other error. Currently, it is only used when a specified hostname to connect
|
||||
to cannot be resolved.
|
||||
|
||||
In every case, the `errstr` field in the context will be set to hold a string representation
|
||||
of the error.
|
||||
|
||||
## Asynchronous API
|
||||
|
||||
Hiredis comes with an asynchronous API that works easily with any event library.
|
||||
Examples are bundled that show using Hiredis with [libev](http://software.schmorp.de/pkg/libev.html)
|
||||
and [libevent](http://monkey.org/~provos/libevent/).
|
||||
|
||||
### Connecting
|
||||
|
||||
The function `redisAsyncConnect` can be used to establish a non-blocking connection to
|
||||
Redis. It returns a pointer to the newly created `redisAsyncContext` struct. The `err` field
|
||||
should be checked after creation to see if there were errors creating the connection.
|
||||
Because the connection that will be created is non-blocking, the kernel is not able to
|
||||
instantly return if the specified host and port is able to accept a connection.
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
// handle error
|
||||
}
|
||||
|
||||
The asynchronous context can hold a disconnect callback function that is called when the
|
||||
connection is disconnected (either because of an error or per user request). This function should
|
||||
have the following prototype:
|
||||
|
||||
void(const redisAsyncContext *c, int status);
|
||||
|
||||
On a disconnect, the `status` argument is set to `REDIS_OK` when disconnection was initiated by the
|
||||
user, or `REDIS_ERR` when the disconnection was caused by an error. When it is `REDIS_ERR`, the `err`
|
||||
field in the context can be accessed to find out the cause of the error.
|
||||
|
||||
The context object is always free'd after the disconnect callback fired. When a reconnect is needed,
|
||||
the disconnect callback is a good point to do so.
|
||||
|
||||
Setting the disconnect callback can only be done once per context. For subsequent calls it will
|
||||
return `REDIS_ERR`. The function to set the disconnect callback has the following prototype:
|
||||
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
||||
|
||||
### Sending commands and their callbacks
|
||||
|
||||
In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
|
||||
Therefore, unlike the synchronous API, there is only a single way to send commands.
|
||||
Because commands are sent to Redis asynchronously, issuing a command requires a callback function
|
||||
that is called when the reply is received. Reply callbacks should have the following prototype:
|
||||
|
||||
void(redisAsyncContext *c, void *reply, void *privdata);
|
||||
|
||||
The `privdata` argument can be used to curry arbitrary data to the callback from the point where
|
||||
the command is initially queued for execution.
|
||||
|
||||
The functions that can be used to issue commands in an asynchronous context are:
|
||||
|
||||
int redisAsyncCommand(
|
||||
redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
|
||||
const char *format, ...);
|
||||
int redisAsyncCommandArgv(
|
||||
redisAsyncContext *ac, redisCallbackFn *fn, void *privdata,
|
||||
int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
Both functions work like their blocking counterparts. The return value is `REDIS_OK` when the command
|
||||
was successfully added to the output buffer and `REDIS_ERR` otherwise. Example: when the connection
|
||||
is being disconnected per user-request, no new commands may be added to the output buffer and `REDIS_ERR` is
|
||||
returned on calls to the `redisAsyncCommand` family.
|
||||
|
||||
If the reply for a command with a `NULL` callback is read, it is immediately free'd. When the callback
|
||||
for a command is non-`NULL`, it is responsible for cleaning up the reply.
|
||||
|
||||
All pending callbacks are called with a `NULL` reply when the context encountered an error.
|
||||
|
||||
### Disconnecting
|
||||
|
||||
An asynchronous connection can be terminated using:
|
||||
|
||||
void redisAsyncDisconnect(redisAsyncContext *ac);
|
||||
|
||||
When this function is called, the connection is **not** immediately terminated. Instead, new
|
||||
commands are no longer accepted and the connection is only terminated when all pending commands
|
||||
have been written to the socket, their respective replies have been read and their respective
|
||||
callbacks have been executed. After this, the disconnection callback is executed with the
|
||||
`REDIS_OK` status and the context object is free'd.
|
||||
|
||||
### Hooking it up to event library *X*
|
||||
|
||||
There are a few hooks that need to be set on the context object after it is created.
|
||||
See the `adapters/` directory for bindings to *libev* and *libevent*.
|
||||
|
||||
## Reply parsing API
|
||||
|
||||
To be done.
|
||||
|
||||
## AUTHORS
|
||||
|
||||
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
|
||||
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
- add redisCommandVector()
|
||||
- add support for pipelining
|
||||
Vendored
+95
@@ -0,0 +1,95 @@
|
||||
#include <sys/types.h>
|
||||
#include <ae.h>
|
||||
#include "../hiredis.h"
|
||||
#include "../async.h"
|
||||
|
||||
typedef struct redisAeEvents {
|
||||
redisAsyncContext *context;
|
||||
aeEventLoop *loop;
|
||||
int fd;
|
||||
int reading, writing;
|
||||
} redisAeEvents;
|
||||
|
||||
void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
((void)el); ((void)fd); ((void)mask);
|
||||
|
||||
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||
redisAsyncHandleRead(e->context);
|
||||
}
|
||||
|
||||
void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
((void)el); ((void)fd); ((void)mask);
|
||||
|
||||
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||
redisAsyncHandleWrite(e->context);
|
||||
}
|
||||
|
||||
void redisAeAddRead(void *privdata) {
|
||||
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||
aeEventLoop *loop = e->loop;
|
||||
if (!e->reading) {
|
||||
e->reading = 1;
|
||||
aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e);
|
||||
}
|
||||
}
|
||||
|
||||
void redisAeDelRead(void *privdata) {
|
||||
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||
aeEventLoop *loop = e->loop;
|
||||
if (e->reading) {
|
||||
e->reading = 0;
|
||||
aeDeleteFileEvent(loop,e->fd,AE_READABLE);
|
||||
}
|
||||
}
|
||||
|
||||
void redisAeAddWrite(void *privdata) {
|
||||
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||
aeEventLoop *loop = e->loop;
|
||||
if (!e->writing) {
|
||||
e->writing = 1;
|
||||
aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e);
|
||||
}
|
||||
}
|
||||
|
||||
void redisAeDelWrite(void *privdata) {
|
||||
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||
aeEventLoop *loop = e->loop;
|
||||
if (e->writing) {
|
||||
e->writing = 0;
|
||||
aeDeleteFileEvent(loop,e->fd,AE_WRITABLE);
|
||||
}
|
||||
}
|
||||
|
||||
void redisAeCleanup(void *privdata) {
|
||||
redisAeEvents *e = (redisAeEvents*)privdata;
|
||||
redisAeDelRead(privdata);
|
||||
redisAeDelWrite(privdata);
|
||||
free(e);
|
||||
}
|
||||
|
||||
int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisAeEvents *e;
|
||||
|
||||
/* Nothing should be attached when something is already attached */
|
||||
if (ac->_adapter_data != NULL)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Create container for context and r/w events */
|
||||
e = (redisAeEvents*)malloc(sizeof(*e));
|
||||
e->context = ac;
|
||||
e->loop = loop;
|
||||
e->fd = c->fd;
|
||||
e->reading = e->writing = 0;
|
||||
|
||||
/* Register functions to start/stop listening for events */
|
||||
ac->evAddRead = redisAeAddRead;
|
||||
ac->evDelRead = redisAeDelRead;
|
||||
ac->evAddWrite = redisAeAddWrite;
|
||||
ac->evDelWrite = redisAeDelWrite;
|
||||
ac->evCleanup = redisAeCleanup;
|
||||
ac->_adapter_data = e;
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
Vendored
+113
@@ -0,0 +1,113 @@
|
||||
#include <sys/types.h>
|
||||
#include <ev.h>
|
||||
#include "../hiredis.h"
|
||||
#include "../async.h"
|
||||
|
||||
typedef struct redisLibevEvents {
|
||||
redisAsyncContext *context;
|
||||
struct ev_loop *loop;
|
||||
int reading, writing;
|
||||
ev_io rev, wev;
|
||||
} redisLibevEvents;
|
||||
|
||||
void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) {
|
||||
#if EV_MULTIPLICITY
|
||||
((void)loop);
|
||||
#endif
|
||||
((void)revents);
|
||||
|
||||
redisLibevEvents *e = (redisLibevEvents*)watcher->data;
|
||||
redisAsyncHandleRead(e->context);
|
||||
}
|
||||
|
||||
void redisLibevWriteEvent(EV_P_ ev_io *watcher, int revents) {
|
||||
#if EV_MULTIPLICITY
|
||||
((void)loop);
|
||||
#endif
|
||||
((void)revents);
|
||||
|
||||
redisLibevEvents *e = (redisLibevEvents*)watcher->data;
|
||||
redisAsyncHandleWrite(e->context);
|
||||
}
|
||||
|
||||
void redisLibevAddRead(void *privdata) {
|
||||
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||
struct ev_loop *loop = e->loop;
|
||||
((void)loop);
|
||||
if (!e->reading) {
|
||||
e->reading = 1;
|
||||
ev_io_start(EV_A_ &e->rev);
|
||||
}
|
||||
}
|
||||
|
||||
void redisLibevDelRead(void *privdata) {
|
||||
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||
struct ev_loop *loop = e->loop;
|
||||
((void)loop);
|
||||
if (e->reading) {
|
||||
e->reading = 0;
|
||||
ev_io_stop(EV_A_ &e->rev);
|
||||
}
|
||||
}
|
||||
|
||||
void redisLibevAddWrite(void *privdata) {
|
||||
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||
struct ev_loop *loop = e->loop;
|
||||
((void)loop);
|
||||
if (!e->writing) {
|
||||
e->writing = 1;
|
||||
ev_io_start(EV_A_ &e->wev);
|
||||
}
|
||||
}
|
||||
|
||||
void redisLibevDelWrite(void *privdata) {
|
||||
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||
struct ev_loop *loop = e->loop;
|
||||
((void)loop);
|
||||
if (e->writing) {
|
||||
e->writing = 0;
|
||||
ev_io_stop(EV_A_ &e->wev);
|
||||
}
|
||||
}
|
||||
|
||||
void redisLibevCleanup(void *privdata) {
|
||||
redisLibevEvents *e = (redisLibevEvents*)privdata;
|
||||
redisLibevDelRead(privdata);
|
||||
redisLibevDelWrite(privdata);
|
||||
free(e);
|
||||
}
|
||||
|
||||
int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisLibevEvents *e;
|
||||
|
||||
/* Nothing should be attached when something is already attached */
|
||||
if (ac->_adapter_data != NULL)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Create container for context and r/w events */
|
||||
e = (redisLibevEvents*)malloc(sizeof(*e));
|
||||
e->context = ac;
|
||||
#if EV_MULTIPLICITY
|
||||
e->loop = loop;
|
||||
#else
|
||||
e->loop = NULL;
|
||||
#endif
|
||||
e->reading = e->writing = 0;
|
||||
e->rev.data = e;
|
||||
e->wev.data = e;
|
||||
|
||||
/* Register functions to start/stop listening for events */
|
||||
ac->evAddRead = redisLibevAddRead;
|
||||
ac->evDelRead = redisLibevDelRead;
|
||||
ac->evAddWrite = redisLibevAddWrite;
|
||||
ac->evDelWrite = redisLibevDelWrite;
|
||||
ac->evCleanup = redisLibevCleanup;
|
||||
ac->_adapter_data = e;
|
||||
|
||||
/* Initialize read/write events */
|
||||
ev_io_init(&e->rev,redisLibevReadEvent,c->fd,EV_READ);
|
||||
ev_io_init(&e->wev,redisLibevWriteEvent,c->fd,EV_WRITE);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
#include <sys/types.h>
|
||||
#include <event.h>
|
||||
#include "../hiredis.h"
|
||||
#include "../async.h"
|
||||
|
||||
typedef struct redisLibeventEvents {
|
||||
redisAsyncContext *context;
|
||||
struct event rev, wev;
|
||||
} redisLibeventEvents;
|
||||
|
||||
void redisLibeventReadEvent(int fd, short event, void *arg) {
|
||||
((void)fd); ((void)event);
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)arg;
|
||||
redisAsyncHandleRead(e->context);
|
||||
}
|
||||
|
||||
void redisLibeventWriteEvent(int fd, short event, void *arg) {
|
||||
((void)fd); ((void)event);
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)arg;
|
||||
redisAsyncHandleWrite(e->context);
|
||||
}
|
||||
|
||||
void redisLibeventAddRead(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_add(&e->rev,NULL);
|
||||
}
|
||||
|
||||
void redisLibeventDelRead(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_del(&e->rev);
|
||||
}
|
||||
|
||||
void redisLibeventAddWrite(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_add(&e->wev,NULL);
|
||||
}
|
||||
|
||||
void redisLibeventDelWrite(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_del(&e->wev);
|
||||
}
|
||||
|
||||
void redisLibeventCleanup(void *privdata) {
|
||||
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
|
||||
event_del(&e->rev);
|
||||
event_del(&e->wev);
|
||||
free(e);
|
||||
}
|
||||
|
||||
int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisLibeventEvents *e;
|
||||
|
||||
/* Nothing should be attached when something is already attached */
|
||||
if (ac->_adapter_data != NULL)
|
||||
return REDIS_ERR;
|
||||
|
||||
/* Create container for context and r/w events */
|
||||
e = (redisLibeventEvents*)malloc(sizeof(*e));
|
||||
e->context = ac;
|
||||
|
||||
/* Register functions to start/stop listening for events */
|
||||
ac->evAddRead = redisLibeventAddRead;
|
||||
ac->evDelRead = redisLibeventDelRead;
|
||||
ac->evAddWrite = redisLibeventAddWrite;
|
||||
ac->evDelWrite = redisLibeventDelWrite;
|
||||
ac->evCleanup = redisLibeventCleanup;
|
||||
ac->_adapter_data = e;
|
||||
|
||||
/* Initialize and install read/write events */
|
||||
event_set(&e->rev,c->fd,EV_READ,redisLibeventReadEvent,e);
|
||||
event_set(&e->wev,c->fd,EV_WRITE,redisLibeventWriteEvent,e);
|
||||
event_base_set(base,&e->rev);
|
||||
event_base_set(base,&e->wev);
|
||||
return REDIS_OK;
|
||||
}
|
||||
Vendored
+321
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include "async.h"
|
||||
#include "sds.h"
|
||||
#include "util.h"
|
||||
|
||||
/* Forward declaration of function in hiredis.c */
|
||||
void __redisAppendCommand(redisContext *c, char *cmd, size_t len);
|
||||
|
||||
static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
|
||||
redisAsyncContext *ac = realloc(c,sizeof(redisAsyncContext));
|
||||
c = &(ac->c);
|
||||
|
||||
/* The regular connect functions will always set the flag REDIS_CONNECTED.
|
||||
* For the async API, we want to wait until the first write event is
|
||||
* received up before setting this flag, so reset it here. */
|
||||
c->flags &= ~REDIS_CONNECTED;
|
||||
|
||||
ac->err = 0;
|
||||
ac->errstr = NULL;
|
||||
ac->data = NULL;
|
||||
ac->_adapter_data = NULL;
|
||||
|
||||
ac->evAddRead = NULL;
|
||||
ac->evDelRead = NULL;
|
||||
ac->evAddWrite = NULL;
|
||||
ac->evDelWrite = NULL;
|
||||
ac->evCleanup = NULL;
|
||||
|
||||
ac->onConnect = NULL;
|
||||
ac->onDisconnect = NULL;
|
||||
|
||||
ac->replies.head = NULL;
|
||||
ac->replies.tail = NULL;
|
||||
return ac;
|
||||
}
|
||||
|
||||
/* We want the error field to be accessible directly instead of requiring
|
||||
* an indirection to the redisContext struct. */
|
||||
static void __redisAsyncCopyError(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
ac->err = c->err;
|
||||
ac->errstr = c->errstr;
|
||||
}
|
||||
|
||||
redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
|
||||
redisContext *c = redisConnectNonBlock(ip,port);
|
||||
redisAsyncContext *ac = redisAsyncInitialize(c);
|
||||
__redisAsyncCopyError(ac);
|
||||
return ac;
|
||||
}
|
||||
|
||||
redisAsyncContext *redisAsyncConnectUnix(const char *path) {
|
||||
redisContext *c = redisConnectUnixNonBlock(path);
|
||||
redisAsyncContext *ac = redisAsyncInitialize(c);
|
||||
__redisAsyncCopyError(ac);
|
||||
return ac;
|
||||
}
|
||||
|
||||
int redisAsyncSetReplyObjectFunctions(redisAsyncContext *ac, redisReplyObjectFunctions *fn) {
|
||||
redisContext *c = &(ac->c);
|
||||
return redisSetReplyObjectFunctions(c,fn);
|
||||
}
|
||||
|
||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
|
||||
if (ac->onConnect == NULL) {
|
||||
ac->onConnect = fn;
|
||||
return REDIS_OK;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
|
||||
if (ac->onDisconnect == NULL) {
|
||||
ac->onDisconnect = fn;
|
||||
return REDIS_OK;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Helper functions to push/shift callbacks */
|
||||
static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
|
||||
redisCallback *cb;
|
||||
|
||||
/* Copy callback from stack to heap */
|
||||
cb = calloc(1,sizeof(*cb));
|
||||
if (!cb) redisOOM();
|
||||
if (source != NULL) {
|
||||
cb->fn = source->fn;
|
||||
cb->privdata = source->privdata;
|
||||
}
|
||||
|
||||
/* Store callback in list */
|
||||
if (list->head == NULL)
|
||||
list->head = cb;
|
||||
if (list->tail != NULL)
|
||||
list->tail->next = cb;
|
||||
list->tail = cb;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) {
|
||||
redisCallback *cb = list->head;
|
||||
if (cb != NULL) {
|
||||
list->head = cb->next;
|
||||
if (cb == list->tail)
|
||||
list->tail = NULL;
|
||||
|
||||
/* Copy callback from heap to stack */
|
||||
if (target != NULL)
|
||||
memcpy(target,cb,sizeof(*cb));
|
||||
free(cb);
|
||||
return REDIS_OK;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
/* Tries to do a clean disconnect from Redis, meaning it stops new commands
|
||||
* from being issued, but tries to flush the output buffer and execute
|
||||
* callbacks for all remaining replies.
|
||||
*
|
||||
* This functions is generally called from within a callback, so the
|
||||
* processCallbacks function will pick up the flag when there are no
|
||||
* more replies. */
|
||||
void redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
c->flags |= REDIS_DISCONNECTING;
|
||||
}
|
||||
|
||||
/* Helper function to make the disconnect happen and clean up. */
|
||||
static void __redisAsyncDisconnect(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisCallback cb;
|
||||
int status;
|
||||
|
||||
/* Make sure error is accessible if there is any */
|
||||
__redisAsyncCopyError(ac);
|
||||
status = (ac->err == 0) ? REDIS_OK : REDIS_ERR;
|
||||
|
||||
if (status == REDIS_OK) {
|
||||
/* When the connection is cleanly disconnected, there should not
|
||||
* be pending callbacks. */
|
||||
assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR);
|
||||
} else {
|
||||
/* Callbacks should not be able to issue new commands. */
|
||||
c->flags |= REDIS_DISCONNECTING;
|
||||
|
||||
/* Execute pending callbacks with NULL reply. */
|
||||
while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) {
|
||||
if (cb.fn != NULL)
|
||||
cb.fn(ac,NULL,cb.privdata);
|
||||
}
|
||||
}
|
||||
|
||||
/* Signal event lib to clean up */
|
||||
if (ac->evCleanup) ac->evCleanup(ac->_adapter_data);
|
||||
|
||||
/* Execute callback with proper status */
|
||||
if (ac->onDisconnect) ac->onDisconnect(ac,status);
|
||||
|
||||
/* Cleanup self */
|
||||
redisFree(c);
|
||||
}
|
||||
|
||||
void redisProcessCallbacks(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisCallback cb;
|
||||
void *reply = NULL;
|
||||
int status;
|
||||
|
||||
while((status = redisGetReply(c,&reply)) == REDIS_OK) {
|
||||
if (reply == NULL) {
|
||||
/* When the connection is being disconnected and there are
|
||||
* no more replies, this is the cue to really disconnect. */
|
||||
if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
return;
|
||||
}
|
||||
|
||||
/* When the connection is not being disconnected, simply stop
|
||||
* trying to get replies and wait for the next loop tick. */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Shift callback and execute it */
|
||||
assert(__redisShiftCallback(&ac->replies,&cb) == REDIS_OK);
|
||||
if (cb.fn != NULL) {
|
||||
cb.fn(ac,reply,cb.privdata);
|
||||
} else {
|
||||
c->fn->freeObject(reply);
|
||||
}
|
||||
}
|
||||
|
||||
/* Disconnect when there was an error reading the reply */
|
||||
if (status != REDIS_OK)
|
||||
__redisAsyncDisconnect(ac);
|
||||
}
|
||||
|
||||
/* This function should be called when the socket is readable.
|
||||
* It processes all replies that can be read and executes their callbacks.
|
||||
*/
|
||||
void redisAsyncHandleRead(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
|
||||
if (redisBufferRead(c) == REDIS_ERR) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
} else {
|
||||
/* Always re-schedule reads */
|
||||
if (ac->evAddRead) ac->evAddRead(ac->_adapter_data);
|
||||
redisProcessCallbacks(ac);
|
||||
}
|
||||
}
|
||||
|
||||
void redisAsyncHandleWrite(redisAsyncContext *ac) {
|
||||
redisContext *c = &(ac->c);
|
||||
int done = 0;
|
||||
|
||||
if (redisBufferWrite(c,&done) == REDIS_ERR) {
|
||||
__redisAsyncDisconnect(ac);
|
||||
} else {
|
||||
/* Continue writing when not done, stop writing otherwise */
|
||||
if (!done) {
|
||||
if (ac->evAddWrite) ac->evAddWrite(ac->_adapter_data);
|
||||
} else {
|
||||
if (ac->evDelWrite) ac->evDelWrite(ac->_adapter_data);
|
||||
}
|
||||
|
||||
/* Always schedule reads after writes */
|
||||
if (ac->evAddRead) ac->evAddRead(ac->_adapter_data);
|
||||
|
||||
/* Fire onConnect when this is the first write event. */
|
||||
if (!(c->flags & REDIS_CONNECTED)) {
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
if (ac->onConnect) ac->onConnect(ac);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper function for the redisAsyncCommand* family of functions.
|
||||
*
|
||||
* Write a formatted command to the output buffer and register the provided
|
||||
* callback function with the context.
|
||||
*/
|
||||
static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, char *cmd, size_t len) {
|
||||
redisContext *c = &(ac->c);
|
||||
redisCallback cb;
|
||||
|
||||
/* Don't accept new commands when the connection is lazily closed. */
|
||||
if (c->flags & REDIS_DISCONNECTING) return REDIS_ERR;
|
||||
__redisAppendCommand(c,cmd,len);
|
||||
|
||||
/* Store callback */
|
||||
cb.fn = fn;
|
||||
cb.privdata = privdata;
|
||||
__redisPushCallback(&ac->replies,&cb);
|
||||
|
||||
/* Always schedule a write when the write buffer is non-empty */
|
||||
if (ac->evAddWrite) ac->evAddWrite(ac->_adapter_data);
|
||||
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {
|
||||
char *cmd;
|
||||
int len;
|
||||
int status;
|
||||
len = redisvFormatCommand(&cmd,format,ap);
|
||||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
||||
free(cmd);
|
||||
return status;
|
||||
}
|
||||
|
||||
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) {
|
||||
va_list ap;
|
||||
int status;
|
||||
va_start(ap,format);
|
||||
status = redisvAsyncCommand(ac,fn,privdata,format,ap);
|
||||
va_end(ap);
|
||||
return status;
|
||||
}
|
||||
|
||||
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
|
||||
char *cmd;
|
||||
int len;
|
||||
int status;
|
||||
len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
|
||||
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
|
||||
free(cmd);
|
||||
return status;
|
||||
}
|
||||
Vendored
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __HIREDIS_ASYNC_H
|
||||
#define __HIREDIS_ASYNC_H
|
||||
#include "hiredis.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct redisAsyncContext; /* need forward declaration of redisAsyncContext */
|
||||
|
||||
/* Reply callback prototype and container */
|
||||
typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*);
|
||||
typedef struct redisCallback {
|
||||
struct redisCallback *next; /* simple singly linked list */
|
||||
redisCallbackFn *fn;
|
||||
void *privdata;
|
||||
} redisCallback;
|
||||
|
||||
/* List of callbacks for either regular replies or pub/sub */
|
||||
typedef struct redisCallbackList {
|
||||
redisCallback *head, *tail;
|
||||
} redisCallbackList;
|
||||
|
||||
/* Connection callback prototypes */
|
||||
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
|
||||
typedef void (redisConnectCallback)(const struct redisAsyncContext*);
|
||||
|
||||
/* Context for an async connection to Redis */
|
||||
typedef struct redisAsyncContext {
|
||||
/* Hold the regular context, so it can be realloc'ed. */
|
||||
redisContext c;
|
||||
|
||||
/* Setup error flags so they can be used directly. */
|
||||
int err;
|
||||
char *errstr;
|
||||
|
||||
/* Not used by hiredis */
|
||||
void *data;
|
||||
|
||||
/* Used by the different event lib adapters to store their private data */
|
||||
void *_adapter_data;
|
||||
|
||||
/* Called when the library expects to start reading/writing.
|
||||
* The supplied functions should be idempotent. */
|
||||
void (*evAddRead)(void *privdata);
|
||||
void (*evDelRead)(void *privdata);
|
||||
void (*evAddWrite)(void *privdata);
|
||||
void (*evDelWrite)(void *privdata);
|
||||
void (*evCleanup)(void *privdata);
|
||||
|
||||
/* Called when either the connection is terminated due to an error or per
|
||||
* user request. The status is set accordingly (REDIS_OK, REDIS_ERR). */
|
||||
redisDisconnectCallback *onDisconnect;
|
||||
|
||||
/* Called when the first write event was received. */
|
||||
redisConnectCallback *onConnect;
|
||||
|
||||
/* Reply callbacks */
|
||||
redisCallbackList replies;
|
||||
} redisAsyncContext;
|
||||
|
||||
/* Functions that proxy to hiredis */
|
||||
redisAsyncContext *redisAsyncConnect(const char *ip, int port);
|
||||
int redisAsyncSetReplyObjectFunctions(redisAsyncContext *ac, redisReplyObjectFunctions *fn);
|
||||
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
|
||||
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
|
||||
void redisAsyncDisconnect(redisAsyncContext *ac);
|
||||
|
||||
/* Handle read/write events */
|
||||
void redisAsyncHandleRead(redisAsyncContext *ac);
|
||||
void redisAsyncHandleWrite(redisAsyncContext *ac);
|
||||
|
||||
/* Command functions for an async context. Write the command to the
|
||||
* output buffer and register the provided callback. */
|
||||
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap);
|
||||
int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...);
|
||||
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Vendored
+53
@@ -0,0 +1,53 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include "hiredis.h"
|
||||
#include "async.h"
|
||||
#include "adapters/ae.h"
|
||||
|
||||
/* Put event loop in the global scope, so it can be explicitly stopped */
|
||||
static aeEventLoop *loop;
|
||||
|
||||
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
redisReply *reply = r;
|
||||
if (reply == NULL) return;
|
||||
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
|
||||
|
||||
/* Disconnect after receiving the reply to GET */
|
||||
redisAsyncDisconnect(c);
|
||||
}
|
||||
|
||||
void connectCallback(const redisAsyncContext *c) {
|
||||
((void)c);
|
||||
printf("connected...\n");
|
||||
}
|
||||
|
||||
void disconnectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
}
|
||||
printf("disconnected...\n");
|
||||
aeStop(loop);
|
||||
}
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
/* Let *c leak for now... */
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
loop = aeCreateEventLoop();
|
||||
redisAeAttach(loop, c);
|
||||
redisAsyncSetConnectCallback(c,connectCallback);
|
||||
redisAsyncSetDisconnectCallback(c,disconnectCallback);
|
||||
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
|
||||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
|
||||
aeMain(loop);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include "hiredis.h"
|
||||
#include "async.h"
|
||||
#include "adapters/libev.h"
|
||||
|
||||
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
redisReply *reply = r;
|
||||
if (reply == NULL) return;
|
||||
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
|
||||
|
||||
/* Disconnect after receiving the reply to GET */
|
||||
redisAsyncDisconnect(c);
|
||||
}
|
||||
|
||||
void connectCallback(const redisAsyncContext *c) {
|
||||
((void)c);
|
||||
printf("connected...\n");
|
||||
}
|
||||
|
||||
void disconnectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
}
|
||||
printf("disconnected...\n");
|
||||
}
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
/* Let *c leak for now... */
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
redisLibevAttach(EV_DEFAULT_ c);
|
||||
redisAsyncSetConnectCallback(c,connectCallback);
|
||||
redisAsyncSetDisconnectCallback(c,disconnectCallback);
|
||||
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
|
||||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
|
||||
ev_loop(EV_DEFAULT_ 0);
|
||||
return 0;
|
||||
}
|
||||
Vendored
+48
@@ -0,0 +1,48 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include "hiredis.h"
|
||||
#include "async.h"
|
||||
#include "adapters/libevent.h"
|
||||
|
||||
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
|
||||
redisReply *reply = r;
|
||||
if (reply == NULL) return;
|
||||
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
|
||||
|
||||
/* Disconnect after receiving the reply to GET */
|
||||
redisAsyncDisconnect(c);
|
||||
}
|
||||
|
||||
void connectCallback(const redisAsyncContext *c) {
|
||||
((void)c);
|
||||
printf("connected...\n");
|
||||
}
|
||||
|
||||
void disconnectCallback(const redisAsyncContext *c, int status) {
|
||||
if (status != REDIS_OK) {
|
||||
printf("Error: %s\n", c->errstr);
|
||||
}
|
||||
printf("disconnected...\n");
|
||||
}
|
||||
|
||||
int main (int argc, char **argv) {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
struct event_base *base = event_base_new();
|
||||
|
||||
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
/* Let *c leak for now... */
|
||||
printf("Error: %s\n", c->errstr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
redisLibeventAttach(c,base);
|
||||
redisAsyncSetConnectCallback(c,connectCallback);
|
||||
redisAsyncSetDisconnectCallback(c,disconnectCallback);
|
||||
redisAsyncCommand(c, NULL, NULL, "SET key %b", argv[argc-1], strlen(argv[argc-1]));
|
||||
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
|
||||
event_base_dispatch(base);
|
||||
return 0;
|
||||
}
|
||||
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
|
||||
int main(void) {
|
||||
unsigned int j;
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
|
||||
c = redisConnect((char*)"127.0.0.1", 6379);
|
||||
if (c->err) {
|
||||
printf("Connection error: %s\n", c->errstr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* PING server */
|
||||
reply = redisCommand(c,"PING");
|
||||
printf("PING: %s\n", reply->str);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Set a key */
|
||||
reply = redisCommand(c,"SET %s %s", "foo", "hello world");
|
||||
printf("SET: %s\n", reply->str);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Set a key using binary safe API */
|
||||
reply = redisCommand(c,"SET %b %b", "bar", 3, "hello", 5);
|
||||
printf("SET (binary API): %s\n", reply->str);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Try a GET and two INCR */
|
||||
reply = redisCommand(c,"GET foo");
|
||||
printf("GET foo: %s\n", reply->str);
|
||||
freeReplyObject(reply);
|
||||
|
||||
reply = redisCommand(c,"INCR counter");
|
||||
printf("INCR counter: %lld\n", reply->integer);
|
||||
freeReplyObject(reply);
|
||||
/* again ... */
|
||||
reply = redisCommand(c,"INCR counter");
|
||||
printf("INCR counter: %lld\n", reply->integer);
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Create a list of numbers, from 0 to 9 */
|
||||
reply = redisCommand(c,"DEL mylist");
|
||||
freeReplyObject(reply);
|
||||
for (j = 0; j < 10; j++) {
|
||||
char buf[64];
|
||||
|
||||
snprintf(buf,64,"%d",j);
|
||||
reply = redisCommand(c,"LPUSH mylist element-%s", buf);
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
|
||||
/* Let's check what we have inside the list */
|
||||
reply = redisCommand(c,"LRANGE mylist 0 -1");
|
||||
if (reply->type == REDIS_REPLY_ARRAY) {
|
||||
for (j = 0; j < reply->elements; j++) {
|
||||
printf("%u) %s\n", j, reply->element[j]->str);
|
||||
}
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Vendored
+6
@@ -2,7 +2,13 @@
|
||||
#define _REDIS_FMACRO_H
|
||||
|
||||
#define _BSD_SOURCE
|
||||
|
||||
#ifdef __linux__
|
||||
#define _XOPEN_SOURCE 700
|
||||
#else
|
||||
#define _XOPEN_SOURCE
|
||||
#endif
|
||||
|
||||
#define _LARGEFILE_SOURCE
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
|
||||
Vendored
+1058
File diff suppressed because it is too large
Load Diff
Vendored
+170
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __HIREDIS_H
|
||||
#define __HIREDIS_H
|
||||
#include <stdio.h> /* for size_t */
|
||||
#include <stdarg.h> /* for va_list */
|
||||
|
||||
#define HIREDIS_MAJOR 0
|
||||
#define HIREDIS_MINOR 9
|
||||
#define HIREDIS_PATCH 2
|
||||
|
||||
#define REDIS_ERR -1
|
||||
#define REDIS_OK 0
|
||||
|
||||
/* When an error occurs, the err flag in a context is set to hold the type of
|
||||
* error that occured. REDIS_ERR_IO means there was an I/O error and you
|
||||
* should use the "errno" variable to find out what is wrong.
|
||||
* For other values, the "errstr" field will hold a description. */
|
||||
#define REDIS_ERR_IO 1 /* error in read or write */
|
||||
#define REDIS_ERR_EOF 3 /* eof */
|
||||
#define REDIS_ERR_PROTOCOL 4 /* protocol error */
|
||||
#define REDIS_ERR_OTHER 2 /* something else */
|
||||
|
||||
/* Connection type can be blocking or non-blocking and is set in the
|
||||
* least significant bit of the flags field in redisContext. */
|
||||
#define REDIS_BLOCK 0x1
|
||||
|
||||
/* Connection may be disconnected before being free'd. The second bit
|
||||
* in the flags field is set when the context is connected. */
|
||||
#define REDIS_CONNECTED 0x2
|
||||
|
||||
/* The async API might try to disconnect cleanly and flush the output
|
||||
* buffer and read all subsequent replies before disconnecting.
|
||||
* This flag means no new commands can come in and the connection
|
||||
* should be terminated once all replies have been read. */
|
||||
#define REDIS_DISCONNECTING 0x4
|
||||
|
||||
#define REDIS_REPLY_STRING 1
|
||||
#define REDIS_REPLY_ARRAY 2
|
||||
#define REDIS_REPLY_INTEGER 3
|
||||
#define REDIS_REPLY_NIL 4
|
||||
#define REDIS_REPLY_STATUS 5
|
||||
#define REDIS_REPLY_ERROR 6
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* This is the reply object returned by redisCommand() */
|
||||
typedef struct redisReply {
|
||||
int type; /* REDIS_REPLY_* */
|
||||
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
|
||||
int len; /* Length of string */
|
||||
char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
|
||||
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
|
||||
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
|
||||
} redisReply;
|
||||
|
||||
typedef struct redisReadTask {
|
||||
int type;
|
||||
int elements; /* number of elements in multibulk container */
|
||||
int idx; /* index in parent (array) object */
|
||||
void *obj; /* holds user-generated value for a read task */
|
||||
struct redisReadTask *parent; /* parent task */
|
||||
void *privdata; /* user-settable arbitrary field */
|
||||
} redisReadTask;
|
||||
|
||||
typedef struct redisReplyObjectFunctions {
|
||||
void *(*createString)(const redisReadTask*, char*, size_t);
|
||||
void *(*createArray)(const redisReadTask*, int);
|
||||
void *(*createInteger)(const redisReadTask*, long long);
|
||||
void *(*createNil)(const redisReadTask*);
|
||||
void (*freeObject)(void*);
|
||||
} redisReplyObjectFunctions;
|
||||
|
||||
struct redisContext; /* need forward declaration of redisContext */
|
||||
|
||||
/* Context for a connection to Redis */
|
||||
typedef struct redisContext {
|
||||
int fd;
|
||||
int flags;
|
||||
char *obuf; /* Write buffer */
|
||||
int err; /* Error flags, 0 when there is no error */
|
||||
char *errstr; /* String representation of error when applicable */
|
||||
|
||||
/* Function set for reply buildup and reply reader */
|
||||
redisReplyObjectFunctions *fn;
|
||||
void *reader;
|
||||
} redisContext;
|
||||
|
||||
void freeReplyObject(void *reply);
|
||||
void *redisReplyReaderCreate();
|
||||
int redisReplyReaderSetReplyObjectFunctions(void *reader, redisReplyObjectFunctions *fn);
|
||||
int redisReplyReaderSetPrivdata(void *reader, void *privdata);
|
||||
void *redisReplyReaderGetObject(void *reader);
|
||||
char *redisReplyReaderGetError(void *reader);
|
||||
void redisReplyReaderFree(void *ptr);
|
||||
void redisReplyReaderFeed(void *reader, char *buf, size_t len);
|
||||
int redisReplyReaderGetReply(void *reader, void **reply);
|
||||
|
||||
/* Functions to format a command according to the protocol. */
|
||||
int redisvFormatCommand(char **target, const char *format, va_list ap);
|
||||
int redisFormatCommand(char **target, const char *format, ...);
|
||||
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
redisContext *redisConnect(const char *ip, int port);
|
||||
redisContext *redisConnectNonBlock(const char *ip, int port);
|
||||
redisContext *redisConnectUnix(const char *path);
|
||||
redisContext *redisConnectUnixNonBlock(const char *path);
|
||||
int redisSetReplyObjectFunctions(redisContext *c, redisReplyObjectFunctions *fn);
|
||||
void redisFree(redisContext *c);
|
||||
int redisBufferRead(redisContext *c);
|
||||
int redisBufferWrite(redisContext *c, int *done);
|
||||
|
||||
/* In a blocking context, this function first checks if there are unconsumed
|
||||
* replies to return and returns one if so. Otherwise, it flushes the output
|
||||
* buffer to the socket and reads until it has a reply. In a non-blocking
|
||||
* context, it will return unconsumed replies until there are no more. */
|
||||
int redisGetReply(redisContext *c, void **reply);
|
||||
int redisGetReplyFromReader(redisContext *c, void **reply);
|
||||
|
||||
/* Write a command to the output buffer. Use these functions in blocking mode
|
||||
* to get a pipeline of commands. */
|
||||
void redisvAppendCommand(redisContext *c, const char *format, va_list ap);
|
||||
void redisAppendCommand(redisContext *c, const char *format, ...);
|
||||
void redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
/* Issue a command to Redis. In a blocking context, it is identical to calling
|
||||
* redisAppendCommand, followed by redisGetReply. The function will return
|
||||
* NULL if there was an error in performing the request, otherwise it will
|
||||
* return the reply. In a non-blocking context, it is identical to calling
|
||||
* only redisAppendCommand and will always return NULL. */
|
||||
void *redisvCommand(redisContext *c, const char *format, va_list ap);
|
||||
void *redisCommand(redisContext *c, const char *format, ...);
|
||||
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Vendored
+170
@@ -0,0 +1,170 @@
|
||||
/* Extracted from anet.c to work properly with Hiredis error reporting.
|
||||
*
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "fmacros.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "net.h"
|
||||
#include "sds.h"
|
||||
|
||||
/* Forward declaration */
|
||||
void __redisSetError(redisContext *c, int type, sds err);
|
||||
|
||||
static int redisCreateSocket(redisContext *c, int type) {
|
||||
int s, on = 1;
|
||||
if ((s = socket(type, SOCK_STREAM, 0)) == -1) {
|
||||
__redisSetError(c,REDIS_ERR_IO,NULL);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if (type == AF_INET) {
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
|
||||
__redisSetError(c,REDIS_ERR_IO,NULL);
|
||||
close(s);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static int redisSetNonBlock(redisContext *c, int fd) {
|
||||
int flags;
|
||||
|
||||
/* Set the socket nonblocking.
|
||||
* Note that fcntl(2) for F_GETFL and F_SETFL can't be
|
||||
* interrupted by a signal. */
|
||||
if ((flags = fcntl(fd, F_GETFL)) == -1) {
|
||||
__redisSetError(c,REDIS_ERR_IO,
|
||||
sdscatprintf(sdsempty(), "fcntl(F_GETFL): %s", strerror(errno)));
|
||||
close(fd);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
|
||||
__redisSetError(c,REDIS_ERR_IO,
|
||||
sdscatprintf(sdsempty(), "fcntl(F_SETFL,O_NONBLOCK): %s", strerror(errno)));
|
||||
close(fd);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static int redisSetTcpNoDelay(redisContext *c, int fd) {
|
||||
int yes = 1;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
|
||||
__redisSetError(c,REDIS_ERR_IO,
|
||||
sdscatprintf(sdsempty(), "setsockopt(TCP_NODELAY): %s", strerror(errno)));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redisContextConnectTcp(redisContext *c, const char *addr, int port) {
|
||||
int s;
|
||||
int blocking = (c->flags & REDIS_BLOCK);
|
||||
struct sockaddr_in sa;
|
||||
|
||||
if ((s = redisCreateSocket(c,AF_INET)) == REDIS_ERR)
|
||||
return REDIS_ERR;
|
||||
if (!blocking && redisSetNonBlock(c,s) == REDIS_ERR)
|
||||
return REDIS_ERR;
|
||||
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_port = htons(port);
|
||||
if (inet_aton(addr, &sa.sin_addr) == 0) {
|
||||
struct hostent *he;
|
||||
|
||||
he = gethostbyname(addr);
|
||||
if (he == NULL) {
|
||||
__redisSetError(c,REDIS_ERR_OTHER,
|
||||
sdscatprintf(sdsempty(),"Can't resolve: %s",addr));
|
||||
close(s);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
|
||||
}
|
||||
|
||||
if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
|
||||
if (errno == EINPROGRESS && !blocking) {
|
||||
/* This is ok. */
|
||||
} else {
|
||||
__redisSetError(c,REDIS_ERR_IO,NULL);
|
||||
close(s);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
if (redisSetTcpNoDelay(c,s) != REDIS_OK) {
|
||||
close(s);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
c->fd = s;
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
int redisContextConnectUnix(redisContext *c, const char *path) {
|
||||
int s;
|
||||
int blocking = (c->flags & REDIS_BLOCK);
|
||||
struct sockaddr_un sa;
|
||||
|
||||
if ((s = redisCreateSocket(c,AF_LOCAL)) == REDIS_ERR)
|
||||
return REDIS_ERR;
|
||||
if (!blocking && redisSetNonBlock(c,s) != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
|
||||
sa.sun_family = AF_LOCAL;
|
||||
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
|
||||
if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
|
||||
if (errno == EINPROGRESS && !blocking) {
|
||||
/* This is ok. */
|
||||
} else {
|
||||
__redisSetError(c,REDIS_ERR_IO,NULL);
|
||||
close(s);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
c->fd = s;
|
||||
c->flags |= REDIS_CONNECTED;
|
||||
return REDIS_OK;
|
||||
}
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
/* Extracted from anet.c to work properly with Hiredis error reporting.
|
||||
*
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __NET_H
|
||||
#define __NET_H
|
||||
|
||||
#include "hiredis.h"
|
||||
|
||||
#if defined(__sun)
|
||||
#define AF_LOCAL AF_UNIX
|
||||
#endif
|
||||
|
||||
int redisContextConnectTcp(redisContext *c, const char *addr, int port);
|
||||
int redisContextConnectUnix(redisContext *c, const char *path);
|
||||
|
||||
#endif
|
||||
Vendored
+161
-19
@@ -33,10 +33,8 @@
|
||||
#include "sds.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "zmalloc.h"
|
||||
|
||||
static void sdsOomAbort(void) {
|
||||
fprintf(stderr,"SDS: Out Of Memory (SDS_ABORT_ON_OOM defined)\n");
|
||||
@@ -46,7 +44,7 @@ static void sdsOomAbort(void) {
|
||||
sds sdsnewlen(const void *init, size_t initlen) {
|
||||
struct sdshdr *sh;
|
||||
|
||||
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
|
||||
sh = malloc(sizeof(struct sdshdr)+initlen+1);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (sh == NULL) sdsOomAbort();
|
||||
#else
|
||||
@@ -82,7 +80,7 @@ sds sdsdup(const sds s) {
|
||||
|
||||
void sdsfree(sds s) {
|
||||
if (s == NULL) return;
|
||||
zfree(s-sizeof(struct sdshdr));
|
||||
free(s-sizeof(struct sdshdr));
|
||||
}
|
||||
|
||||
size_t sdsavail(sds s) {
|
||||
@@ -106,7 +104,7 @@ static sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
len = sdslen(s);
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
newlen = (len+addlen)*2;
|
||||
newsh = zrealloc(sh, sizeof(struct sdshdr)+newlen+1);
|
||||
newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (newsh == NULL) sdsOomAbort();
|
||||
#else
|
||||
@@ -117,7 +115,7 @@ static sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
return newsh->buf;
|
||||
}
|
||||
|
||||
sds sdscatlen(sds s, void *t, size_t len) {
|
||||
sds sdscatlen(sds s, const void *t, size_t len) {
|
||||
struct sdshdr *sh;
|
||||
size_t curlen = sdslen(s);
|
||||
|
||||
@@ -131,7 +129,7 @@ sds sdscatlen(sds s, void *t, size_t len) {
|
||||
return s;
|
||||
}
|
||||
|
||||
sds sdscat(sds s, char *t) {
|
||||
sds sdscat(sds s, const char *t) {
|
||||
return sdscatlen(s, t, strlen(t));
|
||||
}
|
||||
|
||||
@@ -156,31 +154,39 @@ sds sdscpy(sds s, char *t) {
|
||||
return sdscpylen(s, t, strlen(t));
|
||||
}
|
||||
|
||||
sds sdscatprintf(sds s, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
|
||||
va_list cpy;
|
||||
char *buf, *t;
|
||||
size_t buflen = 16;
|
||||
|
||||
while(1) {
|
||||
buf = zmalloc(buflen);
|
||||
buf = malloc(buflen);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (buf == NULL) sdsOomAbort();
|
||||
#else
|
||||
if (buf == NULL) return NULL;
|
||||
#endif
|
||||
buf[buflen-2] = '\0';
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, buflen, fmt, ap);
|
||||
va_end(ap);
|
||||
va_copy(cpy,ap);
|
||||
vsnprintf(buf, buflen, fmt, cpy);
|
||||
if (buf[buflen-2] != '\0') {
|
||||
zfree(buf);
|
||||
free(buf);
|
||||
buflen *= 2;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
t = sdscat(s, buf);
|
||||
zfree(buf);
|
||||
free(buf);
|
||||
return t;
|
||||
}
|
||||
|
||||
sds sdscatprintf(sds s, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
char *t;
|
||||
va_start(ap, fmt);
|
||||
t = sdscatvprintf(s,fmt,ap);
|
||||
va_end(ap);
|
||||
return t;
|
||||
}
|
||||
|
||||
@@ -201,7 +207,7 @@ sds sdstrim(sds s, const char *cset) {
|
||||
return s;
|
||||
}
|
||||
|
||||
sds sdsrange(sds s, long start, long end) {
|
||||
sds sdsrange(sds s, int start, int end) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
size_t newlen, len = sdslen(s);
|
||||
|
||||
@@ -272,7 +278,7 @@ int sdscmp(sds s1, sds s2) {
|
||||
sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
|
||||
int elements = 0, slots = 5, start = 0, j;
|
||||
|
||||
sds *tokens = zmalloc(sizeof(sds)*slots);
|
||||
sds *tokens = malloc(sizeof(sds)*slots);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (tokens == NULL) sdsOomAbort();
|
||||
#endif
|
||||
@@ -287,7 +293,7 @@ sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
|
||||
sds *newtokens;
|
||||
|
||||
slots *= 2;
|
||||
newtokens = zrealloc(tokens,sizeof(sds)*slots);
|
||||
newtokens = realloc(tokens,sizeof(sds)*slots);
|
||||
if (newtokens == NULL) {
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
sdsOomAbort();
|
||||
@@ -330,8 +336,144 @@ cleanup:
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < elements; i++) sdsfree(tokens[i]);
|
||||
zfree(tokens);
|
||||
free(tokens);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void sdsfreesplitres(sds *tokens, int count) {
|
||||
if (!tokens) return;
|
||||
while(count--)
|
||||
sdsfree(tokens[count]);
|
||||
free(tokens);
|
||||
}
|
||||
|
||||
sds sdsfromlonglong(long long value) {
|
||||
char buf[32], *p;
|
||||
unsigned long long v;
|
||||
|
||||
v = (value < 0) ? -value : value;
|
||||
p = buf+31; /* point to the last character */
|
||||
do {
|
||||
*p-- = '0'+(v%10);
|
||||
v /= 10;
|
||||
} while(v);
|
||||
if (value < 0) *p-- = '-';
|
||||
p++;
|
||||
return sdsnewlen(p,32-(p-buf));
|
||||
}
|
||||
|
||||
sds sdscatrepr(sds s, char *p, size_t len) {
|
||||
s = sdscatlen(s,"\"",1);
|
||||
while(len--) {
|
||||
switch(*p) {
|
||||
case '\\':
|
||||
case '"':
|
||||
s = sdscatprintf(s,"\\%c",*p);
|
||||
break;
|
||||
case '\n': s = sdscatlen(s,"\\n",1); break;
|
||||
case '\r': s = sdscatlen(s,"\\r",1); break;
|
||||
case '\t': s = sdscatlen(s,"\\t",1); break;
|
||||
case '\a': s = sdscatlen(s,"\\a",1); break;
|
||||
case '\b': s = sdscatlen(s,"\\b",1); break;
|
||||
default:
|
||||
if (isprint(*p))
|
||||
s = sdscatprintf(s,"%c",*p);
|
||||
else
|
||||
s = sdscatprintf(s,"\\x%02x",(unsigned char)*p);
|
||||
break;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
return sdscatlen(s,"\"",1);
|
||||
}
|
||||
|
||||
/* Split a line into arguments, where every argument can be in the
|
||||
* following programming-language REPL-alike form:
|
||||
*
|
||||
* foo bar "newline are supported\n" and "\xff\x00otherstuff"
|
||||
*
|
||||
* The number of arguments is stored into *argc, and an array
|
||||
* of sds is returned. The caller should sdsfree() all the returned
|
||||
* strings and finally free() the array itself.
|
||||
*
|
||||
* Note that sdscatrepr() is able to convert back a string into
|
||||
* a quoted string in the same format sdssplitargs() is able to parse.
|
||||
*/
|
||||
sds *sdssplitargs(char *line, int *argc) {
|
||||
char *p = line;
|
||||
char *current = NULL;
|
||||
char **vector = NULL;
|
||||
|
||||
*argc = 0;
|
||||
while(1) {
|
||||
/* skip blanks */
|
||||
while(*p && isspace(*p)) p++;
|
||||
if (*p) {
|
||||
/* get a token */
|
||||
int inq=0; /* set to 1 if we are in "quotes" */
|
||||
int done=0;
|
||||
|
||||
if (current == NULL) current = sdsempty();
|
||||
while(!done) {
|
||||
if (inq) {
|
||||
if (*p == '\\' && *(p+1)) {
|
||||
char c;
|
||||
|
||||
p++;
|
||||
switch(*p) {
|
||||
case 'n': c = '\n'; break;
|
||||
case 'r': c = '\r'; break;
|
||||
case 't': c = '\t'; break;
|
||||
case 'b': c = '\b'; break;
|
||||
case 'a': c = '\a'; break;
|
||||
default: c = *p; break;
|
||||
}
|
||||
current = sdscatlen(current,&c,1);
|
||||
} else if (*p == '"') {
|
||||
/* closing quote must be followed by a space */
|
||||
if (*(p+1) && !isspace(*(p+1))) goto err;
|
||||
done=1;
|
||||
} else if (!*p) {
|
||||
/* unterminated quotes */
|
||||
goto err;
|
||||
} else {
|
||||
current = sdscatlen(current,p,1);
|
||||
}
|
||||
} else {
|
||||
switch(*p) {
|
||||
case ' ':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
case '\0':
|
||||
done=1;
|
||||
break;
|
||||
case '"':
|
||||
inq=1;
|
||||
break;
|
||||
default:
|
||||
current = sdscatlen(current,p,1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (*p) p++;
|
||||
}
|
||||
/* add the token to the vector */
|
||||
vector = realloc(vector,((*argc)+1)*sizeof(char*));
|
||||
vector[*argc] = current;
|
||||
(*argc)++;
|
||||
current = NULL;
|
||||
} else {
|
||||
return vector;
|
||||
}
|
||||
}
|
||||
|
||||
err:
|
||||
while((*argc)--)
|
||||
sdsfree(vector[*argc]);
|
||||
free(vector);
|
||||
if (current) sdsfree(current);
|
||||
return NULL;
|
||||
}
|
||||
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
/* SDSLib, A C dynamic strings library
|
||||
*
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __SDS_H
|
||||
#define __SDS_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
typedef char *sds;
|
||||
|
||||
struct sdshdr {
|
||||
int len;
|
||||
int free;
|
||||
char buf[];
|
||||
};
|
||||
|
||||
sds sdsnewlen(const void *init, size_t initlen);
|
||||
sds sdsnew(const char *init);
|
||||
sds sdsempty();
|
||||
size_t sdslen(const sds s);
|
||||
sds sdsdup(const sds s);
|
||||
void sdsfree(sds s);
|
||||
size_t sdsavail(sds s);
|
||||
sds sdscatlen(sds s, const void *t, size_t len);
|
||||
sds sdscat(sds s, const char *t);
|
||||
sds sdscpylen(sds s, char *t, size_t len);
|
||||
sds sdscpy(sds s, char *t);
|
||||
|
||||
sds sdscatvprintf(sds s, const char *fmt, va_list ap);
|
||||
#ifdef __GNUC__
|
||||
sds sdscatprintf(sds s, const char *fmt, ...)
|
||||
__attribute__((format(printf, 2, 3)));
|
||||
#else
|
||||
sds sdscatprintf(sds s, const char *fmt, ...);
|
||||
#endif
|
||||
|
||||
sds sdstrim(sds s, const char *cset);
|
||||
sds sdsrange(sds s, int start, int end);
|
||||
void sdsupdatelen(sds s);
|
||||
int sdscmp(sds s1, sds s2);
|
||||
sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count);
|
||||
void sdsfreesplitres(sds *tokens, int count);
|
||||
void sdstolower(sds s);
|
||||
void sdstoupper(sds s);
|
||||
sds sdsfromlonglong(long long value);
|
||||
sds sdscatrepr(sds s, char *p, size_t len);
|
||||
sds *sdssplitargs(char *line, int *argc);
|
||||
|
||||
#endif
|
||||
Vendored
+479
@@ -0,0 +1,479 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <sys/time.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "hiredis.h"
|
||||
|
||||
/* The following lines make up our testing "framework" :) */
|
||||
static int tests = 0, fails = 0;
|
||||
#define test(_s) { printf("#%02d ", ++tests); printf(_s); }
|
||||
#define test_cond(_c) if(_c) printf("PASSED\n"); else {printf("FAILED\n"); fails++;}
|
||||
|
||||
static long long usec(void) {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv,NULL);
|
||||
return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
|
||||
}
|
||||
|
||||
static int use_unix = 0;
|
||||
static redisContext *blocking_context = NULL;
|
||||
static void __connect(redisContext **target) {
|
||||
*target = blocking_context = (use_unix ?
|
||||
redisConnectUnix("/tmp/redis.sock") : redisConnect((char*)"127.0.0.1", 6379));
|
||||
if (blocking_context->err) {
|
||||
printf("Connection error: %s\n", blocking_context->errstr);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_format_commands() {
|
||||
char *cmd;
|
||||
int len;
|
||||
|
||||
test("Format command without interpolation: ");
|
||||
len = redisFormatCommand(&cmd,"SET foo bar");
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with %%s string interpolation: ");
|
||||
len = redisFormatCommand(&cmd,"SET %s %s","foo","bar");
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with %%s and an empty string: ");
|
||||
len = redisFormatCommand(&cmd,"SET %s %s","foo","");
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(0+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with %%b string interpolation: ");
|
||||
len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"b\0r",3);
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with %%b and an empty string: ");
|
||||
len = redisFormatCommand(&cmd,"SET %b %b","foo",3,"",0);
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(0+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with literal %%: ");
|
||||
len = redisFormatCommand(&cmd,"SET %% %%");
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(1+2)+4+(1+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with printf-delegation (long long): ");
|
||||
len = redisFormatCommand(&cmd,"key:%08lld",1234ll);
|
||||
test_cond(strncmp(cmd,"*1\r\n$12\r\nkey:00001234\r\n",len) == 0 &&
|
||||
len == 4+5+(12+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with printf-delegation (float): ");
|
||||
len = redisFormatCommand(&cmd,"v:%06.1f",12.34f);
|
||||
test_cond(strncmp(cmd,"*1\r\n$8\r\nv:0012.3\r\n",len) == 0 &&
|
||||
len == 4+4+(8+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with printf-delegation and extra interpolation: ");
|
||||
len = redisFormatCommand(&cmd,"key:%d %b",1234,"foo",3);
|
||||
test_cond(strncmp(cmd,"*2\r\n$8\r\nkey:1234\r\n$3\r\nfoo\r\n",len) == 0 &&
|
||||
len == 4+4+(8+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command with wrong printf format and extra interpolation: ");
|
||||
len = redisFormatCommand(&cmd,"key:%08p %b",1234,"foo",3);
|
||||
test_cond(strncmp(cmd,"*2\r\n$6\r\nkey:8p\r\n$3\r\nfoo\r\n",len) == 0 &&
|
||||
len == 4+4+(6+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
const char *argv[3];
|
||||
argv[0] = "SET";
|
||||
argv[1] = "foo\0xxx";
|
||||
argv[2] = "bar";
|
||||
size_t lens[3] = { 3, 7, 3 };
|
||||
int argc = 3;
|
||||
|
||||
test("Format command by passing argc/argv without lengths: ");
|
||||
len = redisFormatCommandArgv(&cmd,argc,argv,NULL);
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(3+2)+4+(3+2));
|
||||
free(cmd);
|
||||
|
||||
test("Format command by passing argc/argv with lengths: ");
|
||||
len = redisFormatCommandArgv(&cmd,argc,argv,lens);
|
||||
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
|
||||
len == 4+4+(3+2)+4+(7+2)+4+(3+2));
|
||||
free(cmd);
|
||||
}
|
||||
|
||||
static void test_blocking_connection() {
|
||||
redisContext *c;
|
||||
redisReply *reply;
|
||||
int major, minor;
|
||||
|
||||
test("Returns error when host cannot be resolved: ");
|
||||
c = redisConnect((char*)"idontexist.local", 6379);
|
||||
test_cond(c->err == REDIS_ERR_OTHER &&
|
||||
strcmp(c->errstr,"Can't resolve: idontexist.local") == 0);
|
||||
redisFree(c);
|
||||
|
||||
test("Returns error when the port is not open: ");
|
||||
c = redisConnect((char*)"localhost", 56380);
|
||||
test_cond(c->err == REDIS_ERR_IO &&
|
||||
strcmp(c->errstr,"Connection refused") == 0);
|
||||
redisFree(c);
|
||||
|
||||
__connect(&c);
|
||||
test("Is able to deliver commands: ");
|
||||
reply = redisCommand(c,"PING");
|
||||
test_cond(reply->type == REDIS_REPLY_STATUS &&
|
||||
strcasecmp(reply->str,"pong") == 0)
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Switch to DB 9 for testing, now that we know we can chat. */
|
||||
reply = redisCommand(c,"SELECT 9");
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* Make sure the DB is emtpy */
|
||||
reply = redisCommand(c,"DBSIZE");
|
||||
if (reply->type != REDIS_REPLY_INTEGER || reply->integer != 0) {
|
||||
printf("Database #9 is not empty, test can not continue\n");
|
||||
exit(1);
|
||||
}
|
||||
freeReplyObject(reply);
|
||||
|
||||
test("Is a able to send commands verbatim: ");
|
||||
reply = redisCommand(c,"SET foo bar");
|
||||
test_cond (reply->type == REDIS_REPLY_STATUS &&
|
||||
strcasecmp(reply->str,"ok") == 0)
|
||||
freeReplyObject(reply);
|
||||
|
||||
test("%%s String interpolation works: ");
|
||||
reply = redisCommand(c,"SET %s %s","foo","hello world");
|
||||
freeReplyObject(reply);
|
||||
reply = redisCommand(c,"GET foo");
|
||||
test_cond(reply->type == REDIS_REPLY_STRING &&
|
||||
strcmp(reply->str,"hello world") == 0);
|
||||
freeReplyObject(reply);
|
||||
|
||||
test("%%b String interpolation works: ");
|
||||
reply = redisCommand(c,"SET %b %b","foo",3,"hello\x00world",11);
|
||||
freeReplyObject(reply);
|
||||
reply = redisCommand(c,"GET foo");
|
||||
test_cond(reply->type == REDIS_REPLY_STRING &&
|
||||
memcmp(reply->str,"hello\x00world",11) == 0)
|
||||
|
||||
test("Binary reply length is correct: ");
|
||||
test_cond(reply->len == 11)
|
||||
freeReplyObject(reply);
|
||||
|
||||
test("Can parse nil replies: ");
|
||||
reply = redisCommand(c,"GET nokey");
|
||||
test_cond(reply->type == REDIS_REPLY_NIL)
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* test 7 */
|
||||
test("Can parse integer replies: ");
|
||||
reply = redisCommand(c,"INCR mycounter");
|
||||
test_cond(reply->type == REDIS_REPLY_INTEGER && reply->integer == 1)
|
||||
freeReplyObject(reply);
|
||||
|
||||
test("Can parse multi bulk replies: ");
|
||||
freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
|
||||
freeReplyObject(redisCommand(c,"LPUSH mylist bar"));
|
||||
reply = redisCommand(c,"LRANGE mylist 0 -1");
|
||||
test_cond(reply->type == REDIS_REPLY_ARRAY &&
|
||||
reply->elements == 2 &&
|
||||
!memcmp(reply->element[0]->str,"bar",3) &&
|
||||
!memcmp(reply->element[1]->str,"foo",3))
|
||||
freeReplyObject(reply);
|
||||
|
||||
/* m/e with multi bulk reply *before* other reply.
|
||||
* specifically test ordering of reply items to parse. */
|
||||
test("Can handle nested multi bulk replies: ");
|
||||
freeReplyObject(redisCommand(c,"MULTI"));
|
||||
freeReplyObject(redisCommand(c,"LRANGE mylist 0 -1"));
|
||||
freeReplyObject(redisCommand(c,"PING"));
|
||||
reply = (redisCommand(c,"EXEC"));
|
||||
test_cond(reply->type == REDIS_REPLY_ARRAY &&
|
||||
reply->elements == 2 &&
|
||||
reply->element[0]->type == REDIS_REPLY_ARRAY &&
|
||||
reply->element[0]->elements == 2 &&
|
||||
!memcmp(reply->element[0]->element[0]->str,"bar",3) &&
|
||||
!memcmp(reply->element[0]->element[1]->str,"foo",3) &&
|
||||
reply->element[1]->type == REDIS_REPLY_STATUS &&
|
||||
strcasecmp(reply->element[1]->str,"pong") == 0);
|
||||
freeReplyObject(reply);
|
||||
|
||||
{
|
||||
/* Find out Redis version to determine the path for the next test */
|
||||
const char *field = "redis_version:";
|
||||
char *p, *eptr;
|
||||
|
||||
reply = redisCommand(c,"INFO");
|
||||
p = strstr(reply->str,field);
|
||||
major = strtol(p+strlen(field),&eptr,10);
|
||||
p = eptr+1; /* char next to the first "." */
|
||||
minor = strtol(p,&eptr,10);
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
|
||||
test("Returns I/O error when the connection is lost: ");
|
||||
reply = redisCommand(c,"QUIT");
|
||||
if (major >= 2 && minor > 0) {
|
||||
/* > 2.0 returns OK on QUIT and read() should be issued once more
|
||||
* to know the descriptor is at EOF. */
|
||||
test_cond(strcasecmp(reply->str,"OK") == 0 &&
|
||||
redisGetReply(c,(void**)&reply) == REDIS_ERR);
|
||||
freeReplyObject(reply);
|
||||
} else {
|
||||
test_cond(reply == NULL);
|
||||
}
|
||||
|
||||
/* On 2.0, QUIT will cause the connection to be closed immediately and
|
||||
* the read(2) for the reply on QUIT will set the error to EOF.
|
||||
* On >2.0, QUIT will return with OK and another read(2) needed to be
|
||||
* issued to find out the socket was closed by the server. In both
|
||||
* conditions, the error will be set to EOF. */
|
||||
assert(c->err == REDIS_ERR_EOF &&
|
||||
strcmp(c->errstr,"Server closed the connection") == 0);
|
||||
|
||||
/* Clean up context and reconnect again */
|
||||
redisFree(c);
|
||||
__connect(&c);
|
||||
}
|
||||
|
||||
static void test_reply_reader() {
|
||||
void *reader;
|
||||
void *reply;
|
||||
char *err;
|
||||
int ret;
|
||||
|
||||
test("Error handling in reply parser: ");
|
||||
reader = redisReplyReaderCreate();
|
||||
redisReplyReaderFeed(reader,(char*)"@foo\r\n",6);
|
||||
ret = redisReplyReaderGetReply(reader,NULL);
|
||||
err = redisReplyReaderGetError(reader);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0);
|
||||
redisReplyReaderFree(reader);
|
||||
|
||||
/* when the reply already contains multiple items, they must be free'd
|
||||
* on an error. valgrind will bark when this doesn't happen. */
|
||||
test("Memory cleanup in reply parser: ");
|
||||
reader = redisReplyReaderCreate();
|
||||
redisReplyReaderFeed(reader,(char*)"*2\r\n",4);
|
||||
redisReplyReaderFeed(reader,(char*)"$5\r\nhello\r\n",11);
|
||||
redisReplyReaderFeed(reader,(char*)"@foo\r\n",6);
|
||||
ret = redisReplyReaderGetReply(reader,NULL);
|
||||
err = redisReplyReaderGetError(reader);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strcasecmp(err,"Protocol error, got \"@\" as reply type byte") == 0);
|
||||
redisReplyReaderFree(reader);
|
||||
|
||||
test("Set error on nested multi bulks with depth > 1: ");
|
||||
reader = redisReplyReaderCreate();
|
||||
redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
|
||||
redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
|
||||
redisReplyReaderFeed(reader,(char*)"*1\r\n",4);
|
||||
ret = redisReplyReaderGetReply(reader,NULL);
|
||||
err = redisReplyReaderGetError(reader);
|
||||
test_cond(ret == REDIS_ERR &&
|
||||
strncasecmp(err,"No support for",14) == 0);
|
||||
redisReplyReaderFree(reader);
|
||||
|
||||
test("Works with NULL functions for reply: ");
|
||||
reader = redisReplyReaderCreate();
|
||||
redisReplyReaderSetReplyObjectFunctions(reader,NULL);
|
||||
redisReplyReaderFeed(reader,(char*)"+OK\r\n",5);
|
||||
ret = redisReplyReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
|
||||
redisReplyReaderFree(reader);
|
||||
|
||||
test("Works when a single newline (\\r\\n) covers two calls to feed: ");
|
||||
reader = redisReplyReaderCreate();
|
||||
redisReplyReaderSetReplyObjectFunctions(reader,NULL);
|
||||
redisReplyReaderFeed(reader,(char*)"+OK\r",4);
|
||||
ret = redisReplyReaderGetReply(reader,&reply);
|
||||
assert(ret == REDIS_OK && reply == NULL);
|
||||
redisReplyReaderFeed(reader,(char*)"\n",1);
|
||||
ret = redisReplyReaderGetReply(reader,&reply);
|
||||
test_cond(ret == REDIS_OK && reply == (void*)REDIS_REPLY_STATUS);
|
||||
redisReplyReaderFree(reader);
|
||||
}
|
||||
|
||||
static void test_throughput() {
|
||||
int i;
|
||||
long long t1, t2;
|
||||
redisContext *c = blocking_context;
|
||||
redisReply **replies;
|
||||
|
||||
test("Throughput:\n");
|
||||
for (i = 0; i < 500; i++)
|
||||
freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
|
||||
|
||||
replies = malloc(sizeof(redisReply*)*1000);
|
||||
t1 = usec();
|
||||
for (i = 0; i < 1000; i++) {
|
||||
replies[i] = redisCommand(c,"PING");
|
||||
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_STATUS);
|
||||
}
|
||||
t2 = usec();
|
||||
for (i = 0; i < 1000; i++) freeReplyObject(replies[i]);
|
||||
free(replies);
|
||||
printf("\t(1000x PING: %.2fs)\n", (t2-t1)/1000000.0);
|
||||
|
||||
replies = malloc(sizeof(redisReply*)*1000);
|
||||
t1 = usec();
|
||||
for (i = 0; i < 1000; i++) {
|
||||
replies[i] = redisCommand(c,"LRANGE mylist 0 499");
|
||||
assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_ARRAY);
|
||||
assert(replies[i] != NULL && replies[i]->elements == 500);
|
||||
}
|
||||
t2 = usec();
|
||||
for (i = 0; i < 1000; i++) freeReplyObject(replies[i]);
|
||||
free(replies);
|
||||
printf("\t(1000x LRANGE with 500 elements: %.2fs)\n", (t2-t1)/1000000.0);
|
||||
}
|
||||
|
||||
static void cleanup() {
|
||||
redisContext *c = blocking_context;
|
||||
redisReply *reply;
|
||||
|
||||
/* Make sure we're on DB 9 */
|
||||
reply = redisCommand(c,"SELECT 9");
|
||||
assert(reply != NULL); freeReplyObject(reply);
|
||||
reply = redisCommand(c,"FLUSHDB");
|
||||
assert(reply != NULL); freeReplyObject(reply);
|
||||
redisFree(c);
|
||||
}
|
||||
|
||||
// static long __test_callback_flags = 0;
|
||||
// static void __test_callback(redisContext *c, void *privdata) {
|
||||
// ((void)c);
|
||||
// /* Shift to detect execution order */
|
||||
// __test_callback_flags <<= 8;
|
||||
// __test_callback_flags |= (long)privdata;
|
||||
// }
|
||||
//
|
||||
// static void __test_reply_callback(redisContext *c, redisReply *reply, void *privdata) {
|
||||
// ((void)c);
|
||||
// /* Shift to detect execution order */
|
||||
// __test_callback_flags <<= 8;
|
||||
// __test_callback_flags |= (long)privdata;
|
||||
// if (reply) freeReplyObject(reply);
|
||||
// }
|
||||
//
|
||||
// static redisContext *__connect_nonblock() {
|
||||
// /* Reset callback flags */
|
||||
// __test_callback_flags = 0;
|
||||
// return redisConnectNonBlock("127.0.0.1", 6379, NULL);
|
||||
// }
|
||||
//
|
||||
// static void test_nonblocking_connection() {
|
||||
// redisContext *c;
|
||||
// int wdone = 0;
|
||||
//
|
||||
// test("Calls command callback when command is issued: ");
|
||||
// c = __connect_nonblock();
|
||||
// redisSetCommandCallback(c,__test_callback,(void*)1);
|
||||
// redisCommand(c,"PING");
|
||||
// test_cond(__test_callback_flags == 1);
|
||||
// redisFree(c);
|
||||
//
|
||||
// test("Calls disconnect callback on redisDisconnect: ");
|
||||
// c = __connect_nonblock();
|
||||
// redisSetDisconnectCallback(c,__test_callback,(void*)2);
|
||||
// redisDisconnect(c);
|
||||
// test_cond(__test_callback_flags == 2);
|
||||
// redisFree(c);
|
||||
//
|
||||
// test("Calls disconnect callback and free callback on redisFree: ");
|
||||
// c = __connect_nonblock();
|
||||
// redisSetDisconnectCallback(c,__test_callback,(void*)2);
|
||||
// redisSetFreeCallback(c,__test_callback,(void*)4);
|
||||
// redisFree(c);
|
||||
// test_cond(__test_callback_flags == ((2 << 8) | 4));
|
||||
//
|
||||
// test("redisBufferWrite against empty write buffer: ");
|
||||
// c = __connect_nonblock();
|
||||
// test_cond(redisBufferWrite(c,&wdone) == REDIS_OK && wdone == 1);
|
||||
// redisFree(c);
|
||||
//
|
||||
// test("redisBufferWrite against not yet connected fd: ");
|
||||
// c = __connect_nonblock();
|
||||
// redisCommand(c,"PING");
|
||||
// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
|
||||
// strncmp(c->error,"write:",6) == 0);
|
||||
// redisFree(c);
|
||||
//
|
||||
// test("redisBufferWrite against closed fd: ");
|
||||
// c = __connect_nonblock();
|
||||
// redisCommand(c,"PING");
|
||||
// redisDisconnect(c);
|
||||
// test_cond(redisBufferWrite(c,NULL) == REDIS_ERR &&
|
||||
// strncmp(c->error,"write:",6) == 0);
|
||||
// redisFree(c);
|
||||
//
|
||||
// test("Process callbacks in the right sequence: ");
|
||||
// c = __connect_nonblock();
|
||||
// redisCommandWithCallback(c,__test_reply_callback,(void*)1,"PING");
|
||||
// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
|
||||
// redisCommandWithCallback(c,__test_reply_callback,(void*)3,"PING");
|
||||
//
|
||||
// /* Write output buffer */
|
||||
// wdone = 0;
|
||||
// while(!wdone) {
|
||||
// usleep(500);
|
||||
// redisBufferWrite(c,&wdone);
|
||||
// }
|
||||
//
|
||||
// /* Read until at least one callback is executed (the 3 replies will
|
||||
// * arrive in a single packet, causing all callbacks to be executed in
|
||||
// * a single pass). */
|
||||
// while(__test_callback_flags == 0) {
|
||||
// assert(redisBufferRead(c) == REDIS_OK);
|
||||
// redisProcessCallbacks(c);
|
||||
// }
|
||||
// test_cond(__test_callback_flags == 0x010203);
|
||||
// redisFree(c);
|
||||
//
|
||||
// test("redisDisconnect executes pending callbacks with NULL reply: ");
|
||||
// c = __connect_nonblock();
|
||||
// redisSetDisconnectCallback(c,__test_callback,(void*)1);
|
||||
// redisCommandWithCallback(c,__test_reply_callback,(void*)2,"PING");
|
||||
// redisDisconnect(c);
|
||||
// test_cond(__test_callback_flags == 0x0201);
|
||||
// redisFree(c);
|
||||
// }
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc > 1) {
|
||||
if (strcmp(argv[1],"-s") == 0)
|
||||
use_unix = 1;
|
||||
}
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
test_format_commands();
|
||||
test_blocking_connection();
|
||||
test_reply_reader();
|
||||
// test_nonblocking_connection();
|
||||
test_throughput();
|
||||
cleanup();
|
||||
|
||||
if (fails == 0) {
|
||||
printf("ALL TESTS PASSED\n");
|
||||
} else {
|
||||
printf("*** %d TESTS FAILED ***\n", fails);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Vendored
+8
-43
@@ -27,49 +27,14 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __REDIS_H__
|
||||
#define __REDIS_H__
|
||||
#ifndef __UTIL_H
|
||||
#define __UTIL_H
|
||||
#include <stdlib.h>
|
||||
|
||||
enum
|
||||
{
|
||||
REG_GS = 0,
|
||||
# define REG_GS REG_GS
|
||||
REG_FS,
|
||||
# define REG_FS REG_FS
|
||||
REG_ES,
|
||||
# define REG_ES REG_ES
|
||||
REG_DS,
|
||||
# define REG_DS REG_DS
|
||||
REG_EDI,
|
||||
# define REG_EDI REG_EDI
|
||||
REG_ESI,
|
||||
# define REG_ESI REG_ESI
|
||||
REG_EBP,
|
||||
# define REG_EBP REG_EBP
|
||||
REG_ESP,
|
||||
# define REG_ESP REG_ESP
|
||||
REG_EBX,
|
||||
# define REG_EBX REG_EBX
|
||||
REG_EDX,
|
||||
# define REG_EDX REG_EDX
|
||||
REG_ECX,
|
||||
# define REG_ECX REG_ECX
|
||||
REG_EAX,
|
||||
# define REG_EAX REG_EAX
|
||||
REG_TRAPNO,
|
||||
# define REG_TRAPNO REG_TRAPNO
|
||||
REG_ERR,
|
||||
# define REG_ERR REG_ERR
|
||||
REG_EIP,
|
||||
# define REG_EIP REG_EIP
|
||||
REG_CS,
|
||||
# define REG_CS REG_CS
|
||||
REG_EFL,
|
||||
# define REG_EFL REG_EFL
|
||||
REG_UESP,
|
||||
# define REG_UESP REG_UESP
|
||||
REG_SS
|
||||
# define REG_SS REG_SS
|
||||
};
|
||||
/* Abort on out of memory */
|
||||
static void redisOOM(void) {
|
||||
fprintf(stderr,"Out of memory in hiredis");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1 @@
|
||||
linenoise_example*
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
linenoise_example: linenoise.h linenoise.c
|
||||
|
||||
linenoise_example: linenoise.o example.o
|
||||
$(CC) $(ARCH) -Wall -W -Os -g -o linenoise_example linenoise.o example.o
|
||||
|
||||
.c.o:
|
||||
$(CC) $(ARCH) -c -Wall -W -Os -g $<
|
||||
|
||||
clean:
|
||||
rm -f linenoise_example *.o
|
||||
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
# Linenoise
|
||||
|
||||
A minimal, zero-config, BSD licensed, readline replacement.
|
||||
|
||||
News: linenoise is now part of [Android](http://android.git.kernel.org/?p=platform/system/core.git;a=tree;f=liblinenoise;h=56450eaed7f783760e5e6a5993ef75cde2e29dea;hb=HEAD Android)!
|
||||
|
||||
## Can a line editing library be 20k lines of code?
|
||||
|
||||
Line editing with some support for history is a really important feature for command line utilities. Instead of retyping almost the same stuff again and again it's just much better to hit the up arrow and edit on syntax errors, or in order to try a slightly different command. But apparently code dealing with terminals is some sort of Black Magic: readline is 30k lines of code, libedit 20k. Is it reasonable to link small utilities to huge libraries just to get a minimal support for line editing?
|
||||
|
||||
So what usually happens is either:
|
||||
|
||||
* Large programs with configure scripts disabling line editing if readline is not present in the system, or not supporting it at all since readline is GPL licensed and libedit (the BSD clone) is not as known and available as readline is (Readl world example of this problem: Tclsh).
|
||||
* Smaller programs not using a configure script not supporting line editing at all (A problem we had with Redis-cli for instance).
|
||||
|
||||
The result is a pollution of binaries without line editing support.
|
||||
|
||||
So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporing line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to linenoise if not.
|
||||
|
||||
## Terminals, in 2010.
|
||||
|
||||
Apparently almost every terminal you can happen to use today has some kind of support for VT100 alike escape sequences. So I tried to write a lib using just very basic VT100 features. The resulting library appears to work everywhere I tried to use it.
|
||||
|
||||
Since it's so young I guess there are a few bugs, or the lib may not compile or work with some operating system, but it's a matter of a few weeks and eventually we'll get it right, and there will be no excuses for not shipping command line tools without built-in line editing support.
|
||||
|
||||
The library is currently less than 400 lines of code. In order to use it in your project just look at the *example.c* file in the source distribution, it is trivial. Linenoise is BSD code, so you can use both in free software and commercial software.
|
||||
|
||||
## Tested with...
|
||||
|
||||
* Linux text only console ($TERM = linux)
|
||||
* Linux KDE terminal application ($TERM = xterm)
|
||||
* Linux xterm ($TERM = xterm)
|
||||
* Mac OS X iTerm ($TERM = xterm)
|
||||
* Mac OS X default Terminal.app ($TERM = xterm)
|
||||
* OpenBSD 4.5 through an OSX Terminal.app ($TERM = screen)
|
||||
* IBM AIX 6.1
|
||||
* FreeBSD xterm ($TERM = xterm)
|
||||
|
||||
Please test it everywhere you can and report back!
|
||||
|
||||
## Let's push this forward!
|
||||
|
||||
Please fork it and add something interesting and send me a pull request. What's especially interesting are fixes, new key bindings, completion.
|
||||
|
||||
Send feedbacks to antirez at gmail
|
||||
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "linenoise.h"
|
||||
|
||||
|
||||
void completion(const char *buf, linenoiseCompletions *lc) {
|
||||
if (buf[0] == 'h') {
|
||||
linenoiseAddCompletion(lc,"hello");
|
||||
linenoiseAddCompletion(lc,"hello there");
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
char *line;
|
||||
|
||||
linenoiseSetCompletionCallback(completion);
|
||||
linenoiseHistoryLoad("history.txt"); /* Load the history at startup */
|
||||
while((line = linenoise("hello> ")) != NULL) {
|
||||
if (line[0] != '\0') {
|
||||
printf("echo: '%s'\n", line);
|
||||
linenoiseHistoryAdd(line);
|
||||
linenoiseHistorySave("history.txt"); /* Save every new entry */
|
||||
}
|
||||
free(line);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Vendored
+609
@@ -0,0 +1,609 @@
|
||||
/* linenoise.c -- guerrilla line editing library against the idea that a
|
||||
* line editing lib needs to be 20,000 lines of C code.
|
||||
*
|
||||
* You can find the latest source code at:
|
||||
*
|
||||
* http://github.com/antirez/linenoise
|
||||
*
|
||||
* Does a number of crazy assumptions that happen to be true in 99.9999% of
|
||||
* the 2010 UNIX computers around.
|
||||
*
|
||||
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* References:
|
||||
* - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||
* - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
|
||||
*
|
||||
* Todo list:
|
||||
* - Switch to gets() if $TERM is something we can't support.
|
||||
* - Filter bogus Ctrl+<char> combinations.
|
||||
* - Win32 support
|
||||
*
|
||||
* Bloat:
|
||||
* - Completion?
|
||||
* - History search like Ctrl+r in readline?
|
||||
*
|
||||
* List of escape sequences used by this program, we do everything just
|
||||
* with three sequences. In order to be so cheap we may have some
|
||||
* flickering effect with some slow terminal, but the lesser sequences
|
||||
* the more compatible.
|
||||
*
|
||||
* CHA (Cursor Horizontal Absolute)
|
||||
* Sequence: ESC [ n G
|
||||
* Effect: moves cursor to column n
|
||||
*
|
||||
* EL (Erase Line)
|
||||
* Sequence: ESC [ n K
|
||||
* Effect: if n is 0 or missing, clear from cursor to end of line
|
||||
* Effect: if n is 1, clear from beginning of line to cursor
|
||||
* Effect: if n is 2, clear entire line
|
||||
*
|
||||
* CUF (CUrsor Forward)
|
||||
* Sequence: ESC [ n C
|
||||
* Effect: moves cursor forward of n chars
|
||||
*
|
||||
* The following are used to clear the screen: ESC [ H ESC [ 2 J
|
||||
* This is actually composed of two sequences:
|
||||
*
|
||||
* cursorhome
|
||||
* Sequence: ESC [ H
|
||||
* Effect: moves the cursor to upper left corner
|
||||
*
|
||||
* ED2 (Clear entire screen)
|
||||
* Sequence: ESC [ 2 J
|
||||
* Effect: clear the whole screen
|
||||
*
|
||||
*/
|
||||
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include "linenoise.h"
|
||||
|
||||
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
|
||||
#define LINENOISE_MAX_LINE 4096
|
||||
static char *unsupported_term[] = {"dumb","cons25",NULL};
|
||||
static linenoiseCompletionCallback *completionCallback = NULL;
|
||||
|
||||
static struct termios orig_termios; /* in order to restore at exit */
|
||||
static int rawmode = 0; /* for atexit() function to check if restore is needed*/
|
||||
static int atexit_registered = 0; /* register atexit just 1 time */
|
||||
static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
|
||||
static int history_len = 0;
|
||||
char **history = NULL;
|
||||
|
||||
static void linenoiseAtExit(void);
|
||||
int linenoiseHistoryAdd(const char *line);
|
||||
|
||||
static int isUnsupportedTerm(void) {
|
||||
char *term = getenv("TERM");
|
||||
int j;
|
||||
|
||||
if (term == NULL) return 0;
|
||||
for (j = 0; unsupported_term[j]; j++)
|
||||
if (!strcasecmp(term,unsupported_term[j])) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void freeHistory(void) {
|
||||
if (history) {
|
||||
int j;
|
||||
|
||||
for (j = 0; j < history_len; j++)
|
||||
free(history[j]);
|
||||
free(history);
|
||||
}
|
||||
}
|
||||
|
||||
static int enableRawMode(int fd) {
|
||||
struct termios raw;
|
||||
|
||||
if (!isatty(STDIN_FILENO)) goto fatal;
|
||||
if (!atexit_registered) {
|
||||
atexit(linenoiseAtExit);
|
||||
atexit_registered = 1;
|
||||
}
|
||||
if (tcgetattr(fd,&orig_termios) == -1) goto fatal;
|
||||
|
||||
raw = orig_termios; /* modify the original mode */
|
||||
/* input modes: no break, no CR to NL, no parity check, no strip char,
|
||||
* no start/stop output control. */
|
||||
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||||
/* output modes - disable post processing */
|
||||
raw.c_oflag &= ~(OPOST);
|
||||
/* control modes - set 8 bit chars */
|
||||
raw.c_cflag |= (CS8);
|
||||
/* local modes - choing off, canonical off, no extended functions,
|
||||
* no signal chars (^Z,^C) */
|
||||
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
|
||||
/* control chars - set return condition: min number of bytes and timer.
|
||||
* We want read to return every single byte, without timeout. */
|
||||
raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
|
||||
|
||||
/* put terminal in raw mode after flushing */
|
||||
if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal;
|
||||
rawmode = 1;
|
||||
return 0;
|
||||
|
||||
fatal:
|
||||
errno = ENOTTY;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void disableRawMode(int fd) {
|
||||
/* Don't even check the return value as it's too late. */
|
||||
if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1)
|
||||
rawmode = 0;
|
||||
}
|
||||
|
||||
/* At exit we'll try to fix the terminal to the initial conditions. */
|
||||
static void linenoiseAtExit(void) {
|
||||
disableRawMode(STDIN_FILENO);
|
||||
freeHistory();
|
||||
}
|
||||
|
||||
static int getColumns(void) {
|
||||
struct winsize ws;
|
||||
|
||||
if (ioctl(1, TIOCGWINSZ, &ws) == -1) return 80;
|
||||
return ws.ws_col;
|
||||
}
|
||||
|
||||
static void refreshLine(int fd, const char *prompt, char *buf, size_t len, size_t pos, size_t cols) {
|
||||
char seq[64];
|
||||
size_t plen = strlen(prompt);
|
||||
|
||||
while((plen+pos) >= cols) {
|
||||
buf++;
|
||||
len--;
|
||||
pos--;
|
||||
}
|
||||
while (plen+len > cols) {
|
||||
len--;
|
||||
}
|
||||
|
||||
/* Cursor to left edge */
|
||||
snprintf(seq,64,"\x1b[0G");
|
||||
if (write(fd,seq,strlen(seq)) == -1) return;
|
||||
/* Write the prompt and the current buffer content */
|
||||
if (write(fd,prompt,strlen(prompt)) == -1) return;
|
||||
if (write(fd,buf,len) == -1) return;
|
||||
/* Erase to right */
|
||||
snprintf(seq,64,"\x1b[0K");
|
||||
if (write(fd,seq,strlen(seq)) == -1) return;
|
||||
/* Move cursor to original position. */
|
||||
snprintf(seq,64,"\x1b[0G\x1b[%dC", (int)(pos+plen));
|
||||
if (write(fd,seq,strlen(seq)) == -1) return;
|
||||
}
|
||||
|
||||
static void beep() {
|
||||
fprintf(stderr, "\x7");
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
static void freeCompletions(linenoiseCompletions *lc) {
|
||||
size_t i;
|
||||
for (i = 0; i < lc->len; i++)
|
||||
free(lc->cvec[i]);
|
||||
if (lc->cvec != NULL)
|
||||
free(lc->cvec);
|
||||
}
|
||||
|
||||
static int completeLine(int fd, const char *prompt, char *buf, size_t buflen, size_t *len, size_t *pos, size_t cols) {
|
||||
linenoiseCompletions lc = { 0, NULL };
|
||||
int nread, nwritten;
|
||||
char c = 0;
|
||||
|
||||
completionCallback(buf,&lc);
|
||||
if (lc.len == 0) {
|
||||
beep();
|
||||
} else {
|
||||
size_t stop = 0, i = 0;
|
||||
size_t clen;
|
||||
|
||||
while(!stop) {
|
||||
/* Show completion or original buffer */
|
||||
if (i < lc.len) {
|
||||
clen = strlen(lc.cvec[i]);
|
||||
refreshLine(fd,prompt,lc.cvec[i],clen,clen,cols);
|
||||
} else {
|
||||
refreshLine(fd,prompt,buf,*len,*pos,cols);
|
||||
}
|
||||
|
||||
nread = read(fd,&c,1);
|
||||
if (nread <= 0) {
|
||||
freeCompletions(&lc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch(c) {
|
||||
case 9: /* tab */
|
||||
i = (i+1) % (lc.len+1);
|
||||
if (i == lc.len) beep();
|
||||
break;
|
||||
case 27: /* escape */
|
||||
/* Re-show original buffer */
|
||||
if (i < lc.len) {
|
||||
refreshLine(fd,prompt,buf,*len,*pos,cols);
|
||||
}
|
||||
stop = 1;
|
||||
break;
|
||||
default:
|
||||
/* Update buffer and return */
|
||||
if (i < lc.len) {
|
||||
nwritten = snprintf(buf,buflen,"%s",lc.cvec[i]);
|
||||
*len = *pos = nwritten;
|
||||
}
|
||||
stop = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeCompletions(&lc);
|
||||
return c; /* Return last read character */
|
||||
}
|
||||
|
||||
void linenoiseClearScreen(void) {
|
||||
if (write(STDIN_FILENO,"\x1b[H\x1b[2J",7) <= 0) {
|
||||
/* nothing to do, just to avoid warning. */
|
||||
}
|
||||
}
|
||||
|
||||
static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt) {
|
||||
size_t plen = strlen(prompt);
|
||||
size_t pos = 0;
|
||||
size_t len = 0;
|
||||
size_t cols = getColumns();
|
||||
int history_index = 0;
|
||||
|
||||
buf[0] = '\0';
|
||||
buflen--; /* Make sure there is always space for the nulterm */
|
||||
|
||||
/* The latest history entry is always our current buffer, that
|
||||
* initially is just an empty string. */
|
||||
linenoiseHistoryAdd("");
|
||||
|
||||
if (write(fd,prompt,plen) == -1) return -1;
|
||||
while(1) {
|
||||
char c;
|
||||
int nread;
|
||||
char seq[2], seq2[2];
|
||||
|
||||
nread = read(fd,&c,1);
|
||||
if (nread <= 0) return len;
|
||||
|
||||
/* Only autocomplete when the callback is set. It returns < 0 when
|
||||
* there was an error reading from fd. Otherwise it will return the
|
||||
* character that should be handled next. */
|
||||
if (c == 9 && completionCallback != NULL) {
|
||||
c = completeLine(fd,prompt,buf,buflen,&len,&pos,cols);
|
||||
/* Return on errors */
|
||||
if (c < 0) return len;
|
||||
/* Read next character when 0 */
|
||||
if (c == 0) continue;
|
||||
}
|
||||
|
||||
switch(c) {
|
||||
case 13: /* enter */
|
||||
history_len--;
|
||||
free(history[history_len]);
|
||||
return (int)len;
|
||||
case 3: /* ctrl-c */
|
||||
errno = EAGAIN;
|
||||
return -1;
|
||||
case 127: /* backspace */
|
||||
case 8: /* ctrl-h */
|
||||
if (pos > 0 && len > 0) {
|
||||
memmove(buf+pos-1,buf+pos,len-pos);
|
||||
pos--;
|
||||
len--;
|
||||
buf[len] = '\0';
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
}
|
||||
break;
|
||||
case 4: /* ctrl-d, remove char at right of cursor */
|
||||
if (len > 1 && pos < (len-1)) {
|
||||
memmove(buf+pos,buf+pos+1,len-pos);
|
||||
len--;
|
||||
buf[len] = '\0';
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
} else if (len == 0) {
|
||||
history_len--;
|
||||
free(history[history_len]);
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case 20: /* ctrl-t */
|
||||
if (pos > 0 && pos < len) {
|
||||
int aux = buf[pos-1];
|
||||
buf[pos-1] = buf[pos];
|
||||
buf[pos] = aux;
|
||||
if (pos != len-1) pos++;
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
}
|
||||
break;
|
||||
case 2: /* ctrl-b */
|
||||
goto left_arrow;
|
||||
case 6: /* ctrl-f */
|
||||
goto right_arrow;
|
||||
case 16: /* ctrl-p */
|
||||
seq[1] = 65;
|
||||
goto up_down_arrow;
|
||||
case 14: /* ctrl-n */
|
||||
seq[1] = 66;
|
||||
goto up_down_arrow;
|
||||
break;
|
||||
case 27: /* escape sequence */
|
||||
if (read(fd,seq,2) == -1) break;
|
||||
if (seq[0] == 91 && seq[1] == 68) {
|
||||
left_arrow:
|
||||
/* left arrow */
|
||||
if (pos > 0) {
|
||||
pos--;
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
}
|
||||
} else if (seq[0] == 91 && seq[1] == 67) {
|
||||
right_arrow:
|
||||
/* right arrow */
|
||||
if (pos != len) {
|
||||
pos++;
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
}
|
||||
} else if (seq[0] == 91 && (seq[1] == 65 || seq[1] == 66)) {
|
||||
up_down_arrow:
|
||||
/* up and down arrow: history */
|
||||
if (history_len > 1) {
|
||||
/* Update the current history entry before to
|
||||
* overwrite it with tne next one. */
|
||||
free(history[history_len-1-history_index]);
|
||||
history[history_len-1-history_index] = strdup(buf);
|
||||
/* Show the new entry */
|
||||
history_index += (seq[1] == 65) ? 1 : -1;
|
||||
if (history_index < 0) {
|
||||
history_index = 0;
|
||||
break;
|
||||
} else if (history_index >= history_len) {
|
||||
history_index = history_len-1;
|
||||
break;
|
||||
}
|
||||
strncpy(buf,history[history_len-1-history_index],buflen);
|
||||
buf[buflen] = '\0';
|
||||
len = pos = strlen(buf);
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
}
|
||||
} else if (seq[0] == 91 && seq[1] > 48 && seq[1] < 55) {
|
||||
/* extended escape */
|
||||
if (read(fd,seq2,2) == -1) break;
|
||||
if (seq[1] == 51 && seq2[0] == 126) {
|
||||
/* delete */
|
||||
if (len > 0 && pos < len) {
|
||||
memmove(buf+pos,buf+pos+1,len-pos-1);
|
||||
len--;
|
||||
buf[len] = '\0';
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (len < buflen) {
|
||||
if (len == pos) {
|
||||
buf[pos] = c;
|
||||
pos++;
|
||||
len++;
|
||||
buf[len] = '\0';
|
||||
if (plen+len < cols) {
|
||||
/* Avoid a full update of the line in the
|
||||
* trivial case. */
|
||||
if (write(fd,&c,1) == -1) return -1;
|
||||
} else {
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
}
|
||||
} else {
|
||||
memmove(buf+pos+1,buf+pos,len-pos);
|
||||
buf[pos] = c;
|
||||
len++;
|
||||
pos++;
|
||||
buf[len] = '\0';
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 21: /* Ctrl+u, delete the whole line. */
|
||||
buf[0] = '\0';
|
||||
pos = len = 0;
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
break;
|
||||
case 11: /* Ctrl+k, delete from current to end of line. */
|
||||
buf[pos] = '\0';
|
||||
len = pos;
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
break;
|
||||
case 1: /* Ctrl+a, go to the start of the line */
|
||||
pos = 0;
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
break;
|
||||
case 5: /* ctrl+e, go to the end of the line */
|
||||
pos = len;
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
break;
|
||||
case 12: /* ctrl+l, clear screen */
|
||||
linenoiseClearScreen();
|
||||
refreshLine(fd,prompt,buf,len,pos,cols);
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {
|
||||
int fd = STDIN_FILENO;
|
||||
int count;
|
||||
|
||||
if (buflen == 0) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
if (!isatty(STDIN_FILENO)) {
|
||||
if (fgets(buf, buflen, stdin) == NULL) return -1;
|
||||
count = strlen(buf);
|
||||
if (count && buf[count-1] == '\n') {
|
||||
count--;
|
||||
buf[count] = '\0';
|
||||
}
|
||||
} else {
|
||||
if (enableRawMode(fd) == -1) return -1;
|
||||
count = linenoisePrompt(fd, buf, buflen, prompt);
|
||||
disableRawMode(fd);
|
||||
printf("\n");
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
char *linenoise(const char *prompt) {
|
||||
char buf[LINENOISE_MAX_LINE];
|
||||
int count;
|
||||
|
||||
if (isUnsupportedTerm()) {
|
||||
size_t len;
|
||||
|
||||
printf("%s",prompt);
|
||||
fflush(stdout);
|
||||
if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL;
|
||||
len = strlen(buf);
|
||||
while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) {
|
||||
len--;
|
||||
buf[len] = '\0';
|
||||
}
|
||||
return strdup(buf);
|
||||
} else {
|
||||
count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt);
|
||||
if (count == -1) return NULL;
|
||||
return strdup(buf);
|
||||
}
|
||||
}
|
||||
|
||||
/* Register a callback function to be called for tab-completion. */
|
||||
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
|
||||
completionCallback = fn;
|
||||
}
|
||||
|
||||
void linenoiseAddCompletion(linenoiseCompletions *lc, char *str) {
|
||||
size_t len = strlen(str);
|
||||
char *copy = malloc(len+1);
|
||||
memcpy(copy,str,len+1);
|
||||
lc->cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1));
|
||||
lc->cvec[lc->len++] = copy;
|
||||
}
|
||||
|
||||
/* Using a circular buffer is smarter, but a bit more complex to handle. */
|
||||
int linenoiseHistoryAdd(const char *line) {
|
||||
char *linecopy;
|
||||
|
||||
if (history_max_len == 0) return 0;
|
||||
if (history == NULL) {
|
||||
history = malloc(sizeof(char*)*history_max_len);
|
||||
if (history == NULL) return 0;
|
||||
memset(history,0,(sizeof(char*)*history_max_len));
|
||||
}
|
||||
linecopy = strdup(line);
|
||||
if (!linecopy) return 0;
|
||||
if (history_len == history_max_len) {
|
||||
free(history[0]);
|
||||
memmove(history,history+1,sizeof(char*)*(history_max_len-1));
|
||||
history_len--;
|
||||
}
|
||||
history[history_len] = linecopy;
|
||||
history_len++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int linenoiseHistorySetMaxLen(int len) {
|
||||
char **new;
|
||||
|
||||
if (len < 1) return 0;
|
||||
if (history) {
|
||||
int tocopy = history_len;
|
||||
|
||||
new = malloc(sizeof(char*)*len);
|
||||
if (new == NULL) return 0;
|
||||
if (len < tocopy) tocopy = len;
|
||||
memcpy(new,history+(history_max_len-tocopy), sizeof(char*)*tocopy);
|
||||
free(history);
|
||||
history = new;
|
||||
}
|
||||
history_max_len = len;
|
||||
if (history_len > history_max_len)
|
||||
history_len = history_max_len;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Save the history in the specified file. On success 0 is returned
|
||||
* otherwise -1 is returned. */
|
||||
int linenoiseHistorySave(char *filename) {
|
||||
FILE *fp = fopen(filename,"w");
|
||||
int j;
|
||||
|
||||
if (fp == NULL) return -1;
|
||||
for (j = 0; j < history_len; j++)
|
||||
fprintf(fp,"%s\n",history[j]);
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Load the history from the specified file. If the file does not exist
|
||||
* zero is returned and no operation is performed.
|
||||
*
|
||||
* If the file exists and the operation succeeded 0 is returned, otherwise
|
||||
* on error -1 is returned. */
|
||||
int linenoiseHistoryLoad(char *filename) {
|
||||
FILE *fp = fopen(filename,"r");
|
||||
char buf[LINENOISE_MAX_LINE];
|
||||
|
||||
if (fp == NULL) return -1;
|
||||
|
||||
while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
|
||||
char *p;
|
||||
|
||||
p = strchr(buf,'\r');
|
||||
if (!p) p = strchr(buf,'\n');
|
||||
if (p) *p = '\0';
|
||||
linenoiseHistoryAdd(buf);
|
||||
}
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
/* linenoise.h -- guerrilla line editing library against the idea that a
|
||||
* line editing lib needs to be 20,000 lines of C code.
|
||||
*
|
||||
* See linenoise.c for more information.
|
||||
*
|
||||
* Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef __LINENOISE_H
|
||||
#define __LINENOISE_H
|
||||
|
||||
typedef struct linenoiseCompletions {
|
||||
size_t len;
|
||||
char **cvec;
|
||||
} linenoiseCompletions;
|
||||
|
||||
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
|
||||
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
|
||||
void linenoiseAddCompletion(linenoiseCompletions *, char *);
|
||||
|
||||
char *linenoise(const char *prompt);
|
||||
int linenoiseHistoryAdd(const char *line);
|
||||
int linenoiseHistorySetMaxLen(int len);
|
||||
int linenoiseHistorySave(char *filename);
|
||||
int linenoiseHistoryLoad(char *filename);
|
||||
void linenoiseClearScreen(void);
|
||||
|
||||
#endif /* __LINENOISE_H */
|
||||
+118
-19
@@ -1,29 +1,101 @@
|
||||
Redis Cluster Design Proposal (work in progress)
|
||||
|
||||
28 Nov 2010: Ver 1.0 - initial version
|
||||
22 APr 2010: Ver 1.1 - more details and rationales
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
Redis is a fast key-value store supporting complex aggregate data types as
|
||||
values. For instance keys can be bound to lists with many elements, sets,
|
||||
sub-dictionaries (hashes) and so forth.
|
||||
|
||||
While Redis is very fast, currently it lacks scalability in the form of ability
|
||||
to transparently run across different nodes. This is desirable mainly for the
|
||||
following three rasons:
|
||||
|
||||
A) Fault tolerance. Some node may go off line without affecting the operations.
|
||||
B) Holding bigger datasets without using a single box with a lot of RAM.
|
||||
C) Scaling writes.
|
||||
|
||||
Since a single Redis instance supports 140,000 operations per second in a good
|
||||
Linux box costing less than $1000, the need for Redis Cluster arises more
|
||||
from "A" and "B". Scaling writes can also be useful in very high load
|
||||
environments. Scaling reads is already easily accomplished using Redis built-in
|
||||
replication.
|
||||
|
||||
Design goals
|
||||
============
|
||||
|
||||
Designing a DHT in 2010 is hard as there is too much bias towards good designs
|
||||
that are already well tested in practice, like the Amazon Dynamo design.
|
||||
Still a Dynamo alike DHT may not be the best fit for Redis.
|
||||
|
||||
Redis is very simple and fast at its core, so Redis cluster should try to
|
||||
follow the same guidelines. The first problem with a Dynamo-alike DHT is that
|
||||
Redis supports complex data types. Merging complex values like lsits, where
|
||||
in the case of a netsplit may diverge in very complex ways, is not going to
|
||||
be easy. The "most recent data" wins is not applicable and all the resolution
|
||||
business should be in the application.
|
||||
|
||||
Even a simple application can end up with complex schema of keys and complex
|
||||
values. Writing code in order to resolve conflicts is not going to be
|
||||
programmer friendly.
|
||||
|
||||
So the author of this document claims that Redis does not need to resist to
|
||||
netsplits, but it is enough to resist to M-1 nodes going offline, where
|
||||
M is the number of nodes storing every key-value pair.
|
||||
|
||||
For instance in a three nodes cluster I may configure the cluster in order to
|
||||
store every key into two instances (M=2). Such a cluster can resist to a single
|
||||
node going offline without interruption of the service.
|
||||
|
||||
When more than M-1 nodes are off line the cluster should detect such a condition
|
||||
and refusing any further query. The system administrator should check why
|
||||
M-1 nodes are offline and bring them back again if possible.
|
||||
|
||||
Once resisting to big net splits is no longer a requirement as there is no
|
||||
conflict resolution stage, since at least an original node responsible of
|
||||
holding every possible key must be online for the cluster to work, there is
|
||||
also no need for a design where every node can act as an independent entity
|
||||
receiving queries and forwarding this queries to other nodes as needed.
|
||||
|
||||
Instead a more decoupled approach can be used, in the form of a Redis Proxy
|
||||
node (or multiple Proxy nodes) that is contacted by clients, and
|
||||
is responsible of forwarding queries and replies back and forth from data nodes.
|
||||
|
||||
Data nodes can be just vanilla redis-server instances.
|
||||
|
||||
Network layout
|
||||
==============
|
||||
|
||||
- N different Data Nodes. Every node is identified by ip:port.
|
||||
- A single Configuration Node.
|
||||
- M different Proxy Nodes (redis-cluster).
|
||||
- A single Handling Node.
|
||||
- One ore more Data Nodes. Every node is identified by ip:port.
|
||||
- A single Configuration Node.
|
||||
- One more more Proxy Nodes (redis-cluster nodes).
|
||||
- A single Handling Node.
|
||||
|
||||
Data Nodes and the Configuration Node are just vanilla redis-server instances.
|
||||
|
||||
Configuration Node
|
||||
==================
|
||||
|
||||
- Contains information about all the Data nodes in the cluster.
|
||||
- Contains information about all the Proxy nodes in the cluster.
|
||||
- Maps the keyspace to different nodes.
|
||||
- Contains information about all the Data nodes in the cluster.
|
||||
- Contains information about all the Proxy nodes in the cluster.
|
||||
- Contains information about what Data Node holds a given sub-space of keys.
|
||||
|
||||
The keyspace is divided into 1024 different "hashing slots".
|
||||
(1024 is just an example, this value should be configurable)
|
||||
|
||||
Given a key perform SHA1(key) and use the last 10 bits of the result to get a 10 bit number representing the key slot (from 0 to 1023).
|
||||
Given a key perform SHA1(key) and use the last 10 bits of the result to get a 10 bit number representing the "key slot" (from 0 to 1023).
|
||||
|
||||
The Configuration node maps every slot of the keyspace to K different Data Nodes.
|
||||
The Configuration node maps every slot of the keyspace to M different Data Nodes (every key is stored into M nodes, configurable).
|
||||
|
||||
The Configuration node can be modified by a single client at a time. Locking is performed using SETNX.
|
||||
|
||||
The Configuration node should be replicated as there is a single configuration node for the whole network.
|
||||
The Configuration node should be replicated as there is a single configuration node for the whole network. It is the only single point of failure of the system.
|
||||
When a Configuration node fails the cluster does not stop operating, but is not
|
||||
able to recover if there is some exceptional condition to handle, like a Data
|
||||
Node going off line or the addition of a new Data Node to the cluster.
|
||||
|
||||
The Configuration node is a standard Redis server, like every other Data node.
|
||||
|
||||
@@ -37,23 +109,52 @@ Proxy Nodes
|
||||
|
||||
Proxy nodes get requests from clients and route this requests to the right Redis nodes.
|
||||
|
||||
When a proxy node is started it needs to know the Configuration node address in order to load the infomration about the Data nodes and the mapping between the key space and the nodes.
|
||||
Proxy nodes take persistent connections to all the Data Nodes and the
|
||||
Configuration Node. This connections are keep alive with PING requests from time
|
||||
to time if there is no traffic. This way Proxy Nodes can understand asap if
|
||||
there is a problem in some Data Node or in the Configuration Node.
|
||||
|
||||
On startup a Proxy node will also register itself in the Configuration node, and will make sure to refresh it's configuration every N seconds (via an EXPIREing key) so that it's possible to detect when a Proxy node fails.
|
||||
When a Proxy Node is started it needs to know the Configuration node address in order to load the infomration about the Data nodes and the mapping between the key space and the nodes.
|
||||
|
||||
The Proxy node also is in charge of signaling failing Data nodes to the Configuration node, so that the Handling node can take appropriate actions.
|
||||
On startup a Proxy Node will also register itself in the Configuration node, and will make sure to refresh it's configuration every N seconds (via an EXPIREing key) so that it's possible to detect when a Proxy node fails.
|
||||
|
||||
Clients can submit queries to any Proxy Node, so well designed clients may ask
|
||||
at startup the list of Proxy Nodes querying the Configuration Node. Then if
|
||||
a query fails against a given Proxy Node it can be retried against the next.
|
||||
|
||||
The Proxy Node is also in charge of signaling failing Data nodes to the Configuration node, so that the Handling Node can take appropriate actions.
|
||||
|
||||
When a new Data node joins or leaves the cluster, and in general when the cluster configuration changes, all the Proxy nodes will receive a notification and will reload the configuration from the Configuration node.
|
||||
|
||||
Proxy Nodes - how queries are submited
|
||||
======================================
|
||||
|
||||
This is how a query is processed:
|
||||
|
||||
1) A client sends a query to a Proxy Node, using the Redis protocol like if it was a plain Redis Node.
|
||||
2) The Proxy Node inspects the command arguments to detect the key. The key is hashed. The Proxy Node has the table mapping a given key to M nodes, and persistent connections to all the nodes.
|
||||
|
||||
At this point the process is different in case of read or write queries:
|
||||
|
||||
WRITE QUERY:
|
||||
|
||||
3a) The Proxy Node forwards the query to M Data Nodes at the same time, waiting for replies.
|
||||
3b) Once all the replies are received the Proxy Node checks that the replies are consistent. For instance all the M nodes need to reply with OK and so forth. If the query fails in a subset of nodes but succeeds in other nodes, the failing nodes are considered unreliable and are put off line notifying the configuration node.
|
||||
3c) The reply is transfered back to the client.
|
||||
|
||||
READ QUERY:
|
||||
|
||||
3d) The Proxy Node forwards the query to a single random client, passing the reply back to the client.
|
||||
|
||||
Handling Node
|
||||
=============
|
||||
|
||||
The handling node is a special Redis client with the following role:
|
||||
|
||||
- Handles the cluster configuration stored in the Config node.
|
||||
- Is in charge for adding and removing nodes dynamically from the net.
|
||||
- Relocates keys on nodes additions / removal.
|
||||
- Signal a configuration change to Proxy nodes.
|
||||
- Handles the cluster configuration stored in the Config node.
|
||||
- Is in charge for adding and removing nodes dynamically from the net.
|
||||
- Relocates keys on nodes additions / removal.
|
||||
- Signal a configuration change to Proxy nodes.
|
||||
|
||||
More details on hashing slots
|
||||
============================
|
||||
@@ -110,6 +211,4 @@ Then the slot is moved (1/1024 of all the keys). The Configuration server is mod
|
||||
Implementation details
|
||||
======================
|
||||
|
||||
Every Proxy node should take persistent connections to all the Data nodes.
|
||||
|
||||
To run the Handling node and the Configuration node in the same physical computer is probably a good idea.
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
Redis Cluster - Alternative 1
|
||||
|
||||
28 Apr 2010: Ver 1.0 - initial version
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
The motivations and design goals of Redis Cluster are already outlined in the
|
||||
first design document of Redis Cluster. This document is just an attempt to
|
||||
provide a completely alternative approach in order to explore more ideas.
|
||||
|
||||
In this document the alternative explored is a cluster where communication is
|
||||
performed directly from client to the target node, without intermediate layer.
|
||||
|
||||
The intermediate layer can be used, in the form of a proxy, in order to provide
|
||||
the same functionality to clients not able to directly use the cluster protocol.
|
||||
So in a first stage clients can use a proxy to implement the hash ring, but
|
||||
later this clients can switch to a native implementation, following a
|
||||
specification that the Redis project will provide.
|
||||
|
||||
In this new design fault tolerance is achieved by replicating M-1 times every
|
||||
data node instead of storing the same key M times across nodes.
|
||||
|
||||
From the point of view of CAP our biggest sacrifice is about "P", that is
|
||||
resistance to partitioning. Only M-1 nodes can go down for the cluster still
|
||||
be functional. Also when possible "A" is somewhat sacrificed for "L", that
|
||||
is, Latency. Not really in the CAP equation but a very important parameter.
|
||||
|
||||
Network layout
|
||||
==============
|
||||
|
||||
In this alternative design the network layout is simple as there are only
|
||||
clients talking directly to N data nodes. So we can imagine to have:
|
||||
|
||||
- K Redis clients, directly talking to the data nodes.
|
||||
- N Redis data nodes, that are, normal Redis instances.
|
||||
|
||||
Data nodes are replicate M-1 times (so there are a total of M copies for
|
||||
every node). If M is one, the system is not fault tolerant. If M is 2 one
|
||||
data node can go off line without affecting the operations. And so forth.
|
||||
|
||||
Hash slots
|
||||
==========
|
||||
|
||||
The key space is divided into 1024 slots.
|
||||
|
||||
Given a key, the SHA1 function is applied to it.
|
||||
The first 10 bytes of the SHA1 digest are interpreted as an unsigned integer
|
||||
from 0 to 1023. This is the hash slot of the key.
|
||||
|
||||
Data nodes
|
||||
==========
|
||||
|
||||
Data nodes are normal Redis instances, but a few additional commands are
|
||||
provided.
|
||||
|
||||
HASHRING ADD ... list of hash slots ...
|
||||
HASHRING DEL ... list of hash slots ...
|
||||
HASHRING REHASHING slot
|
||||
HASHRING SLOTS => returns the list of configured slots
|
||||
HSAHRING KEYS ... list of hash slots ...
|
||||
|
||||
By default Redis instances are configured to accept operations about all
|
||||
the hash slots. With this commands it's possible to configure a Redis instance
|
||||
to accept only a subset of the key space.
|
||||
|
||||
If an operation is performed against a key hashing to a slot that is not
|
||||
configured to be accepted, the Redis instance will reply with:
|
||||
|
||||
"-ERR wrong hash slot"
|
||||
|
||||
More details on the HASHRING command and sub commands will be showed later
|
||||
in this document.
|
||||
|
||||
Additionally three other commands are added:
|
||||
|
||||
DUMP key
|
||||
RESTORE key <dump data>
|
||||
MIGRATE key host port
|
||||
|
||||
DUMP is used to output a very compact binary representation of the data stored at key.
|
||||
|
||||
RESTORE re-creates a value (storing it at key) starting from the output produced by DUMP.
|
||||
|
||||
MIGRATE is like a server-side DUMP+RESTORE command. This atomic command moves one key from the connected instance to another instance, returning the status code of the operation (+OK or an error).
|
||||
|
||||
The protocol described in this draft only uses the MIGRATE command, but this in turn will use RESTORE internally when connecting to another server, and DUMP is provided for symmetry.
|
||||
|
||||
Querying the cluster
|
||||
====================
|
||||
|
||||
1) Reading the cluster config
|
||||
-----------------------------
|
||||
|
||||
Clients of the cluster are required to have the cluster configuration loaded
|
||||
into memory. The cluster configuration is the sum of the following info:
|
||||
|
||||
- Number of data nodes in the cluster, for instance, 10
|
||||
- A map between hash slots and nodes, so for instnace:
|
||||
hash slot 1 -> node 0
|
||||
hash slot 2 -> node 5
|
||||
hash slot 3 -> node 3
|
||||
... and so forth ...
|
||||
- Physical address of nodes, and their replicas.
|
||||
node 0 addr -> 192.168.1.100
|
||||
node 0 replicas -> 192.168.1.101, 192.168.1.105
|
||||
- Configuration version: the SHA1 of the whole configuration
|
||||
|
||||
The configuration is stored in every single data node of the cluster.
|
||||
|
||||
A client without the configuration in memory is require, as a first step, to
|
||||
read the config. In order to do so the client requires to have a list of IPs
|
||||
that are with good probability data nodes of the cluster.
|
||||
|
||||
The client will try to get the config from all this nodes. If no node is found
|
||||
responding, an error is reported to the user.
|
||||
|
||||
2) Caching and refreshing the configuration
|
||||
-------------------------------------------
|
||||
|
||||
A node is allowed to cache the configuration in memory or in a different way
|
||||
(for instance storing the configuration into a file), but every client is
|
||||
required to check if the configuration changed at max every 10 seconds, asking
|
||||
for the configuration version key with a single GET call, and checking if the
|
||||
configuration version matches the one loaded in memory.
|
||||
|
||||
Also a client is required to refresh the configuration every time a node
|
||||
replies with:
|
||||
|
||||
"-ERR wrong hash slot"
|
||||
|
||||
As this means that hash slots were reassigned in some way.
|
||||
|
||||
Checking the configuration every 10 seconds is not required in theory but is
|
||||
a good protection against errors and failures that may happen in real world
|
||||
environments. It is also very cheap to perform, as a GET operation from time
|
||||
to time is going to have no impact in the overall performance.
|
||||
|
||||
3) Read query
|
||||
-------------
|
||||
|
||||
To perform a read query the client hashes the key argument from the command
|
||||
(in the intiial version of Redis Cluster only single-key commands are
|
||||
allowed). Using the in memory configuration it maps the hash key to the
|
||||
node ID.
|
||||
|
||||
If the client is configured to support read-after-write consistency, then
|
||||
the "master" node for this hash slot is queried.
|
||||
|
||||
Otherwise the client picks a random node from the master and the replicas
|
||||
available.
|
||||
|
||||
4) Write query
|
||||
--------------
|
||||
|
||||
A write query is exactly like a read query, with the difference that the
|
||||
write always targets the master node, instead of the replicas.
|
||||
|
||||
Creating a cluster
|
||||
==================
|
||||
|
||||
In order to create a new cluster, the redis-cluster command line utility is
|
||||
used. It gets a list of available nodes and replicas, in order to write the
|
||||
initial configuration in all the nodes.
|
||||
|
||||
At this point the cluster is usable by clients.
|
||||
|
||||
Adding nodes to the cluster
|
||||
===========================
|
||||
|
||||
The command line utility redis-cluster is used in order to add a node to the
|
||||
cluster:
|
||||
|
||||
1) The cluster configuration is loaded.
|
||||
2) A fair number of hash slots are assigned to the new data node.
|
||||
3) Hash slots moved to the new node are marked as "REHASHING" in the old
|
||||
nodes, using the HASHRING command:
|
||||
|
||||
HASHRING SETREHASHING 1 192.168.1.103 6380
|
||||
|
||||
The above command set the hash slot "1" in rehashing state, with the
|
||||
"forwarding address" to 192.168.1.103:6380. As a result if this node receives
|
||||
a query about a key hashing to hash slot 1, that *is not present* in the
|
||||
current data set, it replies with:
|
||||
|
||||
"-MIGRATED 192.168.1.103:6380"
|
||||
|
||||
The client can then reissue the query against the new node.
|
||||
|
||||
Instead even if the hash slot is marked as rehashing but the requested key
|
||||
is still there, the query is processed. This allows for non blocking
|
||||
rehashing.
|
||||
|
||||
Note that no additional memory is used by Redis in order to provide such a
|
||||
feature.
|
||||
|
||||
4) While the Hash slot is marked as "REHASHING", redis-cluster asks this node
|
||||
the list of all the keys matching the specified hash slot. Then all the keys
|
||||
are moved to the new node using the MIGRATE command.
|
||||
5) Once all the keys are migrated, the hash slot is deleted from the old
|
||||
node configuration with "HASHRING DEL 1". And the configuration is update.
|
||||
|
||||
Using this algorithm all the hash slots are migrated one after the other to the new node. In practical implementation before to start the migration the
|
||||
redis-cluster utility should write a log into the configuration so that
|
||||
in case of crash or any other problem the utility is able to recover from
|
||||
were it left.
|
||||
|
||||
Fault tolerance
|
||||
===============
|
||||
|
||||
Fault tolerance is reached replicating every data node M-1 times, so that we
|
||||
have one master and M-1 replicas for a total of M nodes holding the same
|
||||
hash slots. Up to M-1 nodes can go down without affecting the cluster.
|
||||
|
||||
The tricky part about fault tolerance is detecting when a node is failing and
|
||||
signaling it to all the other clients.
|
||||
|
||||
When a master node is failing in a permanent way, promoting the first slave
|
||||
is easy:
|
||||
1) At some point a client will notice there are problems accessing a given node. It will try to refresh the config, but will notice that the config is already up to date.
|
||||
2) In order to make sure the problem is not about the client connectivity itself, it will try to reach other nodes as well. If more than M-1 nodes appear to be down, it's either a client networking problem or alternatively the cluster can't be fixed as too many nodes are down anyway. So no action is taken, but an error is reported.
|
||||
3) If instead only 1 or at max M-1 nodes appear to be down, the client promotes a slave as master and writes the new configuration to all the data nodes.
|
||||
|
||||
All the other clients will see the data node not working, and as a first step will try to refresh the configuration. They will successful refresh the configuration and the cluster will work again.
|
||||
|
||||
Every time a slave is promoted, the information is written in a log that is actually a Redis list, in all the data nodes, so that system administration tools can detect what happened in order to send notifications to the admin.
|
||||
|
||||
Intermittent problems
|
||||
---------------------
|
||||
|
||||
In the above scenario a master was failing in a permanent way. Now instead
|
||||
let's think to a case where a network cable is not working well so a node
|
||||
appears to be a few seconds up and a few seconds down.
|
||||
|
||||
When this happens recovering can be much harder, as a client may notice the
|
||||
problem and will promote a slave to master as a result, but then the host
|
||||
will be up again and the other clients will not see the problem, writing to
|
||||
the old master for at max 10 seconds (after 10 seconds all the clients are
|
||||
required to perform a few GETs to check the configuration version of the
|
||||
cluster and update if needed).
|
||||
|
||||
One way to fix this problem is to delegate the fail over mechanism to a
|
||||
failover agent. When clients notice problems will not take any active action
|
||||
but will just log the problem into a redis list in all the reachable nodes,
|
||||
wait, check for configuration change, and retry.
|
||||
|
||||
The failover agent constantly monitor this logs: if some client is reporting
|
||||
a failing node, it can take appropriate actions, checking if the failure is
|
||||
permanent or not. If it's not he can send a SHUTDOWN command to the failing
|
||||
master if possible. The failover agent can also consider better the problem
|
||||
checking if the failing mode is advertised by all the clients or just a single
|
||||
one, and can check itself if there is a real problem before to proceed with
|
||||
the fail over.
|
||||
|
||||
Redis proxy
|
||||
===========
|
||||
|
||||
In order to make the switch to the clustered version of Redis simpler, and
|
||||
because the client-side protocol is non trivial to implement compared to the
|
||||
usual Redis client lib protocol (where a minimal lib can be as small as
|
||||
100 lines of code), a proxy will be provided to implement the cluster protocol
|
||||
as a proxy.
|
||||
|
||||
Every client will talk to a redis-proxy node that is responsible of using
|
||||
the new protocol and forwarding back the replies.
|
||||
|
||||
In the long run the aim is to switch all the major client libraries to the
|
||||
new protocol in a native way.
|
||||
|
||||
Supported commands
|
||||
==================
|
||||
|
||||
Because with this design we talk directly to data nodes and there is a single
|
||||
"master" version of every value (that's the big gain dropping "P" from CAP!)
|
||||
almost all the redis commands can be supported by the clustered version
|
||||
including MULTI/EXEC and multi key commands as long as all the keys will hash
|
||||
to the same hash slot. In order to guarantee this, key tags can be used,
|
||||
where when a specific pattern is present in the key name, only that part is
|
||||
hashed in order to obtain the hash index.
|
||||
|
||||
Random remarks
|
||||
==============
|
||||
|
||||
- It's still not clear how to perform an atomic election of a slave to master.
|
||||
- In normal conditions (all the nodes working) this new design is just
|
||||
K clients talking to N nodes without intermediate layers, no routes:
|
||||
this means it is horizontally scalable with O(1) lookups.
|
||||
- The cluster should optionally be able to work with manual fail over
|
||||
for environments where it's desirable to do so. For instance it's possible
|
||||
to setup periodic checks on all the nodes, and switch IPs when needed
|
||||
or other advanced configurations that can not be the default as they
|
||||
are too environment dependent.
|
||||
|
||||
A few ideas about client-side slave election
|
||||
============================================
|
||||
|
||||
Detecting failures in a collaborative way
|
||||
-----------------------------------------
|
||||
|
||||
In order to take the node failure detection and slave election a distributed
|
||||
effort, without any "control program" that is in some way a single point
|
||||
of failure (the cluster will not stop when it stops, but errors are not
|
||||
corrected without it running), it's possible to use a few consensus-alike
|
||||
algorithms.
|
||||
|
||||
For instance all the nodes may take a list of errors detected by clients.
|
||||
|
||||
If Client-1 detects some failure accessing Node-3, for instance a connection
|
||||
refused error or a timeout, it logs what happened with LPUSH commands against
|
||||
all the other nodes. This "error messages" will have a timestamp and the Node
|
||||
id. Something like:
|
||||
|
||||
LPUSH __cluster__:errors 3:1272545939
|
||||
|
||||
So if the error is reported many times in a small amount of time, at some
|
||||
point a client can have enough hints about the need of performing a
|
||||
slave election.
|
||||
|
||||
Atomic slave election
|
||||
---------------------
|
||||
|
||||
In order to avoid races when electing a slave to master (that is in order to
|
||||
avoid that some client can still contact the old master for that node in
|
||||
the 10 seconds timeframe), the client performing the election may write
|
||||
some hint in the configuration, change the configuration SHA1 accordingly and
|
||||
wait for more than 10 seconds, in order to be sure all the clients will
|
||||
refresh the configuration before a new access.
|
||||
|
||||
The config hint may be something like:
|
||||
|
||||
"we are switching to a new master, that is x.y.z.k:port, in a few seconds"
|
||||
|
||||
When a client updates the config and finds such a flag set, it starts to
|
||||
continuously refresh the config until a change is noticed (this will take
|
||||
at max 10-15 seconds).
|
||||
|
||||
The client performing the election will wait that famous 10 seconds time frame
|
||||
and finally will update the config in a definitive way setting the new
|
||||
slave as mater. All the clients at this point are guaranteed to have the new
|
||||
config either because they refreshed or because in the next query their config
|
||||
is already expired and they'll update the configuration.
|
||||
|
||||
EOF
|
||||
@@ -1,11 +0,0 @@
|
||||
- Use N working childs (fork at startup) in order to implement async I/O.
|
||||
- The swap file is opened at startup and unlink(2)-ed
|
||||
- Swap file free/used blocks bitmap is taken in memory
|
||||
- When a child is saving on background or rewriting the append only log the swap file gets frozen (no writes from the parent).
|
||||
- When Redis is low on memory keys not recently used and big enough will be transfered on Disk by one of the child processes doing async I/O. Only when the transfer finishes the parent will mark the value as swapped out and will free the associated value (if didn't changed in the meantime).
|
||||
- When Redis is going to process a command will first check that all the keys involved are in memory. If not will send a request to an async I/O child in order to load this keys in memory. When the operation finished Redis will "resume" the client operation (just the client structure will hold the arguments of the suspended command, Redis will execute the command and unmask the read/write events in the client socket).
|
||||
- async I/O childs and parent communicate via pipes, so while Redis is blocked in the event loop can be resumed by an async child I/O just writing a message in the pipe.
|
||||
- Every Redis type should have a function to guess the max space needed to serialized an object.
|
||||
- The swap file is divided into blocks.
|
||||
- Even if Redis unblock the command when not all the keys are loaded, or if a key was swapped out in the mean time, this is not a critical condition as anyway the key lookup process will (this time synchronously) load the keys in memory if needed. This should happen very rarely or possibly never, so a bug in the async loading stage will not cause a bug but just a performance hit.
|
||||
- The blocks allocation algorithm should try to avoid fragmentation and cache misses.
|
||||
@@ -1,590 +0,0 @@
|
||||
/* Hash Tables Implementation.
|
||||
*
|
||||
* This file implements in memory hash tables with insert/del/replace/find/
|
||||
* get-random-element operations. Hash tables will auto resize if needed
|
||||
* tables of power of two in size are used, collisions are handled by
|
||||
* chaining. See the source code for more information... :)
|
||||
*
|
||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of Redis nor the names of its contributors may be used
|
||||
* to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "fmacros.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "dict.h"
|
||||
#include "zmalloc.h"
|
||||
|
||||
/* ---------------------------- Utility funcitons --------------------------- */
|
||||
|
||||
static void _dictPanic(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
fprintf(stderr, "\nDICT LIBRARY PANIC: ");
|
||||
vfprintf(stderr, fmt, ap);
|
||||
fprintf(stderr, "\n\n");
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* ------------------------- Heap Management Wrappers------------------------ */
|
||||
|
||||
static void *_dictAlloc(size_t size)
|
||||
{
|
||||
void *p = zmalloc(size);
|
||||
if (p == NULL)
|
||||
_dictPanic("Out of memory");
|
||||
return p;
|
||||
}
|
||||
|
||||
static void _dictFree(void *ptr) {
|
||||
zfree(ptr);
|
||||
}
|
||||
|
||||
/* -------------------------- private prototypes ---------------------------- */
|
||||
|
||||
static int _dictExpandIfNeeded(dict *ht);
|
||||
static unsigned long _dictNextPower(unsigned long size);
|
||||
static int _dictKeyIndex(dict *ht, const void *key);
|
||||
static int _dictInit(dict *ht, dictType *type, void *privDataPtr);
|
||||
|
||||
/* -------------------------- hash functions -------------------------------- */
|
||||
|
||||
/* Thomas Wang's 32 bit Mix Function */
|
||||
unsigned int dictIntHashFunction(unsigned int key)
|
||||
{
|
||||
key += ~(key << 15);
|
||||
key ^= (key >> 10);
|
||||
key += (key << 3);
|
||||
key ^= (key >> 6);
|
||||
key += ~(key << 11);
|
||||
key ^= (key >> 16);
|
||||
return key;
|
||||
}
|
||||
|
||||
/* Identity hash function for integer keys */
|
||||
unsigned int dictIdentityHashFunction(unsigned int key)
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
/* Generic hash function (a popular one from Bernstein).
|
||||
* I tested a few and this was the best. */
|
||||
unsigned int dictGenHashFunction(const unsigned char *buf, int len) {
|
||||
unsigned int hash = 5381;
|
||||
|
||||
while (len--)
|
||||
hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */
|
||||
return hash;
|
||||
}
|
||||
|
||||
/* ----------------------------- API implementation ------------------------- */
|
||||
|
||||
/* Reset an hashtable already initialized with ht_init().
|
||||
* NOTE: This function should only called by ht_destroy(). */
|
||||
static void _dictReset(dict *ht)
|
||||
{
|
||||
ht->table = NULL;
|
||||
ht->size = 0;
|
||||
ht->sizemask = 0;
|
||||
ht->used = 0;
|
||||
}
|
||||
|
||||
/* Create a new hash table */
|
||||
dict *dictCreate(dictType *type,
|
||||
void *privDataPtr)
|
||||
{
|
||||
dict *ht = _dictAlloc(sizeof(*ht));
|
||||
|
||||
_dictInit(ht,type,privDataPtr);
|
||||
return ht;
|
||||
}
|
||||
|
||||
/* Initialize the hash table */
|
||||
int _dictInit(dict *ht, dictType *type,
|
||||
void *privDataPtr)
|
||||
{
|
||||
_dictReset(ht);
|
||||
ht->type = type;
|
||||
ht->privdata = privDataPtr;
|
||||
return DICT_OK;
|
||||
}
|
||||
|
||||
/* Resize the table to the minimal size that contains all the elements,
|
||||
* but with the invariant of a USER/BUCKETS ration near to <= 1 */
|
||||
int dictResize(dict *ht)
|
||||
{
|
||||
int minimal = ht->used;
|
||||
|
||||
if (minimal < DICT_HT_INITIAL_SIZE)
|
||||
minimal = DICT_HT_INITIAL_SIZE;
|
||||
return dictExpand(ht, minimal);
|
||||
}
|
||||
|
||||
/* Expand or create the hashtable */
|
||||
int dictExpand(dict *ht, unsigned long size)
|
||||
{
|
||||
dict n; /* the new hashtable */
|
||||
unsigned long realsize = _dictNextPower(size), i;
|
||||
|
||||
/* the size is invalid if it is smaller than the number of
|
||||
* elements already inside the hashtable */
|
||||
if (ht->used > size)
|
||||
return DICT_ERR;
|
||||
|
||||
_dictInit(&n, ht->type, ht->privdata);
|
||||
n.size = realsize;
|
||||
n.sizemask = realsize-1;
|
||||
n.table = _dictAlloc(realsize*sizeof(dictEntry*));
|
||||
|
||||
/* Initialize all the pointers to NULL */
|
||||
memset(n.table, 0, realsize*sizeof(dictEntry*));
|
||||
|
||||
/* Copy all the elements from the old to the new table:
|
||||
* note that if the old hash table is empty ht->size is zero,
|
||||
* so dictExpand just creates an hash table. */
|
||||
n.used = ht->used;
|
||||
for (i = 0; i < ht->size && ht->used > 0; i++) {
|
||||
dictEntry *he, *nextHe;
|
||||
|
||||
if (ht->table[i] == NULL) continue;
|
||||
|
||||
/* For each hash entry on this slot... */
|
||||
he = ht->table[i];
|
||||
while(he) {
|
||||
unsigned int h;
|
||||
|
||||
nextHe = he->next;
|
||||
/* Get the new element index */
|
||||
h = dictHashKey(ht, he->key) & n.sizemask;
|
||||
he->next = n.table[h];
|
||||
n.table[h] = he;
|
||||
ht->used--;
|
||||
/* Pass to the next element */
|
||||
he = nextHe;
|
||||
}
|
||||
}
|
||||
assert(ht->used == 0);
|
||||
_dictFree(ht->table);
|
||||
|
||||
/* Remap the new hashtable in the old */
|
||||
*ht = n;
|
||||
return DICT_OK;
|
||||
}
|
||||
|
||||
/* Add an element to the target hash table */
|
||||
int dictAdd(dict *ht, void *key, void *val)
|
||||
{
|
||||
int index;
|
||||
dictEntry *entry;
|
||||
|
||||
/* Get the index of the new element, or -1 if
|
||||
* the element already exists. */
|
||||
if ((index = _dictKeyIndex(ht, key)) == -1)
|
||||
return DICT_ERR;
|
||||
|
||||
/* Allocates the memory and stores key */
|
||||
entry = _dictAlloc(sizeof(*entry));
|
||||
entry->next = ht->table[index];
|
||||
ht->table[index] = entry;
|
||||
|
||||
/* Set the hash entry fields. */
|
||||
dictSetHashKey(ht, entry, key);
|
||||
dictSetHashVal(ht, entry, val);
|
||||
ht->used++;
|
||||
return DICT_OK;
|
||||
}
|
||||
|
||||
/* Add an element, discarding the old if the key already exists.
|
||||
* Return 1 if the key was added from scratch, 0 if there was already an
|
||||
* element with such key and dictReplace() just performed a value update
|
||||
* operation. */
|
||||
int dictReplace(dict *ht, void *key, void *val)
|
||||
{
|
||||
dictEntry *entry, auxentry;
|
||||
|
||||
/* Try to add the element. If the key
|
||||
* does not exists dictAdd will suceed. */
|
||||
if (dictAdd(ht, key, val) == DICT_OK)
|
||||
return 1;
|
||||
/* It already exists, get the entry */
|
||||
entry = dictFind(ht, key);
|
||||
/* Free the old value and set the new one */
|
||||
/* Set the new value and free the old one. Note that it is important
|
||||
* to do that in this order, as the value may just be exactly the same
|
||||
* as the previous one. In this context, think to reference counting,
|
||||
* you want to increment (set), and then decrement (free), and not the
|
||||
* reverse. */
|
||||
auxentry = *entry;
|
||||
dictSetHashVal(ht, entry, val);
|
||||
dictFreeEntryVal(ht, &auxentry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Search and remove an element */
|
||||
static int dictGenericDelete(dict *ht, const void *key, int nofree)
|
||||
{
|
||||
unsigned int h;
|
||||
dictEntry *he, *prevHe;
|
||||
|
||||
if (ht->size == 0)
|
||||
return DICT_ERR;
|
||||
h = dictHashKey(ht, key) & ht->sizemask;
|
||||
he = ht->table[h];
|
||||
|
||||
prevHe = NULL;
|
||||
while(he) {
|
||||
if (dictCompareHashKeys(ht, key, he->key)) {
|
||||
/* Unlink the element from the list */
|
||||
if (prevHe)
|
||||
prevHe->next = he->next;
|
||||
else
|
||||
ht->table[h] = he->next;
|
||||
if (!nofree) {
|
||||
dictFreeEntryKey(ht, he);
|
||||
dictFreeEntryVal(ht, he);
|
||||
}
|
||||
_dictFree(he);
|
||||
ht->used--;
|
||||
return DICT_OK;
|
||||
}
|
||||
prevHe = he;
|
||||
he = he->next;
|
||||
}
|
||||
return DICT_ERR; /* not found */
|
||||
}
|
||||
|
||||
int dictDelete(dict *ht, const void *key) {
|
||||
return dictGenericDelete(ht,key,0);
|
||||
}
|
||||
|
||||
int dictDeleteNoFree(dict *ht, const void *key) {
|
||||
return dictGenericDelete(ht,key,1);
|
||||
}
|
||||
|
||||
/* Destroy an entire hash table */
|
||||
int _dictClear(dict *ht)
|
||||
{
|
||||
unsigned long i;
|
||||
|
||||
/* Free all the elements */
|
||||
for (i = 0; i < ht->size && ht->used > 0; i++) {
|
||||
dictEntry *he, *nextHe;
|
||||
|
||||
if ((he = ht->table[i]) == NULL) continue;
|
||||
while(he) {
|
||||
nextHe = he->next;
|
||||
dictFreeEntryKey(ht, he);
|
||||
dictFreeEntryVal(ht, he);
|
||||
_dictFree(he);
|
||||
ht->used--;
|
||||
he = nextHe;
|
||||
}
|
||||
}
|
||||
/* Free the table and the allocated cache structure */
|
||||
_dictFree(ht->table);
|
||||
/* Re-initialize the table */
|
||||
_dictReset(ht);
|
||||
return DICT_OK; /* never fails */
|
||||
}
|
||||
|
||||
/* Clear & Release the hash table */
|
||||
void dictRelease(dict *ht)
|
||||
{
|
||||
_dictClear(ht);
|
||||
_dictFree(ht);
|
||||
}
|
||||
|
||||
dictEntry *dictFind(dict *ht, const void *key)
|
||||
{
|
||||
dictEntry *he;
|
||||
unsigned int h;
|
||||
|
||||
if (ht->size == 0) return NULL;
|
||||
h = dictHashKey(ht, key) & ht->sizemask;
|
||||
he = ht->table[h];
|
||||
while(he) {
|
||||
if (dictCompareHashKeys(ht, key, he->key))
|
||||
return he;
|
||||
he = he->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dictIterator *dictGetIterator(dict *ht)
|
||||
{
|
||||
dictIterator *iter = _dictAlloc(sizeof(*iter));
|
||||
|
||||
iter->ht = ht;
|
||||
iter->index = -1;
|
||||
iter->entry = NULL;
|
||||
iter->nextEntry = NULL;
|
||||
return iter;
|
||||
}
|
||||
|
||||
dictEntry *dictNext(dictIterator *iter)
|
||||
{
|
||||
while (1) {
|
||||
if (iter->entry == NULL) {
|
||||
iter->index++;
|
||||
if (iter->index >=
|
||||
(signed)iter->ht->size) break;
|
||||
iter->entry = iter->ht->table[iter->index];
|
||||
} else {
|
||||
iter->entry = iter->nextEntry;
|
||||
}
|
||||
if (iter->entry) {
|
||||
/* We need to save the 'next' here, the iterator user
|
||||
* may delete the entry we are returning. */
|
||||
iter->nextEntry = iter->entry->next;
|
||||
return iter->entry;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void dictReleaseIterator(dictIterator *iter)
|
||||
{
|
||||
_dictFree(iter);
|
||||
}
|
||||
|
||||
/* Return a random entry from the hash table. Useful to
|
||||
* implement randomized algorithms */
|
||||
dictEntry *dictGetRandomKey(dict *ht)
|
||||
{
|
||||
dictEntry *he;
|
||||
unsigned int h;
|
||||
int listlen, listele;
|
||||
|
||||
if (ht->used == 0) return NULL;
|
||||
do {
|
||||
h = random() & ht->sizemask;
|
||||
he = ht->table[h];
|
||||
} while(he == NULL);
|
||||
|
||||
/* Now we found a non empty bucket, but it is a linked
|
||||
* list and we need to get a random element from the list.
|
||||
* The only sane way to do so is to count the element and
|
||||
* select a random index. */
|
||||
listlen = 0;
|
||||
while(he) {
|
||||
he = he->next;
|
||||
listlen++;
|
||||
}
|
||||
listele = random() % listlen;
|
||||
he = ht->table[h];
|
||||
while(listele--) he = he->next;
|
||||
return he;
|
||||
}
|
||||
|
||||
/* ------------------------- private functions ------------------------------ */
|
||||
|
||||
/* Expand the hash table if needed */
|
||||
static int _dictExpandIfNeeded(dict *ht)
|
||||
{
|
||||
/* If the hash table is empty expand it to the intial size,
|
||||
* if the table is "full" dobule its size. */
|
||||
if (ht->size == 0)
|
||||
return dictExpand(ht, DICT_HT_INITIAL_SIZE);
|
||||
if (ht->used == ht->size)
|
||||
return dictExpand(ht, ht->size*2);
|
||||
return DICT_OK;
|
||||
}
|
||||
|
||||
/* Our hash table capability is a power of two */
|
||||
static unsigned long _dictNextPower(unsigned long size)
|
||||
{
|
||||
unsigned long i = DICT_HT_INITIAL_SIZE;
|
||||
|
||||
if (size >= LONG_MAX) return LONG_MAX;
|
||||
while(1) {
|
||||
if (i >= size)
|
||||
return i;
|
||||
i *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns the index of a free slot that can be populated with
|
||||
* an hash entry for the given 'key'.
|
||||
* If the key already exists, -1 is returned. */
|
||||
static int _dictKeyIndex(dict *ht, const void *key)
|
||||
{
|
||||
unsigned int h;
|
||||
dictEntry *he;
|
||||
|
||||
/* Expand the hashtable if needed */
|
||||
if (_dictExpandIfNeeded(ht) == DICT_ERR)
|
||||
return -1;
|
||||
/* Compute the key hash value */
|
||||
h = dictHashKey(ht, key) & ht->sizemask;
|
||||
/* Search if this slot does not already contain the given key */
|
||||
he = ht->table[h];
|
||||
while(he) {
|
||||
if (dictCompareHashKeys(ht, key, he->key))
|
||||
return -1;
|
||||
he = he->next;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
void dictEmpty(dict *ht) {
|
||||
_dictClear(ht);
|
||||
}
|
||||
|
||||
#define DICT_STATS_VECTLEN 50
|
||||
void dictPrintStats(dict *ht) {
|
||||
unsigned long i, slots = 0, chainlen, maxchainlen = 0;
|
||||
unsigned long totchainlen = 0;
|
||||
unsigned long clvector[DICT_STATS_VECTLEN];
|
||||
|
||||
if (ht->used == 0) {
|
||||
printf("No stats available for empty dictionaries\n");
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < DICT_STATS_VECTLEN; i++) clvector[i] = 0;
|
||||
for (i = 0; i < ht->size; i++) {
|
||||
dictEntry *he;
|
||||
|
||||
if (ht->table[i] == NULL) {
|
||||
clvector[0]++;
|
||||
continue;
|
||||
}
|
||||
slots++;
|
||||
/* For each hash entry on this slot... */
|
||||
chainlen = 0;
|
||||
he = ht->table[i];
|
||||
while(he) {
|
||||
chainlen++;
|
||||
he = he->next;
|
||||
}
|
||||
clvector[(chainlen < DICT_STATS_VECTLEN) ? chainlen : (DICT_STATS_VECTLEN-1)]++;
|
||||
if (chainlen > maxchainlen) maxchainlen = chainlen;
|
||||
totchainlen += chainlen;
|
||||
}
|
||||
printf("Hash table stats:\n");
|
||||
printf(" table size: %ld\n", ht->size);
|
||||
printf(" number of elements: %ld\n", ht->used);
|
||||
printf(" different slots: %ld\n", slots);
|
||||
printf(" max chain length: %ld\n", maxchainlen);
|
||||
printf(" avg chain length (counted): %.02f\n", (float)totchainlen/slots);
|
||||
printf(" avg chain length (computed): %.02f\n", (float)ht->used/slots);
|
||||
printf(" Chain length distribution:\n");
|
||||
for (i = 0; i < DICT_STATS_VECTLEN-1; i++) {
|
||||
if (clvector[i] == 0) continue;
|
||||
printf(" %s%ld: %ld (%.02f%%)\n",(i == DICT_STATS_VECTLEN-1)?">= ":"", i, clvector[i], ((float)clvector[i]/ht->size)*100);
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------- StringCopy Hash Table Type ------------------------*/
|
||||
|
||||
static unsigned int _dictStringCopyHTHashFunction(const void *key)
|
||||
{
|
||||
return dictGenHashFunction(key, strlen(key));
|
||||
}
|
||||
|
||||
static void *_dictStringCopyHTKeyDup(void *privdata, const void *key)
|
||||
{
|
||||
int len = strlen(key);
|
||||
char *copy = _dictAlloc(len+1);
|
||||
DICT_NOTUSED(privdata);
|
||||
|
||||
memcpy(copy, key, len);
|
||||
copy[len] = '\0';
|
||||
return copy;
|
||||
}
|
||||
|
||||
static void *_dictStringKeyValCopyHTValDup(void *privdata, const void *val)
|
||||
{
|
||||
int len = strlen(val);
|
||||
char *copy = _dictAlloc(len+1);
|
||||
DICT_NOTUSED(privdata);
|
||||
|
||||
memcpy(copy, val, len);
|
||||
copy[len] = '\0';
|
||||
return copy;
|
||||
}
|
||||
|
||||
static int _dictStringCopyHTKeyCompare(void *privdata, const void *key1,
|
||||
const void *key2)
|
||||
{
|
||||
DICT_NOTUSED(privdata);
|
||||
|
||||
return strcmp(key1, key2) == 0;
|
||||
}
|
||||
|
||||
static void _dictStringCopyHTKeyDestructor(void *privdata, void *key)
|
||||
{
|
||||
DICT_NOTUSED(privdata);
|
||||
|
||||
_dictFree((void*)key); /* ATTENTION: const cast */
|
||||
}
|
||||
|
||||
static void _dictStringKeyValCopyHTValDestructor(void *privdata, void *val)
|
||||
{
|
||||
DICT_NOTUSED(privdata);
|
||||
|
||||
_dictFree((void*)val); /* ATTENTION: const cast */
|
||||
}
|
||||
|
||||
dictType dictTypeHeapStringCopyKey = {
|
||||
_dictStringCopyHTHashFunction, /* hash function */
|
||||
_dictStringCopyHTKeyDup, /* key dup */
|
||||
NULL, /* val dup */
|
||||
_dictStringCopyHTKeyCompare, /* key compare */
|
||||
_dictStringCopyHTKeyDestructor, /* key destructor */
|
||||
NULL /* val destructor */
|
||||
};
|
||||
|
||||
/* This is like StringCopy but does not auto-duplicate the key.
|
||||
* It's used for intepreter's shared strings. */
|
||||
dictType dictTypeHeapStrings = {
|
||||
_dictStringCopyHTHashFunction, /* hash function */
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
_dictStringCopyHTKeyCompare, /* key compare */
|
||||
_dictStringCopyHTKeyDestructor, /* key destructor */
|
||||
NULL /* val destructor */
|
||||
};
|
||||
|
||||
/* This is like StringCopy but also automatically handle dynamic
|
||||
* allocated C strings as values. */
|
||||
dictType dictTypeHeapStringCopyKeyValue = {
|
||||
_dictStringCopyHTHashFunction, /* hash function */
|
||||
_dictStringCopyHTKeyDup, /* key dup */
|
||||
_dictStringKeyValCopyHTValDup, /* val dup */
|
||||
_dictStringCopyHTKeyCompare, /* key compare */
|
||||
_dictStringCopyHTKeyDestructor, /* key destructor */
|
||||
_dictStringKeyValCopyHTValDestructor, /* val destructor */
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>AppendCommand: Contents</b><br> <a href="#APPEND _key_ _value_">APPEND _key_ _value_</a><br> <a href="#Return value">Return value</a><br> <a href="#Examples">Examples</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">AppendCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="StringCommandsSidebar.html">StringCommandsSidebar</a><h1><a name="APPEND _key_ _value_">APPEND _key_ _value_</a></h1>
|
||||
<i>Time complexity: O(1). The amortized time complexity is O(1) assuming the appended value is small and the already present value is of any size, since the dynamic string library used by Redis will double the free space available on every reallocation.</i><blockquote>If the <i>key</i> already exists and is a string, this command appends theprovided value at the end of the string.If the <i>key</i> does not exist it is created and set as an empty string, soAPPEND will be very similar to SET in this special case.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically the total length of the string after the append operation.<h2><a name="Examples">Examples</a></h2><pre class="codeblock python" name="code">
|
||||
redis> exists mykey
|
||||
(integer) 0
|
||||
redis> append mykey "Hello "
|
||||
(integer) 6
|
||||
redis> append mykey "World"
|
||||
(integer) 11
|
||||
redis> get mykey
|
||||
"Hello World"
|
||||
</pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>AppendOnlyFileHowto: Contents</b><br> <a href="#General Information">General Information</a><br> <a href="#Log rewriting">Log rewriting</a><br> <a href="#Wait... but how does this work?">Wait... but how does this work?</a><br> <a href="#How durable is the append only file?">How durable is the append only file?</a>
|
||||
<b>AppendOnlyFileHowto: Contents</b><br> <a href="#Append Only File HOWTO">Append Only File HOWTO</a><br> <a href="#General Information">General Information</a><br> <a href="#Log rewriting">Log rewriting</a><br> <a href="#Wait... but how does this work?">Wait... but how does this work?</a><br> <a href="#How durable is the append only file?">How durable is the append only file?</a><br> <a href="#What should I do if my Append Only File gets corrupted?">What should I do if my Append Only File gets corrupted?</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">AppendOnlyFileHowto</h1>
|
||||
@@ -26,11 +26,12 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
= Append Only File HOWTO =<h2><a name="General Information">General Information</a></h2>Append only file is an alternative durability option for Redis. What this mean? Let's start with some fact:<br/><br/><ul><li> For default Redis saves snapshots of the dataset on disk, in a binary file called dump.rdb (by default at least). For instance you can configure Redis to save the dataset every 60 seconds if there are at least 100 changes in the dataset, or every 1000 seconds if there is at least a single change in the dataset. This is known as "Snapshotting".</li><li> Snapshotting is not very durable. If your computer running Redis stops, your power line fails, or you write killall -9 redis-server for a mistake, the latest data written on Redis will get lost. There are applications where this is not a big deal. There are applications where this is not acceptable and Redis <b>was</b> not an option for this applications.</li></ul>
|
||||
#sidebar <a href="RedisGuides.html">RedisGuides</a>
|
||||
<h1><a name="Append Only File HOWTO">Append Only File HOWTO</a></h1><h2><a name="General Information">General Information</a></h2>Append only file is an alternative durability option for Redis. What this mean? Let's start with some fact:<br/><br/><ul><li> For default Redis saves snapshots of the dataset on disk, in a binary file called dump.rdb (by default at least). For instance you can configure Redis to save the dataset every 60 seconds if there are at least 100 changes in the dataset, or every 1000 seconds if there is at least a single change in the dataset. This is known as "Snapshotting".</li><li> Snapshotting is not very durable. If your computer running Redis stops, your power line fails, or you write killall -9 redis-server for a mistake, the latest data written on Redis will get lost. There are applications where this is not a big deal. There are applications where this is not acceptable and Redis <b>was</b> not an option for this applications.</li></ul>
|
||||
What is the solution? To use append only file as alternative to snapshotting. How it works?<br/><br/><ul><li> It is an 1.1 only feature.</li><li> You have to turn it on editing the configuration file. Just make sure you have "appendonly yes" somewhere.</li><li> Append only files work this way: every time Redis receive a command that changes the dataset (for instance a SET or LPUSH command) it appends this command in the append only file. When you restart Redis it will first <b>re-play</b> the append only file to rebuild the state.</li></ul>
|
||||
<h2><a name="Log rewriting">Log rewriting</a></h2>As you can guess... the append log file gets bigger and bigger, every time there is a new operation changing the dataset. Even if you set always the same key "mykey" to the values of "1", "2", "3", ... up to 10000000000 in the end you'll have just a single key in the dataset, just a few bytes! but how big will be the append log file? Very very big.<br/><br/>So Redis supports an interesting feature: it is able to rebuild the append log file, in background, without to stop processing client commands. The key is the command <a href="BGREWRITEAOF.html">BGREWRITEAOF</a>. This command basically is able to use the dataset in memory in order to rewrite the shortest sequence of commands able to rebuild the exact dataset that is currently in memory.<br/><br/>So from time to time when the log gets too big, try this command. It's safe as if it fails you will not lost your old log (but you can make a backup copy given that currently 1.1 is still in beta!).<h2><a name="Wait... but how does this work?">Wait... but how does this work?</a></h2>Basically it uses the same fork() copy-on-write trick that snapshotting already uses. This is how the algorithm works:<br/><br/><ul><li> Redis forks, so now we have a child and a parent.</li><li> The child starts writing the new append log file in a temporary file.</li><li> The parent accumulates all the new changes in an in-memory buffer.</li><li> When the child finished to rewrite the file, the parent gets a signal, and append the in-memory buffer at the end of the file generated by the child.</li><li> Profit! Now Redis atomically renames the old file into the new one, and starts appending new data into the new file.</li></ul>
|
||||
<h2><a name="Log rewriting">Log rewriting</a></h2>As you can guess... the append log file gets bigger and bigger, every time there is a new operation changing the dataset. Even if you set always the same key "mykey" to the values of "1", "2", "3", ... up to 10000000000 in the end you'll have just a single key in the dataset, just a few bytes! but how big will be the append log file? Very very big.<br/><br/>So Redis supports an interesting feature: it is able to rebuild the append log file, in background, without to stop processing client commands. The key is the command <a href="BGREWRITEAOF.html">BGREWRITEAOF</a>. This command basically is able to use the dataset in memory in order to rewrite the shortest sequence of commands able to rebuild the exact dataset that is currently in memory.<br/><br/>So from time to time when the log gets too big, try this command. It's safe as if it fails you will not lost your old log (but you can make a backup copy given that currently 1.1 is still in beta!).<h2><a name="Wait... but how does this work?">Wait... but how does this work?</a></h2>Basically it uses the same fork() copy-on-write trick that snapshotting already uses. This is how the algorithm works:<br/><br/><ul><li> Redis forks, so now we have a child and a parent.</li><li> The child starts writing the new append log file in a temporary file.</li><li> The parent accumulates all the new changes in an in-memory buffer (but at the same time it writes the new changes in the <b>old</b> append only file, so if the rewriting fails, we are safe).</li><li> When the child finished to rewrite the file, the parent gets a signal, and append the in-memory buffer at the end of the file generated by the child.</li><li> Profit! Now Redis atomically renames the old file into the new one, and starts appending new data into the new file.</li></ul>
|
||||
<h2><a name="How durable is the append only file?">How durable is the append only file?</a></h2>Check redis.conf, you can configure how many times Redis will fsync() data on disk. There are three options:<br/><br/><ul><li> Fsync() every time a new command is appended to the append log file. Very very slow, very safe.</li><li> Fsync() one time every second. Fast enough, and you can lose 1 second of data if there is a disaster.</li><li> Never fsync(), just put your data in the hands of the Operating System. The faster and unsafer method.</li></ul>
|
||||
Warning: by default Redis will fsync() after <b>every command</b>! This is because the Redis authors want to ship a default configuration that is the safest pick. But the best compromise for most datasets is to fsync() one time every second.
|
||||
The suggested (and default) policy is "everysec". It is both very fast and pretty safe. The "always" policy is very slow in practice, even if it was improved in Redis 2.0.0 there is no way to make fsync() faster than it is.<h2><a name="What should I do if my Append Only File gets corrupted?">What should I do if my Append Only File gets corrupted?</a></h2>It is possible that the server crashes while writing the AOF file (this still should never lead to inconsistencies) corrupting the file in a way that is no longer loadable by Redis. When this happens you can fix this problem using the following procedure:<br/><br/><ul><li> Make a backup copy of your AOF file.</li><li> Fix the original file with: ./redis-check-aof --fix <code name="code" class="python"><filename></code></li><li> Optionally use diff -u to check what is the difference between two files.</li><li> Restart the server with the fixed file.</li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>BlpopCommand: Contents</b><br> <a href="#BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis >">BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis ></a><br> <a href="#BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis >">BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis ></a><br> <a href="#Non blocking behavior">Non blocking behavior</a><br> <a href="#Blocking behavior">Blocking behavior</a><br> <a href="#Multiple clients blocking for the same keys">Multiple clients blocking for the same keys</a><br> <a href="#blocking POP inside a MULTI/EXEC transaction">blocking POP inside a MULTI/EXEC transaction</a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">BlpopCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="ListCommandsSidebar.html">ListCommandsSidebar</a><h1><a name="BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis >">BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis ></a></h1> 1.3.1) =
|
||||
<h1><a name="BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis >">BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis ></a></h1> 1.3.1) =
|
||||
<i>Time complexity: O(1)</i><blockquote>BLPOP (and BRPOP) is a blocking list pop primitive. You can see this commandsas blocking versions of <a href="LpopCommand.html">LPOP</a> and <a href="LpopCommand.html">RPOP</a> able toblock if the specified keys don't exist or contain empty lists.</blockquote>
|
||||
<blockquote>The following is a description of the exact semantic. We describe BLPOP butthe two commands are identical, the only difference is that BLPOP pops theelement from the left (head) of the list, and BRPOP pops from the right (tail).</blockquote>
|
||||
<h2><a name="Non blocking behavior">Non blocking behavior</a></h2><blockquote>When BLPOP is called, if at least one of the specified keys contain a nonempty list, an element is popped from the head of the list and returned tothe caller together with the name of the key (BLPOP returns a two elementsarray, the first element is the key, the second the popped value).</blockquote>
|
||||
<blockquote>Keys are scanned from left to right, so for instance if youissue <b>BLPOP list1 list2 list3 0</b> against a dataset where <b>list1</b> does notexist but <b>list2</b> and <b>list3</b> contain non empty lists, BLPOP guaranteesto return an element from the list stored at <b>list2</b> (since it is the firstnon empty list starting from the left).</blockquote>
|
||||
<h2><a name="Blocking behavior">Blocking behavior</a></h2><blockquote>If none of the specified keys exist or contain non empty lists, BLPOPblocks until some other client performs a <a href="RpushCommand.html">LPUSH</a> oran <a href="RpushCommand.html">RPUSH</a> operation against one of the lists.</blockquote>
|
||||
<blockquote>Once new data is present on one of the lists, the client finally returnswith the name of the key unblocking it and the popped value.</blockquote>
|
||||
<blockquote>When blocking, if a non-zero timeout is specified, the client will unblockreturning a nil special value if the specified amount of seconds passedwithout a push operation against at least one of the specified keys.</blockquote>
|
||||
<blockquote>The timeout argument is interpreted as an integer value. A timeout of zero means instead to block forever.</blockquote>
|
||||
<h2><a name="Multiple clients blocking for the same keys">Multiple clients blocking for the same keys</a></h2><blockquote>Multiple clients can block for the same key. They are put intoa queue, so the first to be served will be the one that started to waitearlier, in a first-blpopping first-served fashion.</blockquote>
|
||||
<h2><a name="blocking POP inside a MULTI/EXEC transaction">blocking POP inside a MULTI/EXEC transaction</a></h2><blockquote>BLPOP and BRPOP can be used with pipelining (sending multiple commands and reading the replies in batch), but it does not make sense to use BLPOP or BRPOP inside a MULTI/EXEC block (a Redis transaction).</blockquote>
|
||||
<blockquote>The behavior of BLPOP inside MULTI/EXEC when the list is empty is to return a multi-bulk nil reply, exactly what happens when the timeout is reached. If you like science fiction, think at it like if inside MULTI/EXEC the time will flow at infinite speed :) </blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><blockquote>BLPOP returns a two-elements array via a multi bulk reply in order to returnboth the unblocking key and the popped value.</blockquote>
|
||||
<blockquote>When a non-zero timeout is specified, and the BLPOP operation timed out,the return value is a nil multi bulk reply. Most client values will returnfalse or nil accordingly to the programming language used.</blockquote>
|
||||
<a href="ReplyTypes.html">Multi bulk reply</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>BrpoplpushCommand: Contents</b><br> <a href="#BRPOPLPUSH _srckey_ _dstkey_ _timeout_ (Redis >">BRPOPLPUSH _srckey_ _dstkey_ _timeout_ (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">BrpoplpushCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
<h1><a name="BRPOPLPUSH _srckey_ _dstkey_ _timeout_ (Redis >">BRPOPLPUSH _srckey_ _dstkey_ _timeout_ (Redis ></a></h1> 2.1.8) =
|
||||
<i>Time complexity: O(1)</i><blockquote>Blocking version of the <a href="RpoplpushCommand.html">RPOPLPUSH</a> command. Atomically removes and returnsthe last element (tail) of the source list at <i>srckey</i>, and as a side effect pushes the returned element in the head of the list at <i>dstkey</i>.</blockquote>
|
||||
If the source list is empty, the client blocks until another client pushes against the source list. Of course in such a case the push operation against the destination list will be performed after the command unblocks detecting a push against the source list.<br/><br/>Note that the command returns an error if the target key already exists but is not a list. The error is delayed at the time the push operation is attempted, that is, immediately if the source list is not empty, or when the first push against the source list happens in the case the command would block.<br/><br/>The timeout value can be 0 or a positive integer value. When it is zero the command will block forever, until something is pushed against <i>srckey</i>. Otherwise the command will wait the specified number of seconds at max, returning an nil value when the timeout expires.<br/><br/>The source and destination of the list can be the same, having the effect of rotating the list. Please check <a href="RpoplpushCommand.html">RPOPLPUSH</a> for more information.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+13
-10
File diff suppressed because one or more lines are too long
@@ -0,0 +1,76 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>ConfigCommand: Contents</b><br> <a href="#CONFIG GET _pattern_ (Redis >">CONFIG GET _pattern_ (Redis ></a><br> <a href="#CONFIG SET _parameter_ _value_ (Redis >">CONFIG SET _parameter_ _value_ (Redis ></a><br> <a href="#CONFIG GET _pattern_">CONFIG GET _pattern_</a><br> <a href="#CONFIG SET _parameter_ _value_">CONFIG SET _parameter_ _value_</a><br> <a href="#Parameters value format">Parameters value format</a><br> <a href="#See Also">See Also</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">ConfigCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="ControlCommandsSidebar.html">ControlCommandsSidebar</a><h1><a name="CONFIG GET _pattern_ (Redis >">CONFIG GET _pattern_ (Redis ></a></h1> 2.0)=
|
||||
<h1><a name="CONFIG SET _parameter_ _value_ (Redis >">CONFIG SET _parameter_ _value_ (Redis ></a></h1> 2.0)=<br/><br/><blockquote>The CONFIG command is able to retrieve or alter the configuration of a runningRedis server. Not all the configuration parameters are supported.</blockquote>
|
||||
<blockquote>CONFIG has two sub commands, GET and SET. The GET command is used to readthe configuration, while the SET command is used to alter the configuration.</blockquote>
|
||||
<h2><a name="CONFIG GET _pattern_">CONFIG GET _pattern_</a></h2><blockquote>CONFIG GET returns the current configuration parameters. This sub commandonly accepts a single argument, that is glob style pattern. All theconfiguration parameters matching this parameter are reported as alist of key-value pairs. Example:</blockquote><pre class="codeblock python" name="code">
|
||||
$ redis-cli config get '*'
|
||||
1. "dbfilename"
|
||||
2. "dump.rdb"
|
||||
3. "requirepass"
|
||||
4. (nil)
|
||||
5. "masterauth"
|
||||
6. (nil)
|
||||
7. "maxmemory"
|
||||
8. "0\n"
|
||||
9. "appendfsync"
|
||||
10. "everysec"
|
||||
11. "save"
|
||||
12. "3600 1 300 100 60 10000"
|
||||
|
||||
$ redis-cli config get 'm*'
|
||||
1. "masterauth"
|
||||
2. (nil)
|
||||
3. "maxmemory"
|
||||
4. "0\n"
|
||||
</pre>The return type of the command is a <a href="ReplyTypes.html">bulk reply</a>.<h2><a name="CONFIG SET _parameter_ _value_">CONFIG SET _parameter_ _value_</a></h2><blockquote>CONFIG SET is used in order to reconfigure the server, setting a specificconfiguration parameter to a new value.</blockquote>
|
||||
<blockquote>The list of configuration parameters supported by CONFIG SET can beobtained issuing a <code name="code" class="python">CONFIG GET *</code> command.</blockquote>
|
||||
<blockquote>The configuration set using CONFIG SET is immediately loaded by the Redisserver that will start acting as specified starting from the next command.</blockquote>
|
||||
<blockquote>Example:</blockquote><pre class="codeblock python python" name="code">
|
||||
$ ./redis-cli
|
||||
redis> set x 10
|
||||
OK
|
||||
redis> config set maxmemory 200
|
||||
OK
|
||||
redis> set y 20
|
||||
(error) ERR command not allowed when used memory > 'maxmemory'
|
||||
redis> config set maxmemory 0
|
||||
OK
|
||||
redis> set y 20
|
||||
OK
|
||||
</pre><h2><a name="Parameters value format">Parameters value format</a></h2><blockquote>The value of the configuration parameter is the same as the one of thesame parameter in the Redis configuration file, with the following exceptions:</blockquote>
|
||||
<ul><li> The <code name="code" class="python">save</code> paramter is a list of space-separated integers. Every pair of integers specify the time and number of changes limit to trigger a save. For instance the command <code name="code" class="python">CONFIG SET save "3600 10 60 10000"</code> will configure the server to issue a background saving of the RDB file every 3600 seconds if there are at least 10 changes in the dataset, and every 60 seconds if there are at least 10000 changes. To completely disable automatic snapshots just set the parameter as an empty string.</li><li> All the integer parameters representing memory are returned and accepted only using bytes as unit.</li></ul>
|
||||
<h2><a name="See Also">See Also</a></h2>The <a href="InfoCommand.html">INFO</a> command can be used in order to read configuriaton parameters that are not available in the CONFIG command.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+1
-2
@@ -27,12 +27,11 @@
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="DEL _key1_ _key2_ ... _keyN_">DEL _key1_ _key2_ ... _keyN_</a></h1>
|
||||
<i>Time complexity: O(1)</i><blockquote>Remove the specified keys. If a given key does not existno operation is performed for this key. The commnad returns the number ofkeys removed.</blockquote>
|
||||
<i>Time complexity: O(1)</i><blockquote>Remove the specified keys. If a given key does not existno operation is performed for this key. The command returns the number ofkeys removed.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
|
||||
an integer greater than 0 if one or more keys were removed
|
||||
0 if none of the specified key existed
|
||||
</pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>EventLibray: Contents</b><br> <a href="#Event Library">Event Library</a><br> <a href="#Why is an Event Library needed at all?">Why is an Event Library needed at all?</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">EventLibray</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
<h1><a name="Event Library">Event Library</a></h1><h2><a name="Why is an Event Library needed at all?">Why is an Event Library needed at all?</a></h2>Let us figure it out through a series of Q&As.<br/><br/>Q: What do you expect a network server to be doing all the time? <br/>
|
||||
A: Watch for inbound connections on the port its listening and accept them.<br/><br/>Q: Calling <a href="http://man.cx/accept%282%29" target="_blank">accept</a> yields a descriptor. What do I do with it?<br/>
|
||||
A: Save the descriptor and do a non-blocking read/write operation on it.<br/><br/>Q: Why does the read/write have to be non-blocking?<br/>
|
||||
A: If the file operation ( even a socket in Unix is a file ) is blocking how could the server for example accept other connection requests when its blocked in a file I/O operation.<br/><br/>Q: I guess I have to do many such non-blocking operations on the socket to see when it's ready. Am I right?<br/>
|
||||
A: Yes. That is what an event library does for you. Now you get it.<br/><br/>Q: How do Event Libraries do what they do?<br/>
|
||||
A: They use the operating system's <a href="http://www.devshed.com/c/a/BrainDump/Linux-Files-and-the-Event-Poll-Interface/" target="_blank">polling</a> facility along with timers.<br/><br/>Q: So are there any open source event libraries that do what you just described? <br/>
|
||||
A: Yes. Libevent and Libev are two such event libraries that I can recall off the top of my head.<br/><br/>Q: Does Redis use such open source event libraries for handling socket I/O?<br/>
|
||||
A: No. For various <a href="http://groups.google.com/group/redis-db/browse_thread/thread/b52814e9ef15b8d0/" target="_blank">reasons</a> Redis uses its own event library.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+38
-10
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>ExpireCommand: Contents</b><br> <a href="#EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a><br> <a href="#EXPIREAT _key_ _unixtime_ (Redis >">EXPIREAT _key_ _unixtime_ (Redis ></a><br> <a href="#How the expire is removed from a key">How the expire is removed from a key</a><br> <a href="#Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a><br> <a href="#Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a><br> <a href="#Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a><br> <a href="#Version 1.0">Version 1.0</a><br> <a href="#Version 1.1">Version 1.1</a><br> <a href="#Return value">Return value</a>
|
||||
<b>ExpireCommand: Contents</b><br> <a href="#EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a><br> <a href="#EXPIREAT _key_ _unixtime_ (Redis >">EXPIREAT _key_ _unixtime_ (Redis ></a><br> <a href="#PERSIST _key_ (Redis >">PERSIST _key_ (Redis ></a><br> <a href="#How the expire is removed from a key">How the expire is removed from a key</a><br> <a href="#Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a><br> <a href="#Restrictions for write operations with volatile keys as sources">Restrictions for write operations with volatile keys as sources</a><br> <a href="#Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a><br> <a href="#Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a><br> <a href="#Version 1.0">Version 1.0</a><br> <a href="#Version 1.1">Version 1.1</a><br> <a href="#Return value">Return value</a><br> <a href="#FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?</a><br> <a href="#FAQ: How this limitations were solved in Redis versions > 2.1.3?">FAQ: How this limitations were solved in Redis versions > 2.1.3?</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">ExpireCommand</h1>
|
||||
@@ -28,12 +28,15 @@
|
||||
<div class="narrow">
|
||||
#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a></h1>
|
||||
<h1><a name="EXPIREAT _key_ _unixtime_ (Redis >">EXPIREAT _key_ _unixtime_ (Redis ></a></h1> 1.1)=
|
||||
<i>Time complexity: O(1)</i><blockquote>Set a timeout on the specified key. After the timeout the key will beautomatically delete by the server. A key with an associated timeout issaid to be <i>volatile</i> in Redis terminology.</blockquote>
|
||||
<blockquote>Voltile keys are stored on disk like the other keys, the timeout is persistenttoo like all the other aspects of the dataset. Saving a dataset containingthe dataset and stopping the server does not stop the flow of time as Redisregisters on disk when the key will no longer be available as Unix time, andnot the remaining seconds.</blockquote>
|
||||
<h1><a name="PERSIST _key_ (Redis >">PERSIST _key_ (Redis ></a></h1> 2.1.3) =
|
||||
<i>Time complexity: O(1)</i><blockquote>Set a timeout on the specified key. After the timeout the key will beautomatically deleted by the server. A key with an associated timeout issaid to be <i>volatile</i> in Redis terminology.</blockquote>
|
||||
<blockquote>Voltile keys are stored on disk like the other keys, the timeout is persistenttoo like all the other aspects of the dataset. Saving a dataset containingexpires and stopping the server does not stop the flow of time as Redisstores on disk the time when the key will no longer be available as Unixtime, and not the remaining seconds.</blockquote>
|
||||
<blockquote>EXPIREAT works exctly like EXPIRE but instead to get the number of secondsrepresenting the Time To Live of the key as a second argument (that is arelative way of specifing the TTL), it takes an absolute one in the form ofa UNIX timestamp (Number of seconds elapsed since 1 Gen 1970).</blockquote>
|
||||
<blockquote>EXPIREAT was introduced in order to implement [Persistence append only saving mode] so that EXPIRE commands are automatically translated into EXPIREAT commands for the append only file. Of course EXPIREAT can alsoused by programmers that need a way to simply specify that a given key should expire at a given time in the future.</blockquote>
|
||||
<h2><a name="How the expire is removed from a key">How the expire is removed from a key</a></h2><blockquote>When the key is set to a new value using the SET command, the INCR commandor any other command that modify the value stored at key the timeout isremoved from the key and the key becomes non volatile.</blockquote>
|
||||
<h2><a name="Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a></h2><blockquote>Write operations like LPUSH, LSET and every other command that has theeffect of modifying the value stored at a volatile key have a special semantic:basically a volatile key is destroyed when it is target of a write operation.See for example the following usage pattern:</blockquote>
|
||||
<blockquote>EXPIREAT was introduced in order to implement <a href="AppendOnlyFileHowto.html">the Append Only File persistence mode</a>so that EXPIRE commands are automatically translated into EXPIREAT commands for the append only file. Of course EXPIREAT can alsoused by programmers that need a way to simply specify that a given key should expire at a given time in the future.</blockquote>
|
||||
<blockquote>Since Redis 2.1.3 you can update the value of the timeout of a key alreadyhaving an expire set. It is also possible to undo the expire at allturning the key into a normal key using the PERSIST command.</blockquote>
|
||||
<h2><a name="How the expire is removed from a key">How the expire is removed from a key</a></h2><blockquote>When the key is set to a new value using the SET command, or when a keyis destroied via DEL, the timeout is removed from the key.</blockquote>
|
||||
<h2><a name="Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a></h2><blockquote>IMPORTANT: Since Redis 2.1.3 or greater, there are no restrictions aboutthe operations you can perform against volatile keys, however older versionsof Redis, including the current stable version 2.0.0, has the followinglimitations:</blockquote>
|
||||
<blockquote>Write operations like LPUSH, LSET and every other command that has theeffect of modifying the value stored at a volatile key have a special semantic:basically a volatile key is destroyed when it is target of a write operation.See for example the following usage pattern:</blockquote>
|
||||
<pre class="codeblock python" name="code">
|
||||
% ./redis-cli lpush mylist foobar /Users/antirez/hack/redis
|
||||
OK
|
||||
@@ -45,8 +48,13 @@ OK
|
||||
OK
|
||||
% ./redis-cli lrange mylist 0 -1 /Users/antirez/hack/redis
|
||||
1. newelement
|
||||
</pre><blockquote>What happened here is that lpush against the key with a timeout set deletedthe key before to perform the operation. There is so a simple rule, writeoperations against volatile keys will destroy the key before to perform theoperation. Why Redis uses this behavior? In order to retain an importantproperty: a server that receives a given number of commands in the samesequence will end with the same dataset in memory. Without the delete-on-writesemantic what happens is that the state of the server depends on the timeof the commands to. This is not a desirable property in a distributed databasethat supports replication.</blockquote>
|
||||
<h2><a name="Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a></h2><blockquote>Trying to call EXPIRE against a key that already has an associated timeoutwill not change the timeout of the key, but will just return 0. If insteadthe key does not have a timeout associated the timeout will be set and EXPIREwill return 1.</blockquote>
|
||||
</pre><blockquote>What happened here is that LPUSH against the key with a timeout set deletedthe key before to perform the operation. There is so a simple rule, writeoperations against volatile keys will destroy the key before to perform theoperation. Why Redis uses this behavior? In order to retain an importantproperty: a server that receives a given number of commands in the samesequence will end with the same dataset in memory. Without the delete-on-writesemantic what happens is that the state of the server depends on the timethe commands were issued. This is not a desirable property in a distributed databasethat supports replication.</blockquote>
|
||||
<h2><a name="Restrictions for write operations with volatile keys as sources">Restrictions for write operations with volatile keys as sources</a></h2>Even when the volatile key is not modified as part of a write operation, if it is
|
||||
read in a composite write operation (such as SINTERSTORE) it will be cleared at the
|
||||
start of the operation. This is done to avoid concurrency issues in replication.
|
||||
Imagine a key that is about to expire and the composite operation is run against it.
|
||||
On a slave node, this key might already be expired, which leaves you with a
|
||||
desync in your dataset.<h2><a name="Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a></h2><blockquote>Trying to call EXPIRE against a key that already has an associated timeoutwill not change the timeout of the key, but will just return 0. If insteadthe key does not have a timeout associated the timeout will be set and EXPIREwill return 1.</blockquote>
|
||||
<h2><a name="Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a></h2><blockquote>Redis does not constantly monitor keys that are going to be expired.Keys are expired simply when some client tries to access a key, andthe key is found to be timed out.</blockquote>
|
||||
<blockquote>Of course this is not enough as there are expired keys that will neverbe accessed again. This keys should be expired anyway, so once everysecond Redis test a few keys at random among keys with an expire set.All the keys that are already expired are deleted from the keyspace. </blockquote>
|
||||
<h3><a name="Version 1.0">Version 1.0</a></h3><blockquote>Each time a fixed number of keys where tested (100 by default). So ifyou had a client setting keys with a very short expire faster than 100for second the memory continued to grow. When you stopped to insertnew keys the memory started to be freed, 100 keys every second in thebest conditions. Under a peak Redis continues to use more and more RAMeven if most keys are expired in each sweep.</blockquote>
|
||||
@@ -56,9 +64,29 @@ OK
|
||||
<blockquote>This means that at any given moment the maximum amount of keys alreadyexpired that are using memory is at max equal to max setting operations per second divided by 4.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python python" name="code">
|
||||
1: the timeout was set.
|
||||
0: the timeout was not set since the key already has an associated timeout, or the key does not exist.
|
||||
0: the timeout was not set since the key already has an associated timeout
|
||||
(this may happen only in Redis versions < 2.1.3, Redis >= 2.1.3 will
|
||||
happily update the timeout), or the key does not exist.
|
||||
</pre><h2><a name="FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?</a></h2>
|
||||
Ok let's start with the problem:
|
||||
<pre class="codeblock python python python" name="code">
|
||||
redis> set a 100
|
||||
OK
|
||||
redis> expire a 360
|
||||
(integer) 1
|
||||
redis> incr a
|
||||
(integer) 1
|
||||
</pre>
|
||||
|
||||
I set a key to the value of 100, then set an expire of 360 seconds, and then incremented the key (before the 360 timeout expired of course). The obvious result would be: 101, instead the key is set to the value of 1. Why?
|
||||
There is a very important reason involving the Append Only File and Replication. Let's rework a bit our example adding the notion of time to the mix:
|
||||
<pre class="codeblock python python python python" name="code">
|
||||
SET a 100
|
||||
EXPIRE a 5
|
||||
... wait 10 seconds ...
|
||||
INCR a
|
||||
</pre>
|
||||
Imagine a Redis version that does not implement the "Delete keys with an expire set on write operation" semantic.
|
||||
Running the above example with the 10 seconds pause will lead to 'a' being set to the value of 1, as it no longer exists when INCR is called 10 seconds later.<br/><br/>Instead if we drop the 10 seconds pause, the result is that 'a' is set to 101.<br/><br/>And in the practice timing changes! For instance the client may wait 10 seconds before INCR, but the sequence written in the Append Only File (and later replayed-back as fast as possible when Redis is restarted) will not have the pause. Even if we add a timestamp in the AOF, when the time difference is smaller than our timer resolution, we have a race condition.<br/><br/>The same happens with master-slave replication. Again, consider the example above: the client will use the same sequence of commands without the 10 seconds pause, but the replication link will slow down for a few seconds due to a network problem. Result? The master will contain 'a' set to 101, the slave 'a' set to 1.<br/><br/>The only way to avoid this but at the same time have reliable non time dependent timeouts on keys is to destroy volatile keys when a write operation is attempted against it.<br/><br/>After all Redis is one of the rare fully persistent databases that will give you EXPIRE. This comes to a cost :)<h2><a name="FAQ: How this limitations were solved in Redis versions > 2.1.3?">FAQ: How this limitations were solved in Redis versions > 2.1.3?</a></h2>Since Redis 2.1.3 there are no longer restrictions in the use you can do of write commands against volatile keys, still the replication and AOF file are guaranteed to be fully consistent.<br/><br/>In order to obtain a correct behavior without sacrificing consistency now when a key expires, a DEL operation is synthesized in both the AOF file and against all the attached slaves. This way the expiration process is centralized in the master instance, and there is no longer a chance of consistency errors.<br/><br/>However while the slaves while connected to a master will not expire keys independently, they'll still take the full state of the expires existing in the dataset, so when a slave is elected to a master it will be able to expire the keys independently, fully acting as a master.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
+2
-3
File diff suppressed because one or more lines are too long
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
== Generic Commands ==<br/><br/><ul><li> <a href="ExistsCommand.html">EXISTS</a></li><li> <a href="DelCommand.html">DEL</a></li><li> <a href="TypeCommand.html">TYPE</a></li><li> <a href="KeysCommand.html">KEYS</a></li><li> <a href="RandomkeyCommand.html">RANDOMKEY</a></li><li> <a href="RenameCommand.html">RENAME</a></li><li> <a href="RenamenxCommand.html">RENAMENX</a></li><li> <a href="DbsizeCommand.html">DBSIZE</a></li><li> <a href="ExpireCommand.html">EXPIRE</a></li><li> <a href="TtlCommand.html">TTL</a></li><li> <a href="SelectCommand.html">SELECT</a></li><li> <a href="MoveCommand.html">MOVE</a></li><li> <a href="FlushdbCommand.html">FLUSHDB</a></li><li> <a href="FlushallCommand.html">FLUSHALL</a></li></ul>
|
||||
== Generic Commands ==<br/><br/><ul><li> <a href="ExistsCommand.html">EXISTS</a></li><li> <a href="DelCommand.html">DEL</a></li><li> <a href="TypeCommand.html">TYPE</a></li><li> <a href="KeysCommand.html">KEYS</a></li><li> <a href="RandomkeyCommand.html">RANDOMKEY</a></li><li> <a href="RenameCommand.html">RENAME</a></li><li> <a href="RenamenxCommand.html">RENAMENX</a></li><li> <a href="DbsizeCommand.html">DBSIZE</a></li><li> <a href="ExpireCommand.html">EXPIRE</a></li><li> <a href="ExpireCommand.html">PERSIST</a></li><li> <a href="TtlCommand.html">TTL</a></li><li> <a href="SelectCommand.html">SELECT</a></li><li> <a href="MoveCommand.html">MOVE</a></li><li> <a href="FlushdbCommand.html">FLUSHDB</a></li><li> <a href="FlushallCommand.html">FLUSHALL</a></li><li> <a href="MultiExecCommand.html">Redis Transactions</a></li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>GetbitCommand: Contents</b><br> <a href="#GETBIT _key_ _offset_ (Redis >">GETBIT _key_ _offset_ (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">GetbitCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
<h1><a name="GETBIT _key_ _offset_ (Redis >">GETBIT _key_ _offset_ (Redis ></a></h1> 2.1.8) =
|
||||
<i>Time complexity: O(1)</i><blockquote>Returns the bit value at <i>offset</i> in the string value stored at <i>key</i>.</blockquote>
|
||||
When <i>offset</i> is beyond the string length, the string is assumed to be a contiguous space with 0 bits. When <i>key</i> does not exist it is assumed to be an empty string, so <i>offset</i> is always out of range and the value is also assumed to be a contiguous space with 0 bits.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically: the bit value stored at <i>offset</i>.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>HackingStrings: Contents</b><br> <a href="#Hacking Strings">Hacking Strings</a><br> <a href="#Creating Redis Strings">Creating Redis Strings</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">HackingStrings</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
<h1><a name="Hacking Strings">Hacking Strings</a></h1>The implementation of Redis strings is contained in <b></b>sds.c<b></b> ( sds stands for Simple Dynamic Strings ).<br/><br/>The C structure <i>sdshdr</i> declared in <b>sds.h</b> represents a Redis string:<br/><br/><pre class="codeblock python" name="code">
|
||||
struct sdshdr {
|
||||
long len;
|
||||
long free;
|
||||
char buf[];
|
||||
};
|
||||
</pre>The <i>buf</i> character array stores the actual string.<br/><br/>The <i>len</i> field stores the length of <i>buf</i>. This makes obtaining the length
|
||||
of a Redis string an O(1) operation.<br/><br/>The <i>free</i> field stores the number of additional bytes available for use.<br/><br/>Together the <i>len</i> and <i>free</i> field can be thought of as holding the metadata of the
|
||||
<i>buf</i> character array.<h2><a name="Creating Redis Strings">Creating Redis Strings</a></h2>A new data type named <code name="code" class="python">sds</code> is defined in <b>sds.h</b> to be a synonymn for a character pointer:<br/><br/><pre class="codeblock python python" name="code">
|
||||
typedef char *sds;
|
||||
</pre><code name="code" class="python">sdsnewlen</code> function defined in <b>sds.c</b> creates a new Redis String: <br/><br/><pre class="codeblock python python python" name="code">
|
||||
sds sdsnewlen(const void *init, size_t initlen) {
|
||||
struct sdshdr *sh;
|
||||
|
||||
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
|
||||
#ifdef SDS_ABORT_ON_OOM
|
||||
if (sh == NULL) sdsOomAbort();
|
||||
#else
|
||||
if (sh == NULL) return NULL;
|
||||
#endif
|
||||
sh->len = initlen;
|
||||
sh->free = 0;
|
||||
if (initlen) {
|
||||
if (init) memcpy(sh->buf, init, initlen);
|
||||
else memset(sh->buf,0,initlen);
|
||||
}
|
||||
sh->buf[initlen] = '\0';
|
||||
return (char*)sh->buf;
|
||||
}
|
||||
</pre>Remember a Redis string is a variable of type <code name="code" class="python">struct sdshdr</code>. But <code name="code" class="python">sdsnewlen</code> returns a character pointer!!<br/><br/>That's a trick and needs some explanation.<br/><br/>Suppose I create a Redis string using <code name="code" class="python">sdsnewlen</code> like below:<br/><br/><pre class="codeblock python python python python" name="code">
|
||||
sdsnewlen("redis", 5);
|
||||
</pre>This creates a new variable of type <code name="code" class="python">struct sdshdr</code> allocating memory for <i>len</i> and <i>free</i>
|
||||
fields as well as for the <i>buf</i> character array.<br/><br/><pre class="codeblock python python python python python" name="code">
|
||||
sh = zmalloc(sizeof(struct sdshdr)+initlen+1); // initlen is length of init argument.
|
||||
</pre>After <code name="code" class="python">sdsnewlen</code> succesfully creates a Redis string the result is something like:<br/><br/><pre class="codeblock python python python python python python" name="code">
|
||||
-----------
|
||||
|5|0|redis|
|
||||
-----------
|
||||
^ ^
|
||||
sh sh->buf
|
||||
</pre><code name="code" class="python">sdsnewlen</code> returns sh->buf to the caller.<br/><br/>What do you do if you need to free the Redis string pointed by <code name="code" class="python">sh</code>?<br/><br/>You want the pointer <code name="code" class="python">sh</code> but you only have the pointer <code name="code" class="python">sh->buf</code>.<br/><br/>Can you get the pointer <code name="code" class="python">sh</code> from <code name="code" class="python">sh->buf</code>?<br/><br/>Yes. Pointer arithmetic. Notice from the above ASCII art that if you subtract
|
||||
the size of two longs from <code name="code" class="python">sh->buf</code> you get the pointer <code name="code" class="python">sh</code>. <br/><br/>The sizeof two longs happens to be the size of <code name="code" class="python">struct sdshdr</code>.<br/><br/>Look at <code name="code" class="python">sdslen</code> function and see this trick at work:<br/><br/><pre class="codeblock python python python python python python python" name="code">
|
||||
size_t sdslen(const sds s) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
return sh->len;
|
||||
}
|
||||
</pre>Knowing this trick you could easily go through the rest of the functions in <b>sds.c</b>.<br/><br/>The Redis string implementation is hidden behind an interface that accepts only character pointers. The users of Redis strings need not care about how its implemented and treat Redis strings as a character pointer.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>HashCommandsSidebar: Contents</b>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">HashCommandsSidebar</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
== Hash Commands ==<br/><br/><ul><li> <a href="HsetCommand.html">HSET</a></li><li> <a href="HgetCommand.html">HGET</a></li><li> <a href="HsetnxCommand.html">HSETNX</a></li><li> <a href="HmsetCommand.html">HMSET</a></li><li> <a href="HmgetCommand.html">HMGET</a></li><li> <a href="HincrbyCommand.html">HINCRBY</a></li><li> <a href="HexistsCommand.html">HEXISTS</a></li><li> <a href="HdelCommand.html">HDEL</a></li><li> <a href="HlenCommand.html">HLEN</a></li><li> <a href="HgetallCommand.html">HKEYS</a></li><li> <a href="HgetallCommand.html">HVALS</a></li><li> <a href="HgetallCommand.html">HGETALL</a></li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>Hashes: Contents</b><br> <a href="#Redis Hash Type">Redis Hash Type</a><br> <a href="#Implementation details">Implementation details</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">Hashes</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="HashCommandsSidebar.html">HashCommandsSidebar</a><h1><a name="Redis Hash Type">Redis Hash Type</a></h1>Redis Hashes are unordered maps of <a href="String.html">Redis Strings</a> between fields and values. It is possible to add, remove, test for existence of fields in O(1) amortized time. It is also possible to enumerate all the keys, values, or both, in O(N) (where N is the number of fields inside the hash).<br/><br/>Redis Hashes are interesting because they are very well suited to represent objects. For instance web applications users can be represented by a Redis Hash containing fields such username, encrpypted_password, lastlogin, and so forth.<br/><br/>Another very important property of Redis Hashes is that they use very little memory for hashes composed of a small number of fields (configurable, check redis.conf for details), compared to storing every field as a top level Redis key. This is obtained using a different specialized representation for small hashes. See the implementation details paragraph below for more information.<br/><br/>Commands operating on hashes try to make a good use of the return value in order to signal the application about previous existence of fields. For instance the <a href="HsetCommand.html">HSET</a> command will return 1 if the field set was not already present in the hash, otherwise will return 0 (and the user knows this was just an update operation).<br/><br/>The max number of fields in a set is 232-1 (4294967295, more than 4 billion of members per hash).<h1><a name="Implementation details">Implementation details</a></h1>The obvious internal representation of hashes is indeed an hash table, as the name of the data structure itself suggests. Still the drawback of this representation is that there is a lot of space overhead for hash table metadata.<br/><br/>Because one of the most interesting uses of Hashes is object encoding, and objects are often composed of a few fields each, Redis uses a different internal representation for small hashes (for Redis to consider a hash small, this must be composed a limited number of fields, and each field and value can't exceed a given number of bytes. All this is user-configurable).<br/><br/>Small hashes are thus encoded using a data structure called zipmap (is not something you can find in a CS book, the name is a Redis invention), that is a very memory efficient data structure to represent string to string maps, at the cost of being O(N) instead of O(1) for most operations. Since the constant times of this data structure are very small, and the zipmaps are converted into real hash tables once they are big enough, the amortized time of Redis hashes is still O(1), and in the practice small zipmaps are not slower than small hash tables because they are designed for good cache locality and fast access.<br/><br/>The result is that small hashes are both memory efficient and fast, while bigger hashes are fast but not as memory efficient than small hashes.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>HdelCommand: Contents</b><br> <a href="#HDEL _key_ _field_ (Redis >">HDEL _key_ _field_ (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">HdelCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="HashCommandsSidebar.html">HashCommandsSidebar</a><h1><a name="HDEL _key_ _field_ (Redis >">HDEL _key_ _field_ (Redis ></a></h1> 1.3.10)=
|
||||
<i>Time complexity: O(1)</i><blockquote>Remove the specified <i>field</i> from an hash stored at <i>key</i>.</blockquote>
|
||||
<blockquote>If the <i>field</i> was present in the hash it is deleted and 1 is returned, otherwise 0 is returned and no operation is performed.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>HexistsCommand: Contents</b><br> <a href="#HEXISTS _key_ _field_ (Redis >">HEXISTS _key_ _field_ (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">HexistsCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="HashCommandsSidebar.html">HashCommandsSidebar</a><h1><a name="HEXISTS _key_ _field_ (Redis >">HEXISTS _key_ _field_ (Redis ></a></h1> 1.3.10)=
|
||||
<i>Time complexity: O(1)</i><blockquote>Return 1 if the hash stored at <i>key</i> contains the specified <i>field</i>.</blockquote>
|
||||
<blockquote>Return 0 if the <i>key</i> is not found or the <i>field</i> is not present.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>HgetCommand: Contents</b><br> <a href="#HGET _key_ _field_ (Redis >">HGET _key_ _field_ (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">HgetCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="HashCommandsSidebar.html">HashCommandsSidebar</a><h1><a name="HGET _key_ _field_ (Redis >">HGET _key_ _field_ (Redis ></a></h1> 1.3.10)=
|
||||
<i>Time complexity: O(1)</i><blockquote>If <i>key</i> holds a hash, retrieve the value associated to the specified <i>field</i>.</blockquote>
|
||||
<blockquote>If the <i>field</i> is not found or the <i>key</i> does not exist, a special 'nil' value is returned.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>HgetallCommand: Contents</b><br> <a href="#HKEYS _key_ (Redis >">HKEYS _key_ (Redis ></a><br> <a href="#HVALS _key_ (Redis >">HVALS _key_ (Redis ></a><br> <a href="#HGETALL _key_ (Redis >">HGETALL _key_ (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">HgetallCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="HashCommandsSidebar.html">HashCommandsSidebar</a><h1><a name="HKEYS _key_ (Redis >">HKEYS _key_ (Redis ></a></h1> 1.3.10)=
|
||||
<h1><a name="HVALS _key_ (Redis >">HVALS _key_ (Redis ></a></h1> 1.3.10)=
|
||||
<h1><a name="HGETALL _key_ (Redis >">HGETALL _key_ (Redis ></a></h1> 1.3.10)=
|
||||
<i>Time complexity: O(N), where N is the total number of fields in the hash</i><blockquote>HKEYS returns all the fields names contained into a hash, HVALS all the associated values, while HGETALL returns both the fields and values in the form of <i>field1</i>, <i>value1</i>, <i>field2</i>, <i>value2</i>, ..., <i>fieldN</i>, <i>valueN</i>.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi Bulk Reply</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>HincrbyCommand: Contents</b><br> <a href="#HINCRBY _key_ _field_ _value_ (Redis >">HINCRBY _key_ _field_ _value_ (Redis ></a><br> <a href="#Examples">Examples</a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">HincrbyCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
<h1><a name="HINCRBY _key_ _field_ _value_ (Redis >">HINCRBY _key_ _field_ _value_ (Redis ></a></h1> 1.3.10)=
|
||||
<i>Time complexity: O(1)</i><blockquote>Increment the number stored at <i>field</i> in the hash at <i>key</i> by <i>value</i>. If <i>key</i> does not exist, a new key holding a hash is created. If <i>field</i> does not exist or holds a string, the value is set to 0 before applying the operation.</blockquote>
|
||||
<blockquote>The range of values supported by HINCRBY is limited to 64 bit signed integers.</blockquote><h2><a name="Examples">Examples</a></h2>
|
||||
Since the <i>value</i> argument is signed you can use this command to perform both increments and decrements:<br/><br/><pre class="codeblock python" name="code">
|
||||
HINCRBY key field 1 (increment by one)
|
||||
HINCRBY key field -1 (decrement by one, just like the DECR command)
|
||||
HINCRBY key field -10 (decrement by 10)
|
||||
</pre>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a> The new value at <i>field</i> after the increment operation.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>HlenCommand: Contents</b><br> <a href="#HLEN _key_ (Redis >">HLEN _key_ (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">HlenCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="HashCommandsSidebar.html">HashCommandsSidebar</a><h1><a name="HLEN _key_ (Redis >">HLEN _key_ (Redis ></a></h1> 1.3.10)=
|
||||
<i>Time complexity: O(1)</i><blockquote>Return the number of entries (fields) contained in the hash stored at <i>key</i>. If the specified <i>key</i> does not exist, 0 is returned assuming an empty hash. </blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>HmgetCommand: Contents</b><br> <a href="#HMGET _key_ _field1_ ... _fieldN_ (Redis >">HMGET _key_ _field1_ ... _fieldN_ (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">HmgetCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="HashCommandsSidebar.html">HashCommandsSidebar</a><h1><a name="HMGET _key_ _field1_ ... _fieldN_ (Redis >">HMGET _key_ _field1_ ... _fieldN_ (Redis ></a></h1> 1.3.10) =
|
||||
<i>Time complexity: O(N) (with N being the number of fields)</i><blockquote>Retrieve the values associated to the specified <i>fields</i>.</blockquote>
|
||||
<blockquote>If some of the specified <i>fields</i> do not exist, nil values are returned.Non existing keys are considered like empty hashes.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi Bulk Reply</a> specifically a list of all the values associated with the specified fields, in the same order of the request.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>HmsetCommand: Contents</b><br> <a href="#HMSET _key_ _field1_ _value1_ ... _fieldN_ _valueN_ (Redis >">HMSET _key_ _field1_ _value1_ ... _fieldN_ _valueN_ (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">HmsetCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
<h1><a name="HMSET _key_ _field1_ _value1_ ... _fieldN_ _valueN_ (Redis >">HMSET _key_ _field1_ _value1_ ... _fieldN_ _valueN_ (Redis ></a></h1> 1.3.10) =
|
||||
<i>Time complexity: O(N) (with N being the number of fields)</i><blockquote>Set the respective fields to the respective values. HMSET replaces old values with new values.</blockquote>
|
||||
<blockquote>If <i>key</i> does not exist, a new key holding a hash is created.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code reply</a> Always +OK because HMSET can't fail
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>HsetCommand: Contents</b><br> <a href="#HSET _key_ _field_ _value_ (Redis >">HSET _key_ _field_ _value_ (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">HsetCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="HashCommandsSidebar.html">HashCommandsSidebar</a><h1><a name="HSET _key_ _field_ _value_ (Redis >">HSET _key_ _field_ _value_ (Redis ></a></h1> 1.3.10)=
|
||||
<i>Time complexity: O(1)</i><blockquote>Set the specified hash <i>field</i> to the specified <i>value</i>.</blockquote>
|
||||
<blockquote>If <i>key</i> does not exist, a new key holding a hash is created.</blockquote>
|
||||
<blockquote>If the field already exists, and the HSET just produced an update of thevalue, 0 is returned, otherwise if a new field is created 1 is returned.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>HsetnxCommand: Contents</b><br> <a href="#HSETNX _key_ _field_ _value_ (Redis >">HSETNX _key_ _field_ _value_ (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">HsetnxCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
<h1><a name="HSETNX _key_ _field_ _value_ (Redis >">HSETNX _key_ _field_ _value_ (Redis ></a></h1> 1.3.10)=
|
||||
<i>Time complexity: O(1)</i><blockquote>Set the specified hash <i>field</i> to the specified <i>value</i>, if <i>field</i> does not exist yet.</blockquote>
|
||||
<blockquote>If <i>key</i> does not exist, a new key holding a hash is created.</blockquote>
|
||||
<blockquote>If the field already exists, this operation has no effect and returns 0.Otherwise, the field is set to <i>value</i> and the operation returns 1.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -33,8 +33,7 @@
|
||||
<i>Time complexity: O(1)</i><blockquote>Increment or decrement the number stored at <i>key</i> by one. If the key doesnot exist or contains a value of a wrong type, set the key to thevalue of "0" before to perform the increment or decrement operation.</blockquote>
|
||||
<blockquote>INCRBY and DECRBY work just like INCR and DECR but instead toincrement/decrement by 1 the increment/decrement is <i>integer</i>.</blockquote>
|
||||
<blockquote>INCR commands are limited to 64 bit signed integers.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, this commands will reply with the new value of <i>key</i> after the increment or decrement.
|
||||
|
||||
Note: this is actually a string operation, that is, in Redis there are not "integer" types. Simply the string stored at the key is parsed as a base 10 64 bit signed integer, incremented, and then converted back as a string.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, this commands will reply with the new value of <i>key</i> after the increment or decrement.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>IntroductionToRedisDataTypes: Contents</b><br> <a href="#Redis keys">Redis keys</a><br> <a href="#The string type">The string type</a><br> <a href="#The List type">The List type</a><br> <a href="#First steps with Redis lists">First steps with Redis lists</a><br> <a href="#Pushing IDs instead of the actual data in Redis lists">Pushing IDs instead of the actual data in Redis lists</a><br> <a href="#Redis Sets">Redis Sets</a><br> <a href="#A digression. How to get unique identifiers for strings">A digression. How to get unique identifiers for strings</a><br> <a href="#Sorted sets">Sorted sets</a><br> <a href="#Operating on ranges">Operating on ranges</a><br> <a href="#Back to the reddit example">Back to the reddit example</a><br> <a href="#Updating the scores of a sorted set">Updating the scores of a sorted set</a>
|
||||
<b>IntroductionToRedisDataTypes: Contents</b><br> <a href="#A fifteen minutes introduction to Redis data types">A fifteen minutes introduction to Redis data types</a><br> <a href="#Redis keys">Redis keys</a><br> <a href="#The string type">The string type</a><br> <a href="#The List type">The List type</a><br> <a href="#First steps with Redis lists">First steps with Redis lists</a><br> <a href="#Pushing IDs instead of the actual data in Redis lists">Pushing IDs instead of the actual data in Redis lists</a><br> <a href="#Redis Sets">Redis Sets</a><br> <a href="#A digression. How to get unique identifiers for strings">A digression. How to get unique identifiers for strings</a><br> <a href="#Sorted sets">Sorted sets</a><br> <a href="#Operating on ranges">Operating on ranges</a><br> <a href="#Back to the reddit example">Back to the reddit example</a><br> <a href="#Updating the scores of a sorted set">Updating the scores of a sorted set</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">IntroductionToRedisDataTypes</h1>
|
||||
@@ -26,7 +26,8 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
= A fifteen minutes introduction to Redis data types =<br/><br/>As you already probably know Redis is not a plain key-value store, actually it is a <b>data structures server</b>, supporting different kind of values. That is, you can't just set strings as values of keys. All the following data types are supported as values:<br/><br/><ul><li> Binary-safe strings.</li><li> Lists of binary-safe strings.</li><li> Sets of binary-safe strings, that are collection of unique unsorted elements. You can think at this as a Ruby hash where all the keys are set to the 'true' value.</li><li> Sorted sets, similar to Sets but where every element is associated to a floating number score. The elements are taken sorted by score. You can think at this as Ruby hashes where the key is the element and the value is the score, but where elements are always taken in order without requiring a sorting operation.</li></ul>
|
||||
#sidebar <a href="RedisGuides.html">RedisGuides</a>
|
||||
<h1><a name="A fifteen minutes introduction to Redis data types">A fifteen minutes introduction to Redis data types</a></h1>As you already probably know Redis is not a plain key-value store, actually it is a <b>data structures server</b>, supporting different kind of values. That is, you can't just set strings as values of keys. All the following data types are supported as values:<br/><br/><ul><li> Binary-safe strings.</li><li> Lists of binary-safe strings.</li><li> Sets of binary-safe strings, that are collection of unique unsorted elements. You can think at this as a Ruby hash where all the keys are set to the 'true' value.</li><li> Sorted sets, similar to Sets but where every element is associated to a floating number score. The elements are taken sorted by score. You can think at this as Ruby hashes where the key is the element and the value is the score, but where elements are always taken in order without requiring a sorting operation.</li></ul>
|
||||
It's not always trivial to grasp how this data types work and what to use in order to solve a given problem from the <a href="CommandReference.html">Redis command reference</a>, so this document is a crash course to Redis data types and their most used patterns.<br/><br/>For all the examples we'll use the <b>redis-cli</b> utility, that's a simple but handy command line utility to issue commands against the Redis server.<h2><a name="Redis keys">Redis keys</a></h2>Before to start talking about the different kind of values supported by Redis it is better to start saying that keys are not binary safe strings in Redis, but just strings not containing a space or a newline character. For instance "foo" or "123456789" or "foo_bar" are valid keys, while "hello world" or "hello\n" are not.<br/><br/>Actually there is nothing inside the Redis internals preventing the use of binary keys, it's just a matter of protocol, and actually the new protocol introduced with Redis 1.2 (1.2 betas are 1.1.x) in order to implement commands like MSET, is totally binary safe. Still for now consider this as an hard limit as the database is only tested with "normal" keys.<br/><br/>A few other rules about keys:<br/><br/><ul><li> Too long keys are not a good idea, for instance a key of 1024 bytes is not a good idea not only memory-wise, but also because the lookup of the key in the dataset may require several costly key-comparisons.</li><li> Too short keys are not a good idea. There is no point in writing "u:1000:pwd" as key if you can write instead "user:1000:password", the latter is more readable and the added space is very little compared to the space used by the key object itself.</li><li> Try to stick with a schema. For instance "object-type:id:field" can be a nice idea, like in "user:1000:password". I like to use dots for multi-words fields, like in "comment:1234:reply.to".</li></ul>
|
||||
<h2><a name="The string type">The string type</a></h2>This is the simplest Redis type. If you use only this type, Redis will be something like a memcached server with persistence.<br/><br/>Let's play a bit with the string type:<br/><br/><pre class="codeblock python" name="code">
|
||||
$ ./redis-cli set mykey "my binary safe value"
|
||||
@@ -42,7 +43,7 @@ $ ./redis-cli incr counter
|
||||
(integer) 102
|
||||
$ ./redis-cli incrby counter 10
|
||||
(integer) 112
|
||||
</pre>The <a href="IncrCommand.html">INCR</a> command parses the string value as an integer, increments it by one, and finally sets the obtained value as the new string value. There are other similar commands like <a href="IncrCommand.html">INCRBY</a>, <a href="IncrCommand.html">DECR</a> and <a href="IncrCommand.html">DECRBY</a>. Actually internally it's always the same command, acting in a slightly different way.<br/><br/>What means that INCR is atomic? That even multiple clients issuing INCR against the same key will never incur into a race condition. For instance it can't never happen that client 1 read "10", client 2 read "10" at the same time, both increment to 11, and set the new value of 11. The final value will always be of 12 ad the read-increment-set operation is performed while all the other clients are not executing a command at the same time.<br/><br/>Another interesting operation on string is the <a href="GetsetCommand.html">GETSET</a> command, that does just what its name suggests: Set a key to a new value, returning the old value, as result. Why this is useful? Example: you have a system that increments a Redis key using the <a href="IncrCommand.html">INCR</a> command every time your web site receives a new visit. You want to collect this information one time every hour, without loosing a single key. You can GETSET the key assigning it the new value of "0" and reading the old value back.<h2><a name="The List type">The List type</a></h2>To explain the List data type it's better to start with a little of theory, as the term <b>List</b> is often used in an improper way by information technology folks. For instance "Python Lists" are not what the name may suggest (Linked Lists), but them are actually Arrays (the same data type is called Array in Ruby actually).<br/><br/>From a very general point of view a List is just a sequence of ordered elements: 10,20,1,2,3 is a list, but when a list of items is implemented using an Array and when instead a <b>Linked List</b> is used for the implementation, the properties change a lot.<br/><br/>Redis lists are implemented via Linked Lists, this means that even if you have million of elements inside a list, the operation of adding a new element in the head or in the tail of the list is performed <b>in constant time</b>. Adding a new element with the <a href="LpopCommand.html">LPOP</a> command to the head of a ten elements list is the same speed as adding an element to the head of a 10 million elements list.<br/><br/>What's the downside? That accessing an element <b>by index</b> is very fast in lists implemented with an Array and not so fast in lists implemented by linked lists.<br/><br/>Redis Lists are implemented with linked lists because for a database system is crucial to be able to add elements to a very long list in a very fast way. Another strong advantage is, as you'll see in a moment, that Redis Lists can be taken at constant length in constant time.<h3><a name="First steps with Redis lists">First steps with Redis lists</a></h3>The <a href="RpushCommand.html">LPUSH</a> command add a new element into a list, on the left (on head), while the <a href="RpushCommand.html">RPUSH</a> command add a new element into alist, ot the right (on tail). Finally the <a href="LrangeCommand.html">LRANGE</a> command extract ranges of elements from lists:<br/><br/><pre class="codeblock python python python" name="code">
|
||||
</pre>The <a href="IncrCommand.html">INCR</a> command parses the string value as an integer, increments it by one, and finally sets the obtained value as the new string value. There are other similar commands like <a href="IncrCommand.html">INCRBY</a>, <a href="IncrCommand.html">DECR</a> and <a href="IncrCommand.html">DECRBY</a>. Actually internally it's always the same command, acting in a slightly different way.<br/><br/>What means that INCR is atomic? That even multiple clients issuing INCR against the same key will never incur into a race condition. For instance it can't never happen that client 1 read "10", client 2 read "10" at the same time, both increment to 11, and set the new value of 11. The final value will always be of 12 ad the read-increment-set operation is performed while all the other clients are not executing a command at the same time.<br/><br/>Another interesting operation on string is the <a href="GetsetCommand.html">GETSET</a> command, that does just what its name suggests: Set a key to a new value, returning the old value, as result. Why this is useful? Example: you have a system that increments a Redis key using the <a href="IncrCommand.html">INCR</a> command every time your web site receives a new visit. You want to collect this information one time every hour, without loosing a single key. You can GETSET the key assigning it the new value of "0" and reading the old value back.<h2><a name="The List type">The List type</a></h2>To explain the List data type it's better to start with a little of theory, as the term <b>List</b> is often used in an improper way by information technology folks. For instance "Python Lists" are not what the name may suggest (Linked Lists), but them are actually Arrays (the same data type is called Array in Ruby actually).<br/><br/>From a very general point of view a List is just a sequence of ordered elements: 10,20,1,2,3 is a list, but when a list of items is implemented using an Array and when instead a <b>Linked List</b> is used for the implementation, the properties change a lot.<br/><br/>Redis lists are implemented via Linked Lists, this means that even if you have million of elements inside a list, the operation of adding a new element in the head or in the tail of the list is performed <b>in constant time</b>. Adding a new element with the <a href="LpushCommand.html">LPUSH</a> command to the head of a ten elements list is the same speed as adding an element to the head of a 10 million elements list.<br/><br/>What's the downside? That accessing an element <b>by index</b> is very fast in lists implemented with an Array and not so fast in lists implemented by linked lists.<br/><br/>Redis Lists are implemented with linked lists because for a database system is crucial to be able to add elements to a very long list in a very fast way. Another strong advantage is, as you'll see in a moment, that Redis Lists can be taken at constant length in constant time.<h3><a name="First steps with Redis lists">First steps with Redis lists</a></h3>The <a href="RpushCommand.html">LPUSH</a> command add a new element into a list, on the left (on head), while the <a href="RpushCommand.html">RPUSH</a> command add a new element into alist, ot the right (on tail). Finally the <a href="LrangeCommand.html">LRANGE</a> command extract ranges of elements from lists:<br/><br/><pre class="codeblock python python python" name="code">
|
||||
$ ./redis-cli rpush messages "Hello how are you?"
|
||||
OK
|
||||
$ ./redis-cli rpush messages "Fine thanks. I'm having fun with Redis"
|
||||
@@ -78,7 +79,7 @@ $ ./redis-cli sismember myset 3
|
||||
(integer) 1
|
||||
$ ./redis-cli sismember myset 30
|
||||
(integer) 0
|
||||
</pre>"3" is a member of the set, while "30" is not. Sets are very good in order to express relations between objects. For instance we can easily Redis Sets in order to implement tags.<br/><br/>A simple way to model this is to have, for every object you want to tag, a Set with all the IDs of the tags associated with the object, and for every tag that exists, a Set of of all the objects tagged with this tag.<br/><br/>For instance if our news ID 1000 is tagged with tag 1,2,5 and 77, we can specify the following two Sets:<br/><br/><pre class="codeblock python python python python python python python" name="code">
|
||||
</pre>"3" is a member of the set, while "30" is not. Sets are very good in order to express relations between objects. For instance we can easily use Redis Sets in order to implement tags.<br/><br/>A simple way to model this is to have, for every object you want to tag, a Set with all the IDs of the tags associated with the object, and for every tag that exists, a Set of of all the objects tagged with this tag.<br/><br/>For instance if our news ID 1000 is tagged with tag 1,2,5 and 77, we can specify the following two Sets:<br/><br/><pre class="codeblock python python python python python python python" name="code">
|
||||
$ ./redis-cli sadd news:1000:tags 1
|
||||
(integer) 1
|
||||
$ ./redis-cli sadd news:1000:tags 2
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<blockquote>the slow commands that may ruin the DB performance if not usedwith care*.</blockquote>
|
||||
<blockquote>In other words this command is intended only for debugging and *special* operations like creating a script to change the DB schema. Don't use it in your normal code. Use Redis <a href="Sets.html">Sets</a> in order to group together a subset of objects.</blockquote>
|
||||
Glob style patterns examples:
|
||||
<blockquote>* h?llo will match hello hallo hhllo* h*llo will match hllo heeeello* h<code name="code" class="python">[</code>ae<code name="code" class="python">]</code>llo will match hello and hallo, but not hillo</blockquote>Use \ to escape special chars if you want to match them verbatim.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>, specifically a string in the form of space separated list of keys. Note that most client libraries will return an Array of keys and not a single string with space separated keys (that is, split by " " is performed in the client library usually).</b></blockquote>
|
||||
<blockquote>* h?llo will match hello hallo hhllo* h*llo will match hllo heeeello* h<code name="code" class="python">[</code>ae<code name="code" class="python">]</code>llo will match hello and hallo, but not hillo</blockquote>Use \ to escape special chars if you want to match them verbatim.<h2><a name="Return value">Return value</a></h2>
|
||||
<a href="ReplyTypes.html">Multi bulk reply</a></b></blockquote>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -28,10 +28,9 @@
|
||||
<div class="narrow">
|
||||
#sidebar <a href="ListCommandsSidebar.html">ListCommandsSidebar</a><h1><a name="LINDEX _key_ _index_">LINDEX _key_ _index_</a></h1>
|
||||
<i>Time complexity: O(n) (with n being the length of the list)</i><blockquote>Return the specified element of the list stored at the specifiedkey. 0 is the first element, 1 the second and so on. Negative indexesare supported, for example -1 is the last element, -2 the penultimateand so on.</blockquote>
|
||||
<blockquote>If the value stored at key is not of list type an error is returned.If the index is out of range an empty string is returned.</blockquote>
|
||||
<blockquote>If the value stored at key is not of list type an error is returned.If the index is out of range a 'nil' reply is returned.</blockquote>
|
||||
<blockquote>Note that even if the average time complexity is O(n) asking forthe first or the last element of the list is O(1).</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>, specifically the requested element.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
== List Commands ==<br/><br/><ul><li> <a href="RpushCommand.html">RPUSH</a></li><li> <a href="RpushCommand.html">LPUSH</a></li><li> <a href="LlenCommand.html">LLEN</a></li><li> <a href="LrangeCommand.html">LRANGE</a></li><li> <a href="LtrimCommand.html">LTRIM</a></li><li> <a href="LindexCommand.html">LINDEX</a></li><li> <a href="LsetCommand.html">LSET</a></li><li> <a href="LremCommand.html">LREM</a></li><li> <a href="LpopCommand.html">LPOP</a></li><li> <a href="LpopCommand.html">RPOP</a></li><li> <a href="RpoplpushCommand.html">RPOPLPUSH</a></li><li> <a href="SortCommand.html">SORT</a></li></ul>
|
||||
== List Commands ==<br/><br/><ul><li> <a href="RpushCommand.html">RPUSH</a></li><li> <a href="RpushCommand.html">LPUSH</a></li><li> <a href="LlenCommand.html">LLEN</a></li><li> <a href="LrangeCommand.html">LRANGE</a></li><li> <a href="LtrimCommand.html">LTRIM</a></li><li> <a href="LindexCommand.html">LINDEX</a></li><li> <a href="LsetCommand.html">LSET</a></li><li> <a href="LremCommand.html">LREM</a></li><li> <a href="LpopCommand.html">LPOP</a></li><li> <a href="LpopCommand.html">RPOP</a></li><li> <a href="BlpopCommand.html">BLPOP</a></li><li> <a href="BlpopCommand.html">BRPOP</a></li><li> <a href="RpoplpushCommand.html">RPOPLPUSH</a></li><li> <a href="BrpoplpushCommand.html">BRPOPLPUSH</a></li><li> <a href="SortCommand.html">SORT</a></li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
+12
-7
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>LrangeCommand: Contents</b><br> <a href="#LRANGE _key_ _start_ _end_">LRANGE _key_ _start_ _end_</a><br> <a href="#Return value">Return value</a>
|
||||
<b>LrangeCommand: Contents</b><br> <a href="#LRANGE _key_ _start_ _end_">LRANGE _key_ _start_ _end_</a><br> <a href="#Consistency with range functions in various programming languages">Consistency with range functions in various programming languages</a><br> <a href="#Out-of-range indexes">Out-of-range indexes</a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">LrangeCommand</h1>
|
||||
@@ -27,12 +27,17 @@
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="ListCommandsSidebar.html">ListCommandsSidebar</a><h1><a name="LRANGE _key_ _start_ _end_">LRANGE _key_ _start_ _end_</a></h1>
|
||||
<i>Time complexity: O(n) (with n being the length of the range)</i><blockquote>Return the specified elements of the list stored at the specifiedkey. Start and end are zero-based indexes. 0 is the first elementof the list (the list head), 1 the next element and so on.</blockquote>
|
||||
<blockquote>For example LRANGE foobar 0 2 will return the first three elementsof the list.</blockquote>
|
||||
<blockquote>_start_ and <i>end</i> can also be negative numbers indicating offsetsfrom the end of the list. For example -1 is the last element ofthe list, -2 the penultimate element and so on.</blockquote>
|
||||
<blockquote>Indexes out of range will not produce an error: if start is overthe end of the list, or start <code name="code" class="python">></code> end, an empty list is returned.If end is over the end of the list Redis will threat it just likethe last element of the list.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically a list of elements in the specified range.
|
||||
|
||||
<i>Time complexity: O(start+n) (with n being the length of the range and start being the start offset)</i>Return the specified elements of the list stored at the specified
|
||||
key. Start and end are zero-based indexes. 0 is the first element
|
||||
of the list (the list head), 1 the next element and so on.<br/><br/>For example LRANGE foobar 0 2 will return the first three elements
|
||||
of the list.<br/><br/><i>start</i> and <i>end</i> can also be negative numbers indicating offsets
|
||||
from the end of the list. For example -1 is the last element of
|
||||
the list, -2 the penultimate element and so on.<h2><a name="Consistency with range functions in various programming languages">Consistency with range functions in various programming languages</a></h2>Note that if you have a list of numbers from 0 to 100, LRANGE 0 10 will return
|
||||
11 elements, that is, rightmost item is included. This <b>may or may not</b> be consistent with
|
||||
behavior of range-related functions in your programming language of choice (think Ruby's Range.new, Array#slice or Python's range() function).<br/><br/>LRANGE behavior is consistent with one of Tcl.<h2><a name="Out-of-range indexes">Out-of-range indexes</a></h2>Indexes out of range will not produce an error: if start is over
|
||||
the end of the list, or start <code name="code" class="python">></code> end, an empty list is returned.
|
||||
If end is over the end of the list Redis will threat it just like
|
||||
the last element of the list.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically a list of elements in the specified range.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -28,8 +28,7 @@
|
||||
<div class="narrow">
|
||||
#sidebar <a href="ListCommandsSidebar.html">ListCommandsSidebar</a><h1><a name="LSET _key_ _index_ _value_">LSET _key_ _index_ _value_</a></h1>
|
||||
<i>Time complexity: O(N) (with N being the length of the list)</i><blockquote>Set the list element at <i>index</i> (see LINDEX for information about the_index_ argument) with the new <i>value</i>. Out of range indexes willgenerate an error. Note that setting the first or last elements ofthe list is O(1).</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code reply</a>
|
||||
|
||||
<blockquote>Similarly to other list commands accepting indexes, the index can be negative to access elements starting from the end of the list. So -1 is the last element, -2 is the penultimate, and so forth.</blockquote><h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code reply</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>MultiExecCommand: Contents</b><br> <a href="#WATCH key1 key2 ... keyN (Redis >">WATCH key1 key2 ... keyN (Redis ></a><br> <a href="#UNWATCH">UNWATCH</a><br> <a href="#MULTI">MULTI</a><br> <a href="#COMMAND_1 ...">COMMAND_1 ...</a><br> <a href="#COMMAND_2 ...">COMMAND_2 ...</a><br> <a href="#COMMAND_N ...">COMMAND_N ...</a><br> <a href="#EXEC or DISCARD">EXEC or DISCARD</a><br> <a href="#Usage">Usage</a><br> <a href="#The DISCARD command">The DISCARD command</a><br> <a href="#Check and Set (CAS) transactions using WATCH">Check and Set (CAS) transactions using WATCH</a><br> <a href="#WATCH explained">WATCH explained</a><br> <a href="#WATCH used to implement ZPOP">WATCH used to implement ZPOP</a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">MultiExecCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="WATCH key1 key2 ... keyN (Redis >">WATCH key1 key2 ... keyN (Redis ></a></h1> 2.1.0)=
|
||||
<h1><a name="UNWATCH">UNWATCH</a></h1>
|
||||
<h1><a name="MULTI">MULTI</a></h1>
|
||||
<h1><a name="COMMAND_1 ...">COMMAND_1 ...</a></h1>
|
||||
<h1><a name="COMMAND_2 ...">COMMAND_2 ...</a></h1>
|
||||
<h1><a name="COMMAND_N ...">COMMAND_N ...</a></h1>
|
||||
<h1><a name="EXEC or DISCARD">EXEC or DISCARD</a></h1>MULTI, EXEC, DISCARD and WATCH commands are the foundation of Redis Transactions.
|
||||
A Redis Transaction allows the execution of a group of Redis commands in a single
|
||||
step, with two important guarantees:<br/><br/><ul><li> All the commands in a transaction are serialized and executed sequentially. It can never happen that a request issued by another client is served <b>in the middle</b> of the execution of a Redis transaction. This guarantees that the commands are executed as a single atomic operation.</li><li> Either all of the commands or none are processed. The EXEC command triggers the execution of all the commands in the transaction, so if a client loses the connection to the server in the context of a transaction before calling the MULTI command none of the operations are performed, instead if the EXEC command is called, all the operations are performed. An exception to this rule is when the Append Only File is enabled: every command that is part of a Redis transaction will log in the AOF as long as the operation is completed, so if the Redis server crashes or is killed by the system administrator in some hard way it is possible that only a partial number of operations are registered.</li></ul>
|
||||
Since Redis 2.1.0, it's also possible to add a further guarantee to the above two, in the form of optimistic locking of a set of keys in a way very similar to a CAS (check and set) operation. This is documented later in this manual page.<h2><a name="Usage">Usage</a></h2>A Redis transaction is entered using the MULTI command. The command always
|
||||
replies with OK. At this point the user can issue multiple commands. Instead
|
||||
of executing these commands, Redis will "queue" them. All the commands are
|
||||
executed once EXEC is called.<br/><br/>Calling DISCARD instead will flush the transaction queue and will exit
|
||||
the transaction.<br/><br/>The following is an example using the Ruby client:
|
||||
<pre class="codeblock python" name="code">
|
||||
?> r.multi
|
||||
=> "OK"
|
||||
>> r.incr "foo"
|
||||
=> "QUEUED"
|
||||
>> r.incr "bar"
|
||||
=> "QUEUED"
|
||||
>> r.incr "bar"
|
||||
=> "QUEUED"
|
||||
>> r.exec
|
||||
=> [1, 1, 2]
|
||||
</pre>
|
||||
As it is possible to see from the session above, MULTI returns an "array" of
|
||||
replies, where every element is the reply of a single command in the
|
||||
transaction, in the same order the commands were queued.<br/><br/>When a Redis connection is in the context of a MULTI request, all the commands
|
||||
will reply with a simple string "QUEUED" if they are correct from the
|
||||
point of view of the syntax and arity (number of arguments) of the commaand.
|
||||
Some commands are still allowed to fail during execution time.<br/><br/>This is more clear on the protocol level; In the following example one command
|
||||
will fail when executed even if the syntax is right:
|
||||
<pre class="codeblock python python" name="code">
|
||||
Trying 127.0.0.1...
|
||||
Connected to localhost.
|
||||
Escape character is '^]'.
|
||||
MULTI
|
||||
+OK
|
||||
SET a 3
|
||||
abc
|
||||
+QUEUED
|
||||
LPOP a
|
||||
+QUEUED
|
||||
EXEC
|
||||
*2
|
||||
+OK
|
||||
-ERR Operation against a key holding the wrong kind of value
|
||||
</pre>
|
||||
MULTI returned a two elements bulk reply where one is an +OK
|
||||
code and one is a -ERR reply. It's up to the client lib to find a sensible
|
||||
way to provide the error to the user.<br/><br/><blockquote>IMPORTANT: even when a command will raise an error, all the other commandsin the queue will be processed. Redis will NOT stop the processing ofcommands once an error is found.</blockquote>
|
||||
Another example, again using the write protocol with telnet, shows how
|
||||
syntax errors are reported ASAP instead:
|
||||
<pre class="codeblock python python python" name="code">
|
||||
MULTI
|
||||
+OK
|
||||
INCR a b c
|
||||
-ERR wrong number of arguments for 'incr' command
|
||||
</pre>
|
||||
This time due to the syntax error the "bad" INCR command is not queued
|
||||
at all.<h2><a name="The DISCARD command">The DISCARD command</a></h2>DISCARD can be used in order to abort a transaction. No command will be executed, and the state of the client is again the normal one, outside of a transaction. Example using the Ruby client:
|
||||
<pre class="codeblock python python python python" name="code">
|
||||
?> r.set("foo",1)
|
||||
=> true
|
||||
>> r.multi
|
||||
=> "OK"
|
||||
>> r.incr("foo")
|
||||
=> "QUEUED"
|
||||
>> r.discard
|
||||
=> "OK"
|
||||
>> r.get("foo")
|
||||
=> "1"
|
||||
</pre><h2><a name="Check and Set (CAS) transactions using WATCH">Check and Set (CAS) transactions using WATCH</a></h2>WATCH is used in order to provide a CAS (Check and Set) behavior to
|
||||
Redis Transactions.<br/><br/>WATCHed keys are monitored in order to detect changes against this keys.
|
||||
If at least a watched key will be modified before the EXEC call, the
|
||||
whole transaction will abort, and EXEC will return a nil object
|
||||
(A Null Multi Bulk reply) to notify that the transaction failed.<br/><br/>For example imagine we have the need to atomically increment the value
|
||||
of a key by 1 (I know we have INCR, let's suppose we don't have it).<br/><br/>The first try may be the following:
|
||||
<pre class="codeblock python python python python python" name="code">
|
||||
val = GET mykey
|
||||
val = val + 1
|
||||
SET mykey $val
|
||||
</pre>
|
||||
This will work reliably only if we have a single client performing the operation in a given time.
|
||||
If multiple clients will try to increment the key about at the same time
|
||||
there will be a race condition. For instance client A and B will read the
|
||||
old value, for instance, 10. The value will be incremented to 11 by both
|
||||
the clients, and finally SET as the value of the key. So the final value
|
||||
will be "11" instead of "12".<br/><br/>Thanks to WATCH we are able to model the problem very well:
|
||||
<pre class="codeblock python python python python python python" name="code">
|
||||
WATCH mykey
|
||||
val = GET mykey
|
||||
val = val + 1
|
||||
MULTI
|
||||
SET mykey $val
|
||||
EXEC
|
||||
</pre>
|
||||
Using the above code, if there are race conditions and another client
|
||||
modified the result of <i>val</i> in the time between our call to WATCH and
|
||||
our call to EXEC, the transaction will fail.<br/><br/>We'll have just to re-iterate the operation hoping this time we'll not get
|
||||
a new race. This form of locking is called <b>optimistic locking</b> and is
|
||||
a very powerful form of locking as in many problems there are multiple
|
||||
clients accessing a much bigger number of keys, so it's very unlikely that
|
||||
there are collisions: usually operations don't need to be performed
|
||||
multiple times.<h2><a name="WATCH explained">WATCH explained</a></h2>So what is WATCH really about? It is a command that will make the EXEC
|
||||
conditional: we are asking Redis to perform the transaction only if no
|
||||
other client modified any of the WATCHed keys. Otherwise the transaction is not
|
||||
entered at all. (Note that if you WATCH a volatile key and Redis expires the key after you WATCHed it, EXEC will still work. <a href="http://code.google.com/p/redis/issues/detail?id=270" target="_blank">More</a>.)<br/><br/>WATCH can be called multiple times. Simply all the WATCH calls will
|
||||
have the effects to watch for changes starting from the call, up to the
|
||||
moment EXEC is called.<br/><br/>When EXEC is called, either if it will fail or succeed, all keys are
|
||||
UNWATCHed. Also when a client connection is closed, everything gets
|
||||
UNWATCHed.<br/><br/>It is also possible to use the UNWATCH command (without arguments) in order
|
||||
to flush all the watched keys. Sometimes this is useful as we
|
||||
optimistically lock a few keys, since possibly we need to perform a transaction
|
||||
to alter those keys, but after reading the current content of the keys
|
||||
we don't want to proceed. When this happens we just call UNWATCH so that
|
||||
the connection can already be used freely for new transactions.<h2><a name="WATCH used to implement ZPOP">WATCH used to implement ZPOP</a></h2>A good example to illustrate how WATCH can be used to create new atomic
|
||||
operations otherwise not supported by Redis is to implement ZPOP, that is
|
||||
a command that pops the element with the lower score from a sorted set
|
||||
in an atomic way. This is the simplest implementation:
|
||||
<pre class="codeblock python python python python python python python" name="code">
|
||||
WATCH zset
|
||||
ele = ZRANGE zset 0 0
|
||||
MULTI
|
||||
ZREM zset ele
|
||||
EXEC
|
||||
</pre>
|
||||
If EXEC fails (returns a nil value) we just re-iterate the operation.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically:<br/><br/><pre class="codeblock python python python python python python python python" name="code">
|
||||
The result of a MULTI/EXEC command is a multi bulk reply where every element is the return value of every command in the atomic transaction.
|
||||
</pre>If a MULTI/EXEC transaction is aborted because of WATCH detected modified keys, a <a href="ReplyTypes.html">Null Multi Bulk reply</a> is returned.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>NonexistentCommands: Contents</b><br> <a href="#HGETSET">HGETSET</a><br> <a href="#SET with expire">SET with expire</a><br> <a href="#ZADDNX">ZADDNX</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">NonexistentCommands</h1>
|
||||
|
||||
<div class="summary">
|
||||
A list of commands that don't exist in Redis, but can be accomplished in a different way.
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
This is a list of commands that don't exist in Redis, but can be accomplished in a different way, usually by means of <a href="MultiExecCommand.html">WATCH/MULTI/EXEC</a>.<br/><br/>For better performance, you can pipeline multiple commands.<h1><a name="HGETSET">HGETSET</a></h1><a href="GetsetCommand.html">GETSET</a> for Hashes.<br/><br/><pre class="codeblock python" name="code">
|
||||
WATCH foo
|
||||
old_value = HGET foo field
|
||||
MULTI
|
||||
HSET foo field new_value
|
||||
EXEC
|
||||
</pre><h1><a name="SET with expire">SET with expire</a></h1>See <a href="SetexCommand.html">SETEX</a>.<h1><a name="ZADDNX">ZADDNX</a></h1>Add an element to a sorted set, only if the element doesn't already exist (by default, <a href="ZaddCommand.html">ZADD</a> would update the element's score if it already exists). <a href="http://groups.google.com/group/redis-db/browse_thread/thread/fc4c79d72e5bd346/6cdc07ecc36b81e7" target="_blank">See thread</a>.<br/><br/><pre class="codeblock python python" name="code">
|
||||
WATCH foo
|
||||
score = ZSCORE foo bar
|
||||
IF score != NIL
|
||||
MULTI
|
||||
ZADD foo 1 bar
|
||||
EXEC
|
||||
ENDIF
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>ProtocolSpecification: Contents</b><br> <a href="#Networking layer">Networking layer</a><br> <a href="#Simple INLINE commands">Simple INLINE commands</a><br> <a href="#Bulk commands">Bulk commands</a><br> <a href="#Bulk replies">Bulk replies</a><br> <a href="#Multi-Bulk replies">Multi-Bulk replies</a><br> <a href="#Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a><br> <a href="#Single line reply">Single line reply</a><br> <a href="#Integer reply">Integer reply</a><br> <a href="#Multi bulk commands">Multi bulk commands</a><br> <a href="#Multiple commands and pipelining">Multiple commands and pipelining</a>
|
||||
<b>ProtocolSpecification: Contents</b><br> <a href="#Networking layer">Networking layer</a><br> <a href="#Requests">Requests</a><br> <a href="#The new unified request protocol">The new unified request protocol</a><br> <a href="#Replies">Replies</a><br> <a href="#Single line reply">Single line reply</a><br> <a href="#Error reply">Error reply</a><br> <a href="#Integer reply">Integer reply</a><br> <a href="#Bulk replies">Bulk replies</a><br> <a href="#Multi-Bulk replies">Multi-Bulk replies</a><br> <a href="#Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a><br> <a href="#Multiple commands and pipelining">Multiple commands and pipelining</a><br> <a href="#The old protocol for sending commands">The old protocol for sending commands</a><br> <a href="#Inline Commands">Inline Commands</a><br> <a href="#Bulk commands">Bulk commands</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">ProtocolSpecification</h1>
|
||||
@@ -26,53 +26,54 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
= Protocol Specification =<br/><br/>The Redis protocol is a compromise between being easy to parse by a computer
|
||||
and being easy to parse by an human. Before reading this section you are
|
||||
strongly encouraged to read the "REDIS TUTORIAL" section of this README in order
|
||||
to get a first feeling of the protocol playing with it by TELNET.<h2><a name="Networking layer">Networking layer</a></h2>A client connects to a Redis server creating a TCP connection to the port 6379.
|
||||
Every redis command or data transmitted by the client and the server is
|
||||
terminated by "\r\n" (CRLF).<h2><a name="Simple INLINE commands">Simple INLINE commands</a></h2>The simplest commands are the inline commands. This is an example of a
|
||||
server/client chat (the server chat starts with S:, the client chat with C:)<br/><br/><pre class="codeblock python" name="code">
|
||||
C: PING
|
||||
S: +PONG
|
||||
</pre>An inline command is a CRLF-terminated string sent to the client. The server can reply to commands in different ways:
|
||||
<ul><li> With an error message (the first byte of the reply will be "-")</li><li> With a single line reply (the first byte of the reply will be "+)</li><li> With bulk data (the first byte of the reply will be "$")</li><li> With multi-bulk data, a list of values (the first byte of the reply will be "<code name="code" class="python">*</code>")</li><li> With an integer number (the first byte of the reply will be ":")</li></ul>
|
||||
The following is another example of an INLINE command returning an integer:<br/><br/><pre class="codeblock python python" name="code">
|
||||
C: EXISTS somekey
|
||||
S: :0
|
||||
</pre>Since 'somekey' does not exist the server returned ':0'.<br/><br/>Note that the EXISTS command takes one argument. Arguments are separated
|
||||
simply by spaces.<h2><a name="Bulk commands">Bulk commands</a></h2>A bulk command is exactly like an inline command, but the last argument
|
||||
of the command must be a stream of bytes in order to send data to the server.
|
||||
the "SET" command is a bulk command, see the following example:<br/><br/><pre class="codeblock python python python" name="code">
|
||||
C: SET mykey 6
|
||||
C: foobar
|
||||
S: +OK
|
||||
</pre>The last argument of the commnad is '6'. This specify the number of DATA
|
||||
bytes that will follow (note that even this bytes are terminated by two
|
||||
additional bytes of CRLF).<br/><br/>All the bulk commands are in this exact form: instead of the last argument
|
||||
the number of bytes that will follow is specified, followed by the bytes,
|
||||
and CRLF. In order to be more clear for the programmer this is the string
|
||||
sent by the client in the above sample:<br/><br/><blockquote>"SET mykey 6\r\nfoobar\r\n"</blockquote>
|
||||
<h2><a name="Bulk replies">Bulk replies</a></h2>The server may reply to an inline or bulk command with a bulk reply. See
|
||||
the following example:<br/><br/><pre class="codeblock python python python python" name="code">
|
||||
= Protocol Specification =<br/><br/>The Redis protocol is a compromise between the following things:<br/><br/><ul><li> Simple to implement.</li><li> Fast to parse by a computer.</li><li> Easy enough to parse by a human.</li></ul>
|
||||
<h2><a name="Networking layer">Networking layer</a></h2>A client connects to a Redis server creating a TCP connection to the port 6379.
|
||||
Every Redis command or data transmitted by the client and the server is
|
||||
terminated by "\r\n" (CRLF).<h1><a name="Requests">Requests</a></h1>Redis accepts commands composed of different arguments.
|
||||
Once a command is received, it is processed and a reply is sent back to the client.<h2><a name="The new unified request protocol">The new unified request protocol</a></h2>The new unified protocol was introduced in Redis 1.2, but it became the standard way for talking with the Redis server in Redis 2.0.<br/><br/>In the unified protocol all the arguments sent to the Redis server are binary safe. This is the general form:<br/><br/><pre class="codeblock python" name="code">
|
||||
*<number of arguments> CR LF
|
||||
$<number of bytes of argument 1> CR LF
|
||||
<argument data> CR LF
|
||||
...
|
||||
$<number of bytes of argument N> CR LF
|
||||
<argument data> CR LF
|
||||
</pre>See the following example:<br/><br/><pre class="codeblock python python" name="code">
|
||||
*3
|
||||
$3
|
||||
SET
|
||||
$5
|
||||
mykey
|
||||
$7
|
||||
myvalue
|
||||
</pre>This is how the above command looks as a quoted string, so that it is possible to see the exact value of every byte in the query:<br/><br/><pre class="codeblock python python python" name="code">
|
||||
"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$8\r\nmyvalue\r\n"
|
||||
</pre>As you will see in a moment this format is also used in Redis replies.
|
||||
The format used for every argument "$6\r\nmydata\r\n" is called a Bulk Reply.
|
||||
While the actual unified request protocol is what Redis uses to return list of items, and is called a Multi Bulk Reply. It is just the sum of N different
|
||||
Bulk Replies prefixed by a <code name="code" class="python">*<argc>\r\n</code> string where <code name="code" class="python"><argc></code> is the number of arguments (Bulk Replies) that will follow.<h1><a name="Replies">Replies</a></h1>Redis will reply to commands with different kinds of replies. It is possible to check the kind of reply from the first byte sent by the server:<br/><br/><ul><li> With a single line reply the first byte of the reply will be "+"</li><li> With an error message the first byte of the reply will be "-"</li><li> With an integer number the first byte of the reply will be ":"</li><li> With bulk reply the first byte of the reply will be "$"</li><li> With multi-bulk reply the first byte of the reply will be "<code name="code" class="python">*</code>"</li></ul>
|
||||
<h2><a name="Single line reply">Single line reply</a></h2>A single line reply is in the form of a single line string
|
||||
starting with "+" terminated by "\r\n". For example:<br/><br/><pre class="codeblock python python python python" name="code">
|
||||
+OK
|
||||
</pre>The client library should return everything after the "+", that is, the string "OK" in the example.<br/><br/>The following commands reply with a single line reply:
|
||||
PING, SET, SELECT, SAVE, BGSAVE, SHUTDOWN, RENAME, LPUSH, RPUSH, LSET, LTRIM<h2><a name="Error reply">Error reply</a></h2>Errors are sent exactly like Single Line Replies. The only difference is that the first byte is "-" instead of "+".<br/><br/>Error replies are only sent when something strange happened, for instance if you try to perform an operation against the wrong data type, or if the command does not exist and so forth. So an exception should be raised by the library client when an Error Reply is received.<h2><a name="Integer reply">Integer reply</a></h2>This type of reply is just a CRLF terminated string representing an integer, prefixed by a ":" byte. For example ":0\r\n", or ":1000\r\n" are integer replies.<br/><br/>With commands like INCR or LASTSAVE using the integer reply to actually return a value there is no special meaning for the returned integer. It is just an incremental number for INCR, a UNIX time for LASTSAVE and so on.<br/><br/>Some commands like EXISTS will return 1 for true and 0 for false.<br/><br/>Other commands like SADD, SREM and SETNX will return 1 if the operation was actually done, 0 otherwise.<br/><br/>The following commands will reply with an integer reply: SETNX, DEL, EXISTS, INCR, INCRBY, DECR, DECRBY, DBSIZE, LASTSAVE, RENAMENX, MOVE, LLEN, SADD, SREM, SISMEMBER, SCARD<h2><a name="Bulk replies">Bulk replies</a></h2>Bulk replies are used by the server in order to return a single binary safe string.<br/><br/><pre class="codeblock python python python python python" name="code">
|
||||
C: GET mykey
|
||||
S: $6
|
||||
S: foobar
|
||||
</pre>A bulk reply is very similar to the last argument of a bulk command. The
|
||||
server sends as the first line a "$" byte followed by the number of bytes
|
||||
of the actual reply followed by CRLF, then the bytes are sent followed by
|
||||
additional two bytes for the final CRLF. The exact sequence sent by the
|
||||
server is:<br/><br/><blockquote>"$6\r\nfoobar\r\n"</blockquote>
|
||||
If the requested value does not exist the bulk reply will use the special
|
||||
value -1 as data length, example:<br/><br/><pre class="codeblock python python python python python" name="code">
|
||||
</pre>The server sends as the first line a "$" byte followed by the number of bytes
|
||||
of the actual reply, followed by CRLF, then the actual data bytes are sent,
|
||||
followed by additional two bytes for the final CRLF.
|
||||
The exact sequence sent by the server is:<br/><br/><pre class="codeblock python python python python python python" name="code">
|
||||
"$6\r\nfoobar\r\n"
|
||||
</pre>If the requested value does not exist the bulk reply will use the special
|
||||
value -1 as data length, example:<br/><br/><pre class="codeblock python python python python python python python" name="code">
|
||||
C: GET nonexistingkey
|
||||
S: $-1
|
||||
</pre>The client library API should not return an empty string, but a nil object, when the requested object does not exist.
|
||||
For example a Ruby library should return 'nil' while a C library should return
|
||||
NULL, and so forth.<h2><a name="Multi-Bulk replies">Multi-Bulk replies</a></h2>Commands similar to LRANGE needs to return multiple values (every element
|
||||
NULL (or set a special flag in the reply object), and so forth.<h2><a name="Multi-Bulk replies">Multi-Bulk replies</a></h2>Commands like LRANGE need to return multiple values (every element
|
||||
of the list is a value, and LRANGE needs to return more than a single element). This is accomplished using multiple bulk writes,
|
||||
prefixed by an initial line indicating how many bulk writes will follow.
|
||||
The first byte of a multi bulk reply is always <code name="code" class="python">*</code>. Example:<br/><br/><pre class="codeblock python python python python python python" name="code">
|
||||
The first byte of a multi bulk reply is always <code name="code" class="python">*</code>. Example:<br/><br/><pre class="codeblock python python python python python python python python" name="code">
|
||||
C: LRANGE mylist 0 3
|
||||
S: *4
|
||||
S: $3
|
||||
@@ -83,56 +84,50 @@ S: $5
|
||||
S: Hello
|
||||
S: $5
|
||||
S: World
|
||||
</pre>The first line the server sent is "<b>4\r\n" in order to specify that four bulk
|
||||
write will follow. Then every bulk write is transmitted.<br/><br/>If the specified key does not exist instead of the number of elements in the
|
||||
list, the special value -1 is sent as count. Example:<br/><br/><pre class="codeblock python python python python python python python" name="code">
|
||||
</pre>As you can see the multi bulk reply is exactly the same format used in order
|
||||
to send commands to the Redis server unsing the unified protocol.<br/><br/>The first line the server sent is "<b>4\r\n" in order to specify that four bulk
|
||||
replies will follow. Then every bulk write is transmitted.<br/><br/>If the specified key does not exist, instead of the number of elements in the
|
||||
list the special value -1 is sent as count. Example:<br/><br/><pre class="codeblock python python python python python python python python python" name="code">
|
||||
C: LRANGE nokey 0 1
|
||||
S: *-1
|
||||
</pre>A client library API SHOULD return a nil object and not an empty list when this
|
||||
happens. This makes possible to distinguish between empty list and non existing ones.<h2><a name="Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a></h2>Single elements of a multi bulk reply may have -1 length, in order to signal that this elements are missing and not empty strings. This can happen with the SORT command when used with the GET <i>pattern</i> option when the specified key is missing. Example of a multi bulk reply containing an empty element:<br/><br/><pre class="codeblock python python python python python python python python" name="code">
|
||||
happens. This makes possible to distinguish between empty list and other error conditions (for instance a timeout condition in the BLPOP command).<h2><a name="Nil elements in Multi-Bulk replies">Nil elements in Multi-Bulk replies</a></h2>Single elements of a multi bulk reply may have -1 length, in order to signal that this elements are missing and not empty strings. This can happen with the SORT command when used with the GET <i>pattern</i> option when the specified key is missing. Example of a multi bulk reply containing an empty element:<br/><br/><pre class="codeblock python python python python python python python python python python" name="code">
|
||||
S: *3
|
||||
S: $3
|
||||
S: foo
|
||||
S: $-1
|
||||
S: $3
|
||||
S: bar
|
||||
</pre>The second element is nul. The client library should return something like this:<br/><br/><pre class="codeblock python python python python python python python python python" name="code">
|
||||
</pre>The second element is nul. The client library should return something like this:<br/><br/><pre class="codeblock python python python python python python python python python python python" name="code">
|
||||
["foo",nil,"bar"]
|
||||
</pre><h2><a name="Single line reply">Single line reply</a></h2>As already seen a single line reply is in the form of a single line string
|
||||
starting with "+" terminated by "\r\n". For example:<br/><br/><pre class="codeblock python python python python python python python python python python" name="code">
|
||||
+OK
|
||||
</pre>The client library should return everything after the "+", that is, the string "OK" in the example.<br/><br/>The following commands reply with a status code reply:
|
||||
PING, SET, SELECT, SAVE, BGSAVE, SHUTDOWN, RENAME, LPUSH, RPUSH, LSET, LTRIM<h2><a name="Integer reply">Integer reply</a></h2>This type of reply is just a CRLF terminated string representing an integer, prefixed by a ":" byte. For example ":0\r\n", or ":1000\r\n" are integer replies.<br/><br/>With commands like INCR or LASTSAVE using the integer reply to actually return a value there is no special meaning for the returned integer. It is just an incremental number for INCR, a UNIX time for LASTSAVE and so on.<br/><br/>Some commands like EXISTS will return 1 for true and 0 for false.<br/><br/>Other commands like SADD, SREM and SETNX will return 1 if the operation was actually done, 0 otherwise.<br/><br/>The following commands will reply with an integer reply: SETNX, DEL, EXISTS, INCR, INCRBY, DECR, DECRBY, DBSIZE, LASTSAVE, RENAMENX, MOVE, LLEN, SADD, SREM, SISMEMBER, SCARD<h2><a name="Multi bulk commands">Multi bulk commands</a></h2>As you can see with the protocol described so far there is no way to
|
||||
send multiple binary-safe arguments to a command. With bulk commands the
|
||||
last argument is binary safe, but there are commands where multiple binary-safe
|
||||
commands are needed, like the MSET command that is able to SET multiple keys
|
||||
in a single operation.<br/><br/>In order to address this problem Redis 1.1 introduced a new way of seding
|
||||
commands to a Redis server, that uses exactly the same protocol of the
|
||||
multi bulk replies. For instance the following is a SET command using the
|
||||
normal bulk protocol:<br/><br/><pre class="codeblock python python python python python python python python python python python" name="code">
|
||||
SET mykey 8
|
||||
myvalue
|
||||
</pre>While the following uses the multi bulk command protocol:<br/><br/><pre class="codeblock python python python python python python python python python python python python" name="code">
|
||||
*3
|
||||
$3
|
||||
SET
|
||||
$5
|
||||
mykey
|
||||
$8
|
||||
myvalue
|
||||
</pre>Commands sent in this format are longer, so currently they are used only in
|
||||
order to transmit commands containing multiple binary-safe arguments, but
|
||||
actually this protocol can be used to send every kind of command, without to
|
||||
know if it's an inline, bulk or multi-bulk command.<br/><br/>It is possible that in the future Redis will support only this format.<br/><br/>A good client library may implement unknown commands using this
|
||||
command format in order to support new commands out of the box without
|
||||
modifications.<h2><a name="Multiple commands and pipelining">Multiple commands and pipelining</a></h2>A client can use the same connection in order to issue multiple commands.
|
||||
</pre><h2><a name="Multiple commands and pipelining">Multiple commands and pipelining</a></h2>A client can use the same connection in order to issue multiple commands.
|
||||
Pipelining is supported so multiple commands can be sent with a single
|
||||
write operation by the client, it is not needed to read the server reply
|
||||
in order to issue the next command. All the replies can be read at the end.<br/><br/>Usually Redis server and client will have a very fast link so this is not
|
||||
very important to support this feature in a client implementation, still
|
||||
if an application needs to issue a very large number of commands in short
|
||||
time to use pipelining can be much faster.
|
||||
</b>
|
||||
time to use pipelining can be much faster.<h1><a name="The old protocol for sending commands">The old protocol for sending commands</a></h1>Before of the Unified Request Protocol Redis used a different protocol to send
|
||||
commands, that is still supported since it is simpler to type by hand via telnet. In this protocol there are two kind of commands:<br/><br/><blockquote>* Inline commands: simple commands where argumnets are just space separated strings. No binary safeness is possible.* Bulk commands: bulk commands are exactly like inline commands, but the last argument is handled in a special way in order to allow for a binary-safe last argument.</blockquote>
|
||||
<h2><a name="Inline Commands">Inline Commands</a></h2>The simplest way to send Redis a command is via </b>Inline Commands.
|
||||
The following is an example of a server/client chat using an inline command (the server chat starts with S:, the client chat with C:)<br/><br/><pre class="codeblock python python python python python python python python python python python python" name="code">
|
||||
C: PING
|
||||
S: +PONG
|
||||
</pre>The following is another example of an INLINE command returning an integer:<br/><br/><pre class="codeblock python python python python python python python python python python python python python" name="code">
|
||||
C: EXISTS somekey
|
||||
S: :0
|
||||
</pre>Since 'somekey' does not exist the server returned ':0'.<br/><br/>Note that the EXISTS command takes one argument. Arguments are separated
|
||||
by spaces.<h2><a name="Bulk commands">Bulk commands</a></h2>Some commands when sent as inline commands require a special form in order
|
||||
to support a binary safe last argument. This commands will use the last argument
|
||||
for a "byte count", then the bulk data is sent (that can be binary safe since
|
||||
the server knows how many bytes to read).<br/><br/>See for instance the following example:<br/><br/><pre class="codeblock python python python python python python python python python python python python python python" name="code">
|
||||
C: SET mykey 6
|
||||
C: foobar
|
||||
S: +OK
|
||||
</pre>The last argument of the commnad is '6'. This specify the number of DATA
|
||||
bytes that will follow, that is, the string "foobar". Note that even this bytes are terminated by two additional bytes of CRLF.<br/><br/>All the bulk commands are in this exact form: instead of the last argument
|
||||
the number of bytes that will follow is specified, followed by the bytes
|
||||
composing the argument itself, and CRLF. In order to be more clear for the programmer this is the string sent by the client in the above sample:<br/><br/><blockquote>"SET mykey 6\r\nfoobar\r\n"</blockquote>
|
||||
Redis has an internal list of what command is inline and what command is bulk, so you have to send this commands accordingly. It is strongly suggested to use the new Unified Request Protocol instead.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>PublishSubscribe: Contents</b><br> <a href="#UNSUBSCRIBE channel_1 channel_2 ... channel_N">UNSUBSCRIBE channel_1 channel_2 ... channel_N</a><br> <a href="#UNSUBSCRIBE (unsubscribe from all channels)">UNSUBSCRIBE (unsubscribe from all channels)</a><br> <a href="#PSUBSCRIBE pattern_1 pattern_2 ... pattern_N">PSUBSCRIBE pattern_1 pattern_2 ... pattern_N</a><br> <a href="#PUNSUBSCRIBE pattern_1 pattern_2 ... pattern_N">PUNSUBSCRIBE pattern_1 pattern_2 ... pattern_N</a><br> <a href="#PUNSUBSCRIBE (unsubscribe from all patterns)">PUNSUBSCRIBE (unsubscribe from all patterns)</a><br> <a href="#PUBLISH channel message">PUBLISH channel message</a><br> <a href="#Format of pushed messages">Format of pushed messages</a><br> <a href="#Unsubscribing from all the channels at once">Unsubscribing from all the channels at once</a><br> <a href="#Wire protocol example">Wire protocol example</a><br> <a href="#PSUBSCRIBE and PUNSUBSCRIBE: pattern matching subscriptions">PSUBSCRIBE and PUNSUBSCRIBE: pattern matching subscriptions</a><br> <a href="#Messages matching both a pattern and a channel subscription">Messages matching both a pattern and a channel subscription</a><br> <a href="#The meaning of the count of subscriptions with pattern matching">The meaning of the count of subscriptions with pattern matching</a><br> <a href="#More details on the PUBLISH command">More details on the PUBLISH command</a><br> <a href="#Programming Example">Programming Example</a><br> <a href="#Client library implementations hints">Client library implementations hints</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">PublishSubscribe</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
=SUBSCRIBE channel_1 channel_2 ... channel_N=
|
||||
<h1><a name="UNSUBSCRIBE channel_1 channel_2 ... channel_N">UNSUBSCRIBE channel_1 channel_2 ... channel_N</a></h1>
|
||||
<h1><a name="UNSUBSCRIBE (unsubscribe from all channels)">UNSUBSCRIBE (unsubscribe from all channels)</a></h1>
|
||||
<h1><a name="PSUBSCRIBE pattern_1 pattern_2 ... pattern_N">PSUBSCRIBE pattern_1 pattern_2 ... pattern_N</a></h1>
|
||||
<h1><a name="PUNSUBSCRIBE pattern_1 pattern_2 ... pattern_N">PUNSUBSCRIBE pattern_1 pattern_2 ... pattern_N</a></h1>
|
||||
<h1><a name="PUNSUBSCRIBE (unsubscribe from all patterns)">PUNSUBSCRIBE (unsubscribe from all patterns)</a></h1>
|
||||
<h1><a name="PUBLISH channel message">PUBLISH channel message</a></h1>Time complexity: subscribe is O(1), unsubscribe is O(N) where N is the number of clients already subscribed to a channel, publish is O(N+M) where N is the number of clients subscribed to the receiving channel, and M is the total number of subscribed patterns (by any client). Psubscribe is O(N) where N is the number of patterns the Psubscribing client is already subscribed to. Punsubscribe is O(N+M) where N is the number of patterns the Punsubscribing client is already subscribed and M is the number of total patterns subscribed in the system (by any client).<br/><br/><b>Note</b>: this commands are available starting form Redis 2.0.0<br/><br/><blockquote>SUBSCRIBE, UNSUBSCRIBE and PUBLISH commands implement the<a href="http://en.wikipedia.org/wiki/Publish/subscribe" target="_blank">Publish/Subscribe messaging paradigm</a> where (citing Wikipedia) senders (publishers) are not programmed to send their messages to specific receivers (subscribers). Rather, published messages are characterized into channels, without knowledge of what (if any) subscribers there may be. Subscribers express interest in one or more channels, and only receive messages that are of interest, without knowledge of what (if any) publishers there are. This decoupling of publishers and subscribers can allow for greater scalability and a more dynamic network topology.</blockquote>
|
||||
<blockquote>For instance in order to subscribe to the channels foo and bar the clientwill issue the SUBSCRIBE command followed by the names of the channels.</blockquote><pre class="codeblock python" name="code">
|
||||
SUBSCRIBE foo bar
|
||||
</pre>
|
||||
<blockquote>All the messages sent by other clients to this channels will be pushed bythe Redis server to all the subscribed clients, in the form of a threeelements bulk reply, where the first element is the message type, thesecond the originating channel, and the third argument the message payload.</blockquote>
|
||||
<blockquote>A client subscribed to 1 or more channels should NOT issue other commandsother than SUBSCRIBE and UNSUBSCRIBE, but can subscribe or unsubscribeto other channels dynamically.</blockquote>
|
||||
<blockquote>The reply of the SUBSCRIBE and UNSUBSCRIBE operations are sent in the formof messages, so that the client can just read a coherent stream of messageswhere the first element indicates the kind of message.</blockquote><h2><a name="Format of pushed messages">Format of pushed messages</a></h2>
|
||||
<blockquote>Messages are in the form of multi bulk replies with three elements.The first element is the kind of message:</blockquote><ul><li> "subscribe": means that we successfully subscribed to the channel given as second element of the multi bulk reply. The third argument represents the number of channels we are currently subscribed to.</li><li> "unsubscribe": means that we successfully unsubscribed from the channel given as second element of the multi bulk reply. The third argument represents the number of channels we are currently subscribed to. If this latest argument is zero, we are no longer subscribed to any channel, and the client can issue any kind of Redis command as we are outside the Pub/sub state.</li><li> "message": it is a message received as result of a PUBLISH command issued by another client. The second element is the name of the originating channel, and the third the actual message payload.</li></ul><h2><a name="Unsubscribing from all the channels at once">Unsubscribing from all the channels at once</a></h2>
|
||||
If the UNSUBSCRIBE command is issued without additional arguments, it is equivalent to unsubscribing to all the channels we are currently subscribed. A message for every unsubscribed channel will be received.
|
||||
<h2><a name="Wire protocol example">Wire protocol example</a></h2>
|
||||
<pre class="codeblock python python" name="code">
|
||||
SUBSCRIBE first second
|
||||
*3
|
||||
$9
|
||||
subscribe
|
||||
$5
|
||||
first
|
||||
:1
|
||||
*3
|
||||
$9
|
||||
subscribe
|
||||
$6
|
||||
second
|
||||
:2
|
||||
</pre>
|
||||
at this point from another client we issue a PUBLISH operation against the channel named "second". This is what the first client receives:
|
||||
<pre class="codeblock python python python" name="code">
|
||||
*3
|
||||
$7
|
||||
message
|
||||
$6
|
||||
second
|
||||
$5
|
||||
Hello
|
||||
</pre>
|
||||
Now the client unsubscribes itself from all the channels using the UNSUBSCRIBE command without additional arguments:
|
||||
<pre class="codeblock python python python python" name="code">
|
||||
UNSUBSCRIBE
|
||||
*3
|
||||
$11
|
||||
unsubscribe
|
||||
$6
|
||||
second
|
||||
:1
|
||||
*3
|
||||
$11
|
||||
unsubscribe
|
||||
$5
|
||||
first
|
||||
:0
|
||||
</pre>
|
||||
<h2><a name="PSUBSCRIBE and PUNSUBSCRIBE: pattern matching subscriptions">PSUBSCRIBE and PUNSUBSCRIBE: pattern matching subscriptions</a></h2>
|
||||
Redis Pub/Sub implementation supports pattern matching. Clients may subscribe to glob style patterns in order to receive all the messages sent to channel names matching a given pattern.<br/><br/>For instance the command:
|
||||
<pre class="codeblock python python python python python" name="code">
|
||||
PSUBSCRIBE news.*
|
||||
</pre>
|
||||
Will receive all the messages sent to the channel news.art.figurative and news.music.jazz and so forth. All the glob style patterns as valid, so multiple wild cards are supported.<br/><br/>Messages received as a result of pattern matching are sent in a different format:
|
||||
<ul><li> The type of the message is "pmessage": it is a message received as result of a PUBLISH command issued by another client, matching a pattern matching subscription. The second element is the original pattern matched, the third element is the name of the originating channel, and the last element the actual message payload.</li></ul>
|
||||
Similarly to SUBSCRIBE and UNSUBSCRIBE, PSUBSCRIBE and PUNSUBSCRIBE commands are acknowledged by the system sending a message of type "psubscribe" and "punsubscribe" using the same format as the "subscribe" and "unsubscribe" message format.
|
||||
<h2><a name="Messages matching both a pattern and a channel subscription">Messages matching both a pattern and a channel subscription</a></h2>
|
||||
A client may receive a single message multiple time if it's subscribed to multiple patterns matching a published message, or it is subscribed to both patterns and channels matching the message. Like in the following example:
|
||||
<pre class="codeblock python python python python python python" name="code">
|
||||
SUBSCRIBE foo
|
||||
PSUBSCRIBE f*
|
||||
</pre>
|
||||
In the above example, if a message is sent to the <b>foo</b> channel, the client will receive two messages, one of type "message" and one of type "pmessage".
|
||||
<h2><a name="The meaning of the count of subscriptions with pattern matching">The meaning of the count of subscriptions with pattern matching</a></h2>
|
||||
In <b>subscribe</b>, <b>unsubscribe</b>, <b>psubscribe</b> and <b>punsubscribe</b> message types, the last argument is the count of subscriptions still active. This number is actually the total number of channels and patterns the client is still subscribed to. So the client will exit the Pub/Sub state only when this count will drop to zero as a result of unsubscription from all the channels and patterns.
|
||||
<h2><a name="More details on the PUBLISH command">More details on the PUBLISH command</a></h2>
|
||||
The Publish command is a bulk command where the first argument is the target class, and the second argument the data to send. It returns an Integer Reply representing the number of clients that received the message (that is, the number of clients that were listening for this class).
|
||||
<h2><a name="Programming Example">Programming Example</a></h2>
|
||||
Pieter Noordhuis provided a great example using Event-machine and Redis to create <a href="http://chat.redis-db.com" target="_blank">a multi user high performance web chat</a>, with source code included of course!
|
||||
<h2><a name="Client library implementations hints">Client library implementations hints</a></h2>
|
||||
Because all the messages received contain the original subscription causing the message delivery (the channel in the case of "message" type, and the original pattern in the case of "pmessage" type) clinet libraries may bind the original subscription to callbacks (that can be anonymous functions, blocks, function pointers, and so forth), using an hash table.<br/><br/>When a message is received an O(1) lookup can be done in order to deliver the message to the registered callback.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+3
-2
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>QuickStart: Contents</b><br> <a href="#Obtain the latest version">Obtain the latest version</a><br> <a href="#Compile">Compile</a><br> <a href="#Run the server">Run the server</a><br> <a href="#Play with the built in client">Play with the built in client</a><br> <a href="#Further reading">Further reading</a>
|
||||
<b>QuickStart: Contents</b><br> <a href="#Quick Start">Quick Start</a><br> <a href="#Obtain the latest version">Obtain the latest version</a><br> <a href="#Compile">Compile</a><br> <a href="#Run the server">Run the server</a><br> <a href="#Play with the built in client">Play with the built in client</a><br> <a href="#Further reading">Further reading</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">QuickStart</h1>
|
||||
@@ -26,7 +26,8 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
= Quick Start =<br/><br/>This quickstart is a five minutes howto on how to get started with Redis. For more information on Redis check <a href="http://code.google.com/p/redis/wiki/index" target="_blank">Redis Documentation Index</a>.<h2><a name="Obtain the latest version">Obtain the latest version</a></h2>The latest stable source distribution of Redis can be obtained <a href="http://code.google.com/p/redis/downloads/list" target="_blank">at this location as a tarball</a>.<br/><br/><pre class="codeblock python" name="code">
|
||||
#sidebar <a href="RedisGuides.html">RedisGuides</a>
|
||||
<h1><a name="Quick Start">Quick Start</a></h1>This quickstart is a five minutes howto on how to get started with Redis. For more information on Redis check <a href="http://code.google.com/p/redis/wiki/index" target="_blank">Redis Documentation Index</a>.<h2><a name="Obtain the latest version">Obtain the latest version</a></h2>The latest stable source distribution of Redis can be obtained <a href="http://code.google.com/p/redis/downloads/list" target="_blank">at this location as a tarball</a>.<br/><br/><pre class="codeblock python" name="code">
|
||||
$ wget http://redis.googlecode.com/files/redis-1.02.tar.gz
|
||||
</pre>The unstable source code, with more features but not ready for production, can be downloaded using git:<br/><br/><pre class="codeblock python python" name="code">
|
||||
$ git clone git://github.com/antirez/redis.git
|
||||
|
||||
+40
-9
@@ -26,11 +26,42 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
= Introduction =<br/><br/>Redis is a database. To be specific, Redis is a database implementing a dictionary, where every key is associated with a value. For example I can set the key "surname_1992" to the string "Smith".
|
||||
What makes Redis different from many other key-value stores, is that every single value has a type. The following types are supported:<br/><br/><ul><li> <a href="Strings.html">Strings</a></li><li> <a href="Lists.html">Lists</a></li><li> <a href="Sets.html">Sets</a></li><li> <a href="SortedSets.html">Sorted Set</a> (since version 1.1)</li></ul>
|
||||
The type of a value determines what operations (called commands) are available for the value itself.
|
||||
For example you can append elements to a list stored at the key "mylist" using the LPUSH or RPUSH command in O(1). Later you'll be able to get a range of elements with LRANGE or trim the list with LTRIM. Sets are very flexible too, it is possible to add and remove elements from Sets (unsorted collections of strings), and then ask for server-side intersection, union, difference of Sets. Each command is performed through server-side atomic operations.
|
||||
Please refer to the <a href="CommandReference.html">Command Reference</a> to see the full list of operations associated to these data types.<br/><br/>In other words, you can look at Redis as a data structures server. A Redis user is virtually provided with an interface to <a href="http://en.wikipedia.org/wiki/Abstract_data_type" target="_blank">Abstract Data Types</a>, saving her from the responsibility to implement concrete data structures and algorithms. Indeed both algorithms and data structures in Redis are properly choosed in order to obtain the best performance.<h1><a name="All data in memory, but saved on disk">All data in memory, but saved on disk</a></h1>Redis loads and mantains the whole dataset into memory, but the dataset is persistent, since at the same time it is saved on disk, so that when the server is restarted data can be loaded back in memory.<br/><br/>There are two kind of persistence supported: the first one is called snapshotting. In this mode Redis, from time to time, writes a dump on disk asynchronously. The dataset is loaded from the dump every time the server is (re)started.<br/><br/>Redis can be configured to save the dataset when a certain number of changes is reached and after a given number of seconds elapses. For example, you can configure Redis to save after 1000 changes and at most 60 seconds since the last save. You can specify any combination for these numbers.<br/><br/>Because data is written asynchronously, when a system crash occurs, the last few queries can get lost (that is acceptable in many applications but not in all). In order to make this a non issue Redis supports another, safer persistence mode, called <a href="AppendOnlyFileHowto.html">Append Only File</a>, where every command received altering the dataset (so not a read-only command, but a write command) is written on an append only file ASAP. This commands are <i>replayed</i> when the server is restarted in order to rebuild the dataset in memory.<br/><br/>Redis Append Only File supports a very handy feature: the server is able to safely rebuild the append only file in background in a non-blocking fashion when it gets too long. You can find <a href="AppendOnlyFileHowto.html">more details in the Append Only File HOWTO</a>.<h1><a name="Master-Slave replication made trivial">Master-Slave replication made trivial</a></h1>Whatever will be the persistence mode you'll use Redis supports master-slave replications if you want to stay really safe or if you need to scale to huge amounts of reads.<br/><br/><b>Redis Replication is trivial to setup</b>. So trivial that all you need to do in order to configure a Redis server to be a slave of another one, with automatic synchronization if the link will go down and so forth, is the following config line: <code name="code" class="python">slaveof 192.168.1.100 6379</code>. <a href="ReplicationHowto.html">We provide a Replication Howto</a> if you want to know more about this feature.<h1><a name="It's persistent but supports expires">It's persistent but supports expires</a></h1>Redis can be used as a <b>memcached on steroids</b> because is as fast as memcached but with a number of features more. Like memcached, Redis also supports setting timeouts to keys so that this key will be automatically removed when a given amount of time passes.<h1><a name="Beyond key-value databases">Beyond key-value databases</a></h1>All these features allow to use Redis as the sole DB for your scalable application without the need of any relational database. <a href="TwitterAlikeExample.html">We wrote a simple Twitter clone in PHP + Redis</a> to show a real world example, the link points to an article explaining the design and internals in very simple words.<h1><a name="Multiple databases support">Multiple databases support</a></h1>Redis supports multiple databases with commands to atomically move keys from one database to the other. By default DB 0 is selected for every new connection, but using the SELECT command it is possible to select a different database. The MOVE operation can move an item from one DB to another atomically. This can be used as a base for locking free algorithms together with the 'RANDOMKEY' commands.<h1><a name="Know more about Redis!">Know more about Redis!</a></h1>To really get a feeling about what Redis is and how it works please try reading <a href="IntroductionToRedisDataTypes.html">A fifteen minutes introduction to Redis data types</a>.<br/><br/>To know a bit more about how Redis works <i>internally</i> continue reading.<h1><a name="Redis Tutorial">Redis Tutorial</a></h1>(note, you can skip this section if you are only interested in "formal" doc.)<br/><br/>Later in this document you can find detailed information about Redis commands,
|
||||
= Introduction =<br/><br/>Redis is an extremely fast and powerful key-value store database and server implemented in ANSI C. Redis offers many different ways to do one straightforward thing: store a value ("antirez") to a key ("redis"). While the format of keys must always be simple strings, the power is with the values, which support the following data types:<br/><br/><ul><li> <a href="Strings.html">Strings</a></li><li> <a href="Lists.html">Lists</a></li><li> <a href="Sets.html">Sets</a></li><li> <a href="SortedSets.html">Sorted Sets (zsets)</a></li><li> <a href="Hashes.html">Hashes</a></li></ul>
|
||||
Each value type has an associated list of commands which can operate on them, and the <a href="CommandReference.html">The Redis Command Reference</a> contains an up to date list of these commands, organized primarily by data type. The Redis source also includes a <a href="RedisCLI.html">Redis command line interface</a> which allows you to interact directly with the server, and is the means by which this introduction will provide examples. Once you walk through the <a href="QuickStart.html">Redis Quick Start Guide</a> to get your instance of Redis running, you can follow along. <br/><br/>One of the most powerful aspects of Redis is the wide range of commands which are optimized to work with specific data value types and executed as atomic server-side operations. The <a href="Lists.html">List</a> type is a great example - Redis implements O(1) operations such as <a href="RpushCommand.html">LPUSH</a> or <a href="RpushCommand.html">RPUSH</a>, which have accompanying <a href="LpopCommand.html">LPOP</a> and <a href="LpopCommand.html">RPOP</a> methods:<br/><br/><pre class="codeblock python" name="code">
|
||||
redis> lpush programming_languages C
|
||||
OK
|
||||
redis> lpush programming_languages Ruby
|
||||
OK
|
||||
redis> rpush programming_languages Python
|
||||
OK
|
||||
redis> rpop programming_languages
|
||||
Python
|
||||
redis> lpop programming_languages
|
||||
Ruby
|
||||
</pre>More complex operations are available for each data type as well. Continuing with lists, you can get a range of elements with <a href="LrangeCommand.html">LRANGE</a> (O(start+n)) or trim the list with <a href="LtrimCommand.html">LTRIM</a> (O(n)):<br/><br/><pre class="codeblock python python" name="code">
|
||||
redis> lpush cities NYC
|
||||
OK
|
||||
redis> lpush cities SF
|
||||
OK
|
||||
redis> lpush cities Tokyo
|
||||
OK
|
||||
redis> lpush cities London
|
||||
OK
|
||||
redis> lpush cities Paris
|
||||
OK
|
||||
redis> lrange cities 0 2
|
||||
1. Paris
|
||||
2. London
|
||||
3. Tokyo
|
||||
redis> ltrim cities 0 1
|
||||
OK
|
||||
redis> lpop cities
|
||||
Paris
|
||||
redis> lpop cities
|
||||
London
|
||||
redis> lpop cities
|
||||
(nil)
|
||||
</pre>You can also add and remove elements from a set, and perform intersections, unions, and differences. <br/><br/>Redis can also be looked at as a data structures server. A Redis user is virtually provided with an interface to <a href="http://en.wikipedia.org/wiki/Abstract_data_type" target="_blank">Abstract Data Types</a>, saving them from the responsibility of implementing concrete data structures and algorithms -- indeed both algorithms and data structures in Redis are properly chosen in order to obtain the best performance.<h1><a name="All data in memory, but saved on disk">All data in memory, but saved on disk</a></h1>Redis loads and mantains the whole dataset into memory, but the dataset is persistent, since at the same time it is saved on disk, so that when the server is restarted data can be loaded back in memory.<br/><br/>There are two kinds of persistence supported: the first one is called snapshotting. In this mode Redis periodically writes to disk asynchronously. The dataset is loaded from the dump every time the server is (re)started.<br/><br/>Redis can be configured to save the dataset when a certain number of changes is reached and after a given number of seconds elapses. For example, you can configure Redis to save after 1000 changes and at most 60 seconds since the last save. You can specify any combination for these numbers.<br/><br/>Because data is written asynchronously, when a system crash occurs, the last few queries can get lost (that is acceptable in many applications but not in all). In order to make this a non issue Redis supports another, safer persistence mode, called <a href="AppendOnlyFileHowto.html">Append Only File</a>, where every command received altering the dataset (so not a read-only command, but a write command) is written on an append only file ASAP. This commands are <i>replayed</i> when the server is restarted in order to rebuild the dataset in memory.<br/><br/>Redis Append Only File supports a very handy feature: the server is able to safely rebuild the append only file in background in a non-blocking fashion when it gets too long. You can find <a href="AppendOnlyFileHowto.html">more details in the Append Only File HOWTO</a>.<h1><a name="Master-Slave replication made trivial">Master-Slave replication made trivial</a></h1>Whatever will be the persistence mode you'll use Redis supports master-slave replications if you want to stay really safe or if you need to scale to huge amounts of reads.<br/><br/><b>Redis Replication is trivial to setup</b>. So trivial that all you need to do in order to configure a Redis server to be a slave of another one, with automatic synchronization if the link will go down and so forth, is the following config line: <code name="code" class="python">slaveof 192.168.1.100 6379</code>. <a href="ReplicationHowto.html">We provide a Replication Howto</a> if you want to know more about this feature.<h1><a name="It's persistent but supports expires">It's persistent but supports expires</a></h1>Redis can be used as a <b>memcached on steroids</b> because is as fast as memcached but with a number of features more. Like memcached, Redis also supports setting timeouts to keys so that this key will be automatically removed when a given amount of time passes.<h1><a name="Beyond key-value databases">Beyond key-value databases</a></h1>All these features allow to use Redis as the sole DB for your scalable application without the need of any relational database. <a href="TwitterAlikeExample.html">We wrote a simple Twitter clone in PHP + Redis</a> to show a real world example, the link points to an article explaining the design and internals in very simple words.<h1><a name="Multiple databases support">Multiple databases support</a></h1>Redis supports multiple databases with commands to atomically move keys from one database to the other. By default DB 0 is selected for every new connection, but using the SELECT command it is possible to select a different database. The MOVE operation can move an item from one DB to another atomically. This can be used as a base for locking free algorithms together with the 'RANDOMKEY' commands.<h1><a name="Know more about Redis!">Know more about Redis!</a></h1>To really get a feeling about what Redis is and how it works please try reading <a href="IntroductionToRedisDataTypes.html">A fifteen minutes introduction to Redis data types</a>.<br/><br/>To know a bit more about how Redis works <i>internally</i> continue reading.<h1><a name="Redis Tutorial">Redis Tutorial</a></h1>(note, you can skip this section if you are only interested in "formal" doc.)<br/><br/>Later in this document you can find detailed information about Redis commands,
|
||||
the protocol specification, and so on. This kind of documentation is useful
|
||||
but... if you are new to Redis it is also BORING! The Redis protocol is designed
|
||||
so that is both pretty efficient to be parsed by computers, but simple enough
|
||||
@@ -40,7 +71,7 @@ feeling about it, and how it works.<br/><br/>To start just compile redis with 'm
|
||||
The server will start and log stuff on the standard output, if you want
|
||||
it to log more edit redis.conf, set the loglevel to debug, and restart it.<br/><br/>You can specify a configuration file as unique parameter:<br/><br/><blockquote>./redis-server /etc/redis.conf</blockquote>
|
||||
This is NOT required. The server will start even without a configuration file
|
||||
using a default built-in configuration.<br/><br/>Now let's try to set a key to a given value:<br/><br/><pre class="codeblock python" name="code">
|
||||
using a default built-in configuration.<br/><br/>Now let's try to set a key to a given value:<br/><br/><pre class="codeblock python python python" name="code">
|
||||
$ telnet localhost 6379
|
||||
Trying 127.0.0.1...
|
||||
Connected to localhost.
|
||||
@@ -59,17 +90,17 @@ the point of view of both the server and client but allows us to play with
|
||||
Redis with the telnet command easily.<br/><br/>The last line of the chat between server and client is "+OK". This means
|
||||
our key was added without problems. Actually SET can never fail but
|
||||
the "+OK" sent lets us know that the server received everything and
|
||||
the command was actually executed.<br/><br/>Let's try to get the key content now:<br/><br/><pre class="codeblock python python" name="code">
|
||||
the command was actually executed.<br/><br/>Let's try to get the key content now:<br/><br/><pre class="codeblock python python python python" name="code">
|
||||
GET foo
|
||||
$3
|
||||
bar
|
||||
</pre>Ok that's very similar to 'set', just the other way around. We sent "get foo",
|
||||
the server replied with a first line that is just the $ character follwed by
|
||||
the number of bytes the value stored at key contained, followed by the actual
|
||||
bytes. Again "\r\n" are appended both to the bytes count and the actual data. In Redis slang this is called a bulk reply.<br/><br/>What about requesting a non existing key?<br/><br/><pre class="codeblock python python python" name="code">
|
||||
bytes. Again "\r\n" are appended both to the bytes count and the actual data. In Redis slang this is called a bulk reply.<br/><br/>What about requesting a non existing key?<br/><br/><pre class="codeblock python python python python python" name="code">
|
||||
GET blabla
|
||||
$-1
|
||||
</pre>When the key does not exist instead of the length, just the "$-1" string is sent. Since a -1 length of a bulk reply has no meaning it is used in order to specifiy a 'nil' value and distinguish it from a zero length value. Another way to check if a given key exists or not is indeed the EXISTS command:<br/><br/><pre class="codeblock python python python python" name="code">
|
||||
</pre>When the key does not exist instead of the length, just the "$-1" string is sent. Since a -1 length of a bulk reply has no meaning it is used in order to specifiy a 'nil' value and distinguish it from a zero length value. Another way to check if a given key exists or not is indeed the EXISTS command:<br/><br/><pre class="codeblock python python python python python python" name="code">
|
||||
EXISTS nokey
|
||||
:0
|
||||
EXISTS foo
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>RedisBigData: Contents</b><br> <a href="#BGSAVE and BGREWRITEAOF blocking fork() call">BGSAVE and BGREWRITEAOF blocking fork() call</a><br> <a href="#Using multiple cores">Using multiple cores</a><br> <a href="#Splitting data into multiple instances">Splitting data into multiple instances</a><br> <a href="#BGSAVE / AOFSAVE memory usage, and copy on write">BGSAVE / AOFSAVE memory usage, and copy on write</a><br> <a href="#BGSAVE / AOFSAVE time for big datasets">BGSAVE / AOFSAVE time for big datasets</a><br> <a href="#Non blocking hash table">Non blocking hash table</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">RedisBigData</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
=Redis Big Data: facts and guidelines=<h2><a name="BGSAVE and BGREWRITEAOF blocking fork() call">BGSAVE and BGREWRITEAOF blocking fork() call</a></h2>
|
||||
<pre class="codeblock python" name="code">
|
||||
fork.c && ./a.out
|
||||
allocated: 1 MB, fork() took 0.000
|
||||
allocated: 10 MB, fork() took 0.001
|
||||
allocated: 100 MB, fork() took 0.007
|
||||
allocated: 1000 MB, fork() took 0.059
|
||||
allocated: 10000 MB, fork() took 0.460
|
||||
allocated: 20000 MB, fork() took 0.895
|
||||
allocated: 30000 MB, fork() took 1.327
|
||||
allocated: 40000 MB, fork() took 1.759
|
||||
allocated: 50000 MB, fork() took 2.190
|
||||
allocated: 60000 MB, fork() took 2.621
|
||||
allocated: 70000 MB, fork() took 3.051
|
||||
allocated: 80000 MB, fork() took 3.483
|
||||
allocated: 90000 MB, fork() took 3.911
|
||||
allocated: 100000 MB, fork() took 4.340
|
||||
allocated: 110000 MB, fork() took 4.770
|
||||
allocated: 120000 MB, fork() took 5.202
|
||||
</pre>
|
||||
<h2><a name="Using multiple cores">Using multiple cores</a></h2>
|
||||
<h2><a name="Splitting data into multiple instances">Splitting data into multiple instances</a></h2>
|
||||
<h2><a name="BGSAVE / AOFSAVE memory usage, and copy on write">BGSAVE / AOFSAVE memory usage, and copy on write</a></h2>
|
||||
<h2><a name="BGSAVE / AOFSAVE time for big datasets">BGSAVE / AOFSAVE time for big datasets</a></h2>
|
||||
<h2><a name="Non blocking hash table">Non blocking hash table</a></h2>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>RedisCLI: Contents</b><br> <a href="#Redis CLI">Redis CLI</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">RedisCLI</h1>
|
||||
|
||||
<div class="summary">
|
||||
Redis Command Line Interface
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
<h1><a name="Redis CLI">Redis CLI</a></h1>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>RedisEventLibrary: Contents</b><br> <a href="#Redis Event Library">Redis Event Library</a><br> <a href="#Event Loop Initialization">Event Loop Initialization</a><br> <a href="#aeCreateEventLoop">aeCreateEventLoop</a><br> <a href="#aeCreateTimeEvent">aeCreateTimeEvent</a><br> <a href="#aeCreateFileEvent">aeCreateFileEvent</a><br> <a href="#Event Loop Processing">Event Loop Processing</a><br> <a href="#aeProcessEvents">aeProcessEvents</a><br> <a href="#processTimeEvents">processTimeEvents</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">RedisEventLibrary</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="RedisInternals.html">RedisInternals</a><h1><a name="Redis Event Library">Redis Event Library</a></h1>Redis implements its own event library. The event library is implemented in <b>ae.c</b>.<br/><br/>The best way to understand how the Redis event library works is to understand how Redis uses it.<h2><a name="Event Loop Initialization">Event Loop Initialization</a></h2>
|
||||
<code name="code" class="python">initServer</code> function defined in <b>redis.c</b> initializes the numerous fields of the <code name="code" class="python">redisServer</code> structure variable. One such field is the Redis event loop <code name="code" class="python">el</code>:<br/><br/><pre class="codeblock python" name="code">
|
||||
aeEventLoop *el
|
||||
</pre><code name="code" class="python">initServer</code> initializes <code name="code" class="python">server.el</code> field by calling <code name="code" class="python">aeCreateEventLoop</code> defined in <b>ae.c</b>. The definition of <code name="code" class="python">aeEventLoop</code> is below:
|
||||
<pre class="codeblock python python" name="code">
|
||||
typedef struct aeEventLoop
|
||||
{
|
||||
int maxfd;
|
||||
long long timeEventNextId;
|
||||
aeFileEvent events[AE_SETSIZE]; /* Registered events */
|
||||
aeFiredEvent fired[AE_SETSIZE]; /* Fired events */
|
||||
aeTimeEvent *timeEventHead;
|
||||
int stop;
|
||||
void *apidata; /* This is used for polling API specific data */
|
||||
aeBeforeSleepProc *beforesleep;
|
||||
} aeEventLoop;
|
||||
</pre><h3><a name="aeCreateEventLoop">aeCreateEventLoop</a></h3><code name="code" class="python">aeCreateEventLoop</code> first mallocs aeEventLoop structure then calls ae_epoll.c:aeApiCreate<code name="code" class="python">.
|
||||
|
||||
</code>aeApiCreate<code name="code" class="python"> mallocs </code>aeApiState<code name="code" class="python"> that has two fields - </code>epfd<code name="code" class="python"> that holds the epoll file descriptor returned by a call from [http://man.cx/epoll_create%282%29 epoll_create] and </code>events<code name="code" class="python"> that is of type </code>struct epoll_event<code name="code" class="python"> define by the Linux epoll library. The use of the </code>events<code name="code" class="python"> field will be described later.
|
||||
|
||||
Next is 'ae.c:aeCreateTimeEvent</code>. But before that <code name="code" class="python">initServer</code> call <code name="code" class="python">anet.c:anetTcpServer</code> that creates and returns a <i>listening descriptor</i>. The descriptor is listens to <b>port 6379</b> by default. The returned <i>listening descriptor</i> is stored in <code name="code" class="python">server.fd</code> field.<h3><a name="aeCreateTimeEvent">aeCreateTimeEvent</a></h3><code name="code" class="python">aeCreateTimeEvent</code> accepts the following as parameters:<br/><br/><ul><li> eventLoop: This is <code name="code" class="python">server.el</code> in <b>redis.c</b></li><li> milliseconds: The number of milliseconds from the curent time after which the timer expires.</li><li> proc: Function pointer. Stores the address of the function that has to be called after the timer expires.</li><li> clientData: Mostly NULL.</li><li> finalizerProc: Pointer to the function that has to be called before the timed event is removed from the list of timed events.</li></ul>
|
||||
<code name="code" class="python">initServer</code> calls <code name="code" class="python">aeCreateTimeEvent</code> to add a timed event to <code name="code" class="python">timeEventHead</code> field of <code name="code" class="python">server.el</code>. <code name="code" class="python">timeEventHead</code> is a pointer to a list of such timed events. The call to <code name="code" class="python">aeCreateTimeEvent</code> from <code name="code" class="python">redis.c:initServer</code> function is given below:<br/><br/><pre class="codeblock python python python" name="code">
|
||||
aeCreateTimeEvent(server.el /*eventLoop*/, 1 /*milliseconds*/, serverCron /*proc*/, NULL /*clientData*/, NULL /*finalizerProc*/);
|
||||
</pre><code name="code" class="python">redis.c:serverCron</code> performs many operations that helps keep Redis running properly.<h3><a name="aeCreateFileEvent">aeCreateFileEvent</a></h3>The essence of <code name="code" class="python">aeCreateFileEvent</code> function is to execute <a href="http://man.cx/epoll_ctl" target="_blank">epoll_ctl</a> system call which adds a watch for <code name="code" class="python">EPOLLIN</code> event on the <i>listening descriptor</i> create by <code name="code" class="python">anetTcpServer</code> and associate it with the epoll descriptor created by a call to <code name="code" class="python">aeCreateEventLoop</code>. <br/><br/>Following is an explanation of what precisely <code name="code" class="python">aeCreateFileEvent</code> does when called from <code name="code" class="python">redis.c:initServer</code>.<br/><br/><code name="code" class="python">initServer</code> passes the following arguments to <code name="code" class="python">aeCreateFileEvent</code>:
|
||||
<ul><li> server.el: The event loop created by <code name="code" class="python">aeCreateEventLoop</code>. The epoll descriptor is got from server.el. </li><li> server.fd: The <i>listening descriptor</i> that also serves as an index to access the relevant file event structure from the <code name="code" class="python">eventLoop->events</code> table and store extra information like the callback function.</li><li> AE_READABLE: Signifies that server.fd has to be watched for EPOLLIN event.</li><li> acceptHandler: The function that has to be executed when the event being watched for is ready. This function pointer is stored in <code name="code" class="python">eventLoop->events[server.fd]->rfileProc</code>. </li></ul>
|
||||
This completes the initialization of Redis event loop.<h2><a name="Event Loop Processing">Event Loop Processing</a></h2><code name="code" class="python">ae.c:aeMain</code> called from <code name="code" class="python">redis.c:main</code> does the job of processing the event loop that is initialized in the previous phase.<br/><br/><code name="code" class="python">ae.c:aeMain</code> calls <code name="code" class="python">ae.c:aeProcessEvents</code> in a while loop that processes pending time and file events.<h3><a name="aeProcessEvents">aeProcessEvents</a></h3><code name="code" class="python">ae.c:aeProcessEvents</code> looks for the time event that will be pending in the smallest amount of time by calling <code name="code" class="python">ae.c:aeSearchNearestTimer</code> on the event loop. In our case there is only one timer event in the event loop that was created by <code name="code" class="python">ae.c:aeCreateTimeEvent</code>. <br/><br/>Remember, that timer event created by <code name="code" class="python">aeCreateTimeEvent</code> has by now probably elapsed because it had a expiry time of one millisecond. Since, the timer has already expired the seconds and microseconds fields of the <code name="code" class="python">tvp</code> timeval structure variable is initialized to zero. <br/><br/>The <code name="code" class="python">tvp</code> structure variable along with the event loop variable is passed to <code name="code" class="python">ae_epoll.c:aeApiPoll</code>.<br/><br/><code name="code" class="python">aeApiPoll</code> functions does a <a href="http://man.cx/epoll_wait" target="_blank">epoll_wait</a> on the epoll descriptor and populates the <code name="code" class="python">eventLoop->fired</code> table with the details:
|
||||
<ul><li> fd: The descriptor that is now ready to do a read/write operation depending on the mask value. The </li><li> mask: The read/write event that can now be performed on the corresponding descriptor.</li></ul>
|
||||
<code name="code" class="python">aeApiPoll</code> returns the number of such file events ready for operation. Now to put things in context, if any client has requested for a connection then aeApiPoll would have noticed it and populated the <code name="code" class="python">eventLoop->fired</code> table with an entry of the descriptor being the <i>listening descriptor</i> and mask being <code name="code" class="python">AE_READABLE</code>.<br/><br/>Now, <code name="code" class="python">aeProcessEvents</code> calls the <code name="code" class="python">redis.c:acceptHandler</code> registered as the callback. <code name="code" class="python">acceptHandler</code> executes [<a href="http://man.cx/accept(2" target="_blank">http://man.cx/accept(2</a>) accept] on the <i>listening descriptor</i> returning a <i>connected descriptor</i> with the client. <code name="code" class="python">redis.c:createClient</code> adds a file event on the <i>connected descriptor</i> through a call to <code name="code" class="python">ae.c:aeCreateFileEvent</code> like below:<br/><br/><pre class="codeblock python python python python" name="code">
|
||||
if (aeCreateFileEvent(server.el, c->fd, AE_READABLE,
|
||||
readQueryFromClient, c) == AE_ERR) {
|
||||
freeClient(c);
|
||||
return NULL;
|
||||
}
|
||||
</pre><code name="code" class="python">c</code> is the <code name="code" class="python">redisClient</code> structure variable and <code name="code" class="python">c->fd</code> is the connected descriptor.<br/><br/>Next the <code name="code" class="python">ae.c:aeProcessEvent</code> calls <code name="code" class="python">ae.c:processTimeEvents</code><h3><a name="processTimeEvents">processTimeEvents</a></h3><code name="code" class="python">ae.processTimeEvents</code> iterates over list of time events starting at <code name="code" class="python">eventLoop->timeEventHead</code>.<br/><br/>For every timed event that has elapsed <code name="code" class="python">processTimeEvents</code> calls the registered callback. In this case it calls the only timed event callback registered, that is, <code name="code" class="python">redis.c:serverCron</code>. The callback returns the time in milliseconds after which the callback must be called again. This change is recorded via a call to <code name="code" class="python">ae.c:aeAddMilliSeconds</code> and will be handled on the next iteration of <code name="code" class="python">ae.c:aeMain</code> while loop.<br/><br/>That's all.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>RedisGuides: Contents</b>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">RedisGuides</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
= Redis Guides and Howtos=
|
||||
<ul><li> <a href="QuickStart.html">Redis Quick Start</a></li><li> <a href="VirtualMemoryUserGuide.html">Virutal Memory User Guide</a></li><li> <a href="IntroductionToRedisDataTypes.html">A Fifteen Minutes Introduction to the Redis Data Types</a></li><li> <a href="ReplicationHowto.html">The Redis Replication HOWTO</a></li><li> <a href="AppendOnlyFileHowto.html">The Append Only File HOWTO</a></li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>RedisInternals: Contents</b><br> <a href="#Redis Internals">Redis Internals</a><br> <a href="#Redis STRINGS">Redis STRINGS</a><br> <a href="#Redis Virtual Memory">Redis Virtual Memory</a><br> <a href="#Redis Event Library">Redis Event Library</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">RedisInternals</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
<h1><a name="Redis Internals">Redis Internals</a></h1>This is a source code level documentation of Redis.<h2><a name="Redis STRINGS">Redis STRINGS</a></h2>String is the basic building block of Redis types. <br/><br/>Redis is a key-value store.
|
||||
All Redis keys are strings and its also the simplest value type.<br/><br/><blockquote></blockquote>Lists, sets, sorted sets and hashes are other more complex value types and even
|
||||
these are composed of strings.<br/><br/><a href="HackingStrings.html">Hacking Strings</a> documents the Redis String implementation details.<h2><a name="Redis Virtual Memory">Redis Virtual Memory</a></h2>A technical specification full of details about the <a href="VirtualMemorySpecification.html">Redis Virtual Memory subsystem</a><h2><a name="Redis Event Library">Redis Event Library</a></h2>Read <a href="EventLibray.html">event library</a> to understand what an event library does and why its needed.<br/><br/><a href="RedisEventLibrary.html">Redis event library</a> documents the implementation details of the event library used by Redis
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>RedisPipelining: Contents</b><br> <a href="#Request/Response protocols and RTT">Request/Response protocols and RTT</a><br> <a href="#Redis Pipelining">Redis Pipelining</a><br> <a href="#Some benchmark">Some benchmark</a><br> <a href="#Pipelining VS other multi-commands">Pipelining VS other multi-commands</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">RedisPipelining</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
<h1><a name="Request/Response protocols and RTT">Request/Response protocols and RTT</a></h1>
|
||||
Redis is a TCP server using the client-server model and what is called a <b>Request/Response</b> protocol.<br/><br/>This means that usually a request is accomplished with the following steps:
|
||||
<ul><li> The client sends a query to the server, and reads from the socket, usually in a blocking way, for the server response.</li><li> The server processes the command and sends the response back to the server.</li></ul>So for instance a four commands sequence is something like this:
|
||||
<ul><li> <b>Client:</b> INCR X</li><li> <b>Server:</b> 1</li><li> <b>Client:</b> INCR X</li><li> <b>Server:</b> 2</li><li> <b>Client:</b> INCR X</li><li> <b>Server:</b> 3</li><li> <b>Client:</b> INCR X</li><li> <b>Server:</b> 4</li></ul>Clients and Servers are connected via a networking link. Such a link can be very fast (a loopback interface) or very slow (a connection established over the internet with many hops between the two hosts). Whatever the network latency is, there is a time for the packets to travel from the client to the server, and back from the server to the client to carry the reply.<br/><br/>This time is called RTT (Round Trip Time). It is very easy to see how this can affect the performances when a client needs to perform many requests in a row (for instance adding many elements to the same list, or populating a database with many keys). For instance if the RTT time is 250 milliseconds (in the case of a very slow link over the internet), even if the server is able to process 100k requests per second, we'll be able to process at max four requests per second.<br/><br/>If the interface used is a loopback interface, the RTT is much shorter (for instance my host reports 0,044 milliseconds pinging 127.0.0.1), but it is still a lot if you need to perform many writes in a row.<br/><br/>Fortunately there is a way to improve this use cases.
|
||||
<h1><a name="Redis Pipelining">Redis Pipelining</a></h1>
|
||||
A Request/Response server can be implemented so that it is able to process new requests even if the client didn't already read the old responses. This way it is possible to send <b>multiple commands</b> to the server without waiting for the replies at all, and finally read the replies in a single step.<br/><br/>This is called pipelining, and is a technique widely in use since many decades. For instance many POP3 protocol implementations already supported this feature, dramatically speeding up the process of downloading new emails from the server.<br/><br/>Redis supports pipelining since the very early days, so whatever version you are running, you can use pipelining with Redis. This is an example using the raw netcat utility:
|
||||
<pre class="codeblock python" name="code">
|
||||
$ (echo -en "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379
|
||||
+PONG
|
||||
+PONG
|
||||
+PONG
|
||||
</pre>
|
||||
This time we are not paying the cost of RTT for every call, but just one time for the three commands.<br/><br/>To be very explicit, with pipelining the order of operations of our very first example will be the following:
|
||||
<ul><li> <b>Client:</b> INCR X</li><li> <b>Client:</b> INCR X</li><li> <b>Client:</b> INCR X</li><li> <b>Client:</b> INCR X</li><li> <b>Server:</b> 1</li><li> <b>Server:</b> 2</li><li> <b>Server:</b> 3</li><li> <b>Server:</b> 4</li></ul><b>IMPORTANT NOTE</b>: while the client sends commands using pipelining, the server will be forced to queue the replies, using memory. So if you need to send many many commands with pipelining it's better to send this commands up to a given reasonable number, for instance 10k commands, read the replies, and send again other 10k commands and so forth. The speed will be nearly the same, but the additional memory used will be at max the amount needed to queue the replies for this 10k commands.
|
||||
<h1><a name="Some benchmark">Some benchmark</a></h1>
|
||||
In the following benchmark we'll use the Redis Ruby client, supporting pipelining, to test the speed improvement due to pipelining:
|
||||
<pre class="codeblock python python" name="code">
|
||||
require 'rubygems'
|
||||
require 'redis'
|
||||
|
||||
def bench(descr)
|
||||
start = Time.now
|
||||
yield
|
||||
puts "#{descr} #{Time.now-start} seconds"
|
||||
end
|
||||
|
||||
def without_pipelining
|
||||
r = Redis.new
|
||||
10000.times {
|
||||
r.ping
|
||||
}
|
||||
end
|
||||
|
||||
def with_pipelining
|
||||
r = Redis.new
|
||||
r.pipelined {
|
||||
10000.times {
|
||||
r.ping
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
bench("without pipelining") {
|
||||
without_pipelining
|
||||
}
|
||||
bench("with pipelining") {
|
||||
with_pipelining
|
||||
}
|
||||
</pre>
|
||||
Running the above simple script will provide this figures in my Mac OS X system, running over the loopback interface, where pipelining will provide the smallest improvement as the RTT is already pretty low:
|
||||
<pre class="codeblock python python python" name="code">
|
||||
without pipelining 1.185238 seconds
|
||||
with pipelining 0.250783 seconds
|
||||
</pre>
|
||||
As you can see using pipelining we improved the transfer by a factor of five.
|
||||
<h1><a name="Pipelining VS other multi-commands">Pipelining VS other multi-commands</a></h1>
|
||||
Often we get requests about adding new commands performing multiple operations in a single pass.
|
||||
For instance there is no command to add multiple elements in a set. You need calling many times SADD.<br/><br/>With pipelining you can have performances near to an MSADD command, but at the same time we'll avoid bloating the Redis command set with too many commands. An additional advantage is that the version written using just SADD will be ready for a distributed environment (for instance Redis Cluster, that is in the process of being developed) just dropping the pipelining code.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>RedisStatus: Contents</b><br> <a href="#Redis Status Page">Redis Status Page</a><br> <a href="#How stable are the alpha previews?">How stable are the alpha previews?</a><br> <a href="#How to obtain a 2.2-alpha preview">How to obtain a 2.2-alpha preview</a><br> <a href="#ETA for Redis 2.2?">ETA for Redis 2.2?</a><br> <a href="#When will we be able to see a working version of Redis Cluster?">When will we be able to see a working version of Redis Cluster?</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">RedisStatus</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
<h1><a name="Redis Status Page">Redis Status Page</a></h1>Hello! Redis uses versions composed of three numbers separated by a dot: <b>major</b>.<b>minor</b>.<b>patchlevel</b>.<br/><br/>When the <b>minor</b> is an odd number, it is used for an unstable release, so stable releases are for instance 1.2, 2.0, and so forth.<br/><br/>This is the status of the different Redis versions currently available:<br/><br/><ul><li> 1.2 is the <b>legacy redis stable release</b>, now it is completely obsoleted by Redis 2.0. Redis 2.0 is almost completely back compatible with 1.2 so upgrading is usually not a problem. Still 1.2 is believed to be a very stable release that works well, so if you are using it in production with code that probably will not modified to use more advanced Redis features available in 2.0, it makes sense to take 1.2 running. For everything new, it's better to start with 2.0.</li></ul>
|
||||
<ul><li> 2.0 is the current <b>stable release</b>. It is better than 1.2 in more or less everything: more features, more mature code, better replication, better persistence, and so forth. It is currently what most users should use, unless they really need features that are only available into an <b>unstable</b> release.</li></ul>
|
||||
<ul><li> 2.1 is the current <b>unstable release</b>, and there are no tar.gz for this release, you need to download it from git. <b>Warning:</b> the master branch in git may work most of the time but is NOT what you should use. What's better instead is to use the 2.2-alpha tags: every time Redis 2.1.x is stable enough and the new features merged passed all the tests for a couple of weeks, and we didn't received severe bug reports from users, we tag master as 2.2-alpha <i>number</i>, where <i>number</i> is simply a progressive number. Just pick this number.</li></ul>
|
||||
<h1><a name="How stable are the alpha previews?">How stable are the alpha previews?</a></h1>
|
||||
Well it is surely ok for development, but it is not recommended for production. Still there are many users that trust Redis development process so much to use alpha releases in production, but this is up to you, we don't give any guarantee ;)<h1><a name="How to obtain a 2.2-alpha preview">How to obtain a 2.2-alpha preview</a></h1>Simply using git:
|
||||
<pre class="codeblock python" name="code">
|
||||
$ git clone git://github.com/antirez/redis.git
|
||||
Initialized empty Git repository in /tmp/redis/.git/
|
||||
...
|
||||
</pre>
|
||||
Then you can list all the branches matching 2.1-alpha with:
|
||||
<pre class="codeblock python python" name="code">
|
||||
cd redis
|
||||
$ git tag | grep 2.2-alpha
|
||||
2.2-alpha0
|
||||
2.2-alpha1
|
||||
2.2-alpha2
|
||||
</pre>
|
||||
At this point you can just use <b>git checkout <i>tagname</i></b>, substituting <i>tagname</i> with 2.2-alphaX where X is the greater progressive number you see in the listing.<h1><a name="ETA for Redis 2.2?">ETA for Redis 2.2?</a></h1>
|
||||
Redis 2.2 is planned to enter the release candidate stage before the end of the 2010.<h1><a name="When will we be able to see a working version of Redis Cluster?">When will we be able to see a working version of Redis Cluster?</a></h1>
|
||||
I'm already working at it, I mean not just designing, but writing code. In three months we should have some kind of experimental version, while in six months we should have the first release candidate.<br/><br/>Probably the first <b>stable</b> release of Redis with working cluster will be called 3.0, but I'll try to merge it into 2.2 as an experimental support if we'll be sure there is no impact in the stability of the system when clustering is not used.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>Redis_1_2_0_Changelog: Contents</b><br> <a href="#CHANGELOG for Redis 1.1.90">CHANGELOG for Redis 1.1.90</a>
|
||||
<b>Redis_1_2_0_Changelog: Contents</b><br> <a href="#What's new in Redis 1.2">What's new in Redis 1.2</a><br> <a href="#New persistence mode: Append Only File">New persistence mode: Append Only File</a><br> <a href="#New data type: sorted sets">New data type: sorted sets</a><br> <a href="#Specialized integer objects encoding">Specialized integer objects encoding</a><br> <a href="#MSET and MSETNX">MSET and MSETNX</a><br> <a href="#Better Performances">Better Performances</a><br> <a href="#Solaris Support">Solaris Support</a><br> <a href="#Support for the new generation protocol">Support for the new generation protocol</a><br> <a href="#A few new commands about already supported data types">A few new commands about already supported data types</a><br> <a href="#Bug fixing">Bug fixing</a><br> <a href="#CHANGELOG for Redis 1.1.90">CHANGELOG for Redis 1.1.90</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">Redis_1_2_0_Changelog</h1>
|
||||
@@ -26,7 +26,11 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
<h1><a name="CHANGELOG for Redis 1.1.90">CHANGELOG for Redis 1.1.90</a></h1><ul><li> 2009-09-10 in-memory specialized object encoding. (antirez)</li><li> 2009-09-17 maxmemory fixed in 64 systems for values > 4GB. (antirez)</li><li> 2009-10-07 multi-bulk protocol implemented. (antriez)</li><li> 2009-10-16 MSET and MSETNX commands implemented (antirez)</li><li> 2009-10-21 SRANDMEMBER added (antirez)</li><li> 2009-10-23 Fixed compilation in mac os x snow leopard when compiling a 32 bit binary. (antirez)</li><li> 2009-10-23 New data type: Sorted sets and Z-commands (antirez)</li><li> 2009-10-26 Solaris fixed (Alan Harder)</li><li> 2009-10-29 Fixed Issue a number of open issues (antirez)</li><li> 2009-10-30 New persistence mode: append only file (antirez)</li><li> 2009-11-01 SORT STORE option (antirez)</li><li> 2009-11-03 redis-cli now accepts a -r (repeat) switch. (antirez)</li><li> 2009-11-04 masterauth option merged (Anthony Lauzon)</li><li> 2009-11-04 redis-test is now a better Redis citizen, testing everything against DB 9 and 10 and only if this DBs are empty. (antirez)</li><li> 2009-11-10 Implemented a much better lazy expiring algorithm for EXPIRE (antirez)</li><li> 2009-11-11 RPUSHLPOP (antirez from an idea of @ezmobius)</li><li> 2009-11-12 Merge git://github.com/ianxm/redis (Can't remmber what this implements, sorry)</li><li> 2009-11-17 multi-bulk reply support for redis-bench, LRANGE speed tests (antirez)</li><li> 2009-11-17 support for writev implemented. (Stefano Barbato)</li><li> 2009-11-19 debug mode (-D) in redis-bench (antirez)</li><li> 2009-11-21 SORT GET # implemented (antirez)</li><li> 2009-11-23 ae.c made modular, with support for epoll. (antirez)</li><li> 2009-11-26 background append log rebuilding (antirez)</li><li> 2009-11-28 Added support for kqueue. (Harish Mallipeddi)</li><li> 2009-11-29 SORT support for sorted sets (antirez, thanks to @tobi for the idea)</li></ul>
|
||||
<h1><a name="What's new in Redis 1.2">What's new in Redis 1.2</a></h1><h2><a name="New persistence mode: Append Only File">New persistence mode: Append Only File</a></h2>The Append Only File is an alternative way to save your data in Redis that is fully durable! Unlike the snapshotting (default) persistence mode, where the database is saved asynchronously from time to time, the Append Only File saves every change ASAP in a text-only file that works like a journal. Redis will play back this file again at startup reloading the whole dataset back in memory. Redis Append Only File supports background Log compaction. For more info read the <a href="AppendOnlyFileHowto.html">Append Only File HOWTO</a>.<h2><a name="New data type: sorted sets">New data type: sorted sets</a></h2>Sorted sets are collections of elements (like Sets) with an associated score (in the form of a double precision floating point number). Elements in a sorted set are taken in order, so for instance to take the greatest element is an O(1) operation. Insertion and deletion is O(log(N)). Sorted sets are implemented using a dual ported data structure consisting of an hash table and a skip list. For more information please read the <a href="IntroductionToRedisDataTypes.html">Introduction To Redis Data Types</a>.<h2><a name="Specialized integer objects encoding">Specialized integer objects encoding</a></h2>Redis 1.2 will use less memory than Redis 1.0 for values in Strings, Lists or Sets elements that happen to be representable as 32 or 64 bit signed integers (it depends on your arch bits for the long C type). This is totally transparent form the point of view of the user, but will safe a lot of memory (30% less in datasets where there are many integers).<h2><a name="MSET and MSETNX">MSET and MSETNX</a></h2>That is, setting multiple keys in one command, atomically. For more information see the <a href="MsetCommand.html">MSET command</a> wiki page.<h2><a name="Better Performances">Better Performances</a></h2><ul><li> 100x times faster SAVE and BGSAVE! There was a problem in the LZF lib configuration that is now resolved. The effect is this impressive speedup. Also the saving child will no longer use 100% of CPU.</li><li> Glue output buffer and writev(). Many commands producing large outputs, like LRANGE, will now be even 10 times faster, thanks to the new output buffer gluing algorithm and the (optional) use of writev(2) syscall.</li><li> Support for epool and kqueue / kevent. 10,000 clients scalability.</li><li> Much better EXPIRE support, now it's possible to work with very large sets of keys expiring in very short time without to incur in memory problems (the new algorithm expires keys in an adaptive way, so will get more aggressive if there are a lot of expiring keys)</li></ul>
|
||||
<h2><a name="Solaris Support">Solaris Support</a></h2>Redis will now compile and work on Solaris without problems. Warning: the Solaris user base is very little, so Redis running on Solaris may not be as tested and stable as it is on Linux and Mac OS X.<h2><a name="Support for the new generation protocol">Support for the new generation protocol</a></h2><ul><li> Redis is now able to accept commands in a new fully binary safe way: with the new protocol keys are binary safe, not only values, and there is no distinction between bulk commands and inline commands. This new protocol is currently used only for MSET and MSETNX but at some point it will hopefully replace the old one. See the Multi Bulk Commands section in the <a href="ProtocolSpecification.html">Redis Protocol Specification</a> for more information.</li></ul>
|
||||
<h2><a name="A few new commands about already supported data types">A few new commands about already supported data types</a></h2><ul><li> <a href="SrandmemberCommand.html">SRANDMEMBER</a></li><li> The <a href="SortCommand.html">SortCommand</a> is now supprots the <b>STORE</b> and <b>GET #</b> forms, the first can be used to save sorted lists, sets or sorted sets into keys for caching. Check the manual page for more information about the <b>GET #</b> form.</li><li> The new <a href="RpoplpushCommand.html">RPOPLPUSH command</a> can do many interesting magics, and a few of this are documented in the wiki page of the command.</li></ul>
|
||||
<h2><a name="Bug fixing">Bug fixing</a></h2>Of course, many bugs are now fixed, and I bet, a few others introduced: this is how software works after all, so make sure to report issues in the Redis mailing list or in the Google Code issues tracker.<br/><br/>Enjoy!
|
||||
antirez<h1><a name="CHANGELOG for Redis 1.1.90">CHANGELOG for Redis 1.1.90</a></h1><ul><li> 2009-09-10 in-memory specialized object encoding. (antirez)</li><li> 2009-09-17 maxmemory fixed in 64 systems for values > 4GB. (antirez)</li><li> 2009-10-07 multi-bulk protocol implemented. (antriez)</li><li> 2009-10-16 MSET and MSETNX commands implemented (antirez)</li><li> 2009-10-21 SRANDMEMBER added (antirez)</li><li> 2009-10-23 Fixed compilation in mac os x snow leopard when compiling a 32 bit binary. (antirez)</li><li> 2009-10-23 New data type: Sorted sets and Z-commands (antirez)</li><li> 2009-10-26 Solaris fixed (Alan Harder)</li><li> 2009-10-29 Fixed Issue a number of open issues (antirez)</li><li> 2009-10-30 New persistence mode: append only file (antirez)</li><li> 2009-11-01 SORT STORE option (antirez)</li><li> 2009-11-03 redis-cli now accepts a -r (repeat) switch. (antirez)</li><li> 2009-11-04 masterauth option merged (Anthony Lauzon)</li><li> 2009-11-04 redis-test is now a better Redis citizen, testing everything against DB 9 and 10 and only if this DBs are empty. (antirez)</li><li> 2009-11-10 Implemented a much better lazy expiring algorithm for EXPIRE (antirez)</li><li> 2009-11-11 RPUSHLPOP (antirez from an idea of @ezmobius)</li><li> 2009-11-12 Merge git://github.com/ianxm/redis (Can't remmber what this implements, sorry)</li><li> 2009-11-17 multi-bulk reply support for redis-bench, LRANGE speed tests (antirez)</li><li> 2009-11-17 support for writev implemented. (Stefano Barbato)</li><li> 2009-11-19 debug mode (-D) in redis-bench (antirez)</li><li> 2009-11-21 SORT GET # implemented (antirez)</li><li> 2009-11-23 ae.c made modular, with support for epoll. (antirez)</li><li> 2009-11-26 background append log rebuilding (antirez)</li><li> 2009-11-28 Added support for kqueue. (Harish Mallipeddi)</li><li> 2009-11-29 SORT support for sorted sets (antirez, thanks to @tobi for the idea)</li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>Redis_2_0_0_Changelog: Contents</b><br> <a href="#Redis 2.0: What's new?">Redis 2.0: What's new?</a><br> <a href="#MULTI/EXEC">MULTI/EXEC</a><br> <a href="#Blocking pop">Blocking pop</a><br> <a href="#Publish/subscribe">Publish/subscribe</a><br> <a href="#Hashes">Hashes</a><br> <a href="#Virtual Memory">Virtual Memory</a><br> <a href="#Contributors">Contributors</a><br> <a href="#Special Thanks">Special Thanks</a><br> <a href="#DOWNLOAD">DOWNLOAD</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">Redis_2_0_0_Changelog</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
<h1><a name="Redis 2.0: What's new?">Redis 2.0: What's new?</a></h1>The release of Redis 2.0 marks a major milestone in Redis development. Apart from an endless list of new features, there are some major ones that deserve to be highlighted.<br/><br/>It's worth to mention that while Redis 2.0.0 just reached its first stable release, Redis 2.2.0 is near to reach feature freeze, so ... be prepared for new exiting things in very short time!<h3><a name="MULTI/EXEC">MULTI/EXEC</a></h3>
|
||||
The MULTI/EXEC family of commands were added to fulfill the need to execute multiple commands as a single atomic block.
|
||||
Because all commands inside a MULTI/EXEC block are serialized and executed sequentially, it is not possible that another
|
||||
client request is served in the middle of executing this block. All commands are executed one after the other when
|
||||
EXEC is called, which makes sure either <b>all</b> or <b>no</b> commands are executed, independent of the state of the client connection.<br/><br/>More on MULTI/EXEC:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/MultiExecCommand" target="_blank">http://code.google.com/p/redis/wiki/MultiExecCommand</a></li></ul>
|
||||
Note that WATCH, a CAS (check and set) variant of MULTI/EXEC will be available on 2.2.0 and is not part of 2.0.0.<h3><a name="Blocking pop">Blocking pop</a></h3>
|
||||
The commands BLPOP and BRPOP were added to support popping from a list in a blocking fashion. This means the client
|
||||
connection will be blocked for a certain amount of time until another client pushes an item on a list. These commands
|
||||
are frequently used in producer/consumer scenarios.<br/><br/>More on blocking pop:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/BlpopCommand" target="_blank">http://code.google.com/p/redis/wiki/BlpopCommand</a></li></ul>
|
||||
<h3><a name="Publish/subscribe">Publish/subscribe</a></h3>
|
||||
The family of publish/subscribe commands let clients publish messages onto channels and subscribe to receive all messages
|
||||
that are published on channels. Also included are commands to receive all messages for which the channel matches a given pattern.<br/><br/>More on publish/subscribe:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/PublishSubscribe" target="_blank">http://code.google.com/p/redis/wiki/PublishSubscribe</a></li><li> <a href="http://antirez.com/post/redis-weekly-update-3-publish-submit.html" target="_blank">http://antirez.com/post/redis-weekly-update-3-publish-submit.html</a></li><li> <a href="http://rediscookbook.org/pubsub_for_asynchronous_communication.html" target="_blank">http://rediscookbook.org/pubsub_for_asynchronous_communication.html</a></li></ul>
|
||||
<h3><a name="Hashes">Hashes</a></h3>
|
||||
This new datatype allows to store multiple key/value pairs on a single key. Together with the list of regular commands you
|
||||
would expect for such a datatype (HSET, HGET, HDEL, HLEN, HKEYS, ...), it is also possible to use the values <i>inside</i> a hash
|
||||
for any SORT operation.<br/><br/>More on hashes:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/HsetCommand" target="_blank">http://code.google.com/p/redis/wiki/HsetCommand</a></li><li> <a href="http://antirez.com/post/redis-weekly-update-1.html" target="_blank">http://antirez.com/post/redis-weekly-update-1.html</a></li></ul>
|
||||
<h3><a name="Virtual Memory">Virtual Memory</a></h3>
|
||||
Redis Virtual Memory allows users to grow their dataset beyond the limits of their RAM.<br/><br/>More on virtual memory:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/VirtualMemoryUserGuide" target="_blank">http://code.google.com/p/redis/wiki/VirtualMemoryUserGuide</a></li><li> <a href="http://antirez.com/post/redis-virtual-memory-story.html" target="_blank">http://antirez.com/post/redis-virtual-memory-story.html</a></li></ul>
|
||||
<h3><a name="Contributors">Contributors</a></h3><ul><li> Salvatore Sanfilippo</li><li> Pieter Noordhuis</li><li> Antonio Ognio</li><li> Alex McHale</li><li> Michel Martens</li><li> Damian Janowski</li><li> Bruno Deferrari</li><li> Ashley Martens</li><li> Derek Collison</li><li> Damian Janowski</li><li> Jeremy Zawodny</li><li> Konstantin Merenkov</li><li> Michel Martens</li><li> Sam Hendley</li></ul>
|
||||
<h3><a name="Special Thanks">Special Thanks</a></h3>
|
||||
Thanks to VMware sponsoring the work of Salvatore and Pieter, and the Redis community of users and client library developers. Redis 2.0.0 was possible only thanks to your support.<h3><a name="DOWNLOAD">DOWNLOAD</a></h3>
|
||||
You can grab Redis 2.0.0 from <a href="http://code.google.com/p/redis/downloads/list" target="_blank">Google Code</a>.<br/><br/>It is also tagged on Git.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>Redis_2_0_Whats_new: Contents</b><br> <a href="#Redis 2.0: What's new?">Redis 2.0: What's new?</a><br> <a href="#MULTI/EXEC">MULTI/EXEC</a><br> <a href="#Blocking pop">Blocking pop</a><br> <a href="#Publish/subscribe">Publish/subscribe</a><br> <a href="#Hashes">Hashes</a><br> <a href="#Virtual Memory">Virtual Memory</a><br> <a href="#Contributors">Contributors</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">Redis_2_0_Whats_new</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
<h1><a name="Redis 2.0: What's new?">Redis 2.0: What's new?</a></h1>The release of Redis 2.0 marks a major milestone in Redis development. Apart from an endless list of new features, there are some major ones that deserve to be highlighted.<h3><a name="MULTI/EXEC">MULTI/EXEC</a></h3>
|
||||
The MULTI/EXEC family of commands were added to fulfill the need to execute multiple commands as a single atomic block.
|
||||
Because all commands inside a MULTI/EXEC block are serialized and executed sequentially, it is not possible that another
|
||||
client request is served in the middle of executing this block. All commands are executed one after the other when
|
||||
EXEC is called, which makes sure either <b>all</b> or <b>no</b> commands are executed, independent of the state of the client connection.<br/><br/>More on MULTI/EXEC:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/MultiExecCommand" target="_blank">http://code.google.com/p/redis/wiki/MultiExecCommand</a></li></ul>
|
||||
<h3><a name="Blocking pop">Blocking pop</a></h3>
|
||||
The commands BLPOP and BRPOP were added to support popping from a list in a blocking fashion. This means the client
|
||||
connection will be blocked for a certain amount of time until another client pushes an item on a list. These commands
|
||||
are frequently used in producer/consumer scenarios.<br/><br/>More on blocking pop:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/BlpopCommand" target="_blank">http://code.google.com/p/redis/wiki/BlpopCommand</a></li></ul>
|
||||
<h3><a name="Publish/subscribe">Publish/subscribe</a></h3>
|
||||
The family of publish/subscribe commands let clients publish messages onto channels and subscribe to receive all messages
|
||||
that are published on channels. Also included are commands to receive all messages for which the channel matches a given pattern.<br/><br/>More on publish/subscribe:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/PublishSubscribe" target="_blank">http://code.google.com/p/redis/wiki/PublishSubscribe</a></li><li> <a href="http://antirez.com/post/redis-weekly-update-3-publish-submit.html" target="_blank">http://antirez.com/post/redis-weekly-update-3-publish-submit.html</a></li><li> <a href="http://rediscookbook.org/pubsub_for_asynchronous_communication.html" target="_blank">http://rediscookbook.org/pubsub_for_asynchronous_communication.html</a></li></ul>
|
||||
<h3><a name="Hashes">Hashes</a></h3>
|
||||
This new datatype allows to store multiple key/value pairs on a single key. Together with the list of regular commands you
|
||||
would expect for such a datatype (HSET, HGET, HDEL, HLEN, HKEYS, ...), it is also possible to use the values <i>inside</i> a hash
|
||||
for any SORT operation.<br/><br/>More on hashes:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/HsetCommand" target="_blank">http://code.google.com/p/redis/wiki/HsetCommand</a></li><li> <a href="http://antirez.com/post/redis-weekly-update-1.html" target="_blank">http://antirez.com/post/redis-weekly-update-1.html</a></li></ul>
|
||||
<h3><a name="Virtual Memory">Virtual Memory</a></h3>
|
||||
Redis Virtual Memory allows users to grow their dataset beyond the limits of their RAM.<br/><br/>More on virtual memory:
|
||||
<ul><li> <a href="http://code.google.com/p/redis/wiki/VirtualMemoryUserGuide" target="_blank">http://code.google.com/p/redis/wiki/VirtualMemoryUserGuide</a></li><li> <a href="http://antirez.com/post/redis-virtual-memory-story.html" target="_blank">http://antirez.com/post/redis-virtual-memory-story.html</a></li></ul>
|
||||
<h2><a name="Contributors">Contributors</a></h2><ul><li> Salvatore Sanfilippo</li><li> Pieter Noordhuis</li><li> Antonio Ognio</li><li> Alex McHale</li><li> Michel Martens</li><li> Damian Janowski</li><li> Bruno Deferrari</li><li> Ashley Martens</li><li> Derek Collison</li><li> Damian Janowski</li><li> Jeremy Zawodny</li><li> Konstantin Merenkov</li><li> Michel Martens</li><li> Sam Hendley</li></ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -26,12 +26,14 @@
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
<h1><a name="Redis Replication Howto">Redis Replication Howto</a></h1><h2><a name="General Information">General Information</a></h2>Redis replication is a very simple to use and configure master-slave replication that allows slave Redis servers to be exact copies of master servers. The following are some very important facts about Redis replication:<br/><br/><ul><li> A master can have multiple slaves.</li><li> Slaves are able to accept other slaves connections, so instead to connect a number of slaves against the same master it is also possible to connect some of the slaves to other slaves in a graph-alike structure.</li><li> Redis replication is non-blocking on the master side, this means that the master will continue to serve queries while one or more slaves are performing the first synchronization. Instead replication is blocking on the slave side: while the slave is performing the first synchronization it can't reply to queries.</li><li> Replications can be used both for scalability, in order to have multiple slaves for read-only queries (for example heavy <a href="SortCommand.html">SORT</a> operations can be launched against slaves), or simply for data redundancy.</li><li> It is possible to use replication to avoid the saving process on the master side: just configure your master redis.conf in order to avoid saving at all (just comment al the "save" directives), then connect a slave configured to save from time to time.</li></ul>
|
||||
<h2><a name="How Redis replication works">How Redis replication works</a></h2>In order to start the replication, or after the connection closes in order resynchronize with the master, the client connects to the master and issues the SYNC command.<br/><br/>The master starts a background saving, and at the same time starts to collect all the new commands received that had the effect to modify the dataset. When the background saving completed the master starts the transfer of the database file to the slave, that saves it on disk, and then load it in memory. At this point the master starts to send all the accumulated commands, and all the new commands received from clients, that had the effect of a dataset modification.<br/><br/>You can try it yourself via telnet. Connect to the Redis port while the server is doing some work and issue the SYNC command. You'll see a bulk transfer and then every command received by the master will be re-issued in the telnet session.<br/><br/>Slaves are able to automatically reconnect when the master <code name="code" class="python"><-></code> slave link goes down for some reason. If the master receives multiple concurrent slave synchronization requests it performs a single background saving in order to serve all them.<h2><a name="Configuration">Configuration</a></h2>To configure replication is trivial: just add the following line to the slave configuration file:
|
||||
#sidebar <a href="RedisGuides.html">RedisGuides</a>
|
||||
<h1><a name="Redis Replication Howto">Redis Replication Howto</a></h1><h2><a name="General Information">General Information</a></h2>Redis replication is a very simple to use and configure master-slave replication that allows slave Redis servers to be exact copies of master servers. The following are some very important facts about Redis replication:<br/><br/><ul><li> A master can have multiple slaves.</li><li> Slaves are able to accept other slaves connections, so instead to connect a number of slaves against the same master it is also possible to connect some of the slaves to other slaves in a graph-alike structure.</li><li> Redis replication is non-blocking on the master side, this means that the master will continue to serve queries while one or more slaves are performing the first synchronization. Instead replication is blocking on the slave side: while the slave is performing the first synchronization it can't reply to queries.</li><li> Replications can be used both for scalability, in order to have multiple slaves for read-only queries (for example heavy <a href="SortCommand.html">SORT</a> operations can be launched against slaves), or simply for data redundancy.</li><li> It is possible to use replication to avoid the saving process on the master side: just configure your master redis.conf in order to avoid saving at all (just comment al the "save" directives), then connect a slave configured to save from time to time.</li></ul>
|
||||
<h2><a name="How Redis replication works">How Redis replication works</a></h2>In order to start the replication, or after the connection closes in order resynchronize with the master, the slave connects to the master and issues the SYNC command.<br/><br/>The master starts a background saving, and at the same time starts to collect all the new commands received that had the effect to modify the dataset. When the background saving completed the master starts the transfer of the database file to the slave, that saves it on disk, and then load it in memory. At this point the master starts to send all the accumulated commands, and all the new commands received from clients that had the effect of a dataset modification, to the slave, as a stream of commands, in the same format of the Redis protocol itself.<br/><br/>You can try it yourself via telnet. Connect to the Redis port while the server is doing some work and issue the SYNC command. You'll see a bulk transfer and then every command received by the master will be re-issued in the telnet session.<br/><br/>Slaves are able to automatically reconnect when the master <code name="code" class="python"><-></code> slave link goes down for some reason. If the master receives multiple concurrent slave synchronization requests it performs a single background saving in order to serve all them.<h2><a name="Configuration">Configuration</a></h2>To configure replication is trivial: just add the following line to the slave configuration file:
|
||||
<pre class="codeblock python" name="code">
|
||||
slaveof 192.168.1.1 6379
|
||||
</pre>
|
||||
Of course you need to replace 192.168.1.1 6379 with your master ip address (or hostname) and port.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
<div class="narrow">
|
||||
#sidebar <a href="ListCommandsSidebar.html">ListCommandsSidebar</a><h3><a name="RPUSH _key_ _string_">RPUSH _key_ _string_</a></h3>
|
||||
<h3><a name="LPUSH _key_ _string_">LPUSH _key_ _string_</a></h3>
|
||||
<i>Time complexity: O(1)</i><blockquote>Add the <i>string</i> value to the head (RPUSH) or tail (LPUSH) of the liststored at <i>key</i>. If the key does not exist an empty list is created just beforethe append operation. If the key exists but is not a List an erroris returned.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code reply</a>
|
||||
<i>Time complexity: O(1)</i><blockquote>Add the <i>string</i> value to the head (LPUSH) or tail (RPUSH) of the liststored at <i>key</i>. If the key does not exist an empty list is created just beforethe append operation. If the key exists but is not a List an erroris returned.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically, the number of elements inside the list after the push operation.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="ControlCommandsSidebar.html">ControlCommandsSidebar</a><h3><a name="SAVE">SAVE</a></h3>
|
||||
<blockquote>Save the DB on disk. The server hangs while the saving is notcompleted, no connection is served in the meanwhile. An OK codeis returned when the DB was fully stored in disk.</blockquote>
|
||||
<blockquote>Save the whole dataset on disk (this means that all the databases are saved, as well as keys with an EXPIRE set (the expire is preserved). The server hangs while the saving is notcompleted, no connection is served in the meanwhile. An OK codeis returned when the DB was fully stored in disk.</blockquote>
|
||||
<blockquote>The background variant of this command is <a href="BgsaveCommand.html">BGSAVE</a> that is able to perform the saving in the background while the server continues serving other clients.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code reply</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>SetbitCommand: Contents</b><br> <a href="#SETBIT _key_ _offset_ _value_ (Redis >">SETBIT _key_ _offset_ _value_ (Redis ></a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">SetbitCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
|
||||
<h1><a name="SETBIT _key_ _offset_ _value_ (Redis >">SETBIT _key_ _offset_ _value_ (Redis ></a></h1> 2.1.8) =
|
||||
<i>Time complexity: O(1)</i><blockquote>Sets or clears the bit at <i>offset</i> in the string value stored at <i>key</i>.</blockquote>
|
||||
The bit is either set or cleared depending on <i>value</i>, which can be either 0 or 1. When <i>key</i> does not exist, a new string value is created. The string is grown to make sure it can hold a bit at <i>offset</i>. The <i>offset</i> argument is required to be greater than or equal to 0, and is limited to 2<sup>32-1 (which limits bitmaps to 512MB).
|
||||
When the string at <i>key</i> is grown, added bits are set to 0.<br/><br/><b>Warning</b>: When setting the last possible bit (<i>offset</i> equal to 2</sup>32-1) and the string value stored at <i>key</i> does not yet hold a string value, or holds a small string value, Redis needs to allocate all intermediate memory which can block the server for some time.
|
||||
On a 2010 Macbook Pro, setting bit number 2<sup>32-1 (512MB allocation) takes ~300ms,
|
||||
setting bit number 2</sup>30-1 (128MB allocation) takes ~80ms,
|
||||
setting bit number 2<sup>28-1 (32MB allocation) takes ~30ms and
|
||||
setting bit number 2</sup>26-1 (8MB allocation) takes ~8ms.
|
||||
Note that once this first allocation is done, subsequent calls to SETBIT for the same <i>key</i> will not have the allocation overhead.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically: the original bit value stored at <i>offset</i>.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>SetexCommand: Contents</b><br> <a href="#SETEX _key_ _time_ _value_">SETEX _key_ _time_ _value_</a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">SetexCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="StringCommandsSidebar.html">StringCommandsSidebar</a><h1><a name="SETEX _key_ _time_ _value_">SETEX _key_ _time_ _value_</a></h1>
|
||||
<i>Time complexity: O(1)</i><blockquote>The command is exactly equivalent to the following group of commands:</blockquote><pre class="codeblock python" name="code">
|
||||
SET _key_ _value_
|
||||
EXPIRE _key_ _time_
|
||||
</pre>
|
||||
<blockquote>The operation is atomic. An atomic <a href="SetCommand.html">SET</a>+<a href="ExpireCommand.html">EXPIRE</a> operation was already providedusing <a href="MultiExecCommand.html">MULTI/EXEC</a>, but SETEX is a faster alternative providedbecause this operation is very common when Redis is used as a Cache.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Status code reply</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html>
|
||||
<head>
|
||||
<link type="text/css" rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
|
||||
<div id='header'>
|
||||
<a href="index.html">
|
||||
<img style="border:none" alt="Redis Documentation" src="redis.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>SetrangeCommand: Contents</b><br> <a href="#SETRANGE _key_ _offset_ _value_ (Redis >">SETRANGE _key_ _offset_ _value_ (Redis ></a><br> <a href="#Examples">Examples</a><br> <a href="#Patterns">Patterns</a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">SetrangeCommand</h1>
|
||||
|
||||
<div class="summary">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="StringCommandsSidebar.html">StringCommandsSidebar</a><h1><a name="SETRANGE _key_ _offset_ _value_ (Redis >">SETRANGE _key_ _offset_ _value_ (Redis ></a></h1> 2.1.8) =
|
||||
<i>Time complexity: O(1) not counting the time taken to copy the new string in place, as usually this string is small so the amoritzed time is O(1). Otheriwse O(M) with M being the length of the value argument</i><blockquote>Overwrites part of a string at <i>key</i> starting at the specified offset,for all the length of <i>value</i>.If the offset is over the old length of the string, the string is paddedwith zero bytes until needed. Non existing keys are considered likealready containing an empty string.</blockquote>
|
||||
<h2><a name="Examples">Examples</a></h2>First example, basic usage setting a range.<br/><br/><pre class="codeblock python" name="code">
|
||||
redis> set foo "Hello World"
|
||||
OK
|
||||
redis> setrange foo 6 "Redis"
|
||||
(integer) 11
|
||||
redis> get foo
|
||||
"Hello Redis"
|
||||
</pre>Example of the zero padding behavior.<br/><br/><pre class="codeblock python python" name="code">
|
||||
redis> del foo
|
||||
(integer) 1
|
||||
redis> setrange foo 10 bar
|
||||
(integer) 13
|
||||
redis> get foo
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00bar"
|
||||
</pre>Note that the maximum offset that you can set is 536870911 as Redis Strings are limited to 512 megabytes. You can still create longer arrays of values using multiple keys.<br/><br/><b>Warning</b>: When setting the last possible byte and the string value stored at <i>key</i> does not yet hold a string value, or holds a small string value, Redis needs to allocate all intermediate memory which can block the server for some time.
|
||||
On a 2010 Macbook Pro, setting byte number 536870911 (512MB allocation) takes ~300ms,
|
||||
setting byte number 134217728 (128MB allocation) takes ~80ms,
|
||||
setting bit number 33554432 (32MB allocation) takes ~30ms and
|
||||
setting bit number 8388608 (8MB allocation) takes ~8ms.
|
||||
Note that once this first allocation is done, subsequent calls to SETRANGE for the same <i>key</i> will not have the allocation overhead.<h2><a name="Patterns">Patterns</a></h2>Thanks to SETRANGE and the analogous GETRANGE command you can use Redis strings as a linear array of memory with O(1) random access. This is a very fast and efficient storage in many real world use cases.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically: the length of the string after it was modified by the command.
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -27,9 +27,8 @@
|
||||
|
||||
<div class="narrow">
|
||||
#sidebar <a href="SetCommandsSidebar.html">SetCommandsSidebar</a><h1><a name="SMEMBERS _key_">SMEMBERS _key_</a></h1>
|
||||
<i>Time complexity O(N)</i><blockquote>Return all the members (elements) of the set value stored at <i>key</i>. Thisis just syntax glue for <a href="SintersectCommand.html">SINTERSECT</a>.</blockquote>
|
||||
<i>Time complexity O(N)</i><blockquote>Return all the members (elements) of the set value stored at <i>key</i>. Thisis just syntax glue for <a href="SintersectCommand.html">SINTER</a>.</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
+13
-6
@@ -16,7 +16,7 @@
|
||||
<div id="pagecontent">
|
||||
<div class="index">
|
||||
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
|
||||
<b>SortCommand: Contents</b><br> <a href="#Sorting by external keys">Sorting by external keys</a><br> <a href="#Retrieving external keys">Retrieving external keys</a><br> <a href="#Storing the result of a SORT operation">Storing the result of a SORT operation</a><br> <a href="#Return value">Return value</a>
|
||||
<b>SortCommand: Contents</b><br> <a href="#Sorting by external keys">Sorting by external keys</a><br> <a href="#Not Sorting at all">Not Sorting at all</a><br> <a href="#Retrieving external keys">Retrieving external keys</a><br> <a href="#Storing the result of a SORT operation">Storing the result of a SORT operation</a><br> <a href="#SORT and Hashes: BY and GET by hash field">SORT and Hashes: BY and GET by hash field</a><br> <a href="#Return value">Return value</a>
|
||||
</div>
|
||||
|
||||
<h1 class="wikiname">SortCommand</h1>
|
||||
@@ -46,19 +46,26 @@ SORT mylist LIMIT 0 10 ALPHA DESC
|
||||
SORT mylist BY weight_*
|
||||
</pre><blockquote>the <b>BY</b> option takes a pattern (<code name="code" class="python">weight_*</code> in our example) that is usedin order to generate the key names of the weights used for sorting.Weight key names are obtained substituting the first occurrence of <code name="code" class="python">*</code>with the actual value of the elements on the list (1,2,3,4 in our example).</blockquote>
|
||||
<blockquote>Our previous example will return just the sorted IDs. Often it isneeded to get the actual objects sorted (object_1, ..., object_4 in theexample). We can do it with the following command:</blockquote>
|
||||
<h2><a name="Retrieving external keys">Retrieving external keys</a></h2><pre class="codeblock python python python python python python" name="code">
|
||||
<h2><a name="Not Sorting at all">Not Sorting at all</a></h2><pre class="codeblock python python python python python python" name="code">
|
||||
SORT mylist BY nosort
|
||||
</pre><blockquote>also the <b>BY</b> option can take a "nosort" specifier. This is useful if you want to retrieve a external key (using GET, read below) but you don't want the sorting overhead.</blockquote>
|
||||
<h2><a name="Retrieving external keys">Retrieving external keys</a></h2><pre class="codeblock python python python python python python python" name="code">
|
||||
SORT mylist BY weight_* GET object_*
|
||||
</pre><blockquote>Note that <b>GET</b> can be used multiple times in order to get more keys forevery element of the original List, Set or Sorted Set sorted.</blockquote>
|
||||
<blockquote>Since Redis >= 1.1 it's possible to also GET the list elements itselfusing the special # pattern:</blockquote>
|
||||
<pre class="codeblock python python python python python python python" name="code">
|
||||
<pre class="codeblock python python python python python python python python" name="code">
|
||||
SORT mylist BY weight_* GET object_* GET #
|
||||
</pre><h2><a name="Storing the result of a SORT operation">Storing the result of a SORT operation</a></h2><blockquote>By default SORT returns the sorted elements as its return value.Using the <b>STORE</b> option instead to return the elements SORT willstore this elements as a <a href="Lists.html">Redis List</a> in the specified key.An example:</blockquote>
|
||||
<pre class="codeblock python python python python python python python python" name="code">
|
||||
<pre class="codeblock python python python python python python python python python" name="code">
|
||||
SORT mylist BY weight_* STORE resultkey
|
||||
</pre><blockquote>An interesting pattern using SORT ... STORE consists in associatingan <a href="ExpireCommand.html">EXPIRE</a> timeout to the resulting key so that inapplications where the result of a sort operation can be cached forsome time other clients will use the cached list instead to call SORTfor every request. When the key will timeout an updated version ofthe cache can be created using SORT ... STORE again.</blockquote>
|
||||
<blockquote>Note that implementing this pattern it is important to avoid that multipleclients will try to rebuild the cached version of the cacheat the same time, so some form of locking should be implemented(for instance using <a href="SetnxCommand.html">SETNX</a>).</blockquote>
|
||||
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically a list of sorted elements.
|
||||
|
||||
<h2><a name="SORT and Hashes: BY and GET by hash field">SORT and Hashes: BY and GET by hash field</a></h2>
|
||||
<blockquote>It's possible to use BY and GET options against Hash fields using the following syntax:</blockquote><pre class="codeblock python python python python python python python python python python" name="code">
|
||||
SORT mylist BY weight_*->fieldname
|
||||
SORT mylist GET object_*->fieldname
|
||||
</pre>
|
||||
<blockquote>The two chars string -> is used in order to signal the name of the Hash field. The key is substituted as documented above with sort BY and GET against normal keys, and the Hash stored at the resulting key is accessed in order to retrieve the specified field.</blockquote><h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically a list of sorted elements.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user