一、Redis 事务与锁机制

  

  1.Redis的基础事务

  在Redis中开启事务的命令是 multi 命令, 而执行事务的命令是 exec 命令。multi 到 exec 命令之间的 Redis 命令将采取进入队列的形式,直至 exec 命令的出现,才会一次性发送队列里的命令去执行,而在执行这些命令的时候其他客户端就不能再插入任何命令了。

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key1 value1
QUEUED
127.0.0.1:6379> get key1
QUEUED
127.0.0.1:6379> exec
1) OK
2) "value1"

  如果回滚事务,可以使用 discard 命令取消事务中所有命令,使事务中的方法不会被执行了。

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key1 value1
QUEUED
127.0.0.1:6379> get key1
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI

  2.在Spring中使用Redis事务

  SessionCallback接口可以保证所有的命令都是通过同一个 Redis 连接进行操作的。

    public static void testTransaction() {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);SessionCallback callBack = (SessionCallback) (RedisOperations ops) -> {ops.multi();    // 开启事务ops.boundValueOps("key1").set("value1");// 注意由于命令只是进入队列,而没有被执行,所以此处采用get命令返回值为nullString value = (String) ops.boundValueOps("key1").get();System.out.println("value = " + value);// list保存之前进入队列的所有命令的结果List list = ops.exec();// 执行事务// 事务结束后,取出value1value = (String) redisTemplate.opsForValue().get("key1");return value;};// 执行Redis命令String value = (String) redisTemplate.execute(callBack);System.out.println(value);}返回结果:value = nullvalue1

  3.Redis 事务回滚的两种情况

  • 命令格式正确,而数据类型错误时,仅回滚数据类型错误的那条命令

    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set key1 value1
    QUEUED
    127.0.0.1:6379> set key2 value2
    QUEUED
    127.0.0.1:6379> incr key1
    QUEUED
    127.0.0.1:6379> del key2
    QUEUED
    127.0.0.1:6379> exec
    1) OK
    2) OK
    3) (error) ERR value is not an integer or out of range
    4) (integer) 1

  • 命令格式不正确时,直接回滚所有命令
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set key1 value1
    QUEUED
    127.0.0.1:6379> incr
    (error) ERR wrong number of arguments for 'incr' command
    127.0.0.1:6379> set key2 value2
    QUEUED
    127.0.0.1:6379> exec
    (error) EXECABORT Transaction discarded because of previous errors.
    127.0.0.1:6379> get key1
    (nil)
    127.0.0.1:6379> get key2
    (nil)

