Spring Boot 技术探索

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can "just run".

25、Spring Boot之Memcache的使用

平台环境:

名称

版本号

Mac OS X

10.15

JDK

1.8.0_201

Apache Maven

3.6.0

IntelliJ IDEA

2019.2 (Ultimate Edition)

Spring Boot

2.1.9.RELEASE

memcached

1.5.18

 

  什么是Memcache?

  Memcache 是一个自由和开放源代码、高性能、分配的内存对象缓存系统。简单来说,Memcache 是一个高性能的分布式内存对象的 key-value 缓存系统,用于加速动态 Web 应用程序,减轻数据库负载,现在也有很多人将它作为内存式数据库在使用。

 

  不适应场景

  • 缓存对象不能大于 1 MB
  • key 的长度大于 250 字符
  • Memcache 未提供任何安全策略
  • 不支持持久化

 

Memcache服务端的安装

Centos环境

yum install -y memcached

 

Mac环境

1、查看安装信息

brew info memcached

 

2、安装

brew install memcached

 

3、启动

方式1
brew services start memcached

方式2
/usr/local/bin/memcached  -b  -p 11211 -m 150 -u root >> /tmp/memcached.log  &

启动参数可以配置,常用的命令选项如下:
* m 内存
* c 最大链接数
* p 端口
* u 用户
* t 线程数

 

4、查看运行状态

ps -ef|grep memcached

 

5、测试连接

telnet localhost 11211

 

到此,服务端就安装配置好了。


 

Memcache客户端

客户端有很多,这次介绍XMemcached

pom.xml加入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.googlecode.xmemcached/xmemcached -->
    <dependency>
        <groupId>com.googlecode.xmemcached</groupId>
        <artifactId>xmemcached</artifactId>
        <version>2.4.6</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

 

application.properties加入配置

# 单个Memcached配置
memcached.servers=127.0.0.1:11211
# 连接池
memcached.poolSize=10
# 操作超时时间
memcached.opTimeout=6000

 

新建配置类XMemcachedProperties

package com.example.demo.config;


import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;


@Component
@ConfigurationProperties(prefix = "memcached")
public class XMemcachedProperties {
    private String servers;
    private int poolSize;
    private long opTimeout;


    public String getServers() {
        return servers;
    }


    public void setServers(String servers) {
        this.servers = servers;
    }


    public int getPoolSize() {
        return poolSize;
    }


    public void setPoolSize(int poolSize) {
        this.poolSize = poolSize;
    }


    public long getOpTimeout() {
        return opTimeout;
    }


    public void setOpTimeout(long opTimeout) {
        this.opTimeout = opTimeout;
    }
}

 

加载配置类MemcachedBuilder

package com.example.demo.config;


import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.MemcachedClientBuilder;
import net.rubyeye.xmemcached.XMemcachedClientBuilder;
import net.rubyeye.xmemcached.command.BinaryCommandFactory;
import net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator;
import net.rubyeye.xmemcached.transcoders.SerializingTranscoder;
import net.rubyeye.xmemcached.utils.AddrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


import javax.annotation.Resource;
import java.io.IOException;


@Configuration
public class MemcachedBuilder
{
    protected static Logger logger = LoggerFactory.getLogger(MemcachedBuilder.class);
    @Resource
    private XMemcachedProperties xMemcachedProperties;


    @Bean
    public MemcachedClient getMemcachedClient()
    {
        MemcachedClient memcachedClient = null;
        try
        {
            MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses(xMemcachedProperties.getServers()));
            // 设置集群权重
            // MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses(memcacheConfig.getServers()),new int[]{3,2,1});
            // 开启/关闭failure模式
            builder.setFailureMode(false);
            // 多 Memcached 时启用 一致性哈希 算法
            builder.setSessionLocator(new KetamaMemcachedSessionLocator());
            // 多 Memcached 时启用 选举散列 算法
            // builder.setSessionLocator(new ElectionMemcachedSessionLocator());
            builder.setConnectionPoolSize(xMemcachedProperties.getPoolSize());
            //操作超时时间
            builder.setOpTimeout(xMemcachedProperties.getOpTimeout());
            // 进行数据压缩,大于1KB时进行压缩
            builder.getTranscoder().setCompressionThreshold(1024);
            // 使用序列化传输编码
            builder.setTranscoder(new SerializingTranscoder());
            // use binary protocol
            builder.setCommandFactory(new BinaryCommandFactory());
            memcachedClient = builder.build();
        } catch (IOException e)
        {
            logger.error("inint MemcachedClient failed ", e);
        }
        return memcachedClient;
    }
}

 

