首页 文章 精选 留言 我的

精选列表

搜索[高DPI],共10007篇文章
优秀的个人博客,低调大师

Canal高可用架构部署

一、前言 canal 是阿里的一款开源项目,纯 Java 开发。基于数据库增量日志解析,提供增量数据订阅&消费,目前主要支持了 MySQL(也支持 mariaDB)。 canal 模拟 mysql slave 的交互协议,伪装自己为 mysql slave,向 mysql master发送 dump 协议; mysql master 收到 dump 请求,开始推送binary log给 slave(也就是canal) canal 解析 binary log对象(原始为byte流)。 总体架构: 二、部署准备 下载地址: https://github.com/alibaba/canal/releases 分别下载:canal.admin、canal.deployer、canal.adapter PS:只有1.1.5以上版本才支持es7.x 其他依赖: JDK1.8 MySQL:用于canal-admin存储配置和节点等相关数据 Zookeeper 三、HA机制 整个 HA 机制的控制主要是依赖了zookeeper的两个特性:watcher、EPHEMERAL节点。canal的 HA 机制实现分为两部分,canal server 和 canal client分别有对应的实现。 canal server实现流程如下: canal server 要启动某个 canal instance 时都先向 zookeeper 进行一次尝试启动判断 (实现:创建 EPHEMERAL 节点,谁创建成功就允许谁启动); 创建 zookeeper 节点成功后,对应的 canal server 就启动对应的 canal instance,没有创建成功的 canal instance 就会处于 standby 状态; 一旦 zookeeper 发现 canal server A 创建的节点消失后,立即通知其他的 canal server 再次进行步骤1的操作,重新选出一个 canal server 启动instance; canal client 每次进行connect时,会首先向 zookeeper 询问当前是谁启动了canal instance,然后和其建立链接,一旦链接不可用,会重新尝试connect。 PS: 为了减少对mysql dump的请求,不同server上的instance要求同一时间只能有一个处于running,其他的处于standby状态。 canal client实现流程 canal client 的方式和 canal server 方式类似,也是利用 zookeeper 的抢占EPHEMERAL 节点的方式进行控制 为了保证有序性,一份 instance 同一时间只能由一个 canal client 进行get/ack/rollback操作,否则客户端接收无法保证有序。 四、集群部署 4.1. MySQL准备 4.1.1. 开启binlog MySQL的 my.cnf 中配置如下 [mysqld] log-bin=mysql-bin # 开启 binlog binlog-format=ROW # 选择 ROW 模式 server_id=1 # 配置 MySQL replaction 需要定义,不要和 canal 的 slaveId 重复 注意:如果订阅的是mysql的从库,需求增加配置让从库日志也写到binlog里面 log_slave_updates=1 可以通过在 mysql 终端中执行以下命令判断配置是否生效: show variables like 'log_bin'; show variables like 'binlog_format'; 4.1.2. 授权账号权限 授权 canal 链接 MySQL 账号具有作为 MySQL slave 的权限, 如果已有账户可直接 grant: CREATE USER canal IDENTIFIED BY 'canal'; GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%'; FLUSH PRIVILEGES; 4.2. 部署canal-admin 4.2.1. 作用 通过图形化界面管理配置参数。 动态启停 Server 和 Instance 查看日志信息 4.2.2. 执行数据库脚本 执行 conf 目录下载的 canal_manager.sql 脚步,初始化所需的库表。 初始化SQL脚本里会默认创建canal_manager的数据库,建议使用root等有超级权限的账号进行初始化 4.2.3. 配置修改 执行 vim conf/application.yml server: port: 8089 spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 spring.datasource: address: 127.0.0.1:3306 database: canal_manager username: canal password: canal driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://${spring.datasource.address}/${spring.datasource.database}?useUnicode=true&characterEncoding=UTF-8&useSSL=false hikari: maximum-pool-size: 30 minimum-idle: 1 canal: adminUser: admin adminPasswd: admin 修改 address、database、username、password 四个参数 4.2.4. 启停命令 启动 sh bin/startup.sh 停止 sh bin/stop.sh 4.2.5. 使用 通过 http://127.0.0.1:8089/ 访问,默认密码:admin/123456 4.2.5.1. 创建集群 配置 集群名称 与 ZK地址 配置 主配置,该配置为集群内的所有Server实例共享的 主要修改以下配置: canal.zkServers 配置zookeeper集群地址 canal.instance.global.spring.xml 改为classpath:spring/default-instance.xml 4.2.5.2. 创建Server 配置项: 所属集群,可以选择为单机 或者 集群。一般单机Server的模式主要用于一次性的任务或者测试任务 Server名称,唯一即可,方便自己记忆 Server Ip,机器ip admin端口,canal 1.1.4版本新增的能力,会在canal-server上提供远程管理操作,默认值11110 tcp端口,canal提供netty数据订阅服务的端口 metric端口, promethues的exporter监控数据端口 (未来会对接监控) 多台Server关联同一个集群即可形成主备HA架构 4.2.5.3. 创建Instance 每个 Instance 关联一个同步的数据源,如果有多个数据源需要同步则需要创建多个 实例 先填写实例名 选择刚刚创建的集群 载入模板配置 主要修改以下配置: canal.instance.master.address 配置要同步的数据库地址 canal.instance.dbUsername 数据库用户名(需同步权限) canal.instance.dbPassword 数据库密码 canal.instance.filter.regex mysql 数据解析关注的表,Perl正则表达式.多个正则之间以逗号(,)分隔,转义符需要双斜杠(\) canal.instance.filter.regex常见例子: 所有表:.* or .\.. canal schema下所有表: canal\..* canal下的以canal打头的表:canal\.canal.* canal schema下的一张表:canal.test1 多个规则组合使用:canal\..*,mysql.test1,mysql.test2 (逗号分隔) 注意:此过滤条件只针对row模式的数据有效(ps. mixed/statement因为不解析sql,所以无法准确提取tableName进行过滤) 4.3. 部署canal-deployer 4.3.1. 作用 伪装成 MySQL 的从库,同步主库的binlog日志。 解析并结构化 binary log 对象。 4.3.2. 修改配置 执行 vim conf/canal_local.properties 修改配置项 canal.admin.manager 为canal-admin的地址 4.3.3. 启停命令 使用 local 配置启动 bin/startup.sh local 停止 bin/stop.sh 4.4. 部署canal-adapter 4.4.1. 作用 对接上游消息,包括kafka、rocketmq、canal-server 实现mysql数据的增量同步 实现mysql数据的全量同步 下游写入支持mysql、es、hbase等 4.4.2. 修改配置 注意:目前 adapter 是支持动态配置的,也就是说修改配置文件后无需重启,任务会自动刷新配置! (1) 修改application.yml 执行 vim conf/application.yml 修改consumerProperties、srcDataSources、canalAdapters的配置 canal.conf: mode: tcp # kafka rocketMQ # canal client的模式: tcp kafka rocketMQ flatMessage: true # 扁平message开关, 是否以json字符串形式投递数据, 仅在kafka/rocketMQ模式下有效 syncBatchSize: 1000 # 每次同步的批数量 retries: 0 # 重试次数, -1为无限重试 timeout: # 同步超时时间, 单位毫秒 consumerProperties: canal.tcp.server.host: # 对应单机模式下的canal canal.tcp.zookeeper.hosts: 127.0.0.1:2181 # 对应集群模式下的zk地址, 如果配置了canal.tcp.server.host, 则以canal.tcp.server.host为准 canal.tcp.batch.size: 500 # tcp每次拉取消息的数量 srcDataSources: # 源数据库 defaultDS: # 自定义名称 url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true # jdbc url username: root # jdbc 账号 password: 121212 # jdbc 密码 canalAdapters: # 适配器列表 - instance: example # canal 实例名或者 MQ topic 名 groups: # 分组列表 - groupId: g1 # 分组id, 如果是MQ模式将用到该值 outerAdapters: # 分组内适配器列表 - name: es7 # es7适配器 mode: rest # transport or rest hosts: 127.0.0.1:9200 # es地址 security.auth: test:123456 # 访问es的认证信息,如没有则不需要填 cluster.name: my-es # 集群名称,transport模式必需配置 ...... 一份数据可以被多个group同时消费, 多个group之间会是一个并行执行, 一个group内部是一个串行执行多个outerAdapters, 比如例子中logger和hbase 目前client adapter数据订阅的方式支持两种,直连canal server 或者 订阅kafka/RocketMQ的消息 (2) conf/es7目录下新增映射配置文件 adapter将会自动加载 conf/es7 下的所有 .yml 结尾的配置文件 新增表映射的配置文件,如 sys_user.yml 内容如下: dataSourceKey: defaultDS destination: example groupId: g1 esMapping: _index: sys_user _id: id upsert: true sql: "select id, username, , case when sex = 0 then '男' else '女' end sex , case when is_del = 0 then '否' else '是' end isdel from sys_user" etlCondition: "where update_time>={}" commitBatch: 3000 dataSourceKey 配置 application.yml 里 srcDataSources 的值 destination 配置 canal.deployer 的 Instance 名 groupId 配置 application.yml 里 canalAdapters.groups 的值 _index 配置索引名 _id 配置主键对应的字段 upsert 是否更新 sql 映射sql etlCondition etl 的条件参数,全量同步时可以使用 commitBatch 提交批大小 sql映射支持多表关联自由组合, 但是有一定的限制: 主表不能为子查询语句 只能使用left outer join即最左表一定要是主表 关联从表如果是子查询不能有多张表 主sql中不能有where查询条件(从表子查询中可以有where条件但是不推荐, 可能会造成数据同步的不一致, 比如修改了where条件中的字段内容) 关联条件只允许主外键的'='操作不能出现其他常量判断比如: on a.role_id=b.id and b.statues=1 关联条件必须要有一个字段出现在主查询语句中比如: on a.role_id=b.id 其中的 a.role_id 或者 b.id 必须出现在主select语句中 Elastic Search的mapping 属性与sql的查询值将一一对应(不支持 select *), 比如: select a.id as _id, a.name, a.email as _email from user, 其中name将映射到es mapping的name field, _email将 映射到mapping的_email field, 这里以别名(如果有别名)作为最终的映射字段. 这里的_id可以填写到配置文件的 _id: _id映射 4.4.3. 启停命令 启动 bin/startup.sh 关闭 bin/stop.sh 4.5. 遗留问题 目前使用的 1.1.5-SNAPSHOT 版本由于还不是发布版,发现 canal-adapter 的集群部署有个bug,配置 zookeeper 地址后启动会出现以下异常: java.lang.LinkageError: loader constraint violation: when resolving method "com.alibaba.otter.canal.common.zookeeper.ZkClientx.create(Ljava/lang/String;Ljava/lang/Object;Lorg/apache/zookeeper/CreateMode;)Ljava/lang/String;" the class loader (instance of com/alibaba/otter/canal/connector/core/spi/URLClassExtensionLoader) of the current class, com/alibaba/otter/canal/client/impl/running/ClientRunningMonitor, and the class loader (instance of sun/misc/Launcher$AppClassLoader) for the method's defining class, org/I0Itec/zkclient/ZkClient, have different Class objects for the type org/apache/zookeeper/CreateMode used in the signature at com.alibaba.otter.canal.client.impl.running.ClientRunningMonitor.initRunning(ClientRunningMonitor.java:122) [connector.tcp-1.1.5-SNAPSHOT-jar-with-dependencies.jar:na] at com.alibaba.otter.canal.client.impl.running.ClientRunningMonitor.start(ClientRunningMonitor.java:93) [connector.tcp-1.1.5-SNAPSHOT-jar-with-dependencies.jar:na] at com.alibaba.otter.canal.client.impl.SimpleCanalConnector.connect(SimpleCanalConnector.java:108) [connector.tcp-1.1.5-SNAPSHOT-jar-with-dependencies.jar:na] at com.alibaba.otter.canal.client.impl.ClusterCanalConnector.connect(ClusterCanalConnector.java:64) [connector.tcp-1.1.5-SNAPSHOT-jar-with-dependencies.jar:na] at com.alibaba.otter.canal.connector.tcp.consumer.CanalTCPConsumer.connect(CanalTCPConsumer.java:59) [connector.tcp-1.1.5-SNAPSHOT-jar-with-dependencies.jar:na] 有以下3个解决思路: adapter暂时使用单实例模式,等待官方解决问题。 自行修复bug 使用 MQ 模式(adapter则无需注册到zookeeper了) 该 BUG 已修复:https://github.com/zlt2000/canal 五、监控 canal 默认已通过 11112 端口暴露同步相关的 metrics 信息,只需通过集成 prometheus 与 grafana 即可实现实时监控同步情况,效果图如下: 指标 简述 Basic Canal instance 基本信息。 Network bandwith 网络带宽。包含inbound(canal server读取binlog的网络带宽)和outbound(canal server返回给canal client的网络带宽)。 Delay Canal server与master延时;store 的put, get, ack操作对应的延时。 Blocking sink线程blocking占比;dump线程blocking占比(仅parallel mode)。 TPS(events) Canal instance消费所有binlog事件的TPS, 以MySQL binlog events为单位计算。 TPS(transaction) Canal instance 处理binlog的TPS,以MySQL transaction为单位计算。 TPS(tableRows) 分别对应store的put, get, ack操作针对数据表变更行的TPS。 Client requests Canal client请求server的请求数统计,结果按请求类型分类(比如get/ack/sub/rollback等)。 Client QPS client发送请求的QPS,按GET与CLIENTACK分类统计。 Empty packets Canal client请求server返回空结果的统计。 Response time Canal client请求server的响应时间统计。 Store remain events Canal instance ringbuffer中堆积的events数量。 Store remain mem Canal instance ringbuffer中堆积的events内存使用量。 六、总结 准备MySQL 开启binlog(row模式) 准备同步权限的用户 创建canal-admin的库表 准备zookeeper 部署canal-admin 创建集群 创建server:关联集群 创建Instance:关联集群,并配置源库信息 启动canal-deployer 关联canal-admin 启动canal-adapter 关联zookeeper 配置源库信息 关联Instance 配置目标库信息(es) 新增映射配置文件 扫码关注有惊喜!