4.使用 watch 命令监控事务

  在 Redis 中使用 watch 命令可以决定事务是执行还是回滚。一般而言,可以在 multi 命令之前使用 watch 命令监控某些键值对,然后使用 multi 命令开启事务。当Redis 使用 exec 命令执行事务的时候,它首先会去对比被 watch 命令所监控的键值对,如果没有发生变化,那么它会执行事务队列中的命令,提交事务;如果发生变化,那么它不会执行任何事务中的命令,而去事务回滚。无论事务是否回滚,Redis都会去取消执行事务前的watch命令:

  Redis 参考了多线程中使用的 CAS (比较与交换,Compare and Swap)去执行的。当一条线程去执行某些业务逻辑,但是这些业务逻辑操作的数据可能被其他线程共享了,这样会引发多线程中数据不一致的情况。为了克服这个问题,在线程开始时读取这些多线程共享的数据,并将其保存到当前线程的副本中,称为旧值(old value),watch命令就是这样的一个功能。然后,开启线程业务逻辑,由multi命令提供这个功能。在执行更新即exec命令前,比较当前线程副本保存的旧值和当前线程共享的值是否一致,如果不一致,那么该数据已经被其他线程操作过,此次更新失败,事务回滚;否则就认为它没有被其他线程操作过,就执行对应的业务逻辑。在数据高并发环境的操作中,把这样的机制称为乐观锁。

  CAS 会产生 ABA 问题,而 Redis不会产生 ABA 问题。

  

  产生ABA问题的根本原因就是仅仅只记录一个旧值,解决办法例如有Hibernate中对缓存的持久对象加入字段 version 值,每操作一次持久对象,就令version++,可以解决ABA问题。

  Redis多个事务完全可以在非阻塞的多线程环境下并发执行,而且Redis的机制是不会产生ABA问题的。

  例如:成功提交事务的例子:

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> watch key1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key2 value2
QUEUED
127.0.0.1:6379> get key2
QUEUED
127.0.0.1:6379> exec
1) OK
2) "value2"
127.0.0.1:6379> get key1
"value1"
127.0.0.1:6379> get key2
"value2"

  二、流水线(PipeLined)

  当需要使用队列批量执行一系列的命令时,Pipelined可以提高系统性能。

  Redis执行读/写速度非常快,但是系统的瓶颈往往是在网络通信中的时延:

  

  为了解决这个问题,可以使用Redis的流水线,Redis的流水线是一种通信协议:

  1.使用 Java API

    public static void testJedisPipeline() {JedisPool pool = getPool();Jedis jedis = pool.getResource();long start = System.currentTimeMillis();// 开启流水线Pipeline pipeline = jedis.pipelined();// 测试十万条读/写操作for (int i = 0; i < 100000; i++) {int j = i + 1;pipeline.set("pipeline_key_" + j, "pipeline_value_" + j);pipeline.get("pipeline_key_" + j);}// pipeline.sync();// 只执行同步,不返回结果// pipeline.syncAndReturnAll(); 将返回执行过的命令放入List列表中List result = pipeline.syncAndReturnAll();long end = System.currentTimeMillis();System.err.println("耗时: " + (end - start) + "毫秒");}返回:耗时: 499毫秒

  2.在Spring中使用流水线

    public static void testPipeline() {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);SessionCallback callBack = (SessionCallback) (RedisOperations ops) -> {for (int i = 0; i < 100000; i++) {int j = i + 1;ops.boundValueOps("pipeline_key_" + j).set("pipeline_value_" + j);ops.boundValueOps("pipeline_key_" + j).get();}return null;};long start = System.currentTimeMillis();// 执行 Redis 的流水线命令List resultList = redisTemplate.executePipelined(callBack);long end = System.currentTimeMillis();System.out.println(end - start);}返回:511

  三、发布订阅

  当使用银行卡消费的时候,银行往往会通过微信、短信或者邮件通知用户这笔交易的信息,这便是一种发布/订阅模式。

  发布订阅模式首先需要消息源,也就是要有消息发布出来,比如银行通知。首先是银行的记账系统收到了交易的命令,交易成功后,就会把消息发送出来,订阅者就可以接收到这个消息。

  发布订阅需要两点:

  • 要有发送的消息渠道,让记账系统能够发送消息
  • 要有订阅者订阅这个渠道的消息

  1.Redis中的发布订阅

  客户端1监听一个叫做chat的频道:SUBSCRIBE chat

  客户端2在chat上发送消息:publish chat “hello”

  此时,客户端1就收到了客户端2发送到chat上面的消息:“hello”

  2.在Spring环境下使用发布订阅

  (1)Spring中,接收者需要实现MessageListener接口,并实现其中的onMessage方法

package com.ssm.chapter19.redis.listener;import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.core.RedisTemplate;public class RedisMessageListener implements MessageListener {private RedisTemplate redisTemplate;public RedisTemplate getRedisTemplate() {return redisTemplate;}public void setRedisTemplate(RedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}@Overridepublic void onMessage(Message message, byte[] bytes) {// 获取消息byte[] body = message.getBody();// 使用值反序列化其转换String msgBody = (String) getRedisTemplate().getValueSerializer().deserialize(body);System.err.println(msgBody);// 获取频道byte[] channel = message.getChannel();// 使用字符串序列化器转换String channelStr = (String) getRedisTemplate().getStringSerializer().deserialize(channel);System.err.println(channelStr);// 将频道名称的字节数组转换成字符串String bytesStr = new String(bytes);System.err.println(bytesStr);}
}

  (2)在Spring 配置文件中配置这个类

    <bean id="redisMsgListener"class="com.ssm.chapter19.redis.listener.RedisMessageListener"><property name="redisTemplate" ref="redisTemplate" /></bean>

  (3)还需要配置监听容器RedisMessageListenerContainer可以用于监听Redis的发布订阅消息,指定频道名称为chat

  当消息通过chat发送时,就会使用redisMsgListener进行处理。

    <bean id="topicContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer"destroy-method="destroy"><!--Redis连接工厂 --><property name="connectionFactory" ref="connectionFactory" /><!--连接池,这里只要线程池生存,才能继续监听 --><property name="taskExecutor"><beanclass="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler"><property name="poolSize" value="2" /></bean></property><!--消息监听Map --><property name="messageListeners"><map><!--配置监听者,key-ref和bean id定义一致 --><entry key-ref="redisMsgListener"><!--监听类 --><bean class="org.springframework.data.redis.listener.ChannelTopic"><constructor-arg value="chat" /></bean></entry></map></property></bean>

  (4)测试:执行下面的方法后,控制台输出结果为:

    public static void testPubSub() {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);String channel = "chat";redisTemplate.convertAndSend(channel, "I am lazy!!");}

  控制台输出结果为:

I am lazy!!
chat
chat

  四、超时命令

  对于Redis而言,del命令可以删除一些键值对,所以Redis比Java虚拟机更加灵活,与此同时,当内存运行空间满了之后,还可以按照回收机制自动回收一些键值对。  

  但是,当垃圾进行回收的时候,又有可能执行回收而引发系统停顿,因此选择适当的回收机制和时间将有利于系统性能的提高。

  Redis可以给对应的键值设置超时:

  

  1.在Redis中测试超时命令

127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> get key1
"value1"
127.0.0.1:6379> ttl key1
(integer) -1
127.0.0.1:6379> expire key1 120
(integer) 1
127.0.0.1:6379> ttl key1
(integer) 112
127.0.0.1:6379> ttl key1
(integer) 110
127.0.0.1:6379> ttl key1
(integer) 110
127.0.0.1:6379> ttl key1
(integer) 108
127.0.0.1:6379> ttl key1
(integer) 65
127.0.0.1:6379> persist key1
(integer) 1
127.0.0.1:6379> persist key1
(integer) 0
127.0.0.1:6379> ttl key1
(integer) -1

  2.在Spring中使用超时命令

    public static void testExpire() {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);redisTemplate.execute((RedisOperations ops) -> {ops.boundValueOps("key1").set("value1");String keyValue = (String) ops.boundValueOps("key1").get();Long expSecond = ops.getExpire("key1");System.err.println(expSecond);boolean b = false;b = ops.expire("key1", 120L, TimeUnit.SECONDS);b = ops.persist("key1");Long l = 0L;l = ops.getExpire("key1");Long now = System.currentTimeMillis();Date date = new Date();date.setTime(now + 120000);ops.expireAt("key", date);return null;});}

  3.问题:如果key超时了,Redis 会回收key的存储空间吗?

  不会。Redis的key超时不会被其自动回收,它只会标识哪些键值对超时了。

  这样做的好处是,如果一个很大的键值对超时,必须一个列表或者哈希结构,存在数以百万个元素,要对其回收需要很长时间。如果采用超时回收,则可能产生系统停顿。坏处也很明显,就是超时的键值对会浪费比较多的空间。

  Redis 提供两种方式回收超时键值对:

  • 定时回收:在确定的某个时间触发一段代码,回收超时的键值对。定时回收可以完全回收那些超时的键值对,但是缺点也很明显,如果这些键值对比较多,则Redis需要运行较长的时间,从而导致停顿。一般会选择在没有业务发生的时刻触发Redis的定时回收,以便清理超时的键值对。
  • 惰性回收:当一个超时的键,被再次用get命令访问时,将触发Redis将其从内存中情况。优势是可以指定回收超时的键值对,缺点是要执行一个get操作,或者在某些时候,难以判断哪些键值对已经超时。

  五、使用Lua语言

  Redis 命令的计算能力不算很强大,而使用Lua语言则在很大程度上弥补了 Redis 这个不足。只是在 Redis中,执行 Lua 语言是原子性的,也就是Redis执行Lua的时候是不会被中断的。

  Redis支持阆中方式运行Lua,一种是直接输入;另外一种是将 Lua 语言编写成文件。

  1.执行输入Lua程序代码