到此,客户端就配置好了。


 

接下来,测试

新建测试类MemcachedTests

package com.example.demo;


import net.rubyeye.xmemcached.CASOperation;
import net.rubyeye.xmemcached.Counter;
import net.rubyeye.xmemcached.GetsResponse;
import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.transcoders.StringTranscoder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;


import java.net.InetSocketAddress;
import java.util.Map;


@RunWith(SpringRunner.class)
@SpringBootTest
public class MemcachedTests
{


    @Autowired
    private MemcachedClient memcachedClient;


    @Test
    public void testGetSet() throws Exception
    {
        // 第一个是存储的 key 名称
        // 第二个是过期时间(单位秒),超过这个时间,memcached 将这个数据替换出去,0 表示永久存储(默认是一个月)
        // 第三个参数就是实际存储的数据,可以是任意的 Java 可序列化类型。
        memcachedClient.set("hello", 0, "Hello,xmemcached");


        // 获取数据
        String value = memcachedClient.get("hello");
        System.out.println("hello=" + value);


        // 删除数据
        memcachedClient.delete("hello");
        value = memcachedClient.get("hello");
        System.out.println("hello=" + value);
    }


    @Test
    public void testMore() throws Exception
    {
        if (!memcachedClient.set("hello", 0, "world"))
        {
            System.err.println("set error");
        }


        // add与set的作用类似。区别是set会把key对应的value值替换掉,而add则先判断当前key下是否有值,如果有则不做任何操作,如果没有则存入
        if (!memcachedClient.add("hello", 0, "dennis"))
        {
            System.err.println("Add error,key is existed");
        }


        // 替换
        if (!memcachedClient.replace("hello", 0, "dennis"))
        {
            System.err.println("replace error");
        }


        // 在已有值的后面追加值
        memcachedClient.append("hello", " good");


        // 在已有值的前面追加值
        memcachedClient.prepend("hello", "hello ");


        String name = memcachedClient.get("hello");
        System.out.println(name);


        memcachedClient.deleteWithNoReply("hello");
    }


    @Test
    public void testIncrDecr() throws Exception
    {
        memcachedClient.delete("Incr");
        memcachedClient.delete("Decr");


        // 原子递增变量数值
        // 第一个参数指定递增的key名称
        // 第二个参数指定递增的幅度大小
        // 第三个参数指定当key不存在的情况下的初始值
        System.out.println(memcachedClient.incr("Incr", 6, 12));


        // 默认第三个参数是0
        System.out.println(memcachedClient.incr("Incr", 3));
        System.out.println(memcachedClient.incr("Incr", 2));


        // 原子递减变量数值
        // 参数规则与incr相同
        System.out.println(memcachedClient.decr("Decr", 1, 6));
        System.out.println(memcachedClient.decr("Decr", 2));
    }


    @Test
    public void testCounter() throws Exception
    {
        // 获取一个计数器实例
        // 第一个参数key
        // 第二个参数是当值不存在时的初始值
        Counter counter = memcachedClient.getCounter("counter1", 10);
        System.out.println("counter=" + counter.get());


        // 原子增加1
        long c1 = counter.incrementAndGet();
        System.out.println("counter=" + c1);


        // 原子减去1
        long c2 = counter.decrementAndGet();
        System.out.println("counter=" + c2);


        // 增加传入的值,如果是负数则相当于减法
        long c3 = counter.addAndGet(-6);
        System.out.println("counter=" + c3);
    }