优秀的个人博客,低调大师

高并发服务设计—缓存

1 缓存回收策略 1.1 基于空间 即设置缓存的存储空间,如设置为10MB,当达到存储空间时,按照一定的策略移除数据。 1.2 基于容量 基于容量指缓存设置了最大大小,当缓存的条目超过最大大小,则按照一定的策略将旧数据移除。 1.3 基于时间 TTL(Time To Live):存活期,即缓存数据从缓存中创建时间开始直到它到期的一个时间段(不管在这个时间段内有没有访问都将过期)。 TTI(Time To Idle):空闲期,即缓存数据多久没被访问过将从缓存中移除的时间。 1.4 基于Java对象引用 软引用:如果一个对象是软引用,那么当JVM堆内存不足时,垃圾回收器可以回收这些对象。软引用适合用来做缓存,从而当JVM堆内存不足时,可以回收这些对象腾出一些空间供强引用对象使用,从而避免OOM。 弱引用:当垃圾回收器回收内存时,如果发现弱引用,则将立即回收它。相对于软引用有更短的生命周期。 注意:弱引用/软引用对象只有当没有其他强引用对象引用它时,垃圾回收时才回收该引用。即如果有一个对象(不是弱引用/软引用)引用了弱引用/软引用对象,那么垃圾回收是不会回收该引用对象。 1.5 回收算法 使用基于空间和基于容量的缓存会使用一定的策略移除旧数据,常见的如下: FIFO(Fisrt In Fisrt Out):先进先出算法,即先进入缓存的先被移除。 LRU(Least Recently Used):最近最少使用算法,使用时间距离现在最久的数据被移除。 LFU(Least Frequently Used):最不常用算法,一定时间段内使用次数(频率)最少的数据被移除。 实际应用中基于LRU的缓存较多,如Guava Cache、EhCache支持LRU。 2 Java缓存类型 2.1 堆缓存 使用Java堆内存来存储对象。可以使用Guava Cache、Ehcache 3.x、MapDB实现。 优点:使用堆缓存的好处是没有序列化/反序列化,是最快的缓存; 缺点:很明显,当缓存的数据量很大时, GC暂停时间会变长,存储容量受限于堆空间大小;一般通过软引用/弱引用来存储缓存对象,即当堆内存不足时,可以强制回收这部分内存释放堆内存空间。一般使用堆缓存存储较热的数据。 2.2 堆外缓存 即缓存数据存储在堆外内存。可以使用Ehcache 3.x、MapDB实现。 优点:可以减少GC暂停时间(堆对象转移到堆外,GC扫描和移动的对象变少了),可以支持更大的缓存空间(只受机器内存大小限制,不受堆空间的影响)。 缺点:读取数据时需要序列化/反序列化,会比堆缓存慢很多。 2.3 磁盘缓存 即缓存数据的存储在磁盘上。当JVM重启时数据还是在的。而堆缓存/堆外缓存重启时数据会丢失,需要重新加载。可以使用Ehcache 3.x、MapDB实现。 2.4 分布式缓存 在多JVM实例的情况时,进程内缓存和磁盘缓存会存在两个问题:1.单机容量问题; 2.数据一致性问题(既然数据允许缓存,则表示允许一定时间内的不一致,因此可以设置缓存数据的过期时间来定期更新数据); 3.缓存不命中时,需要回源到DB/服务查询变多:每个实例在缓存不命中情况下都会回源到DB加载数据,因此,多实例后DB整体的访问量就变多了。解决办法可以使用如一致性哈希分片算法来解决。因此,这些情况可以考虑使用分布式缓存来解决。可以使用ehcache-clustered(配合Terracotta server)实现Java进程间分布式缓存。当然也可以使用如Redis实现分布式缓存。 两种模式如下: 单机时:存储最热的数据到堆缓存,相对热的数据到堆外缓存,不热的数据存到磁盘缓存。 集群时:存储最热的数据到堆缓存,相对热的数据到堆外缓存,全量数据存到分布式缓存。 3 Java缓存实现 3.1 堆缓存 3.1.1 Guava Cache实现 Guava Cache只提供堆缓存,小巧灵活,性能最好,如果只使用堆缓存,那么使用它就够了。 CachemyCache= CacheBuilder.newBuilder() .concurrencyLevel(4) .expireAfterWrite(10,TimeUnit.SECONDS) .maximumSize(10000) .build(); 然后可以通过put、getIfPresent 来读写缓存。CacheBuilder有几类参数:缓存回收策略、并发设置等。 3.1.1.1 缓存回收策略/基于容量 maximumSize:设置缓存的容量,当超出maximumSize时,按照LRU进行缓存回收。 3.1.1.2 缓存回收策略/基于时间 expireAfterWrite:设置TTL,缓存数据在给定的时间内没有写(创建/覆盖)时,则被回收,即定期的会回收缓存数据。 expireAfterAccess:设置TTI,缓存数据在给定的时间内没有读/写时,则被回收。每次访问时,都会更新它的TTI,从而如果该缓存是非常热的数据,则将一直不过期,可能会导致脏数据存在很长时间(因此,建议设置expireAfterWrite)。 3.1.1.3 缓存回收策略/基于Java对象引用 weakKeys/weakValues:设置弱引用缓存。softValues:设置软引用缓存。 3.1.1.4 缓存回收策略/主动失效 invalidate(Object key)/invalidateAll(Iterablekeys)/invalidateAll():主动失效某些缓存数据。 什么时候触发失效呢? Guava Cache不会在缓存数据失效时立即触发回收操作(如果要这么做,则需要有额外的线程来进行清理),是在PUT时会主动进行一次清理缓存,当然读者也可以根据实际业务通过自己设计线程来调用cleanUp方法进行清理。 3.1.1.5 并发级别 concurrencyLevel:Guava Cache重写了ConcurrentHashMap,concurrencyLevel用来设置Segment数量,concurrencyLevel越大并发能力越强。 3.1.1.6 统计命中率 recordStats:启动记录统计信息,比如命中率等 3.1.2 EhCache 3.x实现 CacheManagercacheManager=CacheManagerBuilder.newCacheManagerBuilder().build(true); CacheConfigurationBuildercacheConfig=CacheConfigurationBuilder.newCacheConfigurationBuilder( String.class, String.class, ResourcePoolsBuilder.newResourcePoolsBuilder() .heap(100,EntryUnit.ENTRIES)) .withDispatcherConcurrency(4) .withExpiry(Expirations.timeToLiveExpiration(Duration.of(10,TimeUnit.SECONDS))); CachemyCache=cacheManager.createCache("myCache",cacheConfig); CacheManager在JVM关闭时请调用CacheManager.close()方法。 可以通过PUT、GET来读写缓存。CacheConfigurationBuilder也有几类参数:缓存回收策略、并发设置、统计命中率等。 3.1.2.1 缓存回收策略/基于容量 heap(100, EntryUnit.ENTRIES):设置缓存的条目数量,当超出此数量时按照LRU进行缓存回收。 3.1.2.2 缓存回收策略/基于空间 heap(100, MemoryUnit.MB):设置缓存的内存空间,当超出此空间时按照LRU进行缓存回收。另外,应该设置withSizeOfMaxObjectGraph(2):统计对象大小时对象图遍历深度和withSizeOfMaxObjectSize(1, MemoryUnit.KB):可缓存的最大对象大小。 3.1.2.3 缓存回收策略/基于时间 withExpiry(Expirations.timeToLiveExpiration(Duration.of(10,TimeUnit.SECONDS))):设置TTL,没有TTI。withExpiry(Expirations.timeToIdleExpiration(Duration.of(10,TimeUnit.SECONDS))):同时设置TTL和TTI,且TTL和TTI值一样。 3.1.2.4 缓存回收策略/主动失效 remove(K key)/ removeAll(Set keys)/clear():主动失效某些缓存数据。什么时候触发失效呢?EhCache使用了类似于Guava Cache同样的机制。 3.1.2.5 并发级别 目前还没有提供API来设置,EhCache内部使用ConcurrentHashMap作为缓存存储,默认并发级别16。withDispatcherConcurrency是用来设置事件分发时的并发级别。 3.1.3 MapDB 3.x 实现 HTreeMapmyCache= DBMaker.heapDB().concurrencyScale(16).make().hashMap("myCache") .expireMaxSize(10000) .expireAfterCreate(10,TimeUnit.SECONDS) .expireAfterUpdate(10,TimeUnit.SECONDS) .expireAfterGet(10,TimeUnit.SECONDS) .create();1234567 然后可以通过PUT、GET来读写缓存。其有几类参数:缓存回收策略、并发设置、统计命中率等。 3.1.3.1 缓存回收策略/基于容量 expireMaxSize:设置缓存的容量,当超出expireMaxSize时,按照LRU进行缓存回收。 3.1.3.2 缓存回收策略/基于时间 expireAfterCreate/expireAfterUpdate:设置TTL,缓存数据在给定的时间内没有写(创建/覆盖)时,则被回收。即定期的会回收缓存数据。 expireAfterGet:设置TTI, 缓存数据在给定的时间内没有读/写时,则被回收。每次访问时都会更新它的TTI,从而如果该缓存是非常热的数据,则将一直不过期,可能会导致脏数据存在很长的时间(因此,建议要设置expireAfterCreate/expireAfterUpdate)。 3.1.3.3 缓存回收策略/主动失效 remove(Object key) /clear():主动失效某些缓存数据。什么时候触发失效呢?MapDB默认使用类似于Guava Cache的机制。不过,也支持可以通过如下配置使用线程池定期进行缓存失效。 expireExecutor(scheduledExecutorService) expireExecutorPeriod(3000) 3.1.3.4 并发级别 concurrencyScale:类似于Guava Cache配置。 还可以使用DBMaker.memoryDB()创建堆缓存,它将数据序列化并存储到1MB大小的byte[]数组中,从而减少垃圾回收的影响。 3.2 堆外缓存 3.2.1 EhCache 3.x实现 CacheConfigurationBuildercacheConfig=CacheConfigurationBuilder.newCacheConfigurationBuilder( String.class, String.class, ResourcePoolsBuilder.newResourcePoolsBuilder() .offheap(100,MemoryUnit.MB)) .withDispatcherConcurrency(4) .withExpiry(Expirations.timeToLiveExpiration(Duration.of(10,TimeUnit.SECONDS))) .withSizeOfMaxObjectGraph(3) .withSizeOfMaxObjectSize(1,MemoryUnit.KB); 堆外缓存不支持基于容量的缓存过期策略。 3.2.2 MapDB 3.x实现 HTreeMapmyCache= DBMaker.memoryDirectDB().concurrencyScale(16).make().hashMap("myCache") .expireStoreSize(64*1024*1024)//指定堆外缓存大小64MB .expireMaxSize(10000) .expireAfterCreate(10,TimeUnit.SECONDS) .expireAfterUpdate(10,TimeUnit.SECONDS) .expireAfterGet(10,TimeUnit.SECONDS) .create(); 在使用堆外缓存时,请记得添加JVM启动参数,如-XX:MaxDirectMemorySize=10G。 3.3 磁盘缓存 3.3.1 EhCache 3.x实现 CacheManagercacheManager=CacheManagerBuilder.newCacheManagerBuilder() //默认线程池 .using(PooledExecutionServiceConfigurationBuilder.newPooledExecutionServiceConfigurationBuilder().defaultPool("default",1,10).build()) //磁盘文件存储位置 .with(newCacheManagerPersistenceConfiguration(newFile("D:\\bak"))) .build(true); CacheConfigurationBuildercacheConfig=CacheConfigurationBuilder.newCacheConfigurationBuilder( String.class, String.class, ResourcePoolsBuilder.newResourcePoolsBuilder() .disk(100,MemoryUnit.MB,true))//磁盘缓存 .withDiskStoreThreadPool("default",5)//使用"default"线程池进行dump文件到磁盘 .withExpiry(Expirations.timeToLiveExpiration(Duration.of(50,TimeUnit.SECONDS))) .withSizeOfMaxObjectGraph(3) .withSizeOfMaxObjectSize(1,MemoryUnit.KB); 在JVM停止时,记得调用cacheManager.close(),从而保证内存数据能dump到磁盘。 3.3.2 MapDB 3.x实现 DBdb=DBMaker .fileDB("D:\\bak\\a.data")//数据存哪里 .fileMmapEnable()//启用mmap .fileMmapEnableIfSupported()//在支持的平台上启用mmap .fileMmapPreclearDisable()//让mmap文件更快 .cleanerHackEnable()//一些BUG处理 .transactionEnable()//启用事务 .closeOnJvmShutdown() .concurrencyScale(16) .make(); HTreeMapmyCache=db.hashMap("myCache") .expireMaxSize(10000) .expireAfterCreate(10,TimeUnit.SECONDS) .expireAfterUpdate(10,TimeUnit.SECONDS) .expireAfterGet(10,TimeUnit.SECONDS) .createOrOpen(); 因为开启了事务,MapDB则开启了WAL。另外,操作完缓存后记得调用db.commit方法提交事务。 myCache.put("key"+counterWriter,"value"+counterWriter); db.commit(); 3.4 分布式缓存 3.4.1 Ehcache 3.1 + Terracotta Server 不建议使用。 3.4.2 Redis 性能非常好,有主从模式、集群模式。 3.5 多级缓存 如先查找堆缓存,如果没有查找磁盘缓存,则使用MapDB可以通过如下配置实现。 HTreeMapdiskCache=db.hashMap("myCache") .expireStoreSize(8*1024*1024*1024) .expireMaxSize(10000) .expireAfterCreate(10,TimeUnit.SECONDS) .expireAfterUpdate(10,TimeUnit.SECONDS) .expireAfterGet(10,TimeUnit.SECONDS) .createOrOpen(); HTreeMapheapCache=db.hashMap("myCache") .expireMaxSize(100) .expireAfterCreate(10,TimeUnit.SECONDS) .expireAfterUpdate(10,TimeUnit.SECONDS) .expireAfterGet(10,TimeUnit.SECONDS) .expireOverflow(diskCache)//当缓存溢出时存储到disk .createOrOpen(); 4 缓存使用模式 主要分两大类:Cache-Aside和Cache-As-SoR(Read-through、Write-through、Write-behind) SoR(system-of-record):记录系统,或者可以叫做数据源,即实际存储原始数据的系统。 Cache:缓存,是SoR的快照数据,Cache的访问速度比SoR要快,放入Cache的目的是提升访问速度,减少回源到SoR的次数。 回源:即回到数据源头获取数据,Cache没有命中时,需要从SoR读取数据,这叫做回源。 4.1 Cache-Aside Cache-Aside 即业务代码围绕着Cache写,是由业务代码直接维护缓存,示例代码如下所示。 4.1.1 读场景 先从缓存获取数据,如果没有命中,则回源到SoR并将源数据放入缓存供下次读取使用。 //1、先从缓存中获取数据 value=myCache.getIfPresent(key); if(value==null){ //2.1、如果缓存没有命中,则回源到SoR获取源数据 value=loadFromSoR(key); //2.2、将数据放入缓存,下次即可从缓存中获取数据 myCache.put(key,value); } 4.1.2 写场景 先将数据写入SoR,写入成功后立即将数据同步写入缓存。 //1、先将数据写入SoR writeToSoR(key,value); //2、执行成功后立即同步写入缓存 myCache.put(key,value); 或者先将数据写入SoR,写入成功后将缓存数据过期,下次读取时再加载缓存。 //1、先将数据写入SoR writeToSoR(key,value); //2、失效缓存,然后下次读时再加载缓存 myCache.invalidate(key); Cache-Aside适合使用AOP模式去实现 4.2 Cache-As-SoR Cache-As-SoR即把Cache看作为SoR,所有操作都是对Cache进行,然后Cache再委托给SoR进行真实的读/写。即业务代码中只看到Cache的操作,看不到关于SoR相关的代码。有三种实现:read-through、write-through、write-behind。 4.2.1 Read-Through Read-Through,业务代码首先调用Cache,如果Cache不命中由Cache回源到SoR,而不是业务代码(即由Cache读SoR)。使用Read-Through模式,需要配置一个CacheLoader组件用来回源到SoR加载源数据。Guava Cache和Ehcache 3.x都支持该模式。 4.2.1.1 Guava Cache实现 LoadingCachegetCache= CacheBuilder.newBuilder() .softValues() .maximumSize(5000).expireAfterWrite(2,TimeUnit.MINUTES) .build(newCacheLoader(){ @Override publicResultload(finalIntegersortId)throwsException{ returncategoryService.get(sortId); } }); 在build Cache时,传入一个CacheLoader用来加载缓存,操作流程如下: 应用业务代码直接调用getCache.get(sortId)。 首先查询Cache,如果缓存中有,则直接返回缓存数据。 如果缓存没有命中,则委托给CacheLoader,CacheLoader会回源到SoR查询源数据(返回值必须不为null,可以包装为Null对象),然后写入缓存。 使用CacheLoader后有几个好处: 应用业务代码更简洁了,不需要像Cache-Aside模式那样缓存查询代码和SoR代码交织在一起。如果缓存使用逻辑散落在多处,则使用这种方式很简单的消除了重复代码。 解决Dog-pile effect,即当某个缓存失效时,又有大量相同的请求没命中缓存,从而同时请求到后端,导致后端压力太大,此时限制一个请求去拿即可。 4.2.1.2 Ehcache 3.x实现 CacheManagercacheManager=CacheManagerBuilder.newCacheManagerBuilder().build(true); org.ehcache.CachemyCache=cacheManager.createCache("myCache", CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class,String.class, ResourcePoolsBuilder.newResourcePoolsBuilder().heap(100,MemoryUnit.MB)) .withDispatcherConcurrency(4) .withExpiry(Expirations.timeToLiveExpiration(Duration.of(10,TimeUnit.SECONDS))) .withLoaderWriter(newDefaultCacheLoaderWriter(){ @Override publicStringload(Stringkey)throwsException{ returnreadDB(key); } @Override publicMaploadAll(Iterablekeys)throwsBulkCacheLoadingException,Exception{ returnnull; } })); Ehcache 3.1没有自己去解决Dog-pile effect。 4.2.2 Write-Through Write-Through,称之为穿透写模式/直写模式,业务代码首先调用Cache写(新增/修改)数据,然后由Cache负责写缓存和写SoR,而不是业务代码。 使用Write-Through模式需要配置一个CacheWriter组件用来回写SoR。Guava Cache没有提供支持。Ehcache 3.x支持该模式。 Ehcache需要配置一个CacheLoaderWriter,CacheLoaderWriter知道如何去写SoR。当Cache需要写(新增/修改)数据时,首先调用CacheLoaderWriter来同步(立即)到SoR,成功后会更新缓存。 CacheManagercacheManager=CacheManagerBuilder.newCacheManagerBuilder().build(true); CachemyCache=cacheManager.createCache("myCache", CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class,String.class, ResourcePoolsBuilder.newResourcePoolsBuilder().heap(100,MemoryUnit.MB)) .withDispatcherConcurrency(4) .withExpiry(Expirations.timeToLiveExpiration(Duration.of(10,TimeUnit.SECONDS))) .withLoaderWriter(newDefaultCacheLoaderWriter(){ @Override publicvoidwrite(Stringkey,Stringvalue)throwsException{ //write } @Override publicvoidwriteAll(Iterableentries)throwsBulkCacheWritingException,Exception{ for(Objectentry:entries){ //batchwrite } } @Override publicvoiddelete(Stringkey)throwsException{ //delete } @Override publicvoiddeleteAll(Iterablekeys)throwsBulkCacheWritingException,Exception{ for(Objectkey:keys){ //batchdelete } } }).build()); Ehcache 3.x还是使用CacheLoaderWriter来实现,通过write(String key, String value)、writeAll(Iterable> entries)和delete(String key)、deleteAll(Iterable keys)分别来支持单个写、批量写和单个删除、批量删除操作。 操作流程如下:当我们调用myCache.put(“e”,”123”)或者myCache.putAll(map)时,写缓存。首先,Cache会将写操作立即委托给CacheLoaderWriter#write和#writeAll,然后由CacheLoaderWriter负责立即去写SoR。当写SoR成功后,再写入Cache。 4.2.3 Write-Behind Write-Behind,也叫Write-Back,称之为回写模式,不同于Write-Through是同步写SoR和Cache,Write-Behind是异步写。异步之后可以实现批量写、合并写、延时和限流。 4.2.3.1 异步写 略,可用EhCache实现 4.2.3.2 批量写 略,可用EhCache实现 4.2.4 Copy Pattern 有两种Copy Pattern, Copy-On-Read和Copy-On-Write。在Guava-Cache和EhCache中堆缓存都是基于引用的,这样如果哟人拿到缓存数据并修改了它,则可能发生不可预测的问题。Guava Cache没有提供支持,EhCache 3.x提供了支持。 publicinterfaceCopier{ TcopyForRead(Tobj);//Copy-On-Read,比如myCache.get() TcopyForWrite(Tobj);//Copy-On-Write,比如myCache.put() } 本文转载自http://blog.csdn.net/foreverling/article/details/78012205