eval lua-script key-num [key1 key2 key3 ...] [value1 value2 value3 ...]
eval:执行Lua语言的命令
Lua-script:代表Lua语言脚本
key-num:代表参数中有多少个key,没有为0
[key1 key2 key3 ...]:以key为参数
[value1 value2 value3 ...]:将这些参数传递给Lua

  例如:

127.0.0.1:6379> eval "return 'hello java'" 0
"hello java"
127.0.0.1:6379> eval "redis.call('set',KEYS[1],ARGV[1])" 1 lua-key lua-value
(nil)
127.0.0.1:6379> get lua-key
"lua-value"

  有时可能需要多次执行同一段脚本,在Redis中脚本会通过SHA-1签名算法加密脚本,返回一个标识字符串,可以通过这个字符串执行加密后的脚本。这样的好处是,如果脚本很长,从客户端传输可能需要很长的时间,那么使用标识字符串,则只需要传递32位字符串即可,这样可以提高传输的效率,从而提高性能。

127.0.0.1:6379> script load "redis.call('set',KEYS[1],ARGV[1])"
"7cfb4342127e7ab3d63ac05e0d3615fd50b45b06"
127.0.0.1:6379> evalsha 7cfb4342127e7ab3d63ac05e0d3615fd50b45b06 1 sha-key sha-value
(nil)
127.0.0.1:6379> get sha-key
"sha-value"

  2.在Spring 中使用 Lua 脚本存储简单字符串

    public static void testLuaScript() {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);Jedis jedis = (Jedis) redisTemplate.getConnectionFactory().getConnection().getNativeConnection();// 执行简单的脚本String helloJava = (String) jedis.eval("return 'hello java'");System.out.println(helloJava);// 执行带参数的脚本jedis.eval("redis.call('set',KEYS[1], ARGV[1])", 1, "lua-key", "lua-value");String luaKey = (String) jedis.get("lua-key");System.out.println(luaKey);// 缓存脚本,返回SHA1签名标识字符串String sha1 = jedis.scriptLoad("redis.call('set',KEYS[1], ARGV[1])");// 执行脚本jedis.evalsha(sha1, 1, new String[] { "sha-key", "sha-val" });// 获取执行脚本后的数据String shaVal = jedis.get("sha-key");System.out.println(shaVal);// �关闭连接
        jedis.close();}

  3.在Spring中使用Lua脚本存储对象

  Spring 提供了 RedisScript 接口和一个实现类 DefaultRedisScript ,通过这个对象就可以通过Lua脚本操作对象。

    public static void testRedisScript() {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);// 定义默认脚本封装类DefaultRedisScript<Role> redisScript = new DefaultRedisScript<Role>();// 设置脚本redisScript.setScriptText("redis.call('set', KEYS[1], ARGV[1])  return redis.call('get', KEYS[1])");// 定义操作的key列表List<String> keyList = new ArrayList<String>();keyList.add("role1");// 需要序列化保存和读取的对象Role role = new Role();role.setId(1L);role.setRoleName("role_name_1");role.setNote("note_1");// 获得标识字符串String sha1 = redisScript.getSha1();System.out.println(sha1);// 设置返回结果类型为Role类型redisScript.setResultType(Role.class);// 使用JdkSerializationRedisSerializer进行序列化JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();// 执行脚本// DefaultRedisScript接口对象,参数序列化器,结果序列化器,key列表,参数列表Role obj = (Role) redisTemplate.execute(redisScript, serializer, serializer, keyList, role);// 打印返回结果
        System.out.println(obj.getId());}

  返回:

731429de653665577edb661a6741c4083e103b77
1

  4.执行Lua文件

  新建Lua文件test.lua

redis.call('set', KEYS[1], ARGV[1])
redis.call('set', KEYS[2], ARGV[2])
local n1 = tonumber(redis.call('get', KEYS[1]))
local n2 = tonumber(redis.call('get', KEYS[2]))
if n1 > n2 thenreturn 1
end
if n1 == n2 thenreturn 0
end
if n1 < n2 then return 2
end

  在命令行输入 redis-cli --eval test.lua key1 key2 , 2 4 会返回:2

  在 Spring 中,只能通过evalsha的方式执行Lua文件,例如:

    public static void testLuaFile() {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");RedisTemplate redisTemplate = applicationContext.getBean(RedisTemplate.class);// 读入文件流File file = new File("D:\\BaiduNetdiskDownload\\ssm\\Chapter19\\src\\test.lua");byte[] bytes = getFileToByte(file);Jedis jedis = (Jedis) redisTemplate.getConnectionFactory().getConnection().getNativeConnection();// 发送二进制文件给Redis服务器,得到标识数组byte[] sha1 = jedis.scriptLoad(bytes);// 传递参数,执行Lua文件Object obj = jedis.evalsha(sha1, 2, "key1".getBytes(), "key2".getBytes(), "2".getBytes(), "4".getBytes());System.out.println(obj);}/*** 把文件转化为二进制数组* * @param file*            * @return 二进制数组*/public static byte[] getFileToByte(File file) {byte[] by = new byte[(int) file.length()];try {InputStream is = new FileInputStream(file);ByteArrayOutputStream bytestream = new ByteArrayOutputStream();byte[] bb = new byte[2048];int ch;ch = is.read(bb);while (ch != -1) {bytestream.write(bb, 0, ch);ch = is.read(bb);}by = bytestream.toByteArray();} catch (Exception ex) {ex.printStackTrace();}return by;}

  六、在Spring中使用 Redis 哨兵模式

  1.配置文件

  主服务器192.168.11.128,两个从服务器192.168.11.129、192.168.11.130。

  然后在三台机器上分别启动哨兵服务。

<?xml version='1.0' encoding='UTF-8' ?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd"><!--配置Redis连接池 --><bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"><property name="maxIdle" value="50" />  <!--最大空闲数 --><property name="maxTotal" value="100" />  <!--最大连接数 --><property name="maxWaitMillis" value="3000" />  <!--最大等待时间3s --></bean><!--jdk序列化器,可保存对象 --><bean id="jdkSerializationRedisSerializer"class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /><!--String序列化器 --><bean id="stringRedisSerializer"class="org.springframework.data.redis.serializer.StringRedisSerializer" /><!--哨兵配置 --><bean id="sentinelConfig"class="org.springframework.data.redis.connection.RedisSentinelConfiguration"><!--服务名称 --><property name="master"><bean class="org.springframework.data.redis.connection.RedisNode"><property name="name" value="mymaster" /></bean></property><!--哨兵服务IP和端口 --><property name="sentinels"><set><bean class="org.springframework.data.redis.connection.RedisNode"><constructor-arg name="host" value="192.168.11.128" /><constructor-arg name="port" value="26379" /></bean><bean class="org.springframework.data.redis.connection.RedisNode"><constructor-arg name="host" value="192.168.11.129" /><constructor-arg name="port" value="26379" /></bean><bean class="org.springframework.data.redis.connection.RedisNode"><constructor-arg name="host" value="192.168.11.130" /><constructor-arg name="port" value="26379" /></bean></set></property></bean><!--连接池设置 --><bean id="connectionFactory"class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"><constructor-arg name="sentinelConfig" ref="sentinelConfig" /><constructor-arg name="poolConfig" ref="poolConfig" /><property name="password" value="abcdefg" /></bean><!--配置RedisTemplate --><bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"><property name="connectionFactory" ref="connectionFactory" /><property name="keySerializer" ref="stringRedisSerializer" /><property name="defaultSerializer" ref="stringRedisSerializer" /><property name="valueSerializer" ref="jdkSerializationRedisSerializer" /></bean>
</beans>

  2.验证哨兵模式

  关闭192.168.11.128主服务其上的Redis服务,然后3分钟后,哨兵会进行投票切换新的主机,然后执行下面的方法。

    public static void testSpringSentinel() {ApplicationContext ctx = new ClassPathXmlApplicationContext("com/ssm/chapter20/config/spring-cfg.xml");RedisTemplate redisTemplate = ctx.getBean(RedisTemplate.class);String retVal = (String) redisTemplate.execute((RedisOperations ops) -> {ops.boundValueOps("mykey").set("myvalue");String value = (String) ops.boundValueOps("mykey").get();return value;});System.out.println(retVal);}

  

  七、Spring 缓存机制和Redis的结合

  

转载于:https://www.cnblogs.com/BigJunOba/p/9792421.html

Redis(十五)Redis 的一些常用技术(Spring 环境下)相关推荐

  1. 深入剖析Redis系列(五) - Redis数据结构之字符串

    前言 字符串类型 是 Redis 最基础的数据结构.字符串类型 的值实际可以是 字符串(简单 和 复杂 的字符串,例如 JSON.XML).数字(整数.浮点数),甚至是 二进制(图片.音频.视频),但 ...

  2. 第十五回(二):文会内战平分秋色 树下阔论使坏心焦【林大帅作品】

    时光飞逝,转眼便至周五,亦是莉莉一行人歌咏大赛之日.当日兴化府,为那重阳妈祖祭典造势,这歌咏便是其一.故莉莉恩师率音乐生参战,因每年会试,那榜上题名的,文会总不如擢英,哲理.刘学监便剑走偏方,每每于宫 ...

  3. 五天完成项目-《基于Linux环境下的Cortex A53的手势识别智能家居》-第四天

    音视频播放功能 <智能家居系列> 一.音频播放 1.Linux下的音频播放实现 2.实现步骤 二.语音播报信息 三.视频播放 1.素材准备 2.实现步骤 四.项目框架搭建 <智能家居 ...

  4. 五天完成项目-《基于Linux环境下的Cortex A53的手势识别智能家居》-第五天

    视频监控和家电控制 <智能家居系列> 一.视频监控基础 1.摄像头 2.v4l2 3.Linux下的摄像头使用流程 4.烧写程序到开发板 5.运行程序 (先插上摄像头) 二.视频监控功能 ...

  5. 第十五届智能车竞赛技术报告-成电金秋-AI电磁

    01引言 1.1 大赛介绍 全国大学生"恩智浦"杯智能汽车竞赛是以"立足培养.重在参与.鼓励 探索.追求卓越"为宗旨,鼓励创新的一项科技竞赛活动.今年首次新增了 ...

  6. redis学习(五) redis实现购物车

    <redis实战> 第二章 每个用户的购物车都是一个散列,这个散列存储了商品ID与商品订购数量之间的映射 对商品数量的验证由web应用程序负责,我们要做的就是在商品订购的数量出现变化时,对 ...

  7. csrediscore访问redis集群_搭建文档 | centos7.6环境下redis5.0.8集群搭建

    " 本文作者:墨篱弦 " 一.做基础配置 a) 首先创建3个空文件 mkdir -p /server/redis_cluster/7001/datamkdir -p /server ...

  8. linux目录隐藏技术,Linux环境下的高级隐藏技术

    摘要:本文深入分析了Linux环境下文件.进程及模块的高级隐藏技术,其中包括:Linux可卸载模块编程技术.修改内存映象直接对系统调用进行修改技术,通过虚拟文件系统proc隐藏特定进程的技术. 隐藏技 ...

  9. 云计算环境下的服务器虚拟化技术,云计算环境下的虚拟化能力

    随着云计算技术的飞速发展,它改写了现代企业的竞争法则,也带来了全新的网络生态环境.以企业用户为例自身的业务能突破了本地物理设备的限制,可以部署在远端数据中心且运行在不同服务器上,可以大幅降低企业业务运 ...