    @Test
    public void testCas() throws Exception
    {
        // 设置初始值
        memcachedClient.set("hello", 0, 100);


        // 注意这里是gets不是get
        // gets方法获取一个GetsResponse,此对象包装了存储的数据和CAS值,该CAS值可以用来原子更新操作
        GetsResponse<Integer> result = memcachedClient.gets("hello");
        System.out.println("hello value " + result.getValue());


        // 传统方法(需要显式地提前调用gets获取CAS值)
        // 尝试将cas的值更新为200
        long cas = result.getCas();
        if (!memcachedClient.cas("hello", 0, 222, cas))
        {
            System.err.println("cas error");
        }
        System.out.println("hello value " + memcachedClient.get("hello"));


        // 新方法(无需显式地调用gets获取CAS值,且支持自定义"尝试更新次数")
        memcachedClient.cas("hello", 0, new CASOperation<Integer>()
        {
            // 设置"尝试更新次数"
            public int getMaxTries()
            {
                return 1;
            }


            // 自动获取当前的GetsResponse来更新数据
            // 如果更新成功,则意味着这个方法返回的值已存储成功。其这个方法的两个参数则是最新的GetsResponse中的两个值。
            public Integer getNewValue(long currentCAS, Integer currentValue)
            {
                return 999;
            }
        });
        System.out.println("hello value " + memcachedClient.get("hello"));
    }


    @Test
    public void testTouch() throws Exception
    {
        // 设置操作等待的超时时间2秒(默认5秒)
        memcachedClient.set("Touch", 2, "Touch Value");


        // 设置缓存过期时间(2秒)
        // 如果报错Unknow command TOUCH,则服务端版本过低,需要更新
        memcachedClient.touch("Touch", 2);
        Thread.sleep(3000);


        // 设置操作等待的超时时间1秒(默认5秒)
        String value = memcachedClient.get("Touch", 1000);
        System.out.println("Touch=" + value);
    }


    @Test
    public void testStat() throws Exception
    {
        // 查看统计信息
        Map<InetSocketAddress, Map<String, String>> result = memcachedClient.getStats();
        System.out.println("Stats=" + result.toString());


        // 根据传入需要统计的项目名称,返回统计信息
        Map<InetSocketAddress, Map<String, String>> items = memcachedClient.getStatsByItem("items");
        System.out.println("items=" + items.toString());
    }


}

 

其他知识点补充:

Memcached 集群

Memcached 的分布是通过客户端实现的,客户端根据 key 的哈希值得到将要存储的 Memcached 节点,并将对应的 value 存储到相应的节点。

XMemcached 同样支持客户端的分布策略,默认分布的策略是按照 key 的哈希值模以连接数得到的余数,对应的连接就是将要存储的节点。如果使用默认的分布策略,不需要做任何配置或者编程。

XMemcached 同样支持一致性哈希(Consistent Hash),通过编程设置:

MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses("server1:11211 server2:11211 server3:11211"));
builder.setSessionLocator(new KetamaMemcachedSessionLocator());
MemcachedClient client=builder.build();

XMemcached 还提供了额外的一种哈希算法——选举散列,在某些场景下可以替代一致性哈希:

MemcachedClientBuilder builder = new XMemcachedClientBuilder(
                                    AddrUtil.getAddresses("server1:11211 server2:11211 server3:11211"));
builder.setSessionLocator(new ElectionMemcachedSessionLocator());
MemcachedClient mc = builder.build();

在集群的状态下可以给每个服务设置不同的权重:

MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses("localhost:12000 localhost:12001"),new int[]{1,3});
MemcachedClient memcachedClient=builder.build();

 

SASL 验证

Memcached 1.4.3 开始支持 SASL 验证客户端,在服务器配置启用 SASL 之后,客户端需要通过授权验证才可以跟 Memcached 继续交互,否则将被拒绝请求,XMemcached 1.2.5 开始支持这个特性。假设 Memcached 设置了 SASL 验证,典型地使用 CRAM-MD 5 或者 PLAIN 的文本用户名和密码的验证机制,假设用户名为 cacheuser,密码为 123456,那么编程的方式如下:

MemcachedClientBuilder builder = new XMemcachedClientBuilder(
                AddrUtil.getAddresses("localhost:11211"));
builder.addAuthInfo(AddrUtil.getOneAddress("localhost:11211"), AuthInfo
                .typical("cacheuser", "123456"));
// Must use binary protocol
builder.setCommandFactory(new BinaryCommandFactory());
MemcachedClient client=builder.build();

请注意,授权验证仅支持二进制协议。

 

 

参考资料:

https://gitbook.cn/gitchat/column/5b86228ce15aa17d68b5b55a/topic/5c05ffbfb4075a37edf18a7f

 

Bootstrap Thumbnail Second
MySQL

MySQL is the world's most popular open source database.

GO

Bootstrap Thumbnail Third
算法基础

本书介绍了什么是计算机算法,如何描述它们,以及如何来评估它们。

GO