优秀的个人博客,低调大师

JAVA高并发设计[转]

一、同步(Synchronous)和异步(Asynchronous) 同步和异步通常用来形容一次方法调用,同步方法,调用者必须等到方法调用返回后,才能继续后续的行为,异步方法调用会立即返回,调用者就可以继续后续的操作 二、并发和并行 并发和并行都可以表示两个或多个任务一起执行,但偏重点点不同,并发偏重于多个任务交替执行,而多个任务之间有可能还是串行的。而并行是真正意义上的“同时执行”。 三、阻塞(Blocking)和非阻塞(Non-Blocking) 一个线程占用了临界资源,那么其他所有需要这个资源的线程就必须在这个临界区中进行等待,等待会导致线程挂起,这种情况就是阻塞,非阻塞的意思与之相反。 四、线程的状态 线程的状态 1、线程的启动是调用start()方法,而不是run()方法。 2、线程的终止、不用stop()是因为stop()方法太过暴力,强行把执行到一半的线程终止,可能会引起数据不一致的问题,一般我们定义一个线程终止的方法,告知线程何时停止即可。 3、线程中断:线程中断并不会使线程立即退出,而是给线程发一个通知,告知目标线程,有人希望你退出,至于目标线程接到通知后如何处理,则完全由目标线程自行决定。与线程中断的有三个方法 Thread.interrupt(): // 中断线程 Thread.isInterrupted()://判断是否中断 Thread.Interrupted():// 判断是否中断,并清除当前中断状态 注:Thread.sleep()方法会抛出一个InterruptedException中断异常,这不是运行时异常,也就是说程序必须捕获并处理它。当线程在休眠时,如果被中断,这个异常会产生。 4、等待(wait)和通知(notify) 注:这两个方法是在Object类中的,意味着任何对象都可以调用这两个方法。 obj.wait()方法,线程会停止继续执行,转为等待状态,直到其他线程调用obj.notify()方法为止。调用object.wait()方法,就会进入object对象的等待队列,当调用object.notify()时,会从这个等待队列中,随机选择一个线程,并将其唤醒,这个选择是不公平的,完全是随机的。notifyAll()会唤醒等待队列里的所有线程,而不是随机选择一个线程。 5、挂起(suspend)和继续执行(resume)线程 suspend与resume是一组相反的操作,调用suspend方法后的线程,必须等到resume方法调用后,才能继续执行。 注:此方法已经被废弃,并不推荐使用,因为suspend()在导致线程暂停的同时,并不会去释放任何资源。此 时,若其他任何线程想要访问被它暂用的锁时,都会被牵连,导致无法正常继续运行。同时,若resume()方法在suspend()前就执行了,那么被suspend()方法挂起的线程,很难有机会被继续执行,更为严重的是,它所占用的锁不会被释放,可能导致整个系统工作不正常。同时,对于被挂起的线程,从线程状态上看,还是Runnable,会严重影响我们的判断. 原文链接:http://geek.csdn.net/news/detail/243219

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册