最新文章

  1. 正面反击 Google、FB 等巨头,万维网之父携 Solid 归来
  2. 我国光纤速率创世界记录
  3. 服务器运行环境怎么搭建,服务器运行环境怎么快速搭建?
  4. 修改Jupyter的工作空间
  5. saiku 连接 MySQL_Saiku连接mysql数据库(二)
  6. struts2文件上传,下载
  7. wifisetting.java_Wifi 笔记 | 启动流程
  8. 《引爆点》读书笔记(一)
  9. (转)OL2中设置鼠标的样式
  10. python运维系统开发_老男孩Python运维系统开发教程
  11. 数据库和SQL基本知识点
  12. 重庆对口高职计算机类专业答案2020,2020年重庆市高职分类考试教育类专业招生试题及答案...
  13. latex里图片大小如何调整_如何使Latex中的图片放大依然清晰
  14. 测试网络连通性的PING命令
  15. win7 装显卡驱动后只显示桌面背景 - 解法办法一例
  16. 我参加了资金盘培训,get了这些“知识点”
  17. 用Random类做猜数字游戏
  18. 关联性——灰色关联分析
  19. [历朝通俗演义-蔡东藩]
  20. 消费品行业会员营销的5大策略及建议

热门文章

  1. vue 自定义指令_vue 自定义指令
  2. AcWing 789. 数的范围
  3. 西门子1500和300哪个贵_西门子S7-1500凭什么秒杀S7-300-400?
  4. java退出函数_如何更新线上的 Java 服务器代码
  5. mysql oracle 左链接_mysql左连接与oracle(+)使用对照
  6. html语言中 头元素的标记是,HTML元信息标记
  7. 什么是java socket_java 网络编程,Socket编程
  8. 跟着开源项目学因果推断——whynot(十四)
  9. dotnet core 文档链接
  10. 基于c语言中调试工具的用法汇总(不包含gdb)【转】